BookmarkPlugin.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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. if (!defined('GNUSOCIAL')) { exit(1); }
  31. /**
  32. * Bookmark plugin main class
  33. *
  34. * @category Bookmark
  35. * @package StatusNet
  36. * @author Brion Vibber <brionv@status.net>
  37. * @author Evan Prodromou <evan@status.net>
  38. * @copyright 2010 StatusNet, Inc.
  39. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
  40. * @link http://status.net/
  41. */
  42. class BookmarkPlugin extends MicroAppPlugin
  43. {
  44. const PLUGIN_VERSION = '0.1.0';
  45. const IMPORTDELICIOUS = 'BookmarkPlugin:IMPORTDELICIOUS';
  46. /**
  47. * Authorization for importing delicious bookmarks
  48. *
  49. * By default, everyone can import bookmarks except silenced people.
  50. *
  51. * @param Profile $profile Person whose rights to check
  52. * @param string $right Right to check; const value
  53. * @param boolean &$result Result of the check, writeable
  54. *
  55. * @return boolean hook value
  56. */
  57. function onUserRightsCheck($profile, $right, &$result)
  58. {
  59. if ($right == self::IMPORTDELICIOUS) {
  60. $result = !$profile->isSilenced();
  61. return false;
  62. }
  63. return true;
  64. }
  65. /**
  66. * Database schema setup
  67. *
  68. * @see Schema
  69. * @see ColumnDef
  70. *
  71. * @return boolean hook value; true means continue processing, false means stop.
  72. */
  73. function onCheckSchema()
  74. {
  75. $schema = Schema::get();
  76. $schema->ensureTable('bookmark', Bookmark::schemaDef());
  77. return true;
  78. }
  79. /**
  80. * Show the CSS necessary for this plugin
  81. *
  82. * @param Action $action the action being run
  83. *
  84. * @return boolean hook value
  85. */
  86. function onEndShowStyles($action)
  87. {
  88. $action->cssLink($this->path('css/bookmark.css'));
  89. return true;
  90. }
  91. function onEndShowScripts($action)
  92. {
  93. $action->script($this->path('js/bookmark.js'));
  94. return true;
  95. }
  96. /**
  97. * Map URLs to actions
  98. *
  99. * @param URLMapper $m path-to-action mapper
  100. *
  101. * @return boolean hook value; true means continue processing, false means stop.
  102. */
  103. public function onRouterInitialized(URLMapper $m)
  104. {
  105. if (common_config('singleuser', 'enabled')) {
  106. $nickname = User::singleUserNickname();
  107. $m->connect('bookmarks',
  108. ['action' => 'bookmarks',
  109. 'nickname' => $nickname]);
  110. $m->connect('bookmarks/rss',
  111. ['action' => 'bookmarksrss',
  112. 'nickname' => $nickname]);
  113. } else {
  114. $m->connect(':nickname/bookmarks',
  115. ['action' => 'bookmarks'],
  116. ['nickname' => Nickname::DISPLAY_FMT]);
  117. $m->connect(':nickname/bookmarks/rss',
  118. ['action' => 'bookmarksrss'],
  119. ['nickname' => Nickname::DISPLAY_FMT]);
  120. }
  121. $m->connect('api/bookmarks/:id.:format',
  122. ['action' => 'ApiTimelineBookmarks'],
  123. ['id' => Nickname::INPUT_FMT,
  124. 'format' => '(xml|json|rss|atom|as)']);
  125. $m->connect('main/bookmark/new',
  126. ['action' => 'newbookmark']);
  127. $m->connect('main/bookmark/popup',
  128. ['action' => 'bookmarkpopup']);
  129. $m->connect('main/bookmark/import',
  130. ['action' => 'importdelicious']);
  131. $m->connect('main/bookmark/forurl',
  132. ['action' => 'bookmarkforurl']);
  133. $m->connect('bookmark/:id',
  134. ['action' => 'showbookmark'],
  135. ['id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}']);
  136. $m->connect('notice/by-url/:id',
  137. ['action' => 'noticebyurl'],
  138. ['id' => '[0-9]+']);
  139. return true;
  140. }
  141. /**
  142. * Add our two queue handlers to the queue manager
  143. *
  144. * @param QueueManager $qm current queue manager
  145. *
  146. * @return boolean hook value
  147. */
  148. function onEndInitializeQueueManager($qm)
  149. {
  150. $qm->connect('dlcsback', 'DeliciousBackupImporter');
  151. $qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter');
  152. return true;
  153. }
  154. /**
  155. * Plugin version data
  156. *
  157. * @param array &$versions array of version data
  158. *
  159. * @return value
  160. */
  161. function onPluginVersion(array &$versions)
  162. {
  163. $versions[] = array('name' => 'Bookmark',
  164. 'version' => self::PLUGIN_VERSION,
  165. 'author' => 'Evan Prodromou, Stephane Berube, Jean Baptiste Favre, Mikael Nordfeldth',
  166. 'homepage' => 'https://gnu.io/social',
  167. 'description' =>
  168. // TRANS: Plugin description.
  169. _m('Plugin for posting bookmarks. ') .
  170. 'BookmarkList feature has been developped by Stephane Berube. ' .
  171. 'Integration has been done by Jean Baptiste Favre.');
  172. return true;
  173. }
  174. /**
  175. * Load our document if requested
  176. *
  177. * @param string &$title Title to fetch
  178. * @param string &$output HTML to output
  179. *
  180. * @return boolean hook value
  181. */
  182. function onStartLoadDoc(&$title, &$output)
  183. {
  184. if ($title == 'bookmarklet') {
  185. $filename = INSTALLDIR.'/plugins/Bookmark/bookmarklet';
  186. $c = file_get_contents($filename);
  187. $output = common_markup_to_html($c);
  188. return false; // success!
  189. }
  190. return true;
  191. }
  192. /**
  193. * Show a link to our delicious import page on profile settings form
  194. *
  195. * @param Action $action Profile settings action being shown
  196. *
  197. * @return boolean hook value
  198. */
  199. function onEndProfileSettingsActions($action)
  200. {
  201. $user = common_current_user();
  202. if (!empty($user) && $user->hasRight(self::IMPORTDELICIOUS)) {
  203. $action->elementStart('li');
  204. $action->element('a',
  205. array('href' => common_local_url('importdelicious')),
  206. // TRANS: Link text in proile leading to import form.
  207. _m('Import del.icio.us bookmarks'));
  208. $action->elementEnd('li');
  209. }
  210. return true;
  211. }
  212. /**
  213. * Modify the default menu to link to our custom action
  214. *
  215. * Using event handlers, it's possible to modify the default UI for pages
  216. * almost without limit. In this method, we add a menu item to the default
  217. * primary menu for the interface to link to our action.
  218. *
  219. * The Action class provides a rich set of events to hook, as well as output
  220. * methods.
  221. *
  222. * @param Action $action The current action handler. Use this to
  223. * do any output.
  224. *
  225. * @return boolean hook value; true means continue processing, false means stop.
  226. *
  227. * @see Action
  228. */
  229. function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped=null)
  230. {
  231. $menu->menuItem(common_local_url('bookmarks', array('nickname' => $target->getNickname())),
  232. // TRANS: Menu item in sample plugin.
  233. _m('Bookmarks'),
  234. // TRANS: Menu item title in sample plugin.
  235. _m('A list of your bookmarks'), false, 'nav_timeline_bookmarks');
  236. return true;
  237. }
  238. function types()
  239. {
  240. return array(ActivityObject::BOOKMARK);
  241. }
  242. /**
  243. * When a notice is deleted, delete the related Bookmark
  244. *
  245. * @param Notice $notice Notice being deleted
  246. *
  247. * @return boolean hook value
  248. */
  249. function deleteRelated(Notice $notice)
  250. {
  251. try {
  252. $nb = Bookmark::fromStored($notice);
  253. } catch (NoResultException $e) {
  254. throw new AlreadyFulfilledException('Bookmark already gone when deleting: '.$e->getMessage());
  255. }
  256. $nb->delete();
  257. return true;
  258. }
  259. /**
  260. * Save a bookmark from an activity
  261. *
  262. * @param Activity $activity Activity to save
  263. * @param Profile $actor Profile to use as author
  264. * @param array $options Options to pass to bookmark-saving code
  265. *
  266. * @return Notice resulting notice
  267. */
  268. protected function saveObjectFromActivity(Activity $activity, Notice $stored, array $options=array())
  269. {
  270. return Bookmark::saveActivityObject($activity->objects[0], $stored);
  271. }
  272. public function onEndNoticeAsActivity(Notice $stored, Activity $act, Profile $scoped=null)
  273. {
  274. if (!$this->isMyNotice($stored)) {
  275. return true;
  276. }
  277. $this->extendActivity($stored, $act, $scoped);
  278. return false;
  279. }
  280. public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null)
  281. {
  282. /*$hashtags = array();
  283. $taglinks = array();
  284. foreach ($tags as $tag) {
  285. $hashtags[] = '#'.$tag;
  286. $attrs = array('href' => Notice_tag::url($tag),
  287. 'rel' => $tag,
  288. 'class' => 'tag');
  289. $taglinks[] = XMLStringer::estring('a', $attrs, $tag);
  290. }*/
  291. }
  292. function activityObjectFromNotice(Notice $notice)
  293. {
  294. return Bookmark::fromStored($notice)->asActivityObject();
  295. }
  296. function entryForm($out)
  297. {
  298. return new InitialBookmarkForm($out);
  299. }
  300. function tag()
  301. {
  302. return 'bookmark';
  303. }
  304. function appTitle()
  305. {
  306. // TRANS: Application title.
  307. return _m('TITLE','Bookmark');
  308. }
  309. function onEndUpgrade()
  310. {
  311. printfnq('Making sure Bookmark notices have correct verb and object_type...');
  312. // Version 0.9.x of the plugin didn't stamp notices
  313. // with verb and object-type (for obvious reasons). Update
  314. // those notices here.
  315. $notice = new Notice();
  316. $notice->joinAdd(array('uri', 'bookmark:uri'));
  317. $notice->whereAdd('object_type IS NULL OR object_type = '.$notice->_quote(ActivityObject::NOTE));
  318. $notice->find();
  319. while ($notice->fetch()) {
  320. $original = clone($notice);
  321. $notice->verb = ActivityVerb::POST;
  322. $notice->object_type = ActivityObject::BOOKMARK;
  323. $notice->update($original);
  324. }
  325. printfnq("DONE.\n");
  326. }
  327. public function activityObjectOutputJson(ActivityObject $obj, array &$out)
  328. {
  329. assert($obj->type == ActivityObject::BOOKMARK);
  330. $bm = Bookmark::getByPK(array('uri' => $obj->id));
  331. $out['displayName'] = $bm->getTitle();
  332. $out['targetUrl'] = $bm->getUrl();
  333. return true;
  334. }
  335. protected function showNoticeContent(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
  336. {
  337. $nb = Bookmark::fromStored($stored);
  338. // Whether to nofollow
  339. $attrs = array('href' => $nb->getUrl(), 'class' => 'bookmark-title');
  340. $nf = common_config('nofollow', 'external');
  341. if ($nf == 'never' || ($nf == 'sometimes' and $out instanceof ShowstreamAction)) {
  342. $attrs['rel'] = 'external';
  343. } else {
  344. $attrs['rel'] = 'nofollow external';
  345. }
  346. $out->elementStart('h3');
  347. $out->element('a', $attrs, $nb->getTitle());
  348. $out->elementEnd('h3');
  349. // Replies look like "for:" tags
  350. $replies = $stored->getReplies();
  351. $tags = $stored->getTags();
  352. if (!empty($nb->description)) {
  353. $out->element('p',
  354. array('class' => 'bookmark-description'),
  355. $nb->description);
  356. }
  357. if (!empty($replies) || !empty($tags)) {
  358. $out->elementStart('ul', array('class' => 'bookmark-tags'));
  359. foreach ($replies as $reply) {
  360. $other = Profile::getByPK($reply);
  361. $out->elementStart('li');
  362. $out->element('a', array('rel' => 'tag',
  363. 'href' => $other->getUrl(),
  364. 'title' => $other->getBestName()),
  365. sprintf('for:%s', $other->getNickname()));
  366. $out->elementEnd('li');
  367. $out->text(' ');
  368. }
  369. foreach ($tags as $tag) {
  370. $tag = trim($tag);
  371. if (!empty($tag)) {
  372. $out->elementStart('li');
  373. $out->element('a',
  374. array('rel' => 'tag',
  375. 'href' => Notice_tag::url($tag)),
  376. $tag);
  377. $out->elementEnd('li');
  378. $out->text(' ');
  379. }
  380. }
  381. $out->elementEnd('ul');
  382. }
  383. }
  384. }