File Coverage

blib/lib/OpenGuides/CGI.pm
Criterion Covered Total %
statement 156 159 98.1
branch 57 68 83.8
condition 35 40 87.5
subroutine 17 17 100.0
pod 8 9 88.8
total 273 293 93.1


line stmt bran cond sub pod time code
1             package OpenGuides::CGI;
2 92     92   1687 use strict;
  92         140  
  92         3775  
3 92     92   431 use vars qw( $VERSION );
  92         141  
  92         4524  
4             $VERSION = '0.12';
5              
6 92     92   433 use Carp qw( croak );
  92         139  
  92         6178  
7 92     92   1275 use CGI;
  92         25170  
  92         1304  
8 92     92   55982 use CGI::Cookie;
  92         389300  
  92         175127  
9              
10             =head1 NAME
11              
12             OpenGuides::CGI - An OpenGuides helper for CGI-related things.
13              
14             =head1 DESCRIPTION
15              
16             Does CGI stuff for OpenGuides. Distributed and installed as part of
17             the OpenGuides project, not intended for independent installation.
18             This documentation is probably only useful to OpenGuides developers.
19              
20             =head1 SYNOPSIS
21              
22             Saving preferences in a cookie:
23              
24             use OpenGuides::CGI;
25             use OpenGuides::Config;
26             use OpenGuides::Template;
27             use OpenGuides::Utils;
28              
29             my $config = OpenGuides::Config->new( file => "wiki.conf" );
30              
31             my $cookie = OpenGuides::CGI->make_prefs_cookie(
32             config => $config,
33             username => "Kake",
34             include_geocache_link => 1,
35             preview_above_edit_box => 1,
36             latlong_traditional => 1,
37             omit_help_links => 1,
38             show_minor_edits_in_rc => 1,
39             default_edit_type => "tidying",
40             cookie_expires => "never",
41             track_recent_changes_views => 1,
42             display_google_maps => 1,
43             is_admin => 1
44             );
45              
46             my $wiki = OpenGuides::Utils->make_wiki_object( config => $config );
47             print OpenGuides::Template->output( wiki => $wiki,
48             config => $config,
49             template => "preferences.tt",
50             cookies => $cookie
51             );
52              
53             # and to retrive prefs later:
54             my %prefs = OpenGuides::CGI->get_prefs_from_cookie(
55             config => $config
56             );
57              
58             Tracking visits to Recent Changes:
59              
60             use OpenGuides::CGI;
61             use OpenGuides::Config;
62             use OpenGuides::Template;
63             use OpenGuides::Utils;
64              
65             my $config = OpenGuides::Config->new( file => "wiki.conf" );
66              
67             my $cookie = OpenGuides::CGI->make_recent_changes_cookie(
68             config => $config,
69             );
70              
71             =head1 METHODS
72              
73             =over 4
74              
75             =item B
76              
77             my $config_file = $ENV{OPENGUIDES_CONFIG_FILE} || "wiki.conf";
78             my $config = OpenGuides::Config->new( file => $config_file );
79             my $guide = OpenGuides->new( config => $config );
80             my $wiki = $guide->wiki;
81              
82             my $q = CGI->new;
83              
84             my $node_param = OpenGuides::CGI->extract_node_param(
85             wiki => $wiki, cgi_obj => $q );
86              
87             Returns the title, id, or keywords parameter from the URL. Normally
88             this will be something like "British_Museum", i.e. with underscores
89             instead of spaces. However if the URL does contain spaces (encoded as
90             %20 or +), the return value will be e.g. "British Museum" instead.
91              
92             Croaks unless a L object is supplied as C and a
93             L object is supplied as C.
94              
95             =cut
96              
97             sub extract_node_param {
98 52     52 1 4547 my ($class, %args) = @_;
99 52 50       159 my $wiki = $args{wiki} or croak "No wiki supplied";
100 52 50       368 croak "wiki not a Wiki::Toolkit object"
101             unless UNIVERSAL::isa( $wiki, "Wiki::Toolkit" );
102 52 50       216 my $q = $args{cgi_obj} or croak "No cgi_obj supplied";
103 52 50       161 croak "cgi_obj not a CGI object"
104             unless UNIVERSAL::isa( $q, "CGI" );
105              
106             # Note $q->param( "keywords" ) gives you the entire param string.
107             # We need this to do URLs like foo.com/wiki.cgi?This_Page
108 52   50     144 my $param = $q->param( "id" )
109             || $q->param( "title" )
110             || join( " ", $q->multi_param( "keywords" ) )
111             || "";
112 52         2549 $param =~ s/%20/ /g;
113 52         196 $param =~ s/\+/ /g;
114 52         156 return $param;
115             }
116              
117             =item B
118              
119             my $config_file = $ENV{OPENGUIDES_CONFIG_FILE} || "wiki.conf";
120             my $config = OpenGuides::Config->new( file => $config_file );
121             my $guide = OpenGuides->new( config => $config );
122             my $wiki = $guide->wiki;
123              
124             my $q = CGI->new;
125              
126             my $node_name = OpenGuides::CGI->extract_node_name(
127             wiki => $wiki, cgi_obj => $q );
128              
129             Returns the name of the node the user wishes to display/manipulate, as
130             we expect it to be stored in the database. Normally this will be
131             something like "British Museum", i.e. with spaces in. Croaks unless a
132             L object is supplied as C and a L object is
133             supplied as C.
134              
135             =cut
136              
137             sub extract_node_name {
138 26     26 1 5959 my ($class, %args) = @_;
139             # The next call will validate our args for us and croak if necessary.
140 26         99 my $param = $class->extract_node_param( %args );
141              
142             # Sometimes people type spaces instead of underscores.
143 26         85 $param =~ s/ /_/g;
144 26         51 $param =~ s/%20/_/g;
145 26         45 $param =~ s/\+/_/g;
146              
147 26         149 my $formatter = $args{wiki}->formatter;
148 26         189 return $formatter->node_param_to_node_name( $param );
149             }
150              
151             =item B
152              
153             my $config_file = $ENV{OPENGUIDES_CONFIG_FILE} || "wiki.conf";
154             my $config = OpenGuides::Config->new( file => $config_file );
155             my $guide = OpenGuides->new( config => $config );
156              
157             my $q = CGI->new;
158              
159             my $url = OpenGuides::CGI->check_spaces_redirect(
160             wiki => $wiki, cgi_obj => $q );
161              
162             If the user seems to have typed a URL with spaces in the node param
163             instead of underscores, this method will return the URL with the
164             underscores put in. Otherwise, it returns false.
165              
166             =cut
167              
168             sub check_spaces_redirect {
169 17     17 1 22703 my ($class, %args) = @_;
170 17         42 my $wiki = $args{wiki};
171 17         36 my $q = $args{cgi_obj};
172              
173 17         63 my $name = $class->extract_node_name( wiki => $wiki, cgi_obj => $q );
174 17         242 my $param = $class->extract_node_param( wiki => $wiki, cgi_obj => $q );
175              
176             # If we can't figure out the name or param, it's safest to do nothing.
177 17 50 33     145 if ( !$name || !$param ) {
178 0         0 return 0;
179             }
180              
181             # If the name has no spaces in, or the name and param differ, we're
182             # probably OK.
183 17 100 100     121 if ( ( $name !~ / / ) || ( $name ne $param ) ) {
184 6         22 return 0;
185             }
186              
187             # Make a new CGI object to manipulate, to avoid action-at-a-distance.
188 11         55 my $new_q = CGI->new( $q );
189 11         6171 my $formatter = $wiki->formatter;
190 11         85 my $real_param = $formatter->node_name_to_node_param( $name );
191              
192 11 100       562 if ( $q->param( "id" ) ) {
    100          
193 4         115 $new_q->param( -name => "id", -value => $real_param );
194             } elsif ( $q->param( "title" ) ) {
195 4         173 $new_q->param( -name => "title", -value => $real_param );
196             } else {
197             # OK, we have the keywords case; the entire param string is the
198             # node param. So just delete all existing parameters and stick
199             # the node param back in.
200 3         104 $new_q->delete_all();
201 3         277 $new_q->param( -name => "id", -value => $real_param );
202             }
203              
204 11         1308 my $url = $new_q->self_url;
205              
206             # Escaped commas are ugly.
207 11         6341 $url =~ s/%2C/,/g;
208 11         114 return $url;
209             }
210              
211             =item B
212              
213             my $cookie = OpenGuides::CGI->make_prefs_cookie(
214             config => $config,
215             username => "Kake",
216             include_geocache_link => 1,
217             preview_above_edit_box => 1,
218             latlong_traditional => 1,
219             omit_help_links => 1,
220             show_minor_edits_in_rc => 1,
221             default_edit_type => "tidying",
222             cookie_expires => "never",
223             track_recent_changes_views => 1,
224             display_google_maps => 1,
225             is_admin => 1
226             );
227              
228             Croaks unless an L object is supplied as C.
229             Acceptable values for C are C, C,
230             C; anything else will default to C.
231              
232             =cut
233              
234             sub make_prefs_cookie {
235 47     47 1 160087 my ($class, %args) = @_;
236 47 100       422 my $config = $args{config} or croak "No config object supplied";
237 46 100       392 croak "Config object not an OpenGuides::Config"
238             unless UNIVERSAL::isa( $config, "OpenGuides::Config" );
239 45         219 my $cookie_name = $class->_get_cookie_name( config => $config );
240 45         221 my $expires;
241 45 100 100     390 if ( $args{cookie_expires} and $args{cookie_expires} eq "never" ) {
    100 100        
242             # Gosh, a hack. YES I AM ASHAMED OF MYSELF.
243             # Putting no expiry date means cookie expires when browser closes.
244             # Putting a date later than 2037 makes it wrap round, at least on Linux
245             # I will only be 62 by the time I need to redo this hack, so I should
246             # still be alive to fix it.
247 13         29 $expires = "Thu, 31-Dec-2037 22:22:22 GMT";
248             } elsif ( $args{cookie_expires} and $args{cookie_expires} eq "year" ) {
249 1         2 $expires = "+1y";
250             } else {
251 31         85 $args{cookie_expires} = "month";
252 31         134 $expires = "+1M";
253             }
254             # Supply 'default' values to stop CGI::Cookie complaining about
255             # uninitialised values. *Real* default should be applied before
256             # calling this method.
257 45   100     2278 my $cookie = CGI::Cookie->new(
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
258             -name => $cookie_name,
259             -value => { user => $args{username} || "",
260             gclink => $args{include_geocache_link} || 0,
261             prevab => $args{preview_above_edit_box} || 0,
262             lltrad => $args{latlong_traditional} || 0,
263             omithlplks => $args{omit_help_links} || 0,
264             rcmined => $args{show_minor_edits_in_rc} || 0,
265             defedit => $args{default_edit_type} || "normal",
266             exp => $args{cookie_expires},
267             trackrc => $args{track_recent_changes_views} || 0,
268             gmaps => $args{display_google_maps} || 0,
269             admin => $args{is_admin} || 0
270             },
271             -expires => $expires,
272             );
273 45         11197 return $cookie;
274             }
275              
276             =item B
277              
278             my %prefs = OpenGuides::CGI->get_prefs_from_cookie(
279             config => $config,
280             cookies => \@cookies
281             );
282              
283             Croaks unless an L object is supplied as C.
284             Returns default values for any parameter not specified in cookie.
285              
286             If C is provided, and includes a preferences cookie, this overrides
287             any preferences cookie submitted by the browser.
288              
289             =cut
290              
291             sub get_prefs_from_cookie {
292 1412     1412 1 8440 my ($class, %args) = @_;
293 1412 100       4698 my $config = $args{config} or croak "No config object supplied";
294 1411 100       7213 croak "Config object not an OpenGuides::Config"
295             unless UNIVERSAL::isa( $config, "OpenGuides::Config" );
296 1410         4527 my $cookie_name = $class->_get_cookie_name( config => $config );
297 1410         2006 my %cookies;
298 1410 100       3854 if ( my $cookies = $args{cookies} ) {
299 11 100       1567 if (ref $cookies ne 'ARRAY') {
300 10         32 $cookies = [ $cookies ];
301             }
302 11         28 %cookies = map { $_->name => $_ } @{ $cookies };
  11         40  
  11         38  
303             }
304 1410 50       4004 if ( !$cookies{$cookie_name} ) {
305 1410         8497 my %stored_cookies = CGI::Cookie->fetch;
306 1410         161023 $cookies{$cookie_name} = $stored_cookies{$cookie_name};
307             }
308 1410         1956 my %data;
309 1410 100       3972 if ( $cookies{$cookie_name} ) {
310 224         83423 %data = $cookies{$cookie_name}->value; # call ->value in list context
311             }
312              
313 1410         15763 my %long_forms = (
314             user => "username",
315             gclink => "include_geocache_link",
316             prevab => "preview_above_edit_box",
317             lltrad => "latlong_traditional",
318             omithlplks => "omit_help_links",
319             rcmined => "show_minor_edits_in_rc",
320             defedit => "default_edit_type",
321             exp => "cookie_expires",
322             trackrc => "track_recent_changes_views",
323             gmaps => "display_google_maps",
324             admin => "is_admin",
325             );
326 1410         4849 my %long_data = map { $long_forms{$_} => $data{$_} } keys %long_forms;
  15510         29446  
327              
328 1410         7842 return $class->get_prefs_from_hash( %long_data );
329             }
330              
331             sub get_prefs_from_hash {
332 1410     1410 0 5439 my ($class, %data) = @_;
333 1410         8509 my %defaults = (
334             username => "Anonymous",
335             include_geocache_link => 0,
336             preview_above_edit_box => 0,
337             latlong_traditional => 0,
338             omit_help_links => 0,
339             # This has been set to 1 to work around
340             # Wiki::Toolkit bug #41 - consider reverting this
341             # when that bug gets fixed
342             show_minor_edits_in_rc => 1,
343             default_edit_type => "normal",
344             cookie_expires => "never",
345             track_recent_changes_views => 0,
346             display_google_maps => 1,
347             is_admin => 0,
348             );
349 1410         1699 my %return;
350 1410         4849 foreach my $key ( keys %data ) {
351 15510 100       30488 $return{$key} = defined $data{$key} ? $data{$key} : $defaults{$key};
352             }
353              
354 1410         22099 return %return;
355             }
356              
357              
358             =item B
359              
360             my $cookie = OpenGuides::CGI->make_recent_changes_cookie(
361             config => $config,
362             );
363              
364             Makes a cookie that stores the time now as the time of the latest
365             visit to Recent Changes. Or, if C is specified and
366             true, makes a cookie with an expiration date in the past:
367              
368             my $cookie = OpenGuides::CGI->make_recent_changes_cookie(
369             config => $config,
370             clear_cookie => 1,
371             );
372              
373             =cut
374              
375             sub make_recent_changes_cookie {
376 19     19 1 1612 my ($class, %args) = @_;
377 19 100       292 my $config = $args{config} or croak "No config object supplied";
378 18 100       194 croak "Config object not an OpenGuides::Config"
379             unless UNIVERSAL::isa( $config, "OpenGuides::Config" );
380 17         75 my $cookie_name = $class->_get_rc_cookie_name( config => $config );
381             # See explanation of expiry date hack above in make_prefs_cookie.
382 17         29 my $expires;
383 17 50       66 if ( $args{clear_cookie} ) {
384 0         0 $expires = "-1M";
385             } else {
386 17         40 $expires = "Thu, 31-Dec-2037 22:22:22 GMT";
387             }
388 17         161 my $cookie = CGI::Cookie->new(
389             -name => $cookie_name,
390             -value => {
391             time => time,
392             },
393             -expires => $expires,
394             );
395 17         2834 return $cookie;
396             }
397              
398              
399             =item B
400              
401             my %prefs = OpenGuides::CGI->get_last_recent_changes_visit_from_cookie(
402             config => $config
403             );
404              
405             Croaks unless an L object is supplied as C.
406             Returns the time (as seconds since epoch) of the user's last visit to
407             Recent Changes.
408              
409             =cut
410              
411             sub get_last_recent_changes_visit_from_cookie {
412 13     13 1 1806 my ($class, %args) = @_;
413 13 100       137 my $config = $args{config} or croak "No config object supplied";
414 12 100       143 croak "Config object not an OpenGuides::Config"
415             unless UNIVERSAL::isa( $config, "OpenGuides::Config" );
416 11         47 my %cookies = CGI::Cookie->fetch;
417 11         5528 my $cookie_name = $class->_get_rc_cookie_name( config => $config );
418 11         24 my %data;
419 11 100       42 if ( $cookies{$cookie_name} ) {
420 6         713 %data = $cookies{$cookie_name}->value; # call ->value in list context
421             }
422 11         140 return $data{time};
423             }
424              
425              
426             sub _get_cookie_name {
427 1455     1455   3143 my ($class, %args) = @_;
428 1455 50       5532 my $site_name = $args{config}->site_name
429             or croak "No site name in config";
430 1455         18055 return $site_name . "_userprefs";
431             }
432              
433             sub _get_rc_cookie_name {
434 28     28   70 my ($class, %args) = @_;
435 28 50       110 my $site_name = $args{config}->site_name
436             or croak "No site name in config";
437 28         370 return $site_name . "_last_rc_visit";
438             }
439              
440             =item B
441              
442             my @dropdowns = OpenGuides::CGI->make_index_form_dropdowns (
443             guide => $guide,
444             selected => [
445             { type => "category", value => "pubs" },
446             { type => "locale", value => "holborn" },
447             ],
448             );
449             %tt_vars = ( %tt_vars, dropdowns => \@dropdowns );
450              
451             # In the template
452             [% FOREACH dropdown = dropdowns %]
453             [% dropdown.type.ucfirst | html %]:
454             [% dropdown.html %]
455            
456             [% END %]
457              
458             Makes HTML dropdown selects suitable for passing to an indexing template.
459              
460             The C argument is optional; if supplied, it gives default values
461             for the dropdowns. At least one category and one locale dropdown will be
462             returned; if no defaults are given for either then they'll default to
463             everything/everywhere.
464              
465             =cut
466              
467             sub make_index_form_dropdowns {
468 37     37 1 13561 my ( $class, %args ) = @_;
469 37 100       59 my @selected = @{$args{selected} || [] };
  37         227  
470 37         78 my $guide = $args{guide};
471 37         69 my @dropdowns;
472 37         53 my ( $got_cat, $got_loc );
473 37         309 foreach my $criterion ( @selected ) {
474 44   50     152 my $type = $criterion->{type} || "";
475 44   50     128 my $value = $criterion->{value} || "";
476 44         55 my $html;
477 44 100       180 if ( $type eq "category" ) {
    50          
478 19         122 $html = $class->_make_dropdown_html(
479             %$criterion, guide => $guide );
480 19         11814 $got_cat = 1;
481             } elsif ( $type eq "locale" ) {
482 25         132 $html = $class->_make_dropdown_html(
483             %$criterion, guide => $guide );
484 25         16238 $got_loc = 1;
485             } else {
486 0         0 warn "Unknown or missing criterion type: $type";
487             }
488 44 50       152 if ( $html ) {
489 44         209 push @dropdowns, { type => $type, html => $html };
490             }
491             }
492 37 100       115 if ( !$got_cat ) {
493 18         147 push @dropdowns, { type => "category", html =>
494             $class->_make_dropdown_html( type => "category", guide => $guide )
495             };
496             }
497 37 100       8655 if ( !$got_loc ) {
498 12         60 push @dropdowns, { type => "locale", html =>
499             $class->_make_dropdown_html( type => "locale", guide => $guide )
500             };
501             }
502             # List the category dropdowns before the locale dropdowns, for consistency.
503 37         7484 @dropdowns = sort { $a->{type} cmp $b->{type} } @dropdowns;
  37         140  
504 37         190 return @dropdowns;
505             }
506              
507             sub _make_dropdown_html {
508 74     74   261 my ( $class, %args ) = @_;
509 74         90 my ( $field_name, $any_label );
510              
511 74 100       182 if ( $args{type} eq "locale" ) {
512 37         78 $args{type} = "locales"; # hysterical raisins
513 37         60 $any_label = " -- anywhere -- ";
514 37         55 $field_name = "loc";
515             } else {
516 37         66 $any_label = " -- anything -- ";
517 37         57 $field_name = "cat";
518             }
519              
520 74         274 my @options = $args{guide}->wiki->list_nodes_by_metadata(
521             metadata_type => "category",
522             metadata_value => $args{type},
523             ignore_case => 1,
524             );
525 74         24679 @options = map { s/^Category //; s/^Locale //; $_ } @options;
  118         289  
  118         293  
  118         264  
526 74         134 my %labels = map { lc( $_ ) => $_ } @options;
  118         348  
527 74         321 my @values = sort keys %labels;
528 74   100     369 my $default = lc( $args{value} || "");
529              
530 74         379 my $q = CGI->new( "" );
531 74         14200 return $q->popup_menu( -name => $field_name,
532             -values => [ "", @values ],
533             -labels => { "" => $any_label, %labels },
534             -default => $default );
535             }
536              
537             =back
538              
539             =head1 AUTHOR
540              
541             The OpenGuides Project (openguides-dev@lists.openguides.org)
542              
543             =head1 COPYRIGHT
544              
545             Copyright (C) 2003-2013 The OpenGuides Project. All Rights Reserved.
546              
547             This module is free software; you can redistribute it and/or modify it
548             under the same terms as Perl itself.
549              
550             =cut
551              
552             1;
553