]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/microappplugin.php
Merge branch '1.0.x' into testing
[quix0rs-gnu-social.git] / lib / microappplugin.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2011, StatusNet, Inc.
5  *
6  * Superclass for microapp plugin
7  * 
8  * PHP version 5
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU Affero General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Affero General Public License for more details.
19  *
20  * You should have received a copy of the GNU Affero General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  * @category  Microapp
24  * @package   StatusNet
25  * @author    Evan Prodromou <evan@status.net>
26  * @copyright 2011 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('STATUSNET')) {
32     // This check helps protect against security problems;
33     // your code file can't be executed directly from the web.
34     exit(1);
35 }
36
37 /**
38  * Superclass for microapp plugins
39  *
40  * This class lets you define micro-applications with different kinds of activities.
41  *
42  * The applications work more-or-less like other 
43  * 
44  * @category  Microapp
45  * @package   StatusNet
46  * @author    Evan Prodromou <evan@status.net>
47  * @copyright 2011 StatusNet, Inc.
48  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
49  * @link      http://status.net/
50  */
51
52 abstract class MicroAppPlugin extends Plugin
53 {
54     /**
55      * Returns a localized string which represents this micro-app,
56      * to be shown to users selecting what type of post to make.
57      * This is paired with the key string in $this->tag().
58      *
59      * All micro-app classes must override this method.
60      *
61      * @return string
62      */
63     abstract function appTitle();
64
65     /**
66      * Returns a key string which represents this micro-app in HTML
67      * ids etc, as when offering selection of what type of post to make.
68      * This is paired with the user-visible localizable $this->appTitle().
69      *
70      * All micro-app classes must override this method.
71      */
72     abstract function tag();
73
74     /**
75      * Return a list of ActivityStreams object type URIs
76      * which this micro-app handles. Default implementations
77      * of the base class will use this list to check if a
78      * given ActivityStreams object belongs to us, via
79      * $this->isMyNotice() or $this->isMyActivity.
80      *
81      * All micro-app classes must override this method.
82      *
83      * @fixme can we confirm that these types are the same
84      * for Atom and JSON streams? Any limitations or issues?
85      *
86      * @return array of strings
87      */
88     abstract function types();
89
90     /**
91      * Given a parsed ActivityStreams activity, your plugin
92      * gets to figure out how to actually save it into a notice
93      * and any additional data structures you require.
94      *
95      * This will handle things received via AtomPub, OStatus
96      * (PuSH and Salmon transports), or ActivityStreams-based
97      * backup/restore of account data.
98      *
99      * You should be able to accept as input the output from your
100      * $this->activityObjectFromNotice(). Where applicable, try to
101      * use existing ActivityStreams structures and object types,
102      * and be liberal in accepting input from what might be other
103      * compatible apps.
104      *
105      * All micro-app classes must override this method.
106      *
107      * @fixme are there any standard options?
108      *
109      * @param Activity $activity
110      * @param Profile $actor
111      * @param array $options=array()
112      *
113      * @return Notice the resulting notice
114      */
115     abstract function saveNoticeFromActivity($activity, $actor, $options=array());
116
117     /**
118      * Given an existing Notice object, your plugin gets to
119      * figure out how to arrange it into an ActivityStreams
120      * object.
121      *
122      * This will be how your specialized notice gets output in
123      * Atom feeds and JSON-based ActivityStreams output, including
124      * account backup/restore and OStatus (PuSH and Salmon transports).
125      *
126      * You should be able to round-trip data from this format back
127      * through $this->saveNoticeFromActivity(). Where applicable, try
128      * to use existing ActivityStreams structures and object types,
129      * and consider interop with other compatible apps.
130      *
131      * All micro-app classes must override this method.
132      *
133      * @fixme this outputs an ActivityObject, not an Activity. Any compat issues?
134      *
135      * @param Notice $notice
136      *
137      * @return ActivityObject
138      */
139     abstract function activityObjectFromNotice($notice);
140
141     /**
142      * Custom HTML output for your special notice; called when a
143      * matching notice turns up in a NoticeListItem.
144      *
145      * All micro-app classes must override this method.
146      *
147      * @param Notice $notice
148      * @param HTMLOutputter $out
149      *
150      * @fixme WARNING WARNING WARNING base plugin stuff below tries to close
151      * a div that this function opens in the BookmarkPlugin child class.
152      * This is probably wrong.
153      */
154     abstract function showNotice($notice, $out);
155
156     /**
157      * When building the primary notice form, we'll fetch also some
158      * alternate forms for specialized types -- that's you!
159      *
160      * Return a custom Widget or Form object for the given output
161      * object, and it'll be included in the HTML output. Beware that
162      * your form may be initially hidden.
163      *
164      * All micro-app classes must override this method.
165      *
166      * @param HTMLOutputter $out
167      * @return Widget
168      */
169     abstract function entryForm($out);
170
171     /**
172      * When a notice is deleted, you'll be called here for a chance
173      * to clean up any related resources.
174      *
175      * All micro-app classes must override this method.
176      *
177      * @param Notice $notice
178      */
179     abstract function deleteRelated($notice);
180
181     /**
182      * Check if a given notice object should be handled by this micro-app
183      * plugin.
184      *
185      * The default implementation checks against the activity type list
186      * returned by $this->types(). You can override this method to expand
187      * your checks.
188      *
189      * @param Notice $notice
190      * @return boolean
191      */
192     function isMyNotice($notice) {
193         $types = $this->types();
194         return in_array($notice->object_type, $types);
195     }
196
197     /**
198      * Check if a given ActivityStreams activity should be handled by this
199      * micro-app plugin.
200      *
201      * The default implementation checks against the activity type list
202      * returned by $this->types(), and requires that exactly one matching
203      * object be present. You can override this method to expand
204      * your checks or to compare the activity's verb, etc.
205      *
206      * @param Activity $activity
207      * @return boolean
208      */
209     function isMyActivity($activity) {
210         $types = $this->types();
211         return (count($activity->objects) == 1 &&
212                 in_array($activity->objects[0]->type, $types));
213     }
214
215     /**
216      * Called when generating Atom XML ActivityStreams output from an
217      * ActivityObject belonging to this plugin. Gives the plugin
218      * a chance to add custom output.
219      *
220      * Note that you can only add output of additional XML elements,
221      * not change existing stuff here.
222      *
223      * If output is already handled by the base Activity classes,
224      * you can leave this base implementation as a no-op.
225      *
226      * @param ActivityObject $obj
227      * @param XMLOutputter $out to add elements at end of object
228      */
229     function activityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
230     {
231         // default is a no-op
232     }
233
234     /**
235      * Called when generating JSON ActivityStreams output from an
236      * ActivityObject belonging to this plugin. Gives the plugin
237      * a chance to add custom output.
238      *
239      * Modify the array contents to your heart's content, and it'll
240      * all get serialized out as JSON.
241      *
242      * If output is already handled by the base Activity classes,
243      * you can leave this base implementation as a no-op.
244      *
245      * @param ActivityObject $obj
246      * @param array &$out JSON-targeted array which can be modified
247      */
248     public function activityObjectOutputJson(ActivityObject $obj, array &$out)
249     {
250         // default is a no-op
251     }
252
253     /**
254      * When a notice is deleted, delete the related objects
255      * by calling the overridable $this->deleteRelated().
256      *
257      * @param Notice $notice Notice being deleted
258      * 
259      * @return boolean hook value
260      */
261
262     function onNoticeDeleteRelated($notice)
263     {
264         if ($this->isMyNotice($notice)) {
265             $this->deleteRelated($notice);
266         }
267
268         return true;
269     }
270
271     /**
272      * Output the HTML for this kind of object in a list
273      *
274      * @param NoticeListItem $nli The list item being shown.
275      *
276      * @return boolean hook value
277      *
278      * @fixme WARNING WARNING WARNING this closes a 'div' that is implicitly opened in BookmarkPlugin's showNotice implementation
279      */
280
281     function onStartShowNoticeItem($nli)
282     {
283         if (!$this->isMyNotice($nli->notice)) {
284             return true;
285         }
286
287         $out = $nli->out;
288         $notice = $nli->notice;
289
290         $this->showNotice($notice, $out);
291
292         $nli->showNoticeLink();
293         $nli->showNoticeSource();
294         $nli->showNoticeLocation();
295         $nli->showContext();
296         $nli->showRepeat();
297         
298         $out->elementEnd('div');
299         
300         $nli->showNoticeOptions();
301
302         return false;
303     }
304
305     /**
306      * Render a notice as one of our objects
307      *
308      * @param Notice         $notice  Notice to render
309      * @param ActivityObject &$object Empty object to fill
310      *
311      * @return boolean hook value
312      */
313      
314     function onStartActivityObjectFromNotice($notice, &$object)
315     {
316         if ($this->isMyNotice($notice)) {
317             $object = $this->activityObjectFromNotice($notice);
318             return false;
319         }
320
321         return true;
322     }
323
324     /**
325      * Handle a posted object from PuSH
326      *
327      * @param Activity        $activity activity to handle
328      * @param Ostatus_profile $oprofile Profile for the feed
329      *
330      * @return boolean hook value
331      */
332
333     function onStartHandleFeedEntryWithProfile($activity, $oprofile)
334     {
335         if ($this->isMyActivity($activity)) {
336
337             $actor = $oprofile->checkAuthorship($activity);
338
339             if (empty($actor)) {
340                 throw new ClientException(_('Can\'t get author for activity.'));
341             }
342
343             $object = $activity->objects[0];
344
345             $options = array('uri' => $object->id,
346                              'url' => $object->link,
347                              'is_local' => Notice::REMOTE_OMB,
348                              'source' => 'ostatus');
349
350             // $actor is an ostatus_profile
351             $this->saveNoticeFromActivity($activity, $actor->localProfile(), $options);
352
353             return false;
354         }
355
356         return true;
357     }
358
359     /**
360      * Handle a posted object from Salmon
361      *
362      * @param Activity $activity activity to handle
363      * @param mixed    $target   user or group targeted
364      *
365      * @return boolean hook value
366      */
367
368     function onStartHandleSalmonTarget($activity, $target)
369     {
370         if ($this->isMyActivity($activity)) {
371
372             $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap.");
373
374             if ($target instanceof User_group) {
375                 $uri = $target->getUri();
376                 if (!in_array($uri, $activity->context->attention)) {
377                     throw new ClientException(_("Bookmark not posted ".
378                                                 "to this group."));
379                 }
380             } else if ($target instanceof User) {
381                 $uri      = $target->uri;
382                 $original = null;
383                 if (!empty($activity->context->replyToID)) {
384                     $original = Notice::staticGet('uri', 
385                                                   $activity->context->replyToID); 
386                 }
387                 if (!in_array($uri, $activity->context->attention) &&
388                     (empty($original) ||
389                      $original->profile_id != $target->id)) {
390                     throw new ClientException(_("Object not posted ".
391                                                 "to this user."));
392                 }
393             } else {
394                 throw new ServerException(_("Don't know how to handle ".
395                                             "this kind of target."));
396             }
397
398             $actor = Ostatus_profile::ensureActivityObjectProfile($activity->actor);
399
400             $object = $activity->objects[0];
401
402             $options = array('uri' => $object->id,
403                              'url' => $object->link,
404                              'is_local' => Notice::REMOTE_OMB,
405                              'source' => 'ostatus');
406
407             // $actor is an ostatus_profile
408             $this->saveNoticeFromActivity($activity, $actor->localProfile(), $options);
409
410             return false;
411         }
412
413         return true;
414     }
415
416     /**
417      * Handle object posted via AtomPub
418      *
419      * @param Activity &$activity Activity that was posted
420      * @param User     $user      User that posted it
421      * @param Notice   &$notice   Resulting notice
422      *
423      * @return boolean hook value
424      */
425
426     function onStartAtomPubNewActivity(&$activity, $user, &$notice)
427     {
428         if ($this->isMyActivity($activity)) {
429
430             $options = array('source' => 'atompub');
431
432             // $user->getProfile() is a Profile
433             $this->saveNoticeFromActivity($activity,
434                                           $user->getProfile(),
435                                           $options);
436
437             return false;
438         }
439
440         return true;
441     }
442
443     /**
444      * Handle object imported from a backup file
445      *
446      * @param User           $user     User to import for
447      * @param ActivityObject $author   Original author per import file
448      * @param Activity       $activity Activity to import
449      * @param boolean        $trusted  Is this a trusted user?
450      * @param boolean        &$done    Is this done (success or unrecoverable error)
451      *
452      * @return boolean hook value
453      */
454
455     function onStartImportActivity($user, $author, $activity, $trusted, &$done)
456     {
457         if ($this->isMyActivity($activity)) {
458
459             $obj = $activity->objects[0];
460
461             $options = array('uri' => $object->id,
462                              'url' => $object->link,
463                              'source' => 'restore');
464
465             // $user->getProfile() is a Profile
466             $saved = $this->saveNoticeFromActivity($activity,
467                                                    $user->getProfile(),
468                                                    $options);
469
470             if (!empty($saved)) {
471                 $done = true;
472             }
473
474             return false;
475         }
476
477         return true;
478     }
479
480     /**
481      * Event handler gives the plugin a chance to add custom
482      * Atom XML ActivityStreams output from a previously filled-out
483      * ActivityObject.
484      *
485      * The atomOutput method is called if it's one of
486      * our matching types.
487      *
488      * @param ActivityObject $obj
489      * @param XMLOutputter $out to add elements at end of object
490      * @return boolean hook return value
491      */
492     function onEndActivityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
493     {
494         if (in_array($obj->type, $this->types())) {
495             $this->activityObjectOutputAtom($obj, $out);
496         }
497         return true;
498     }
499
500     /**
501      * Event handler gives the plugin a chance to add custom
502      * JSON ActivityStreams output from a previously filled-out
503      * ActivityObject.
504      *
505      * The activityObjectOutputJson method is called if it's one of
506      * our matching types.
507      *
508      * @param ActivityObject $obj
509      * @param array &$out JSON-targeted array which can be modified
510      * @return boolean hook return value
511      */
512     function onEndActivityObjectOutputJson(ActivityObject $obj, array &$out)
513     {
514         if (in_array($obj->type, $this->types())) {
515             $this->activityObjectOutputJson($obj, &$out);
516         }
517         return true;
518     }
519
520     function onStartShowEntryForms(&$tabs)
521     {
522         $tabs[$this->tag()] = $this->appTitle();
523         return true;
524     }
525
526     function onStartMakeEntryForm($tag, $out, &$form)
527     {
528         if ($tag == $this->tag()) {
529             $form = $this->entryForm($out);
530             return false;
531         }
532
533         return true;
534     }
535 }