root/wiki-toolkit-plugin-json/trunk/lib/Wiki/Toolkit/Plugin/JSON.pm @ 530

Revision 530, 9.1 KB (checked in by kake, 13 months ago)

Added a start at testing, and fixed a bug that was stopping rdf_url being set.

Line 
1package Wiki::Toolkit::Plugin::JSON;
2
3use strict;
4
5use vars qw( $VERSION );
6$VERSION = '0.03';
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        $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.
151
152=head1 SYNOPSIS
153
154  use Wiki::Toolkit;
155  use Wiki::Toolkit::Plugin::JSON;
156
157  my $wiki = Wiki::Toolkit->new( ... );  # See perldoc Wiki::Toolkit
158
159  # Set up the JSON feeder with the mandatory arguments - see
160  # C<new()> below for more, optional, arguments.
161  my $json = Wiki::Toolkit::Plugin::JSON->new(
162    wiki                => $wiki,
163    site_name           => 'My Wiki',
164    site_url            => 'http://example.com/',
165    make_node_url       => sub
166                           {
167                             my ($node_name, $version) = @_;
168                             return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
169                           },
170    recent_changes_link => 'http://example.com/?RecentChanges',
171  );
172
173  print "Content-type: application/xml\n\n";
174  print $json->recent_changes;
175
176=head1 METHODS
177
178=head2 C<new()>
179
180  my $json = Wiki::Toolkit::Plugin::JSON->new(
181    # Mandatory arguments:
182    wiki                 => $wiki,
183    site_name            => 'My Wiki',
184    site_url             => 'http://example.com/',
185    make_node_url        => sub
186                            {
187                              my ($node_name, $version) = @_;
188                              return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
189                            },
190    recent_changes_link  => 'http://example.com/?RecentChanges',
191
192    # Optional arguments:
193    site_description     => 'My wiki about my stuff',
194    interwiki_identifier => 'MyWiki',
195    make_diff_url        => sub
196                            {
197                              my $node_name = shift;
198                              return 'http://example.com/?diff=' . uri_escape($node_name)
199                            },
200    make_history_url     => sub
201                            {
202                              my $node_name = shift;
203                              return 'http://example.com/?hist=' . uri_escape($node_name)
204                            },
205  );
206
207C<wiki> must be a L<Wiki::Toolkit> object. C<make_node_url>, and
208C<make_diff_url> and C<make_history_url>, if supplied, must be coderefs.
209
210The mandatory arguments are:
211
212=over 4
213
214=item * wiki
215
216=item * site_name
217
218=item * site_url
219
220=item * make_node_url
221
222=item * recent_changes_link
223
224=back
225
226=head2 C<recent_changes()>
227
228  $wiki->write_node(
229                     'About This Wiki',
230                     'blah blah blah',
231                                 $checksum,
232                           {
233                       comment  => 'Stub page, please update!',
234                                   username => 'Fred',
235                     }
236  );
237
238  print "Content-type: application/xml\n\n";
239  print $json->recent_changes;
240
241  # Or get something other than the default of the latest 15 changes.
242  print $json->recent_changes( items => 50 );
243  print $json->recent_changes( days => 7 );
244
245  # Or ignore minor edits.
246  print $json->recent_changes( ignore_minor_edits => 1 );
247
248  # Personalise your feed further - consider only changes
249  # made by Fred to pages about bookshops.
250  print $json->recent_changes(
251             filter_on_metadata => {
252                         username => 'Fred',
253                         category => 'Bookshops',
254                       },
255              );
256
257If using C<filter_on_metadata>, note that only changes satisfying
258I<all> criteria will be returned.
259
260B<Note:> Many of the fields emitted by the JSON generator are taken
261from the node metadata. The form of this metadata is I<not> mandated
262by L<Wiki::Toolkit>. Your wiki application should make sure to store some or
263all of the following metadata when calling C<write_node>:
264
265=over 4
266
267=item B<comment> - a brief comment summarising the edit that has just been made.  Defaults to the empty string.
268
269=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.
270
271=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.
272
273=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>).
274
275=back
276
277=head2 C<rss_timestamp()>
278
279  print $json->rss_timestamp();
280
281Returns the timestamp of the feed in POSIX::strftime style ("Tue, 29 Feb 2000
28212:34:56 GMT"), which is equivalent to the timestamp of the most recent item
283in the feed. Takes the same arguments as recent_changes(). You will most likely
284need this to print a Last-Modified HTTP header so user-agents can determine
285whether they need to reload the feed or not.
286 
287=head1 SEE ALSO
288
289=over 4
290
291=item * L<Wiki::Toolkit>
292
293=item * L<http://web.resource.org/rss/1.0/spec>
294
295=item * L<http://www.usemod.com/cgi-bin/mb.pl?ModWiki>
296
297=back
298
299=head1 MAINTAINER
300
301Earle Martin <EMARTIN@cpan.org>. Originally by Kake Pugh <kake@earth.li>.
302
303=head1 COPYRIGHT AND LICENSE
304
305Copyright 2003-4 Kake Pugh. Subsequent modifications copyright 2005
306Earle Martin.
307
308Copyright 2008 the Wiki::Toolkit team
309
310This module is free software; you can redistribute it and/or modify it
311under the same terms as Perl itself.
312
313=head1 THANKS
314
315The members of the Semantic Web Interest Group channel on irc.freenode.net,
316#swig, were very useful in the development of this module.
317
318=cut
Note: See TracBrowser for help on using the browser.