From: Evan Prodromou Date: Tue, 28 Dec 2010 06:13:23 +0000 (-0800) Subject: Merge branch '0.9.x' into socialbookmark X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=1cc8be4d8b4931a138f97fbd86c5f31dbb93da02;hp=75e671774db5bd118e17a48a7c8b63168626d788;p=quix0rs-gnu-social.git Merge branch '0.9.x' into socialbookmark --- 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) { 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; + } } diff --git a/lib/oembedhelper.php b/lib/oembedhelper.php index 84cf105867..3cd20c8e8e 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); + } } class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception diff --git a/plugins/Bookmark/Bookmark.php b/plugins/Bookmark/Bookmark.php new file mode 100644 index 0000000000..684532dbfe --- /dev/null +++ b/plugins/Bookmark/Bookmark.php @@ -0,0 +1,344 @@ + + * @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 Bookmark extends Memcached_DataObject +{ + 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 + * + * 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('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 + * + * 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('profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'url' => DB_DATAOBJECT_STR, + 'title' => 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); + } + + /** + * 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('profile_id' => 'K', + 'url' => 'K', + 'uri' => 'U'); + } + + /** + * 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); + } + + /** + * 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 Bookmark bookmark found or null + */ + + static function getByURL($profile, $url) + { + return self::pkeyGet(array('profile_id' => $profile->id, + 'url' => $url)); + return null; + } + + /** + * 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 $bookmarks; + } + + /** + * Save a new notice bookmark + * + * @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($profile, $title, $url, $rawtags, $description, + $options=null) + { + $nb = self::getByURL($profile, $url); + + if (!empty($nb)) { + throw new ClientException(_('Bookmark already exists.')); + } + + if (empty($options)) { + $options = array(); + } + + if (is_string($rawtags)) { + $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); + + 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, 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' => $created, + 'crc32' => $crc32)); + } + + $nb->insert(); + + $tags = array(); + $replies = array(); + + // filter "for:nickname" tags + + foreach ($rawtags as $tag) { + if (strtolower(mb_substr($tag, 0, 4)) == 'for:') { + 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); + } + } + + // + + $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); + } + + // 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, + $shortUrl, + $description, + implode(' ', $hashtags)); + + $rendered = sprintf(_(''. + '%s '. + '%s '. + '%s'. + ''), + htmlspecialchars($url), + htmlspecialchars($title), + htmlspecialchars($description), + implode(' ', $taglinks)); + + $options = array_merge(array('urls' => array($url), + 'rendered' => $rendered, + 'tags' => $tags, + 'replies' => $replies), + $options); + + 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); + + return $saved; + } +} diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php new file mode 100644 index 0000000000..470ddaa0ce --- /dev/null +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -0,0 +1,622 @@ +. + * + * @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 +{ + const VERSION = '0.1'; + + /** + * 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('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'), + new ColumnDef('uri', + 'varchar', + 255, + false, + 'UNI'), + new ColumnDef('url_crc32', + 'integer unsigned', + 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 Bookmark + * + * @param Notice $notice Notice being deleted + * + * @return boolean hook value + */ + + function onNoticeDeleteRelated($notice) + { + $nb = Bookmark::getByNotice($notice); + + if (!empty($nb)) { + $nb->delete(); + } + + return true; + } + + /** + * Show the CSS necessary for this plugin + * + * @param Action $action the action being run + * + * @return boolean hook value + */ + + function onEndShowStyles($action) + { + $action->cssLink('plugins/Bookmark/bookmark.css'); + 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 'ShowbookmarkAction': + case 'NewbookmarkAction': + case 'BookmarkpopupAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + case 'Bookmark': + include_once $dir.'/'.$cls.'.php'; + return false; + case 'BookmarkForm': + case 'DeliciousBackupImporter': + case 'DeliciousBookmarkImporter': + 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]+')); + + $m->connect('main/bookmark/popup', array('action' => 'bookmarkpopup')); + + $m->connect('bookmark/:user/:created/:crc32', + array('action' => 'showbookmark'), + array('user' => '[0-9]+', + 'created' => '[0-9]{14}', + 'crc32' => '[0-9a-f]{8}')); + + 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 = Bookmark::getByNotice($nli->notice); + + if (!empty($nb)) { + + $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; + } + + $att = $atts[0]; + + // 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')); + + // Replies look like "for:" tags + + $replies = $nli->notice->getReplies(); + + if (!empty($replies)) { + foreach ($replies as $reply) { + $other = Profile::staticGet('id', $reply); + $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(' '); + } + } + + $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); + + if (common_config('attachments', 'show_thumbs')) { + $al = new InlineAttachmentList($notice, $out); + $al->show(); + } + + $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; + } + + /** + * 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) + { + common_log(LOG_INFO, + "Checking {$notice->uri} to see if it's a bookmark."); + + $nb = Bookmark::getByNotice($notice); + + 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; + $object->summary = $nb->description; + $object->link = $notice->bestUrl(); + + // 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, null); + + // 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, null); + } + + return false; + } + + 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'); + $qm->connect('dlcsbkmk', 'DeliciousBookmarkImporter'); + 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; + } + + /** + * 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; + } + + /** + * 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 (self::_isPostBookmark($activity)) { + + 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; + } + + /** + * 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 (self::_isPostBookmark($activity)) { + + $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; + } + + 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) { + 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); + } + + if (!empty($activity->time)) { + $options['created'] = common_sql_date($activity->time); + } + + // 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'] = array(); + $options['replies'] = array(); + + foreach ($replies as $replyURI) { + $other = Profile::fromURI($replyURI); + if (!empty($other)) { + $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? + + if (!empty($activity->context->replyToID)) { + $orig = Notice::staticGet('uri', + $activity->context->replyToID); + if (!empty($orig)) { + $options['reply_to'] = $orig->id; + } + } + + 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); + } +} + diff --git a/plugins/Bookmark/bookmark.css b/plugins/Bookmark/bookmark.css new file mode 100644 index 0000000000..27d716da7f --- /dev/null +++ b/plugins/Bookmark/bookmark.css @@ -0,0 +1,3 @@ +.bookmark_tags li { display: inline; } +.bookmark_mentions li { display: inline; } +.bookmark_avatar { float: left } diff --git a/plugins/Bookmark/bookmarkform.php b/plugins/Bookmark/bookmarkform.php new file mode 100644 index 0000000000..b99568e154 --- /dev/null +++ b/plugins/Bookmark/bookmarkform.php @@ -0,0 +1,164 @@ +. + * + * @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; + + /** + * 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 + */ + + 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_settings'; + } + + /** + * 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/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.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 new file mode 100644 index 0000000000..24ed79612b --- /dev/null +++ b/plugins/Bookmark/bookmarkpopup.php @@ -0,0 +1,112 @@ +. + * + * @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 +{ + /** + * Show the title section of the window + * + * @return void + */ + + 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'))); + } + + /** + * 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')); + $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'); + } + + /** + * Hide the core section of the page + * + * @return void + */ + + function showCore() + { + } + + /** + * Hide the footer section of the page + * + * @return void + */ + + function showFooter() + { + } + + function showScripts() + { + parent::showScripts(); + $this->script(common_path('plugins/Bookmark/bookmarkpopup.js')); + } +} diff --git a/plugins/Bookmark/deliciousbackupimporter.php b/plugins/Bookmark/deliciousbackupimporter.php new file mode 100644 index 0000000000..1b55115d6d --- /dev/null +++ b/plugins/Bookmark/deliciousbackupimporter.php @@ -0,0 +1,196 @@ +. + * + * @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 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 + * + * 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 array $data pair of user, text + * + * @return boolean success value + */ + + function handle($data) + { + list($user, $body) = $data; + + $doc = $this->importHTML($body); + + $dls = $doc->getElementsByTagName('dl'); + + if ($dls->length != 1) { + throw new ClientException(_("Bad import file.")); + } + + $dl = $dls->item(0); + + $children = $dl->childNodes; + + $dt = null; + + for ($i = 0; $i < $children->length; $i++) { + try { + $child = $children->item($i); + if ($child->nodeType != XML_ELEMENT_NODE) { + continue; + } + 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; + } + } + + return true; + } + + /** + * 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 + */ + + 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 < $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; + } + } + } + + $qm = QueueManager::get(); + + $qm->enqueue(array($user, $dt, $dd), 'dlcsbkmk'); + } + + /** + * 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); + error_reporting($old); + + if ($ok) { + return $dom; + } else { + return null; + } + } +} diff --git a/plugins/Bookmark/deliciousbookmarkimporter.php b/plugins/Bookmark/deliciousbookmarkimporter.php new file mode 100644 index 0000000000..061572d95a --- /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 = Bookmark::saveNew($user->getProfile(), + $title, + $url, + $tags, + $description, + array('created' => $created)); + + return true; + } +} diff --git a/plugins/Bookmark/importbookmarks.php b/plugins/Bookmark/importbookmarks.php new file mode 100644 index 0000000000..5518b00e97 --- /dev/null +++ b/plugins/Bookmark/importbookmarks.php @@ -0,0 +1,96 @@ +. + * + * @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='); + +$helptext = <<enqueue(array($user, $html), 'dlcsback'); + +} catch (Exception $e) { + print $e->getMessage()."\n"; + exit(1); +} diff --git a/plugins/Bookmark/newbookmark.php b/plugins/Bookmark/newbookmark.php new file mode 100644 index 0000000000..a0cf3fffb2 --- /dev/null +++ b/plugins/Bookmark/newbookmark.php @@ -0,0 +1,196 @@ +. + * + * @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 +{ + 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 + * + * @return string Action title + */ + + 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.')); + } + + + $saved = Bookmark::saveNew($this->user->getProfile(), + $this->title, + $this->url, + $this->tags, + $this->description); + + } 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; + } + } +} + \ No newline at end of file diff --git a/plugins/Bookmark/showbookmark.php b/plugins/Bookmark/showbookmark.php new file mode 100644 index 0000000000..07dec5a98c --- /dev/null +++ b/plugins/Bookmark/showbookmark.php @@ -0,0 +1,132 @@ +. + * + * @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); + + sscanf($this->trimmed('crc32'), '%08x', $crc32); + + if (empty($crc32)) { + throw new ClientException(_('No such URL.'), 404); + } + + $dt = DateTime::createFromFormat('YmdHis', + $this->trimmed('created'), + new DateTimeZone('UTC')); + + if (empty($dt)) { + throw new ClientException(_('No such create date.'), 404); + } + + $bookmarks = Bookmark::getByCRC32($this->profile, + $crc32); + + foreach ($bookmarks as $bookmark) { + $bdt = new DateTime($bookmark->created, new DateTimeZone('UTC')); + 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; + } + + 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'); + } +} 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/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; + } } /** 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)); } }