From 4c853f8117dcc47d689d2fcccc63ed457f110abd Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 24 May 2012 14:31:42 +0200 Subject: [PATCH 01/17] Fixes issue #3612 with Twitter avatars that lack extension --- plugins/TwitterBridge/twitterimport.php | 26 +++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php index 1c362d6fe8..2b8055b126 100644 --- a/plugins/TwitterBridge/twitterimport.php +++ b/plugins/TwitterBridge/twitterimport.php @@ -339,10 +339,7 @@ class TwitterImport { global $config; - $path_parts = pathinfo($twitter_user->profile_image_url); - - $newname = 'Twitter_' . $twitter_user->id . '_' . - $path_parts['basename']; + $newname = 'Twitter_' . $twitter_user->id . '_' . basename($twitter_user->profile_image_url); $oldname = $profile->getAvatar(48)->filename; @@ -370,15 +367,15 @@ class TwitterImport $path_parts = pathinfo($twitter_user->profile_image_url); - $img_root = substr($path_parts['basename'], 0, -11); - $ext = $path_parts['extension']; + $ext = (isset($path_parts['extension']) ? '.'.$path_parts['extension'] : ''); // some lack extension + $img_root = basename($path_parts['basename'], '_normal'.$ext); // cut off extension $mediatype = $this->getMediatype($ext); foreach (array('mini', 'normal', 'bigger') as $size) { $url = $path_parts['dirname'] . '/' . - $img_root . '_' . $size . ".$ext"; + $img_root . '_' . $size . $ext; $filename = 'Twitter_' . $twitter_user->id . '_' . - $img_root . "_$size.$ext"; + $img_root . '_' . $size . $ext; $this->updateAvatar($profile->id, $size, $mediatype, $filename); $this->fetchAvatar($url, $filename); @@ -401,10 +398,10 @@ class TwitterImport $mediatype = null; switch (strtolower($ext)) { - case 'jpg': + case '.jpg': $mediatype = 'image/jpg'; break; - case 'gif': + case '.gif': $mediatype = 'image/gif'; break; default: @@ -419,16 +416,15 @@ class TwitterImport global $config; $path_parts = pathinfo($user->profile_image_url); - $ext = $path_parts['extension']; - $end = strlen('_normal' . $ext); - $img_root = substr($path_parts['basename'], 0, -($end+1)); + $ext = (isset($path_parts['extension']) ? '.'.$path_parts['extension'] : ''); + $img_root = basename($path_parts['basename'], '_normal'.$ext); $mediatype = $this->getMediatype($ext); foreach (array('mini', 'normal', 'bigger') as $size) { $url = $path_parts['dirname'] . '/' . - $img_root . '_' . $size . ".$ext"; + $img_root . '_' . $size . $ext; $filename = 'Twitter_' . $user->id . '_' . - $img_root . "_$size.$ext"; + $img_root . '_' . $size . $ext; if ($this->fetchAvatar($url, $filename)) { $this->newAvatar($id, $size, $mediatype, $filename); From b2a91944bbab6fd5a1741d7b0bcd08c983ab04c5 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 24 May 2012 23:08:40 +0200 Subject: [PATCH 02/17] retaining compatibility with previous TwitterBridge getMediatype --- plugins/TwitterBridge/twitterimport.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php index 2b8055b126..4d165d4dd2 100644 --- a/plugins/TwitterBridge/twitterimport.php +++ b/plugins/TwitterBridge/twitterimport.php @@ -369,7 +369,7 @@ class TwitterImport $ext = (isset($path_parts['extension']) ? '.'.$path_parts['extension'] : ''); // some lack extension $img_root = basename($path_parts['basename'], '_normal'.$ext); // cut off extension - $mediatype = $this->getMediatype($ext); + $mediatype = $this->getMediatype(substr($ext, 1)); foreach (array('mini', 'normal', 'bigger') as $size) { $url = $path_parts['dirname'] . '/' . @@ -398,10 +398,10 @@ class TwitterImport $mediatype = null; switch (strtolower($ext)) { - case '.jpg': + case 'jpg': $mediatype = 'image/jpg'; break; - case '.gif': + case 'gif': $mediatype = 'image/gif'; break; default: @@ -418,7 +418,7 @@ class TwitterImport $path_parts = pathinfo($user->profile_image_url); $ext = (isset($path_parts['extension']) ? '.'.$path_parts['extension'] : ''); $img_root = basename($path_parts['basename'], '_normal'.$ext); - $mediatype = $this->getMediatype($ext); + $mediatype = $this->getMediatype(substr($ext, 1)); foreach (array('mini', 'normal', 'bigger') as $size) { $url = $path_parts['dirname'] . '/' . From bd8178592c60e8be4733b43e5fdb72aebf6d42f7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 24 May 2012 23:09:56 +0200 Subject: [PATCH 03/17] adding the odd but reported Twitter avatar .jpeg file extension --- plugins/TwitterBridge/twitterimport.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php index 4d165d4dd2..85fcaf08fe 100644 --- a/plugins/TwitterBridge/twitterimport.php +++ b/plugins/TwitterBridge/twitterimport.php @@ -398,6 +398,7 @@ class TwitterImport $mediatype = null; switch (strtolower($ext)) { + case 'jpeg': case 'jpg': $mediatype = 'image/jpg'; break; From fcb1b115fcf972e608703c1f81960b09c6fdd83d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 24 May 2012 23:24:53 +0200 Subject: [PATCH 04/17] MIME type for jpeg is with an e --- plugins/TwitterBridge/twitterimport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php index 85fcaf08fe..0bf266b1a8 100644 --- a/plugins/TwitterBridge/twitterimport.php +++ b/plugins/TwitterBridge/twitterimport.php @@ -400,7 +400,7 @@ class TwitterImport switch (strtolower($ext)) { case 'jpeg': case 'jpg': - $mediatype = 'image/jpg'; + $mediatype = 'image/jpeg'; break; case 'gif': $mediatype = 'image/gif'; From d36f4436665bea3768f84cd92499ab638f1c24b2 Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Fri, 14 Sep 2012 17:37:42 +0200 Subject: [PATCH 05/17] Bookmark plugin enhancement: display Bookmark's list. Integration of @chimo's work (http://http://sn.chromic.org/) from https://github.com/chimo/BookmarkList into official plugin. --- plugins/Bookmark/BookmarkPlugin.php | 64 ++++- plugins/Bookmark/apitimelinebookmarks.php | 268 +++++++++++++++++++++ plugins/Bookmark/bookmarks.php | 233 ++++++++++++++++++ plugins/Bookmark/bookmarksnoticestream.php | 80 ++++++ plugins/Bookmark/bookmarksrss.php | 137 +++++++++++ 5 files changed, 780 insertions(+), 2 deletions(-) create mode 100644 plugins/Bookmark/apitimelinebookmarks.php create mode 100644 plugins/Bookmark/bookmarks.php create mode 100644 plugins/Bookmark/bookmarksnoticestream.php create mode 100644 plugins/Bookmark/bookmarksrss.php diff --git a/plugins/Bookmark/BookmarkPlugin.php b/plugins/Bookmark/BookmarkPlugin.php index 319366c19b..f644289ed4 100644 --- a/plugins/Bookmark/BookmarkPlugin.php +++ b/plugins/Bookmark/BookmarkPlugin.php @@ -148,6 +148,9 @@ class BookmarkPlugin extends MicroAppPlugin switch ($cls) { + case 'BookmarksAction': + case 'BookmarksrssAction': + case 'ApiTimelineBookmarksAction': case 'ShowbookmarkAction': case 'NewbookmarkAction': case 'BookmarkpopupAction': @@ -180,6 +183,26 @@ class BookmarkPlugin extends MicroAppPlugin */ function onRouterInitialized($m) { + if (common_config('singleuser', 'enabled')) { + $nickname = User::singleUserNickname(); + $m->connect('bookmarks', + array('action' => 'bookmarks', 'nickname' => $nickname)); + $m->connect('bookmarks/rss', + array('action' => 'bookmarksrss', 'nickname' => $nickname)); + } else { + $m->connect(':nickname/bookmarks', + array('action' => 'bookmarks'), + array('nickname' => Nickname::DISPLAY_FMT)); + $m->connect(':nickname/bookmarks/rss', + array('action' => 'bookmarksrss'), + array('nickname' => Nickname::DISPLAY_FMT)); + } + + $m->connect('api/bookmarks/:id.:format', + array('action' => 'ApiTimelineBookmarks', + 'id' => Nickname::INPUT_FMT, + 'format' => '(xml|json|rss|atom|as)')); + $m->connect('main/bookmark/new', array('action' => 'newbookmark'), array('id' => '[0-9]+')); @@ -230,11 +253,13 @@ class BookmarkPlugin extends MicroAppPlugin { $versions[] = array('name' => 'Bookmark', 'version' => self::VERSION, - 'author' => 'Evan Prodromou', + 'author' => 'Evan Prodromou, Stephane Berube, Jean Baptiste Favre', 'homepage' => 'http://status.net/wiki/Plugin:Bookmark', 'description' => // TRANS: Plugin description. - _m('Simple extension for supporting bookmarks.')); + _m('Simple extension for supporting bookmarks. ') . + 'BookmarkList feature has been developped by Stephane Berube. ' . + 'Integration has been done by Jean Baptiste Favre.'); return true; } @@ -315,6 +340,41 @@ class BookmarkPlugin extends MicroAppPlugin return false; } + /** + * Modify the default menu to link to our custom action + * + * Using event handlers, it's possible to modify the default UI for pages + * almost without limit. In this method, we add a menu item to the default + * primary menu for the interface to link to our action. + * + * The Action class provides a rich set of events to hook, as well as output + * methods. + * + * @param Action $action The current action handler. Use this to + * do any output. + * + * @return boolean hook value; true means continue processing, false means stop. + * + * @see Action + */ + function onEndPersonalGroupNav($action) + { + $this->user = common_current_user(); + + if (!$this->user) { + // TRANS: Client error displayed when trying to display bookmarks for a non-existing user. + $this->clientError(_('No such user.')); + return false; + } + + $action->menuItem(common_local_url('bookmarks', array('nickname' => $this->user->nickname)), + // TRANS: Menu item in sample plugin. + _m('Bookmarks'), + // TRANS: Menu item title in sample plugin. + _m('A list of your bookmarks'), false, 'nav_timeline_bookmarks'); + return true; + } + /** * Save a remote bookmark (from Salmon or PuSH) * diff --git a/plugins/Bookmark/apitimelinebookmarks.php b/plugins/Bookmark/apitimelinebookmarks.php new file mode 100644 index 0000000000..1753462350 --- /dev/null +++ b/plugins/Bookmark/apitimelinebookmarks.php @@ -0,0 +1,268 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @copyright 2009-2010 StatusNet, Inc. + * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/apibareauth.php'; +require_once 'bookmarksnoticestream.php'; + +/** + * Returns the 20 most recent favorite notices for the authenticating user or user + * specified by the ID parameter in the requested format. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ApiTimelineBookmarksAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + // TRANS: Client error displayed when requesting most recent favourite notices by a user for a non-existing user. + $this->clientError(_('No such user.'), 404, $this->format); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + function showTimeline() + { + $profile = $this->user->getProfile(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + + $sitename = common_config('site', 'name'); + $title = sprintf( + // TRANS: Title for timeline of most recent favourite notices by a user. + // TRANS: %1$s is the StatusNet sitename, %2$s is a user nickname. + _('%1$s / Bookmarks from %2$s'), + $sitename, + $this->user->nickname + ); + + $taguribase = TagURI::base(); + $id = "tag:$taguribase:Bookmarks:" . $this->user->id; + + $subtitle = sprintf( + // TRANS: Subtitle for timeline of most recent favourite notices by a user. + // TRANS: %1$s is the StatusNet sitename, %2$s is a user's full name, + // TRANS: %3$s is a user nickname. + _('%1$s updates bookmarked by %2$s / %3$s.'), + $sitename, + $profile->getBestName(), + $this->user->nickname + ); + $logo = !empty($avatar) + ? $avatar->displayUrl() + : Avatar::defaultImage(AVATAR_PROFILE_SIZE); + + $link = common_local_url( + 'bookmarks', + array('nickname' => $this->user->nickname) + ); + + $self = $this->getSelfUri(); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline( + $this->notices, + $title, + $link, + $subtitle, + null, + $logo, + $self + ); + break; + case 'atom': + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed($this->auth_user); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + + $atom->addLink($link); + $atom->setSelfLink($self); + + $atom->addEntryFromNotices($this->notices); + + $this->raw($atom->getString()); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + case 'as': + header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->setTitle($title); + $doc->addLink($link,'alternate', 'text/html'); + $doc->addItemsFromNotices($this->notices); + $this->raw($doc->asString()); + break; + default: + // TRANS: Client error displayed when coming across a non-supported API method. + $this->clientError(_('API method not found.'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + function getNotices() + { + $notices = array(); + + common_debug("since id = " . $this->since_id . " max id = " . $this->max_id); + + $notice = new BookmarksNoticeStream($this->user->id, true); + $notice = $notice->getNotices( + ($this->page-1) * $this->count, + $this->count, + $this->since_id, + $this->max_id + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_user_cache_hash($this->auth_user), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } +} diff --git a/plugins/Bookmark/bookmarks.php b/plugins/Bookmark/bookmarks.php new file mode 100644 index 0000000000..6faf99929c --- /dev/null +++ b/plugins/Bookmark/bookmarks.php @@ -0,0 +1,233 @@ + + * @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); +} + +require_once 'bookmarksnoticestream.php'; + +/** + * List currently logged-in user's bookmakrs + * + * @category Bookmark + * @package StatusNet + * @author Stephane Berube + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link https://github.com/chimo/BookmarkList + */ +class BookmarksAction extends Action +{ + var $user = null; + var $gc = null; + + /** + * Take arguments for running + * + * This method is called first, and it lets the action class get + * all its arguments and validate them. It's also the time + * to fetch any relevant data from the database. + * + * Action classes should run parent::prepare($args) as the first + * line of this method to make sure the default argument-processing + * happens. + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + + if (common_config('singleuser', 'enabled')) { + $nickname = User::singleUserNickname(); + } else { + // PHP 5.4 + // $nickname = $this->returnToArgs()[1]['nickname']; + + // PHP < 5.4 + $nickname = $this->returnToArgs(); + $nickname = $nickname[1]['nickname']; + } + + $this->user = User::staticGet('nickname', $nickname); + + if (!$this->user) { + // TRANS: Client error displayed when trying to display bookmarks for a non-existing user. + $this->clientError(_('No such user.')); + return false; + } + + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + $stream = new BookmarksNoticeStream($this->user->id, true); + $this->notices = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1); + + if($this->page > 1 && $this->notices->N == 0) { + throw new ClientException(_('No such page.'), 404); + } + + return true; + } + + /** + * Handle request + * + * This is the main method for handling a request. Note that + * most preparation should be done in the prepare() method; + * by the time handle() is called the action should be + * more or less ready to go. + * + * @param array $args $_REQUEST args; handled in prepare() + * + * @return void + */ + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + /** + * Title of this page + * + * Override this method to show a custom title. + * + * @return string Title of the page + */ + function title() + { + + if (empty($this->user)) { + // TRANS: Page title for sample plugin. + return _m('Log in'); + } else { + // TRANS: Page title for sample plugin. %s is a user nickname. + return sprintf(_m('%s\'s bookmarks'), $this->user->nickname); + } + } + + /** + * Feeds for the section + * + * @return array Feed objects to show + */ + function getFeeds() + { + return array(new Feed(Feed::JSON, + common_local_url('ApiTimelineBookmarks', + array( + 'id' => $this->user->nickname, + 'format' => 'as')), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (Activity Streams JSON)'), + $this->user->nickname)), + new Feed(Feed::RSS1, + common_local_url('bookmarksrss', + array('nickname' => $this->user->nickname)), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (RSS 1.0)'), + $this->user->nickname)), + new Feed(Feed::RSS2, + common_local_url('ApiTimelineBookmarks', + array( + 'id' => $this->user->nickname, + 'format' => 'rss')), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (RSS 2.0)'), + $this->user->nickname)), + new Feed(Feed::ATOM, + common_local_url('ApiTimelineBookmarks', + array( + 'id' => $this->user->nickname, + 'format' => 'atom')), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (Atom)'), + $this->user->nickname))); + } + + /** + * Show content in the content area + * + * The default StatusNet page has a lot of decorations: menus, + * logos, tabs, all that jazz. This method is used to show + * content in the content area of the page; it's the main + * thing you want to overload. + * + * This method also demonstrates use of a plural localized string. + * + * @return void + */ + function showContent() + { + + $nl = new NoticeList($this->notices, $this); + + $cnt = $nl->show(); + + if ($cnt == 0) { + $this->showEmptyList(); + } + + $this->pagination($this->page > 1, + $cnt > NOTICES_PER_PAGE, + $this->page, 'bookmarks', + array('nickname' => $this->user->nickname)); + } + + function showEmptyList() { + $message = sprintf(_('This is %1$s\'s bookmark stream, but %1$s hasn\'t bookmarked anything yet.'), $this->user->nickname) . ' '; + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + /** + * Return true if read only. + * + * Some actions only read from the database; others read and write. + * The simple database load-balancer built into StatusNet will + * direct read-only actions to database mirrors (if they are configured), + * and read-write actions to the master database. + * + * This defaults to false to avoid data integrity issues, but you + * should make sure to overload it for performance gains. + * + * @param array $args other arguments, if RO/RW status depends on them. + * + * @return boolean is read only action? + */ + function isReadOnly($args) + { + return true; + } +} diff --git a/plugins/Bookmark/bookmarksnoticestream.php b/plugins/Bookmark/bookmarksnoticestream.php new file mode 100644 index 0000000000..a3ac0359d7 --- /dev/null +++ b/plugins/Bookmark/bookmarksnoticestream.php @@ -0,0 +1,80 @@ +user_id = $user_id; + $this->own = $own; + } + + function getNoticeIds($offset, $limit, $since_id, $max_id) + { + $notice = new Notice(); + $qry = null; + + $qry = 'SELECT notice.* FROM notice '; + $qry .= 'INNER JOIN bookmark ON bookmark.uri = notice.uri '; + $qry .= 'WHERE bookmark.profile_id = ' . $this->user_id . ' '; + $qry .= 'AND notice.is_local != ' . Notice::GATEWAY . ' '; + + if ($since_id != 0) { + $qry .= 'AND notice.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice.id <= ' . $max_id . ' '; + } + + // NOTE: we sort by bookmark time, not by notice time! + $qry .= 'ORDER BY created DESC '; + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $notice->query($qry); + $ids = array(); + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + unset($notice); + return $ids; + } +} + +/** + * Notice stream for bookmarks + * + * @category Stream + * @package StatusNet + * @author Stephane Berube + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class BookmarksNoticeStream extends ScopingNoticeStream +{ + function __construct($user_id, $own, $profile = -1) + { + $stream = new RawBookmarksNoticeStream($user_id, $own); + + if ($own) { + $key = 'bookmark:ids_by_user_own:'.$user_id; + } else { + $key = 'bookmark:ids_by_user:'.$user_id; + } + + if (is_int($profile) && $profile == -1) { + $profile = Profile::current(); + } + + parent::__construct(new CachingNoticeStream($stream, $key), + $profile); + } +} diff --git a/plugins/Bookmark/bookmarksrss.php b/plugins/Bookmark/bookmarksrss.php new file mode 100644 index 0000000000..63534cac79 --- /dev/null +++ b/plugins/Bookmark/bookmarksrss.php @@ -0,0 +1,137 @@ + + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 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') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/rssaction.php'; +require_once 'bookmarksnoticestream.php'; + +/** + * RSS feed for user bookmarks action class. + * + * Formatting of RSS handled by Rss10Action + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @author Stephane Berube (modified 'favoritesrss.php' to show bookmarks instead) + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ +class BookmarksrssAction extends Rss10Action +{ + /** The user whose bookmarks to display */ + + var $user = null; + + /** + * Find the user to display by supplied nickname + * + * @param array $args Arguments from $_REQUEST + * + * @return boolean success + */ + function prepare($args) + { + parent::prepare($args); + + $nickname = $this->trimmed('nickname'); + $this->user = User::staticGet('nickname', $nickname); + + if (!$this->user) { + // TRANS: Client error displayed when trying to get the RSS feed with bookmarks of a user that does not exist. + $this->clientError(_('No such user.')); + return false; + } else { + $this->notices = $this->getNotices($this->limit); + return true; + } + } + + /** + * Get notices + * + * @param integer $limit max number of notices to return + * + * @return array notices + */ + function getNotices($limit=0) + { + $user = $this->user; + + $notice = new BookmarksNoticeStream($this->user->id, true); + $notice = $notice->getNotices(0, NOTICES_PER_PAGE); + + $notices = array(); + while ($notice->fetch()) { + $notices[] = clone($notice); + } + return $notices; + } + + /** + * Get channel. + * + * @return array associative array on channel information + */ + function getChannel() + { + $user = $this->user; + $c = array('url' => common_local_url('bookmarksrss', + array('nickname' => + $user->nickname)), + // TRANS: Title of RSS feed with bookmarks of a user. + // TRANS: %s is a user's nickname. + 'title' => sprintf(_("%s's bookmarks"), $user->nickname), + 'link' => common_local_url('bookmarks', + array('nickname' => + $user->nickname)), + // TRANS: Desciption of RSS feed with bookmarks of a user. + // TRANS: %1$s is a user's nickname, %2$s is the name of the StatusNet site. + 'description' => sprintf(_('Bookmarks posted by %1$s on %2$s!'), + $user->nickname, common_config('site', 'name'))); + return $c; + } + + /** + * Get image. + * + * @return void + */ + function getImage() + { + return null; + } + +} From 368906258ad338c6307e1df1f7e6280f7d849dc5 Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Wed, 29 Aug 2012 21:36:55 +0200 Subject: [PATCH 06/17] You need an API key when using embed.ly. Unfortunatly oembedhelper.php does not support it. This commit aims to fix it. --- lib/oembedhelper.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/oembedhelper.php b/lib/oembedhelper.php index 903f80c581..6b5b8d34f2 100644 --- a/lib/oembedhelper.php +++ b/lib/oembedhelper.php @@ -216,6 +216,10 @@ class oEmbedHelper { $params['url'] = $url; $params['format'] = 'json'; + $key=common_config('oembed','apikey'); + if(isset($key)) { + $params['key'] = common_config('oembed','apikey'); + } $data = self::json($api, $params); return self::normalize($data); } From d48076253b02f8ab1f91e54289a189af7017db9f Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Mon, 27 Aug 2012 19:41:28 +0200 Subject: [PATCH 07/17] Fix error 'No matches for action subscriptions with arguments nickname...' when displaying remote profile. --- plugins/ModPlus/remoteprofileaction.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/ModPlus/remoteprofileaction.php b/plugins/ModPlus/remoteprofileaction.php index 7c9a4d1473..240ce6d488 100644 --- a/plugins/ModPlus/remoteprofileaction.php +++ b/plugins/ModPlus/remoteprofileaction.php @@ -96,8 +96,7 @@ class RemoteProfileAction extends ShowstreamAction function showSections() { - ProfileAction::showSections(); - // skip tag cloud + // skip } function showStatistics() From 5a0f17933bc41fed06beb35fa08c6d7e6e4a7922 Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Mon, 27 Aug 2012 21:02:53 +0200 Subject: [PATCH 08/17] Display notices for remote profile. Would like to hide avatar like in local profile but did not found how to do it. --- plugins/ModPlus/remoteprofileaction.php | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/plugins/ModPlus/remoteprofileaction.php b/plugins/ModPlus/remoteprofileaction.php index 240ce6d488..59626b7375 100644 --- a/plugins/ModPlus/remoteprofileaction.php +++ b/plugins/ModPlus/remoteprofileaction.php @@ -31,6 +31,15 @@ class RemoteProfileAction extends ShowstreamAction $this->tag = $this->trimmed('tag'); $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; common_set_returnto($this->selfUrl()); + + $p = Profile::current(); + if (empty($this->tag)) { + $stream = new ProfileNoticeStream($this->profile, $p); + } else { + $stream = new TaggedProfileNoticeStream($this->profile, $this->tag, $p); + } + $this->notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); + return true; } @@ -71,6 +80,25 @@ class RemoteProfileAction extends ShowstreamAction // TRANS: Message on blocked remote profile page. $markdown = _m('Site moderators have silenced this profile, which prevents delivery of new messages to any users on this site.'); $this->raw(common_markup_to_html($markdown)); + }else{ + + $pnl = null; + if (Event::handle('ShowStreamNoticeList', array($this->notice, $this, &$pnl))) { + $pnl = new ProfileNoticeList($this->notice, $this); + } + $cnt = $pnl->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + + $args = array('id' => $this->profile->id); + if (!empty($this->tag)) + { + $args['tag'] = $this->tag; + } + $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page, + 'remoteprofile', $args); + } } From d1e46e61ac9f5aa557cdf970ad8d2c0dd8b6b72f Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Tue, 28 Aug 2012 00:21:41 +0200 Subject: [PATCH 09/17] Add same CSS rules for #remoteprofile than for #showstream. Allows to hide avatars, like for local profiles. --- theme/base/css/display.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index d2d07c4cec..b0b4f26098 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -688,18 +688,22 @@ font-style:italic; display:none; } +#remoteprofile .notice .entry-title, #remoteprofile .notice div.entry-content, #showstream .notice .entry-title, #showstream .notice div.entry-content { margin-left: 0; } +#remoteprofile .notice .entry-title, #showstream .notice .entry-title { min-height: 1px; } +#remoteprofile #content .notice .author, #showstream #content .notice .author { display: none; } +#remoteprofile .notice, #showstream .notice { min-height: 1em; } From 93c8969a27f37f108c9be1f4d228271887741201 Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Tue, 28 Aug 2012 00:25:53 +0200 Subject: [PATCH 10/17] Remove alone 'groups' link on the left side. Useless I guess. --- plugins/ModPlus/remoteprofileaction.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/ModPlus/remoteprofileaction.php b/plugins/ModPlus/remoteprofileaction.php index 59626b7375..1d7a84d9f3 100644 --- a/plugins/ModPlus/remoteprofileaction.php +++ b/plugins/ModPlus/remoteprofileaction.php @@ -118,8 +118,7 @@ class RemoteProfileAction extends ShowstreamAction function showLocalNav() { - $nav = new PublicGroupNav($this); - $nav->show(); + // skip } function showSections() From b8a69d023b33173c4a5ee7786c08ba7739386e17 Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Sun, 26 Aug 2012 22:52:21 +0200 Subject: [PATCH 11/17] Add basic support for GetValidDaemon event. Shall be extended with configuration check. --- plugins/Xmpp/XmppPlugin.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index 0f82ed041c..b21afab461 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -433,6 +433,25 @@ class XmppPlugin extends ImPlugin ); } + /** + * Add XMPP plugin daemon to the list of daemon to start + * + * @param array $daemons the list of daemons to run + * + * @return boolean hook return + */ + function onGetValidDaemons($daemons) + { + array_push( + $daemons, + INSTALLDIR + . '/scripts/imdaemon.php' + ); + + return true; + } + + function onPluginVersion(&$versions) { $versions[] = array('name' => 'XMPP', From f175512748f54601cbaa5170100ac970666011be Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Sun, 26 Aug 2012 22:53:09 +0200 Subject: [PATCH 12/17] Remove static definition of imdaemon.php as valid daemon. --- scripts/getvaliddaemons.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 80c21bce58..78cbba8c07 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -39,8 +39,6 @@ $daemons = array(); $daemons[] = INSTALLDIR.'/scripts/queuedaemon.php'; -$daemons[] = INSTALLDIR.'/scripts/imdaemon.php'; - if (Event::handle('GetValidDaemons', array(&$daemons))) { foreach ($daemons as $daemon) { print $daemon . ' '; From 1b39f89b964feee673961e5bdff2266b8716f96e Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Sun, 26 Aug 2012 23:20:03 +0200 Subject: [PATCH 13/17] Add configuration check. Need 'server', 'port', 'user' and 'password' to be defined (not valid, just defined). --- plugins/Xmpp/XmppPlugin.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index b21afab461..74e104d974 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -442,11 +442,17 @@ class XmppPlugin extends ImPlugin */ function onGetValidDaemons($daemons) { - array_push( - $daemons, - INSTALLDIR - . '/scripts/imdaemon.php' - ); + if( isset($this->server) && + isset($this->port) && + isset($this->user) && + isset($this->password) ){ + + array_push( + $daemons, + INSTALLDIR + . '/scripts/imdaemon.php' + ); + } return true; } From ec072e0af7267b772d25c586881661af0d3e2bcb Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Thu, 6 Sep 2012 11:11:33 -0400 Subject: [PATCH 14/17] Notice update with media attachment may fail through API when status text + attachment length get higher than max notice length. Calling URL shortener can make global length less than maxlength, though allowing notice update. --- actions/apistatusesupdate.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index b0f3527160..0385b01961 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -300,6 +300,9 @@ class ApiStatusesUpdateAction extends ApiAuthAction if (isset($upload)) { $status_shortened .= ' ' . $upload->shortUrl(); +//JBTEST + $status_shortened = $this->auth_user->shortenlinks($status_shortened); +//JBTEST if (Notice::contentTooLong($status_shortened)) { $upload->delete(); From 344a10be8b0e405752da7997eae48cb6fe9498a9 Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Thu, 6 Sep 2012 11:16:30 -0400 Subject: [PATCH 15/17] Code cleaning, remove 'TEST' tags. --- actions/apistatusesupdate.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 0385b01961..40a78744b7 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -300,9 +300,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction if (isset($upload)) { $status_shortened .= ' ' . $upload->shortUrl(); -//JBTEST $status_shortened = $this->auth_user->shortenlinks($status_shortened); -//JBTEST if (Notice::contentTooLong($status_shortened)) { $upload->delete(); From 58a26309336b65d851797a36f9b9a67443beaf0f Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Sat, 8 Sep 2012 17:56:19 -0400 Subject: [PATCH 16/17] Code cleaning. Do call shortenLinks only once, right before saving new notice. --- actions/apistatusesupdate.php | 62 ++++++++++++++--------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 40a78744b7..2e1ddc2274 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -231,32 +231,12 @@ class ApiStatusesUpdateAction extends ApiAuthAction return; } - $status_shortened = $this->auth_user->shortenlinks($this->status); - - if (Notice::contentTooLong($status_shortened)) { - // Note: Twitter truncates anything over 140, flags the status - // as "truncated." - - $this->clientError( - sprintf( - // TRANS: Client error displayed exceeding the maximum notice length. - // TRANS: %d is the maximum length for a notice. - _m('That\'s too long. Maximum notice size is %d character.', - 'That\'s too long. Maximum notice size is %d characters.', - Notice::maxContent()), - Notice::maxContent() - ), - 406, - $this->format - ); - - return; - } + /* Do not call shortenlinks until the whole notice has been build */ // Check for commands $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($this->auth_user, $status_shortened); + $cmd = $inter->handle_command($this->auth_user, $this->status); if ($cmd) { if ($this->supported($cmd)) { @@ -299,24 +279,32 @@ class ApiStatusesUpdateAction extends ApiAuthAction } if (isset($upload)) { - $status_shortened .= ' ' . $upload->shortUrl(); - $status_shortened = $this->auth_user->shortenlinks($status_shortened); + $this->status .= ' ' . $upload->shortUrl(); - if (Notice::contentTooLong($status_shortened)) { - $upload->delete(); - // TRANS: Client error displayed exceeding the maximum notice length. - // TRANS: %d is the maximum lenth for a notice. - $msg = _m('Maximum notice size is %d character, including attachment URL.', - 'Maximum notice size is %d characters, including attachment URL.', - Notice::maxContent()); - $this->clientError( - sprintf($msg, Notice::maxContent()), - 400, - $this->format - ); - } + /* Do not call shortenlinks until the whole notice has been build */ } + /* Do call shortenlinks here & check notice length since notice is about to be saved & sent */ + $status_shortened = $this->auth_user->shortenlinks($status_shortened); + + if (Notice::contentTooLong($status_shortened)) { + if (isset($upload)) { + $upload->delete(); + } + // TRANS: Client error displayed exceeding the maximum notice length. + // TRANS: %d is the maximum lenth for a notice. + $msg = _m('Maximum notice size is %d character, including attachment URL.', + 'Maximum notice size is %d characters, including attachment URL.', + Notice::maxContent()); + /* Use HTTP 413 error code (Request Entity Too Large) + * instead of basic 400 for better understanding + */ + $this->clientError(sprintf($msg, Notice::maxContent()), + 413, + $this->format); + } + + $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'); $options = array('reply_to' => $reply_to); From fcdd4d2cf04b783908ccdbdd89673d282eba5e0c Mon Sep 17 00:00:00 2001 From: Jean Baptiste Favre Date: Tue, 11 Sep 2012 15:57:13 +0200 Subject: [PATCH 17/17] Fix introduced bug, trying to shorten an empty status. --- actions/apistatusesupdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 2e1ddc2274..c772f96afc 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -285,7 +285,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction } /* Do call shortenlinks here & check notice length since notice is about to be saved & sent */ - $status_shortened = $this->auth_user->shortenlinks($status_shortened); + $status_shortened = $this->auth_user->shortenlinks($this->status); if (Notice::contentTooLong($status_shortened)) { if (isset($upload)) {