]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Bookmark/BookmarkPlugin.php
01dd6f1eaae87a60c658ccf7e60a417b4af7a22a
[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
47 class BookmarkPlugin extends Plugin
48 {
49     const VERSION = '0.1';
50
51     /**
52      * Database schema setup
53      *
54      * @see Schema
55      * @see ColumnDef
56      *
57      * @return boolean hook value; true means continue processing, false means stop.
58      */
59
60     function onCheckSchema()
61     {
62         $schema = Schema::get();
63
64         // For storing user-submitted flags on profiles
65
66         $schema->ensureTable('bookmark',
67                              array(new ColumnDef('profile_id',
68                                                  'integer',
69                                                  null,
70                                                  false,
71                                                  'PRI'),
72                                    new ColumnDef('url',
73                                                  'varchar',
74                                                  255,
75                                                  false,
76                                                  'PRI'),
77                                    new ColumnDef('title',
78                                                  'varchar',
79                                                  255),
80                                    new ColumnDef('description',
81                                                  'text'),
82                                    new ColumnDef('uri',
83                                                  'varchar',
84                                                  255,
85                                                  false,
86                                                  'UNI'),
87                                    new ColumnDef('url_crc32',
88                                                  'integer',
89                                                  null,
90                                                  false,
91                                                  'MUL'),
92                                    new ColumnDef('created',
93                                                  'datetime',
94                                                  null,
95                                                  false,
96                                                  'MUL')));
97
98         try {
99             $schema->createIndex('bookmark', 
100                                  array('profile_id', 
101                                        'url_crc32'),
102                                  'bookmark_profile_url_idx');
103         } catch (Exception $e) {
104             common_log(LOG_ERR, $e->getMessage());
105         }
106
107         return true;
108     }
109
110     /**
111      * When a notice is deleted, delete the related Bookmark
112      *
113      * @param Notice $notice Notice being deleted
114      * 
115      * @return boolean hook value
116      */
117
118     function onNoticeDeleteRelated($notice)
119     {
120         $nb = Bookmark::getByNotice($notice);
121
122         if (!empty($nb)) {
123             $nb->delete();
124         }
125
126         return true;
127     }
128
129     /**
130      * Show the CSS necessary for this plugin
131      *
132      * @param Action $action the action being run
133      *
134      * @return boolean hook value
135      */
136
137     function onEndShowStyles($action)
138     {
139         $action->cssLink('plugins/Bookmark/bookmark.css');
140         return true;
141     }
142
143     /**
144      * Load related modules when needed
145      *
146      * @param string $cls Name of the class to be loaded
147      *
148      * @return boolean hook value; true means continue processing, false means stop.
149      */
150
151     function onAutoload($cls)
152     {
153         $dir = dirname(__FILE__);
154
155         switch ($cls)
156         {
157         case 'NewbookmarkAction':
158         case 'BookmarkpopupAction':
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 '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
182     function onRouterInitialized($m)
183     {
184         $m->connect('main/bookmark/new',
185                     array('action' => 'newbookmark'),
186                     array('id' => '[0-9]+'));
187
188         $m->connect('main/bookmark/popup', array('action' => 'bookmarkpopup'));
189
190         $m->connect('bookmark/:user/:created/:crc32',
191                     array('action' => 'showbookmark'),
192                     array('user' => '[0-9]+',
193                           'created' => '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z',
194                           'crc32' => '[0-9A-F]{8}'));
195
196         return true;
197     }
198
199     /**
200      * Output the HTML for a bookmark in a list
201      *
202      * @param NoticeListItem $nli The list item being shown.
203      *
204      * @return boolean hook value
205      */
206
207     function onStartShowNoticeItem($nli)
208     {
209         $nb = Bookmark::getByNotice($nli->notice);
210
211         if (!empty($nb)) {
212
213             $out     = $nli->out;
214             $notice  = $nli->notice;
215             $profile = $nli->profile;
216
217             $atts = $notice->attachments();
218
219             if (count($atts) < 1) {
220                 // Something wrong; let default code deal with it.
221                 return true;
222             }
223
224             $att = $atts[0];
225
226             $out->elementStart('h3');
227             $out->element('a',
228                           array('href' => $att->url),
229                           $nb->title);
230             $out->elementEnd('h3');
231
232             $out->elementStart('ul', array('class' => 'bookmark_tags'));
233             
234             // Replies look like "for:" tags
235
236             $replies = $nli->notice->getReplies();
237
238             if (!empty($replies)) {
239                 foreach ($replies as $reply) {
240                     $other = Profile::staticGet('id', $reply);
241                     $out->elementStart('li');
242                     $out->element('a', array('rel' => 'tag',
243                                              'href' => $other->profileurl,
244                                              'title' => $other->getBestName()),
245                                   sprintf('for:%s', $other->nickname));
246                     $out->elementEnd('li');
247                     $out->text(' ');
248                 }
249             }
250
251             $tags = $nli->notice->getTags();
252
253             foreach ($tags as $tag) {
254                 $out->elementStart('li');
255                 $out->element('a', 
256                               array('rel' => 'tag',
257                                     'href' => Notice_tag::url($tag)),
258                               $tag);
259                 $out->elementEnd('li');
260                 $out->text(' ');
261             }
262
263             $out->elementEnd('ul');
264
265             $out->element('p',
266                           array('class' => 'bookmark_description'),
267                           $nb->description);
268
269             $nli->showNoticeAttachments();
270
271             $out->elementStart('p', array('style' => 'float: left'));
272
273             $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
274
275             $out->element('img', array('src' => ($avatar) ?
276                                        $avatar->displayUrl() :
277                                        Avatar::defaultImage(AVATAR_MINI_SIZE),
278                                        'class' => 'avatar photo bookmark_avatar',
279                                        'width' => AVATAR_MINI_SIZE,
280                                        'height' => AVATAR_MINI_SIZE,
281                                        'alt' => $profile->getBestName()));
282             $out->raw('&nbsp;');
283             $out->element('a', array('href' => $profile->profileurl,
284                                      'title' => $profile->getBestName()),
285                           $profile->nickname);
286
287             $nli->showNoticeLink();
288             $nli->showNoticeSource();
289             $nli->showNoticeLocation();
290             $nli->showContext();
291             $nli->showRepeat();
292
293             $out->elementEnd('p');
294
295             $nli->showNoticeOptions();
296
297             return false;
298         }
299         return true;
300     }
301
302     /**
303      * Render a notice as a Bookmark object
304      *
305      * @param Notice         $notice  Notice to render
306      * @param ActivityObject &$object Empty object to fill
307      *
308      * @return boolean hook value
309      */
310      
311     function onStartActivityObjectFromNotice($notice, &$object)
312     {
313         common_log(LOG_INFO,
314                    "Checking {$notice->uri} to see if it's a bookmark.");
315
316         $nb = Bookmark::getByNotice($notice);
317                                          
318         if (!empty($nb)) {
319
320             common_log(LOG_INFO,
321                        "Formatting notice {$notice->uri} as a bookmark.");
322
323             $object->id      = $notice->uri;
324             $object->type    = ActivityObject::BOOKMARK;
325             $object->title   = $nb->title;
326             $object->summary = $nb->description;
327             $object->link    = $notice->bestUrl();
328
329             // Attributes of the URL
330
331             $attachments = $notice->attachments();
332
333             if (count($attachments) != 1) {
334                 throw new ServerException(_('Bookmark notice with the '.
335                                             'wrong number of attachments.'));
336             }
337
338             $target = $attachments[0];
339
340             $attrs = array('rel' => 'related',
341                            'href' => $target->url);
342
343             if (!empty($target->title)) {
344                 $attrs['title'] = $target->title;
345             }
346
347             $object->extra[] = array('link', $attrs);
348                                                    
349             // Attributes of the thumbnail, if any
350
351             $thumbnail = $target->getThumbnail();
352
353             if (!empty($thumbnail)) {
354                 $tattrs = array('rel' => 'preview',
355                                 'href' => $thumbnail->url);
356
357                 if (!empty($thumbnail->width)) {
358                     $tattrs['media:width'] = $thumbnail->width;
359                 }
360
361                 if (!empty($thumbnail->height)) {
362                     $tattrs['media:height'] = $thumbnail->height;
363                 }
364
365                 $object->extra[] = array('link', $attrs);
366             }
367
368             return false;
369         }
370
371         return true;
372     }
373
374     /**
375      * Add our two queue handlers to the queue manager
376      *
377      * @param QueueManager $qm current queue manager
378      * 
379      * @return boolean hook value
380      */
381
382     function onEndInitializeQueueManager($qm)
383     {
384         $qm->connect('dlcsback', 'DeliciousBackupImporter');
385         $qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter');
386         return true;
387     }
388
389     /**
390      * Plugin version data
391      *
392      * @param array &$versions array of version data
393      * 
394      * @return value
395      */
396
397     function onPluginVersion(&$versions)
398     {
399         $versions[] = array('name' => 'Sample',
400                             'version' => self::VERSION,
401                             'author' => 'Evan Prodromou',
402                             'homepage' => 'http://status.net/wiki/Plugin:Bookmark',
403                             'rawdescription' =>
404                             _m('Simple extension for supporting bookmarks.'));
405         return true;
406     }
407
408     /**
409      * Load our document if requested
410      *
411      * @param string &$title  Title to fetch
412      * @param string &$output HTML to output
413      *
414      * @return boolean hook value
415      */
416
417     function onStartLoadDoc(&$title, &$output)
418     {
419         if ($title == 'bookmarklet') {
420             $filename = INSTALLDIR.'/plugins/Bookmark/bookmarklet';
421
422             $c      = file_get_contents($filename);
423             $output = common_markup_to_html($c);
424             return false; // success!
425         }
426
427         return true;
428     }
429
430     /**
431      * Handle a posted bookmark from PuSH
432      *
433      * @param Activity        $activity activity to handle
434      * @param Ostatus_profile $oprofile Profile for the feed
435      *
436      * @return boolean hook value
437      */
438
439     function onStartHandleFeedEntryWithProfile($activity, $oprofile) {
440
441         common_log(LOG_INFO, "BookmarkPlugin called for new feed entry.");
442
443         if ($activity->verb == ActivityVerb::POST &&
444             $activity->objects[0]->type == ActivityObject::BOOKMARK) {
445
446             common_log(LOG_INFO, "Importing activity {$activity->id} as a bookmark.");
447
448             $author = $oprofile->checkAuthorship($activity);
449
450             if (empty($author)) {
451                 throw new ClientException(_('Can\'t get author for activity.'));
452             }
453
454             self::_postRemoteBookmark($author,
455                                       $activity);
456
457             return false;
458         }
459
460         return true;
461     }
462
463     static private function _postRemoteBookmark(Ostatus_profile $author, Activity $activity)
464     {
465         $bookmark = $activity->objects[0];
466
467         $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related');
468
469         if (count($relLinkEls) < 1) {
470             throw new ClientException(_('Expected exactly 1 link rel=related in a Bookmark.'));
471         }
472
473         if (count($relLinkEls) > 1) {
474             common_log(LOG_WARNING, "Got too many link rel=related in a Bookmark.");
475         }
476
477         $linkEl = $relLinkEls[0];
478
479         $url = $linkEl->getAttribute('href');
480
481         $tags = array();
482
483         foreach ($activity->categories as $category) {
484             $tags[] = common_canonical_tag($category->term);
485         }
486
487         $options = array('uri' => $bookmark->id,
488                          'url' => $bookmark->link,
489                          'created' => common_sql_time($activity->time),
490                          'is_local' => Notice::REMOTE_OMB,
491                          'source' => 'ostatus');
492
493         // Fill in location if available
494
495         $location = $activity->context->location;
496
497         if ($location) {
498             $options['lat'] = $location->lat;
499             $options['lon'] = $location->lon;
500             if ($location->location_id) {
501                 $options['location_ns'] = $location->location_ns;
502                 $options['location_id'] = $location->location_id;
503             }
504         }
505
506         $replies = $activity->context->attention;
507         $options['groups'] = $author->filterReplies($author, $replies);
508         $options['replies'] = $replies;
509
510         // Maintain direct reply associations
511         // @fixme what about conversation ID?
512
513         if (!empty($activity->context->replyToID)) {
514             $orig = Notice::staticGet('uri',
515                                       $activity->context->replyToID);
516             if (!empty($orig)) {
517                 $options['reply_to'] = $orig->id;
518             }
519         }
520
521         Bookmark::saveNew($author->localProfile(),
522                                  $bookmark->title,
523                                  $url,
524                                  $tags,
525                                  $bookmark->summary,
526                                  $options);
527     }
528 }
529