File Coverage

blib/lib/WWW/Mediawiki/Client.pm
Criterion Covered Total %
statement 40 42 95.2
branch n/a
condition n/a
subroutine 14 14 100.0
pod n/a
total 54 56 96.4


line stmt bran cond sub pod time code
1             package WWW::Mediawiki::Client;
2              
3 3     3   121951 use warnings;
  3         6  
  3         127  
4 3     3   16 use strict;
  3         6  
  3         89  
5 3     3   16 use File::Spec;
  3         9  
  3         57  
6 3     3   15 use File::Find;
  3         3  
  3         209  
7 3     3   4511 use LWP::UserAgent;
  3         194291  
  3         110  
8 3     3   3681 use HTML::TokeParser;
  3         41918  
  3         99  
9 3     3   28 use HTTP::Request;
  3         6  
  3         81  
10 3     3   4450 use HTTP::Request::Common;
  3         7746  
  3         320  
11 3     3   3015 use HTTP::Cookies;
  3         29760  
  3         106  
12 3     3   52 use URI::Escape;
  3         6  
  3         283  
13 3     3   3381 use VCS::Lite;
  3         25344  
  3         102  
14 3     3   5746 use Data::Dumper;
  3         26624  
  3         248  
15 3     3   2474 use WWW::Mediawiki::Client::Exceptions;
  3         15  
  3         111  
16 3     3   2688 use XML::LibXML ();
  0            
  0            
17             use HTML::Entities qw(encode_entities);
18             use File::Temp ();
19             use Encode;
20             use Encode::Guess;
21             use utf8;
22              
23             BEGIN {
24             # If we tell LWP it's dealing with UTF-8 then URI::Escape will munge the text
25             # So either we put up with warnings or do this:
26             for (0x100 .. 0xd7ff, 0xf000 .. 0xfdcf) {
27             $URI::Escape::escapes{chr($_)} =
28             &URI::Escape::uri_escape_utf8(chr($_));
29             }
30             }
31              
32             use base 'Exporter';
33             our %EXPORT_TAGS = (
34             options => [qw(OPT_YES OPT_NO OPT_DEFAULT OPT_KEEP)],
35             );
36             our @EXPORT_OK = map { @{$EXPORT_TAGS{$_}} } keys %EXPORT_TAGS;
37              
38             =head1 NAME
39              
40             WWW::Mediawiki::Client
41              
42             =cut
43              
44             =head1 SYNOPSIS
45            
46             use WWW::Mediawiki::Client;
47              
48             my $filename = 'Subject.wiki';
49             my $mvs = WWW::Mediawiki::Client->new(
50             host => 'www.wikitravel.org'
51             );
52              
53             # like cvs update
54             $mvs->do_update($filename);
55              
56             # like cvs commit
57             $mvs->do_commit($filename, $message);
58              
59             #aliases
60             $mvs->do_up($filename);
61             $mvs->do_com($filename, $message);
62              
63             =cut
64              
65             =head1 DESCRIPTION
66              
67             WWW::Mediawiki::Client provides a very simple cvs-like interface for
68             Mediawiki driven WikiWiki websites, such as
69             L or
70             L
71             The interface mimics the two most basic cvs commands: update and commit
72             with similarly named methods. Each of these has a shorter alias, as in
73             cvs.
74              
75             =cut
76              
77             =head1 CONSTANTS
78              
79             =cut
80              
81             use constant ACTION => 'action';
82              
83             use constant TITLE => 'title';
84              
85             use constant SUBMIT => 'submit';
86              
87             use constant LOGIN => 'submitlogin';
88              
89             use constant LOGIN_TITLE => 'Special:Userlogin';
90              
91             use constant EDIT => 'edit';
92              
93             # defaults for various known Mediawiki installations
94             my %DEFAULTS;
95              
96             $DEFAULTS{'www.wikitravel.org'} =
97             {
98             'host' => 'wikitravel.org',
99             'protocol' => 'http',
100             'space_substitute' => '_',
101             'wiki_path' => 'wiki/__LANG__/index.php',
102             };
103             $DEFAULTS{'wikitravel.org'} = $DEFAULTS{'www.wikitravel.org'};
104              
105             $DEFAULTS{'www.wikipedia.org'} =
106             {
107             'host' => '__LANG__.wikipedia.org',
108             'protocol' => 'http',
109             'space_substitute' => '+',
110             'wiki_path' => 'w/wiki.phtml',
111             };
112             $DEFAULTS{'wikipedia.org'} = $DEFAULTS{'www.wikipedia.org'};
113              
114             $DEFAULTS{'www.wiktionary.org'} =
115             {
116             'host' => '__LANG__.wiktionary.org',
117             'protocol' => 'http',
118             'space_substitute' => '_',
119             'wiki_path' => 'w/wiki.phtml',
120             };
121             $DEFAULTS{'wiktionary.org'} = $DEFAULTS{'www.wiktionary.org'};
122              
123             $DEFAULTS{'www.wikibooks.org'} =
124             {
125             'host' => '__LANG__.wikibooks.org',
126             'protocol' => 'http',
127             'space_substitute' => '_',
128             'wiki_path' => 'w/wiki.phtml',
129             };
130             $DEFAULTS{'wikibooks.org'} = $DEFAULTS{'www.wikibooks.org'};
131              
132             sub DEFAULTS { \%DEFAULTS };
133              
134             use constant SPACE_SUBSTITUTE => '_';
135             use constant WIKI_PATH => 'wiki/index.php';
136             use constant LANGUAGE_CODE => 'en';
137             use constant PROTOCOL => 'http';
138              
139             use constant SPECIAL_EXPORT => 'Special:Export';
140             use constant SPECIAL_VERSION => 'Special:Version';
141              
142              
143             =head3 $VERSION
144              
145             =cut
146              
147             our $VERSION = 0.31;
148              
149             =head2 Update Status
150              
151             =head3 STATUS_UNKNOWN
152              
153             Indicates that C has no information about the file.
154              
155             =head3 STATUS_UNCHANGED
156              
157             Indicates that niether the file nor the server page have changed.
158              
159             =head3 STATUS_LOCAL_ADDED
160              
161             Indicates that the file is new locally, and does not exist on the server.
162              
163             =head3 STATUS_LOCAL_MODIFIED
164              
165             Indicates that the file has been modified locally.
166              
167             =head3 STATUS_SERVER_MODIFIED
168              
169             Indicates that the server page was modified, and that the modifications
170             have been successfully merged into the local file.
171              
172             =head3 STATUS_CONFLICT
173              
174             Indicates that there are conflicts in the local file resulting from a
175             failed merge between the server page and the local file.
176              
177             =cut
178              
179             use constant STATUS_UNKNOWN => '?';
180             use constant STATUS_UNCHANGED => '=';
181             use constant STATUS_LOCAL_ADDED => 'A';
182             use constant STATUS_LOCAL_MODIFIED => 'M';
183             use constant STATUS_SERVER_MODIFIED => 'U';
184             use constant STATUS_CONFLICT => 'C';
185              
186             =head2 Option Settings
187              
188             =head3 OPT_YES
189              
190             Indicates that the setting should always be applied.
191              
192             =head3 OPT_NO
193              
194             Indicates that the setting should never be applied.
195              
196             =head3 OPT_DEFAULT
197              
198             Indicates that the setting should be applied based on the user profile
199             default on the Wikimedia server.
200              
201             =head3 OPT_KEEP
202              
203             Four-state options only. Indicates that the setting should not be
204             changed from its current value on the server.
205              
206             =cut
207              
208             # Option values:
209             use constant OPT_YES => 1;
210             use constant OPT_NO => 0;
211             use constant OPT_DEFAULT => -1;
212             use constant OPT_KEEP => -2; # Only for watch.
213              
214             # Reverse lookup:
215             use constant OPTION_SETTINGS => (
216             OPT_YES, 'OPT_YES',
217             OPT_NO, 'OPT_NO',
218             OPT_DEFAULT, 'OPT_DEFAULT',
219             OPT_KEEP, 'OPT_KEEP',
220             );
221              
222             # Option defaults:
223             use constant MINOR_DEFAULT => OPT_DEFAULT;
224             use constant WATCH_DEFAULT => OPT_DEFAULT;
225              
226             =head2 Mediawiki form widgets
227              
228             =head3 TEXTAREA_NAME
229              
230             =head3 COMMENT_NAME
231              
232             =head3 EDIT_SUBMIT_NAME
233              
234             =head3 EDIT_SUBMIT_VALUE
235              
236             =head3 EDIT_PREVIEW_NAME
237              
238             =head3 EDIT_PREVIEW_VALUE
239              
240             =head3 EDIT_TIME_NAME
241              
242             =head3 EDIT_TOKEN_NAME
243              
244             =head3 EDIT_WATCH_NAME
245              
246             =head3 EDIT_MINOR_NAME
247              
248             =head3 CHECKED
249              
250             =head3 UNCHECKED
251              
252             =head3 USERNAME_NAME
253              
254             =head3 PASSWORD_NAME
255              
256             =head3 REMEMBER_NAME
257              
258             =head3 LOGIN_SUBMIT_NAME
259              
260             =head3 LOGIN_SUBMIT_VALUE
261              
262             =cut
263              
264             use constant TEXTAREA_NAME => 'wpTextbox1';
265             use constant COMMENT_NAME => 'wpSummary';
266             use constant EDIT_SUBMIT_NAME => 'wpSave';
267             use constant EDIT_SUBMIT_VALUE => 'Save Page';
268             use constant EDIT_PREVIEW_NAME => 'wpPreview';
269             use constant EDIT_PREVIEW_VALUE => 'Show preview';
270             use constant EDIT_TIME_NAME => 'wpEdittime';
271             use constant EDIT_TOKEN_NAME => 'wpEditToken';
272             use constant EDIT_WATCH_NAME => 'wpWatchthis';
273             use constant EDIT_MINOR_NAME => 'wpMinoredit';
274             use constant CHECKED => 1;
275             use constant UNCHECKED => 0;
276             use constant USERNAME_NAME => 'wpName';
277             use constant PASSWORD_NAME => 'wpPassword';
278             use constant REMEMBER_NAME => 'wpRemember';
279             use constant LOGIN_SUBMIT_NAME => 'wpLoginattempt';
280             use constant LOGIN_SUBMIT_VALUE => 'Log In';
281              
282             =head2 Files
283              
284             =head3 CONFIG_FILE
285              
286             .mediawiki
287              
288             =head3 COOKIE_FILE
289              
290             .mediawiki.cookies
291              
292             =head3 SAVED_ATTRIBUTES
293              
294             Controls which attributes get saved out to the config file.
295              
296             =cut
297              
298             use constant CONFIG_FILE => '.mediawiki';
299             use constant COOKIE_FILE => '.mediawiki_cookies.dat';
300             use constant SAVED_ATTRIBUTES => (
301             qw(site_url host protocol language_code space_substitute username
302             password wiki_path watch encoding minor_edit escape_filenames)
303             ); # It's important that host goes first since it has side effects
304              
305              
306             =head1 CONSTRUCTORS
307              
308             =cut
309              
310             =head2 new
311              
312             my $mvs = WWW::Mediawiki::Client->new(host = 'www.wikitravel.org');
313              
314             Accepts name-value pairs which will be used as initial values for any of
315             the fields which have accessors below. Throws the same execptions as the
316             accessor for any field named.
317              
318             =cut
319              
320             sub new {
321             my $pkg = shift;
322             my %init = @_;
323             my $self = bless {};
324             $self->load_state;
325             foreach my $attr (SAVED_ATTRIBUTES) {
326             next unless $init{$attr};
327             $self->$attr($init{$attr});
328             }
329             $self->{ua} = LWP::UserAgent->new();
330             push @{ $self->{ua}->requests_redirectable }, 'POST';
331             my $agent = 'WWW::Mediawiki::Client/' . $VERSION;
332             $self->{ua}->agent($agent);
333             $self->{ua}->env_proxy;
334             my $cookie_jar = HTTP::Cookies->new(
335             file => COOKIE_FILE,
336             autosave => 1,
337             );
338             $self->{ua}->cookie_jar($cookie_jar);
339             return $self;
340             }
341              
342             =head1 ACCESSORS
343              
344             =cut
345              
346             =head2 host
347              
348             my $url = $mvs->host('www.wikipediea.org');
349              
350             my $url = $mvs->host('www.wikitravel.org');
351              
352             The C is the name of the Mediawiki server from which you want to
353             obtain content, and to which your submissions will be made. There is no
354             default. This has to be set before attempting to use any of the methods
355             which attempt to access the server.
356              
357             B
358              
359             =over 4
360              
361             =item Server defaults
362              
363             If WWW::Mediawiki::Client knows about the path settings for the Mediawiki
364             installation you are trying to use then the various path fields will also
365             be set as a side-effect.
366              
367             =item Trailing slashes
368              
369             Any trailing slashes are deleted I the value of C is set.
370              
371             =back
372              
373             =cut
374              
375             sub host {
376             my ($self, $host) = @_;
377             if ($host) {
378             $host =~ s{/*$}{}; # remove any trailing /s
379             $self->{host} = $host;
380             my $defaults = $DEFAULTS{$host};
381             foreach my $k (keys %$defaults) {
382             $self->{$k} = $defaults->{$k};
383             }
384             }
385             return $self->{host};
386             }
387              
388             =head2 protocol
389              
390             my $url = $mvs->protocol('www.wikipediea.org');
391              
392             my $url = $mvs->protocol('www.wikitravel.org');
393              
394             The C is the protocol used by the Mediawiki server from which you
395             want to obtain content, and to which your submissions will be made. It can
396             be one of C or C with the default value being http.
397              
398             B
399              
400             =over 4
401              
402             =item Server defaults
403              
404             If WWW::Mediawiki::Client knows about the settings for the Mediawiki
405             installation you are trying to use then the various path fields will also
406             be set as a side-effect.
407              
408             =back
409              
410             =cut
411              
412             sub protocol {
413             my ($self, $protocol) = @_;
414             if ($protocol) {
415             WWW::Mediawiki::Client::URLConstructionException->throw(
416             "The protocol must be either 'http' or 'https'."
417             . "You specified $protocol." )
418             unless $protocol =~ m/^http(s){0,1}$/;
419             $self->{protocol} = $protocol;
420             }
421             return $self->{protocol} || PROTOCOL;
422             }
423              
424             =head2 language_code
425              
426             my $lang = $mvs->language_code($lang);
427              
428             Most Mediawiki projects have multiple language versions. This field can be
429             set to target a particular language version of the project the client is
430             set up to address. When the C and C methods
431             encounter the text '__LANG__' in any part of their constructed URL the
432             C will be substituted.
433              
434             C defaults to 'en'.
435              
436             =cut
437              
438             sub language_code {
439             my ($self, $char) = @_;
440             $self->{language_code} = $char if $char;
441             $self->{language_code} = LANGUAGE_CODE
442             unless $self->{language_code};
443             return $self->{language_code};
444             }
445              
446             =head2 space_substitute
447              
448             my $char = $mvs->space_substitute($char);
449              
450             Mediawiki allows article names to have spaces, for instance the default
451             Meidawiki main page is called "Main Page". The spaces need to be converted
452             for the URL, and to avoid the normal but somewhat difficult to read URL
453             escape the Mediawiki software substitutes some other character. Wikipedia
454             uses a '+', as in "Main+Page" and Wikitravel uses a '_' as in "Main_page".
455             WWW::Mediawiki::Client always writes wiki files using the '_', but converts
456             them to whatever the C is set to for the URL.
457              
458             B
459              
460             =over
461              
462             =item WWW::Mediawiki::Client::URLConstructionException
463              
464             =back
465              
466             =cut
467              
468             sub space_substitute {
469             my ($self, $char) = @_;
470             if ($char) {
471             WWW::Mediawiki::Client::URLConstructionException->throw(
472             "Illegal Character in space_substitute $char" )
473             if $char =~ /[\&\?\=\\\/]/;
474             $self->{space_substitute} = $char;
475             }
476             $self->{space_substitute} = SPACE_SUBSTITUTE
477             unless $self->{space_substitute};
478             return $self->{space_substitute};
479             }
480              
481             =head2 escape_filenames
482              
483             my $char = $mvs->escape_filenames($do_escape);
484              
485             Mediawiki allows article names to be in UTF-8 and most international
486             Wikipedias use this feature. That leads us to UTF-8 encoded file names
487             and not all filesystems can handle them. So you can set this option to
488             some true value to make all your local file names with wiki articles
489             URL-escaped.
490              
491             =cut
492              
493             sub escape_filenames {
494             my ($self, $do_escape) = @_;
495             if ($do_escape) {
496             $self->{escape_filenames} = $do_escape;
497             } elsif (!defined $self->{escape_filenames}) {
498             $self->{escape_filenames} = 0;
499             }
500              
501             return $self->{escape_filenames};
502             }
503              
504             =head2 wiki_path
505              
506             my $path = $mvs->wiki_path($path);
507              
508             C is the path to the php page which handles all request to
509             edit or submit a page, or to login. If you are using a Mediawiki site
510             which WWW::Mediawiki::Client knows about this will be set for you when you
511             set the C. Otherwise it defaults to the 'wiki/wiki.phtml' which is
512             what you'll get if you follow the installation instructions that some with
513             Mediawiki.
514              
515             B
516              
517             =over
518              
519             =item Leading slashes
520              
521             Leading slashes in any incoming value will be stripped.
522              
523             =back
524              
525             =cut
526              
527             sub wiki_path {
528             my ($self, $wiki_path) = @_;
529             if ($wiki_path) {
530             $wiki_path =~ s{^/*}{}; # strip leading slashes
531             $self->{wiki_path} = $wiki_path;
532             }
533             $self->{wiki_path} = WIKI_PATH
534             unless $self->{wiki_path};
535             return $self->{wiki_path};
536             }
537              
538             =head2 encoding
539              
540             my $encoding = $mvs->encoding($encoding);
541              
542             C is the charset in which the Mediawiki server expects uploaded
543             content to be encoded. This should be set the first time you use do_login.
544              
545             =cut
546              
547             sub encoding {
548             my ($self, $encoding) = @_;
549             $self->{encoding} = $encoding if $encoding;
550             return $self->{encoding};
551             }
552              
553             =head2 username
554              
555             my $url = $mvs->username($url);
556              
557             The username to use if WWW::Mediawiki::Client is to log in to the Mediawiki server as a given
558             user.
559              
560             =cut
561              
562             sub username {
563             my ($self, $username) = @_;
564             $self->{username} = $username if $username;
565             return $self->{username};
566             }
567              
568             =head2 password
569              
570             my $url = $mvs->password($url);
571              
572             The password to use if WWW::Mediawiki::Client is to log in to the Mediawiki server as a given
573             user. Note that this password is sent I, so it's probably not a
574             good idea to use an important one.
575              
576             =cut
577              
578             sub password {
579             my ($self, $password) = @_;
580             $self->{password} = $password if $password;
581             return $self->{password};
582             }
583              
584             =head2 commit_message
585              
586             my $msg = $mvs->commit_message($msg);
587              
588             A C must be specified before C can be run. This
589             will be used as the comment when submitting pages to the Mediawiki server.
590              
591             =cut
592              
593             sub commit_message {
594             my ($self, $msg) = @_;
595             $self->{commit_message} = $msg if $msg;
596             return $self->{commit_message};
597             }
598              
599             =head2 watch
600              
601             my $watch = $mvs->watch($watch);
602              
603             Mediawiki allows users to add a page to thier watchlist at submit time
604             using using the "Watch this page" checkbox. The field C allows
605             commits from this library to add or remove the page in question to/from
606             your watchlist.
607              
608             This is a four-state option:
609              
610             =over
611              
612             =item C
613              
614             Always add pages to the watchlist.
615              
616             =item C
617              
618             Remove pages from the watchlist.
619              
620             =item C
621              
622             Maintain current watched state.
623              
624             =item C (default)
625              
626             Adhere to user profile default on the server. Watched pages will
627             always remain watched, and all other pages will be watched if the
628             "watch all pages by default" option is enabled in the user profile.
629              
630             =back
631              
632             B
633              
634             =over
635              
636             =item WWW::Mediawiki::Client::InvalidOptionException
637              
638             =back
639              
640             =cut
641              
642             sub watch {
643             my ($self, $watch) = @_;
644             if (defined($watch)) {
645             $self->_option_verify('watch', $watch,
646             [OPT_YES, OPT_NO, OPT_KEEP, OPT_DEFAULT]);
647             $self->{watch} = $watch;
648             }
649             $self->{watch} = WATCH_DEFAULT unless defined $self->{watch};
650             return $self->{watch};
651             }
652              
653             =head2 minor_edit
654              
655             my $minor = $mvs->minor_edit($minor);
656              
657             Mediawiki allows users to mark some of their edits as minor using the "This
658             is a minor edit" checkbox. The field C allows a commit from
659             the mediawiki client to be marked as a minor edit.
660              
661             This is a three-state option:
662              
663             =over
664              
665             =item C
666              
667             Always declare change as minor.
668              
669             =item C
670              
671             Never declare change as minor.
672              
673             =item C (default)
674              
675             Adhere to user profile default on the server. Edits will be marked
676             as minor if the "minor changes by default" option is enabled in the
677             user profile.
678              
679             =back
680              
681             B
682              
683             =over
684              
685             =item WWW::Mediawiki::Client::InvalidOptionException
686              
687             =back
688              
689             =cut
690              
691             sub minor_edit {
692             my ($self, $minor) = @_;
693             if (defined($minor)) {
694             $self->_option_verify('minor_edit', $minor,
695             [OPT_YES, OPT_NO, OPT_DEFAULT]);
696             $self->{minor_edit} = $minor;
697             }
698             $self->{minor_edit} = MINOR_DEFAULT unless defined $self->{minor_edit};
699             return $self->{minor_edit};
700             }
701              
702             =head2 status
703              
704             my %status = $mvs->status;
705              
706             This field will be empty until do_update has been called, after which it
707             will be set to a hash of C => C pairs. Each C
708             will be one of the following (see CONSTANTS for discriptions):
709              
710             =item WWW::Mediawiki::Client::STATUS_UNKNOWN;
711              
712             =item WWW::Mediawiki::Client::STATUS_UNCHANGED;
713              
714             =item WWW::Mediawiki::Client::STATUS_LOCAL_ADDED;
715              
716             =item WWW::Mediawiki::Client::STATUS_LOCAL_MODIFIED;
717              
718             =item WWW::Mediawiki::Client::STATUS_SERVER_MODIFIED;
719              
720             =item WWW::Mediawiki::Client::STATUS_CONFLICT;
721              
722             =cut
723              
724             sub status {
725             my ($self, $arg) = @_;
726             WWW::Mediawiki::Client::ReadOnlyFieldException->throw(
727             "Tried to set read-only field 'status' to $arg.") if $arg;
728             return unless defined($self->{status});
729             return $self->{status};
730             }
731              
732             =head2 site_url DEPRICATED
733              
734             my $url = $mvs->site_url($url);
735              
736             The site URL is the base url for reaching the Mediawiki server who's
737             content you wish to edit. This field is now depricated in favor of the
738             C field which is basically the same thing without the protocol
739             string.
740              
741              
742             B
743              
744             =over 4
745              
746             =item Server defaults
747              
748             If WWW::Mediawiki::Client knows about the path settings for the Mediawiki
749             installation you are trying to use then the various path fields will also
750             be set as a side-effect.
751              
752             =item Trailing slashes
753              
754             Any trailing slashes are deleted I the value of C is set.
755              
756             =back
757              
758             =cut
759              
760             sub site_url {
761             my ($self, $host) = @_;
762             my ($pkg, $caller, $line) = caller;
763             warn "Using depricated method 'site_url' at $caller line $line."
764             unless $pkg =~ "WWW::Mediawiki::Client";
765             my $protocol = $self->protocol;
766             $host =~ s{^$protocol://}{} if $host;
767             $host = $self->host($host);
768             return "$protocol://" . $host if $host;
769             }
770              
771             =head1 Instance Methods
772              
773             =cut
774              
775             =head2 do_login
776              
777             $mvs->do_login;
778              
779             The C method operates like the cvs login command. The
780             C, C, and C attributes must be set before
781             attempting to login. Once C has been called successfully any
782             successful commit from the same directory will be logged in the Mediawiki
783             server as having been done by C.
784              
785             B
786              
787             =over
788              
789             =item WWW::Mediawiki::Client::AuthException
790              
791             =item WWW::Mediawiki::Client::CookieJarException
792              
793             =item WWW::Mediawiki::Client::LoginException
794              
795             =item WWW::Mediawiki::Client::URLConstructionException
796              
797             =back
798              
799             =cut
800              
801             sub do_login {
802             my $self = shift;
803             WWW::Mediawiki::Client::URLConstructionException->throw(
804             "No Mediawiki host specified.")
805             unless $self->host;
806             WWW::Mediawiki::Client::URLConstructionException->throw(
807             "No wiki_path specified.")
808             unless $self->wiki_path;
809             WWW::Mediawiki::Client::AuthException->throw(
810             "Must have username and password to login.")
811             unless $self->username && $self->password;
812             my $host = $self->host;
813             my $path = $self->wiki_path;
814             my $lang = $self->language_code;
815             $host =~ s/__LANG__/$lang/;
816             $path =~ s/__LANG__/$lang/;
817             my $protocol = $self->protocol;
818             my $url = "$protocol://$host/$path"
819             . "?" . ACTION . "=" . LOGIN
820             . "&" . TITLE . "=" . LOGIN_TITLE;
821             $self->{ua}->cookie_jar->clear;
822             $self->{ua}->cookie_jar->save
823             or WWW::Mediawiki::Client::CookieJarException->throw(
824             "Could not save cookie jar.");
825             my $res = $self->{ua}->request(POST $url,
826             [
827             &USERNAME_NAME => $self->username,
828             &PASSWORD_NAME => $self->password,
829             &REMEMBER_NAME => 1,
830             &LOGIN_SUBMIT_NAME => &LOGIN_SUBMIT_VALUE,
831             ]
832             );
833             # success == Mediawiki gave us a Password cookie
834             if ($self->{ua}->cookie_jar->as_string =~ /UserID=/) {
835             $self->encoding($self->_get_server_encoding);
836             $self->save_state;
837             $self->{ua}->cookie_jar->save
838             or WWW::Mediawiki::Client::CookieJarException->throw(
839             "Could not save cookie jar.");
840             return $self;
841             } elsif ($res->is_success) { # got a page, but not what we wanted
842             WWW::Mediawiki::Client::LoginException->throw(
843             error => "Login did not work, please check username and password.\n",
844             res => $res,
845             cookie_jar => $self->{ua}->cookie_jar,
846             );
847             } else { # something else went wrong, send all the data in exception
848             my $err = "Login to $url failed.";
849             WWW::Mediawiki::Client::LoginException->throw(
850             error => $err,
851             res => $res,
852             cookie_jar => $self->{ua}->cookie_jar,
853             );
854             }
855             }
856              
857             =head2 do_li
858            
859             $mvs->do_li;
860              
861             An alias for C.
862              
863             =cut
864              
865             sub do_li {
866             do_login(@_);
867             }
868              
869             =head2 do_update
870            
871             $self->do_update($filename, ...);
872              
873             The C method operates like a much-simplified version of the cvs
874             update command. The argument is a list of filenames, whose contents will
875             be compared to the version on the WikiMedia server and to a locally stored
876             reference copy. Lines which have changed only in the server version will
877             be merged into the local version, while lines which have changed in both
878             the server and local version will be flagged as possible conflicts, and
879             marked as such, somewhate in the manner of cvs (actually this syntax comes
880             from the default conflict behavior of VCS::Lite):
881              
882             ********************Start of conflict 1 Insert to Primary, Insert to Secondary ************************************************************
883              
884             The line as it appears on the server
885              
886             ****************************************************************************************************
887              
888             The line as it appears locally
889             ********************End of conflict 1********************************************************************************
890              
891             After the merging, and conflict marking is complete the server version will
892             be copied into the reference version.
893              
894             If either the reference version or the local version are empty, or if
895             either file does not exist they will both be created as a copy of the
896             current server version.
897              
898             B
899              
900             =over
901              
902             =item WWW::Mediawiki::Client::URLConstructionException
903              
904             =item WWW::Mediawiki::Client::FileAccessException
905              
906             =item WWW::Mediawiki::Client::FileTypeException
907              
908             =item WWW::Mediawiki::Client::ServerPageException
909              
910             =item WWW::Mediawiki::Client::AbsoluteFileNameException
911              
912             =back
913              
914             =cut
915              
916             sub do_update {
917             my ($self, @files) = @_;
918             @files = $self->list_wiki_files unless @files;
919             WWW::Mediawiki::Client::URLConstructionException->throw(
920             "No server URL specified.") unless $self->{host};
921             my %pages;
922             my %dirs;
923             foreach my $filename (@files) {
924             my ($vol, $dirs, $fn) = $self->_check_path($filename);
925             my $pagename = $self->filename_to_pagename($filename);
926             $pages{$filename} = $pagename;
927             $dirs{$filename} = $dirs;
928             }
929             $self->_get_exported_pages(values %pages);
930             foreach my $filename (@files) {
931             my $pagename = $pages{$filename};
932             my $dirs = $dirs{$filename};
933             my $status = $self->_update_core($filename, $pagename, $dirs);
934             $self->{status}->{$filename} = $status;
935             }
936             return $self->status;
937             }
938              
939             sub _update_core {
940             my ($self, $filename, $pagename, $dirs) = @_;
941             my $sv = $self->get_server_page($pagename);
942             my $lv = $self->get_local_page($filename);
943             my $rv = $self->_get_reference_page($filename);
944             my $nv = $self->_merge($filename, $rv, $sv, $lv);
945             my $status = $self->_get_update_status($rv, $sv, $lv, $nv);
946             return unless $status; # nothing changes, nothing to do
947             return $status
948             if $status eq STATUS_LOCAL_ADDED
949             or $status eq STATUS_UNKNOWN
950             or $status eq STATUS_UNCHANGED;
951             # save the newly retrieved and/or merged version as our local copy
952             my @dirs = split '/', $dirs;
953             for my $d (@dirs) {
954             mkdir $d;
955             chdir $d;
956             }
957             for (@dirs) {
958             chdir '..';
959             }
960             open OUT, ">:utf8", $filename or WWW::Mediawiki::Client::FileAccessException->throw(
961             "Cannot open $filename for writing.");
962             print OUT $nv;
963             # save the server version out as the reference file
964             $filename = $self->_get_ref_filename($filename);
965             open OUT, ">:utf8", $filename or WWW::Mediawiki::Client::FileAccessException->throw(
966             "Cannot open $filename for writing.");
967             print OUT $sv;
968             close OUT;
969             return $status;
970             }
971              
972             =head2 do_up
973              
974             An alias for C.
975              
976             =cut
977              
978             sub do_up {
979             do_update(@_);
980             }
981              
982             =head2 do_commit
983            
984             $self->do_commit($filename);
985              
986             As with C the C method operates like a much
987             simplified version of the cvs commit command. Again, the argument is a
988             filename. In keeping with the operation of cvs, C does not
989             automatically do an update, but does check the server version against the
990             local reference copy, throwing an error if the server version has changed,
991             thus forcing the user to do an update. A different error is thrown if the
992             conflict pattern sometimes created by C is found.
993              
994             After the error checking is done the local copy is submitted to the server,
995             and, if all goes well, copied to the local reference version.
996              
997             B
998              
999             =over
1000              
1001             =item WWW::Mediawiki::Client::CommitMessageException
1002              
1003             =item WWW::Mediawiki::Client::ConflictsPresentException
1004              
1005             =item WWW::Mediawiki::Client::FileAccessException
1006              
1007             =item WWW::Mediawiki::Client::FileTypeException
1008              
1009             =item WWW::Mediawiki::Client::URLConstructionException
1010              
1011             =item WWW::Mediawiki::Client::UpdateNeededException
1012              
1013             =item WWW::Mediawiki::Client::InvalidOptionException
1014              
1015             =back
1016              
1017             =cut
1018              
1019             sub do_commit {
1020             my ($self, $filename) = @_;
1021             WWW::Mediawiki::Client::CommitMessageException->throw(
1022             "No commit message specified")
1023             unless $self->{commit_message};
1024             # Perform the actual upload:
1025             my ($res, $text) = $self->_upload_file($filename, 1);
1026             # save the local version as the reference version
1027             my $refname = $self->_get_ref_filename($filename);
1028             open OUT, ">:utf8", $refname
1029             or WWW::Mediawiki::Client::FileAccessException->throw(
1030             "Cannot open $refname for writing.");
1031             print OUT $text;
1032             close OUT;
1033             }
1034              
1035             =head2 do_com
1036              
1037             This is an alias for C.
1038              
1039             =cut
1040              
1041             sub do_com {
1042             do_commit(@_);
1043             }
1044              
1045             =head2 do_preview
1046            
1047             $self->do_preview($filename);
1048              
1049             The C method is a non-writing version of the C
1050             method. It uploads the given filename to test its formatting. Its
1051             behaviour and arguments are identical to C.
1052              
1053             The behaviour of C is currently based on the environment.
1054             If C is set, this program (typically a web browser) will
1055             be launched on a temporary file. Otherwise, the preview will be saved
1056             to the file specified by the C variable, or preview.html
1057             if this is unset. This behaviour is considered a prototype for future
1058             functionality, and is C in the near future.
1059              
1060             Returns the name of the preview file, or undef if the file was sent to
1061             a web browser.
1062              
1063             B
1064              
1065             =over
1066              
1067             =item WWW::Mediawiki::Client::ConflictsPresentException
1068              
1069             =item WWW::Mediawiki::Client::FileAccessException
1070              
1071             =item WWW::Mediawiki::Client::FileTypeException
1072              
1073             =item WWW::Mediawiki::Client::URLConstructionException
1074              
1075             =item WWW::Mediawiki::Client::UpdateNeededException
1076              
1077             =back
1078              
1079             =cut
1080              
1081             sub do_preview {
1082             my ($self, $filename) = @_;
1083             my ($response) = $self->_upload_file($filename, 0);
1084             my $url = encode_entities($response->request->uri);
1085             my $content = $response->decoded_content;
1086             $content =~ s##$&#;
1087             my $browser = $ENV{MVS_BROWSER};
1088             if (defined($browser)) {
1089             my $fh = new File::Temp(UNLINK => 1, SUFFIX => '.html');
1090             print $fh Encode::encode_utf8($content);
1091             $fh->close;
1092             system($browser, $fh->filename);
1093             return undef;
1094             }
1095             my $preview = $ENV{MVS_PREVIEW};
1096             $preview = 'preview.html' unless defined($preview);
1097             open(PREVIEW, '>', $preview)
1098             or WWW::Mediawiki::Client::FileAccessException->throw(
1099             "Cannot open $preview for writing.");
1100             print PREVIEW $content;
1101             close(PREVIEW) or WWW::Mediawiki::Client::FileAccessException->throw(
1102             "Cannot close $preview.");
1103             print STDERR "Saved preview: $preview\n";
1104             return $preview;
1105             }
1106              
1107             =head2 do_clean
1108              
1109             $self->do_clean;
1110              
1111             Removes all reference files under the current directory that have no
1112             corresponding Wiki files.
1113              
1114             B
1115              
1116             =over
1117              
1118             =item WWW::Mediawiki::Client::FileAccessException
1119              
1120             =back
1121              
1122             =cut
1123              
1124             sub do_clean {
1125             my ($self) = @_;
1126              
1127             my $dir = File::Spec->curdir();
1128             find(sub {
1129             return unless m/^\..*\.ref\.wiki\z/s;
1130              
1131             my $name = $File::Find::name;
1132             $name = File::Spec->abs2rel($name);
1133              
1134             my $wiki = $self->_ref_to_filename($name);
1135             return if -e $wiki;
1136              
1137             warn "Deleting: $name\n";
1138             unlink($name)
1139             or WWW::Mediawiki::Client::FileAccessException->throw(
1140             "Cannot delete reference file $name");
1141             }, $dir);
1142             }
1143              
1144             =head2 save_state
1145            
1146             $mvs->save_state;
1147              
1148             Saves the current state of the wmc object in the current working directory.
1149              
1150             B
1151              
1152             =over
1153              
1154             =item WWW::Mediawiki::Client::FileAccessException
1155              
1156             =back
1157              
1158             =cut
1159              
1160             sub save_state {
1161             my $self = shift;
1162             my $conf = CONFIG_FILE;
1163             my %init;
1164             foreach my $attr (SAVED_ATTRIBUTES) {
1165             $init{$attr} = $self->$attr;
1166             }
1167             open OUT, ">:utf8", $conf or WWW::Mediawiki::Client::FileAccessException->throw(
1168             "Cannot write to config file, $conf.");
1169             print OUT Dumper(\%init);
1170             close OUT;
1171             }
1172              
1173             =head2 load_state
1174              
1175             $mvs = $mvs->load_state;
1176              
1177             Loads the state of the wmc object from that saved in the current working
1178             directory.
1179              
1180             B
1181              
1182             =over
1183              
1184             =item WWW::Mediawiki::Client::CorruptedConfigFileException
1185              
1186             =back
1187              
1188             =cut
1189              
1190             sub load_state {
1191             my $self = shift;
1192             my $config = CONFIG_FILE;
1193             return $self unless -e $config;
1194             our $VAR1;
1195             do $config or
1196             WWW::Mediawiki::Client::CorruptedConfigFileException->throw(
1197             "Could not read config file: $config.");
1198             my %init = %$VAR1;
1199             foreach my $attr (SAVED_ATTRIBUTES) {
1200             $self->$attr($init{$attr});
1201             }
1202             return $self;
1203             }
1204              
1205             =head2 get_server_page
1206              
1207             my $wikitext = $mvs->get_server_page($pagename);
1208              
1209             Returns the wikitext of the given Mediawiki page name.
1210              
1211             B
1212              
1213             =over
1214              
1215             =item WWW::Mediawiki::Client::ServerPageException
1216              
1217             =back
1218              
1219             =cut
1220              
1221             sub get_server_page {
1222             my ($self, $pagename) = @_;
1223              
1224             my $export = delete $self->{export}->{$pagename};
1225             return $export if defined($export);
1226              
1227             my $url = $self->pagename_to_url($pagename, EDIT);
1228             my $res = $self->{ua}->get($url);
1229             WWW::Mediawiki::Client::ServerPageException->throw(
1230             error => "Couldn't fetch \"$pagename\" from the server.",
1231             res => $res,
1232             ) unless $res->is_success;
1233             my $doc = $res->decoded_content;
1234             my $text = $self->_get_wiki_text($doc);
1235             $self->{edit}->{date} = $self->_get_edit_date($doc);
1236             $self->{edit}->{token} = $self->_get_edit_token($doc);
1237             $self->{edit}->{watch_now} = $self->_get_edit_is_watching($doc);
1238             $self->{edit}->{def_watch} = $self->_get_edit_watch_default($doc);
1239             $self->{edit}->{def_minor} = $self->_get_edit_minor_default($doc);
1240             my $headline = Encode::encode("utf8", $self->_get_page_headline($doc));
1241             my $expected = lc $pagename;
1242             unless (lc($headline) =~ /\Q$expected\E$/) {
1243             WWW::Mediawiki::Client::ServerPageException->throw(
1244             error => "The server could not resolve the page name
1245             '$pagename', but responded that it was '$headline'.",
1246             res => $res,
1247             ) if ($headline && $headline =~ /^Editing /);
1248             WWW::Mediawiki::Client::ServerPageException->throw(
1249             error => "Error message from the server: '$headline'.",
1250             res => $res,
1251             ) if ($headline);
1252             WWW::Mediawiki::Client::ServerPageException->throw(
1253             error => "Could not identify the error in this context.",
1254             res => $res,
1255             );
1256             }
1257             chomp $text;
1258             return $text;
1259             }
1260              
1261             =head2 get_local_page
1262              
1263             my $wikitext = $mvs->get_local_page($filename);
1264              
1265             Returns the wikitext from the given local file;
1266              
1267             B
1268              
1269             =over
1270              
1271             =item WWW::Mediawiki::Client::FileAccessException
1272              
1273             =item WWW::Mediawiki::Client::FileTypeException
1274              
1275             =item WWW::Mediawiki::Client::AbsoluteFileNameException
1276              
1277             =back
1278              
1279             =cut
1280              
1281             sub get_local_page {
1282             my ($self, $filename) = @_;
1283             $self->_check_path($filename);
1284             return '' unless -e $filename;
1285             open IN, "<:utf8", $filename or
1286             WWW::Mediawiki::Client::FileAccessException->throw(
1287             "Cannot open $filename.");
1288             local $/;
1289             my $text = ;
1290             close IN;
1291             return $text;
1292             }
1293              
1294             =head2 pagename_to_url
1295              
1296             my $url = $mvs->pagename_to_url($pagename);
1297              
1298             Returns the url at which a given pagename will be found on the Mediawiki
1299             server to which this instance of points.
1300              
1301             B
1302              
1303             =over
1304              
1305             =item WWW::Mediawiki::Client::URLConstructionException;
1306              
1307             =back
1308              
1309             =cut
1310              
1311             sub pagename_to_url {
1312             my ($self, $name, $action) = @_;
1313             WWW::Mediawiki::Client::URLConstructionException->throw(
1314             error => 'No action supplied.',
1315             ) unless $action;
1316             WWW::Mediawiki::Client::URLConstructionException->throw(
1317             error => "Page name $name ends with '.wiki'.",
1318             ) if $name =~ /.wiki$/;
1319             my $char = $self->space_substitute;
1320             $name =~ s/ /$char/;
1321             my $lang = $self->language_code;
1322             my $host = $self->host;
1323             $host =~ s/__LANG__/$lang/g;
1324             my $wiki_path = $self->wiki_path;
1325             $wiki_path =~ s/__LANG__/$lang/g;
1326             my $protocol = $self->protocol;
1327             return "$protocol://$host/$wiki_path?" . ACTION . "=$action&" . TITLE . "=$name";
1328             }
1329              
1330             =head2 filename_to_pagename
1331              
1332             my $pagename = $mvs->filname_to_pagename($filename);
1333              
1334             Returns the cooresponding server page name given a filename.
1335              
1336             B
1337              
1338             =over
1339              
1340             =item WWW::Mediawiki::Client::AbsoluteFileNameException
1341              
1342             =item WWW::Mediawiki::Client::FileTypeException
1343              
1344             =back
1345              
1346             =cut
1347              
1348             sub filename_to_pagename {
1349             my ($self, $name) = @_;
1350             $self->_check_path($name);
1351             $name =~ s/.wiki$//;
1352              
1353             $self->{escape_filenames} and $name = decode('UTF-8', URI::Escape::uri_unescape($name));
1354              
1355             $name =~ s/_/ /g;
1356             return ucfirst $name;
1357             }
1358              
1359             =head2 filename_to_url
1360              
1361             my $pagename = $mvs->filname_to_url($filename);
1362              
1363             Returns the cooresponding server URL given a filename.
1364              
1365             B
1366              
1367             =over
1368              
1369             =item WWW::Mediawiki::Client::AbsoluteFileNameException
1370              
1371             =item WWW::Mediawiki::Client::FileTypeException
1372              
1373             =back
1374              
1375             =cut
1376              
1377             sub filename_to_url {
1378             my ($self, $name, $action) = @_;
1379             $name = $self->filename_to_pagename($name);
1380             return $self->pagename_to_url($name, $action);
1381             }
1382              
1383             =head2 pagename_to_filename
1384              
1385             my $filename = $mvs->pagename_to_filename($pagename);
1386              
1387             Returns a local filename which cooresponds to the given Mediawiki page
1388             name.
1389              
1390             =cut
1391              
1392             sub pagename_to_filename {
1393             my ($self, $name) = @_;
1394             $name =~ s/ /_/;
1395              
1396             $self->{escape_filenames} and $name = URI::Escape::uri_escape_utf8($name);
1397            
1398             $name .= '.wiki';
1399             return $name;
1400             }
1401              
1402             =head2 url_to_filename
1403            
1404             my $filename = $mvs->url_to_filename($url);
1405              
1406             Returns the local filename which cooresponds to a given URL.
1407              
1408             =cut
1409              
1410             sub url_to_filename {
1411             my ($self, $url) = @_;
1412             my $char = '\\' . $self->space_substitute;
1413             $url =~ s/$char/_/g;
1414             my $title = TITLE;
1415             $url =~ m/&$title=([^&]*)/;
1416             return "$1.wiki";
1417             }
1418              
1419             =head2 list_wiki_files
1420              
1421             @filenames = $mvs->list_wiki_files;
1422              
1423             Returns a recursive list of all wikitext files in the local repository.
1424              
1425             =cut
1426              
1427             sub list_wiki_files {
1428             my $self = shift;
1429             my @files;
1430             my $dir = File::Spec->curdir();
1431             find(sub {
1432             return unless /^[^.].*\.wiki\z/s;
1433             my $name = $File::Find::name;
1434             $name = File::Spec->abs2rel($name);
1435             push @files, $name;
1436             }, $dir);
1437             return @files;
1438             }
1439              
1440             =begin comment
1441              
1442             =head1 Private Methods
1443              
1444             =cut
1445              
1446             sub _merge {
1447             my ($self, $filename, $ref, $server, $local) = @_;
1448             my $control = {
1449             in => $\,
1450             out => $/,
1451             chomp => 1
1452             };
1453             $ref = VCS::Lite->new('ref', "\n", "$ref\n");
1454             $server = VCS::Lite->new('server', "\n", "$server\n");
1455             $local = VCS::Lite->new('local', "\n", "$local\n");
1456             my $merge = $ref->merge($server, $local);
1457             return scalar $merge->text();
1458             }
1459              
1460             sub _get_wiki_text {
1461             my ($self, $doc) = @_;
1462             my $p = HTML::TokeParser->new(\$doc);
1463             $p->get_tag("textarea");
1464             my $text = $p->get_text;
1465             $text =~ s/ //gs; # convert endlines
1466             return $text;
1467             }
1468              
1469             sub _get_server_encoding {
1470             my ($self) = @_;
1471             my $url = $self->_get_version_url;
1472             my $res = $self->{ua}->get($url);
1473             my $doc = $res->decoded_content;
1474             my $p = HTML::TokeParser->new(\$doc);
1475             while ( my $t = $p->get_tag("meta") ) {
1476             next unless defined $t->[1]->{'http-equiv'}
1477             and ($t->[1]->{'http-equiv'} eq 'Content-Type'
1478             or $t->[1]->{'http-equiv'} eq 'Content-type');
1479             my $cont = $t->[1]->{'content'};
1480             $cont =~ m/charset=(.*)/;
1481             return $1;
1482             }
1483             }
1484              
1485             sub _get_page_headline {
1486             my ($self, $doc) = @_;
1487             my $p = HTML::TokeParser->new(\$doc);
1488             $p->get_tag("h1");
1489             my $text = $p->get_text;
1490             $text =~ s/ //gs; # convert endlines
1491             return $text;
1492             }
1493              
1494             sub _get_edit_date {
1495             my ($self, $doc) = @_;
1496             my $p = HTML::TokeParser->new(\$doc);
1497             my $date;
1498             while (my $tag = $p->get_tag('input')) {
1499             next unless $tag->[1]->{type} eq 'hidden';
1500             next unless $tag->[1]->{name} eq EDIT_TIME_NAME;
1501             $date = $tag->[1]->{value};
1502             }
1503             return $date;
1504             }
1505              
1506             sub _get_edit_token {
1507             my ($self, $doc) = @_;
1508             my $p = HTML::TokeParser->new(\$doc);
1509             my $token;
1510             while (my $tag = $p->get_tag('input')) {
1511             next unless $tag->[1]->{type} eq 'hidden';
1512             next unless $tag->[1]->{name} eq 'wpEditToken';
1513             $token = $tag->[1]->{value};
1514             }
1515             return $token;
1516             }
1517              
1518             sub _get_edit_is_watching {
1519             my ($self, $doc, $name) = @_;
1520             my $p = HTML::TokeParser->new(\$doc);
1521             my $status;
1522             while (my $tag = $p->get_tag('a')) {
1523             next unless $tag->[1]->{href}
1524             && $tag->[1]->{href} =~ m/&action=((?:un)?watch)/;
1525             # If 'un'watch, then it's watched; otherwise, it's not.
1526             $status = ($1 eq 'unwatch' ? 1 : 0);
1527             }
1528             return $status;
1529             }
1530              
1531             sub _get_edit_checkbox {
1532             my ($self, $doc, $name) = @_;
1533             my $p = HTML::TokeParser->new(\$doc);
1534             my $status;
1535             while (my $tag = $p->get_tag('input')) {
1536             next unless $tag->[1]->{type} eq 'checkbox';
1537             next unless $tag->[1]->{name} eq $name;
1538             $status = ($tag->[1]->{checked} ? 1 : 0);
1539             }
1540             return $status;
1541             }
1542              
1543             sub _get_edit_watch_default {
1544             my ($self, $doc) = @_;
1545             return $self->_get_edit_checkbox($doc, EDIT_WATCH_NAME);
1546             }
1547              
1548             sub _get_edit_minor_default {
1549             my ($self, $doc) = @_;
1550             return $self->_get_edit_checkbox($doc, EDIT_MINOR_NAME);
1551             }
1552              
1553             sub _check_path {
1554             my ($self, $filename) = @_;
1555             WWW::Mediawiki::Client::FileTypeException->throw(
1556             "'$filename' doesn't appear to be a wiki file.")
1557             unless $filename =~ /\.wiki$/;
1558             WWW::Mediawiki::Client::AbsoluteFileNameException->throw(
1559             "No absolute filenames allowed!")
1560             if File::Spec->file_name_is_absolute($filename);
1561             return File::Spec->splitpath($filename);
1562             }
1563              
1564             sub _get_reference_page {
1565             my ($self, $filename) = @_;
1566             return '' unless -e $filename;
1567             $filename = $self->_get_ref_filename($filename);
1568             my $ref = $self->get_local_page($filename);
1569             return $ref;
1570             }
1571              
1572             sub _get_ref_filename {
1573             my ($self, $filename) = @_;
1574             WWW::Mediawiki::Client::FileTypeException->throw(
1575             "Not a .wiki file.") unless $filename =~ /\.wiki$/;
1576             my ($vol, $dirs, $fn) = File::Spec->splitpath($filename);
1577             $fn =~ s/(.*)\.wiki/.$1.ref.wiki/;
1578             return File::Spec->catfile('.', $dirs, $fn);
1579             }
1580              
1581             sub _ref_to_filename {
1582             my ($self, $ref) = @_;
1583             my ($vol, $dirs, $fn) = File::Spec->splitpath($ref);
1584             $fn =~ s/^\.(.*)\.ref\.wiki$/$1.wiki/
1585             or WWW::Mediawiki::Client::FileTypeException->throw(
1586             "Not a .ref.wiki file.");
1587             return File::Spec->catpath($vol, $dirs, $fn);
1588             }
1589              
1590             sub _conflicts_found_in {
1591             my ($self, $text) = @_;
1592             return 1 if $text =~ /Start of conflict 1/m;
1593             return 0;
1594             }
1595              
1596             sub _get_update_status {
1597             my ($self, $rv, $sv, $lv, $nv) = @_;
1598             chomp ($rv, $sv, $lv, $nv); # double chomp
1599             chomp ($rv, $sv, $lv, $nv); # it's a nasty hack, but necessary until we re-write
1600             return STATUS_CONFLICT if $self->_conflicts_found_in($nv);
1601             return STATUS_UNKNOWN unless $sv or $lv;
1602             return STATUS_LOCAL_ADDED unless $sv;
1603             return STATUS_UNCHANGED if $sv eq $lv;
1604             return STATUS_LOCAL_MODIFIED if $lv ne $rv;
1605             return STATUS_SERVER_MODIFIED if $rv ne $sv;
1606             return STATUS_UNKNOWN;
1607             }
1608              
1609             sub _get_host_url {
1610             my ($self) = @_;
1611             my $lang = $self->language_code;
1612             my $host = $self->host;
1613             $host =~ s/__LANG__/$lang/g;
1614             my $protocol = $self->protocol;
1615             return "$protocol://$host/";
1616             }
1617              
1618             sub _get_version_url {
1619             my ($self) = @_;
1620             my $lang = $self->language_code;
1621             my $path = $self->wiki_path;
1622             $path =~ s/__LANG__/$lang/g;
1623             return $self->_get_host_url
1624             . $path . '?' . TITLE . '=' . SPECIAL_VERSION;
1625             }
1626              
1627             sub _get_export_url {
1628             my ($self) = @_;
1629             my $lang = $self->language_code;
1630             my $path = $self->wiki_path;
1631             $path =~ s/__LANG__/$lang/g;
1632             return $self->_get_host_url
1633             . $path . '?' . TITLE . '=' . SPECIAL_EXPORT;
1634             }
1635              
1636             sub _get_exported_pages {
1637             my ($self, @pages) = @_;
1638             my $count = scalar @pages;
1639             my $url = $self->_get_export_url;
1640             my $response = $self->{ua}->request(POST $url, [
1641             pages => join("\n", @pages),
1642             action => 'submit',
1643             curonly => 'true',
1644             ]);
1645             WWW::Mediawiki::Client::ServerPageException->throw(
1646             error => "Couldn't fetch $count pages from the server.",
1647             res => $response,
1648             ) unless $response->is_success;
1649             my $parser = XML::LibXML->new;
1650             my $doc = $parser->parse_string($response->decoded_content);
1651             my %expecting = map {$_ => 1} @pages;
1652             my %export = ();
1653             my %timestamp = ();
1654             foreach my $node ($doc->findnodes('/mediawiki/page')) {
1655             my $page = $node->findvalue(TITLE);
1656             my $text = $node->findvalue('revision/text');
1657             my $time = $node->findvalue('revision/timestamp');
1658             WWW::Mediawiki::Client::ServerPageException->throw(
1659             error => "Server returned unexpected page '$page'.",
1660             res => $response) unless $expecting{$page};
1661             $export{$page} = $text;
1662             $timestamp{$page} = $time;
1663             }
1664             $self->{export} = \%export;
1665             $self->{timestamp} = \%timestamp;
1666             }
1667              
1668             sub _upload_file {
1669             my ($self, $filename, $commit) = @_;
1670             WWW::Mediawiki::Client::URLConstructionException->throw(
1671             "No server URL specified.") unless $self->{host};
1672             WWW::Mediawiki::Client::FileAccessException->throw("No such file!")
1673             unless -e $filename;
1674             WWW::Mediawiki::Client::CommitException->throw(
1675             'Could not determine charset for uploading to this server.'
1676             ) unless $self->encoding;
1677             my $text = $self->get_local_page($filename);
1678             my $pagename = $self->filename_to_pagename($filename);
1679             my $sp = $self->get_server_page($pagename);
1680             if ($self->{edit}->{date}) {
1681             my $ref = $self->_get_reference_page($filename);
1682             chomp ($sp, $ref);
1683             WWW::Mediawiki::Client::UpdateNeededException->throw(
1684             error => $self->filename_to_pagename($filename)
1685             . " has changed on the server.",
1686             ) unless $sp eq $ref;
1687             }
1688             chomp ($text);
1689             WWW::Mediawiki::Client::ConflictsPresentException->throw(
1690             "$filename appears to have unresolved conflicts")
1691             if $self->_conflicts_found_in($text);
1692             my @params;
1693             push(@params, EDIT_MINOR_NAME, CHECKED) if $self->_option_check(
1694             'minor_edit', $self->{minor_edit},
1695             $self->{edit}->{def_minor});
1696             push(@params, EDIT_WATCH_NAME, CHECKED) if $self->_option_check(
1697             'watch', $self->{watch},
1698             $self->{edit}->{def_watch}, $self->{edit}->{watch_now});
1699             my $act_name = ($commit ? EDIT_SUBMIT_NAME : EDIT_PREVIEW_NAME );
1700             my $act_value = ($commit ? EDIT_SUBMIT_VALUE : EDIT_PREVIEW_VALUE);
1701             my $url = $self->filename_to_url($filename, SUBMIT);
1702             my $octets = Encode::encode($self->encoding, $text);
1703             my $res = $self->{ua}->post($url,
1704             [
1705             $act_name => $act_value,
1706             &TEXTAREA_NAME => $octets,
1707             &COMMENT_NAME => $self->{commit_message},
1708             &EDIT_TIME_NAME => $self->{edit}->{date},
1709             &EDIT_TOKEN_NAME => $self->{edit}->{token},
1710             @params,
1711             ],
1712             );
1713             my $doc = $res->decoded_content;
1714             my $headline = $self->_get_page_headline($doc);
1715             my $expect = ($commit ? $pagename : "Editing $pagename");
1716             unless (lc($headline) eq lc($expect)) {
1717             WWW::Mediawiki::Client::CommitException->throw(
1718             error => "The page you are trying to commit appears to contain a link which is associated with Wikispam.",
1719             res => $res,
1720             ) if ($headline eq 'Spam protection filter');
1721             WWW::Mediawiki::Client::CommitException->throw(
1722             error => "When we tried to commit '$pagename' the server responded with '$headline'.",
1723             res => $res,
1724             ) if ($headline);
1725             }
1726             return ($res, $text);
1727             }
1728              
1729             sub _option_verify {
1730             my ($self, $name, $value, $r_accept) = @_;
1731              
1732             foreach my $acc (@{$r_accept}) {
1733             return 1 if $acc == $value;
1734             }
1735              
1736             my %opts = OPTION_SETTINGS;
1737             my $valstr = $opts{$value};
1738             $valstr = "value '$value'" unless defined($valstr);
1739              
1740             WWW::Mediawiki::Client::InvalidOptionException->throw(
1741             error => "Cannot set field $name to $valstr.",
1742             field => $name,
1743             option => $opts{$value},
1744             value => $value,
1745             );
1746             }
1747              
1748             sub _option_check {
1749             my ($self, $name, $value, $default, $current) = @_;
1750              
1751             return 1 if $value == OPT_YES;
1752             return 0 if $value == OPT_NO;
1753              
1754             if ($value == OPT_DEFAULT) {
1755             return $default if defined($default);
1756             WWW::Mediawiki::Client::InvalidOptionException->throw(
1757             error => "Field '$name' cannot use OPT_DEFAULT:"
1758             . " Default information could not be determined.",
1759             field => $name,
1760             option => 'OPT_DEFAULT',
1761             value => $value,
1762             );
1763             }
1764              
1765             if ($value == OPT_KEEP) {
1766             return $current if defined($current);
1767             WWW::Mediawiki::Client::InvalidOptionException->throw(
1768             error => "Field '$name' cannot use OPT_KEEP:"
1769             . " Current information could not be determined.",
1770             field => $name,
1771             option => 'OPT_DEFAULT',
1772             value => $value,
1773             );
1774             }
1775              
1776             # Should never happen; these are verified at assignment time.
1777             my %opts = OPTION_SETTINGS;
1778             WWW::Mediawiki::Client::InvalidOptionException->throw(
1779             error => "Field '$name' is in unknown state '$value'.",
1780             field => $name,
1781             option => $opts{$value},
1782             value => $value
1783             );
1784             }
1785              
1786             1;
1787              
1788             __END__