File Coverage

blib/lib/Socialtext/Resting.pm
Criterion Covered Total %
statement 207 395 52.4
branch 61 134 45.5
condition 18 66 27.2
subroutine 27 59 45.7
pod 41 42 97.6
total 354 696 50.8


line stmt bran cond sub pod time code
1             package Socialtext::Resting;
2              
3 2     2   46495 use strict;
  2         5  
  2         67  
4 2     2   10 use warnings;
  2         5  
  2         53  
5              
6 2     2   1789 use URI::Escape;
  2         4409  
  2         139  
7 2     2   273776 use LWP::UserAgent;
  2         4987367  
  2         67  
8 2     2   16 use HTTP::Request;
  2         3  
  2         51  
9 2     2   2015 use Class::Field 'field';
  2         193048  
  2         130  
10 2     2   2171 use JSON::XS;
  2         15603  
  2         127  
11              
12 2     2   1694 use Readonly;
  2         7875  
  2         4005  
13              
14             our $VERSION = '0.38';
15              
16             =head1 NAME
17              
18             Socialtext::Resting - module for accessing Socialtext REST APIs
19              
20             =head1 SYNOPSIS
21              
22             use Socialtext::Resting;
23             my $Rester = Socialtext::Resting->new(
24             username => $opts{username},
25             password => $opts{password},
26             server => $opts{server},
27             );
28             $Rester->workspace('wikiname');
29             $Rester->get_page('my_page');
30             }
31              
32             =head1 DESCRIPTION
33              
34             C is a module designed to allow remote access
35             to the Socialtext REST APIs for use in perl programs.
36              
37             =head1 METHODS
38              
39             =cut
40              
41             Readonly my $BASE_URI => '/data';
42             Readonly my $BASE_WS_URI => $BASE_URI . '/workspaces';
43             Readonly my %ROUTES => (
44             backlinks => $BASE_WS_URI . '/:ws/pages/:pname/backlinks',
45             breadcrumbs => $BASE_WS_URI . '/:ws/breadcrumbs',
46             frontlinks => $BASE_WS_URI . '/:ws/pages/:pname/frontlinks',
47             page => $BASE_WS_URI . '/:ws/pages/:pname',
48             pagerevision => $BASE_WS_URI . '/:ws/pages/:pname/revisions/:revisionid',
49             pages => $BASE_WS_URI . '/:ws/pages',
50             pagetag => $BASE_WS_URI . '/:ws/pages/:pname/tags/:tag',
51             pagetags => $BASE_WS_URI . '/:ws/pages/:pname/tags',
52             pagetaghistory => $BASE_WS_URI . '/:ws/pages/:pname/taghistory',
53             pagecomments => $BASE_WS_URI . '/:ws/pages/:pname/comments',
54             pageattachment => $BASE_WS_URI
55             . '/:ws/pages/:pname/attachments/:attachment_id',
56             pageattachments => $BASE_WS_URI . '/:ws/pages/:pname/attachments',
57             sheetcells => $BASE_WS_URI . '/:ws/sheets/:pname/cells/:cellid',
58             revisions => $BASE_WS_URI . '/:ws/pages/:pname/revisions',
59             taggedpages => $BASE_WS_URI . '/:ws/tags/:tag/pages',
60             workspace => $BASE_WS_URI . '/:ws',
61             workspaces => $BASE_WS_URI,
62             workspacetag => $BASE_WS_URI . '/:ws/tags/:tag',
63             workspacetags => $BASE_WS_URI . '/:ws/tags',
64             workspaceattachment => $BASE_WS_URI . '/:ws/attachments/:attachment_id',
65             workspaceattachments => $BASE_WS_URI . '/:ws/attachments',
66             workspaceuser => $BASE_WS_URI . '/:ws/users/:user_id',
67             workspaceusers => $BASE_WS_URI . '/:ws/users',
68             user => '/data/users/:user_id',
69             users => '/data/users',
70             homepage => $BASE_WS_URI . '/:ws/homepage',
71             people => $BASE_URI . '/people',
72             person => $BASE_URI . '/people/:pname',
73             person_tag => $BASE_URI . '/people/:pname/tags',
74             profile_photo => $BASE_URI . '/people/:pname/photo/:version',
75             signals => $BASE_URI . '/signals',
76             webhooks => $BASE_URI . '/webhooks',
77             webhook => $BASE_URI . '/webhooks/:id',
78             );
79              
80             field 'workspace';
81             field 'username';
82             field 'password';
83             field 'user_cookie';
84             field 'server';
85             field 'verbose';
86             field 'accept';
87             field 'filter';
88             field 'order';
89             field 'offset';
90             field 'count';
91             field 'query';
92             field 'etag_cache' => {};
93             field 'http_header_debug';
94             field 'response';
95             field 'json_verbose';
96             field 'cookie';
97             field 'agent_string';
98             field 'on_behalf_of';
99             field 'additional_headers' => {};
100             field 'siteminder';
101              
102             =head2 new
103              
104             my $Rester = Socialtext::Resting->new(
105             username => $opts{username},
106             password => $opts{password},
107             server => $opts{server},
108             );
109              
110             or
111              
112             my $Rester = Socialtext::Resting->new(
113             user_cookie => $opts{user_cookie},
114             server => $opts{server},
115             );
116              
117             Creates a Socialtext::Resting object for the specified
118             server/user/password, or server/cookie combination.
119              
120             =cut
121              
122             sub new {
123 17     17 1 91121 my $invocant = shift;
124 17   33     148 my $class = ref($invocant) || $invocant;
125 17         67 my $self = {@_};
126 17         65 return bless $self, $class;
127             }
128              
129             =head2 accept
130              
131             $Rester->accept($mime_type);
132              
133             Sets the HTTP Accept header to ask the server for a specific
134             representation in future requests.
135              
136             Standard representations:
137             http://www.socialtext.net/st-rest-docs/index.cgi?standard_representations
138              
139             Common representations:
140              
141             =over 4
142              
143             =item text/x.socialtext-wiki
144              
145             =item text/html
146              
147             =item application/json
148              
149             =back
150              
151             =head2 get_page
152              
153             $Rester->workspace('wikiname');
154             $Rester->get_page('page_name');
155              
156             Retrieves the content of the specified page. Note that
157             the workspace method needs to be called first to specify
158             which workspace to operate on.
159              
160             =cut
161              
162             sub get_page {
163 4     4 1 270 my $self = shift;
164 4         8 my $pname = shift;
165              
166 4         14 return $self->_get_page_or_revision(
167             'page',
168             $pname,
169             );
170             }
171              
172             =head2 get_page_revision
173              
174             $Rester->workspace('wikiname');
175             $Rester->get_page_revision('page_name', 'revision_id');
176              
177             Retrieves the content of the specified page revision. Note that the workspace
178             method needs to be called first to specify which workspace to operate on.
179              
180             =cut
181              
182             sub get_page_revision {
183 0     0 1 0 my $self = shift;
184 0         0 my $pname = shift;
185 0         0 my $revisionid = shift;
186              
187 0         0 return $self->_get_page_or_revision(
188             'pagerevision',
189             $pname,
190             $revisionid,
191             );
192             }
193              
194             sub _get_page_or_revision {
195 4     4   5 my $self = shift;
196 4         6 my $route = shift;
197 4         6 my $pname = shift;
198 4         5 my $revisionid = shift;
199              
200 4 50       133 my $paccept = (ref $pname) ? $pname->{accept} : $self->accept;
201              
202 4         45 $pname = name_to_id($pname);
203 4   100     17 my $accept = $paccept || 'text/x.socialtext-wiki';
204              
205 4         89 my $workspace = $self->workspace;
206 4         50 my $uri = $self->_make_uri(
207             $route,
208             { pname => $pname, ws => $workspace, revisionid => $revisionid }
209             );
210 4 100       96 $uri .= '?verbose=1' if $self->json_verbose;
211              
212 4 50       40 $accept = 'application/json' if $accept eq 'perl_hash';
213 4         13 my ( $status, $content, $response ) = $self->_request(
214             uri => $uri,
215             method => 'GET',
216             accept => $accept,
217             );
218              
219 4 100 66     514 if ( $status == 200 || $status == 404 ) {
220 3         14 $self->{etag_cache}{$workspace}{$pname} = $response->header('etag');
221 3 50 100     216 return decode_json($content)
222             if (($self->accept || '') eq 'perl_hash');
223 3         49 return $content;
224             }
225             else {
226 1         7 die "$status: $content\n";
227             }
228             }
229              
230             =head2 get_attachment
231              
232             $Rester->workspace('wikiname');
233             $Rester->get_attachment('attachment_id');
234              
235             Retrieves the specified attachment from the workspace.
236             Note that the workspace method needs to be called first
237             to specify which workspace to operate on.
238              
239             =cut
240              
241             # REVIEW: dup with above, some
242             sub get_attachment {
243 0     0 1 0 my $self = shift;
244 0         0 my $attachment_id = shift;
245              
246 0         0 my $uri = $self->_make_uri(
247             'workspaceattachment',
248             { attachment_id => $attachment_id, ws => $self->workspace, }
249             );
250              
251 0         0 my ( $status, $content ) = $self->_request(
252             uri => $uri,
253             method => 'GET',
254             );
255              
256 0 0 0     0 if ( $status == 200 || $status == 404 ) {
257 0         0 return $content;
258             }
259             else {
260 0         0 die "$status: $content\n";
261             }
262             }
263              
264             =head2 put_workspacetag
265              
266             $Rester->workspace('wikiname');
267             $Rester->put_workspacetag('tag');
268              
269             Add the specified tag to the workspace.
270              
271             =cut
272              
273             sub put_workspacetag {
274 0     0 1 0 my $self = shift;
275 0         0 my $tag = shift;
276              
277 0         0 my $uri = $self->_make_uri(
278             'workspacetag',
279             { ws => $self->workspace, tag => $tag }
280             );
281              
282 0         0 my ( $status, $content ) = $self->_request(
283             uri => $uri,
284             method => 'PUT',
285             );
286              
287 0 0 0     0 if ( $status == 204 || $status == 201 ) {
288 0         0 return $content;
289             }
290             else {
291 0         0 die "$status: $content\n";
292             }
293             }
294              
295             =head2 put_pagetag
296              
297             $Rester->workspace('wikiname');
298             $Rester->put_pagetag('page_name', 'tag');
299              
300             Add the specified tag to the page.
301              
302             =cut
303              
304             sub put_pagetag {
305 1     1 1 34 my $self = shift;
306 1         3 my $pname = shift;
307 1         2 my $tag = shift;
308              
309 1         10 $pname = name_to_id($pname);
310 1         32 my $uri = $self->_make_uri(
311             'pagetag',
312             { pname => $pname, ws => $self->workspace, tag => $tag }
313             );
314              
315 1         6 my ( $status, $content ) = $self->_request(
316             uri => $uri,
317             method => 'PUT',
318             );
319              
320 1 50 33     133 if ( $status == 204 || $status == 201 ) {
321 1         3 return $content;
322             }
323             else {
324 0         0 die "$status: $content\n";
325             }
326             }
327              
328             =head2 delete_workspacetag
329              
330             $Rester->workspace('wikiname');
331             $Rester->delete_workspacetag('tag');
332              
333             Delete the specified tag from the workspace.
334              
335             =cut
336              
337             sub delete_workspacetag {
338 0     0 1 0 my $self = shift;
339 0         0 my $tag = shift;
340              
341 0         0 my $uri = $self->_make_uri(
342             'workspacetag',
343             { ws => $self->workspace, tag => $tag }
344             );
345              
346 0         0 my ( $status, $content ) = $self->_request(
347             uri => $uri,
348             method => 'DELETE',
349             );
350              
351 0 0       0 if ( $status == 204 ) {
352 0         0 return $content;
353             }
354             else {
355 0         0 die "$status: $content\n";
356             }
357             }
358              
359             =head2 delete_pagetag
360              
361             $Rester->workspace('wikiname');
362             $Rester->delete_pagetag('page_name', 'tag');
363              
364             Delete the specified tag from the page.
365              
366             =cut
367              
368             sub delete_pagetag {
369 0     0 1 0 my $self = shift;
370 0         0 my $pname = shift;
371 0         0 my $tag = shift;
372              
373 0         0 $pname = name_to_id($pname);
374 0         0 my $uri = $self->_make_uri(
375             'pagetag',
376             { pname => $pname, ws => $self->workspace, tag => $tag }
377             );
378              
379 0         0 my ( $status, $content ) = $self->_request(
380             uri => $uri,
381             method => 'DELETE',
382             );
383              
384 0 0       0 if ( $status == 204 ) {
385 0         0 return $content;
386             }
387             else {
388 0         0 die "$status: $content\n";
389             }
390             }
391              
392             =head2 post_attachment
393              
394             $Rester->workspace('wikiname');
395             $Rester->post_attachment('page_name',$id,$content,$mime_type);
396              
397             Attach the file to the specified page
398              
399             =cut
400              
401             sub post_attachment {
402 1     1 1 30 my $self = shift;
403 1         2 my $pname = shift;
404 1         2 my $attachment_id = shift;
405 1         2 my $attachment_content = shift;
406 1         1 my $attachment_type = shift;
407              
408 1         3 $pname = name_to_id($pname);
409 1         25 my $uri = $self->_make_uri(
410             'pageattachments',
411             {
412             pname => $pname,
413             ws => $self->workspace
414             },
415             );
416              
417 1         5 $uri .= "?name=$attachment_id";
418              
419 1         4 my ( $status, $content, $response ) = $self->_request(
420             uri => $uri,
421             method => 'POST',
422             type => $attachment_type,
423             content => $attachment_content,
424             );
425              
426 1         135 my $location = $response->header('location');
427 1         46 $location =~ m{.*/attachments/([^/]+)};
428 1         4 $location = URI::Escape::uri_unescape($1);
429              
430 1 50 33     11 if ( $status == 204 || $status == 201 ) {
431 1         3 return $location;
432             }
433             else {
434 0         0 die "$status: $content\n";
435             }
436             }
437              
438             =head2 post_comment
439              
440             $Rester->workspace('wikiname');
441             $Rester->post_comment( 'page_name', "me too" );
442              
443             Add a comment to a page.
444              
445             =cut
446              
447             sub post_comment {
448 0     0 1 0 my $self = shift;
449 0         0 my $pname = shift;
450 0         0 my $comment = shift;
451              
452 0         0 $pname = name_to_id($pname);
453 0         0 my $uri = $self->_make_uri(
454             'pagecomments',
455             {
456             pname => $pname,
457             ws => $self->workspace
458             },
459             );
460              
461 0         0 my ( $status, $content ) = $self->_request(
462             uri => $uri,
463             method => 'POST',
464             type => 'text/x.socialtext-wiki',
465             content => $comment,
466             );
467              
468 0 0       0 die "$status: $content\n" unless $status == 204;
469             }
470              
471             =head2 put_page
472              
473             $Rester->workspace('wikiname');
474             $Rester->put_page('page_name',$content);
475              
476             Save the content as a page in the wiki. $content can either be a string,
477             which is treated as wikitext, or a hash with the following keys:
478              
479             =over
480              
481             =item content
482              
483             A string which is the page's wiki content.
484              
485             =item date
486              
487             RFC 2616 HTTP Date format string of the time the page was last edited
488              
489             =item from
490              
491             A username of the last editor of the page. If the the user does not exist it
492             will be created, but will not be added to the workspace.
493              
494             =back
495              
496             =cut
497              
498             sub put_page {
499 6     6 1 6305 my $self = shift;
500 6         12 my $pname = shift;
501 6         8 my $page_content = shift;
502              
503 6         174 my $workspace = $self->workspace;
504 6         68 my $uri = $self->_make_uri(
505             'page',
506             { pname => $pname, ws => $workspace }
507             );
508              
509 6         18 my $type = 'text/x.socialtext-wiki';
510 6 100       14 if ( ref $page_content ) {
511 1         2 $type = 'application/json';
512 1         22 $page_content = encode_json($page_content);
513             }
514              
515 6         5 my %extra_opts;
516 6         15 my $page_id = name_to_id($pname);
517 6 100       26 if (my $prev_etag = $self->{etag_cache}{$workspace}{$page_id}) {
518 1         4 $extra_opts{if_match} = $prev_etag;
519             }
520              
521 6         24 my ( $status, $content ) = $self->_request(
522             uri => $uri,
523             method => 'PUT',
524             type => $type,
525             content => $page_content,
526             %extra_opts,
527             );
528              
529 6 100 100     759 if ( $status == 204 || $status == 201 ) {
530 4         9 return $content;
531             }
532             else {
533 2         14 die "$status: $content\n";
534             }
535             }
536              
537             =head2 delete_page
538              
539             $Rester->workspace('wikiname');
540             $Rester->delete_page('page_name');
541              
542             Delete the specified page.
543              
544             =cut
545              
546             sub delete_page {
547 1     1 1 27 my $self = shift;
548 1         2 my $pname = shift;
549              
550 1         18 my $workspace = $self->workspace;
551 1         10 my $uri = $self->_make_uri(
552             'page',
553             { pname => $pname, ws => $workspace }
554             );
555              
556 1         5 my ( $status, $content ) = $self->_request(
557             uri => $uri,
558             method => 'DELETE',
559             type => 'application/json',
560             content => '{}',
561             );
562              
563 1 50       121 if ( $status == 204 ) {
564 1         14 return $content;
565             }
566             else {
567 0         0 die "$status: $content\n";
568             }
569             }
570              
571             # REVIEW: This is here because of escaping problems we have with
572             # apache web servers. This code effectively translate a Page->uri
573             # to a Page->id. By so doing the troublesome characters are factored
574             # out, getting us past a bug. This change should _not_ be maintained
575             # any longer than strictly necessary, primarily because it
576             # creates an informational dependency between client and server
577             # code by representing name_to_id translation code on both sides
578             # of the system. Since it is not used for page PUT, new pages
579             # will safely have correct page titles.
580             #
581             # This method is useful for clients, so lets make it public. In the
582             # future, this call could go to the server to reduce code duplication.
583              
584             =head2 name_to_id
585              
586             my $id = $Rester->name_to_id($name);
587             my $id = Socialtext::Resting::name_to_id($name);
588              
589             Convert a page name into a page ID. Can be called as a method or
590             as a function.
591              
592             =cut
593              
594 0     0   0 sub _name_to_id { name_to_id(@_) }
595             sub name_to_id {
596 12     12 1 18 my $id = shift;
597 12 50       29 $id = shift if ref($id); # handle being called as a method
598 12 50       26 $id = '' if not defined $id;
599 2     2   20 $id =~ s/[^\p{Letter}\p{Number}\p{ConnectorPunctuation}\pM]+/_/g;
  2         4  
  2         27  
  12         25  
600 12         25 $id =~ s/_+/_/g;
601 12         17 $id =~ s/^_(?=.)//;
602 12         19 $id =~ s/(?<=.)_$//;
603 12         14 $id =~ s/^0$/_/;
604 12         22 $id = lc($id);
605 12         25 return $id;
606             }
607              
608              
609             sub _make_uri {
610 19     19   68 my $self = shift;
611 19         24 my $thing = shift;
612 19         20 my $replacements = shift;
613              
614 19         111 my $uri = $ROUTES{$thing};
615              
616             # REVIEW: tried to do this in on /g go but had issues where
617             # syntax errors were happening...
618 19         157 foreach my $stub ( keys(%$replacements) ) {
619 38         107 my $replacement
620             = URI::Escape::uri_escape_utf8( $replacements->{$stub} );
621 38         1055 $uri =~ s{/:$stub\b}{/$replacement};
622             }
623              
624 19         55 return $uri;
625             }
626              
627             =head2 get_pages
628              
629             $Rester->workspace('wikiname');
630             $Rester->get_pages();
631              
632             List all pages in the wiki.
633              
634             =cut
635              
636             sub get_pages {
637 0     0 1 0 my $self = shift;
638              
639 0         0 return $self->_get_things('pages');
640             }
641              
642             =head2 get_page_attachments
643              
644             $Rester->get_page_attachments($page)
645              
646             List all the attachments on a page.
647              
648             =cut
649              
650             sub get_page_attachments {
651 0     0 1 0 my $self = shift;
652 0         0 my $pname = shift;
653              
654 0         0 return $self->_get_things( 'pageattachments', pname => $pname );
655             }
656              
657             =head2 get_sheet_cell
658              
659             $Rester->get_sheet_cell($page_id, $cellid)
660              
661             Get the value of a cell in a spreadsheet.
662              
663             =cut
664              
665             sub get_sheet_cell {
666 0     0 1 0 my $self = shift;
667 0         0 my $pname = shift;
668 0         0 my $cellid = shift;
669              
670 0         0 return $self->_get_things('sheetcells', pname => $pname,
671             cellid => $cellid);
672             }
673              
674             =head2 get_revisions
675              
676             $Rester->get_revisions($page)
677              
678             List all the revisions of a page.
679              
680             =cut
681              
682             sub get_revisions {
683 1     1 1 59 my $self = shift;
684 1         2 my $pname = shift;
685              
686 1         6 return $self->_get_things( 'revisions', pname => $pname );
687             }
688              
689             =head2 get_taghistory
690              
691             $Rester->workspace('wikiname');
692             $Rester->get_taghistory($page)
693              
694             Get a history, by revision, of all tags for a page.
695              
696             =cut
697              
698             sub get_taghistory {
699 0     0 1 0 my $self = shift;
700 0         0 my $pname = shift;
701              
702 0         0 return $self->_get_things( 'pagetaghistory', pname => $pname );
703             }
704              
705             sub _extend_uri {
706 3     3   5 my $self = shift;
707 3         5 my $uri = shift;
708 3         47 my @extend;
709              
710 3 50       72 if ( $self->filter ) {
711 0         0 push (@extend, "filter=" . $self->filter);
712             }
713 3 50       86 if ( $self->query ) {
714 0         0 push (@extend, "q=" . $self->query);
715             }
716 3 50       109 if ( $self->order ) {
717 0         0 push (@extend, "order=" . $self->order);
718             }
719 3 50       84 if ( $self->offset ) {
720 0         0 push (@extend, "offset=" . $self->offset);
721             }
722 3 50       79 if ( $self->count ) {
723 0         0 push (@extend, "count=" . $self->count);
724             }
725 3 50       26 if (@extend) {
726 0         0 $uri .= "?" . join(';', @extend);
727             }
728 3         9 return $uri;
729              
730             }
731             sub _get_things {
732 3     3   6 my $self = shift;
733 3         6 my $things = shift;
734 3         7 my %replacements = @_;
735 3   100     68 my $accept = $self->accept || 'text/plain';
736              
737 3         95 my $uri = $self->_make_uri(
738             $things,
739             { ws => $self->workspace, %replacements }
740             );
741 3         13 $uri = $self->_extend_uri($uri);
742              
743             # Add query parameters from a
744 3 100       10 if ( exists $replacements{_query} ) {
745 2         3 my @params;
746 2         2 for my $q ( keys %{ $replacements{_query} } ) {
  2         7  
747 1         4 push @params, "$q=" . $replacements{_query}->{$q};
748             }
749 2 100       8 if (my $query = join( ';', @params )) {
750 1 50       4 if ( $uri =~ /\?/ ) {
751 0         0 $uri .= ";$query";
752             }
753             else {
754 1         3 $uri .= "?$query";
755             }
756             }
757             }
758              
759 3 50       9 $accept = 'application/json' if $accept eq 'perl_hash';
760 3         10 my ( $status, $content ) = $self->_request(
761             uri => $uri,
762             method => 'GET',
763             accept => $accept,
764             );
765              
766 3 50 33     406 if ( $status == 200 and wantarray ) {
    50          
    0          
    0          
767 0         0 return ( grep defined, ( split "\n", $content ) );
768             }
769             elsif ( $status == 200 ) {
770 3 50 100     56 return decode_json($content)
771             if (($self->accept || '') eq 'perl_hash');
772 3         40 return $content;
773             }
774             elsif ( $status == 404 ) {
775 0         0 return ();
776             }
777             elsif ( $status == 302 ) {
778 0         0 return $self->response->header('Location');
779             }
780             else {
781 0         0 die "$status: $content\n";
782             }
783             }
784              
785             =head2 get_workspace_tags
786              
787             $Rester->workspace('foo');
788             $Rester->get_workspace_tags()
789              
790             List all the tags in workspace foo.
791              
792             =cut
793              
794             sub get_workspace_tags {
795 0     0 1 0 my $self = shift;
796 0         0 return $self->_get_things( 'workspacetags' )
797             }
798              
799             =head2 get_homepage
800              
801             Return the page name of the homepage of the current workspace.
802              
803             =cut
804              
805             sub get_homepage {
806 0     0 1 0 my $self = shift;
807 0         0 my $uri = $self->_get_things( 'homepage' );
808 0         0 my $workspace = $self->workspace;
809 0 0       0 $uri =~ s#.*/data/workspaces/\Q$workspace\E/pages/(.+)#$1# if $uri;
810 0         0 return $uri;
811             }
812              
813             =head2 get_backlinks
814              
815             $Rester->workspace('wikiname');
816             $Rester->get_backlinks('page_name');
817              
818             List all backlinks to the specified page
819              
820             =cut
821              
822             sub get_backlinks {
823 0     0 1 0 my $self = shift;
824 0         0 my $pname = shift;
825 0         0 $pname = name_to_id($pname);
826 0         0 return $self->_get_things( 'backlinks', pname => $pname );
827             }
828              
829             =head2 get_frontlinks
830              
831             $Rester->workspace('wikiname');
832             $Rester->get_frontlinks('page_name');
833              
834             List all 'frontlinks' on the specified page
835              
836             =cut
837              
838             sub get_frontlinks {
839 0     0 1 0 my $self = shift;
840 0         0 my $pname = shift;
841 0   0     0 my $incipients = shift || 0;
842 0         0 $pname = name_to_id($pname);
843 0 0       0 return $self->_get_things(
844             'frontlinks', pname => $pname,
845             ( $incipients ? ( _query => { incipient => 1 } ) : () )
846             );
847             }
848              
849             =head2 get_pagetags
850              
851             $Rester->workspace('wikiname');
852             $Rester->get_pagetags('page_name');
853              
854             List all pagetags on the specified page
855              
856             =cut
857              
858             sub get_pagetags {
859 0     0 1 0 my $self = shift;
860 0         0 my $pname = shift;
861 0         0 $pname = name_to_id($pname);
862 0         0 return $self->_get_things( 'pagetags', pname => $pname );
863             }
864              
865             =head2 get_taggedpages
866              
867             $Rester->worksapce('wikiname');
868             $Rester->get_taggedpages('tag');
869              
870             List all the pages that are tagged with 'tag'.
871              
872             =cut
873             sub get_taggedpages {
874 0     0 1 0 my $self = shift;
875 0         0 my $tag = shift;
876 0         0 return $self->_get_things( 'taggedpages', tag => $tag );
877             }
878              
879             =head2 get_tag
880              
881             $Rester->workspace('wikiname');
882             $Rester->get_tag('tag');
883              
884             Retrieves the specified tag from the workspace.
885             Note that the workspace method needs to be called first
886             to specify which workspace to operate on.
887              
888             =cut
889              
890             # REVIEW: dup with above, some
891             sub get_tag {
892 0     0 1 0 my $self = shift;
893 0         0 my $tag = shift;
894              
895 0   0     0 my $accept = $self->accept || 'text/html';
896              
897 0         0 my $uri = $self->_make_uri(
898             'workspacetag',
899             { tag => $tag, ws => $self->workspace, }
900             );
901              
902 0         0 my ( $status, $content ) = $self->_request(
903             uri => $uri,
904             accept => $accept,
905             method => 'GET',
906             );
907              
908 0 0 0     0 if ( $status == 200 || $status == 404 ) {
909 0         0 return $content;
910             }
911             else {
912 0         0 die "$status: $content\n";
913             }
914             }
915              
916             =head2 get_breadcrumbs
917              
918             $Rester->get_breadcrumbs('workspace')
919              
920             Get breadcrumbs for current user in this workspace
921              
922             =cut
923              
924             sub get_breadcrumbs {
925 0     0 1 0 my $self = shift;
926              
927 0         0 return $self->_get_things('breadcrumbs');
928             }
929              
930             =head2 get_workspace
931              
932             $Rester->get_workspace();
933              
934             Return the metadata about a particular workspace.
935              
936             =cut
937              
938             sub get_workspace {
939 0     0 1 0 my $self = shift;
940 0         0 my $wksp = shift;
941              
942 0         0 my $prev_wksp = $self->workspace();
943 0 0       0 $self->workspace($wksp) if $wksp;
944 0         0 my $result = $self->_get_things('workspace');
945 0 0       0 $self->workspace($prev_wksp) if $wksp;
946 0         0 return $result;
947             }
948              
949             =head2 get_workspaces
950              
951             $Rester->get_workspaces();
952              
953             List all workspaces on the server
954              
955             =cut
956              
957             sub get_workspaces {
958 0     0 1 0 my $self = shift;
959              
960 0         0 return $self->_get_things('workspaces');
961             }
962              
963             =head2 get_user
964              
965             my $userinfo = $Rester->get_user($username);
966             print $userinfo->{email_address};
967              
968             Get information about a username
969              
970             =cut
971              
972             sub get_user {
973 0     0 1 0 my $self = shift;
974 0         0 my $uname = shift;
975              
976 0         0 my $uri = $self->_make_uri(
977             'user',
978             { user_id => $uname, ws => $self->workspace }
979             );
980            
981 0         0 my ( $status, $content ) = $self->_request(
982             uri => $uri,
983             accept => 'application/json',
984             method => 'GET'
985             );
986              
987 0 0       0 if ( $status == 200 ) {
    0          
988 0         0 return decode_json( $content );
989             } elsif ( $status == 404 ) {
990 0         0 return $content;
991             } else {
992 0         0 die "$status: $content\n";
993             }
994             }
995              
996             =head2 create_user
997              
998             $Rester->create_user( { username => $username,
999             email_address => $email,
1000             password => $password } );
1001              
1002             Create a new user. Other parameters can be specified, see POD for
1003             Socialtext::User. username is optional and will default to the email address,
1004             as in most cases username and email_address will be the same.
1005              
1006             =cut
1007              
1008             sub create_user {
1009 0     0 1 0 my $self = shift;
1010 0         0 my $args = shift;
1011              
1012 0   0     0 $args->{ username } ||= $args->{ email_address };
1013 0         0 $args = encode_json($args);
1014              
1015 0         0 my ( $status, $content ) = $self->_request(
1016             uri => $ROUTES{'users'},
1017             method => 'POST',
1018             type => 'application/json',
1019             content => $args
1020             );
1021              
1022 0 0 0     0 if ( $status == 201 || $status == 400 || $status == 409 ) {
      0        
1023 0         0 return $content;
1024             } else {
1025 0         0 die "$status: $content\n";
1026             }
1027             }
1028              
1029             =head2 add_user_to_workspace
1030              
1031             $Rester->add_user_to_workspace( $workspace, { username => $user,
1032             rolename => $role,
1033             send_confirmation_invitation => 0 || 1,
1034             from_address => $from_email } );
1035              
1036             Add a user that already exists to a workspace. rolename defaults to 'member',
1037             send_confirmation_invitation defaults to '0'. from_address must refer to a
1038             valid existing user, and is only needed if send_confirmation_invitation is set
1039             to '1'. If the user is already a member of the workspace, this will reset their
1040             role if you specify a role that's different from their current role.
1041              
1042             =cut
1043              
1044             sub add_user_to_workspace {
1045 0     0 1 0 my $self = shift;
1046 0         0 my $workspace = shift;
1047 0         0 my $args = shift;
1048              
1049 0         0 my $uri = $self->_make_uri(
1050             'workspaceusers',
1051             { ws => $workspace }
1052             );
1053              
1054 0   0     0 $args->{rolename} ||= 'member';
1055 0   0     0 $args->{send_confirmation_invitation} ||= 0;
1056 0         0 $args = encode_json($args);
1057              
1058 0         0 my ( $status, $content ) = $self->_request(
1059             uri => $uri,
1060             method => 'POST',
1061             type => 'application/json',
1062             content => $args
1063             );
1064              
1065 0 0 0     0 if ( $status == 201 || $status == 400 ) {
1066 0         0 return $content;
1067             } else {
1068 0         0 die "$status: $content\n";
1069             }
1070             }
1071            
1072             =head2 get_users_for_workspace
1073              
1074             my @users = $Rester->get_users_for_workspace( $workspace );
1075             for ( @users ) { print "$_->{name}, $_->{role}, $->{is_workspace_admin}\n" }
1076              
1077             Get a list of users in a workspace, and their roles and admin status.
1078              
1079             =cut
1080              
1081             sub get_users_for_workspace {
1082 0     0 1 0 my $self = shift;
1083 0         0 my $workspace = shift;
1084              
1085 0         0 my $uri = $self->_make_uri(
1086             'workspaceusers',
1087             { ws => $workspace }
1088             );
1089            
1090 0         0 my ( $status, $content ) = $self->_request(
1091             uri => $uri,
1092             method => 'GET',
1093             accept => 'application/json'
1094             );
1095              
1096 0 0       0 if ( $status == 200 ) {
1097 0         0 return @{ decode_json( $content ) };
  0         0  
1098             } else {
1099 0         0 die "$status: $content\n";
1100             }
1101             }
1102              
1103             =head2 put_persontag
1104              
1105             $Rester->put_persontag( $person, $tag )
1106              
1107             Tag a person.
1108              
1109             =cut
1110              
1111             sub put_persontag {
1112 1     1 1 6 my $self = shift;
1113 1         1 my $person = shift;
1114 1         1 my $tag = shift;
1115              
1116 1         6 my $uri = $self->_make_uri(
1117             'person_tag',
1118             { pname => $person }
1119             );
1120            
1121 1         13 my ( $status, $content ) = $self->_request(
1122             uri => $uri,
1123             method => 'POST',
1124             type => 'application/json',
1125             content => encode_json({ tag_name => $tag }),
1126             );
1127              
1128 1 50       146 return if $status == 200;
1129 0         0 die "$status: $content\n";
1130             }
1131              
1132             =head2 get_persontags
1133              
1134             $Rester->get_persontags($person);
1135              
1136             Retrieves all tags for a person
1137              
1138             =cut
1139              
1140             sub get_persontags {
1141 0     0 1 0 my ($self, $person, %opts) = @_;
1142 0         0 return $self->_get_things('person_tag',
1143             pname => $person,
1144             _query => \%opts);
1145             }
1146              
1147             =head2 get_people
1148              
1149             $Rester->get_people();
1150              
1151             Retrieves all people.
1152              
1153             =cut
1154              
1155             sub get_people {
1156 0     0 1 0 my ($self, %opts) = @_;
1157 0         0 return $self->_get_things('people', _query => \%opts);
1158             }
1159              
1160             sub get_profile_photo {
1161 0     0 0 0 my $self = shift;
1162 0         0 my $pname = shift;
1163 0         0 my $version = shift;
1164              
1165 0   0     0 my $uri = $self->_make_uri( 'profile_photo', {
1166             pname => $pname,
1167             version => $version || 'max',
1168             });
1169              
1170 0         0 my ( $status, $content, $response ) = $self->_request(
1171             uri => $uri,
1172             method => 'GET',
1173             );
1174              
1175 0 0       0 if ( $status == 200 ) {
1176 0         0 return $content;
1177             }
1178             else {
1179 0         0 die "$status: $content\n";
1180             }
1181             }
1182              
1183             =head2 get_person
1184              
1185             $Rester->get_person();
1186              
1187             Retrieves a person.
1188              
1189             =cut
1190              
1191             sub get_person {
1192 0     0 1 0 my $self = shift;
1193 0   0     0 my $identifier = shift || $self->username;
1194              
1195 0         0 return $self->_get_things('person', pname => $identifier );
1196             }
1197              
1198              
1199             =head2 get_signals
1200              
1201             $Rester->get_signals();
1202             $Rester->get_signals(group_id => 42);
1203             $Rester->get_signals(account_id => 2);
1204              
1205             Retrieves the list of signals.
1206              
1207             Optional arguments are passed as query paramaters.
1208              
1209             =cut
1210              
1211             sub get_signals {
1212 2     2 1 69 my $self = shift;
1213 2         6 my %opts = @_;
1214              
1215 2         9 return $self->_get_things('signals', _query => \%opts);
1216             }
1217              
1218             =head2 post_signal
1219              
1220             $Rester->post_signal('O HAI');
1221             $Rester->post_signal('O HAI', group_id => 42);
1222             $Rester->post_signal('O HAI', group_ids => [2,3,4]);
1223             $Rester->post_signal('O HAI', account_id => 42);
1224             $Rester->post_signal('O HAI', account_ids => [2,3,4]);
1225             $Rester->post_signal('O HAI', in_reply_to => { signal_id => 142 });
1226              
1227             Posts a signal.
1228              
1229             Optional C and C arguments for targetting the signal.
1230              
1231             Optional C for specifying a signal_id this signal is in reply to.
1232              
1233             Optional C to annotate the signal. C should be an array
1234             ref containing hashrefs that have one key (the annotation type) and a value that is
1235             a hashref containing key/value pairs.
1236              
1237             =cut
1238              
1239             sub post_signal {
1240 2     2 1 67 my $self = shift;
1241 2         4 my $text = shift;
1242 2         6 my %args = @_;
1243              
1244 2         6 my %sig = ( signal => $text );
1245              
1246 2         5 for my $k (qw(account_id group_id)) {
1247 4 100       5 my @ids = @{ $args{$k.'s'} || [] };
  4         18  
1248 4 100       11 push @ids, $args{$k} if $args{$k}; # must be non-zero
1249 4 100       16 $sig{$k.'s'} = \@ids if @ids;
1250             }
1251              
1252 2         4 for my $k (qw(in_reply_to annotations attachments)) {
1253 6 50       25 next unless exists $args{$k};
1254 0         0 $sig{$k} = $args{$k};
1255             }
1256              
1257 2         6 my $uri = $self->_make_uri('signals');
1258 2         17 my ( $status, $content, $response ) = $self->_request(
1259             uri => $uri,
1260             method => 'POST',
1261             type => "application/json",
1262             content => encode_json( \%sig ),
1263             );
1264              
1265 2         279 my $location = $response->header('location');
1266 2         92 $location = URI::Escape::uri_unescape($1);
1267              
1268 2 50 33     17 if ( $status == 204 || $status == 201 ) {
1269 2         7 return $location;
1270             }
1271             else {
1272 0         0 die "$status: $content\n";
1273             }
1274             }
1275              
1276             =head2 post_webhook
1277              
1278             $Rester->post_webhook( %args )
1279              
1280             Creates a webhook. Args will be encoded as JSON and put up.
1281              
1282             =cut
1283              
1284             sub post_webhook {
1285 0     0 1 0 my $self = shift;
1286 0         0 my %args = @_;
1287              
1288 0         0 my $uri = $self->_make_uri('webhooks');
1289 0         0 my ( $status, $content, $response ) = $self->_request(
1290             uri => $uri,
1291             method => 'POST',
1292             type => "application/json",
1293             content => encode_json( \%args ),
1294             );
1295              
1296 0 0 0     0 if ( $status == 204 || $status == 201 ) {
1297 0         0 return $response->header('Location');
1298             }
1299             else {
1300 0         0 die "$status: $content\n";
1301             }
1302             }
1303              
1304             =head2 get_webhooks
1305              
1306             my $hooks = $Rester->get_webhooks();
1307              
1308             Returns an arrayref containing hashrefs of each webhook on the server.
1309              
1310             =cut
1311              
1312             sub get_webhooks {
1313 0     0 1 0 my $self = shift;
1314              
1315 0         0 my $uri = $self->_make_uri('webhooks');
1316 0         0 my ( $status, $content, $response ) = $self->_request(
1317             uri => $uri,
1318             method => 'GET',
1319             type => "application/json",
1320             );
1321              
1322 0 0       0 if ( $status == 200 ) {
1323 0         0 return decode_json($content);
1324             }
1325             else {
1326 0         0 die "$status: $content\n";
1327             }
1328             }
1329              
1330             =head2 delete_webhook
1331              
1332             $Rester->delete_webhook( id => $webhook_id );
1333              
1334             Deletes the specified webhook.
1335              
1336             =cut
1337              
1338             sub delete_webhook {
1339 0     0 1 0 my $self = shift;
1340 0         0 my %args = @_;
1341 0 0       0 die "id is mandatory" unless $args{id};
1342              
1343 0         0 my $uri = $self->_make_uri('webhook', {id => $args{id}});
1344 0         0 my ( $status, $content, $response ) = $self->_request(
1345             uri => $uri,
1346             method => 'DELETE',
1347             );
1348              
1349 0 0       0 if ( $status == 204 ) {
1350 0         0 return;
1351             }
1352             else {
1353 0         0 die "$status: $content\n";
1354             }
1355             }
1356              
1357             sub _request {
1358 19     19   29 my $self = shift;
1359 19         71 my %p = @_;
1360 19         394 my $ua = LWP::UserAgent->new(agent => $self->agent_string);
1361 19         567 my $server = $self->server;
1362 19 50       137 die "No server defined!\n" unless $server;
1363 19         31 $server =~ s#/$##;
1364 19         50 my $uri = "$server$p{uri}";
1365 19 50       364 warn "uri: $uri\n" if $self->verbose;
1366              
1367 19         187 my $request = HTTP::Request->new( $p{method}, $uri );
1368 19 50       484 if ( !$self->siteminder ) {
1369 19 50       462 if ( $self->user_cookie ) {
1370 0         0 $request->header( 'Cookie' => 'NLW-user=' . $self->user_cookie );
1371             }
1372             else {
1373 19         454 $request->authorization_basic( $self->username, $self->password );
1374             }
1375             }
1376 19 100       1530 $request->header( 'Accept' => $p{accept} ) if $p{accept};
1377 19 100       340 $request->header( 'Content-Type' => $p{type} ) if $p{type};
1378 19 100       470 $request->header( 'If-Match' => $p{if_match} ) if $p{if_match};
1379 19 50       414 $request->header( 'X-On-Behalf-Of' => $self->on_behalf_of ) if $self->on_behalf_of;
1380 19         128 foreach my $key (keys %{$self->additional_headers}) {
  19         385  
1381 0         0 $request->header($key => $self->additional_headers->{$key});
1382             }
1383              
1384 19 100       222 if ($p{method} eq 'PUT') {
1385 7         8 my $content_len = 0;
1386 2 100   2   57481 $content_len = do { use bytes; length $p{content} } if $p{content};
  2         5  
  2         18  
  7         19  
  6         9  
1387 7         26 $request->header( 'Content-Length' => $content_len );
1388             }
1389              
1390 19 50       672 if (my $cookie = $self->cookie) {
1391 0         0 $request->header('cookie' => $cookie);
1392             }
1393 19 100       187 $request->content( $p{content} ) if $p{content};
1394 19         506 $self->response( $ua->simple_request($request) );
1395              
1396 19 50       1525 if ( $self->http_header_debug ) {
1397 2     2   2200 use Data::Dumper;
  2         11952  
  2         425  
1398 0         0 warn "Code: "
1399             . $self->response->code . "\n"
1400             . Dumper $self->response->headers;
1401             }
1402              
1403             # We should refactor to not return these response things
1404 19         431 return ( $self->response->code, $self->response->content,
1405             $self->response );
1406             }
1407              
1408             =head2 response
1409              
1410             my $resp = $Rester->response;
1411              
1412             Return the HTTP::Response object from the last request.
1413              
1414             =head1 AUTHORS / MAINTAINERS
1415              
1416             Shawn Devlin C<< >>
1417              
1418             Kevin Jones C<< >>
1419              
1420             Brandon Noard C<< >>
1421              
1422             =head2 CONTRIBUTORS
1423              
1424             Luke Closs
1425              
1426             Jeremy Stashewsky
1427              
1428             Chris Dent
1429              
1430             Kirsten Jones
1431              
1432             Michele Berg - get_revisions()
1433              
1434             =cut
1435              
1436             1;