File Coverage

blib/lib/Confluence/Client/XMLRPC.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1              
2             package Confluence::Client::XMLRPC;
3             {
4             $Confluence::Client::XMLRPC::VERSION = '2.5';
5             }
6 1     1   24032 use strict;
  1         3  
  1         36  
7 1     1   6 use warnings;
  1         2  
  1         36  
8              
9             # ABSTRACT: Client for the Atlassian Confluence wiki, based on RPC::XML
10              
11             # Copyright (c) 2004 Asgeir.Nilsen@telenor.com
12             #
13             # This program is free software; you can redistribute it and/or modify
14             # it under the terms of the GNU General Public License as published by
15             # the Free Software Foundation; either version 2 of the License, or
16             # (at your option) any later version.
17             #
18             # This program is distributed in the hope that it will be useful,
19             # but WITHOUT ANY WARRANTY; without even the implied warranty of
20             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21             # GNU General Public License for more details.
22             #
23             # You should have received a copy of the GNU General Public License
24             # along with this program; if not, write to the Free Software
25             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26              
27             # Version 2.1.1 changes by Torben K. Jensen
28             # + Support for automatic reconnect upon session expiration.
29              
30 1     1   463 use RPC::XML;
  0            
  0            
31             use RPC::XML::Client;
32             use Env qw(CONFLDEBUG);
33             use Carp;
34             use vars '$AUTOLOAD'; # keep 'use strict' happy
35              
36             our $AUTO_SESSION_RENEWAL = 1;
37              
38             use fields qw(url user pass token client _cflVersion _serverInfo);
39              
40             # Global variables
41             our $API = 'confluence1';
42             our $RaiseError = 1;
43             our $PrintError = 0;
44             our $LastError = '';
45              
46             # For debugging
47             sub _debugPrint {
48             require Data::Dumper;
49             local $Data::Dumper::Terse = 1;
50             local $Data::Dumper::Indent = 0;
51             local $Data::Dumper::Quotekeys = 0;
52             print STDERR ( shift @_ );
53             print STDERR ( Data::Dumper::Dumper($_) . ( scalar @_ ? ', ' : '' ) ) while ( $_ = shift @_ );
54             print STDERR "\n";
55             }
56              
57             sub setRaiseError {
58             shift if ref $_[0];
59             shift if $_[0] eq __PACKAGE__;
60             carp "setRaiseError expected scalar"
61             unless defined $_[0] and not ref $_[0];
62             my $old = $RaiseError;
63             $RaiseError = $_[0];
64             return $old;
65             }
66              
67             sub setPrintError {
68             shift if ref $_[0];
69             shift if $_[0] eq __PACKAGE__;
70             carp "setPrintError expected scalar"
71             unless defined $_[0] and not ref $_[0];
72             my $old = $PrintError;
73             $PrintError = $_[0];
74             return $old;
75             }
76              
77             sub setApiVersion {
78             shift if ref $_[0];
79             shift if $_[0] eq __PACKAGE__;
80             my $new = shift;
81             carp "setApiVersion expected scalar"
82             unless defined $new and not ref $new;
83             my $old = $API;
84              
85             if ( defined($new) and $new =~ /\A(?:confluence)?([1-9])\Z/i ) {
86             $API = 'confluence' . $1;
87             }
88              
89             return $old;
90             }
91              
92             sub lastError {
93             return $LastError;
94             }
95              
96             # This function converts scalars to RPC::XML strings
97             sub argcopy {
98             my ( $arg, $depth ) = @_;
99             return $arg if $depth > 1;
100             my $typ = ref $arg;
101             if ( !$typ ) {
102             if ( defined($arg) and ( $arg eq 'true' or $arg eq 'false' ) and $depth == 0 ) {
103             return new RPC::XML::boolean($arg);
104             }
105             else {
106             return new RPC::XML::string($arg);
107             }
108             }
109             if ( $typ eq "HASH" ) {
110             my %hash;
111             foreach my $key ( keys %$arg ) {
112             $hash{$key} = argcopy( $arg->{$key}, $depth + 1 );
113             }
114             return \%hash;
115             }
116             if ( $typ eq "ARRAY" ) {
117             my @array = map { argcopy( $_, $depth + 1 ) } @$arg;
118             return \@array;
119             }
120             return $arg;
121             }
122              
123             sub new {
124             my Confluence::Client::XMLRPC $self = shift;
125             my ( $url, $user, $pass, $version ) = @_;
126             unless ( ref $self ) {
127             $self = fields::new($self);
128             }
129             $self->{url} = $url;
130             $self->{user} = $user;
131             $self->{pass} = $pass;
132              
133             $API = 'confluence1';
134             if ( defined($version) and $version =~ /\A(?:confluence)?([1-9])\Z/i ) {
135             $API = 'confluence' . $1;
136             }
137              
138             warn "Creating client connection to $url" if $CONFLDEBUG;
139             $self->{client} = new RPC::XML::Client $url;
140             warn "Logging in $user" if $CONFLDEBUG;
141             my $result = $self->{client}->simple_request( "$API.login", $user, $pass );
142             $LastError
143             = defined($result)
144             ? (
145             ref($result) eq 'HASH'
146             ? ( exists $result->{faultString} ? "REMOTE ERROR: " . $result->{faultString} : '' )
147             : ''
148             )
149             : "XML-RPC ERROR: Unable to connect to " . $self->{url};
150             _debugPrint( "Result=", $result ) if $CONFLDEBUG;
151              
152             if ($LastError) {
153             croak $LastError if $RaiseError;
154             warn $LastError if $PrintError;
155             $self->{token} = '';
156             return '';
157             }
158              
159             $self->{token} = $result;
160              
161             _debugPrint( "Checking Confluence server version" ) if $CONFLDEBUG;
162             my $serverInfo = _rpc( $self, 'getServerInfo' );
163             if ( !defined($serverInfo) or ref($serverInfo) ne ref({}) ) {
164             croak "Unable to determine Confluence version: aborting" if $RaiseError;
165             warn "Unable to determine Confluence version: aborting" if $PrintError;
166             $self->{token} = '';
167             return '';
168             }
169             $self->{'_serverInfo'} = $serverInfo;
170             $self->{'_cflVersion'} = sprintf( "%03s%03s%03s", @{ $serverInfo }{ 'majorVersion', 'minorVersion', 'patchLevel' } );
171              
172             # set default API version based on Confluence version (unless explicitly given)
173             unless ( defined($version) and $version =~ /\A(?:confluence)?([1-9])\Z/i ) {
174             if ( $self->{'_cflVersion'} ge '004000000' ) {
175             $API = 'confluence2';
176             }
177             else {
178             $API = 'confluence1';
179             }
180             }
181             return $self;
182             } ## end sub new
183              
184             # login is an alias for new
185             sub login {
186             return new @_;
187             }
188              
189             sub getServerInfo {
190             my Confluence::Client::XMLRPC $self = shift;
191             if ($CONFLDEBUG) {
192             _debugPrint("Retrieving serverInfo from local cache");
193             _debugPrint( "Result=", $self->{'_serverInfo'} );
194             }
195             return $self->{'_serverInfo'};
196             }
197              
198             sub getPageSummary {
199             my Confluence::Client::XMLRPC $self = shift;
200              
201             if ( $self->{'_cflVersion'} ge "004000000" ) {
202             return _rpc( $self, 'getPageSummary', @_ );
203             }
204             else {
205             # Emulate method on older Confluence versions
206             if ( my $page = _rpc( $self, 'getPage', @_ ) ) {
207             my (%data) = map { $_ => $page->{$_} } grep { /\A(?:id|parentId|permissions|space|title|url|version)\Z/ } keys %{$page};
208             return \%data;
209             }
210             else {
211             return '';
212             }
213             }
214             }
215              
216             sub updatePage {
217             my Confluence::Client::XMLRPC $self = shift;
218             my $page = shift;
219             my $pageUpdateOptions = ( shift || {} );
220              
221             if ( $self->{'_cflVersion'} ge "002010000" ) {
222             _debugPrint("Using API method updatePage() for Confluence >= 2.10") if $CONFLDEBUG;
223             return _rpc( $self, 'updatePage', $page, $pageUpdateOptions );
224             }
225             else {
226             _debugPrint("Trying to emulate updatePage() for Confluence < 2.10") if $CONFLDEBUG;
227             if ( my $existingPage = $self->getPage( $page->{'id'} ) ) {
228             my %new = ();
229             foreach my $key ( keys %{$existingPage} ) {
230             $new{$key} = $existingPage->{$key};
231             }
232             foreach my $key ( keys %{$page} ) {
233             $new{$key} = $page->{$key};
234             }
235             return _rpc( $self, 'storePage', \%new );
236             }
237             }
238             return '';
239             }
240              
241             sub updateOrStorePage {
242             my Confluence::Client::XMLRPC $self = shift;
243             my $newPage = shift;
244              
245             my $couldUpdate = 1;
246             foreach my $field ( qw( id space title content version ) ) {
247             $couldUpdate--, last unless exists $newPage->{$field};
248             }
249              
250             # do we have all necessary data for calling the updatePage method?
251             if ($couldUpdate) {
252             return $self->updatePage( $newPage, @_ );
253             }
254             elsif ( exists( $newPage->{'id'} ) ) {
255              
256             # something is missing, but we might be able to get it from Confluence
257             # check if page already exists
258             my ( $raise, $print ) = ( setRaiseError(0), setPrintError(0) );
259             my $oldPage = $self->getPageSummary( $newPage->{'id'} );
260             setRaiseError($raise);
261             setPrintError($print);
262             if ( defined($oldPage) and ref($oldPage) eq ref({}) ) {
263             foreach my $key ( keys %{$newPage} ) {
264             $oldPage->{$key} = $newPage->{$key}
265             }
266             return $self->updatePage( $oldPage, @_ );
267             }
268             }
269             elsif ( exists( $newPage->{'space'} ) and exists( $newPage->{'title'} ) ) {
270              
271             # can store new page with these two fields
272             # check if page already exists
273             my ( $raise, $print ) = ( setRaiseError(0), setPrintError(0) );
274             my $oldPage = $self->getPageSummary( $newPage->{'space'}, $newPage->{'title'} );
275             setRaiseError($raise);
276             setPrintError($print);
277             if ( defined($oldPage) and ref($oldPage) eq ref({}) ) {
278             foreach my $key ( keys %{$newPage} ) {
279             $oldPage->{$key} = $newPage->{$key}
280             }
281             return $self->updatePage( $oldPage, @_ );
282             }
283             }
284              
285             # might as well fail...
286             return _rpc( $self, 'storePage', $newPage );
287             }
288              
289             sub _rpc {
290             my Confluence::Client::XMLRPC $self = shift;
291             my $method = shift;
292             croak "ERROR: Not connected" unless $self->{token};
293             my @args = map { argcopy( $_, 0 ) } @_;
294             _debugPrint( "Sending $API.$method ", @args ) if $CONFLDEBUG;
295             my $result = $self->{client}->simple_request( "$API.$method", $self->{token}, @args );
296             $LastError
297             = defined($result)
298             ? (
299             ref($result) eq 'HASH'
300             ? (
301             exists $result->{faultString}
302             ? "REMOTE ERROR: " . $result->{faultString}
303             : ''
304             )
305             : ''
306             )
307             : defined $RPC::XML::ERROR ? $RPC::XML::ERROR
308             : "XML-RPC ERROR: Unable to connect to " . $self->{url};
309              
310             _debugPrint( "Result=", $result ) if $CONFLDEBUG;
311              
312             if ( ( $LastError =~ /InvalidSessionException/i ) && $AUTO_SESSION_RENEWAL ) {
313              
314             # Session time-out; log back in.
315             warn "SESSION EXPIRED: Reconnecting...\n" if $PrintError;
316             my ( $url, $user, $pass ) = ( $self->{url}, $self->{user}, $self->{pass} );
317             $self->{pass} = ''; # Prevent repeated attempts.
318             if ( my $clone = Confluence::Client::XMLRPC->new( $url, $user, $pass, $API ) ) {
319             $self->{token} = $clone->{token};
320             $self->{'_cflVersion'} = $clone->{'_cflVersion'};
321             $self->{'_serverInfo'} = $clone->{'_serverInfo'};
322              
323             $result = _rpc( $self, $method, @_ );
324             $self->{pass} = $pass;
325             }
326             }
327             if ($LastError) {
328             croak $LastError if $RaiseError;
329             warn $LastError if $PrintError;
330             }
331             return $LastError ? '' : $result;
332             } ## end sub _rpc
333              
334             # Define commonly used functions to avoid overhead of autoload
335             sub getPage {
336             my Confluence::Client::XMLRPC $self = shift;
337             _rpc( $self, 'getPage', @_ );
338             }
339              
340             sub storePage {
341             my Confluence::Client::XMLRPC $self = shift;
342             _rpc( $self, 'storePage', @_ );
343             }
344              
345             # Use autolaod for everything else
346             sub AUTOLOAD {
347             my Confluence::Client::XMLRPC $self = shift;
348             $AUTOLOAD =~ s/Confluence::Client::XMLRPC:://;
349             return if $AUTOLOAD =~ /DESTROY/;
350             _rpc( $self, $AUTOLOAD, @_ );
351             }
352              
353             1;
354              
355             __END__