From f64103447188f71101ce90fdb2f76b1c6e5889a0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 18 Dec 2010 02:27:14 -0500 Subject: [PATCH 01/74] First pass at storing bookmarks Form for saving bookmarks that looks like the delicious.com form. Save a new notice with the right text, but attach a new notice_bookmark table which marks this as a bookmark. Tags, URLs are kept the same. --- plugins/Bookmark/BookmarkPlugin.php | 130 +++++++++++++++ plugins/Bookmark/Notice_bookmark.php | 114 +++++++++++++ plugins/Bookmark/bookmarkform.php | 151 +++++++++++++++++ plugins/Bookmark/newbookmark.php | 238 +++++++++++++++++++++++++++ 4 files changed, 633 insertions(+) create mode 100644 plugins/Bookmark/BookmarkPlugin.php create mode 100644 plugins/Bookmark/Notice_bookmark.php create mode 100644 plugins/Bookmark/bookmarkform.php create mode 100644 plugins/Bookmark/newbookmark.php diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php new file mode 100644 index 0000000000..cf014cc82a --- /dev/null +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -0,0 +1,130 @@ +. + * + * @category SocialBookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Bookmark plugin main class + * + * @category Bookmark + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class BookmarkPlugin extends Plugin +{ + /** + * Database schema setup + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onCheckSchema() + { + $schema = Schema::get(); + + // For storing user-submitted flags on profiles + + $schema->ensureTable('notice_bookmark', + array(new ColumnDef('notice_id', + 'integer', + null, + true, + 'PRI'))); + + return true; + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'NewbookmarkAction': + include_once $dir.'/newbookmark.php'; + return false; + case 'Notice_bookmark': + include_once $dir.'/'.$cls.'.php'; + return false; + case 'BookmarkForm': + include_once $dir.'/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + /** + * Map URLs to actions + * + * @param Net_URL_Mapper $m path-to-action mapper + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onRouterInitialized($m) + { + $m->connect('main/bookmark/new', + array('action' => 'newbookmark'), + array('id' => '[0-9]+')); + + return true; + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'Sample', + 'version' => STATUSNET_VERSION, + 'author' => 'Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:Bookmark', + 'rawdescription' => + _m('Simple extension for supporting bookmarks.')); + return true; + } +} + diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php new file mode 100644 index 0000000000..772fad528b --- /dev/null +++ b/plugins/Bookmark/Notice_bookmark.php @@ -0,0 +1,114 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * For storing the fact that a notice is a bookmark + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class Notice_bookmark extends Memcached_DataObject +{ + public $__table = 'notice_bookmark'; // table name + public $notice_id; // int(4) primary_key not_null + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param mixed $v Value to lookup + * + * @return User_greeting_count object found, or null for no hits + * + */ + + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('Notice_bookmark', $k, $v); + } + + /** + * return table definition for DB_DataObject + * + * DB_DataObject needs to know something about the table to manipulate + * instances. This method provides all the DB_DataObject needs to know. + * + * @return array array of column definitions + */ + + function table() + { + return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL); + } + + /** + * return key definitions for DB_DataObject + * + * @return array list of key field names + */ + + function keys() + { + return array_keys($this->keyTypes()); + } + + /** + * return key definitions for Memcached_DataObject + * + * @return array associative array of key definitions + */ + + function keyTypes() + { + return array('notice_id' => 'K'); + } + + /** + * Magic formula for non-autoincrementing integer primary keys + * + * @return array magic three-false array that stops auto-incrementing. + */ + + function sequenceKey() + { + return array(false, false, false); + } +} diff --git a/plugins/Bookmark/bookmarkform.php b/plugins/Bookmark/bookmarkform.php new file mode 100644 index 0000000000..deeed84829 --- /dev/null +++ b/plugins/Bookmark/bookmarkform.php @@ -0,0 +1,151 @@ +. + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Form to add a new bookmark + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class BookmarkForm extends Form +{ + private $_title = null; + private $_url = null; + private $_tags = null; + private $_description = null; + + function __construct($out=null, $title=null, $url=null, $tags=null, $description=null) + { + parent::__construct($out); + + $this->_title = $title; + $this->_url = $url; + $this->_tags = $tags; + $this->_description = $description; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'form_new_bookmark'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_new_bookmark'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('newbookmark'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('fieldset', array('id' => 'new_bookmark_data')); + $this->out->elementStart('ul', 'form_data'); + + $this->li(); + $this->out->input('title', + _('Title'), + $this->_title, + _('Title of the bookmark')); + $this->unli(); + + $this->li(); + $this->out->input('url', + _('URL'), + $this->_url, + _('URL to bookmark')); + $this->unli(); + + $this->li(); + $this->out->input('tags', + _('Tags'), + $this->_tags, + _('Comma- or space-separated list of tags')); + $this->unli(); + + $this->li(); + $this->out->input('description', + _('Description'), + $this->_description, + _('Description of the URL')); + $this->unli(); + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _m('BUTTON', 'Save')); + } +} diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php new file mode 100644 index 0000000000..034e73b429 --- /dev/null +++ b/plugins/Bookmark/newbookmark.php @@ -0,0 +1,238 @@ +. + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Add a new bookmark + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class NewbookmarkAction extends Action +{ + private $_user = null; + private $_error = null; + private $_complete = null; + private $_title = null; + private $_url = null; + private $_tags = null; + private $_description = null; + + function title() + { + return _('New bookmark'); + } + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + + $this->_user = common_current_user(); + + if (empty($this->_user)) { + throw new ClientException(_("Must be logged in to post a bookmark."), 403); + } + + if ($this->isPost()) { + $this->checkSessionToken(); + } + + $this->_title = $this->trimmed('title'); + $this->_url = $this->trimmed('url'); + $this->_tags = $this->trimmed('tags'); + $this->_description = $this->trimmed('description'); + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + + function handle($argarray=null) + { + parent::handle($argarray); + + if ($this->isPost()) { + $this->newBookmark(); + } else { + $this->showPage(); + } + + return; + } + + /** + * Add a new bookmark + * + * @return void + */ + + function newBookmark() + { + try { + if (empty($this->_title)) { + throw new ClientException(_('Bookmark must have a title.')); + } + + if (empty($this->_url)) { + throw new ClientException(_('Bookmark must have an URL.')); + } + + // XXX: filter "for:nickname" tags + + $tags = array_map('common_canonical_tag', + preg_split('/[\s,]+/', $this->_tags)); + + $hashtags = array(); + $taglinks = array(); + + foreach ($tags as $tag) { + $hashtags[] = '#'.$tag; + if (common_config('singleuser', 'enabled')) { + // regular TagAction isn't set up in 1user mode + $nickname = User::singleUserNickname(); + $url = common_local_url('showstream', + array('nickname' => $nickname, + 'tag' => $tag)); + } else { + $url = common_local_url('tag', array('tag' => $tag)); + } + $attrs = array('href' => $url, + 'rel' => $tag, + 'class' => 'tag'); + $taglinks[] = XMLStringer::estring('a', $attrs, $tag); + } + + $content = sprintf(_('"%s" %s %s %s'), + $this->_title, + File_redirection::makeShort($this->_url, $this->_user), + $this->_description, + implode(' ', $hashtags)); + + $rendered = sprintf(_(''. + '%s '. + '%s '. + '%s'. + ''), + htmlspecialchars($this->_url), + htmlspecialchars($this->_title), + htmlspecialchars($this->_description), + implode(' ', $taglinks)); + + $options = array('urls' => array($this->_url), + 'rendered' => $rendered, + 'tags' => $tags); + + $saved = Notice::saveNew($this->_user->id, + $content, + 'web', + $options); + + if (!empty($saved)) { + $nb = new Notice_bookmark(); + $nb->notice_id = $saved->id; + $nb->insert(); + } + + } catch (ClientException $ce) { + $this->_error = $ce->getMessage(); + $this->showPage(); + return; + } + + common_redirect($saved->bestUrl(), 303); + } + + /** + * Show the bookmark form + * + * @return void + */ + + function showContent() + { + if (!empty($this->_error)) { + $this->element('p', 'error', $this->_error); + } + + $form = new BookmarkForm($this, + $this->_title, + $this->_url, + $this->_tags, + $this->_description); + + $form->show(); + + return; + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return true; + } else { + return false; + } + } +} From 85d54cbdb7be522a1a6afef6d29a3370ac37b17d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 18 Dec 2010 02:36:13 -0500 Subject: [PATCH 02/74] save title and description of bookmark --- plugins/Bookmark/BookmarkPlugin.php | 7 ++++++- plugins/Bookmark/newbookmark.php | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index cf014cc82a..a3067a9eb1 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -66,7 +66,12 @@ class BookmarkPlugin extends Plugin 'integer', null, true, - 'PRI'))); + 'PRI'), + new ColumnDef('title', + 'varchar', + 255), + new ColumnDef('description', + 'text'))); return true; } diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index 034e73b429..7352b1b952 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -180,7 +180,9 @@ class NewbookmarkAction extends Action if (!empty($saved)) { $nb = new Notice_bookmark(); - $nb->notice_id = $saved->id; + $nb->notice_id = $saved->id; + $nb->title = $this->_title; + $nb->description = $this->_description; $nb->insert(); } From 563f0675086dcbe3f45c5adedf3a4b9d811ca6ff Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 18 Dec 2010 02:39:24 -0500 Subject: [PATCH 03/74] save title and description of bookmark --- plugins/Bookmark/Notice_bookmark.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php index 772fad528b..6a49421fb5 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Notice_bookmark.php @@ -47,6 +47,8 @@ class Notice_bookmark extends Memcached_DataObject { public $__table = 'notice_bookmark'; // table name public $notice_id; // int(4) primary_key not_null + public $title; // varchar(255) + public $description; // text /** * Get an instance by key @@ -76,7 +78,9 @@ class Notice_bookmark extends Memcached_DataObject function table() { - return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL); + return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'title' => DB_DATAOBJECT_STR, + 'description' => DB_DATAOBJECT_STR); } /** From 6b7931bcc86accf00c8ff2b2ea7657d27b290f10 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 18 Dec 2010 02:39:44 -0500 Subject: [PATCH 04/74] delete bookmark stuff when deleting notice --- plugins/Bookmark/BookmarkPlugin.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index a3067a9eb1..2137d0c225 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -76,6 +76,17 @@ class BookmarkPlugin extends Plugin return true; } + function onNoticeDeleteRelated($notice) + { + $nb = Notice_bookmark::staticGet('notice_id', $notice->id); + + if (!empty($nb)) { + $nb->delete(); + } + + return true; + } + /** * Load related modules when needed * From fce2078dfb4a503287cad0ec7e199524b65ed06f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 18 Dec 2010 17:21:40 -0500 Subject: [PATCH 05/74] code done on debugging in bookmarks --- plugins/Bookmark/BookmarkPlugin.php | 52 +++++++++++++++++++++++++++++ plugins/Bookmark/newbookmark.php | 16 +++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 2137d0c225..e50e45cade 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -87,6 +87,12 @@ class BookmarkPlugin extends Plugin return true; } + function onEndShowStyles($action) + { + $action->style('.bookmark_tags li { display: inline; }'); + return true; + } + /** * Load related modules when needed * @@ -132,6 +138,52 @@ class BookmarkPlugin extends Plugin return true; } + function onStartShowNoticeItem($nli) + { + $nb = Notice_bookmark::staticGet('notice_id', + $nli->notice->id); + + if (!empty($nb)) { + $att = $nli->notice->attachments(); + $nli->out->elementStart('h3'); + $nli->out->element('a', + array('href' => $att[0]->url), + $nb->title); + $nli->out->elementEnd('h3'); + $nli->out->element('p', + array('class' => 'bookmark_description'), + $nb->description); + $nli->out->elementStart('p'); + $nli->out->element('a', array('href' => $nli->profile->profileurl, + 'class' => 'bookmark_author', + 'title' => $nli->profile->getBestName()), + $nli->profile->getBestName()); + $nli->out->elementEnd('p'); + $tags = $nli->notice->getTags(); + $nli->out->elementStart('ul', array('class' => 'bookmark_tags')); + foreach ($tags as $tag) { + if (common_config('singleuser', 'enabled')) { + // regular TagAction isn't set up in 1user mode + $nickname = User::singleUserNickname(); + $url = common_local_url('showstream', + array('nickname' => $nickname, + 'tag' => $tag)); + } else { + $url = common_local_url('tag', array('tag' => $tag)); + } + $nli->out->elementStart('li'); + $nli->out->element('a', array('rel' => 'tag', + 'href' => $url), + $tag); + $nli->out->elementEnd('li'); + $nli->out->text(' '); + } + $nli->out->elementEnd('ul'); + return false; + } + return true; + } + function onPluginVersion(&$versions) { $versions[] = array('name' => 'Sample', diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index 7352b1b952..466785de8d 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -128,15 +128,25 @@ class NewbookmarkAction extends Action throw new ClientException(_('Bookmark must have an URL.')); } - // XXX: filter "for:nickname" tags + $rawtags = preg_split('/[\s,]+/', $this->_tags); - $tags = array_map('common_canonical_tag', - preg_split('/[\s,]+/', $this->_tags)); + $tags = array(); + + // filter "for:nickname" tags + + foreach ($rawtags as $tag) { + if (0 == mb_stricmp($tag, 'for:', 4)) { + + } else { + $tags[] = common_canonical_tag($tag); + } + } $hashtags = array(); $taglinks = array(); foreach ($tags as $tag) { + $hashtags[] = '#'.$tag; if (common_config('singleuser', 'enabled')) { // regular TagAction isn't set up in 1user mode From 9480bf1d10d84dc68951df19d35f2b865892452e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 19 Dec 2010 10:15:56 -0500 Subject: [PATCH 06/74] Notice_tag::url() gets the URL for a tag string --- classes/Notice_tag.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index bb67c8f819..f795bfc601 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -87,4 +87,19 @@ class Notice_tag extends Memcached_DataObject { return Memcached_DataObject::pkeyGet('Notice_tag', $kv); } + + static function url($tag) + { + if (common_config('singleuser', 'enabled')) { + // regular TagAction isn't set up in 1user mode + $nickname = User::singleUserNickname(); + $url = common_local_url('showstream', + array('nickname' => $nickname, + 'tag' => $tag)); + } else { + $url = common_local_url('tag', array('tag' => $tag)); + } + + return $url; + } } From 688841fb417b67c6749ffab06d69db30f5ceb72e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 19 Dec 2010 10:16:44 -0500 Subject: [PATCH 07/74] ActivityObject has attribute for other elements --- lib/activityobject.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/activityobject.php b/lib/activityobject.php index 536e021334..32d9a9aa4f 100644 --- a/lib/activityobject.php +++ b/lib/activityobject.php @@ -106,6 +106,8 @@ class ActivityObject public $largerImage; public $description; + public $extra; // For extra stuff + /** * Constructor * @@ -565,6 +567,11 @@ class ActivityObject $xs->raw($this->poco->asString()); } + foreach ($this->extra as $el) { + list($tag, $attrs, $content) = $el; + $xs->element($tag, $attrs, $content); + } + $xs->elementEnd($tag); return $xs->getString(); From cee93dd15d4b922f98529ccfe1eba450e6647be2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 19 Dec 2010 10:17:23 -0500 Subject: [PATCH 08/74] Move notice bookmark creation to Notice_bookmark::saveNew() --- plugins/Bookmark/Notice_bookmark.php | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php index 6a49421fb5..bff5df57d0 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Notice_bookmark.php @@ -115,4 +115,76 @@ class Notice_bookmark extends Memcached_DataObject { return array(false, false, false); } + + static function saveNew($user, $title, $url, $rawtags, $description) + { + if (is_string($rawtags)) { + $rawtags = preg_split('/[\s,]+/', $rawtags); + } + + $tags = array(); + $replies = array(); + + // filter "for:nickname" tags + + foreach ($rawtags as $tag) { + if (strtolower(mb_substr($tag, 0, 4)) == 'for:') { + $nickname = mb_substr($tag, 4); + $other = common_relative_profile($user->getProfile(), + $nickname); + if (!empty($other)) { + $replies[] = $other->getUri(); + } + } else { + $tags[] = common_canonical_tag($tag); + } + } + + $hashtags = array(); + $taglinks = array(); + + foreach ($tags as $tag) { + $hashtags[] = '#'.$tag; + $attrs = array('href' => Notice_tag::url($tag), + 'rel' => $tag, + 'class' => 'tag'); + $taglinks[] = XMLStringer::estring('a', $attrs, $tag); + } + + $content = sprintf(_('"%s" %s %s %s'), + $title, + File_redirection::makeShort($url, $user), + $description, + implode(' ', $hashtags)); + + $rendered = sprintf(_(''. + '%s '. + '%s '. + '%s'. + ''), + htmlspecialchars($url), + htmlspecialchars($title), + htmlspecialchars($description), + implode(' ', $taglinks)); + + $options = array('urls' => array($url), + 'rendered' => $rendered, + 'tags' => $tags, + 'replies' => $replies); + + $saved = Notice::saveNew($user->id, + $content, + 'web', + $options); + + if (!empty($saved)) { + $nb = new Notice_bookmark(); + $nb->notice_id = $saved->id; + $nb->title = $title; + $nb->description = $description; + $nb->insert(); + } + + return $saved; + } } From d6030714f3bbdfa63053040ca86655f4b4a6c83f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 19 Dec 2010 10:18:07 -0500 Subject: [PATCH 09/74] Use Notice_bookmark::saveNew() from NewbookmarkAction --- plugins/Bookmark/newbookmark.php | 71 +++----------------------------- 1 file changed, 5 insertions(+), 66 deletions(-) diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index 466785de8d..079babccd4 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -128,73 +128,12 @@ class NewbookmarkAction extends Action throw new ClientException(_('Bookmark must have an URL.')); } - $rawtags = preg_split('/[\s,]+/', $this->_tags); - $tags = array(); - - // filter "for:nickname" tags - - foreach ($rawtags as $tag) { - if (0 == mb_stricmp($tag, 'for:', 4)) { - - } else { - $tags[] = common_canonical_tag($tag); - } - } - - $hashtags = array(); - $taglinks = array(); - - foreach ($tags as $tag) { - - $hashtags[] = '#'.$tag; - if (common_config('singleuser', 'enabled')) { - // regular TagAction isn't set up in 1user mode - $nickname = User::singleUserNickname(); - $url = common_local_url('showstream', - array('nickname' => $nickname, - 'tag' => $tag)); - } else { - $url = common_local_url('tag', array('tag' => $tag)); - } - $attrs = array('href' => $url, - 'rel' => $tag, - 'class' => 'tag'); - $taglinks[] = XMLStringer::estring('a', $attrs, $tag); - } - - $content = sprintf(_('"%s" %s %s %s'), - $this->_title, - File_redirection::makeShort($this->_url, $this->_user), - $this->_description, - implode(' ', $hashtags)); - - $rendered = sprintf(_(''. - '%s '. - '%s '. - '%s'. - ''), - htmlspecialchars($this->_url), - htmlspecialchars($this->_title), - htmlspecialchars($this->_description), - implode(' ', $taglinks)); - - $options = array('urls' => array($this->_url), - 'rendered' => $rendered, - 'tags' => $tags); - - $saved = Notice::saveNew($this->_user->id, - $content, - 'web', - $options); - - if (!empty($saved)) { - $nb = new Notice_bookmark(); - $nb->notice_id = $saved->id; - $nb->title = $this->_title; - $nb->description = $this->_description; - $nb->insert(); - } + $saved = Notice_bookmark::saveNew($this->_user, + $this->_title, + $this->_url, + $this->_tags, + $this->_description); } catch (ClientException $ce) { $this->_error = $ce->getMessage(); From cb76465cfabbe508928098720dc08816dac7ea29 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 19 Dec 2010 10:18:33 -0500 Subject: [PATCH 10/74] Better output for activities and HTML in BookmarkPlugin --- plugins/Bookmark/BookmarkPlugin.php | 88 +++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index e50e45cade..3f36770391 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -65,7 +65,7 @@ class BookmarkPlugin extends Plugin array(new ColumnDef('notice_id', 'integer', null, - true, + false, 'PRI'), new ColumnDef('title', 'varchar', @@ -90,6 +90,7 @@ class BookmarkPlugin extends Plugin function onEndShowStyles($action) { $action->style('.bookmark_tags li { display: inline; }'); + $action->style('.bookmark_mentions li { display: inline; }'); return true; } @@ -162,28 +163,91 @@ class BookmarkPlugin extends Plugin $tags = $nli->notice->getTags(); $nli->out->elementStart('ul', array('class' => 'bookmark_tags')); foreach ($tags as $tag) { - if (common_config('singleuser', 'enabled')) { - // regular TagAction isn't set up in 1user mode - $nickname = User::singleUserNickname(); - $url = common_local_url('showstream', - array('nickname' => $nickname, - 'tag' => $tag)); - } else { - $url = common_local_url('tag', array('tag' => $tag)); - } $nli->out->elementStart('li'); - $nli->out->element('a', array('rel' => 'tag', - 'href' => $url), + $nli->out->element('a', + array('rel' => 'tag', + 'href' => Notice_tag::url($tag)), $tag); $nli->out->elementEnd('li'); $nli->out->text(' '); } $nli->out->elementEnd('ul'); + $replies = $nli->notice->getReplies(); + if (!empty($replies)) { + $nli->out->elementStart('ul', array('class' => 'bookmark_mentions')); + foreach ($replies as $reply) { + $other = Profile::staticGet('id', $reply); + $nli->out->elementStart('li'); + $nli->out->element('a', array('rel' => 'tag', + 'href' => $other->profileurl, + 'title' => $other->getBestName()), + sprintf('for:%s', $other->nickname)); + $nli->out->elementEnd('li'); + $nli->out->text(' '); + } + $nli->out->elementEnd('ul'); + } return false; } return true; } + function onStartActivityObjectFromNotice($notice, &$object) + { + $nb = Notice_bookmark::staticGet('notice_id', + $notice->id); + + if (!empty($nb)) { + + $object->id = $notice->uri; + $object->type = ActivityObject::BOOKMARK; + $object->title = $nb->title; + $object->summary = $nb->summary; + + // Attributes of the URL + + $attachments = $notice->attachments(); + + if (count($attachments) != 1) { + throw new ServerException(_('Bookmark notice with the wrong number of attachments.')); + } + + $target = $attachments[0]; + + $attrs = array('rel' => 'related', + 'href' => $target->url); + + if (!empty($target->title)) { + $attrs['title'] = $target->title; + } + + $object->extra[] = array('link', $attrs); + + // Attributes of the thumbnail, if any + + $thumbnail = $target->getThumbnail(); + + if (!empty($thumbnail)) { + $tattrs = array('rel' => 'preview', + 'href' => $thumbnail->url); + + if (!empty($thumbnail->width)) { + $tattrs['media:width'] = $thumbnail->width; + } + + if (!empty($thumbnail->height)) { + $tattrs['media:height'] = $thumbnail->height; + } + + $object->extra[] = array('link', $attrs); + } + + return false; + } + + return true; + } + function onPluginVersion(&$versions) { $versions[] = array('name' => 'Sample', From cab75224761b675e70b4ebd1fc31601270d91177 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 12:03:33 -0500 Subject: [PATCH 11/74] Notice_bookmark::saveNew() takes options arg --- plugins/Bookmark/Notice_bookmark.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php index bff5df57d0..679064dbe9 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Notice_bookmark.php @@ -116,8 +116,12 @@ class Notice_bookmark extends Memcached_DataObject return array(false, false, false); } - static function saveNew($user, $title, $url, $rawtags, $description) + static function saveNew($user, $title, $url, $rawtags, $description, $options=null) { + if (empty($options)) { + $options = array(); + } + if (is_string($rawtags)) { $rawtags = preg_split('/[\s,]+/', $rawtags); } @@ -167,10 +171,10 @@ class Notice_bookmark extends Memcached_DataObject htmlspecialchars($description), implode(' ', $taglinks)); - $options = array('urls' => array($url), - 'rendered' => $rendered, - 'tags' => $tags, - 'replies' => $replies); + $options = array_merge($options, array('urls' => array($url), + 'rendered' => $rendered, + 'tags' => $tags, + 'replies' => $replies)); $saved = Notice::saveNew($user->id, $content, From 510e79a96ceecc27594164d68d79396f62aa5eac Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 12:04:02 -0500 Subject: [PATCH 12/74] Starting point for adding bookmarks --- plugins/Bookmark/BookmarkPlugin.php | 1 + plugins/Bookmark/deliciousbackupimporter.php | 117 +++++++++++++++++++ plugins/Bookmark/importbookmarks.php | 79 +++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 plugins/Bookmark/deliciousbackupimporter.php create mode 100644 plugins/Bookmark/importbookmarks.php diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 3f36770391..14b1f1d2a5 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -115,6 +115,7 @@ class BookmarkPlugin extends Plugin include_once $dir.'/'.$cls.'.php'; return false; case 'BookmarkForm': + case 'DeliciousBackupImporter': include_once $dir.'/'.strtolower($cls).'.php'; return false; default: diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php new file mode 100644 index 0000000000..af34f6066b --- /dev/null +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -0,0 +1,117 @@ +importHTML($body); + + $dls = $doc->getElementsByTagName('dl'); + + if ($dls->length != 1) { + throw new ClientException(_("Bad import file.")); + } + + $dl = $dls->item(0); + + $children = $dl->childNodes; + + common_debug("
child nodes is " . $children->length); + + $dt = null; + + for ($i = 0; $i < $children->length; $i++) { + try { + $child = $children->item($i); + if ($child->nodeType != XML_ELEMENT_NODE) { + continue; + } + common_log(LOG_INFO, $child->tagName); + switch (strtolower($child->tagName)) { + case 'dt': + if (!empty($dt)) { + // No DD provided + $this->importBookmark($user, $dt); + $dt = null; + } + $dt = $child; + break; + case 'dd': + $dd = $child; + $saved = $this->importBookmark($user, $dt, $dd); + $dt = null; + case 'p': + common_log(LOG_INFO, 'Skipping the

in the

.'); + break; + default: + common_log(LOG_WARNING, "Unexpected element $child->tagName found in import."); + } + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + } + } + } + + function importBookmark($user, $dt, $dd = null) + { + common_debug("DT child nodes length = " . $dt->childNodes->length); + + for ($i = 0; $i < $dt->childNodes->length; $i++) { + $child = $dt->childNodes->item($i); + if ($child->nodeType == XML_ELEMENT_NODE) { + common_debug('DT has an element child with tag name '. $child->tagName); + } + } + + $as = $dt->getElementsByTagName('a'); + + if ($as->length == 0) { + throw new ClientException(_("No tag in a
.")); + } + + $a = $as->item(0); + + $private = $a->getAttribute('private'); + + if ($private != 0) { + throw new ClientException(_('Skipping private bookmark.')); + } + + if (!empty($dd)) { + $description = $dd->nodeValue; + } else { + $description = null; + } + + $title = $a->getAttribute('title'); + $url = $a->getAttribute('href'); + $tags = $a->getAttribute('tags'); + $addDate = $a->getAttribute('add_date'); + $created = common_sql_date(intval($addDate)); + + $saved = Notice_bookmark::saveNew($user, + $title, + $url, + $tags, + $description, + array('created' => $created)); + + return $saved; + } + + function importHTML($body) + { + // DOMDocument::loadHTML may throw warnings on unrecognized elements, + // and notices on unrecognized namespaces. + $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE)); + $dom = new DOMDocument(); + $ok = $dom->loadHTML($body); + error_reporting($old); + + if ($ok) { + return $dom; + } else { + return null; + } + } +} diff --git a/plugins/Bookmark/importbookmarks.php b/plugins/Bookmark/importbookmarks.php new file mode 100644 index 0000000000..3074be39f9 --- /dev/null +++ b/plugins/Bookmark/importbookmarks.php @@ -0,0 +1,79 @@ +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +$shortoptions = 'i:n:f:'; +$longoptions = array('id=', 'nickname=', 'file='); + +$helptext = <<importBookmarks($user, $html); + +} catch (Exception $e) { + print $e->getMessage()."\n"; + exit(1); +} From 6a6dd81d1f905768df0be1c6a812fc64f38d7c4e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 13:26:57 -0500 Subject: [PATCH 13/74] Let activity objects write directly to activity's own outputter --- lib/activity.php | 33 +++---------------------- lib/activityobject.php | 55 +++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/lib/activity.php b/lib/activity.php index c3a984a7b9..3458c76b9a 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -349,32 +349,7 @@ class Activity if ($this->verb == ActivityVerb::POST && count($this->objects) == 1) { $obj = $this->objects[0]; - - $xs->element('id', null, $obj->id); - $xs->element('activity:object-type', null, $obj->type); - - if (!empty($obj->title)) { - $xs->element('title', null, $obj->title); - } else { - // XXX need a better default title - $xs->element('title', null, _('Post')); - } - - if (!empty($obj->content)) { - $xs->element('content', array('type' => 'html'), $obj->content); - } - - if (!empty($obj->summary)) { - $xs->element('summary', null, $obj->summary); - } - - if (!empty($obj->link)) { - $xs->element('link', array('rel' => 'alternate', - 'type' => 'text/html'), - $obj->link); - } - - // XXX: some object types might have other values here. + $obj->outputTo($xs, null); } else { $xs->element('id', null, $this->id); @@ -408,12 +383,12 @@ class Activity $xs->element('name', array(), $this->actor->title); } $xs->elementEnd('author'); - $xs->raw($this->actor->asString('activity:actor')); + $this->actor->outputTo($xs, 'activity:actor'); } if ($this->verb != ActivityVerb::POST || count($this->objects) != 1) { foreach($this->objects as $object) { - $xs->raw($object->asString()); + $object->outputTo($xs, 'activity:object'); } } @@ -467,7 +442,7 @@ class Activity } if ($this->target) { - $xs->raw($this->target->asString('activity:target')); + $this->target->outputTo($xs, 'activity:target'); } foreach ($this->categories as $cat) { diff --git a/lib/activityobject.php b/lib/activityobject.php index 32d9a9aa4f..ebe686a077 100644 --- a/lib/activityobject.php +++ b/lib/activityobject.php @@ -417,10 +417,10 @@ class ActivityObject } $sizes = array( - AVATAR_PROFILE_SIZE, - AVATAR_STREAM_SIZE, - AVATAR_MINI_SIZE - ); + AVATAR_PROFILE_SIZE, + AVATAR_STREAM_SIZE, + AVATAR_MINI_SIZE + ); foreach ($sizes as $size) { $alink = null; @@ -490,19 +490,19 @@ class ActivityObject return $object; } + + function outputTo($xo, $tag='activity:object') + { + if (!empty($tag)) { + $xo->elementStart($tag); + } - function asString($tag='activity:object') - { - $xs = new XMLStringer(true); + $xo->element('activity:object-type', null, $this->type); - $xs->elementStart($tag); - - $xs->element('activity:object-type', null, $this->type); - - $xs->element(self::ID, null, $this->id); + $xo->element(self::ID, null, $this->id); if (!empty($this->title)) { - $xs->element( + $xo->element( self::TITLE, null, common_xml_safe_str($this->title) @@ -510,7 +510,7 @@ class ActivityObject } if (!empty($this->summary)) { - $xs->element( + $xo->element( self::SUMMARY, null, common_xml_safe_str($this->summary) @@ -519,7 +519,7 @@ class ActivityObject if (!empty($this->content)) { // XXX: assuming HTML content here - $xs->element( + $xo->element( ActivityUtils::CONTENT, array('type' => 'html'), common_xml_safe_str($this->content) @@ -527,7 +527,7 @@ class ActivityObject } if (!empty($this->link)) { - $xs->element( + $xo->element( 'link', array( 'rel' => 'alternate', @@ -542,7 +542,7 @@ class ActivityObject || $this->type == ActivityObject::GROUP) { foreach ($this->avatarLinks as $avatar) { - $xs->element( + $xo->element( 'link', array( 'rel' => 'avatar', 'type' => $avatar->type, @@ -556,7 +556,7 @@ class ActivityObject } if (!empty($this->geopoint)) { - $xs->element( + $xo->element( 'georss:point', null, $this->geopoint @@ -564,15 +564,26 @@ class ActivityObject } if (!empty($this->poco)) { - $xs->raw($this->poco->asString()); + $xo->raw($this->poco->asString()); } foreach ($this->extra as $el) { - list($tag, $attrs, $content) = $el; - $xs->element($tag, $attrs, $content); + list($extraTag, $attrs, $content) = $el; + $xo->element($extraTag, $attrs, $content); } - $xs->elementEnd($tag); + if (!empty($tag)) { + $xo->elementEnd($tag); + } + + return; + } + + function asString($tag='activity:object') + { + $xs = new XMLStringer(true); + + $this->outputTo($xs, $tag); return $xs->getString(); } From d8de285d4eca3bccbefddce4b0ce784aac232189 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 13:35:21 -0500 Subject: [PATCH 14/74] reindent importbookmarks.php --- plugins/Bookmark/importbookmarks.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/Bookmark/importbookmarks.php b/plugins/Bookmark/importbookmarks.php index 3074be39f9..38a53d800d 100644 --- a/plugins/Bookmark/importbookmarks.php +++ b/plugins/Bookmark/importbookmarks.php @@ -23,13 +23,12 @@ $shortoptions = 'i:n:f:'; $longoptions = array('id=', 'nickname=', 'file='); $helptext = << Date: Mon, 20 Dec 2010 13:35:30 -0500 Subject: [PATCH 15/74] reindent BookmarkPlugin --- plugins/Bookmark/BookmarkPlugin.php | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 14b1f1d2a5..50c28363c6 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -107,20 +107,20 @@ class BookmarkPlugin extends Plugin $dir = dirname(__FILE__); switch ($cls) - { - case 'NewbookmarkAction': - include_once $dir.'/newbookmark.php'; + { + case 'NewbookmarkAction': + include_once $dir.'/newbookmark.php'; + return false; + case 'Notice_bookmark': + include_once $dir.'/'.$cls.'.php'; + return false; + case 'BookmarkForm': + case 'DeliciousBackupImporter': + include_once $dir.'/'.strtolower($cls).'.php'; return false; - case 'Notice_bookmark': - include_once $dir.'/'.$cls.'.php'; - return false; - case 'BookmarkForm': - case 'DeliciousBackupImporter': - include_once $dir.'/'.strtolower($cls).'.php'; - return false; - default: - return true; - } + default: + return true; + } } /** @@ -203,7 +203,8 @@ class BookmarkPlugin extends Plugin $object->id = $notice->uri; $object->type = ActivityObject::BOOKMARK; $object->title = $nb->title; - $object->summary = $nb->summary; + $object->summary = $nb->description; + $object->link = $notice->bestUrl(); // Attributes of the URL From 770efece839493178e8c3b2446452c0ffc517ced Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 13:38:24 -0500 Subject: [PATCH 16/74] don't reinsert existing bookmark --- plugins/Bookmark/Notice_bookmark.php | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php index 679064dbe9..e816c45596 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Notice_bookmark.php @@ -116,8 +116,37 @@ class Notice_bookmark extends Memcached_DataObject return array(false, false, false); } + static function getByURL($user, $url) + { + $file = File::staticGet('url', $url); + if (!empty($file)) { + $f2p = new File_to_post(); + $f2p->file_id = $file->id; + if ($f2p->find()) { + while ($f2p->fetch()) { + $n = Notice::staticGet('id', $f2p->post_id); + if (!empty($n)) { + if ($n->profile_id == $user->id) { + $nb = Notice_bookmark::staticGet('notice_id', $n->id); + if (!empty($nb)) { + return $nb; + } + } + } + } + } + } + return null; + } + static function saveNew($user, $title, $url, $rawtags, $description, $options=null) { + $nb = self::getByURL($user, $url); + + if (!empty($nb)) { + throw new ClientException(_('Bookmark already exists.')); + } + if (empty($options)) { $options = array(); } From 704a20f58b828bbbcfb66236e60cbe0abd49b52a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 13:39:07 -0500 Subject: [PATCH 17/74] some corrections for double-posting of bookmarks --- plugins/Bookmark/deliciousbackupimporter.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php index af34f6066b..b9dc7d8879 100644 --- a/plugins/Bookmark/deliciousbackupimporter.php +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -16,8 +16,6 @@ class DeliciousBackupImporter $children = $dl->childNodes; - common_debug("
child nodes is " . $children->length); - $dt = null; for ($i = 0; $i < $children->length; $i++) { @@ -40,6 +38,7 @@ class DeliciousBackupImporter $dd = $child; $saved = $this->importBookmark($user, $dt, $dd); $dt = null; + $dd = null; case 'p': common_log(LOG_INFO, 'Skipping the

in the

.'); break; @@ -48,18 +47,25 @@ class DeliciousBackupImporter } } catch (Exception $e) { common_log(LOG_ERR, $e->getMessage()); + $dt = $dd = null; } } } function importBookmark($user, $dt, $dd = null) { - common_debug("DT child nodes length = " . $dt->childNodes->length); + // We have to go squirrelling around in the child nodes + // on the off chance that we've received another
+ // as a child. for ($i = 0; $i < $dt->childNodes->length; $i++) { $child = $dt->childNodes->item($i); if ($child->nodeType == XML_ELEMENT_NODE) { - common_debug('DT has an element child with tag name '. $child->tagName); + if ($child->tagName == 'dt' && !is_null($dd)) { + $this->importBookmark($user, $dt); + $this->importBookmark($user, $child, $dd); + return; + } } } @@ -83,7 +89,7 @@ class DeliciousBackupImporter $description = null; } - $title = $a->getAttribute('title'); + $title = $a->nodeValue; $url = $a->getAttribute('href'); $tags = $a->getAttribute('tags'); $addDate = $a->getAttribute('add_date'); From f63355451d9a3618bc21e621a1b019eefc6fc48a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 18:28:21 -0500 Subject: [PATCH 18/74] fixup exception constructor for php 5.2 --- lib/oembedhelper.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/oembedhelper.php b/lib/oembedhelper.php index 84cf105867..4b3559ffd8 100644 --- a/lib/oembedhelper.php +++ b/lib/oembedhelper.php @@ -299,6 +299,10 @@ class oEmbedHelper class oEmbedHelper_Exception extends Exception { + public function __construct($message = "", $code = 0, $previous = null) + { + parent::__construct($message, $code, $previous); + } } class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception From 17515aacac8e681042f6f546f32a0d611cdf2ae2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 20 Dec 2010 18:32:43 -0500 Subject: [PATCH 19/74] drop previous in oembedhelperexception code --- lib/oembedhelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/oembedhelper.php b/lib/oembedhelper.php index 4b3559ffd8..3cd20c8e8e 100644 --- a/lib/oembedhelper.php +++ b/lib/oembedhelper.php @@ -301,7 +301,7 @@ class oEmbedHelper_Exception extends Exception { public function __construct($message = "", $code = 0, $previous = null) { - parent::__construct($message, $code, $previous); + parent::__construct($message, $code); } } From 331639d6e45f5aa59c64a73f4d48ce516398d0b5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 09:42:44 -0500 Subject: [PATCH 20/74] Code standards for deliciousbackupimporter.php --- plugins/Bookmark/deliciousbackupimporter.php | 283 ++++++++++++------- 1 file changed, 188 insertions(+), 95 deletions(-) diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php index b9dc7d8879..6ab87b5212 100644 --- a/plugins/Bookmark/deliciousbackupimporter.php +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -1,123 +1,216 @@ . + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Importer class for Delicious bookmarks + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ class DeliciousBackupImporter { - function importBookmarks($user, $body) - { - $doc = $this->importHTML($body); + /** + * Import an in-memory bookmark list to a user's account + * + * Take a delicious.com backup file (same as Netscape bookmarks.html) + * and import to StatusNet as Bookmark activities. + * + * The document format is terrible. It consists of a
with + * a bunch of
's, occasionally with
's. + * There are sometimes

's lost inside. + * + * @param User $user User whose feed we're going to fill + * @param string $body Body of the file + * + * @return void + */ - $dls = $doc->getElementsByTagName('dl'); + function importBookmarks($user, $body) + { + $doc = $this->importHTML($body); - if ($dls->length != 1) { - throw new ClientException(_("Bad import file.")); - } + $dls = $doc->getElementsByTagName('dl'); - $dl = $dls->item(0); + if ($dls->length != 1) { + throw new ClientException(_("Bad import file.")); + } - $children = $dl->childNodes; + $dl = $dls->item(0); - $dt = null; + $children = $dl->childNodes; - for ($i = 0; $i < $children->length; $i++) { - try { - $child = $children->item($i); - if ($child->nodeType != XML_ELEMENT_NODE) { - continue; - } - common_log(LOG_INFO, $child->tagName); - switch (strtolower($child->tagName)) { - case 'dt': - if (!empty($dt)) { - // No DD provided - $this->importBookmark($user, $dt); - $dt = null; - } - $dt = $child; - break; - case 'dd': - $dd = $child; - $saved = $this->importBookmark($user, $dt, $dd); - $dt = null; - $dd = null; - case 'p': - common_log(LOG_INFO, 'Skipping the

in the

.'); - break; - default: - common_log(LOG_WARNING, "Unexpected element $child->tagName found in import."); - } - } catch (Exception $e) { - common_log(LOG_ERR, $e->getMessage()); - $dt = $dd = null; - } - } - } + $dt = null; - function importBookmark($user, $dt, $dd = null) - { - // We have to go squirrelling around in the child nodes - // on the off chance that we've received another
- // as a child. + for ($i = 0; $i < $children->length; $i++) { + try { + $child = $children->item($i); + if ($child->nodeType != XML_ELEMENT_NODE) { + continue; + } + common_log(LOG_INFO, $child->tagName); + switch (strtolower($child->tagName)) { + case 'dt': + if (!empty($dt)) { + // No DD provided + $this->importBookmark($user, $dt); + $dt = null; + } + $dt = $child; + break; + case 'dd': + $dd = $child; - for ($i = 0; $i < $dt->childNodes->length; $i++) { - $child = $dt->childNodes->item($i); - if ($child->nodeType == XML_ELEMENT_NODE) { - if ($child->tagName == 'dt' && !is_null($dd)) { - $this->importBookmark($user, $dt); - $this->importBookmark($user, $child, $dd); - return; - } - } - } + $saved = $this->importBookmark($user, $dt, $dd); - $as = $dt->getElementsByTagName('a'); + $dt = null; + $dd = null; + case 'p': + common_log(LOG_INFO, 'Skipping the

in the

.'); + break; + default: + common_log(LOG_WARNING, + "Unexpected element $child->tagName ". + " found in import."); + } + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + $dt = $dd = null; + } + } + } - if ($as->length == 0) { - throw new ClientException(_("No tag in a
.")); - } + /** + * Import a single bookmark + * + * Takes a
/
pair. The
has a single + * in it with some non-standard attributes. + * + * A
sequence will appear as a
with + * anothe
as a child. We handle this case recursively. + * + * @param User $user User to import data as + * @param DOMElement $dt
element + * @param DOMElement $dd
element + * + * @return Notice imported notice + */ - $a = $as->item(0); - - $private = $a->getAttribute('private'); + function importBookmark($user, $dt, $dd = null) + { + // We have to go squirrelling around in the child nodes + // on the off chance that we've received another
+ // as a child. - if ($private != 0) { - throw new ClientException(_('Skipping private bookmark.')); - } + for ($i = 0; $i < $dt->childNodes->length; $i++) { + $child = $dt->childNodes->item($i); + if ($child->nodeType == XML_ELEMENT_NODE) { + if ($child->tagName == 'dt' && !is_null($dd)) { + $this->importBookmark($user, $dt); + $this->importBookmark($user, $child, $dd); + return; + } + } + } - if (!empty($dd)) { - $description = $dd->nodeValue; - } else { - $description = null; - } + $as = $dt->getElementsByTagName('a'); - $title = $a->nodeValue; - $url = $a->getAttribute('href'); - $tags = $a->getAttribute('tags'); - $addDate = $a->getAttribute('add_date'); - $created = common_sql_date(intval($addDate)); + if ($as->length == 0) { + throw new ClientException(_("No tag in a
.")); + } - $saved = Notice_bookmark::saveNew($user, - $title, - $url, - $tags, - $description, - array('created' => $created)); + $a = $as->item(0); + + $private = $a->getAttribute('private'); - return $saved; - } + if ($private != 0) { + throw new ClientException(_('Skipping private bookmark.')); + } - function importHTML($body) - { + if (!empty($dd)) { + $description = $dd->nodeValue; + } else { + $description = null; + } + + $title = $a->nodeValue; + $url = $a->getAttribute('href'); + $tags = $a->getAttribute('tags'); + $addDate = $a->getAttribute('add_date'); + $created = common_sql_date(intval($addDate)); + + $saved = Notice_bookmark::saveNew($user, + $title, + $url, + $tags, + $description, + array('created' => $created)); + + return $saved; + } + + /** + * Parse some HTML + * + * Hides the errors that the dom parser returns + * + * @param string $body Data to import + * + * @return DOMDocument parsed document + */ + + function importHTML($body) + { // DOMDocument::loadHTML may throw warnings on unrecognized elements, // and notices on unrecognized namespaces. $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE)); $dom = new DOMDocument(); - $ok = $dom->loadHTML($body); + $ok = $dom->loadHTML($body); error_reporting($old); - if ($ok) { - return $dom; - } else { - return null; - } - } + if ($ok) { + return $dom; + } else { + return null; + } + } } From c96faf065dcf8dd42507cbfd72bc0ef5e085a642 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 10:13:20 -0500 Subject: [PATCH 21/74] PHPCS BookmarkPlugin.php --- plugins/Bookmark/BookmarkPlugin.php | 414 +++++++++++++++------------- 1 file changed, 229 insertions(+), 185 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 50c28363c6..900dc7651b 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -29,7 +29,7 @@ */ if (!defined('STATUSNET')) { - exit(1); + exit(1); } /** @@ -46,219 +46,263 @@ if (!defined('STATUSNET')) { class BookmarkPlugin extends Plugin { - /** - * Database schema setup - * - * @see Schema - * @see ColumnDef - * - * @return boolean hook value; true means continue processing, false means stop. - */ + const VERSION = '0.1'; - function onCheckSchema() - { - $schema = Schema::get(); + /** + * Database schema setup + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ - // For storing user-submitted flags on profiles + function onCheckSchema() + { + $schema = Schema::get(); - $schema->ensureTable('notice_bookmark', - array(new ColumnDef('notice_id', - 'integer', - null, - false, - 'PRI'), - new ColumnDef('title', - 'varchar', - 255), - new ColumnDef('description', - 'text'))); + // For storing user-submitted flags on profiles - return true; - } + $schema->ensureTable('notice_bookmark', + array(new ColumnDef('notice_id', + 'integer', + null, + false, + 'PRI'), + new ColumnDef('title', + 'varchar', + 255), + new ColumnDef('description', + 'text'))); - function onNoticeDeleteRelated($notice) - { - $nb = Notice_bookmark::staticGet('notice_id', $notice->id); + return true; + } - if (!empty($nb)) { - $nb->delete(); - } + /** + * When a notice is deleted, delete the related Notice_bookmark + * + * @param Notice $notice Notice being deleted + * + * @return boolean hook value + */ - return true; - } + function onNoticeDeleteRelated($notice) + { + $nb = Notice_bookmark::staticGet('notice_id', $notice->id); - function onEndShowStyles($action) - { - $action->style('.bookmark_tags li { display: inline; }'); - $action->style('.bookmark_mentions li { display: inline; }'); - return true; - } + if (!empty($nb)) { + $nb->delete(); + } - /** - * Load related modules when needed - * - * @param string $cls Name of the class to be loaded - * - * @return boolean hook value; true means continue processing, false means stop. - */ + return true; + } - function onAutoload($cls) - { - $dir = dirname(__FILE__); + /** + * Show the CSS necessary for this plugin + * + * @param Action $action the action being run + * + * @return boolean hook value + */ - switch ($cls) - { - case 'NewbookmarkAction': - include_once $dir.'/newbookmark.php'; - return false; - case 'Notice_bookmark': - include_once $dir.'/'.$cls.'.php'; - return false; - case 'BookmarkForm': - case 'DeliciousBackupImporter': - include_once $dir.'/'.strtolower($cls).'.php'; - return false; - default: - return true; + function onEndShowStyles($action) + { + $action->style('.bookmark_tags li { display: inline; }'); + $action->style('.bookmark_mentions li { display: inline; }'); + return true; + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'NewbookmarkAction': + include_once $dir.'/newbookmark.php'; + return false; + case 'Notice_bookmark': + include_once $dir.'/'.$cls.'.php'; + return false; + case 'BookmarkForm': + case 'DeliciousBackupImporter': + include_once $dir.'/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + /** + * Map URLs to actions + * + * @param Net_URL_Mapper $m path-to-action mapper + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onRouterInitialized($m) + { + $m->connect('main/bookmark/new', + array('action' => 'newbookmark'), + array('id' => '[0-9]+')); + + return true; + } + + /** + * Output the HTML for a bookmark in a list + * + * @param NoticeListItem $nli The list item being shown. + * + * @return boolean hook value + */ + + function onStartShowNoticeItem($nli) + { + $nb = Notice_bookmark::staticGet('notice_id', + $nli->notice->id); + + if (!empty($nb)) { + $att = $nli->notice->attachments(); + $nli->out->elementStart('h3'); + $nli->out->element('a', + array('href' => $att[0]->url), + $nb->title); + $nli->out->elementEnd('h3'); + $nli->out->element('p', + array('class' => 'bookmark_description'), + $nb->description); + $nli->out->elementStart('p'); + $nli->out->element('a', array('href' => $nli->profile->profileurl, + 'class' => 'bookmark_author', + 'title' => $nli->profile->getBestName()), + $nli->profile->getBestName()); + $nli->out->elementEnd('p'); + $tags = $nli->notice->getTags(); + $nli->out->elementStart('ul', array('class' => 'bookmark_tags')); + foreach ($tags as $tag) { + $nli->out->elementStart('li'); + $nli->out->element('a', + array('rel' => 'tag', + 'href' => Notice_tag::url($tag)), + $tag); + $nli->out->elementEnd('li'); + $nli->out->text(' '); } - } + $nli->out->elementEnd('ul'); + $replies = $nli->notice->getReplies(); + if (!empty($replies)) { + $nli->out->elementStart('ul', array('class' => 'bookmark_mentions')); + foreach ($replies as $reply) { + $other = Profile::staticGet('id', $reply); + $nli->out->elementStart('li'); + $nli->out->element('a', array('rel' => 'tag', + 'href' => $other->profileurl, + 'title' => $other->getBestName()), + sprintf('for:%s', $other->nickname)); + $nli->out->elementEnd('li'); + $nli->out->text(' '); + } + $nli->out->elementEnd('ul'); + } + return false; + } + return true; + } - /** - * Map URLs to actions - * - * @param Net_URL_Mapper $m path-to-action mapper - * - * @return boolean hook value; true means continue processing, false means stop. - */ + /** + * Render a notice as a Bookmark object + * + * @param Notice $notice Notice to render + * @param ActivityObject &$object Empty object to fill + * + * @return boolean hook value + */ + + function onStartActivityObjectFromNotice($notice, &$object) + { + $nb = Notice_bookmark::staticGet('notice_id', + $notice->id); + + if (!empty($nb)) { - function onRouterInitialized($m) - { - $m->connect('main/bookmark/new', - array('action' => 'newbookmark'), - array('id' => '[0-9]+')); - - return true; - } - - function onStartShowNoticeItem($nli) - { - $nb = Notice_bookmark::staticGet('notice_id', - $nli->notice->id); - - if (!empty($nb)) { - $att = $nli->notice->attachments(); - $nli->out->elementStart('h3'); - $nli->out->element('a', - array('href' => $att[0]->url), - $nb->title); - $nli->out->elementEnd('h3'); - $nli->out->element('p', - array('class' => 'bookmark_description'), - $nb->description); - $nli->out->elementStart('p'); - $nli->out->element('a', array('href' => $nli->profile->profileurl, - 'class' => 'bookmark_author', - 'title' => $nli->profile->getBestName()), - $nli->profile->getBestName()); - $nli->out->elementEnd('p'); - $tags = $nli->notice->getTags(); - $nli->out->elementStart('ul', array('class' => 'bookmark_tags')); - foreach ($tags as $tag) { - $nli->out->elementStart('li'); - $nli->out->element('a', - array('rel' => 'tag', - 'href' => Notice_tag::url($tag)), - $tag); - $nli->out->elementEnd('li'); - $nli->out->text(' '); - } - $nli->out->elementEnd('ul'); - $replies = $nli->notice->getReplies(); - if (!empty($replies)) { - $nli->out->elementStart('ul', array('class' => 'bookmark_mentions')); - foreach ($replies as $reply) { - $other = Profile::staticGet('id', $reply); - $nli->out->elementStart('li'); - $nli->out->element('a', array('rel' => 'tag', - 'href' => $other->profileurl, - 'title' => $other->getBestName()), - sprintf('for:%s', $other->nickname)); - $nli->out->elementEnd('li'); - $nli->out->text(' '); - } - $nli->out->elementEnd('ul'); - } - return false; - } - return true; - } - - function onStartActivityObjectFromNotice($notice, &$object) - { - $nb = Notice_bookmark::staticGet('notice_id', - $notice->id); - - if (!empty($nb)) { - - $object->id = $notice->uri; - $object->type = ActivityObject::BOOKMARK; - $object->title = $nb->title; - $object->summary = $nb->description; + $object->id = $notice->uri; + $object->type = ActivityObject::BOOKMARK; + $object->title = $nb->title; + $object->summary = $nb->description; $object->link = $notice->bestUrl(); - // Attributes of the URL + // Attributes of the URL - $attachments = $notice->attachments(); + $attachments = $notice->attachments(); - if (count($attachments) != 1) { - throw new ServerException(_('Bookmark notice with the wrong number of attachments.')); - } + if (count($attachments) != 1) { + throw new ServerException(_('Bookmark notice with the '. + 'wrong number of attachments.')); + } - $target = $attachments[0]; + $target = $attachments[0]; - $attrs = array('rel' => 'related', - 'href' => $target->url); + $attrs = array('rel' => 'related', + 'href' => $target->url); - if (!empty($target->title)) { - $attrs['title'] = $target->title; - } + if (!empty($target->title)) { + $attrs['title'] = $target->title; + } - $object->extra[] = array('link', $attrs); - - // Attributes of the thumbnail, if any + $object->extra[] = array('link', $attrs); + + // Attributes of the thumbnail, if any - $thumbnail = $target->getThumbnail(); + $thumbnail = $target->getThumbnail(); - if (!empty($thumbnail)) { - $tattrs = array('rel' => 'preview', - 'href' => $thumbnail->url); + if (!empty($thumbnail)) { + $tattrs = array('rel' => 'preview', + 'href' => $thumbnail->url); - if (!empty($thumbnail->width)) { - $tattrs['media:width'] = $thumbnail->width; - } + if (!empty($thumbnail->width)) { + $tattrs['media:width'] = $thumbnail->width; + } - if (!empty($thumbnail->height)) { - $tattrs['media:height'] = $thumbnail->height; - } + if (!empty($thumbnail->height)) { + $tattrs['media:height'] = $thumbnail->height; + } - $object->extra[] = array('link', $attrs); - } + $object->extra[] = array('link', $attrs); + } - return false; - } + return false; + } - return true; - } + return true; + } - function onPluginVersion(&$versions) - { - $versions[] = array('name' => 'Sample', - 'version' => STATUSNET_VERSION, - 'author' => 'Evan Prodromou', - 'homepage' => 'http://status.net/wiki/Plugin:Bookmark', - 'rawdescription' => - _m('Simple extension for supporting bookmarks.')); - return true; - } + /** + * Plugin version data + * + * @param array &$versions array of version data + * + * @return value + */ + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'Sample', + 'version' => self::VERSION, + 'author' => 'Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:Bookmark', + 'rawdescription' => + _m('Simple extension for supporting bookmarks.')); + return true; + } } From 14babfb9000d4602765339c297f09e81deae2fb8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 10:16:53 -0500 Subject: [PATCH 22/74] PHPCS BookmarkForm --- plugins/Bookmark/bookmarkform.php | 59 +++++++++++++++++++------------ 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/plugins/Bookmark/bookmarkform.php b/plugins/Bookmark/bookmarkform.php index deeed84829..75f20b7576 100644 --- a/plugins/Bookmark/bookmarkform.php +++ b/plugins/Bookmark/bookmarkform.php @@ -47,20 +47,33 @@ if (!defined('STATUSNET')) { class BookmarkForm extends Form { - private $_title = null; - private $_url = null; - private $_tags = null; - private $_description = null; + private $_title = null; + private $_url = null; + private $_tags = null; + private $_description = null; - function __construct($out=null, $title=null, $url=null, $tags=null, $description=null) - { - parent::__construct($out); + /** + * Construct a bookmark form + * + * @param HTMLOutputter $out output channel + * @param string $title Title of the bookmark + * @param string $url URL of the bookmark + * @param string $tags Tags to show + * @param string $description Description of the bookmark + * + * @return void + */ - $this->_title = $title; - $this->_url = $url; - $this->_tags = $tags; - $this->_description = $description; - } + function __construct($out=null, $title=null, $url=null, $tags=null, + $description=null) + { + parent::__construct($out); + + $this->_title = $title; + $this->_url = $url; + $this->_tags = $tags; + $this->_description = $description; + } /** * ID of the form @@ -103,35 +116,35 @@ class BookmarkForm extends Form function formData() { - $this->out->elementStart('fieldset', array('id' => 'new_bookmark_data')); + $this->out->elementStart('fieldset', array('id' => 'new_bookmark_data')); $this->out->elementStart('ul', 'form_data'); $this->li(); - $this->out->input('title', - _('Title'), - $this->_title, - _('Title of the bookmark')); + $this->out->input('title', + _('Title'), + $this->_title, + _('Title of the bookmark')); $this->unli(); $this->li(); $this->out->input('url', - _('URL'), + _('URL'), $this->_url, - _('URL to bookmark')); + _('URL to bookmark')); $this->unli(); $this->li(); $this->out->input('tags', - _('Tags'), + _('Tags'), $this->_tags, - _('Comma- or space-separated list of tags')); + _('Comma- or space-separated list of tags')); $this->unli(); $this->li(); $this->out->input('description', - _('Description'), + _('Description'), $this->_description, - _('Description of the URL')); + _('Description of the URL')); $this->unli(); $this->out->elementEnd('ul'); From 907f1ad63304b775d857195eb35b46fdb4a9066a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 10:20:49 -0500 Subject: [PATCH 23/74] PHPCS importbookmarks --- plugins/Bookmark/importbookmarks.php | 46 ++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/plugins/Bookmark/importbookmarks.php b/plugins/Bookmark/importbookmarks.php index 38a53d800d..991329dd6d 100644 --- a/plugins/Bookmark/importbookmarks.php +++ b/plugins/Bookmark/importbookmarks.php @@ -1,8 +1,12 @@ . + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ */ define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); $shortoptions = 'i:n:f:'; -$longoptions = array('id=', 'nickname=', 'file='); +$longoptions = array('id=', 'nickname=', 'file='); $helptext = <<importBookmarks($user, $html); + $dbi->importBookmarks($user, $html); } catch (Exception $e) { print $e->getMessage()."\n"; From d9ff466d2cf753bee555be64ce4f845aa48873c4 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 10:22:50 -0500 Subject: [PATCH 24/74] PHPCS newbookmark --- plugins/Bookmark/newbookmark.php | 118 +++++++++++++++++-------------- 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index 079babccd4..efc664e7b6 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -47,18 +47,24 @@ if (!defined('STATUSNET')) { class NewbookmarkAction extends Action { - private $_user = null; - private $_error = null; - private $_complete = null; - private $_title = null; - private $_url = null; - private $_tags = null; - private $_description = null; + private $_user = null; + private $_error = null; + private $_complete = null; + private $_title = null; + private $_url = null; + private $_tags = null; + private $_description = null; - function title() - { - return _('New bookmark'); - } + /** + * Returns the title of the action + * + * @return string Action title + */ + + function title() + { + return _('New bookmark'); + } /** * For initializing members of the class. @@ -72,20 +78,21 @@ class NewbookmarkAction extends Action { parent::prepare($argarray); - $this->_user = common_current_user(); + $this->_user = common_current_user(); - if (empty($this->_user)) { - throw new ClientException(_("Must be logged in to post a bookmark."), 403); - } + if (empty($this->_user)) { + throw new ClientException(_("Must be logged in to post a bookmark."), + 403); + } - if ($this->isPost()) { - $this->checkSessionToken(); - } + if ($this->isPost()) { + $this->checkSessionToken(); + } - $this->_title = $this->trimmed('title'); - $this->_url = $this->trimmed('url'); - $this->_tags = $this->trimmed('tags'); - $this->_description = $this->trimmed('description'); + $this->_title = $this->trimmed('title'); + $this->_url = $this->trimmed('url'); + $this->_tags = $this->trimmed('tags'); + $this->_description = $this->trimmed('description'); return true; } @@ -100,13 +107,13 @@ class NewbookmarkAction extends Action function handle($argarray=null) { - parent::handle($argarray); + parent::handle($argarray); - if ($this->isPost()) { - $this->newBookmark(); - } else { - $this->showPage(); - } + if ($this->isPost()) { + $this->newBookmark(); + } else { + $this->showPage(); + } return; } @@ -119,29 +126,29 @@ class NewbookmarkAction extends Action function newBookmark() { - try { - if (empty($this->_title)) { - throw new ClientException(_('Bookmark must have a title.')); - } + try { + if (empty($this->_title)) { + throw new ClientException(_('Bookmark must have a title.')); + } - if (empty($this->_url)) { - throw new ClientException(_('Bookmark must have an URL.')); - } + if (empty($this->_url)) { + throw new ClientException(_('Bookmark must have an URL.')); + } - $saved = Notice_bookmark::saveNew($this->_user, - $this->_title, - $this->_url, - $this->_tags, - $this->_description); + $saved = Notice_bookmark::saveNew($this->_user, + $this->_title, + $this->_url, + $this->_tags, + $this->_description); - } catch (ClientException $ce) { - $this->_error = $ce->getMessage(); - $this->showPage(); - return; - } + } catch (ClientException $ce) { + $this->_error = $ce->getMessage(); + $this->showPage(); + return; + } - common_redirect($saved->bestUrl(), 303); + common_redirect($saved->bestUrl(), 303); } /** @@ -152,17 +159,17 @@ class NewbookmarkAction extends Action function showContent() { - if (!empty($this->_error)) { - $this->element('p', 'error', $this->_error); - } + if (!empty($this->_error)) { + $this->element('p', 'error', $this->_error); + } - $form = new BookmarkForm($this, - $this->_title, - $this->_url, - $this->_tags, - $this->_description); + $form = new BookmarkForm($this, + $this->_title, + $this->_url, + $this->_tags, + $this->_description); - $form->show(); + $form->show(); return; } @@ -187,3 +194,4 @@ class NewbookmarkAction extends Action } } } + \ No newline at end of file From b8a182dc4443f66ba4f542a3bf6e65d7fc9c2bad Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 10:32:35 -0500 Subject: [PATCH 25/74] PHPCS Notice_bookmark --- plugins/Bookmark/Notice_bookmark.php | 209 +++++++++++++++------------ 1 file changed, 117 insertions(+), 92 deletions(-) diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php index e816c45596..622c641ede 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Notice_bookmark.php @@ -47,8 +47,8 @@ class Notice_bookmark extends Memcached_DataObject { public $__table = 'notice_bookmark'; // table name public $notice_id; // int(4) primary_key not_null - public $title; // varchar(255) - public $description; // text + public $title; // varchar(255) + public $description; // text /** * Get an instance by key @@ -80,7 +80,7 @@ class Notice_bookmark extends Memcached_DataObject { return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, 'title' => DB_DATAOBJECT_STR, - 'description' => DB_DATAOBJECT_STR); + 'description' => DB_DATAOBJECT_STR); } /** @@ -116,108 +116,133 @@ class Notice_bookmark extends Memcached_DataObject return array(false, false, false); } - static function getByURL($user, $url) - { - $file = File::staticGet('url', $url); - if (!empty($file)) { - $f2p = new File_to_post(); - $f2p->file_id = $file->id; - if ($f2p->find()) { - while ($f2p->fetch()) { - $n = Notice::staticGet('id', $f2p->post_id); - if (!empty($n)) { - if ($n->profile_id == $user->id) { - $nb = Notice_bookmark::staticGet('notice_id', $n->id); - if (!empty($nb)) { - return $nb; - } - } - } - } - } - } - return null; - } + /** + * Get the bookmark that a user made for an URL + * + * @param User $user User to check for + * @param string $url URL to check for + * + * @return Notice_bookmark bookmark found or null + */ + + static function getByURL($user, $url) + { + $file = File::staticGet('url', $url); + if (!empty($file)) { + $f2p = new File_to_post(); - static function saveNew($user, $title, $url, $rawtags, $description, $options=null) - { - $nb = self::getByURL($user, $url); + $f2p->file_id = $file->id; + if ($f2p->find()) { + while ($f2p->fetch()) { + $n = Notice::staticGet('id', $f2p->post_id); + if (!empty($n)) { + if ($n->profile_id == $user->id) { + $nb = Notice_bookmark::staticGet('notice_id', $n->id); + if (!empty($nb)) { + return $nb; + } + } + } + } + } + } + return null; + } - if (!empty($nb)) { - throw new ClientException(_('Bookmark already exists.')); - } + /** + * Save a new notice bookmark + * + * @param User $user To save the bookmark for + * @param string $title Title of the bookmark + * @param string $url URL of the bookmark + * @param mixed $rawtags array of tags or string + * @param string $description Description of the bookmark + * @param array $options Options for the Notice::saveNew() + * + * @return Notice saved notice + */ - if (empty($options)) { - $options = array(); - } + static function saveNew($user, $title, $url, $rawtags, $description, + $options=null) + { + $nb = self::getByURL($user, $url); - if (is_string($rawtags)) { - $rawtags = preg_split('/[\s,]+/', $rawtags); - } + if (!empty($nb)) { + throw new ClientException(_('Bookmark already exists.')); + } - $tags = array(); - $replies = array(); + if (empty($options)) { + $options = array(); + } - // filter "for:nickname" tags + if (is_string($rawtags)) { + $rawtags = preg_split('/[\s,]+/', $rawtags); + } - foreach ($rawtags as $tag) { - if (strtolower(mb_substr($tag, 0, 4)) == 'for:') { - $nickname = mb_substr($tag, 4); - $other = common_relative_profile($user->getProfile(), - $nickname); - if (!empty($other)) { - $replies[] = $other->getUri(); - } - } else { - $tags[] = common_canonical_tag($tag); - } - } + $tags = array(); + $replies = array(); - $hashtags = array(); - $taglinks = array(); + // filter "for:nickname" tags - foreach ($tags as $tag) { - $hashtags[] = '#'.$tag; - $attrs = array('href' => Notice_tag::url($tag), - 'rel' => $tag, - 'class' => 'tag'); - $taglinks[] = XMLStringer::estring('a', $attrs, $tag); - } + foreach ($rawtags as $tag) { + if (strtolower(mb_substr($tag, 0, 4)) == 'for:') { + $nickname = mb_substr($tag, 4); + $other = common_relative_profile($user->getProfile(), + $nickname); + if (!empty($other)) { + $replies[] = $other->getUri(); + } + } else { + $tags[] = common_canonical_tag($tag); + } + } - $content = sprintf(_('"%s" %s %s %s'), - $title, - File_redirection::makeShort($url, $user), - $description, - implode(' ', $hashtags)); + $hashtags = array(); + $taglinks = array(); - $rendered = sprintf(_(''. - '%s '. - '%s '. - '%s'. - ''), - htmlspecialchars($url), - htmlspecialchars($title), - htmlspecialchars($description), - implode(' ', $taglinks)); + foreach ($tags as $tag) { + $hashtags[] = '#'.$tag; + $attrs = array('href' => Notice_tag::url($tag), + 'rel' => $tag, + 'class' => 'tag'); + $taglinks[] = XMLStringer::estring('a', $attrs, $tag); + } - $options = array_merge($options, array('urls' => array($url), - 'rendered' => $rendered, - 'tags' => $tags, - 'replies' => $replies)); + $content = sprintf(_('"%s" %s %s %s'), + $title, + File_redirection::makeShort($url, $user), + $description, + implode(' ', $hashtags)); - $saved = Notice::saveNew($user->id, - $content, - 'web', - $options); + $rendered = sprintf(_(''. + '%s '. + '%s '. + '%s'. + ''), + htmlspecialchars($url), + htmlspecialchars($title), + htmlspecialchars($description), + implode(' ', $taglinks)); - if (!empty($saved)) { - $nb = new Notice_bookmark(); - $nb->notice_id = $saved->id; - $nb->title = $title; - $nb->description = $description; - $nb->insert(); - } + $options = array_merge($options, array('urls' => array($url), + 'rendered' => $rendered, + 'tags' => $tags, + 'replies' => $replies)); - return $saved; - } + $saved = Notice::saveNew($user->id, + $content, + 'web', + $options); + + if (!empty($saved)) { + $nb = new Notice_bookmark(); + + $nb->notice_id = $saved->id; + $nb->title = $title; + $nb->description = $description; + $nb->insert(); + } + + return $saved; + } } From ccb290cb6831d519f7be530f9ad00a24d9663d8f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 11:09:01 -0500 Subject: [PATCH 26/74] Break up delicious import into a queue manager by bookmark --- plugins/Bookmark/BookmarkPlugin.php | 8 ++ plugins/Bookmark/deliciousbackupimporter.php | 63 ++++------ .../Bookmark/deliciousbookmarkimporter.php | 108 ++++++++++++++++++ plugins/Bookmark/importbookmarks.php | 7 +- 4 files changed, 141 insertions(+), 45 deletions(-) create mode 100644 plugins/Bookmark/deliciousbookmarkimporter.php diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 900dc7651b..6bd38a8c12 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -134,6 +134,7 @@ class BookmarkPlugin extends Plugin return false; case 'BookmarkForm': case 'DeliciousBackupImporter': + case 'DeliciousBookmarkImporter': include_once $dir.'/'.strtolower($cls).'.php'; return false; default: @@ -286,6 +287,13 @@ class BookmarkPlugin extends Plugin return true; } + function onEndInitializeQueueManager($qm) + { + $qm->connect('dlcsback', 'DeliciousBackupImporter'); + $qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter'); + return true; + } + /** * Plugin version data * diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php index 6ab87b5212..01b996bbe5 100644 --- a/plugins/Bookmark/deliciousbackupimporter.php +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -34,8 +34,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/apiauth.php'; - /** * Importer class for Delicious bookmarks * @@ -47,8 +45,19 @@ require_once INSTALLDIR . '/lib/apiauth.php'; * @link http://status.net/ */ -class DeliciousBackupImporter +class DeliciousBackupImporter extends QueueHandler { + /** + * Transport of the importer + * + * @return string transport string + */ + + function transport() + { + return 'dlcsback'; + } + /** * Import an in-memory bookmark list to a user's account * @@ -59,14 +68,15 @@ class DeliciousBackupImporter * a bunch of
's, occasionally with
's. * There are sometimes

's lost inside. * - * @param User $user User whose feed we're going to fill - * @param string $body Body of the file + * @param array $data pair of user, text * - * @return void + * @return boolean success value */ - function importBookmarks($user, $body) + function handle($data) { + list($user, $body) = $data; + $doc = $this->importHTML($body); $dls = $doc->getElementsByTagName('dl'); @@ -117,6 +127,8 @@ class DeliciousBackupImporter $dt = $dd = null; } } + + return true; } /** @@ -152,40 +164,9 @@ class DeliciousBackupImporter } } - $as = $dt->getElementsByTagName('a'); - - if ($as->length == 0) { - throw new ClientException(_("No tag in a

.")); - } - - $a = $as->item(0); - - $private = $a->getAttribute('private'); - - if ($private != 0) { - throw new ClientException(_('Skipping private bookmark.')); - } - - if (!empty($dd)) { - $description = $dd->nodeValue; - } else { - $description = null; - } - - $title = $a->nodeValue; - $url = $a->getAttribute('href'); - $tags = $a->getAttribute('tags'); - $addDate = $a->getAttribute('add_date'); - $created = common_sql_date(intval($addDate)); - - $saved = Notice_bookmark::saveNew($user, - $title, - $url, - $tags, - $description, - array('created' => $created)); - - return $saved; + $qm = QueueManager::get(); + + $qm->enqueue(array($user, $dt, $dd), 'dlcsbkmk'); } /** diff --git a/plugins/Bookmark/deliciousbookmarkimporter.php b/plugins/Bookmark/deliciousbookmarkimporter.php new file mode 100644 index 0000000000..686e1a39c0 --- /dev/null +++ b/plugins/Bookmark/deliciousbookmarkimporter.php @@ -0,0 +1,108 @@ +. + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Importer class for Delicious bookmarks + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class DeliciousBookmarkImporter extends QueueHandler +{ + /** + * Return the transport for this queue handler + * + * @return string 'dlcsbkmk' + */ + + function transport() + { + return 'dlcsbkmk'; + } + + /** + * Handle the data + * + * @param array $data array of user, dt, dd + * + * @return boolean success value + */ + + function handle($data) + { + list($user, $dt, $dd) = $data; + + $as = $dt->getElementsByTagName('a'); + + if ($as->length == 0) { + throw new ClientException(_("No tag in a
.")); + } + + $a = $as->item(0); + + $private = $a->getAttribute('private'); + + if ($private != 0) { + throw new ClientException(_('Skipping private bookmark.')); + } + + if (!empty($dd)) { + $description = $dd->nodeValue; + } else { + $description = null; + } + + $title = $a->nodeValue; + $url = $a->getAttribute('href'); + $tags = $a->getAttribute('tags'); + $addDate = $a->getAttribute('add_date'); + $created = common_sql_date(intval($addDate)); + + $saved = Notice_bookmark::saveNew($user, + $title, + $url, + $tags, + $description, + array('created' => $created)); + + return true; + } +} diff --git a/plugins/Bookmark/importbookmarks.php b/plugins/Bookmark/importbookmarks.php index 991329dd6d..5518b00e97 100644 --- a/plugins/Bookmark/importbookmarks.php +++ b/plugins/Bookmark/importbookmarks.php @@ -83,13 +83,12 @@ function getBookmarksFile() } try { - $dbi = new DeliciousBackupImporter(); - $user = getUser(); - $html = getBookmarksFile(); - $dbi->importBookmarks($user, $html); + $qm = QueueManager::get(); + + $qm->enqueue(array($user, $html), 'dlcsback'); } catch (Exception $e) { print $e->getMessage()."\n"; From 6670dd84506c9e01cd384f146e19bad91f185e5d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 12:25:23 -0500 Subject: [PATCH 27/74] Layout on the bookmark form --- plugins/Bookmark/bookmarkform.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Bookmark/bookmarkform.php b/plugins/Bookmark/bookmarkform.php index 75f20b7576..b99568e154 100644 --- a/plugins/Bookmark/bookmarkform.php +++ b/plugins/Bookmark/bookmarkform.php @@ -94,7 +94,7 @@ class BookmarkForm extends Form function formClass() { - return 'form_new_bookmark'; + return 'form_settings'; } /** From 0665beec583cc0e6dcc90060433087f1f9a9c341 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 14:43:03 -0500 Subject: [PATCH 28/74] Bookmarklet for new bookmarks Override the bookmarklet help page to add a new popup. --- plugins/Bookmark/BookmarkPlugin.php | 27 ++++++++- plugins/Bookmark/bookmarklet | 9 +++ plugins/Bookmark/bookmarkpopup.php | 87 +++++++++++++++++++++++++++++ plugins/Bookmark/newbookmark.php | 14 ++--- 4 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 plugins/Bookmark/bookmarklet create mode 100644 plugins/Bookmark/bookmarkpopup.php diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 6bd38a8c12..ae0f493abb 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -127,7 +127,8 @@ class BookmarkPlugin extends Plugin switch ($cls) { case 'NewbookmarkAction': - include_once $dir.'/newbookmark.php'; + case 'BookmarkpopupAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'Notice_bookmark': include_once $dir.'/'.$cls.'.php'; @@ -156,6 +157,8 @@ class BookmarkPlugin extends Plugin array('action' => 'newbookmark'), array('id' => '[0-9]+')); + $m->connect('main/bookmark/popup', array('action' => 'bookmarkpopup')); + return true; } @@ -312,5 +315,27 @@ class BookmarkPlugin extends Plugin _m('Simple extension for supporting bookmarks.')); return true; } + + /** + * Load our document if requested + * + * @param string &$title Title to fetch + * @param string &$output HTML to output + * + * @return boolean hook value + */ + + function onStartLoadDoc(&$title, &$output) + { + if ($title == 'bookmarklet') { + $filename = INSTALLDIR.'/plugins/Bookmark/bookmarklet'; + + $c = file_get_contents($filename); + $output = common_markup_to_html($c); + return false; // success! + } + + return true; + } } diff --git a/plugins/Bookmark/bookmarklet b/plugins/Bookmark/bookmarklet new file mode 100644 index 0000000000..fc1f8b9d05 --- /dev/null +++ b/plugins/Bookmark/bookmarklet @@ -0,0 +1,9 @@ + + + + +A bookmarklet is a small piece of javascript code used as a bookmark. This one will let you post to %%site.name%% simply by selecting some text on a page and pressing the bookmarklet. + +Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy. + +Bookmark on %%site.name%% diff --git a/plugins/Bookmark/bookmarkpopup.php b/plugins/Bookmark/bookmarkpopup.php new file mode 100644 index 0000000000..52a40de921 --- /dev/null +++ b/plugins/Bookmark/bookmarkpopup.php @@ -0,0 +1,87 @@ +. + * + * @category Bookmark + * @package StatusNet + * @author Sarven Capadisli + * @author Evan Prodromou + * @copyright 2008-2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Action for posting a new bookmark + * + * @category Bookmark + * @package StatusNet + * @author Sarven Capadisli + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class BookmarkpopupAction extends NewbookmarkAction +{ + function prepare($args) + { + $result = parent::prepare($args); + common_debug('Values: ' . $this->_title . ' ' . $this->_url); + return $result; + } + + function showTitle() + { + // TRANS: Title for mini-posting window loaded from bookmarklet. + // TRANS: %s is the StatusNet site name. + $this->element('title', + null, sprintf(_('Bookmark on %s'), + common_config('site', 'name'))); + } + + function showHeader() + { + $this->elementStart('div', array('id' => 'header')); + $this->elementStart('address'); + $this->element('a', array('class' => 'url', + 'href' => common_local_url('public')), + ''); + $this->elementEnd('address'); + if (common_logged_in()) { + $form = new BookmarkForm($this, + $this->_title, + $this->_url); + $form->show(); + } + $this->elementEnd('div'); + } + + function showCore() + { + } + + function showFooter() + { + } +} diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index efc664e7b6..7a11b08ad3 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -47,13 +47,13 @@ if (!defined('STATUSNET')) { class NewbookmarkAction extends Action { - private $_user = null; - private $_error = null; - private $_complete = null; - private $_title = null; - private $_url = null; - private $_tags = null; - private $_description = null; + protected $_user = null; + protected $_error = null; + protected $_complete = null; + protected $_title = null; + protected $_url = null; + protected $_tags = null; + protected $_description = null; /** * Returns the title of the action From 6ff8977243715f29bd54d4328042bfbe6b1f77f8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 21 Dec 2010 14:46:31 -0500 Subject: [PATCH 29/74] Bookmarklet for Bookmark plugin --- plugins/Bookmark/BookmarkPlugin.php | 8 +++++ plugins/Bookmark/bookmarkpopup.php | 35 +++++++++++++----- plugins/Bookmark/newbookmark.php | 55 ++++++++++++++--------------- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index ae0f493abb..9b8addf632 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -290,6 +290,14 @@ class BookmarkPlugin extends Plugin return true; } + /** + * Add our two queue handlers to the queue manager + * + * @param QueueManager $qm current queue manager + * + * @return boolean hook value + */ + function onEndInitializeQueueManager($qm) { $qm->connect('dlcsback', 'DeliciousBackupImporter'); diff --git a/plugins/Bookmark/bookmarkpopup.php b/plugins/Bookmark/bookmarkpopup.php index 52a40de921..2e6d457a83 100644 --- a/plugins/Bookmark/bookmarkpopup.php +++ b/plugins/Bookmark/bookmarkpopup.php @@ -44,12 +44,11 @@ if (!defined('STATUSNET')) { */ class BookmarkpopupAction extends NewbookmarkAction { - function prepare($args) - { - $result = parent::prepare($args); - common_debug('Values: ' . $this->_title . ' ' . $this->_url); - return $result; - } + /** + * Show the title section of the window + * + * @return void + */ function showTitle() { @@ -60,6 +59,14 @@ class BookmarkpopupAction extends NewbookmarkAction common_config('site', 'name'))); } + /** + * Show the header section of the page + * + * Shows a stub page and the bookmark form. + * + * @return void + */ + function showHeader() { $this->elementStart('div', array('id' => 'header')); @@ -70,17 +77,29 @@ class BookmarkpopupAction extends NewbookmarkAction $this->elementEnd('address'); if (common_logged_in()) { $form = new BookmarkForm($this, - $this->_title, - $this->_url); + $this->title, + $this->url); $form->show(); } $this->elementEnd('div'); } + /** + * Hide the core section of the page + * + * @return void + */ + function showCore() { } + /** + * Hide the footer section of the page + * + * @return void + */ + function showFooter() { } diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index 7a11b08ad3..94d8f3f4f0 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -27,7 +27,6 @@ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ - if (!defined('STATUSNET')) { // This check helps protect against security problems; // your code file can't be executed directly from the web. @@ -47,13 +46,13 @@ if (!defined('STATUSNET')) { class NewbookmarkAction extends Action { - protected $_user = null; - protected $_error = null; - protected $_complete = null; - protected $_title = null; - protected $_url = null; - protected $_tags = null; - protected $_description = null; + protected $user = null; + protected $error = null; + protected $complete = null; + protected $title = null; + protected $url = null; + protected $tags = null; + protected $description = null; /** * Returns the title of the action @@ -78,9 +77,9 @@ class NewbookmarkAction extends Action { parent::prepare($argarray); - $this->_user = common_current_user(); + $this->user = common_current_user(); - if (empty($this->_user)) { + if (empty($this->user)) { throw new ClientException(_("Must be logged in to post a bookmark."), 403); } @@ -89,10 +88,10 @@ class NewbookmarkAction extends Action $this->checkSessionToken(); } - $this->_title = $this->trimmed('title'); - $this->_url = $this->trimmed('url'); - $this->_tags = $this->trimmed('tags'); - $this->_description = $this->trimmed('description'); + $this->title = $this->trimmed('title'); + $this->url = $this->trimmed('url'); + $this->tags = $this->trimmed('tags'); + $this->description = $this->trimmed('description'); return true; } @@ -127,23 +126,23 @@ class NewbookmarkAction extends Action function newBookmark() { try { - if (empty($this->_title)) { + if (empty($this->title)) { throw new ClientException(_('Bookmark must have a title.')); } - if (empty($this->_url)) { + if (empty($this->url)) { throw new ClientException(_('Bookmark must have an URL.')); } - $saved = Notice_bookmark::saveNew($this->_user, - $this->_title, - $this->_url, - $this->_tags, - $this->_description); + $saved = Notice_bookmark::saveNew($this->user, + $this->title, + $this->url, + $this->tags, + $this->description); } catch (ClientException $ce) { - $this->_error = $ce->getMessage(); + $this->error = $ce->getMessage(); $this->showPage(); return; } @@ -159,15 +158,15 @@ class NewbookmarkAction extends Action function showContent() { - if (!empty($this->_error)) { - $this->element('p', 'error', $this->_error); + if (!empty($this->error)) { + $this->element('p', 'error', $this->error); } $form = new BookmarkForm($this, - $this->_title, - $this->_url, - $this->_tags, - $this->_description); + $this->title, + $this->url, + $this->tags, + $this->description); $form->show(); From 73dccdd7f5760ff88955a2e67df53c987ca3a6a6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 22 Dec 2010 12:35:45 -0500 Subject: [PATCH 30/74] Notice_bookmark::saveNew() takes a Profile argument --- plugins/Bookmark/Notice_bookmark.php | 37 +++++++++++-------- .../Bookmark/deliciousbookmarkimporter.php | 2 +- plugins/Bookmark/newbookmark.php | 2 +- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php index 622c641ede..38e2890abe 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Notice_bookmark.php @@ -119,13 +119,13 @@ class Notice_bookmark extends Memcached_DataObject /** * Get the bookmark that a user made for an URL * - * @param User $user User to check for - * @param string $url URL to check for + * @param Profile $profile Profile to check for + * @param string $url URL to check for * * @return Notice_bookmark bookmark found or null */ - static function getByURL($user, $url) + static function getByURL($profile, $url) { $file = File::staticGet('url', $url); if (!empty($file)) { @@ -136,7 +136,7 @@ class Notice_bookmark extends Memcached_DataObject while ($f2p->fetch()) { $n = Notice::staticGet('id', $f2p->post_id); if (!empty($n)) { - if ($n->profile_id == $user->id) { + if ($n->profile_id == $profile->id) { $nb = Notice_bookmark::staticGet('notice_id', $n->id); if (!empty($nb)) { return $nb; @@ -152,20 +152,20 @@ class Notice_bookmark extends Memcached_DataObject /** * Save a new notice bookmark * - * @param User $user To save the bookmark for - * @param string $title Title of the bookmark - * @param string $url URL of the bookmark - * @param mixed $rawtags array of tags or string - * @param string $description Description of the bookmark - * @param array $options Options for the Notice::saveNew() + * @param Profile $profile To save the bookmark for + * @param string $title Title of the bookmark + * @param string $url URL of the bookmark + * @param mixed $rawtags array of tags or string + * @param string $description Description of the bookmark + * @param array $options Options for the Notice::saveNew() * * @return Notice saved notice */ - static function saveNew($user, $title, $url, $rawtags, $description, + static function saveNew($profile, $title, $url, $rawtags, $description, $options=null) { - $nb = self::getByURL($user, $url); + $nb = self::getByURL($profile, $url); if (!empty($nb)) { throw new ClientException(_('Bookmark already exists.')); @@ -187,7 +187,7 @@ class Notice_bookmark extends Memcached_DataObject foreach ($rawtags as $tag) { if (strtolower(mb_substr($tag, 0, 4)) == 'for:') { $nickname = mb_substr($tag, 4); - $other = common_relative_profile($user->getProfile(), + $other = common_relative_profile($profile, $nickname); if (!empty($other)) { $replies[] = $other->getUri(); @@ -208,9 +208,16 @@ class Notice_bookmark extends Memcached_DataObject $taglinks[] = XMLStringer::estring('a', $attrs, $tag); } + // Use user's preferences for short URLs, if possible + + $user = User::staticGet('id', $profile->id); + + $shortUrl = File_redirection::makeShort($url, + empty($user) ? null : $user); + $content = sprintf(_('"%s" %s %s %s'), $title, - File_redirection::makeShort($url, $user), + $shortUrl, $description, implode(' ', $hashtags)); @@ -229,7 +236,7 @@ class Notice_bookmark extends Memcached_DataObject 'tags' => $tags, 'replies' => $replies)); - $saved = Notice::saveNew($user->id, + $saved = Notice::saveNew($profile->id, $content, 'web', $options); diff --git a/plugins/Bookmark/deliciousbookmarkimporter.php b/plugins/Bookmark/deliciousbookmarkimporter.php index 686e1a39c0..22ad45882e 100644 --- a/plugins/Bookmark/deliciousbookmarkimporter.php +++ b/plugins/Bookmark/deliciousbookmarkimporter.php @@ -96,7 +96,7 @@ class DeliciousBookmarkImporter extends QueueHandler $addDate = $a->getAttribute('add_date'); $created = common_sql_date(intval($addDate)); - $saved = Notice_bookmark::saveNew($user, + $saved = Notice_bookmark::saveNew($user->getProfile(), $title, $url, $tags, diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index 94d8f3f4f0..63944c8176 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -135,7 +135,7 @@ class NewbookmarkAction extends Action } - $saved = Notice_bookmark::saveNew($this->user, + $saved = Notice_bookmark::saveNew($this->user->getProfile(), $this->title, $this->url, $this->tags, From 1faaaed72bbf62f3701da8a79caf0ad9950ab8e0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 22 Dec 2010 13:08:07 -0800 Subject: [PATCH 31/74] Move bookmark CSS to its own file --- plugins/Bookmark/BookmarkPlugin.php | 3 +-- plugins/Bookmark/bookmark.css | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 plugins/Bookmark/bookmark.css diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 9b8addf632..83d9e131d3 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -107,8 +107,7 @@ class BookmarkPlugin extends Plugin function onEndShowStyles($action) { - $action->style('.bookmark_tags li { display: inline; }'); - $action->style('.bookmark_mentions li { display: inline; }'); + $action->cssLink('plugins/Bookmark/bookmark.css'); return true; } diff --git a/plugins/Bookmark/bookmark.css b/plugins/Bookmark/bookmark.css new file mode 100644 index 0000000000..ed9d0e07ff --- /dev/null +++ b/plugins/Bookmark/bookmark.css @@ -0,0 +1,3 @@ +.bookmark_tags li { display: inline; } +.bookmark_mentions li { display: inline; } + From ae64963d712614a8999997e6c011b12ac05e7165 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 22 Dec 2010 15:24:13 -0800 Subject: [PATCH 32/74] Reformat bookmark output --- plugins/Bookmark/BookmarkPlugin.php | 113 +++++++++++++++++++--------- plugins/Bookmark/bookmark.css | 2 +- 2 files changed, 79 insertions(+), 36 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 83d9e131d3..080daac59b 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -175,48 +175,91 @@ class BookmarkPlugin extends Plugin $nli->notice->id); if (!empty($nb)) { - $att = $nli->notice->attachments(); - $nli->out->elementStart('h3'); - $nli->out->element('a', - array('href' => $att[0]->url), - $nb->title); - $nli->out->elementEnd('h3'); - $nli->out->element('p', - array('class' => 'bookmark_description'), - $nb->description); - $nli->out->elementStart('p'); - $nli->out->element('a', array('href' => $nli->profile->profileurl, - 'class' => 'bookmark_author', - 'title' => $nli->profile->getBestName()), - $nli->profile->getBestName()); - $nli->out->elementEnd('p'); - $tags = $nli->notice->getTags(); - $nli->out->elementStart('ul', array('class' => 'bookmark_tags')); - foreach ($tags as $tag) { - $nli->out->elementStart('li'); - $nli->out->element('a', - array('rel' => 'tag', - 'href' => Notice_tag::url($tag)), - $tag); - $nli->out->elementEnd('li'); - $nli->out->text(' '); + + $out = $nli->out; + $notice = $nli->notice; + $profile = $nli->profile; + + $atts = $notice->attachments(); + + if (count($atts) < 1) { + // Something wrong; let default code deal with it. + return true; } - $nli->out->elementEnd('ul'); + + $att = $atts[0]; + + $out->elementStart('h3'); + $out->element('a', + array('href' => $att->url), + $nb->title); + $out->elementEnd('h3'); + + $out->elementStart('ul', array('class' => 'bookmark_tags')); + + // Replies look like "for:" tags + $replies = $nli->notice->getReplies(); + if (!empty($replies)) { - $nli->out->elementStart('ul', array('class' => 'bookmark_mentions')); foreach ($replies as $reply) { $other = Profile::staticGet('id', $reply); - $nli->out->elementStart('li'); - $nli->out->element('a', array('rel' => 'tag', - 'href' => $other->profileurl, - 'title' => $other->getBestName()), - sprintf('for:%s', $other->nickname)); - $nli->out->elementEnd('li'); - $nli->out->text(' '); + $out->elementStart('li'); + $out->element('a', array('rel' => 'tag', + 'href' => $other->profileurl, + 'title' => $other->getBestName()), + sprintf('for:%s', $other->nickname)); + $out->elementEnd('li'); + $out->text(' '); } - $nli->out->elementEnd('ul'); } + + $tags = $nli->notice->getTags(); + + foreach ($tags as $tag) { + $out->elementStart('li'); + $out->element('a', + array('rel' => 'tag', + 'href' => Notice_tag::url($tag)), + $tag); + $out->elementEnd('li'); + $out->text(' '); + } + + $out->elementEnd('ul'); + + $out->element('p', + array('class' => 'bookmark_description'), + $nb->description); + + $nli->showNoticeAttachments(); + + $out->elementStart('p', array('style' => 'float: left')); + + $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); + + $out->element('img', array('src' => ($avatar) ? + $avatar->displayUrl() : + Avatar::defaultImage(AVATAR_MINI_SIZE), + 'class' => 'avatar photo bookmark_avatar', + 'width' => AVATAR_MINI_SIZE, + 'height' => AVATAR_MINI_SIZE, + 'alt' => $profile->getBestName())); + $out->raw(' '); + $out->element('a', array('href' => $profile->profileurl, + 'title' => $profile->getBestName()), + $profile->nickname); + + $nli->showNoticeLink(); + $nli->showNoticeSource(); + $nli->showNoticeLocation(); + $nli->showContext(); + $nli->showRepeat(); + + $out->elementEnd('p'); + + $nli->showNoticeOptions(); + return false; } return true; diff --git a/plugins/Bookmark/bookmark.css b/plugins/Bookmark/bookmark.css index ed9d0e07ff..27d716da7f 100644 --- a/plugins/Bookmark/bookmark.css +++ b/plugins/Bookmark/bookmark.css @@ -1,3 +1,3 @@ .bookmark_tags li { display: inline; } .bookmark_mentions li { display: inline; } - +.bookmark_avatar { float: left } From 67bde86f7c434eee8d5d9d05c651bcb2260927f8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 23 Dec 2010 09:42:42 -0800 Subject: [PATCH 33/74] accept bookmarks over PuSH --- plugins/Bookmark/BookmarkPlugin.php | 105 ++++++++++++++++++++ plugins/Bookmark/Notice_bookmark.php | 3 +- plugins/OStatus/classes/Ostatus_profile.php | 75 ++++++++------ 3 files changed, 152 insertions(+), 31 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 080daac59b..e18ea25eaa 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -276,11 +276,17 @@ class BookmarkPlugin extends Plugin function onStartActivityObjectFromNotice($notice, &$object) { + common_log(LOG_INFO, + "Checking {$notice->uri} to see if it's a bookmark."); + $nb = Notice_bookmark::staticGet('notice_id', $notice->id); if (!empty($nb)) { + common_log(LOG_INFO, + "Formatting notice {$notice->uri} as a bookmark."); + $object->id = $notice->uri; $object->type = ActivityObject::BOOKMARK; $object->title = $nb->title; @@ -387,5 +393,104 @@ class BookmarkPlugin extends Plugin return true; } + + /** + * Handle a posted bookmark from PuSH + * + * @param Activity $activity activity to handle + * @param Ostatus_profile $oprofile Profile for the feed + * + * @return boolean hook value + */ + + function onStartHandleFeedEntryWithProfile($activity, $oprofile) { + + common_log(LOG_INFO, "BookmarkPlugin called for new feed entry."); + + if ($activity->verb == ActivityVerb::POST && + $activity->objects[0]->type == ActivityObject::BOOKMARK) { + + common_log(LOG_INFO, "Importing activity {$activity->id} as a bookmark."); + + $author = $oprofile->checkAuthorship($activity); + + if (empty($author)) { + throw new ClientException(_('Can\'t get author for activity.')); + } + + self::_postRemoteBookmark($author, + $activity); + + return false; + } + + return true; + } + + static private function _postRemoteBookmark(Ostatus_profile $author, Activity $activity) + { + $bookmark = $activity->objects[0]; + + $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related'); + + if (count($relLinkEls) < 1) { + throw new ClientException(_('Expected exactly 1 link rel=related in a Bookmark.')); + } + + if (count($relLinkEls) > 1) { + common_log(LOG_WARNING, "Got too many link rel=related in a Bookmark."); + } + + $linkEl = $relLinkEls[0]; + + $url = $linkEl->getAttribute('href'); + + $tags = array(); + + foreach ($activity->categories as $category) { + $tags[] = common_canonical_tag($category->term); + } + + $options = array('uri' => $bookmark->id, + 'url' => $bookmark->link, + 'created' => common_sql_time($activity->time), + 'is_local' => Notice::REMOTE_OMB, + 'source' => 'ostatus'); + + // Fill in location if available + + $location = $activity->context->location; + + if ($location) { + $options['lat'] = $location->lat; + $options['lon'] = $location->lon; + if ($location->location_id) { + $options['location_ns'] = $location->location_ns; + $options['location_id'] = $location->location_id; + } + } + + $replies = $activity->context->attention; + $options['groups'] = $author->filterReplies($author, $replies); + $options['replies'] = $replies; + + // Maintain direct reply associations + // @fixme what about conversation ID? + + if (!empty($activity->context->replyToID)) { + $orig = Notice::staticGet('uri', + $activity->context->replyToID); + if (!empty($orig)) { + $options['reply_to'] = $orig->id; + } + } + + Notice_bookmark::saveNew($author->localProfile(), + $bookmark->title, + $url, + $tags, + $bookmark->summary, + $options); + } } diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Notice_bookmark.php index 38e2890abe..3a7d0ed742 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Notice_bookmark.php @@ -238,7 +238,8 @@ class Notice_bookmark extends Memcached_DataObject $saved = Notice::saveNew($profile->id, $content, - 'web', + array_key_exists('source', $options) ? + $options['source'] : 'web', $options); if (!empty($saved)) { diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 77cf57a670..9c0f014fc6 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -457,7 +457,8 @@ class Ostatus_profile extends Memcached_DataObject { $activity = new Activity($entry, $feed); - if (Event::handle('StartHandleFeedEntry', array($activity))) { + if (Event::handle('StartHandleFeedEntryWithProfile', array($activity, $this)) && + Event::handle('StartHandleFeedEntry', array($activity))) { // @todo process all activity objects switch ($activity->objects[0]->type) { @@ -479,6 +480,7 @@ class Ostatus_profile extends Memcached_DataObject } Event::handle('EndHandleFeedEntry', array($activity)); + Event::handle('EndHandleFeedEntryWithProfile', array($activity, $this)); } } @@ -491,36 +493,10 @@ class Ostatus_profile extends Memcached_DataObject */ public function processPost($activity, $method) { - if ($this->isGroup()) { - // A group feed will contain posts from multiple authors. - // @fixme validate these profiles in some way! - $oprofile = self::ensureActorProfile($activity); - if ($oprofile->isGroup()) { - // Groups can't post notices in StatusNet. - common_log(LOG_WARNING, "OStatus: skipping post with group listed as author: $oprofile->uri in feed from $this->uri"); - return false; - } - } else { - $actor = $activity->actor; + $oprofile = $this->checkAuthorship($activity); - if (empty($actor)) { - // OK here! assume the default - } else if ($actor->id == $this->uri || $actor->link == $this->uri) { - $this->updateFromActivityObject($actor); - } else if ($actor->id) { - // We have an ActivityStreams actor with an explicit ID that doesn't match the feed owner. - // This isn't what we expect from mainline OStatus person feeds! - // Group feeds go down another path, with different validation... - // Most likely this is a plain ol' blog feed of some kind which - // doesn't match our expectations. We'll take the entry, but ignore - // the info. - common_log(LOG_WARNING, "Got an actor '{$actor->title}' ({$actor->id}) on single-user feed for {$this->uri}"); - } else { - // Plain without ActivityStreams actor info. - // We'll just ignore this info for now and save the update under the feed's identity. - } - - $oprofile = $this; + if (empty($oprofile)) { + return false; } // It's not always an ActivityObject::NOTE, but... let's just say it is. @@ -1810,6 +1786,45 @@ class Ostatus_profile extends Memcached_DataObject } return $oprofile; } + + function checkAuthorship($activity) + { + if ($this->isGroup()) { + // A group feed will contain posts from multiple authors. + // @fixme validate these profiles in some way! + $oprofile = self::ensureActorProfile($activity); + if ($oprofile->isGroup()) { + // Groups can't post notices in StatusNet. + common_log(LOG_WARNING, + "OStatus: skipping post with group listed as author: ". + "$oprofile->uri in feed from $this->uri"); + return false; + } + } else { + $actor = $activity->actor; + + if (empty($actor)) { + // OK here! assume the default + } else if ($actor->id == $this->uri || $actor->link == $this->uri) { + $this->updateFromActivityObject($actor); + } else if ($actor->id) { + // We have an ActivityStreams actor with an explicit ID that doesn't match the feed owner. + // This isn't what we expect from mainline OStatus person feeds! + // Group feeds go down another path, with different validation... + // Most likely this is a plain ol' blog feed of some kind which + // doesn't match our expectations. We'll take the entry, but ignore + // the info. + common_log(LOG_WARNING, "Got an actor '{$actor->title}' ({$actor->id}) on single-user feed for {$this->uri}"); + } else { + // Plain without ActivityStreams actor info. + // We'll just ignore this info for now and save the update under the feed's identity. + } + + $oprofile = $this; + } + + return $oprofile; + } } /** From 4048d1ec3dfe68be8d8e31b859b2f219adc34397 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 24 Dec 2010 20:34:15 -0800 Subject: [PATCH 34/74] Radical differences in Bookmark storage Had some problems with PuSH and Salmon use of Bookmarks; they were being required to generate Atom versions of the bookmark _before_ the bookmark was saved. So, I reversed the order of how things are saved, and associate notices and bookmarks by URI rather than notice_id. --- .../{Notice_bookmark.php => Bookmark.php} | 128 +++++++++++++----- plugins/Bookmark/BookmarkPlugin.php | 55 ++++++-- .../Bookmark/deliciousbookmarkimporter.php | 2 +- plugins/Bookmark/newbookmark.php | 2 +- plugins/Bookmark/showbookmark.php | 116 ++++++++++++++++ 5 files changed, 254 insertions(+), 49 deletions(-) rename plugins/Bookmark/{Notice_bookmark.php => Bookmark.php} (66%) create mode 100644 plugins/Bookmark/showbookmark.php diff --git a/plugins/Bookmark/Notice_bookmark.php b/plugins/Bookmark/Bookmark.php similarity index 66% rename from plugins/Bookmark/Notice_bookmark.php rename to plugins/Bookmark/Bookmark.php index 3a7d0ed742..aa9e9af43b 100644 --- a/plugins/Bookmark/Notice_bookmark.php +++ b/plugins/Bookmark/Bookmark.php @@ -43,12 +43,16 @@ if (!defined('STATUSNET')) { * @see DB_DataObject */ -class Notice_bookmark extends Memcached_DataObject +class Bookmark extends Memcached_DataObject { - public $__table = 'notice_bookmark'; // table name - public $notice_id; // int(4) primary_key not_null - public $title; // varchar(255) - public $description; // text + public $__table = 'bookmark'; // table name + public $profile_id; // int(4) primary_key not_null + public $url; // varchar(255) primary_key not_null + public $title; // varchar(255) + public $description; // text + public $uri; // varchar(255) + public $url_crc32; // int(4) not_null + public $created; // datetime /** * Get an instance by key @@ -64,7 +68,7 @@ class Notice_bookmark extends Memcached_DataObject function staticGet($k, $v=null) { - return Memcached_DataObject::staticGet('Notice_bookmark', $k, $v); + return Memcached_DataObject::staticGet('Bookmark', $k, $v); } /** @@ -78,9 +82,13 @@ class Notice_bookmark extends Memcached_DataObject function table() { - return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + return array('profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'url' => DB_DATAOBJECT_STR, 'title' => DB_DATAOBJECT_STR, - 'description' => DB_DATAOBJECT_STR); + 'description' => DB_DATAOBJECT_STR, + 'uri' => DB_DATAOBJECT_STR, + 'url_crc32' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); } /** @@ -102,7 +110,9 @@ class Notice_bookmark extends Memcached_DataObject function keyTypes() { - return array('notice_id' => 'K'); + return array('profile_id' => 'K', + 'url' => 'K', + 'uri' => 'U'); } /** @@ -116,37 +126,60 @@ class Notice_bookmark extends Memcached_DataObject return array(false, false, false); } + /** + * Get a bookmark based on a notice + * + * @param Notice $notice Notice to check for + * + * @return Bookmark found bookmark or null + */ + + function getByNotice($notice) + { + return self::staticGet('uri', $notice->uri); + } + /** * Get the bookmark that a user made for an URL * * @param Profile $profile Profile to check for * @param string $url URL to check for * - * @return Notice_bookmark bookmark found or null + * @return Bookmark bookmark found or null */ static function getByURL($profile, $url) { - $file = File::staticGet('url', $url); - if (!empty($file)) { - $f2p = new File_to_post(); + return self::pkeyGet(array('profile_id' => $profile->id, + 'url' => $url)); + return null; + } - $f2p->file_id = $file->id; - if ($f2p->find()) { - while ($f2p->fetch()) { - $n = Notice::staticGet('id', $f2p->post_id); - if (!empty($n)) { - if ($n->profile_id == $profile->id) { - $nb = Notice_bookmark::staticGet('notice_id', $n->id); - if (!empty($nb)) { - return $nb; - } - } - } - } + /** + * Get the bookmark that a user made for an URL + * + * @param Profile $profile Profile to check for + * @param integer $crc32 CRC-32 of URL to check for + * + * @return array Bookmark objects found (usually 1 or 0) + */ + + static function getByCRC32($profile, $crc32) + { + $bookmarks = array(); + + $nb = new Bookmark(); + + $nb->profile_id = $profile->id; + $nb->url_crc32 = $crc32; + + if ($nb->find()) { + while ($nb->fetch()) { + $bookmarks[] = clone($nb); } } - return null; + + return $bookmarks; } /** @@ -179,6 +212,32 @@ class Notice_bookmark extends Memcached_DataObject $rawtags = preg_split('/[\s,]+/', $rawtags); } + $nb = new Bookmark(); + + $nb->profile_id = $profile->id; + $nb->url = $url; + $nb->title = $title; + $nb->description = $description; + $nb->url_crc32 = crc32($nb->url); + $nb->created = common_sql_now(); + + if (array_key_exists('uri', $options)) { + $nb->uri = $options['uri']; + } else { + $dt = new DateTime($nb->created); + // I posit that it's sufficiently impossible + // for the same user to generate two CRC-32-clashing + // URLs in the same second that this is a safe unique identifier. + // If you find a real counterexample, contact me at acct:evan@status.net + // and I will publicly apologize for my hubris. + $nb->uri = common_local_url('showbookmark', + array('user' => $profile->id, + 'created' => $dt->format(DateTime::W3C), + 'crc32' => sprintf('%08x', $nb->url_crc32))); + } + + $nb->insert(); + $tags = array(); $replies = array(); @@ -197,6 +256,8 @@ class Notice_bookmark extends Memcached_DataObject } } + // + $hashtags = array(); $taglinks = array(); @@ -236,21 +297,16 @@ class Notice_bookmark extends Memcached_DataObject 'tags' => $tags, 'replies' => $replies)); + if (!array_key_exists('uri', $options)) { + $options['uri'] = $nb->uri; + } + $saved = Notice::saveNew($profile->id, $content, array_key_exists('source', $options) ? $options['source'] : 'web', $options); - if (!empty($saved)) { - $nb = new Notice_bookmark(); - - $nb->notice_id = $saved->id; - $nb->title = $title; - $nb->description = $description; - $nb->insert(); - } - return $saved; } } diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index e18ea25eaa..01dd6f1eaa 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -63,23 +63,52 @@ class BookmarkPlugin extends Plugin // For storing user-submitted flags on profiles - $schema->ensureTable('notice_bookmark', - array(new ColumnDef('notice_id', + $schema->ensureTable('bookmark', + array(new ColumnDef('profile_id', 'integer', null, false, 'PRI'), + new ColumnDef('url', + 'varchar', + 255, + false, + 'PRI'), new ColumnDef('title', 'varchar', 255), new ColumnDef('description', - 'text'))); + 'text'), + new ColumnDef('uri', + 'varchar', + 255, + false, + 'UNI'), + new ColumnDef('url_crc32', + 'integer', + null, + false, + 'MUL'), + new ColumnDef('created', + 'datetime', + null, + false, + 'MUL'))); + + try { + $schema->createIndex('bookmark', + array('profile_id', + 'url_crc32'), + 'bookmark_profile_url_idx'); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + } return true; } /** - * When a notice is deleted, delete the related Notice_bookmark + * When a notice is deleted, delete the related Bookmark * * @param Notice $notice Notice being deleted * @@ -88,7 +117,7 @@ class BookmarkPlugin extends Plugin function onNoticeDeleteRelated($notice) { - $nb = Notice_bookmark::staticGet('notice_id', $notice->id); + $nb = Bookmark::getByNotice($notice); if (!empty($nb)) { $nb->delete(); @@ -129,7 +158,7 @@ class BookmarkPlugin extends Plugin case 'BookmarkpopupAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; - case 'Notice_bookmark': + case 'Bookmark': include_once $dir.'/'.$cls.'.php'; return false; case 'BookmarkForm': @@ -158,6 +187,12 @@ class BookmarkPlugin extends Plugin $m->connect('main/bookmark/popup', array('action' => 'bookmarkpopup')); + $m->connect('bookmark/:user/:created/:crc32', + array('action' => 'showbookmark'), + array('user' => '[0-9]+', + 'created' => '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z', + 'crc32' => '[0-9A-F]{8}')); + return true; } @@ -171,8 +206,7 @@ class BookmarkPlugin extends Plugin function onStartShowNoticeItem($nli) { - $nb = Notice_bookmark::staticGet('notice_id', - $nli->notice->id); + $nb = Bookmark::getByNotice($nli->notice); if (!empty($nb)) { @@ -279,8 +313,7 @@ class BookmarkPlugin extends Plugin common_log(LOG_INFO, "Checking {$notice->uri} to see if it's a bookmark."); - $nb = Notice_bookmark::staticGet('notice_id', - $notice->id); + $nb = Bookmark::getByNotice($notice); if (!empty($nb)) { @@ -485,7 +518,7 @@ class BookmarkPlugin extends Plugin } } - Notice_bookmark::saveNew($author->localProfile(), + Bookmark::saveNew($author->localProfile(), $bookmark->title, $url, $tags, diff --git a/plugins/Bookmark/deliciousbookmarkimporter.php b/plugins/Bookmark/deliciousbookmarkimporter.php index 22ad45882e..061572d95a 100644 --- a/plugins/Bookmark/deliciousbookmarkimporter.php +++ b/plugins/Bookmark/deliciousbookmarkimporter.php @@ -96,7 +96,7 @@ class DeliciousBookmarkImporter extends QueueHandler $addDate = $a->getAttribute('add_date'); $created = common_sql_date(intval($addDate)); - $saved = Notice_bookmark::saveNew($user->getProfile(), + $saved = Bookmark::saveNew($user->getProfile(), $title, $url, $tags, diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php index 63944c8176..a0cf3fffb2 100644 --- a/plugins/Bookmark/newbookmark.php +++ b/plugins/Bookmark/newbookmark.php @@ -135,7 +135,7 @@ class NewbookmarkAction extends Action } - $saved = Notice_bookmark::saveNew($this->user->getProfile(), + $saved = Bookmark::saveNew($this->user->getProfile(), $this->title, $this->url, $this->tags, diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php new file mode 100644 index 0000000000..f6213ef5db --- /dev/null +++ b/plugins/Bookmark/showbookmark.php @@ -0,0 +1,116 @@ +. + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Show a single bookmark, with associated information + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class ShowbookmarkAction extends ShownoticeAction +{ + protected $bookmark = null; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + OwnerDesignAction::prepare($argarray); + + $this->user = User::staticGet('id', $this->trimmed('user')); + + if (empty($this->user)) { + throw new ClientException(_('No such user.'), 404); + } + + $this->profile = $this->user->getProfile(); + + if (empty($this->profile)) { + throw new ServerException(_('User without a profile.')); + } + + $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); + + $crc32 = pack("H*", $this->trimmed('crc32')); + + if (empty($crc32)) { + throw new ClientException(_('No such URL.'), 404); + } + + $dt = DateTime::createFromFormat(DateTime::W3C, + $this->trimmed('created'), + new DateTimeZone('UTC')); + + if (empty($dt)) { + throw new ClientException(_('No such create date.'), 404); + } + + $bookmarks = Bookmark::getByCRC32($this->profile, + $this->crc32); + + foreach ($bookmarks as $bookmark) { + $bdt = new DateTime($bookmark->created); + if ($bdt->getTimestamp() == $dt->getTimestamp()) { + $this->bookmark = $bookmark; + break; + } + } + + if (empty($this->bookmark)) { + throw new ClientException(_('No such bookmark.'), 404); + } + + $this->notice = Notice::staticGet('uri', $this->bookmark->uri); + + if (empty($this->notice)) { + // Did we used to have it, and it got deleted? + throw new ClientException(_('No such bookmark.'), 404); + } + + return true; + } +} From 763a5f182d1725684ee76aca66cfc22f60d0b4e8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 26 Dec 2010 21:08:20 -0800 Subject: [PATCH 35/74] Memcache_DataObject checks for PEAR::isError() on results --- classes/Memcached_DataObject.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index eb5d2627f2..d50b4071d1 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -74,7 +74,7 @@ class Memcached_DataObject extends Safe_DataObject return $i; } else { $i = DB_DataObject::factory($cls); - if (empty($i)) { + if (empty($i) || PEAR::isError($i)) { return false; } foreach ($kv as $k => $v) { From ea1676cb0acfe70858f041ac133d3240f2827597 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 26 Dec 2010 21:10:23 -0800 Subject: [PATCH 36/74] UTC only and tighter date format for showbookmark --- plugins/Bookmark/showbookmark.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php index f6213ef5db..a547de09f0 100644 --- a/plugins/Bookmark/showbookmark.php +++ b/plugins/Bookmark/showbookmark.php @@ -75,13 +75,13 @@ class ShowbookmarkAction extends ShownoticeAction $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); - $crc32 = pack("H*", $this->trimmed('crc32')); + sscanf($this->trimmed('crc32'), '%08x', $crc32); if (empty($crc32)) { throw new ClientException(_('No such URL.'), 404); } - $dt = DateTime::createFromFormat(DateTime::W3C, + $dt = DateTime::createFromFormat('YmdHis', $this->trimmed('created'), new DateTimeZone('UTC')); @@ -90,10 +90,10 @@ class ShowbookmarkAction extends ShownoticeAction } $bookmarks = Bookmark::getByCRC32($this->profile, - $this->crc32); + $crc32); foreach ($bookmarks as $bookmark) { - $bdt = new DateTime($bookmark->created); + $bdt = new DateTime($bookmark->created, new DateTimeZone('UTC')); if ($bdt->getTimestamp() == $dt->getTimestamp()) { $this->bookmark = $bookmark; break; From ca28140107e934dbb15803ed417bfc178ac6126e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 26 Dec 2010 21:10:54 -0800 Subject: [PATCH 37/74] remove debugging outputter from delicious backup importer --- plugins/Bookmark/deliciousbackupimporter.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php index 01b996bbe5..1b55115d6d 100644 --- a/plugins/Bookmark/deliciousbackupimporter.php +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -97,7 +97,6 @@ class DeliciousBackupImporter extends QueueHandler if ($child->nodeType != XML_ELEMENT_NODE) { continue; } - common_log(LOG_INFO, $child->tagName); switch (strtolower($child->tagName)) { case 'dt': if (!empty($dt)) { From 7d56f1cd193eab9ce85b4f62059a7a0975bdc1f9 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 26 Dec 2010 21:11:27 -0800 Subject: [PATCH 38/74] Some fixes from debugging of bookmark plugin URI foramt Tightened up the URI format, fixed some auto-loading issues, and forced the url_crc32 column to be unsigned. --- plugins/Bookmark/Bookmark.php | 37 +++++++++++++++++++++++++---- plugins/Bookmark/BookmarkPlugin.php | 7 +++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/plugins/Bookmark/Bookmark.php b/plugins/Bookmark/Bookmark.php index aa9e9af43b..c3394c542b 100644 --- a/plugins/Bookmark/Bookmark.php +++ b/plugins/Bookmark/Bookmark.php @@ -71,6 +71,24 @@ class Bookmark extends Memcached_DataObject return Memcached_DataObject::staticGet('Bookmark', $k, $v); } + /** + * Get an instance by compound key + * + * This is a utility method to get a single instance with a given set of + * key-value pairs. Usually used for the primary key for a compound key; thus + * the name. + * + * @param array $kv array of key-value mappings + * + * @return Bookmark object found, or null for no hits + * + */ + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Bookmark', $kv); + } + /** * return table definition for DB_DataObject * @@ -219,21 +237,32 @@ class Bookmark extends Memcached_DataObject $nb->title = $title; $nb->description = $description; $nb->url_crc32 = crc32($nb->url); - $nb->created = common_sql_now(); + + if (array_key_exists('created', $options)) { + $nb->created = $options['created']; + } else { + $nb->created = common_sql_now(); + } if (array_key_exists('uri', $options)) { $nb->uri = $options['uri']; } else { - $dt = new DateTime($nb->created); + $dt = new DateTime($nb->created, new DateTimeZone('UTC')); + // I posit that it's sufficiently impossible // for the same user to generate two CRC-32-clashing // URLs in the same second that this is a safe unique identifier. // If you find a real counterexample, contact me at acct:evan@status.net // and I will publicly apologize for my hubris. + + $created = $dt->format('YmdHis'); + + $crc32 = sprintf('%08x', $nb->url_crc32); + $nb->uri = common_local_url('showbookmark', array('user' => $profile->id, - 'created' => $dt->format(DateTime::W3C), - 'crc32' => sprintf('%08x', $nb->url_crc32))); + 'created' => $created, + 'crc32' => $crc32)); } $nb->insert(); diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 01dd6f1eaa..53121276b1 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -85,7 +85,7 @@ class BookmarkPlugin extends Plugin false, 'UNI'), new ColumnDef('url_crc32', - 'integer', + 'integer unsigned', null, false, 'MUL'), @@ -154,6 +154,7 @@ class BookmarkPlugin extends Plugin switch ($cls) { + case 'ShowbookmarkAction': case 'NewbookmarkAction': case 'BookmarkpopupAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; @@ -190,8 +191,8 @@ class BookmarkPlugin extends Plugin $m->connect('bookmark/:user/:created/:crc32', array('action' => 'showbookmark'), array('user' => '[0-9]+', - 'created' => '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z', - 'crc32' => '[0-9A-F]{8}')); + 'created' => '[0-9]{14}', + 'crc32' => '[0-9a-f]{8}')); return true; } From bf75119b3cb6b7bd31e006e544b86a8736363990 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 26 Dec 2010 21:25:26 -0800 Subject: [PATCH 39/74] Fix bugs in BookmarkPlugin --- plugins/Bookmark/BookmarkPlugin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 53121276b1..f18c60c586 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -345,7 +345,7 @@ class BookmarkPlugin extends Plugin $attrs['title'] = $target->title; } - $object->extra[] = array('link', $attrs); + $object->extra[] = array('link', $attrs, null); // Attributes of the thumbnail, if any @@ -363,7 +363,7 @@ class BookmarkPlugin extends Plugin $tattrs['media:height'] = $thumbnail->height; } - $object->extra[] = array('link', $attrs); + $object->extra[] = array('link', $attrs, null); } return false; @@ -487,7 +487,7 @@ class BookmarkPlugin extends Plugin $options = array('uri' => $bookmark->id, 'url' => $bookmark->link, - 'created' => common_sql_time($activity->time), + 'created' => common_sql_date($activity->time), 'is_local' => Notice::REMOTE_OMB, 'source' => 'ostatus'); From 14113b267eacc09e6889e45a4c3f8b4a32926282 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 26 Dec 2010 21:38:28 -0800 Subject: [PATCH 40/74] replace call to protected Ostatus_profile method --- plugins/Bookmark/BookmarkPlugin.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index f18c60c586..99cfc1284e 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -505,8 +505,21 @@ class BookmarkPlugin extends Plugin } $replies = $activity->context->attention; - $options['groups'] = $author->filterReplies($author, $replies); - $options['replies'] = $replies; + + $options['groups'] = array(); + $options['replies'] = array(); + + foreach ($replies as $replyURI) { + $profile = Profile::fromURI($replyURI); + if (!empty($profile)) { + $options['replies'][] = $replyURI; + } else { + $group = User_group::staticGet('uri', $replyURI); + if (!empty($group)) { + $options['groups'][] = $replyURI; + } + } + } // Maintain direct reply associations // @fixme what about conversation ID? From 4777c927add9159720ab3ffce98848b346187569 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 09:14:11 -0800 Subject: [PATCH 41/74] Fix bookmark replies handling so doesn't overwrite --- plugins/Bookmark/Bookmark.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/plugins/Bookmark/Bookmark.php b/plugins/Bookmark/Bookmark.php index c3394c542b..684532dbfe 100644 --- a/plugins/Bookmark/Bookmark.php +++ b/plugins/Bookmark/Bookmark.php @@ -274,11 +274,13 @@ class Bookmark extends Memcached_DataObject foreach ($rawtags as $tag) { if (strtolower(mb_substr($tag, 0, 4)) == 'for:') { - $nickname = mb_substr($tag, 4); - $other = common_relative_profile($profile, - $nickname); - if (!empty($other)) { - $replies[] = $other->getUri(); + if (!array_key_exists('replies', $options)) { // skip if done by caller + $nickname = mb_substr($tag, 4); + $other = common_relative_profile($profile, + $nickname); + if (!empty($other)) { + $replies[] = $other->getUri(); + } } } else { $tags[] = common_canonical_tag($tag); @@ -321,10 +323,11 @@ class Bookmark extends Memcached_DataObject htmlspecialchars($description), implode(' ', $taglinks)); - $options = array_merge($options, array('urls' => array($url), - 'rendered' => $rendered, - 'tags' => $tags, - 'replies' => $replies)); + $options = array_merge(array('urls' => array($url), + 'rendered' => $rendered, + 'tags' => $tags, + 'replies' => $replies), + $options); if (!array_key_exists('uri', $options)) { $options['uri'] = $nb->uri; From b54ea6767a510f0d7c9af140bb16a0b9ecc4f0ce Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 10:51:59 -0800 Subject: [PATCH 42/74] New event for Salmon including target --- plugins/OStatus/actions/groupsalmon.php | 3 +++ plugins/OStatus/actions/usersalmon.php | 2 ++ plugins/OStatus/lib/salmonaction.php | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/OStatus/actions/groupsalmon.php b/plugins/OStatus/actions/groupsalmon.php index 3a3d63fe20..024f0cc217 100644 --- a/plugins/OStatus/actions/groupsalmon.php +++ b/plugins/OStatus/actions/groupsalmon.php @@ -47,6 +47,9 @@ class GroupsalmonAction extends SalmonAction $this->clientError(_m('No such group.')); } + + $this->target = $this->group; + $oprofile = Ostatus_profile::staticGet('group_id', $id); if ($oprofile) { // TRANS: Client error. diff --git a/plugins/OStatus/actions/usersalmon.php b/plugins/OStatus/actions/usersalmon.php index e78c653300..5355aeba03 100644 --- a/plugins/OStatus/actions/usersalmon.php +++ b/plugins/OStatus/actions/usersalmon.php @@ -43,6 +43,8 @@ class UsersalmonAction extends SalmonAction $this->clientError(_m('No such user.')); } + $this->target = $this->user; + return true; } diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php index 41bdb48928..8bfd7c8261 100644 --- a/plugins/OStatus/lib/salmonaction.php +++ b/plugins/OStatus/lib/salmonaction.php @@ -30,6 +30,7 @@ class SalmonAction extends Action { var $xml = null; var $activity = null; + var $target = null; function prepare($args) { @@ -82,7 +83,8 @@ class SalmonAction extends Action StatusNet::setApi(true); // Send smaller error pages common_log(LOG_DEBUG, "Got a " . $this->activity->verb); - if (Event::handle('StartHandleSalmon', array($this->activity))) { + if (Event::handle('StartHandleSalmonTarget', array($this->activity, $this->target)) && + Event::handle('StartHandleSalmon', array($this->activity))) { switch ($this->activity->verb) { case ActivityVerb::POST: @@ -118,6 +120,7 @@ class SalmonAction extends Action throw new ClientException(_m("Unrecognized activity type.")); } Event::handle('EndHandleSalmon', array($this->activity)); + Event::handle('EndHandleSalmonTarget', array($this->activity, $this->target)); } } From 29103f5d0ed5b5319086d6ee701947a7de22eec0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 11:29:16 -0800 Subject: [PATCH 43/74] send and receive bookmarks by Salmon --- plugins/Bookmark/BookmarkPlugin.php | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 99cfc1284e..8fa73abc61 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -461,6 +461,52 @@ class BookmarkPlugin extends Plugin return true; } + /** + * Handle a posted bookmark from Salmon + * + * @param Activity $activity activity to handle + * @param mixed $target user or group targeted + * + * @return boolean hook value + */ + + function onStartHandleSalmonTarget($activity, $target) { + + if ($activity->verb == ActivityVerb::POST && + $activity->objects[0]->type == ActivityObject::BOOKMARK) { + + $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap."); + + if ($target instanceof User_group) { + $uri = $target->getUri(); + if (!in_array($uri, $activity->context->attention)) { + throw new ClientException(_("Bookmark not posted to this group.")); + } + } else if ($target instanceof User) { + $uri = $target->uri; + $original = null; + if (!empty($activity->context->replyToID)) { + $original = Notice::staticGet('uri', $activity->context->replyToID); + } + if (!in_array($uri, $activity->context->attention) && + (empty($original) || $original->profile_id != $target->id)) { + throw new ClientException(_("Bookmark not posted to this user.")); + } + } else { + throw new ServerException(_("Don't know how to handle this kind of target.")); + } + + $author = Ostatus_profile::ensureActivityObjectProfile($activity->actor); + + self::_postRemoteBookmark($author, + $activity); + + return false; + } + + return true; + } + static private function _postRemoteBookmark(Ostatus_profile $author, Activity $activity) { $bookmark = $activity->objects[0]; From 45b2059cd86ca790213fbce6ef247f569db247ae Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 12:08:55 -0800 Subject: [PATCH 44/74] better layout for single-bookmark page --- plugins/Bookmark/BookmarkPlugin.php | 20 ++++++++++++++------ plugins/Bookmark/showbookmark.php | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 8fa73abc61..64e7bf8bb1 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -224,11 +224,16 @@ class BookmarkPlugin extends Plugin $att = $atts[0]; - $out->elementStart('h3'); - $out->element('a', - array('href' => $att->url), - $nb->title); - $out->elementEnd('h3'); + // XXX: only show the bookmark URL for non-single-page stuff + + if ($out instanceof ShowbookmarkAction) { + } else { + $out->elementStart('h3'); + $out->element('a', + array('href' => $att->url), + $nb->title); + $out->elementEnd('h3'); + } $out->elementStart('ul', array('class' => 'bookmark_tags')); @@ -267,7 +272,10 @@ class BookmarkPlugin extends Plugin array('class' => 'bookmark_description'), $nb->description); - $nli->showNoticeAttachments(); + if (common_config('attachments', 'show_thumbs')) { + $al = new InlineAttachmentList($notice, $out); + $al->show(); + } $out->elementStart('p', array('style' => 'float: left')); diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php index a547de09f0..07dec5a98c 100644 --- a/plugins/Bookmark/showbookmark.php +++ b/plugins/Bookmark/showbookmark.php @@ -113,4 +113,20 @@ class ShowbookmarkAction extends ShownoticeAction return true; } + + function title() + { + return sprintf(_('%s\'s bookmark for "%s"'), + $this->user->nickname, + $this->bookmark->title); + } + + function showPageTitle() + { + $this->elementStart('h1'); + $this->element('a', + array('href' => $this->bookmark->url), + $this->bookmark->title); + $this->elementEnd('h1'); + } } From 141b4b67b333c37cf96dcad03fa8c8e9fca768b9 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 12:57:03 -0800 Subject: [PATCH 45/74] Better UI for bookmark popup --- plugins/Bookmark/bookmarkpopup.js | 23 +++++++++++++++++++++++ plugins/Bookmark/bookmarkpopup.php | 6 ++++++ 2 files changed, 29 insertions(+) create mode 100644 plugins/Bookmark/bookmarkpopup.js diff --git a/plugins/Bookmark/bookmarkpopup.js b/plugins/Bookmark/bookmarkpopup.js new file mode 100644 index 0000000000..29f314ed06 --- /dev/null +++ b/plugins/Bookmark/bookmarkpopup.js @@ -0,0 +1,23 @@ +$(document).ready( + function() { + var form = $('#form_new_bookmark'); + form.append(''); + form.ajaxForm({dataType: 'xml', + timeout: '60000', + beforeSend: function(formData) { + form.addClass('processing'); + form.find('#submit').addClass('disabled'); + }, + error: function (xhr, textStatus, errorThrown) { + form.removeClass('processing'); + form.find('#submit').removeClass('disabled'); + self.close(); + }, + success: function(data, textStatus) { + form.removeClass('processing'); + form.find('#submit').removeClass('disabled'); + self.close(); + }}); + + } +); \ No newline at end of file diff --git a/plugins/Bookmark/bookmarkpopup.php b/plugins/Bookmark/bookmarkpopup.php index 2e6d457a83..24ed79612b 100644 --- a/plugins/Bookmark/bookmarkpopup.php +++ b/plugins/Bookmark/bookmarkpopup.php @@ -103,4 +103,10 @@ class BookmarkpopupAction extends NewbookmarkAction function showFooter() { } + + function showScripts() + { + parent::showScripts(); + $this->script(common_path('plugins/Bookmark/bookmarkpopup.js')); + } } From a85bbd908821fb09de6a291a167ab19902cb2ed2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 20:49:08 -0800 Subject: [PATCH 46/74] Add a user parameter to atom pub api events --- EVENTS.txt | 2 ++ actions/apitimelineuser.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 65265cdf0f..521f568efe 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -969,9 +969,11 @@ EndRevokeRole: when a role has been revoked StartAtomPubNewActivity: When a new activity comes in through Atom Pub API - &$activity: received activity +- $user: user publishing the entry EndAtomPubNewActivity: When a new activity comes in through Atom Pub API - $activity: received activity +- $user: user publishing the entry - $notice: notice that was created StartXrdActionAliases: About to set aliases for the XRD object for a user diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index 42988a00f6..1573f74897 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -324,7 +324,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction $activity = new Activity($dom->documentElement); - if (Event::handle('StartAtomPubNewActivity', array(&$activity))) { + if (Event::handle('StartAtomPubNewActivity', array(&$activity, $this->user))) { if ($activity->verb != ActivityVerb::POST) { // TRANS: Client error displayed when not using the POST verb. @@ -347,7 +347,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction $saved = $this->postNote($activity); - Event::handle('EndAtomPubNewActivity', array($activity, $saved)); + Event::handle('EndAtomPubNewActivity', array($activity, $this->user, $saved)); } if (!empty($saved)) { From 7b9ea622599f5af9508484a9b5c5b232b84c24ef Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:09:29 -0800 Subject: [PATCH 47/74] Make AtomPub work for bookmarks --- plugins/Bookmark/BookmarkPlugin.php | 49 ++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 64e7bf8bb1..470ddaa0ce 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -449,8 +449,7 @@ class BookmarkPlugin extends Plugin common_log(LOG_INFO, "BookmarkPlugin called for new feed entry."); - if ($activity->verb == ActivityVerb::POST && - $activity->objects[0]->type == ActivityObject::BOOKMARK) { + if (self::_isPostBookmark($activity)) { common_log(LOG_INFO, "Importing activity {$activity->id} as a bookmark."); @@ -480,8 +479,7 @@ class BookmarkPlugin extends Plugin function onStartHandleSalmonTarget($activity, $target) { - if ($activity->verb == ActivityVerb::POST && - $activity->objects[0]->type == ActivityObject::BOOKMARK) { + if (self::_isPostBookmark($activity)) { $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap."); @@ -515,10 +513,33 @@ class BookmarkPlugin extends Plugin return true; } + function onStartAtomPubNewActivity(&$activity, $user) + { + if (self::_isPostBookmark($activity)) { + $options = array('source' => 'atompub'); + self::_postBookmark($user->getProfile(), $activity, $options); + return false; + } + + return true; + } + static private function _postRemoteBookmark(Ostatus_profile $author, Activity $activity) { $bookmark = $activity->objects[0]; + $options = array('uri' => $bookmark->id, + 'url' => $bookmark->link, + 'is_local' => Notice::REMOTE_OMB, + 'source' => 'ostatus'); + + return self::_postBookmark($author->localProfile(), $activity, $options); + } + + static private function _postBookmark(Profile $profile, Activity $activity, $options=array()) + { + $bookmark = $activity->objects[0]; + $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related'); if (count($relLinkEls) < 1) { @@ -539,11 +560,9 @@ class BookmarkPlugin extends Plugin $tags[] = common_canonical_tag($category->term); } - $options = array('uri' => $bookmark->id, - 'url' => $bookmark->link, - 'created' => common_sql_date($activity->time), - 'is_local' => Notice::REMOTE_OMB, - 'source' => 'ostatus'); + if (!empty($activity->time)) { + $options['created'] = common_sql_date($activity->time); + } // Fill in location if available @@ -564,8 +583,8 @@ class BookmarkPlugin extends Plugin $options['replies'] = array(); foreach ($replies as $replyURI) { - $profile = Profile::fromURI($replyURI); - if (!empty($profile)) { + $other = Profile::fromURI($replyURI); + if (!empty($other)) { $options['replies'][] = $replyURI; } else { $group = User_group::staticGet('uri', $replyURI); @@ -586,12 +605,18 @@ class BookmarkPlugin extends Plugin } } - Bookmark::saveNew($author->localProfile(), + return Bookmark::saveNew($profile, $bookmark->title, $url, $tags, $bookmark->summary, $options); } + + static private function _isPostBookmark($activity) + { + return ($activity->verb == ActivityVerb::POST && + $activity->objects[0]->type == ActivityObject::BOOKMARK); + } } From 0bcc3ee0054f3b1b77038d5627b55ecdd2d4c3e8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:11:30 -0800 Subject: [PATCH 48/74] include saved notice in atompub events --- actions/apitimelineuser.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index 1573f74897..5809df3b5e 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -324,7 +324,9 @@ class ApiTimelineUserAction extends ApiBareAuthAction $activity = new Activity($dom->documentElement); - if (Event::handle('StartAtomPubNewActivity', array(&$activity, $this->user))) { + $saved = null; + + if (Event::handle('StartAtomPubNewActivity', array(&$activity, $this->user, &$saved))) { if ($activity->verb != ActivityVerb::POST) { // TRANS: Client error displayed when not using the POST verb. From 75e671774db5bd118e17a48a7c8b63168626d788 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:13:17 -0800 Subject: [PATCH 49/74] Documentation for change in atompub events --- EVENTS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/EVENTS.txt b/EVENTS.txt index 521f568efe..74cf720bc0 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -970,6 +970,7 @@ EndRevokeRole: when a role has been revoked StartAtomPubNewActivity: When a new activity comes in through Atom Pub API - &$activity: received activity - $user: user publishing the entry +- &$notice: notice created; initially null, can be set EndAtomPubNewActivity: When a new activity comes in through Atom Pub API - $activity: received activity From 4465724ed3b057057d079f4670a48f6c0910c076 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:16:34 -0800 Subject: [PATCH 50/74] return saved notice to AtomPub for Bookmarks --- plugins/Bookmark/BookmarkPlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 470ddaa0ce..fe1563f038 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -513,11 +513,11 @@ class BookmarkPlugin extends Plugin return true; } - function onStartAtomPubNewActivity(&$activity, $user) + function onStartAtomPubNewActivity(&$activity, $user, &$notice) { if (self::_isPostBookmark($activity)) { $options = array('source' => 'atompub'); - self::_postBookmark($user->getProfile(), $activity, $options); + $notice = self::_postBookmark($user->getProfile(), $activity, $options); return false; } From 1996545947265b92be796d2fff8d2075523cff7c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:28:20 -0800 Subject: [PATCH 51/74] add events for restoring activities --- lib/activityimporter.php | 55 +++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/lib/activityimporter.php b/lib/activityimporter.php index 4a76781328..b3b7ffb066 100644 --- a/lib/activityimporter.php +++ b/lib/activityimporter.php @@ -63,31 +63,40 @@ class ActivityImporter extends QueueHandler $this->trusted = $trusted; - try { - switch ($activity->verb) { - case ActivityVerb::FOLLOW: - $this->subscribeProfile($user, $author, $activity); - break; - case ActivityVerb::JOIN: - $this->joinGroup($user, $activity); - break; - case ActivityVerb::POST: - $this->postNote($user, $author, $activity); - break; - default: - throw new Exception("Unknown verb: {$activity->verb}"); + $done = null; + + if (Event::handle('StartImportActivity', + array($user, $author, $activity, $trusted, &$done))) { + + try { + switch ($activity->verb) { + case ActivityVerb::FOLLOW: + $this->subscribeProfile($user, $author, $activity); + break; + case ActivityVerb::JOIN: + $this->joinGroup($user, $activity); + break; + case ActivityVerb::POST: + $this->postNote($user, $author, $activity); + break; + default: + throw new ClientException("Unknown verb: {$activity->verb}"); + } + Event::handle('EndImportActivity', + array($user, $author, $activity, $trusted)); + $done = true; + } catch (ClientException $ce) { + common_log(LOG_WARNING, $ce->getMessage()); + $done = true; + } catch (ServerException $se) { + common_log(LOG_ERR, $se->getMessage()); + $done = false; + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + $done = false; } - } catch (ClientException $ce) { - common_log(LOG_WARNING, $ce->getMessage()); - return true; - } catch (ServerException $se) { - common_log(LOG_ERR, $se->getMessage()); - return false; - } catch (Exception $e) { - common_log(LOG_ERR, $e->getMessage()); - return false; } - return true; + return $done; } function subscribeProfile($user, $author, $activity) From c458bafaa1158b5e7224c5d240cb1abc365cce4d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:35:57 -0800 Subject: [PATCH 52/74] pass through $idField and $createdField in Notice queries --- classes/Notice.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 50909f9707..14fffe6976 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -2033,7 +2033,7 @@ class Notice extends Memcached_DataObject */ public static function addWhereSinceId(DB_DataObject $obj, $id, $idField='id', $createdField='created') { - $since = self::whereSinceId($id); + $since = self::whereSinceId($id, $idField, $createdField); if ($since) { $obj->whereAdd($since); } @@ -2072,7 +2072,7 @@ class Notice extends Memcached_DataObject */ public static function addWhereMaxId(DB_DataObject $obj, $id, $idField='id', $createdField='created') { - $max = self::whereMaxId($id); + $max = self::whereMaxId($id, $idField, $createdField); if ($max) { $obj->whereAdd($max); } From 3bcfee906abfb86dcb63951ce220d02e1395898b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:37:34 -0800 Subject: [PATCH 53/74] document events for activity import --- EVENTS.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/EVENTS.txt b/EVENTS.txt index 74cf720bc0..91008f54fa 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -1026,3 +1026,17 @@ StartActivityObjectFromGroup: When converting a group to an activity:object EndActivityObjectFromGroup: After converting a group to an activity:object - $group: The group being converted - &$object: The finished object. Tweak as needed. + +StartImportActivity: when we start to import an activity +- $user: User to make the author import +- $author: Author of the feed; good for comparisons +- $activity: The current activity +- $trusted: How "trusted" the process is +- &$done: Return value; whether to continue + +EndImportActivity: when we finish importing an activity +- $user: User to make the author import +- $author: Author of the feed; good for comparisons +- $activity: The current activity +- $trusted: How "trusted" the process is + From 0a56e88a646697140d93f073922a38823330fe0c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:57:11 -0800 Subject: [PATCH 54/74] Don't double-insert a bookmark --- plugins/Bookmark/Bookmark.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/Bookmark/Bookmark.php b/plugins/Bookmark/Bookmark.php index 684532dbfe..87715ecad6 100644 --- a/plugins/Bookmark/Bookmark.php +++ b/plugins/Bookmark/Bookmark.php @@ -226,6 +226,13 @@ class Bookmark extends Memcached_DataObject $options = array(); } + if (array_key_exists('uri', $options)) { + $other = Bookmark::staticGet('uri', $options['uri']); + if (!empty($other)) { + throw new ClientException(_('Bookmark already exists.')); + } + } + if (is_string($rawtags)) { $rawtags = preg_split('/[\s,]+/', $rawtags); } @@ -287,8 +294,6 @@ class Bookmark extends Memcached_DataObject } } - // - $hashtags = array(); $taglinks = array(); From c8bbde69dffb9afd4bb11118ed9c48f0eab4af04 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:57:35 -0800 Subject: [PATCH 55/74] import bookmarks from backups --- plugins/Bookmark/BookmarkPlugin.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index fe1563f038..a5ec0f098a 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -524,6 +524,30 @@ class BookmarkPlugin extends Plugin return true; } + function onStartImportActivity($user, $author, $activity, $trusted, &$done) { + + if (self::_isPostBookmark($activity)) { + + $bookmark = $activity->objects[0]; + + $this->log(LOG_INFO, 'Importing Bookmark ' . $bookmark->id . ' for user ' . $user->nickname); + + $options = array('uri' => $bookmark->id, + 'url' => $bookmark->link, + 'source' => 'restore'); + + $saved = self::_postBookmark($user->getProfile(), $activity, $options); + + if (!empty($saved)) { + $done = true; + } + + return false; + } + + return true; + } + static private function _postRemoteBookmark(Ostatus_profile $author, Activity $activity) { $bookmark = $activity->objects[0]; From 320e73a3216ba7fada7a8f638497b2b0bd108093 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 27 Dec 2010 22:58:13 -0800 Subject: [PATCH 56/74] If notice has been deleted before, don't store URI again --- classes/Notice.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index 14fffe6976..0e8bd3c691 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -109,6 +109,11 @@ class Notice extends Memcached_DataObject // @fixme we have some cases where things get re-run and so the // insert fails. $deleted = Deleted_notice::staticGet('id', $this->id); + + if (!$deleted) { + $deleted = Deleted_notice::staticGet('uri', $this->uri); + } + if (!$deleted) { $deleted = new Deleted_notice(); From d3d9797496a3777d781627595565c5ea3a71f683 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 28 Dec 2010 11:34:02 -0800 Subject: [PATCH 57/74] Prevent group creation by silenced users. * adds Right::CREATEGROUP * logic in Profile::hasRight() checks for silencing * NewgroupAction checks for the permission before letting you see or process the form in the UI * User_group::register() logic does a low-level check on the specified initial group admin, and rejects creation if that user doesn't have the right; guaranteeing that API methods etc will also have this restriction applied sensibly. --- actions/newgroup.php | 7 +++++++ classes/Profile.php | 1 + classes/User_group.php | 10 ++++++++++ lib/right.php | 1 + 4 files changed, 19 insertions(+) diff --git a/actions/newgroup.php b/actions/newgroup.php index 05520223c0..04441e71c6 100644 --- a/actions/newgroup.php +++ b/actions/newgroup.php @@ -66,6 +66,13 @@ class NewgroupAction extends Action return false; } + $user = common_current_user(); + $profile = $user->getProfile(); + if (!$profile->hasRight(Right::CREATEGROUP)) { + // TRANS: Client exception thrown when a user tries to create a group while banned. + throw new ClientException(_('You are not allowed to create groups on this site.'), 403); + } + return true; } diff --git a/classes/Profile.php b/classes/Profile.php index 2e88f17ad3..00e076a624 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -909,6 +909,7 @@ class Profile extends Memcached_DataObject case Right::NEWNOTICE: case Right::NEWMESSAGE: case Right::SUBSCRIBE: + case Right::CREATEGROUP: $result = !$this->isSilenced(); break; case Right::PUBLICNOTICE: diff --git a/classes/User_group.php b/classes/User_group.php index 7d6e219148..f223164d04 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -465,6 +465,16 @@ class User_group extends Memcached_DataObject } static function register($fields) { + if (!empty($fields['userid'])) { + $profile = Profile::staticGet('id', $fields['userid']); + if ($profile && !$profile->hasRight(Right::CREATEGROUP)) { + common_log(LOG_WARNING, "Attempted group creation from banned user: " . $profile->nickname); + + // TRANS: Client exception thrown when a user tries to create a group while banned. + throw new ClientException(_('You are not allowed to create groups on this site.'), 403); + } + } + // MAGICALLY put fields into current scope extract($fields); diff --git a/lib/right.php b/lib/right.php index bacbea5f29..ccabd00c92 100644 --- a/lib/right.php +++ b/lib/right.php @@ -61,5 +61,6 @@ class Right const GRANTROLE = 'grantrole'; const REVOKEROLE = 'revokerole'; const DELETEGROUP = 'deletegroup'; + const CREATEGROUP = 'creategroup'; } From bf4c5cb41a179439d5d3d26b3cc42fd255b6418f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 28 Dec 2010 11:58:55 -0800 Subject: [PATCH 58/74] Stream of notices linking to an URL --- classes/File.php | 65 ++++++++++++++++++++++++++++++++++++++++ classes/File_to_post.php | 15 ++++++++++ 2 files changed, 80 insertions(+) diff --git a/classes/File.php b/classes/File.php index ef9dbf14ab..d25c7529f0 100644 --- a/classes/File.php +++ b/classes/File.php @@ -412,4 +412,69 @@ class File extends Memcached_DataObject { return File_thumbnail::staticGet('file_id', $this->id); } + + function blowCache($last=false) + { + self::blow('file:notice-ids:%s', $this->url); + if ($last) { + self::blow('file:notice-ids:%s;last', $this->url); + } + } + + /** + * Stream of notices linking to this URL + * + * @param integer $offset Offset to show; default is 0 + * @param integer $limit Limit of notices to show + * @param integer $since_id Since this notice + * @param integer $max_id Before this notice + * + * @return array ids of notices that link to this file + */ + + function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) + { + $ids = Notice::stream(array($this, '_streamDirect'), + null, + 'file:notice-ids:'.$this->url, + $offset, $limit, $since_id, $max_id); + return $ids; + } + + /** + * Stream of notices linking to this URL + * + * @param integer $offset Offset to show; default is 0 + * @param integer $limit Limit of notices to show + * @param integer $since_id Since this notice + * @param integer $max_id Before this notice + * + * @return array ids of notices that link to this file + */ + + function _streamDirect($offset, $limit, $since_id, $max_id) + { + $f2p = new File_to_post(); + + $f2p->file_id = $this->id; + + Notice::addWhereSinceId($f2p, $since_id, 'post_id', 'modified'); + Notice::addWhereMaxId($f2p, $max_id, 'post_id', 'modified'); + + $f2p->orderBy('modified DESC, notice_id DESC'); + + if (!is_null($offset)) { + $reply->limit($offset, $limit); + } + + $ids = array(); + + if ($f2p->find()) { + while ($f2p->fetch()) { + $ids[] = $f2p->notice_id; + } + } + + return $ids; + } } diff --git a/classes/File_to_post.php b/classes/File_to_post.php index 530921adcb..7d417d58b7 100644 --- a/classes/File_to_post.php +++ b/classes/File_to_post.php @@ -52,6 +52,12 @@ class File_to_post extends Memcached_DataObject $f2p->file_id = $file_id; $f2p->post_id = $notice_id; $f2p->insert(); + + $f = File::staticGet($file_id); + + if (!empty($f)) { + $f->blowNoticeCache(); + } } if (empty($seen[$notice_id])) { @@ -66,4 +72,13 @@ class File_to_post extends Memcached_DataObject { return Memcached_DataObject::pkeyGet('File_to_post', $kv); } + + function delete() + { + $f = File::staticGet('id', $this->file_id); + if (!empty($f)) { + $f->blowNoticeCache(); + } + return parent::delete(); + } } From faf0081a8b4a5ce3ef1fde0a701a381778037834 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 28 Dec 2010 12:57:31 -0800 Subject: [PATCH 59/74] Fixes from testing File::stream() --- classes/File.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/classes/File.php b/classes/File.php index d25c7529f0..2b0e636c16 100644 --- a/classes/File.php +++ b/classes/File.php @@ -413,6 +413,14 @@ class File extends Memcached_DataObject return File_thumbnail::staticGet('file_id', $this->id); } + /** + * Blow the cache of notices that link to this URL + * + * @param boolean $last Whether to blow the "last" cache too + * + * @return void + */ + function blowCache($last=false) { self::blow('file:notice-ids:%s', $this->url); @@ -435,10 +443,11 @@ class File extends Memcached_DataObject function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { $ids = Notice::stream(array($this, '_streamDirect'), - null, + array(), 'file:notice-ids:'.$this->url, $offset, $limit, $since_id, $max_id); - return $ids; + + return Notice::getStreamByIds($ids); } /** @@ -456,22 +465,25 @@ class File extends Memcached_DataObject { $f2p = new File_to_post(); + $f2p->selectAdd(); + $f2p->selectAdd('post_id'); + $f2p->file_id = $this->id; Notice::addWhereSinceId($f2p, $since_id, 'post_id', 'modified'); Notice::addWhereMaxId($f2p, $max_id, 'post_id', 'modified'); - $f2p->orderBy('modified DESC, notice_id DESC'); + $f2p->orderBy('modified DESC, post_id DESC'); if (!is_null($offset)) { - $reply->limit($offset, $limit); + $f2p->limit($offset, $limit); } $ids = array(); if ($f2p->find()) { while ($f2p->fetch()) { - $ids[] = $f2p->notice_id; + $ids[] = $f2p->post_id; } } From 821770966bc6f2c36ad27b65a5e6eed212c64267 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 28 Dec 2010 12:58:10 -0800 Subject: [PATCH 60/74] Page with a list of notices that link to an URL --- plugins/Bookmark/BookmarkPlugin.php | 8 +- plugins/Bookmark/noticebyurl.php | 163 ++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 plugins/Bookmark/noticebyurl.php diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index a5ec0f098a..a6ee92f026 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -157,6 +157,7 @@ class BookmarkPlugin extends Plugin case 'ShowbookmarkAction': case 'NewbookmarkAction': case 'BookmarkpopupAction': + case 'NoticebyurlAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'Bookmark': @@ -186,7 +187,8 @@ class BookmarkPlugin extends Plugin array('action' => 'newbookmark'), array('id' => '[0-9]+')); - $m->connect('main/bookmark/popup', array('action' => 'bookmarkpopup')); + $m->connect('main/bookmark/popup', + array('action' => 'bookmarkpopup')); $m->connect('bookmark/:user/:created/:crc32', array('action' => 'showbookmark'), @@ -194,6 +196,10 @@ class BookmarkPlugin extends Plugin 'created' => '[0-9]{14}', 'crc32' => '[0-9a-f]{8}')); + $m->connect('notice/by-url/:id', + array('action' => 'noticebyurl'), + array('id' => '[0-9]+')); + return true; } diff --git a/plugins/Bookmark/noticebyurl.php b/plugins/Bookmark/noticebyurl.php new file mode 100644 index 0000000000..9fc480b62d --- /dev/null +++ b/plugins/Bookmark/noticebyurl.php @@ -0,0 +1,163 @@ +. + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * List notices that contain/link to/use a given URL + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class NoticebyurlAction extends Action +{ + protected $url = null; + protected $file = null; + protected $notices = null; + protected $page = null; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + + $this->file = File::staticGet('id', $this->trimmed('id')); + + if (empty($this->file)) { + throw new ClientException(_('Unknown URL')); + } + + $pageArg = $this->trimmed('page'); + + $this->page = (empty($pageArg)) ? 1 : intval($pageArg); + + $this->notices = $this->file->stream(($this->page - 1) * NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1); + + return true; + } + + function title() + { + if ($this->page == 1) { + return sprintf(_("Notices linking to %s"), $this->file->url); + } else { + return sprintf(_("Notices linking to %s, page %d"), + $this->file->url, + $this->page); + } + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + + function handle($argarray=null) + { + $this->showPage(); + } + + function showContent() + { + $nl = new NoticeList($this->notices, $this); + + $nl->show(); + + $cnt = $nl->show(); + + $this->pagination($this->page > 1, + $cnt > NOTICES_PER_PAGE, + $this->page, + 'noticebyurl', + array('id' => $this->file->id)); + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + + /** + * Return last modified, if applicable. + * + * MAY override + * + * @return string last modified http header + */ + function lastModified() + { + // For comparison with If-Last-Modified + // If not applicable, return null + return null; + } + + /** + * Return etag, if applicable. + * + * MAY override + * + * @return string etag http header + */ + + function etag() + { + return null; + } +} From 6ab46c70f7a7dcab6694ebd33af13c64b809ebd7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 28 Dec 2010 13:44:18 -0800 Subject: [PATCH 61/74] Delete file links when Notice is deleted --- classes/Notice.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index 0e8bd3c691..561999966c 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -135,6 +135,7 @@ class Notice extends Memcached_DataObject $this->clearFaves(); $this->clearTags(); $this->clearGroupInboxes(); + $this->clearFiles(); // NOTE: we don't clear inboxes // NOTE: we don't clear queue items @@ -1785,6 +1786,21 @@ class Notice extends Memcached_DataObject $reply->free(); } + function clearFiles() + { + $f2p = new File_to_post(); + + $f2p->post_id = $this->id; + + if ($f2p->find()) { + while ($f2p->fetch()) { + $f2p->delete(); + } + } + // FIXME: decide whether to delete File objects + // ...and related (actual) files + } + function clearRepeats() { $repeatNotice = new Notice(); From d31397bd453f9003f9fdac465741dc61df2decd5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 28 Dec 2010 13:44:49 -0800 Subject: [PATCH 62/74] method to count notices linking to an URL --- classes/File.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/classes/File.php b/classes/File.php index 2b0e636c16..29a8f0f1c5 100644 --- a/classes/File.php +++ b/classes/File.php @@ -427,6 +427,7 @@ class File extends Memcached_DataObject if ($last) { self::blow('file:notice-ids:%s;last', $this->url); } + self::blow('file:notice-count:%d', $this->id); } /** @@ -489,4 +490,24 @@ class File extends Memcached_DataObject return $ids; } + + function noticeCount() + { + $cacheKey = sprintf('file:notice-count:%d', $this->id); + + $count = self::cacheGet($cacheKey); + + if ($count === false) { + + $f2p = new File_to_post(); + + $f2p->file_id = $this->id; + + $count = $f2p->count(); + + self::cacheSet($cacheKey, $count); + } + + return $count; + } } From dcd0e3ec7e8dfaaf121cafa25771427e809f33e6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 28 Dec 2010 13:45:24 -0800 Subject: [PATCH 63/74] show count of other bookmarks with link to stream --- plugins/Bookmark/BookmarkPlugin.php | 5 +++++ plugins/Bookmark/bookmark.css | 1 + 2 files changed, 6 insertions(+) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index a6ee92f026..d93f61c81d 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -239,6 +239,11 @@ class BookmarkPlugin extends Plugin array('href' => $att->url), $nb->title); $out->elementEnd('h3'); + + $out->element('a', array('class' => 'bookmark_notice_count', + 'href' => common_local_url('noticebyurl', + array('id' => $att->id))), + $att->noticeCount()); } $out->elementStart('ul', array('class' => 'bookmark_tags')); diff --git a/plugins/Bookmark/bookmark.css b/plugins/Bookmark/bookmark.css index 27d716da7f..b86e749fd9 100644 --- a/plugins/Bookmark/bookmark.css +++ b/plugins/Bookmark/bookmark.css @@ -1,3 +1,4 @@ .bookmark_tags li { display: inline; } .bookmark_mentions li { display: inline; } .bookmark_avatar { float: left } +.bookmark_notice_count { float: right } From db43195fbf29c16d68e1ce43cb316949b1c1f4ea Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 28 Dec 2010 23:36:25 +0000 Subject: [PATCH 64/74] fixup date handling in showbookmark for PHP 5.2 --- plugins/Bookmark/showbookmark.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php index 07dec5a98c..54f333fd52 100644 --- a/plugins/Bookmark/showbookmark.php +++ b/plugins/Bookmark/showbookmark.php @@ -81,9 +81,8 @@ class ShowbookmarkAction extends ShownoticeAction throw new ClientException(_('No such URL.'), 404); } - $dt = DateTime::createFromFormat('YmdHis', - $this->trimmed('created'), - new DateTimeZone('UTC')); + $dt = new DateTime($this->trimmed('created'), + new DateTimeZone('UTC')); if (empty($dt)) { throw new ClientException(_('No such create date.'), 404); @@ -92,9 +91,9 @@ class ShowbookmarkAction extends ShownoticeAction $bookmarks = Bookmark::getByCRC32($this->profile, $crc32); - foreach ($bookmarks as $bookmark) { + foreach ($bookmarks as $bookmark) { $bdt = new DateTime($bookmark->created, new DateTimeZone('UTC')); - if ($bdt->getTimestamp() == $dt->getTimestamp()) { + if ($bdt->format('U') == $dt->format('U')) { $this->bookmark = $bookmark; break; } From 39cf2338c26d55fced83a0c411550d522f9badd8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 13:28:32 -0800 Subject: [PATCH 65/74] Bad method call in File_to_post --- classes/File_to_post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/File_to_post.php b/classes/File_to_post.php index 7d417d58b7..bcb6771f4f 100644 --- a/classes/File_to_post.php +++ b/classes/File_to_post.php @@ -56,7 +56,7 @@ class File_to_post extends Memcached_DataObject $f = File::staticGet($file_id); if (!empty($f)) { - $f->blowNoticeCache(); + $f->blowCache(); } } @@ -77,7 +77,7 @@ class File_to_post extends Memcached_DataObject { $f = File::staticGet('id', $this->file_id); if (!empty($f)) { - $f->blowNoticeCache(); + $f->blowCache(); } return parent::delete(); } From 4a9a5076ff08ef441e28cd3b87e8e9502478b6e5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 13:51:59 -0800 Subject: [PATCH 66/74] Web UI for importing delicious backup files --- plugins/Bookmark/BookmarkPlugin.php | 14 ++ plugins/Bookmark/importdelicious.php | 309 +++++++++++++++++++++++++++ 2 files changed, 323 insertions(+) create mode 100644 plugins/Bookmark/importdelicious.php diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index d93f61c81d..a714adc984 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -47,6 +47,16 @@ if (!defined('STATUSNET')) { class BookmarkPlugin extends Plugin { const VERSION = '0.1'; + const IMPORTDELICIOUS = 'BookmarkPlugin:IMPORTDELICIOUS'; + + function onUserRightsCheck($profile, $right, &$result) + { + if ($right == self::IMPORTDELICIOUS) { + $result = !$profile->isSilenced(); + return false; + } + return true; + } /** * Database schema setup @@ -158,6 +168,7 @@ class BookmarkPlugin extends Plugin case 'NewbookmarkAction': case 'BookmarkpopupAction': case 'NoticebyurlAction': + case 'ImportdeliciousAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'Bookmark': @@ -190,6 +201,9 @@ class BookmarkPlugin extends Plugin $m->connect('main/bookmark/popup', array('action' => 'bookmarkpopup')); + $m->connect('main/bookmark/import', + array('action' => 'importdelicious')); + $m->connect('bookmark/:user/:created/:crc32', array('action' => 'showbookmark'), array('user' => '[0-9]+', diff --git a/plugins/Bookmark/importdelicious.php b/plugins/Bookmark/importdelicious.php new file mode 100644 index 0000000000..52f532bf5a --- /dev/null +++ b/plugins/Bookmark/importdelicious.php @@ -0,0 +1,309 @@ +. + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * UI for importing del.icio.us bookmark backups + * + * @category Bookmark + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class ImportdeliciousAction extends Action +{ + protected $success = false; + + function title() + { + return _("Import del.icio.us bookmarks"); + } + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + + $cur = common_current_user(); + + if (empty($cur)) { + throw new ClientException(_('Only logged-in users can import del.icio.us backups.'), 403); + } + + if (!$cur->hasRight(BookmarkPlugin::IMPORTDELICIOUS)) { + throw new ClientException(_('You may not restore your account.'), 403); + } + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + + function handle($argarray=null) + { + parent::handle($argarray); + + if ($this->isPost()) { + $this->importDelicious(); + } else { + $this->showPage(); + } + return; + } + + /** + * Queue a file for importation + * + * Uses the DeliciousBackupImporter class; may take a long time! + * + * @return void + */ + + function importDelicious() + { + $this->checkSessionToken(); + + if (!isset($_FILES[ImportDeliciousForm::FILEINPUT]['error'])) { + throw new ClientException(_('No uploaded file.')); + } + + switch ($_FILES[ImportDeliciousForm::FILEINPUT]['error']) { + case UPLOAD_ERR_OK: // success, jump out + break; + case UPLOAD_ERR_INI_SIZE: + // TRANS: Client exception thrown when an uploaded file is larger than set in php.ini. + throw new ClientException(_('The uploaded file exceeds the ' . + 'upload_max_filesize directive in php.ini.')); + return; + case UPLOAD_ERR_FORM_SIZE: + throw new ClientException( + // TRANS: Client exception. + _('The uploaded file exceeds the MAX_FILE_SIZE directive' . + ' that was specified in the HTML form.')); + return; + case UPLOAD_ERR_PARTIAL: + @unlink($_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name']); + // TRANS: Client exception. + throw new ClientException(_('The uploaded file was only' . + ' partially uploaded.')); + return; + case UPLOAD_ERR_NO_FILE: + // No file; probably just a non-AJAX submission. + throw new ClientException(_('No uploaded file.')); + return; + case UPLOAD_ERR_NO_TMP_DIR: + // TRANS: Client exception thrown when a temporary folder is not present to store a file upload. + throw new ClientException(_('Missing a temporary folder.')); + return; + case UPLOAD_ERR_CANT_WRITE: + // TRANS: Client exception thrown when writing to disk is not possible during a file upload operation. + throw new ClientException(_('Failed to write file to disk.')); + return; + case UPLOAD_ERR_EXTENSION: + // TRANS: Client exception thrown when a file upload operation has been stopped by an extension. + throw new ClientException(_('File upload stopped by extension.')); + return; + default: + common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . + $_FILES[ImportDeliciousForm::FILEINPUT]['error']); + // TRANS: Client exception thrown when a file upload operation has failed with an unknown reason. + throw new ClientException(_('System error uploading file.')); + return; + } + + $filename = $_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name']; + + try { + if (!file_exists($filename)) { + throw new ServerException("No such file '$filename'."); + } + + if (!is_file($filename)) { + throw new ServerException("Not a regular file: '$filename'."); + } + + if (!is_readable($filename)) { + throw new ServerException("File '$filename' not readable."); + } + + common_debug(sprintf(_("Getting backup from file '%s'."), $filename)); + + $html = file_get_contents($filename); + + // Enqueue for processing. + + $qm = QueueManager::get(); + $qm->enqueue(array(common_current_user(), $html), 'dlcsback'); + + $this->success = true; + + $this->showPage(); + + } catch (Exception $e) { + // Delete the file and re-throw + @unlink($_FILES[ImportDeliciousForm::FILEINPUT]['tmp_name']); + throw $e; + } + } + + function showContent() + { + if ($this->success) { + $this->element('p', null, + _('Feed will be restored. Please wait a few minutes for results.')); + } else { + $form = new ImportDeliciousForm($this); + $form->show(); + } + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return !$this->isPost(); + } +} + +/** + * A form for backing up the account. + * + * @category Account + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class ImportDeliciousForm extends Form +{ + const FILEINPUT = 'deliciousbackupfile'; + + function __construct($out=null) { + parent::__construct($out); + $this->enctype = 'multipart/form-data'; + } + + /** + * Class of the form. + * + * @return string the form's class + */ + + function formClass() + { + return 'form_import_delicious'; + } + + /** + * URL the form posts to + * + * @return string the form's action URL + */ + + function action() + { + return common_local_url('importdelicious'); + } + + /** + * Output form data + * + * Really, just instructions for doing a backup. + * + * @return void + */ + + function formData() + { + $this->out->elementStart('p', 'instructions'); + + $this->out->raw(_('You can upload a backed-up delicious.com bookmarks file.')); + + $this->out->elementEnd('p'); + + $this->out->elementStart('ul', 'form_data'); + + $this->out->elementStart('li', array ('id' => 'settings_attach')); + $this->out->element('input', array('name' => self::FILEINPUT, + 'type' => 'file', + 'id' => self::FILEINPUT)); + $this->out->elementEnd('li'); + + $this->out->elementEnd('ul'); + } + + /** + * Buttons for the form + * + * In this case, a single submit button + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', + _m('BUTTON', 'Upload'), + 'submit', + null, + _('Upload the file')); + } +} From 48edbb30231e28b74409e02308f15e38940e5da1 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:02:04 -0800 Subject: [PATCH 67/74] add hooks for the account-management tools --- EVENTS.txt | 5 +++++ actions/profilesettings.php | 45 ++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 91008f54fa..f7cc7df67c 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -1040,3 +1040,8 @@ EndImportActivity: when we finish importing an activity - $activity: The current activity - $trusted: How "trusted" the process is +StartProfileSettingsActions: when we're showing account-management action list +- $action: Action being shown (use for output) + +EndProfileSettingsActions: when we're showing account-management action list +- $action: Action being shown (use for output) diff --git a/actions/profilesettings.php b/actions/profilesettings.php index 8f55a47189..19fbdbd293 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -458,27 +458,32 @@ class ProfilesettingsAction extends AccountSettingsAction $this->elementStart('div', array('id' => 'aside_primary', 'class' => 'aside')); - if ($user->hasRight(Right::BACKUPACCOUNT)) { - $this->elementStart('li'); - $this->element('a', - array('href' => common_local_url('backupaccount')), - _('Backup account')); - $this->elementEnd('li'); - } - if ($user->hasRight(Right::DELETEACCOUNT)) { - $this->elementStart('li'); - $this->element('a', - array('href' => common_local_url('deleteaccount')), - _('Delete account')); - $this->elementEnd('li'); - } - if ($user->hasRight(Right::RESTOREACCOUNT)) { - $this->elementStart('li'); - $this->element('a', - array('href' => common_local_url('restoreaccount')), - _('Restore account')); - $this->elementEnd('li'); + $this->elementStart('ul'); + if (Event::handle('StartProfileSettingsActions', array($this))) { + if ($user->hasRight(Right::BACKUPACCOUNT)) { + $this->elementStart('li'); + $this->element('a', + array('href' => common_local_url('backupaccount')), + _('Backup account')); + $this->elementEnd('li'); + } + if ($user->hasRight(Right::DELETEACCOUNT)) { + $this->elementStart('li'); + $this->element('a', + array('href' => common_local_url('deleteaccount')), + _('Delete account')); + $this->elementEnd('li'); + } + if ($user->hasRight(Right::RESTOREACCOUNT)) { + $this->elementStart('li'); + $this->element('a', + array('href' => common_local_url('restoreaccount')), + _('Restore account')); + $this->elementEnd('li'); + } + Event::handle('EndProfileSettingsActions', array($this)); } + $this->elementEnd('ul'); $this->elementEnd('div'); } } From 68f44dad8236c28106803898c894b8b9faa9c86f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:02:31 -0800 Subject: [PATCH 68/74] Add link to delicious bookmark importer to profile settings --- plugins/Bookmark/BookmarkPlugin.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index a714adc984..dc57b00e13 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -573,6 +573,21 @@ class BookmarkPlugin extends Plugin return true; } + function onEndProfileSettingsActions($action) + { + $user = common_current_user(); + + if (!empty($user) && $user->hasRight(self::IMPORTDELICIOUS)) { + $action->elementStart('li'); + $action->element('a', + array('href' => common_local_url('importdelicious')), + _('Import del.icio.us bookmarks')); + $action->elementEnd('li'); + } + + return true; + } + static private function _postRemoteBookmark(Ostatus_profile $author, Activity $activity) { $bookmark = $activity->objects[0]; From 10fa41454dfd034782b3e912cfd2db697aa07300 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:16:15 -0800 Subject: [PATCH 69/74] phpcs BookmarkPlugin.php --- plugins/Bookmark/BookmarkPlugin.php | 130 +++++++++++++++++++++++----- 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index dc57b00e13..4f31349801 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -46,9 +46,21 @@ if (!defined('STATUSNET')) { class BookmarkPlugin extends Plugin { - const VERSION = '0.1'; + const VERSION = '0.1'; const IMPORTDELICIOUS = 'BookmarkPlugin:IMPORTDELICIOUS'; + /** + * Authorization for importing delicious bookmarks + * + * By default, everyone can import bookmarks except silenced people. + * + * @param Profile $profile Person whose rights to check + * @param string $right Right to check; const value + * @param boolean &$result Result of the check, writeable + * + * @return boolean hook value + */ + function onUserRightsCheck($profile, $right, &$result) { if ($right == self::IMPORTDELICIOUS) { @@ -254,9 +266,11 @@ class BookmarkPlugin extends Plugin $nb->title); $out->elementEnd('h3'); + $countUrl = common_local_url('noticebyurl', + array('id' => $att->id)); + $out->element('a', array('class' => 'bookmark_notice_count', - 'href' => common_local_url('noticebyurl', - array('id' => $att->id))), + 'href' => $countUrl), $att->noticeCount()); } @@ -470,13 +484,14 @@ class BookmarkPlugin extends Plugin * @return boolean hook value */ - function onStartHandleFeedEntryWithProfile($activity, $oprofile) { - + function onStartHandleFeedEntryWithProfile($activity, $oprofile) + { common_log(LOG_INFO, "BookmarkPlugin called for new feed entry."); if (self::_isPostBookmark($activity)) { - common_log(LOG_INFO, "Importing activity {$activity->id} as a bookmark."); + common_log(LOG_INFO, + "Importing activity {$activity->id} as a bookmark."); $author = $oprofile->checkAuthorship($activity); @@ -502,8 +517,8 @@ class BookmarkPlugin extends Plugin * @return boolean hook value */ - function onStartHandleSalmonTarget($activity, $target) { - + function onStartHandleSalmonTarget($activity, $target) + { if (self::_isPostBookmark($activity)) { $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap."); @@ -511,20 +526,25 @@ class BookmarkPlugin extends Plugin if ($target instanceof User_group) { $uri = $target->getUri(); if (!in_array($uri, $activity->context->attention)) { - throw new ClientException(_("Bookmark not posted to this group.")); + throw new ClientException(_("Bookmark not posted ". + "to this group.")); } } else if ($target instanceof User) { - $uri = $target->uri; + $uri = $target->uri; $original = null; if (!empty($activity->context->replyToID)) { - $original = Notice::staticGet('uri', $activity->context->replyToID); + $original = Notice::staticGet('uri', + $activity->context->replyToID); } if (!in_array($uri, $activity->context->attention) && - (empty($original) || $original->profile_id != $target->id)) { - throw new ClientException(_("Bookmark not posted to this user.")); + (empty($original) || + $original->profile_id != $target->id)) { + throw new ClientException(_("Bookmark not posted ". + "to this user.")); } } else { - throw new ServerException(_("Don't know how to handle this kind of target.")); + throw new ServerException(_("Don't know how to handle ". + "this kind of target.")); } $author = Ostatus_profile::ensureActivityObjectProfile($activity->actor); @@ -538,24 +558,50 @@ class BookmarkPlugin extends Plugin return true; } + /** + * Handle bookmark posted via AtomPub + * + * @param Activity &$activity Activity that was posted + * @param User $user User that posted it + * @param Notice &$notice Resulting notice + * + * @return boolean hook value + */ + function onStartAtomPubNewActivity(&$activity, $user, &$notice) { if (self::_isPostBookmark($activity)) { $options = array('source' => 'atompub'); - $notice = self::_postBookmark($user->getProfile(), $activity, $options); + $notice = self::_postBookmark($user->getProfile(), + $activity, + $options); return false; } return true; } - function onStartImportActivity($user, $author, $activity, $trusted, &$done) { + /** + * Handle bookmark imported from a backup file + * + * @param User $user User to import for + * @param ActivityObject $author Original author per import file + * @param Activity $activity Activity to import + * @param boolean $trusted Is this a trusted user? + * @param boolean &$done Is this done (success or unrecoverable error) + * + * @return boolean hook value + */ + function onStartImportActivity($user, $author, $activity, $trusted, &$done) + { if (self::_isPostBookmark($activity)) { $bookmark = $activity->objects[0]; - $this->log(LOG_INFO, 'Importing Bookmark ' . $bookmark->id . ' for user ' . $user->nickname); + $this->log(LOG_INFO, + 'Importing Bookmark ' . $bookmark->id . + ' for user ' . $user->nickname); $options = array('uri' => $bookmark->id, 'url' => $bookmark->link, @@ -573,6 +619,14 @@ class BookmarkPlugin extends Plugin return true; } + /** + * Show a link to our delicious import page on profile settings form + * + * @param Action $action Profile settings action being shown + * + * @return boolean hook value + */ + function onEndProfileSettingsActions($action) { $user = common_current_user(); @@ -588,7 +642,17 @@ class BookmarkPlugin extends Plugin return true; } - static private function _postRemoteBookmark(Ostatus_profile $author, Activity $activity) + /** + * Save a remote bookmark (from Salmon or PuSH) + * + * @param Ostatus_profile $author Author of the bookmark + * @param Activity $activity Activity to save + * + * @return Notice resulting notice. + */ + + static private function _postRemoteBookmark(Ostatus_profile $author, + Activity $activity) { $bookmark = $activity->objects[0]; @@ -600,18 +664,32 @@ class BookmarkPlugin extends Plugin return self::_postBookmark($author->localProfile(), $activity, $options); } - static private function _postBookmark(Profile $profile, Activity $activity, $options=array()) + /** + * Save a bookmark from an activity + * + * @param Profile $profile Profile to use as author + * @param Activity $activity Activity to save + * @param array $options Options to pass to bookmark-saving code + * + * @return Notice resulting notice + */ + + static private function _postBookmark(Profile $profile, + Activity $activity, + $options=array()) { $bookmark = $activity->objects[0]; $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related'); if (count($relLinkEls) < 1) { - throw new ClientException(_('Expected exactly 1 link rel=related in a Bookmark.')); + throw new ClientException(_('Expected exactly 1 link '. + 'rel=related in a Bookmark.')); } if (count($relLinkEls) > 1) { - common_log(LOG_WARNING, "Got too many link rel=related in a Bookmark."); + common_log(LOG_WARNING, + "Got too many link rel=related in a Bookmark."); } $linkEl = $relLinkEls[0]; @@ -643,7 +721,7 @@ class BookmarkPlugin extends Plugin $replies = $activity->context->attention; - $options['groups'] = array(); + $options['groups'] = array(); $options['replies'] = array(); foreach ($replies as $replyURI) { @@ -677,6 +755,14 @@ class BookmarkPlugin extends Plugin $options); } + /** + * Test if an activity represents posting a bookmark + * + * @param Activity $activity Activity to test + * + * @return true if it's a Post of a Bookmark, else false + */ + static private function _isPostBookmark($activity) { return ($activity->verb == ActivityVerb::POST && From b00d113bb4b8ec4e9f34ee91b8baa454a7672e6b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:17:32 -0800 Subject: [PATCH 70/74] phpcs Bookmark.php --- plugins/Bookmark/Bookmark.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/Bookmark/Bookmark.php b/plugins/Bookmark/Bookmark.php index 87715ecad6..61fe3c5b97 100644 --- a/plugins/Bookmark/Bookmark.php +++ b/plugins/Bookmark/Bookmark.php @@ -106,7 +106,8 @@ class Bookmark extends Memcached_DataObject 'description' => DB_DATAOBJECT_STR, 'uri' => DB_DATAOBJECT_STR, 'url_crc32' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); } /** @@ -264,7 +265,7 @@ class Bookmark extends Memcached_DataObject $created = $dt->format('YmdHis'); - $crc32 = sprintf('%08x', $nb->url_crc32); + $crc32 = sprintf('%08x', $nb->url_crc32); $nb->uri = common_local_url('showbookmark', array('user' => $profile->id, @@ -281,7 +282,8 @@ class Bookmark extends Memcached_DataObject foreach ($rawtags as $tag) { if (strtolower(mb_substr($tag, 0, 4)) == 'for:') { - if (!array_key_exists('replies', $options)) { // skip if done by caller + // skip if done by caller + if (!array_key_exists('replies', $options)) { $nickname = mb_substr($tag, 4); $other = common_relative_profile($profile, $nickname); From f5256eb0281941bf2265f1813d19b41e45302629 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:21:25 -0800 Subject: [PATCH 71/74] phpcs importdelicious.php --- plugins/Bookmark/importdelicious.php | 45 ++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/plugins/Bookmark/importdelicious.php b/plugins/Bookmark/importdelicious.php index 52f532bf5a..f8529cc914 100644 --- a/plugins/Bookmark/importdelicious.php +++ b/plugins/Bookmark/importdelicious.php @@ -49,6 +49,12 @@ class ImportdeliciousAction extends Action { protected $success = false; + /** + * Return the title of the page + * + * @return string page title + */ + function title() { return _("Import del.icio.us bookmarks"); @@ -69,7 +75,9 @@ class ImportdeliciousAction extends Action $cur = common_current_user(); if (empty($cur)) { - throw new ClientException(_('Only logged-in users can import del.icio.us backups.'), 403); + throw new ClientException(_('Only logged-in users can '. + 'import del.icio.us backups.'), + 403); } if (!$cur->hasRight(BookmarkPlugin::IMPORTDELICIOUS)) { @@ -119,7 +127,7 @@ class ImportdeliciousAction extends Action case UPLOAD_ERR_OK: // success, jump out break; case UPLOAD_ERR_INI_SIZE: - // TRANS: Client exception thrown when an uploaded file is larger than set in php.ini. + // TRANS: Client exception thrown when an uploaded file is too large. throw new ClientException(_('The uploaded file exceeds the ' . 'upload_max_filesize directive in php.ini.')); return; @@ -140,21 +148,21 @@ class ImportdeliciousAction extends Action throw new ClientException(_('No uploaded file.')); return; case UPLOAD_ERR_NO_TMP_DIR: - // TRANS: Client exception thrown when a temporary folder is not present to store a file upload. + // TRANS: Client exception thrown when a temporary folder is not present throw new ClientException(_('Missing a temporary folder.')); return; case UPLOAD_ERR_CANT_WRITE: - // TRANS: Client exception thrown when writing to disk is not possible during a file upload operation. + // TRANS: Client exception thrown when writing to disk is not possible throw new ClientException(_('Failed to write file to disk.')); return; case UPLOAD_ERR_EXTENSION: - // TRANS: Client exception thrown when a file upload operation has been stopped by an extension. + // TRANS: Client exception thrown when a file upload has been stopped throw new ClientException(_('File upload stopped by extension.')); return; default: common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[ImportDeliciousForm::FILEINPUT]['error']); - // TRANS: Client exception thrown when a file upload operation has failed with an unknown reason. + // TRANS: Client exception thrown when a file upload operation has failed throw new ClientException(_('System error uploading file.')); return; } @@ -194,11 +202,18 @@ class ImportdeliciousAction extends Action } } + /** + * Show the content of the page + * + * @return void + */ + function showContent() { if ($this->success) { $this->element('p', null, - _('Feed will be restored. Please wait a few minutes for results.')); + _('Feed will be restored. '. + 'Please wait a few minutes for results.')); } else { $form = new ImportDeliciousForm($this); $form->show(); @@ -236,7 +251,18 @@ class ImportDeliciousForm extends Form { const FILEINPUT = 'deliciousbackupfile'; - function __construct($out=null) { + /** + * Constructor + * + * Set the encoding type, since this is a file upload. + * + * @param HTMLOutputter $out output channel + * + * @return ImportDeliciousForm this + */ + + function __construct($out=null) + { parent::__construct($out); $this->enctype = 'multipart/form-data'; } @@ -275,7 +301,8 @@ class ImportDeliciousForm extends Form { $this->out->elementStart('p', 'instructions'); - $this->out->raw(_('You can upload a backed-up delicious.com bookmarks file.')); + $this->out->raw(_('You can upload a backed-up '. + 'delicious.com bookmarks file.')); $this->out->elementEnd('p'); From f3999ab92d59eadf740e7d54379f07c088760323 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:22:41 -0800 Subject: [PATCH 72/74] phpcs noticebyurl.php --- plugins/Bookmark/noticebyurl.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/Bookmark/noticebyurl.php b/plugins/Bookmark/noticebyurl.php index 9fc480b62d..226c7a32bf 100644 --- a/plugins/Bookmark/noticebyurl.php +++ b/plugins/Bookmark/noticebyurl.php @@ -80,6 +80,12 @@ class NoticebyurlAction extends Action return true; } + /** + * Title of the page + * + * @return string page title + */ + function title() { if ($this->page == 1) { @@ -104,6 +110,14 @@ class NoticebyurlAction extends Action $this->showPage(); } + /** + * Show main page content. + * + * Shows a list of the notices that link to the given URL + * + * @return void + */ + function showContent() { $nl = new NoticeList($this->notices, $this); From 3fea4aba7f02b9489a3209c4104f3b013ec30709 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:24:30 -0800 Subject: [PATCH 73/74] phpcs showbookmark.php --- plugins/Bookmark/showbookmark.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php index 54f333fd52..e9e656f84c 100644 --- a/plugins/Bookmark/showbookmark.php +++ b/plugins/Bookmark/showbookmark.php @@ -82,7 +82,7 @@ class ShowbookmarkAction extends ShownoticeAction } $dt = new DateTime($this->trimmed('created'), - new DateTimeZone('UTC')); + new DateTimeZone('UTC')); if (empty($dt)) { throw new ClientException(_('No such create date.'), 404); @@ -91,7 +91,7 @@ class ShowbookmarkAction extends ShownoticeAction $bookmarks = Bookmark::getByCRC32($this->profile, $crc32); - foreach ($bookmarks as $bookmark) { + foreach ($bookmarks as $bookmark) { $bdt = new DateTime($bookmark->created, new DateTimeZone('UTC')); if ($bdt->format('U') == $dt->format('U')) { $this->bookmark = $bookmark; @@ -113,6 +113,14 @@ class ShowbookmarkAction extends ShownoticeAction return true; } + /** + * Title of the page + * + * Used by Action class for layout. + * + * @return string page tile + */ + function title() { return sprintf(_('%s\'s bookmark for "%s"'), @@ -120,6 +128,12 @@ class ShowbookmarkAction extends ShownoticeAction $this->bookmark->title); } + /** + * Overload page title display to show bookmark link + * + * @return void + */ + function showPageTitle() { $this->elementStart('h1'); From 2d576aea30bf3e53415cc21df07b3aa102fd9737 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 29 Dec 2010 14:52:43 -0800 Subject: [PATCH 74/74] don't distribute when restoring archived delicious bookmarks --- plugins/Bookmark/deliciousbookmarkimporter.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/Bookmark/deliciousbookmarkimporter.php b/plugins/Bookmark/deliciousbookmarkimporter.php index 061572d95a..297ef81246 100644 --- a/plugins/Bookmark/deliciousbookmarkimporter.php +++ b/plugins/Bookmark/deliciousbookmarkimporter.php @@ -97,11 +97,12 @@ class DeliciousBookmarkImporter extends QueueHandler $created = common_sql_date(intval($addDate)); $saved = Bookmark::saveNew($user->getProfile(), - $title, - $url, - $tags, - $description, - array('created' => $created)); + $title, + $url, + $tags, + $description, + array('created' => $created, + 'distribute' => false)); return true; }