BookmarkPlugin.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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 VERSION = '0.1';
  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. array('action' => 'bookmarks', 'nickname' => $nickname));
  109. $m->connect('bookmarks/rss',
  110. array('action' => 'bookmarksrss', 'nickname' => $nickname));
  111. } else {
  112. $m->connect(':nickname/bookmarks',
  113. array('action' => 'bookmarks'),
  114. array('nickname' => Nickname::DISPLAY_FMT));
  115. $m->connect(':nickname/bookmarks/rss',
  116. array('action' => 'bookmarksrss'),
  117. array('nickname' => Nickname::DISPLAY_FMT));
  118. }
  119. $m->connect('api/bookmarks/:id.:format',
  120. array('action' => 'ApiTimelineBookmarks',
  121. 'id' => Nickname::INPUT_FMT,
  122. 'format' => '(xml|json|rss|atom|as)'));
  123. $m->connect('main/bookmark/new',
  124. array('action' => 'newbookmark'),
  125. array('id' => '[0-9]+'));
  126. $m->connect('main/bookmark/popup',
  127. array('action' => 'bookmarkpopup'));
  128. $m->connect('main/bookmark/import',
  129. array('action' => 'importdelicious'));
  130. $m->connect('main/bookmark/forurl',
  131. array('action' => 'bookmarkforurl'));
  132. $m->connect('bookmark/:id',
  133. array('action' => 'showbookmark'),
  134. array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
  135. $m->connect('notice/by-url/:id',
  136. array('action' => 'noticebyurl'),
  137. array('id' => '[0-9]+'));
  138. return true;
  139. }
  140. /**
  141. * Add our two queue handlers to the queue manager
  142. *
  143. * @param QueueManager $qm current queue manager
  144. *
  145. * @return boolean hook value
  146. */
  147. function onEndInitializeQueueManager($qm)
  148. {
  149. $qm->connect('dlcsback', 'DeliciousBackupImporter');
  150. $qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter');
  151. return true;
  152. }
  153. /**
  154. * Plugin version data
  155. *
  156. * @param array &$versions array of version data
  157. *
  158. * @return value
  159. */
  160. function onPluginVersion(array &$versions)
  161. {
  162. $versions[] = array('name' => 'Bookmark',
  163. 'version' => GNUSOCIAL_VERSION,
  164. 'author' => 'Evan Prodromou, Stephane Berube, Jean Baptiste Favre, Mikael Nordfeldth',
  165. 'homepage' => 'https://gnu.io/social',
  166. 'description' =>
  167. // TRANS: Plugin description.
  168. _m('Plugin for posting bookmarks. ') .
  169. 'BookmarkList feature has been developped by Stephane Berube. ' .
  170. 'Integration has been done by Jean Baptiste Favre.');
  171. return true;
  172. }
  173. /**
  174. * Load our document if requested
  175. *
  176. * @param string &$title Title to fetch
  177. * @param string &$output HTML to output
  178. *
  179. * @return boolean hook value
  180. */
  181. function onStartLoadDoc(&$title, &$output)
  182. {
  183. if ($title == 'bookmarklet') {
  184. $filename = INSTALLDIR.'/plugins/Bookmark/bookmarklet';
  185. $c = file_get_contents($filename);
  186. $output = common_markup_to_html($c);
  187. return false; // success!
  188. }
  189. return true;
  190. }
  191. /**
  192. * Show a link to our delicious import page on profile settings form
  193. *
  194. * @param Action $action Profile settings action being shown
  195. *
  196. * @return boolean hook value
  197. */
  198. function onEndProfileSettingsActions($action)
  199. {
  200. $user = common_current_user();
  201. if (!empty($user) && $user->hasRight(self::IMPORTDELICIOUS)) {
  202. $action->elementStart('li');
  203. $action->element('a',
  204. array('href' => common_local_url('importdelicious')),
  205. // TRANS: Link text in proile leading to import form.
  206. _m('Import del.icio.us bookmarks'));
  207. $action->elementEnd('li');
  208. }
  209. return true;
  210. }
  211. /**
  212. * Modify the default menu to link to our custom action
  213. *
  214. * Using event handlers, it's possible to modify the default UI for pages
  215. * almost without limit. In this method, we add a menu item to the default
  216. * primary menu for the interface to link to our action.
  217. *
  218. * The Action class provides a rich set of events to hook, as well as output
  219. * methods.
  220. *
  221. * @param Action $action The current action handler. Use this to
  222. * do any output.
  223. *
  224. * @return boolean hook value; true means continue processing, false means stop.
  225. *
  226. * @see Action
  227. */
  228. function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped=null)
  229. {
  230. $menu->menuItem(common_local_url('bookmarks', array('nickname' => $target->getNickname())),
  231. // TRANS: Menu item in sample plugin.
  232. _m('Bookmarks'),
  233. // TRANS: Menu item title in sample plugin.
  234. _m('A list of your bookmarks'), false, 'nav_timeline_bookmarks');
  235. return true;
  236. }
  237. function types()
  238. {
  239. return array(ActivityObject::BOOKMARK);
  240. }
  241. /**
  242. * When a notice is deleted, delete the related Bookmark
  243. *
  244. * @param Notice $notice Notice being deleted
  245. *
  246. * @return boolean hook value
  247. */
  248. function deleteRelated(Notice $notice)
  249. {
  250. try {
  251. $nb = Bookmark::fromStored($notice);
  252. } catch (NoResultException $e) {
  253. throw new AlreadyFulfilledException('Bookmark already gone when deleting: '.$e->getMessage());
  254. }
  255. $nb->delete();
  256. return true;
  257. }
  258. /**
  259. * Save a bookmark from an activity
  260. *
  261. * @param Activity $activity Activity to save
  262. * @param Profile $actor Profile to use as author
  263. * @param array $options Options to pass to bookmark-saving code
  264. *
  265. * @return Notice resulting notice
  266. */
  267. protected function saveObjectFromActivity(Activity $activity, Notice $stored, array $options=array())
  268. {
  269. return Bookmark::saveActivityObject($activity->objects[0], $stored);
  270. }
  271. public function onEndNoticeAsActivity(Notice $stored, Activity $act, Profile $scoped=null)
  272. {
  273. if (!$this->isMyNotice($stored)) {
  274. return true;
  275. }
  276. $this->extendActivity($stored, $act, $scoped);
  277. return false;
  278. }
  279. public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null)
  280. {
  281. /*$hashtags = array();
  282. $taglinks = array();
  283. foreach ($tags as $tag) {
  284. $hashtags[] = '#'.$tag;
  285. $attrs = array('href' => Notice_tag::url($tag),
  286. 'rel' => $tag,
  287. 'class' => 'tag');
  288. $taglinks[] = XMLStringer::estring('a', $attrs, $tag);
  289. }*/
  290. }
  291. function activityObjectFromNotice(Notice $notice)
  292. {
  293. return Bookmark::fromStored($notice)->asActivityObject();
  294. }
  295. function entryForm($out)
  296. {
  297. return new InitialBookmarkForm($out);
  298. }
  299. function tag()
  300. {
  301. return 'bookmark';
  302. }
  303. function appTitle()
  304. {
  305. // TRANS: Application title.
  306. return _m('TITLE','Bookmark');
  307. }
  308. function onEndUpgrade()
  309. {
  310. // Version 0.9.x of the plugin didn't stamp notices
  311. // with verb and object-type (for obvious reasons). Update
  312. // those notices here.
  313. $notice = new Notice();
  314. $notice->whereAdd('exists (select uri from bookmark where bookmark.uri = notice.uri)');
  315. $notice->whereAdd('((object_type is null) or (object_type = "' .ActivityObject::NOTE.'"))');
  316. $notice->find();
  317. while ($notice->fetch()) {
  318. $original = clone($notice);
  319. $notice->verb = ActivityVerb::POST;
  320. $notice->object_type = ActivityObject::BOOKMARK;
  321. $notice->update($original);
  322. }
  323. }
  324. public function activityObjectOutputJson(ActivityObject $obj, array &$out)
  325. {
  326. assert($obj->type == ActivityObject::BOOKMARK);
  327. $bm = Bookmark::getByPK(array('uri' => $obj->id));
  328. $out['displayName'] = $bm->getTitle();
  329. $out['targetUrl'] = $bm->getUrl();
  330. return true;
  331. }
  332. protected function showNoticeContent(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
  333. {
  334. $nb = Bookmark::fromStored($stored);
  335. // Whether to nofollow
  336. $attrs = array('href' => $nb->getUrl(), 'class' => 'bookmark-title');
  337. $nf = common_config('nofollow', 'external');
  338. if ($nf == 'never' || ($nf == 'sometimes' and $out instanceof ShowstreamAction)) {
  339. $attrs['rel'] = 'external';
  340. } else {
  341. $attrs['rel'] = 'nofollow external';
  342. }
  343. $out->elementStart('h3');
  344. $out->element('a', $attrs, $nb->getTitle());
  345. $out->elementEnd('h3');
  346. // Replies look like "for:" tags
  347. $replies = $stored->getReplies();
  348. $tags = $stored->getTags();
  349. if (!empty($nb->description)) {
  350. $out->element('p',
  351. array('class' => 'bookmark-description'),
  352. $nb->description);
  353. }
  354. if (!empty($replies) || !empty($tags)) {
  355. $out->elementStart('ul', array('class' => 'bookmark-tags'));
  356. foreach ($replies as $reply) {
  357. $other = Profile::getByPK($reply);
  358. $out->elementStart('li');
  359. $out->element('a', array('rel' => 'tag',
  360. 'href' => $other->getUrl(),
  361. 'title' => $other->getBestName()),
  362. sprintf('for:%s', $other->getNickname()));
  363. $out->elementEnd('li');
  364. $out->text(' ');
  365. }
  366. foreach ($tags as $tag) {
  367. $tag = trim($tag);
  368. if (!empty($tag)) {
  369. $out->elementStart('li');
  370. $out->element('a',
  371. array('rel' => 'tag',
  372. 'href' => Notice_tag::url($tag)),
  373. $tag);
  374. $out->elementEnd('li');
  375. $out->text(' ');
  376. }
  377. }
  378. $out->elementEnd('ul');
  379. }
  380. }
  381. }