]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Bookmark/BookmarkPlugin.php
hard-require the adapter
[quix0rs-gnu-social.git] / plugins / Bookmark / BookmarkPlugin.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010, StatusNet, Inc.
5  *
6  * A plugin to enable social-bookmarking functionality
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  SocialBookmark
24  * @package   StatusNet
25  * @author    Evan Prodromou <evan@status.net>
26  * @copyright 2010 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     exit(1);
33 }
34
35 require_once INSTALLDIR.'/plugins/Bookmark/bookmarknoticelistitemadapter.php';
36
37 /**
38  * Bookmark plugin main class
39  *
40  * @category  Bookmark
41  * @package   StatusNet
42  * @author    Brion Vibber <brionv@status.net>
43  * @author    Evan Prodromou <evan@status.net>
44  * @copyright 2010 StatusNet, Inc.
45  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
46  * @link      http://status.net/
47  */
48 class BookmarkPlugin extends MicroAppPlugin
49 {
50     const VERSION         = '0.1';
51     const IMPORTDELICIOUS = 'BookmarkPlugin:IMPORTDELICIOUS';
52
53     /**
54      * Authorization for importing delicious bookmarks
55      *
56      * By default, everyone can import bookmarks except silenced people.
57      *
58      * @param Profile $profile Person whose rights to check
59      * @param string  $right   Right to check; const value
60      * @param boolean &$result Result of the check, writeable
61      *
62      * @return boolean hook value
63      */
64     function onUserRightsCheck($profile, $right, &$result)
65     {
66         if ($right == self::IMPORTDELICIOUS) {
67             $result = !$profile->isSilenced();
68             return false;
69         }
70         return true;
71     }
72
73     /**
74      * Database schema setup
75      *
76      * @see Schema
77      * @see ColumnDef
78      *
79      * @return boolean hook value; true means continue processing, false means stop.
80      */
81     function onCheckSchema()
82     {
83         $schema = Schema::get();
84
85         // For storing user-submitted flags on profiles
86
87         $schema->ensureTable('bookmark',
88                              array(new ColumnDef('id',
89                                                  'char',
90                                                  36,
91                                                  false,
92                                                  'PRI'),
93                                    new ColumnDef('profile_id',
94                                                  'integer',
95                                                  null,
96                                                  false,
97                                                  'MUL'),
98                                    new ColumnDef('url',
99                                                  'varchar',
100                                                  255,
101                                                  false,
102                                                  'MUL'),
103                                    new ColumnDef('title',
104                                                  'varchar',
105                                                  255),
106                                    new ColumnDef('description',
107                                                  'text'),
108                                    new ColumnDef('uri',
109                                                  'varchar',
110                                                  255,
111                                                  false,
112                                                  'UNI'),
113                                    new ColumnDef('created',
114                                                  'datetime',
115                                                  null,
116                                                  false,
117                                                  'MUL')));
118
119         return true;
120     }
121
122     /**
123      * Show the CSS necessary for this plugin
124      *
125      * @param Action $action the action being run
126      *
127      * @return boolean hook value
128      */
129     function onEndShowStyles($action)
130     {
131         $action->cssLink($this->path('bookmark.css'));
132         return true;
133     }
134
135     function onEndShowScripts($action)
136     {
137         $action->script($this->path('js/bookmark.js'));
138         return true;
139     }
140     /**
141      * Load related modules when needed
142      *
143      * @param string $cls Name of the class to be loaded
144      *
145      * @return boolean hook value; true means continue processing, false means stop.
146      */
147     function onAutoload($cls)
148     {
149         $dir = dirname(__FILE__);
150
151         switch ($cls)
152         {
153         case 'ShowbookmarkAction':
154         case 'NewbookmarkAction':
155         case 'BookmarkpopupAction':
156         case 'NoticebyurlAction':
157         case 'BookmarkforurlAction':
158         case 'ImportdeliciousAction':
159             include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
160             return false;
161         case 'Bookmark':
162             include_once $dir.'/'.$cls.'.php';
163             return false;
164         case 'BookmarkForm':
165         case 'InitialBookmarkForm':
166         case 'DeliciousBackupImporter':
167         case 'DeliciousBookmarkImporter':
168         case 'BookmarkNoticeListItemAdapter':
169             include_once $dir.'/'.strtolower($cls).'.php';
170             return false;
171         default:
172             return true;
173         }
174     }
175
176     /**
177      * Map URLs to actions
178      *
179      * @param Net_URL_Mapper $m path-to-action mapper
180      *
181      * @return boolean hook value; true means continue processing, false means stop.
182      */
183     function onRouterInitialized($m)
184     {
185         $m->connect('main/bookmark/new',
186                     array('action' => 'newbookmark'),
187                     array('id' => '[0-9]+'));
188
189         $m->connect('main/bookmark/popup',
190                     array('action' => 'bookmarkpopup'));
191
192         $m->connect('main/bookmark/import',
193                     array('action' => 'importdelicious'));
194
195         $m->connect('main/bookmark/forurl',
196                     array('action' => 'bookmarkforurl'));
197
198         $m->connect('bookmark/:id',
199                     array('action' => 'showbookmark'),
200                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
201
202         $m->connect('notice/by-url/:id',
203                     array('action' => 'noticebyurl'),
204                     array('id' => '[0-9]+'));
205
206         return true;
207     }
208
209
210     /**
211      * Add our two queue handlers to the queue manager
212      *
213      * @param QueueManager $qm current queue manager
214      *
215      * @return boolean hook value
216      */
217     function onEndInitializeQueueManager($qm)
218     {
219         $qm->connect('dlcsback', 'DeliciousBackupImporter');
220         $qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter');
221         return true;
222     }
223
224     /**
225      * Plugin version data
226      *
227      * @param array &$versions array of version data
228      *
229      * @return value
230      */
231     function onPluginVersion(&$versions)
232     {
233         $versions[] = array('name' => 'Bookmark',
234                             'version' => self::VERSION,
235                             'author' => 'Evan Prodromou',
236                             'homepage' => 'http://status.net/wiki/Plugin:Bookmark',
237                             'description' =>
238                             // TRANS: Plugin description.
239                             _m('Simple extension for supporting bookmarks.'));
240         return true;
241     }
242
243     /**
244      * Load our document if requested
245      *
246      * @param string &$title  Title to fetch
247      * @param string &$output HTML to output
248      *
249      * @return boolean hook value
250      */
251     function onStartLoadDoc(&$title, &$output)
252     {
253         if ($title == 'bookmarklet') {
254             $filename = INSTALLDIR.'/plugins/Bookmark/bookmarklet';
255
256             $c      = file_get_contents($filename);
257             $output = common_markup_to_html($c);
258             return false; // success!
259         }
260
261         return true;
262     }
263
264     /**
265      * Show a link to our delicious import page on profile settings form
266      *
267      * @param Action $action Profile settings action being shown
268      *
269      * @return boolean hook value
270      */
271     function onEndProfileSettingsActions($action)
272     {
273         $user = common_current_user();
274
275         if (!empty($user) && $user->hasRight(self::IMPORTDELICIOUS)) {
276             $action->elementStart('li');
277             $action->element('a',
278                              array('href' => common_local_url('importdelicious')),
279                              // TRANS: Link text in proile leading to import form.
280                              _m('Import del.icio.us bookmarks'));
281             $action->elementEnd('li');
282         }
283
284         return true;
285     }
286
287     /**
288      * Output our CSS class for bookmark notice list elements
289      *
290      * @param NoticeListItem $nli The item being shown
291      *
292      * @return boolean hook value
293      */
294
295     function onStartOpenNoticeListItemElement($nli)
296     {
297         $nb = Bookmark::getByNotice($nli->notice);
298         if (!empty($nb)) {
299             $id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
300             $class = 'hentry notice bookmark';
301             if ($nli->notice->scope != 0 && $nli->notice->scope != 1) {
302                 $class .= ' limited-scope';
303             }
304             $nli->out->elementStart('li', array('class' => $class,
305                                                  'id' => 'notice-' . $id));
306             Event::handle('EndOpenNoticeListItemElement', array($nli));
307             return false;
308         }
309         return true;
310     }
311
312     /**
313      * Save a remote bookmark (from Salmon or PuSH)
314      *
315      * @param Ostatus_profile $author   Author of the bookmark
316      * @param Activity        $activity Activity to save
317      *
318      * @return Notice resulting notice.
319      */
320     static private function _postRemoteBookmark(Ostatus_profile $author,
321                                                 Activity $activity)
322     {
323         $bookmark = $activity->objects[0];
324
325         $options = array('uri' => $bookmark->id,
326                          'url' => $bookmark->link,
327                          'is_local' => Notice::REMOTE_OMB,
328                          'source' => 'ostatus');
329
330         return self::_postBookmark($author->localProfile(), $activity, $options);
331     }
332
333     /**
334      * Test if an activity represents posting a bookmark
335      *
336      * @param Activity $activity Activity to test
337      *
338      * @return true if it's a Post of a Bookmark, else false
339      */
340     static private function _isPostBookmark($activity)
341     {
342         return ($activity->verb == ActivityVerb::POST &&
343                 $activity->objects[0]->type == ActivityObject::BOOKMARK);
344     }
345
346     function types()
347     {
348         return array(ActivityObject::BOOKMARK);
349     }
350
351     /**
352      * When a notice is deleted, delete the related Bookmark
353      *
354      * @param Notice $notice Notice being deleted
355      *
356      * @return boolean hook value
357      */
358     function deleteRelated($notice)
359     {
360         $nb = Bookmark::getByNotice($notice);
361
362         if (!empty($nb)) {
363             $nb->delete();
364         }
365
366         return true;
367     }
368
369     /**
370      * Save a bookmark from an activity
371      *
372      * @param Activity $activity Activity to save
373      * @param Profile  $profile  Profile to use as author
374      * @param array    $options  Options to pass to bookmark-saving code
375      *
376      * @return Notice resulting notice
377      */
378     function saveNoticeFromActivity($activity, $profile, $options=array())
379     {
380         $bookmark = $activity->objects[0];
381
382         $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related');
383
384         if (count($relLinkEls) < 1) {
385             // TRANS: Client exception thrown when a bookmark is formatted incorrectly.
386             throw new ClientException(_m('Expected exactly 1 link '.
387                                         'rel=related in a Bookmark.'));
388         }
389
390         if (count($relLinkEls) > 1) {
391             common_log(LOG_WARNING,
392                        "Got too many link rel=related in a Bookmark.");
393         }
394
395         $linkEl = $relLinkEls[0];
396
397         $url = $linkEl->getAttribute('href');
398
399         $tags = array();
400
401         foreach ($activity->categories as $category) {
402             $tags[] = common_canonical_tag($category->term);
403         }
404
405         if (!empty($activity->time)) {
406             $options['created'] = common_sql_date($activity->time);
407         }
408
409         // Fill in location if available
410
411         $location = $activity->context->location;
412
413         if ($location) {
414             $options['lat'] = $location->lat;
415             $options['lon'] = $location->lon;
416             if ($location->location_id) {
417                 $options['location_ns'] = $location->location_ns;
418                 $options['location_id'] = $location->location_id;
419             }
420         }
421
422         $replies = $activity->context->attention;
423
424         $options['groups']  = array();
425         $options['replies'] = array();
426
427         foreach ($replies as $replyURI) {
428             $other = Profile::fromURI($replyURI);
429             if (!empty($other)) {
430                 $options['replies'][] = $replyURI;
431             } else {
432                 $group = User_group::staticGet('uri', $replyURI);
433                 if (!empty($group)) {
434                     $options['groups'][] = $replyURI;
435                 }
436             }
437         }
438
439         // Maintain direct reply associations
440         // @fixme what about conversation ID?
441
442         if (!empty($activity->context->replyToID)) {
443             $orig = Notice::staticGet('uri',
444                                       $activity->context->replyToID);
445             if (!empty($orig)) {
446                 $options['reply_to'] = $orig->id;
447             }
448         }
449
450         return Bookmark::saveNew($profile,
451                                  $bookmark->title,
452                                  $url,
453                                  $tags,
454                                  $bookmark->summary,
455                                  $options);
456     }
457
458     function activityObjectFromNotice($notice)
459     {
460         assert($this->isMyNotice($notice));
461
462         common_log(LOG_INFO,
463                    "Formatting notice {$notice->uri} as a bookmark.");
464
465         $object = new ActivityObject();
466         $nb = Bookmark::getByNotice($notice);
467
468         $object->id      = $notice->uri;
469         $object->type    = ActivityObject::BOOKMARK;
470         $object->title   = $nb->title;
471         $object->summary = $nb->description;
472         $object->link    = $notice->bestUrl();
473
474         // Attributes of the URL
475
476         $attachments = $notice->attachments();
477
478         if (count($attachments) != 1) {
479             // TRANS: Server exception thrown when a bookmark has multiple attachments.
480             throw new ServerException(_m('Bookmark notice with the '.
481                                         'wrong number of attachments.'));
482         }
483
484         $target = $attachments[0];
485
486         $attrs = array('rel' => 'related',
487                        'href' => $target->url);
488
489         if (!empty($target->title)) {
490             $attrs['title'] = $target->title;
491         }
492
493         $object->extra[] = array('link', $attrs, null);
494
495         // Attributes of the thumbnail, if any
496
497         $thumbnail = $target->getThumbnail();
498
499         if (!empty($thumbnail)) {
500             $tattrs = array('rel' => 'preview',
501                             'href' => $thumbnail->url);
502
503             if (!empty($thumbnail->width)) {
504                 $tattrs['media:width'] = $thumbnail->width;
505             }
506
507             if (!empty($thumbnail->height)) {
508                 $tattrs['media:height'] = $thumbnail->height;
509             }
510
511             $object->extra[] = array('link', $attrs, null);
512         }
513
514         return $object;
515     }
516
517     /**
518      * Given a notice list item, returns an adapter specific
519      * to this plugin.
520      *
521      * @param NoticeListItem $nli item to adapt
522      *
523      * @return NoticeListItemAdapter adapter or null
524      */
525     function adaptNoticeListItem($nli)
526     {
527         return new BookmarkNoticeListItemAdapter($nli);
528     }
529
530     function entryForm($out)
531     {
532         return new InitialBookmarkForm($out);
533     }
534
535     function tag()
536     {
537         return 'bookmark';
538     }
539
540     function appTitle()
541     {
542         // TRANS: Application title.
543         return _m('TITLE','Bookmark');
544     }
545 }