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