File Coverage

blib/lib/OpenGuides/Utils.pm
Criterion Covered Total %
statement 222 250 88.8
branch 87 118 73.7
condition 22 35 62.8
subroutine 31 33 93.9
pod 8 12 66.6
total 370 448 82.5


line stmt bran cond sub pod time code
1             package OpenGuides::Utils;
2              
3 94     94   1809 use strict;
  94         156  
  94         4574  
4 94     94   469 use vars qw( $VERSION );
  94         148  
  94         5515  
5             $VERSION = '0.19';
6              
7 94     94   491 use Carp qw( croak );
  94         147  
  94         5316  
8 94     94   66308 use Wiki::Toolkit;
  94         1264328  
  94         3379  
9 94     94   64121 use Wiki::Toolkit::Formatter::UseMod;
  94         2566686  
  94         4068  
10 94     94   908 use URI::Escape;
  94         145  
  94         5765  
11 94     94   88037 use MIME::Lite;
  94         3119188  
  94         3886  
12 94     94   51817 use Net::Netmask;
  94         516633  
  94         10538  
13 94     94   891 use List::Util qw( first );
  94         169  
  94         6337  
14 94     94   11541 use Data::Validate::URI qw( is_web_uri );
  94         913859  
  94         336057  
15              
16             =head1 NAME
17              
18             OpenGuides::Utils - General utility methods for OpenGuides scripts.
19              
20             =head1 DESCRIPTION
21              
22             Provides general utility methods for OpenGuides scripts. Distributed
23             and installed as part of the OpenGuides project, not intended for
24             independent installation. This documentation is probably only useful
25             to OpenGuides developers.
26              
27             =head1 SYNOPSIS
28              
29             use OpenGuide::Config;
30             use OpenGuides::Utils;
31              
32             my $config = OpenGuides::Config->new( file => "wiki.conf" );
33             my $wiki = OpenGuides::Utils->make_wiki_object( config => $config );
34              
35             =head1 METHODS
36              
37             =over 4
38              
39             =item B
40              
41             my $config = OpenGuides::Config->new( file => "wiki.conf" );
42             my $wiki = OpenGuides::Utils->make_wiki_object( config => $config );
43              
44             Croaks unless an C object is supplied. Returns a
45             C object made from the given config file on success,
46             croaks if any other error occurs.
47              
48             The config file needs to define at least the following variables:
49              
50             =over
51              
52             =item *
53              
54             dbtype - one of C, C and C
55              
56             =item *
57              
58             dbname
59              
60             =item *
61              
62             indexing_directory - for the L, L,
63             or C files to go in
64              
65             =back
66              
67             =cut
68              
69             sub make_wiki_object {
70 154     154 1 102086 my ($class, %args) = @_;
71 154 100       1004 my $config = $args{config} or croak "No config param supplied";
72 153 100       1183 croak "config param isn't an OpenGuides::Config object"
73             unless UNIVERSAL::isa( $config, "OpenGuides::Config" );
74              
75             # Require in the right database module.
76 152         805 my $dbtype = $config->dbtype;
77              
78 152         3362 my %wiki_toolkit_exts = (
79             postgres => "Pg",
80             mysql => "MySQL",
81             sqlite => "SQLite",
82             );
83              
84 152         576 my $wiki_toolkit_module = "Wiki::Toolkit::Store::" . $wiki_toolkit_exts{$dbtype};
85 152         15174 eval "require $wiki_toolkit_module";
86 152 50       1005985 croak "Can't 'require' $wiki_toolkit_module.\n" if $@;
87              
88             # Make store.
89 152         1232 my $store = $wiki_toolkit_module->new(
90             dbname => $config->dbname,
91             dbuser => $config->dbuser,
92             dbpass => $config->dbpass,
93             dbhost => $config->dbhost,
94             dbport => $config->dbport,
95             charset => $config->dbencoding,
96             );
97              
98             # Make search.
99 152         232534 my $search;
100 152 50 33     889 if ( $config->use_lucy ) {
    50 33        
101 0         0 $search = $class->make_lucy_searcher( config => $config );
102             } elsif ( $config->use_plucene
103             && ( lc($config->use_plucene) eq "y"
104             || $config->use_plucene == 1 )
105             ) {
106 152         13752 require Wiki::Toolkit::Search::Plucene;
107 152         764240 my %plucene_args = ( path => $config->indexing_directory );
108 152         2278 my $munger = $config->search_content_munger_module;
109 152 100       1461 if ( $munger ) {
110 2         4 eval {
111 2         176 eval "require $munger";
112             $plucene_args{content_munger} = sub {
113 2     2   39061 my $content = shift;
114 2         24 return $munger->search_content_munger( $content );
115 2         19 };
116             };
117             }
118 152         2145 $search = Wiki::Toolkit::Search::Plucene->new( %plucene_args );
119             } else {
120 0         0 require Wiki::Toolkit::Search::SII;
121 0         0 require Search::InvertedIndex::DB::DB_File_SplitHash;
122 0         0 my $indexdb = Search::InvertedIndex::DB::DB_File_SplitHash->new(
123             -map_name => $config->indexing_directory,
124             -lock_mode => "EX"
125             );
126 0         0 $search = Wiki::Toolkit::Search::SII->new( indexdb => $indexdb );
127             }
128              
129             # Make formatter.
130 152         4579 my $script_name = $config->script_name;
131 152         1905 my $search_url = $config->script_url . "search.cgi";
132              
133             my %macros = (
134             '@SEARCHBOX' =>
135             qq(
),
136             qr/\@INDEX_LINK\s+\[\[(Category|Locale)\s+([^\]|]+)\|?([^\]]+)?\]\]/ =>
137             sub {
138             # We may be being called by Wiki::Toolkit::Plugin::Diff,
139             # which doesn't know it has to pass us $wiki - and
140             # we don't use it anyway.
141 3 100   3   23599 if ( UNIVERSAL::isa( $_[0], "Wiki::Toolkit" ) ) {
142 2         5 shift; # just throw it away
143             }
144 3 50       16 my $type = ( lc( $_[0] ) eq "category" ) ? "cat" : "loc";
145 3   66     19 my $link_title = $_[2] || "View all pages in $_[0] $_[1]";
146 3         22 return qq($link_title);
147             },
148             qr/\@INDEX_LIST\s+\[\[(Category|Locale)\s+([^\]]+)]]/ =>
149             sub {
150 5     5   5545 my ($wiki, $type, $value) = @_;
151 5         46 return $class->do_index_list_macro(
152             wiki => $wiki, type => $type, value => $value,
153             include_prefix => 1 );
154             },
155             qr/\@INDEX_LIST_NO_PREFIX\s+\[\[(Category|Locale)\s+([^\]]+)]]/ =>
156             sub {
157 4     4   2037 my ($wiki, $type, $value) = @_;
158 4         38 return $class->do_index_list_macro(
159             wiki => $wiki, type => $type, value => $value );
160             },
161             qr/\@NODE_COUNT\s+\[\[(Category|Locale)\s+([^\]]+)]]/ =>
162             sub {
163 1     1   567 my ($wiki, $type, $value) = @_;
164 1         13 return $class->do_node_count(
165             wiki => $wiki, type => $type, value => $value );
166             },
167             qr/\@MAP_LINK\s+\[\[(Category|Locale)\s+([^\]|]+)\|?([^\]]+)?\]\]/ =>
168             sub {
169 2 50   2   1057 if ( UNIVERSAL::isa( $_[0], "Wiki::Toolkit" ) ) {
170 2         5 shift; # don't need $wiki
171             }
172              
173 2 50       11 my $type = ( lc( $_[0] ) eq "category" ) ? "cat" : "loc";
174 2   66     13 my $link_title = $_[2]
175             || "View map of pages in $_[0] $_[1]";
176 2         14 return qq($link_title);
177             },
178             qr/\@RANDOM_PAGE_LINK(?:\s+\[\[(Category|Locale)\s+([^\]|]+)\|?([^\]]+)?\]\])?/ =>
179             sub {
180 5 50   5   3183 if ( UNIVERSAL::isa( $_[0], "Wiki::Toolkit" ) ) {
181 5         10 shift; # don't need $wiki
182             }
183 5         25 my ( $type, $value, $link_title ) = @_;
184 5         13 my $link = "$script_name?action=random";
185              
186 5 100 66     37 if ( $type && $value ) {
187 4         29 $link .= ";" . lc( uri_escape( $type ) ) . "="
188             . lc( uri_escape( $value ) );
189 4   66     102 $link_title ||= "View a random page in $type $value";
190             } else {
191 1   50     10 $link_title ||= "View a random page on this guide";
192             }
193 5         38 return qq($link_title);
194             },
195             qr/\@INCLUDE_NODE\s+\[\[([^\]|]+)\]\]/ =>
196             sub {
197 2     2   1172 my ($wiki, $node) = @_;
198 2         14 my %node_data = $wiki->retrieve_node( $node );
199 2         3290 return $node_data{content};
200             },
201 152         8006 );
202              
203 152         1252 my $custom_macro_module = $config->custom_macro_module;
204 152 100       1969 if ( $custom_macro_module ) {
205 2         3 eval {
206 2         142 eval "require $custom_macro_module";
207 2         36 %macros = $custom_macro_module->custom_macros(macros => \%macros);
208             };
209             }
210              
211 152         3224 my $formatter = Wiki::Toolkit::Formatter::UseMod->new(
212             extended_links => 1,
213             implicit_links => 0,
214             allowed_tags => [qw(a p b strong i em pre small img table td
215             tr th br hr ul li center blockquote kbd
216             div code span strike sub sup font dl dt dd
217             )],
218             macros => \%macros,
219             pass_wiki_to_macros => 1,
220             node_prefix => "$script_name?",
221             edit_prefix => "$script_name?action=edit;id=",
222             munge_urls => 1,
223             external_link_class => "external",
224             );
225              
226 152         13277 my %conf = ( store => $store,
227             search => $search,
228             formatter => $formatter );
229              
230 152         1311 my $wiki = Wiki::Toolkit->new( %conf );
231 152         6956 return $wiki;
232             }
233              
234             sub make_lucy_searcher {
235 0     0 0 0 my ( $class, %args ) = @_;
236 0         0 require Wiki::Toolkit::Search::Lucy;
237 0         0 my $config = $args{config};
238 0         0 my %lucy_args = (
239             path => $config->indexing_directory,
240             metadata_fields => [ qw( address category locale ) ],
241             boost => { title => 10 }, # empirically determined (test t/306)
242             );
243 0         0 my $munger = $config->search_content_munger_module;
244 0 0       0 if ( $munger ) {
245 0         0 eval {
246 0         0 eval "require $munger";
247             $lucy_args{content_munger} = sub {
248 0     0   0 my $content = shift;
249 0         0 return $munger->search_content_munger( $content );
250 0         0 };
251             };
252             }
253 0         0 return Wiki::Toolkit::Search::Lucy->new( %lucy_args );
254             }
255              
256             sub do_index_list_macro {
257 9     9 0 45 my ( $class, %args ) = @_;
258 9         32 my ( $wiki, $type, $value, $include_prefix )
259             = @args{ qw( wiki type value include_prefix ) };
260              
261             # We may be being called by Wiki::Toolkit::Plugin::Diff,
262             # which doesn't know it has to pass us $wiki
263 9 100       72 if ( !UNIVERSAL::isa( $wiki, "Wiki::Toolkit" ) ) {
264 1 50       7 if ( $args{include_prefix} ) {
265 1         7 return "(unprocessed INDEX_LIST macro)";
266             } else {
267 0         0 return "(unprocessed INDEX_LIST_NO_PREFIX macro)";
268             }
269             }
270              
271 8         38 my @nodes = sort $wiki->list_nodes_by_metadata(
272             metadata_type => $type,
273             metadata_value => $value,
274             ignore_case => 1,
275             );
276 8 100       2533 unless ( scalar @nodes ) {
277 4         45 return "\n* No pages currently in " . lc($type) . " $value\n";
278             }
279 4         11 my $return = "\n";
280 4         10 foreach my $node ( @nodes ) {
281 8         3135 my $title = $node;
282 8 100       42 $title =~ s/^(Category|Locale) // unless $args{include_prefix};
283 8         33 $return .= "* "
284             . $wiki->formatter->format_link( wiki => $wiki,
285             link => "$node|$title" )
286             . "\n";
287             }
288 4         3227 return $return;
289             }
290             sub do_node_count {
291 1     1 0 6 my ( $class, %args ) = @_;
292 1         4 my ( $wiki, $type, $value )
293             = @args{ qw( wiki type value ) };
294              
295             # We may be being called by Wiki::Toolkit::Plugin::Diff,
296             # which doesn't know it has to pass us $wiki
297 1 50       9 if ( !UNIVERSAL::isa( $wiki, "Wiki::Toolkit" ) ) {
298 0         0 return "(unprocessed NODE_COUNT macro)";
299             }
300              
301 1         7 my $num_nodes = scalar $wiki->list_nodes_by_metadata(
302             metadata_type => $type,
303             metadata_value => $value,
304             ignore_case => 1,
305             );
306 1         379 return $num_nodes;
307             }
308             =item B
309              
310             Returns coordinate data suitable for use with Google Maps (and other GIS
311             systems that assume WGS-84 data).
312              
313             my ($wgs84_long, $wgs84_lat) = OpenGuides::Utils->get_wgs84_coords(
314             longitude => $longitude,
315             latitude => $latitude,
316             config => $config
317             );
318              
319             =cut
320              
321             sub get_wgs84_coords {
322 185     185 0 13308 my ($self, %args) = @_;
323 185 50       956 my ($longitude, $latitude, $config) = ($args{longitude}, $args{latitude},
324             $args{config})
325             or croak "No longitude supplied to get_wgs84_coords";
326 185 50       888 croak "geo_handler not defined!" unless $config->geo_handler;
327              
328 185 100       2808 if ($config->force_wgs84) {
329             # Only as a rough approximation, good enough for large scale guides
330 161         1671 return ($longitude, $latitude);
331             }
332              
333             # If we don't have a lat and long, return undef right away
334 24 100 66     367 unless($args{longitude} || $args{latitude}) {
335 17         76 return undef;
336             }
337              
338             # Try to load a provider of Helmert Transforms
339 7         13 my $helmert;
340             # First up, try the MySociety Geo::HelmertTransform
341 7 50       24 unless($helmert) {
342 7         15 eval {
343 7         1355 require Geo::HelmertTransform;
344             $helmert = sub($$$) {
345 7     7   23 my ($datum,$oldlat,$oldlong) = @_;
346 7 100       26 if ($datum eq 'Airy') {
347 1         1 $datum = 'Airy1830';
348             }
349 7         50 my $datum_helper = new Geo::HelmertTransform::Datum(Name=>$datum);
350 7         10662 my $wgs84_helper = new Geo::HelmertTransform::Datum(Name=>'WGS84');
351 7 50       834 unless($datum_helper) {
352 0         0 croak("No convertion helper for datum '$datum'");
353 0         0 return undef;
354             }
355              
356 7         34 my ($lat,$long,$h) =
357             Geo::HelmertTransform::convert_datum($datum_helper,$wgs84_helper,$oldlat,$oldlong,0);
358 7         7376 return ($long,$lat);
359 7         7052 };
360             };
361             }
362             # Give up, return undef
363 7 50       32 unless($helmert) {
364 0         0 return undef;
365             }
366              
367              
368 7 100       31 if ($config->geo_handler == 1) {
    50          
    50          
369             # Do conversion here
370 6         80 return &$helmert('Airy1830',$latitude,$longitude);
371             } elsif ($config->geo_handler == 2) {
372             # Do conversion here
373 0         0 return &$helmert('Airy1830Modified',$latitude,$longitude);
374             } elsif ($config->geo_handler == 3) {
375 1 50       29 if ($config->ellipsoid eq "WGS-84") {
376 0         0 return ($longitude, $latitude);
377             } else {
378             # Do conversion here
379 1         10 return &$helmert($config->ellipsoid,$latitude,$longitude);
380             }
381             } else {
382 0         0 croak "Invalid geo_handler config option $config->geo_handler";
383             }
384             }
385              
386             =item B
387              
388             Given a set of WGS84 coordinate data, returns the minimum, maximum,
389             and centre latitude and longitude.
390              
391             %data = OpenGuides::Utils->get_wgs84_min_max(
392             nodes => [
393             { wgs84_lat => 51.1, wgs84_long => 1.1 },
394             { wgs84_lat => 51.2, wgs84_long => 1.2 },
395             ]
396             );
397             print "Top right-hand corner is $data{max_lat}, $data{max_long}";
398             print "Centre point is $data{centre_lat}, $data{centre_long}";
399              
400             The hashes in the C argument can include other key/value pairs;
401             these will just be ignored.
402              
403             Returns false if it can't find any valid geodata in the nodes.
404              
405             =cut
406              
407             sub get_wgs84_min_max {
408 15     15 1 2820 my ( $self, %args ) = @_;
409 15         34 my @nodes = @{$args{nodes}};
  15         48  
410              
411 39 100       285 my @lats = sort
412 39         83 grep { defined $_ && /^[-.\d]+$/ }
413 15         46 map { $_->{wgs84_lat} }
414             @nodes;
415 39 100       217 my @longs = sort
416 39         71 grep { defined $_ && /^[-.\d]+$/ }
417 15         39 map { $_->{wgs84_long} }
418             @nodes;
419              
420 15 100 66     97 if ( !scalar @lats || !scalar @longs ) {
421 2         6 return;
422             }
423              
424 13         83 my %data = ( min_lat => $lats[0], max_lat => $lats[$#lats],
425             min_long => $longs[0], max_long => $longs[$#longs] );
426 13         94 $data{centre_lat} = ( $data{min_lat} + $data{max_lat} ) / 2;
427 13         50 $data{centre_long} = ( $data{min_long} + $data{max_long} ) / 2;
428 13         115 return %data;
429             }
430              
431             =item B
432              
433             $tt_vars{page_description} =
434             OpenGuides::Utils->get_index_page_description(
435             format => "map",
436             criteria => [ type => "locale", value => "croydon" ],
437             );
438              
439             Returns a sentence that can be used as a summary of what's shown on an
440             index page.
441              
442             =cut
443              
444             sub get_index_page_description {
445 33     33 1 116 my ( $class, %args ) = @_;
446 33 100       151 my $desc = ( $args{format} eq "map" ) ? "Map" : "List";
447 33         75 $desc .= " of all our pages";
448              
449 33         56 my ( @cats, @locs );
450 33         55 foreach my $criterion ( @{$args{criteria}} ) {
  33         99  
451 40         112 my ( $type, $name ) = ( $criterion->{type}, $criterion->{name} );
452 40 100       97 if ( $type eq "category" ) {
453 17         72 $name =~ s/Category //;
454 17         54 push @cats, $name;
455             } else {
456 23         101 $name =~ s/Locale //;
457 23         76 push @locs, $name;
458             }
459             }
460              
461 33 100       117 if ( scalar @cats ) {
462 17         58 $desc .= " labelled with: " . join( ", ", @cats );
463 17 100       51 if ( scalar @locs ) {
464 8         17 $desc .= ", and";
465             }
466             }
467 33 100       105 if ( scalar @locs ) {
468 23         72 $desc .= " located in: " . join( ", ", @locs );
469             }
470 33         57 $desc .= ".";
471 33         148 return $desc;
472             }
473              
474             =item B
475              
476             $redir = OpenGuides::Utils->detect_redirect( content => "foo" );
477              
478             Checks the content of a node to see if the node is a redirect to another
479             node. If so, returns the name of the node that this one redirects to. If
480             not, returns false.
481              
482             (Also returns false if no content is provided.)
483              
484             =cut
485              
486             sub detect_redirect {
487 137     137 1 4965 my ( $self, %args ) = @_;
488 137 100       529 return unless $args{content};
489              
490 119 100       884 if ( $args{content} =~ /^#REDIRECT\s+(.+?)\s*$/ ) {
491 9         37 my $redirect = $1;
492              
493             # Strip off enclosing [[ ]] in case this is an extended link.
494 9         59 $redirect =~ s/^\[\[//;
495 9         59 $redirect =~ s/\]\]\s*$//;
496              
497 9         91 return $redirect;
498             }
499             }
500              
501             =item B
502              
503             my $fails = OpenGuides::Utils->validate_edit(
504             id => $node,
505             cgi_obj => $q
506             );
507              
508             Checks supplied content for general validity. If anything is invalid,
509             returns an array ref of errors to report to the user.
510              
511             =cut
512              
513             sub validate_edit {
514 353     353 1 1288 my ( $self, %args ) = @_;
515 353         753 my $q = $args{cgi_obj};
516 353         502 my @fails;
517 353 50       1085 push @fails, "Content missing" unless $q;
518 353 50       1089 return \@fails if @fails;
519              
520             # Now do our real validation
521 353         998 foreach my $var (qw(os_x os_y)) {
522 706 100 100     9809 if ($q->param($var) and $q->param($var) !~ /^-?\d+$/) {
523 1         35 push @fails, "$var must be integer, was: " . $q->param($var);
524             }
525             }
526              
527 353         7746 foreach my $var (qw(latitude longitude)) {
528 706 50 66     9865 if ($q->param($var) and $q->param($var) !~ /^-?\d+\.?(\d+)?$/) {
529 0         0 push @fails, "$var must be numeric, was: " . $q->param($var);
530             }
531             }
532              
533 353 100 66     8549 if ( $q->param('website') and $q->param('website') ne 'http://' ) {
534 5 50       200 unless ( is_web_uri( scalar $q->param('website') ) ) {
535 0         0 push @fails, $q->param('website') . ' is not a valid web URI';
536             }
537             }
538              
539 353         9189 return \@fails;
540              
541             };
542              
543             =item B
544              
545             my $change_comment = parse_change_comment($string, $base_url);
546              
547             Given a base URL (for example, C), takes a string,
548             replaces C<[[page]]> and C<[[page|titled link]]> with
549              
550             page
551              
552             and
553              
554             titled link
555              
556             respectively, and returns it. This is a limited subset of wiki markup suitable for
557             use in page change comments.
558              
559             =cut
560              
561             sub parse_change_comment {
562 79     79 1 15543 my ($comment, $base_url) = @_;
563              
564 79         482 my @links = $comment =~ m{\[\[(.*?)\]\]}g;
565              
566             # It's not all that great having to reinvent the wheel in this way, but
567             # Text::WikiFormat won't let you specify the subset of wiki notation that
568             # you're interested in. C'est la vie.
569 79         197 foreach (@links) {
570 2 100       16 if (/(.*?)\|(.*)/) {
571 1         6 my ($page, $title) = ($1, $2);
572 1         30 $comment =~ s{\[\[$page\|$title\]\]}
573             {$title};
574             } else {
575 1         2 my $page = $_;
576 1         41 $comment =~ s{\[\[$page\]\]}
577             {$page};
578             }
579             }
580              
581 79         457 return $comment;
582             }
583              
584             =item B
585              
586             eval { OpenGuides::Utils->send_email(
587             config => $config,
588             subject => "Subject",
589             body => "Test body",
590             admin => 1,
591             nobcc => 1,
592             return_output => 1
593             ) };
594              
595             if ($@) {
596             print "Error mailing admin: $@\n";
597             } else {
598             print "Mailed admin\n";
599             }
600              
601             Send out email. If C is true, the email will be sent to the site
602             admin. If C is defined, email will be sent to addresses in that
603             arrayref. If C is true, there will be no Bcc to the admin.
604              
605             C and C are mandatory arguments.
606              
607             Debugging: if C is true, the message will be returned as
608             a string instead of being sent by email.
609              
610             =cut
611              
612              
613             sub send_email {
614 4     4 1 2938 my ( $self, %args ) = @_;
615 4 50       16 my $config = $args{config} or die "config argument not supplied";
616 4         5 my @to;
617 4 100       11 @to = @{$args{to}} if $args{to};
  1         2  
618 4         5 my @bcc;
619 4 100       17 push @to, $config->contact_email if $args{admin};
620 4 100       34 die "No recipients specified" unless $to[0];
621 3 50       7 die "No subject specified" unless $args{subject};
622 3 50       9 die "No body specified" unless $args{body};
623 3         7 my $to_str = join ',', @to;
624 3 50       13 push @bcc, $config->contact_email unless $args{nobcc};
625 3         24 my $bcc_str = join ',', @bcc;
626 3         8 my $msg = MIME::Lite->new(
627             From => $config->contact_email,
628             To => $to_str,
629             Bcc => $bcc_str,
630             Subject => $args{subject},
631             Data => $args{body}
632             );
633              
634 3 50       64189 if ( $args{return_output} ) {
635 3         10 return $msg->as_string;
636             } else {
637 0 0       0 $msg->send or die "Couldn't send mail!";
638             }
639             }
640              
641             =item B
642              
643             if (OpenGuides::Utils->in_moderate_whitelist( '127.0.0.1' )) {
644             # skip moderation and apply new verson to published site
645             }
646              
647             Admins can supply a comma separated list of IP addresses or CIDR-notation
648             subnets indicating the hosts which can bypass enforced moderation. Any
649             values which cannot be parsed by C will be ignored.
650              
651             =cut
652              
653             sub in_moderate_whitelist {
654 351     351 1 930 my ($self, $config, $ip) = @_;
655 351 50       1404 return undef if not defined $ip;
656              
657             # create NetAddr::IP object of the test IP
658 351 100       4115 my $addr = Net::Netmask->new2($ip) or return undef;
659              
660             # load the configured whitelist
661             my @whitelist
662 350         43000 = split ',', $config->moderate_whitelist;
663              
664             # test each entry in the whitelist
665 350         4192 return eval{
666 2     2   8 first { Net::Netmask->new2($_)->match($addr->base) } @whitelist
667 350         4643 };
668             }
669              
670             =back
671              
672             =head1 AUTHOR
673              
674             The OpenGuides Project (openguides-dev@lists.openguides.org)
675              
676             =head1 COPYRIGHT
677              
678             Copyright (C) 2003-2013 The OpenGuides Project. All Rights Reserved.
679              
680             This module is free software; you can redistribute it and/or modify it
681             under the same terms as Perl itself.
682              
683             =cut
684              
685             1;