package OpenGuides::CGI; use strict; use vars qw( $VERSION ); $VERSION = '0.11'; use Carp qw( croak ); use CGI; use CGI::Cookie; =head1 NAME OpenGuides::CGI - An OpenGuides helper for CGI-related things. =head1 DESCRIPTION Does CGI stuff for OpenGuides. Distributed and installed as part of the OpenGuides project, not intended for independent installation. This documentation is probably only useful to OpenGuides developers. =head1 SYNOPSIS Saving preferences in a cookie: use OpenGuides::CGI; use OpenGuides::Config; use OpenGuides::Template; use OpenGuides::Utils; my $config = OpenGuides::Config->new( file => "wiki.conf" ); my $cookie = OpenGuides::CGI->make_prefs_cookie( config => $config, username => "Kake", include_geocache_link => 1, preview_above_edit_box => 1, latlong_traditional => 1, omit_help_links => 1, show_minor_edits_in_rc => 1, default_edit_type => "tidying", cookie_expires => "never", track_recent_changes_views => 1, display_google_maps => 1, is_admin => 1 ); my $wiki = OpenGuides::Utils->make_wiki_object( config => $config ); print OpenGuides::Template->output( wiki => $wiki, config => $config, template => "preferences.tt", cookies => $cookie ); # and to retrive prefs later: my %prefs = OpenGuides::CGI->get_prefs_from_cookie( config => $config ); Tracking visits to Recent Changes: use OpenGuides::CGI; use OpenGuides::Config; use OpenGuides::Template; use OpenGuides::Utils; my $config = OpenGuides::Config->new( file => "wiki.conf" ); my $cookie = OpenGuides::CGI->make_recent_changes_cookie( config => $config, ); =head1 METHODS =over 4 =item B my $config_file = $ENV{OPENGUIDES_CONFIG_FILE} || "wiki.conf"; my $config = OpenGuides::Config->new( file => $config_file ); my $guide = OpenGuides->new( config => $config ); my $wiki = $guide->wiki; my $q = CGI->new; my $node_param = OpenGuides::CGI->extract_node_param( wiki => $wiki, cgi_obj => $q ); Returns the title, id, or keywords parameter from the URL. Normally this will be something like "British_Museum", i.e. with underscores instead of spaces. However if the URL does contain spaces (encoded as %20 or +), the return value will be e.g. "British Museum" instead. Croaks unless a L object is supplied as C and a L object is supplied as C. =cut sub extract_node_param { my ($class, %args) = @_; my $wiki = $args{wiki} or croak "No wiki supplied"; croak "wiki not a Wiki::Toolkit object" unless UNIVERSAL::isa( $wiki, "Wiki::Toolkit" ); my $q = $args{cgi_obj} or croak "No cgi_obj supplied"; croak "cgi_obj not a CGI object" unless UNIVERSAL::isa( $q, "CGI" ); # Note $q->param( "keywords" ) gives you the entire param string. # We need this to do URLs like foo.com/wiki.cgi?This_Page my $param = $q->param( "id" ) || $q->param( "title" ) || join( " ", $q->param( "keywords" ) ) || ""; $param =~ s/%20/ /g; $param =~ s/\+/ /g; return $param; } =item B my $config_file = $ENV{OPENGUIDES_CONFIG_FILE} || "wiki.conf"; my $config = OpenGuides::Config->new( file => $config_file ); my $guide = OpenGuides->new( config => $config ); my $wiki = $guide->wiki; my $q = CGI->new; my $node_name = OpenGuides::CGI->extract_node_name( wiki => $wiki, cgi_obj => $q ); Returns the name of the node the user wishes to display/manipulate, as we expect it to be stored in the database. Normally this will be something like "British Museum", i.e. with spaces in. Croaks unless a L object is supplied as C and a L object is supplied as C. =cut sub extract_node_name { my ($class, %args) = @_; # The next call will validate our args for us and croak if necessary. my $param = $class->extract_node_param( %args ); # Sometimes people type spaces instead of underscores. $param =~ s/ /_/g; $param =~ s/%20/_/g; $param =~ s/\+/_/g; my $formatter = $args{wiki}->formatter; return $formatter->node_param_to_node_name( $param ); } =item B my $config_file = $ENV{OPENGUIDES_CONFIG_FILE} || "wiki.conf"; my $config = OpenGuides::Config->new( file => $config_file ); my $guide = OpenGuides->new( config => $config ); my $q = CGI->new; my $url = OpenGuides::CGI->check_spaces_redirect( wiki => $wiki, cgi_obj => $q ); If the user seems to have typed a URL with spaces in the node param instead of underscores, this method will return the URL with the underscores put in. Otherwise, it returns false. =cut sub check_spaces_redirect { my ($class, %args) = @_; my $wiki = $args{wiki}; my $q = $args{cgi_obj}; my $name = $class->extract_node_name( wiki => $wiki, cgi_obj => $q ); my $param = $class->extract_node_param( wiki => $wiki, cgi_obj => $q ); # If we can't figure out the name or param, it's safest to do nothing. if ( !$name || !$param ) { return 0; } # If the name has no spaces in, or the name and param differ, we're # probably OK. if ( ( $name !~ / / ) || ( $name ne $param ) ) { return 0; } # Make a new CGI object to manipulate, to avoid action-at-a-distance. my $new_q = CGI->new( $q ); my $formatter = $wiki->formatter; my $real_param = $formatter->node_name_to_node_param( $name ); if ( $q->param( "id" ) ) { $new_q->param( -name => "id", -value => $real_param ); } elsif ( $q->param( "title" ) ) { $new_q->param( -name => "title", -value => $real_param ); } else { # OK, we have the keywords case; the entire param string is the # node param. So just delete all existing parameters and stick # the node param back in. $new_q->delete_all(); $new_q->param( -name => "id", -value => $real_param ); } my $url = $new_q->self_url; # Escaped commas are ugly. $url =~ s/%2C/,/g; return $url; } =item B my $cookie = OpenGuides::CGI->make_prefs_cookie( config => $config, username => "Kake", include_geocache_link => 1, preview_above_edit_box => 1, latlong_traditional => 1, omit_help_links => 1, show_minor_edits_in_rc => 1, default_edit_type => "tidying", cookie_expires => "never", track_recent_changes_views => 1, display_google_maps => 1, is_admin => 1 ); Croaks unless an L object is supplied as C. Acceptable values for C are C, C, C; anything else will default to C. =cut sub make_prefs_cookie { my ($class, %args) = @_; my $config = $args{config} or croak "No config object supplied"; croak "Config object not an OpenGuides::Config" unless UNIVERSAL::isa( $config, "OpenGuides::Config" ); my $cookie_name = $class->_get_cookie_name( config => $config ); my $expires; if ( $args{cookie_expires} and $args{cookie_expires} eq "never" ) { # Gosh, a hack. YES I AM ASHAMED OF MYSELF. # Putting no expiry date means cookie expires when browser closes. # Putting a date later than 2037 makes it wrap round, at least on Linux # I will only be 62 by the time I need to redo this hack, so I should # still be alive to fix it. $expires = "Thu, 31-Dec-2037 22:22:22 GMT"; } elsif ( $args{cookie_expires} and $args{cookie_expires} eq "year" ) { $expires = "+1y"; } else { $args{cookie_expires} = "month"; $expires = "+1M"; } # Supply 'default' values to stop CGI::Cookie complaining about # uninitialised values. *Real* default should be applied before # calling this method. my $cookie = CGI::Cookie->new( -name => $cookie_name, -value => { user => $args{username} || "", gclink => $args{include_geocache_link} || 0, prevab => $args{preview_above_edit_box} || 0, lltrad => $args{latlong_traditional} || 0, omithlplks => $args{omit_help_links} || 0, rcmined => $args{show_minor_edits_in_rc} || 0, defedit => $args{default_edit_type} || "normal", exp => $args{cookie_expires}, trackrc => $args{track_recent_changes_views} || 0, gmaps => $args{display_google_maps} || 0, admin => $args{is_admin} || 0 }, -expires => $expires, ); return $cookie; } =item B my %prefs = OpenGuides::CGI->get_prefs_from_cookie( config => $config, cookies => \@cookies ); Croaks unless an L object is supplied as C. Returns default values for any parameter not specified in cookie. If C is provided, and includes a preferences cookie, this overrides any preferences cookie submitted by the browser. =cut sub get_prefs_from_cookie { my ($class, %args) = @_; my $config = $args{config} or croak "No config object supplied"; croak "Config object not an OpenGuides::Config" unless UNIVERSAL::isa( $config, "OpenGuides::Config" ); my $cookie_name = $class->_get_cookie_name( config => $config ); my %cookies; if ( my $cookies = $args{cookies} ) { if (ref $cookies ne 'ARRAY') { $cookies = [ $cookies ]; } %cookies = map { $_->name => $_ } @{ $cookies }; } if ( !$cookies{$cookie_name} ) { my %stored_cookies = CGI::Cookie->fetch; $cookies{$cookie_name} = $stored_cookies{$cookie_name}; } my %data; if ( $cookies{$cookie_name} ) { %data = $cookies{$cookie_name}->value; # call ->value in list context } my %long_forms = ( user => "username", gclink => "include_geocache_link", prevab => "preview_above_edit_box", lltrad => "latlong_traditional", omithlplks => "omit_help_links", rcmined => "show_minor_edits_in_rc", defedit => "default_edit_type", exp => "cookie_expires", trackrc => "track_recent_changes_views", gmaps => "display_google_maps", admin => "is_admin", ); my %long_data = map { $long_forms{$_} => $data{$_} } keys %long_forms; return $class->get_prefs_from_hash( %long_data ); } sub get_prefs_from_hash { my ($class, %data) = @_; my %defaults = ( username => "Anonymous", include_geocache_link => 0, preview_above_edit_box => 0, latlong_traditional => 0, omit_help_links => 0, # This has been set to 1 to work around # Wiki::Toolkit bug #41 - consider reverting this # when that bug gets fixed show_minor_edits_in_rc => 1, default_edit_type => "normal", cookie_expires => "never", track_recent_changes_views => 0, display_google_maps => 1, is_admin => 0, ); my %return; foreach my $key ( keys %data ) { $return{$key} = defined $data{$key} ? $data{$key} : $defaults{$key}; } return %return; } =item B my $cookie = OpenGuides::CGI->make_recent_changes_cookie( config => $config, ); Makes a cookie that stores the time now as the time of the latest visit to Recent Changes. Or, if C is specified and true, makes a cookie with an expiration date in the past: my $cookie = OpenGuides::CGI->make_recent_changes_cookie( config => $config, clear_cookie => 1, ); =cut sub make_recent_changes_cookie { my ($class, %args) = @_; my $config = $args{config} or croak "No config object supplied"; croak "Config object not an OpenGuides::Config" unless UNIVERSAL::isa( $config, "OpenGuides::Config" ); my $cookie_name = $class->_get_rc_cookie_name( config => $config ); # See explanation of expiry date hack above in make_prefs_cookie. my $expires; if ( $args{clear_cookie} ) { $expires = "-1M"; } else { $expires = "Thu, 31-Dec-2037 22:22:22 GMT"; } my $cookie = CGI::Cookie->new( -name => $cookie_name, -value => { time => time, }, -expires => $expires, ); return $cookie; } =item B my %prefs = OpenGuides::CGI->get_last_recent_changes_visit_from_cookie( config => $config ); Croaks unless an L object is supplied as C. Returns the time (as seconds since epoch) of the user's last visit to Recent Changes. =cut sub get_last_recent_changes_visit_from_cookie { my ($class, %args) = @_; my $config = $args{config} or croak "No config object supplied"; croak "Config object not an OpenGuides::Config" unless UNIVERSAL::isa( $config, "OpenGuides::Config" ); my %cookies = CGI::Cookie->fetch; my $cookie_name = $class->_get_rc_cookie_name( config => $config ); my %data; if ( $cookies{$cookie_name} ) { %data = $cookies{$cookie_name}->value; # call ->value in list context } return $data{time}; } sub _get_cookie_name { my ($class, %args) = @_; my $site_name = $args{config}->site_name or croak "No site name in config"; return $site_name . "_userprefs"; } sub _get_rc_cookie_name { my ($class, %args) = @_; my $site_name = $args{config}->site_name or croak "No site name in config"; return $site_name . "_last_rc_visit"; } =item B my @dropdowns = OpenGuides::CGI->make_index_form_dropdowns ( guide => $guide, selected => [ { type => "category", value => "pubs" }, { type => "locale", value => "holborn" }, ], ); %tt_vars = ( %tt_vars, dropdowns => \@dropdowns ); # In the template [% FOREACH dropdown = dropdowns %] [% dropdown.type.ucfirst | html %]: [% dropdown.html %]
[% END %] Makes HTML dropdown selects suitable for passing to an indexing template. The C argument is optional; if supplied, it gives default values for the dropdowns. At least one category and one locale dropdown will be returned; if no defaults are given for either then they'll default to everything/everywhere. =cut sub make_index_form_dropdowns { my ( $class, %args ) = @_; my @selected = @{$args{selected} || [] }; my $guide = $args{guide}; my @dropdowns; my ( $got_cat, $got_loc ); foreach my $criterion ( @selected ) { my $type = $criterion->{type} || ""; my $value = $criterion->{value} || ""; my $html; if ( $type eq "category" ) { $html = $class->_make_dropdown_html( %$criterion, guide => $guide ); $got_cat = 1; } elsif ( $type eq "locale" ) { $html = $class->_make_dropdown_html( %$criterion, guide => $guide ); $got_loc = 1; } else { warn "Unknown or missing criterion type: $type"; } if ( $html ) { push @dropdowns, { type => $type, html => $html }; } } if ( !$got_cat ) { push @dropdowns, { type => "category", html => $class->_make_dropdown_html( type => "category", guide => $guide ) }; } if ( !$got_loc ) { push @dropdowns, { type => "locale", html => $class->_make_dropdown_html( type => "locale", guide => $guide ) }; } # List the category dropdowns before the locale dropdowns, for consistency. @dropdowns = sort { $a->{type} cmp $b->{type} } @dropdowns; return @dropdowns; } sub _make_dropdown_html { my ( $class, %args ) = @_; my ( $field_name, $any_label ); if ( $args{type} eq "locale" ) { $args{type} = "locales"; # hysterical raisins $any_label = " -- anywhere -- "; $field_name = "loc"; } else { $any_label = " -- anything -- "; $field_name = "cat"; } my @options = $args{guide}->wiki->list_nodes_by_metadata( metadata_type => "category", metadata_value => $args{type}, ignore_case => 1, ); @options = map { s/^Category //; s/^Locale //; $_ } @options; my %labels = map { lc( $_ ) => $_ } @options; my @values = sort keys %labels; my $default = lc( $args{value} || ""); my $q = CGI->new( "" ); return $q->popup_menu( -name => $field_name, -values => [ "", @values ], -labels => { "" => $any_label, %labels }, -default => $default ); } =back =head1 AUTHOR The OpenGuides Project (openguides-dev@lists.openguides.org) =head1 COPYRIGHT Copyright (C) 2003-2012 The OpenGuides Project. All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1;