Ticket #154: JSON.2.pm

File JSON.2.pm, 9.6 kB (added by perigrin, 2 years ago)
Line 
1package Wiki::Toolkit::Plugin::JSON;
2
3use strict;
4
5use vars qw( $VERSION );
6$VERSION = '0.09';
7
8use POSIX 'strftime';
9use Time::Piece;
10use URI::Escape;
11use Carp qw( croak );
12
13BEGIN {
14    require constant;
15    eval {
16        require JSON::Syck;
17        JSON::Syck->import();
18    };
19
20    unless ($@) {
21        constant->import( JSON_SYCK => 1 );
22    }
23    else {
24        eval { require JSON; JSON->import() };
25        die "Couldn't find a JSON Package, install JSON::Syck or JSON" if $@;
26        constant->import( JSON_SYCK => 0 );
27    }
28}
29
30sub new {
31    my $class = shift;
32    my $self  = {@_};
33    bless $self, $class;
34
35    unless ( $self->{wiki} && UNIVERSAL::isa( $self->{wiki}, 'Wiki::Toolkit' ) )
36    {
37        croak 'No Wiki::Toolkit object supplied';
38    }
39
40    # Mandatory arguments.
41    foreach my $arg (qw/site_name site_url make_node_url recent_changes_link/) {
42        croak "No $arg supplied" unless $self->{$arg};
43    }
44
45    $self->{timestamp_fmt} = $Wiki::Toolkit::Store::Database::timestamp_fmt;
46    $self->{utc_offset} = strftime "%z", localtime;
47    $self->{utc_offset} =~ s/(..)(..)$/$1:$2/;
48
49    return $self;
50}
51
52sub recent_changes {
53    my ( $self, %args ) = @_;
54    my $wiki = $self->{wiki};
55
56# If we're not passed any parameters to limit the items returned, default to 15.
57
58    my %criteria = ( ignore_case => 1, );
59
60    if ( $args{days} ) {
61        $criteria{days} = $args{days};
62    }
63    else {
64        $criteria{last_n_changes} = $args{items} || 15;
65    }
66
67    if ( $args{ignore_minor_edits} ) {
68        $criteria{metadata_wasnt} = { major_change => 0 };
69    }
70
71    if ( $args{filter_on_metadata} ) {
72        $criteria{metadata_was} = $args{filter_on_metadata};
73    }
74
75    my @changes = $wiki->list_recent_changes(%criteria);
76
77    foreach my $change (@changes) {
78
79        $change->{timestamp} = $change->{last_modified};
80
81        # Make a Time::Piece object.
82        my $time =
83          Time::Piece->strptime( $change->{timestamp}, $self->{timestamp_fmt} );
84
85        my $utc_offset = $self->{utc_offset};
86
87        $change->{timestamp} = $time->strftime("%Y-%m-%dT%H:%M:%S$utc_offset");
88
89        $change->{author} = $change->{metadata}{username}[0]
90          || $change->{metadata}{host}[0]
91          || '';
92        $change->{description} = $change->{metadata}{comment}[0] || '';
93
94        $change->{status} = ( 1 == $change->{version} ) ? 'new' : 'updated';
95
96        $change->{major_change} = $change->{metadata}{major_change}[0];
97        $change->{major_change} = 1 unless defined $change->{major_change};
98        $change->{importance}   = $change->{major_change} ? 'major' : 'minor';
99
100        $change->{url} =
101          $self->{make_node_url}->( $change->{name}, $change->{version} );
102
103        if ( $self->{make_diff_url} ) {
104            $change->{diff_url} = $self->{make_diff_url}->( $change->{name} );
105        }
106
107        if ( $self->{make_history_url} ) {
108            $change->{history_url} =
109              $self->{make_history_url}->( $change->{name} );
110        }
111
112        my $change->{node_url} = $self->{make_node_url}->( $change->{name} );
113
114        my $rdf_url = $change->{node_url};
115        $rdf_url =~ s/\?/\?id=/;
116        $rdf_url .= ';format=rdf';
117        $change->{rdf_url} = $rdf_url;
118
119        # make XML-clean
120        my $title = $change->{name};
121        $title =~ s/&/&/g;
122        $title =~ s/</&lt;/g;
123        $title =~ s/>/&gt;/g;
124        $change->{title} = $title;
125    }
126    return $self->make_json( \@changes );
127}
128
129sub make_json {
130    my ( $self, $data ) = @_;
131    if (JSON_SYCK) {
132        return JSON::Syck::Dump($data);
133    }
134    else {
135        return JSON::objToJson($data);
136    }
137}
138
1391;
140
141__END__
142
143=head1 NAME
144
145  Wiki::Toolkit::Plugin::JSON - A Wiki::Toolkit plugin to output RecentChanges JSON.
146
147=head1 DESCRIPTION
148
149This is an alternative access to the recent changes of a Wiki::Toolkit
150wiki. It outputs JSON as described by the ModWiki proposal at
151L<http://www.usemod.com/cgi-bin/mb.pl?ModWiki>
152
153=head1 SYNOPSIS
154
155  use Wiki::Toolkit;
156  use Wiki::Toolkit::Plugin::JSON;
157
158  my $wiki = Wiki::Toolkit->new( ... );  # See perldoc Wiki::Toolkit
159
160  # Set up the JSON feeder with the mandatory arguments - see
161  # C<new()> below for more, optional, arguments.
162  my $json = Wiki::Toolkit::Plugin::JSON->new(
163    wiki                => $wiki,
164    site_name           => 'My Wiki',
165    site_url            => 'http://example.com/',
166    make_node_url       => sub
167                           {
168                             my ($node_name, $version) = @_;
169                             return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
170                           },
171    recent_changes_link => 'http://example.com/?RecentChanges',
172  );
173
174  print "Content-type: application/xml\n\n";
175  print $json->recent_changes;
176
177=head1 METHODS
178
179=head2 C<new()>
180
181  my $json = Wiki::Toolkit::Plugin::JSON->new(
182    # Mandatory arguments:
183    wiki                 => $wiki,
184    site_name            => 'My Wiki',
185    site_url             => 'http://example.com/',
186    make_node_url        => sub
187                            {
188                              my ($node_name, $version) = @_;
189                              return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
190                            },
191    recent_changes_link  => 'http://example.com/?RecentChanges',
192
193    # Optional arguments:
194    site_description     => 'My wiki about my stuff',
195    interwiki_identifier => 'MyWiki',
196    make_diff_url        => sub
197                            {
198                              my $node_name = shift;
199                              return 'http://example.com/?diff=' . uri_escape($node_name)
200                            },
201    make_history_url     => sub
202                            {
203                              my $node_name = shift;
204                              return 'http://example.com/?hist=' . uri_escape($node_name)
205                            },
206    software_name        => $your_software_name,     # e.g. "Wiki::Toolkit"
207    software_version     => $your_software_version,  # e.g. "0.73"
208    software_homepage    => $your_software_homepage, # e.g. "http://search.cpan.org/dist/CGI-Wiki/"
209  );
210
211C<wiki> must be a L<Wiki::Toolkit> object. C<make_node_url>, and
212C<make_diff_url> and C<make_history_url>, if supplied, must be coderefs.
213
214The mandatory arguments are:
215
216=over 4
217
218=item * wiki
219
220=item * site_name
221
222=item * site_url
223
224=item * make_node_url
225
226=item * recent_changes_link
227
228=back
229
230The three optional arguments
231
232=over 4
233
234=item * software_name
235
236=item * software_version
237
238=item * software_homepage
239
240=back
241
242are used to generate DOAP (Description Of A Project - see L<http://usefulinc.com/doap>) metadata
243for the feed to show what generated it.
244
245=head2 C<recent_changes()>
246
247  $wiki->write_node(
248                     'About This Wiki',
249                     'blah blah blah',
250                                 $checksum,
251                           {
252                       comment  => 'Stub page, please update!',
253                                   username => 'Fred',
254                     }
255  );
256
257  print "Content-type: application/xml\n\n";
258  print $json->recent_changes;
259
260  # Or get something other than the default of the latest 15 changes.
261  print $json->recent_changes( items => 50 );
262  print $json->recent_changes( days => 7 );
263
264  # Or ignore minor edits.
265  print $json->recent_changes( ignore_minor_edits => 1 );
266
267  # Personalise your feed further - consider only changes
268  # made by Fred to pages about bookshops.
269  print $json->recent_changes(
270             filter_on_metadata => {
271                         username => 'Fred',
272                         category => 'Bookshops',
273                       },
274              );
275
276If using C<filter_on_metadata>, note that only changes satisfying
277I<all> criteria will be returned.
278
279B<Note:> Many of the fields emitted by the JSON generator are taken
280from the node metadata. The form of this metadata is I<not> mandated
281by L<Wiki::Toolkit>. Your wiki application should make sure to store some or
282all of the following metadata when calling C<write_node>:
283
284=over 4
285
286=item B<comment> - a brief comment summarising the edit that has just been made.  Defaults to the empty string.
287
288=item B<username> - an identifier for the person who made the edit; will be used as the Dublin Core contributor for this item.  Defaults to the empty string.
289
290=item B<host> - the hostname or IP address of the computer used to make the edit; if no username is supplied then this will be used as the Dublin Core contributor for this item.  Defaults to the empty string.
291
292=item B<major_change> - true if the edit was a major edit and false if it was a minor edit; used for the importance of the item.  Defaults to true (ie if C<major_change> was not defined or was explicitly stored as C<undef>).
293
294=back
295
296=head2 C<rss_timestamp()>
297
298  print $json->rss_timestamp();
299
300Returns the timestamp of the feed in POSIX::strftime style ("Tue, 29 Feb 2000
30112:34:56 GMT"), which is equivalent to the timestamp of the most recent item
302in the feed. Takes the same arguments as recent_changes(). You will most likely
303need this to print a Last-Modified HTTP header so user-agents can determine
304whether they need to reload the feed or not.
305 
306=head1 SEE ALSO
307
308=over 4
309
310=item * L<Wiki::Toolkit>
311
312=item * L<http://web.resource.org/rss/1.0/spec>
313
314=item * L<http://www.usemod.com/cgi-bin/mb.pl?ModWiki>
315
316=back
317
318=head1 MAINTAINER
319
320Earle Martin <EMARTIN@cpan.org>. Originally by Kake Pugh <kake@earth.li>.
321
322=head1 COPYRIGHT AND LICENSE
323
324Copyright 2003-4 Kake Pugh. Subsequent modifications copyright 2005
325Earle Martin.
326
327This module is free software; you can redistribute it and/or modify it
328under the same terms as Perl itself.
329
330=head1 THANKS
331
332The members of the Semantic Web Interest Group channel on irc.freenode.net,
333#swig, were very useful in the development of this module.
334
335=cut