$profile = new Profile();
$tagged = array();
- $cnt = $profile->query(sprintf($qry, $this->id, $this->id, $tag));
+ $cnt = $profile->query(sprintf($qry, $this->id, $this->id, $profile->escape($tag)));
while ($profile->fetch()) {
$tagged[] = clone($profile);
'tag = "%s", tagger = "%s" ' .
'WHERE tag = "%s" ' .
'AND tagger = "%s"';
- $result = $tags->query(sprintf($qry, $new->tag, $new->tagger,
- $orig->tag, $orig->tagger));
+ $result = $tags->query(sprintf($qry,
+ $tags->escape($new->tag),
+ $tags->escape($new->tagger),
+ $tags->escape($orig->tag),
+ $tags->escape($orig->tagger)));
if (!$result) {
common_log_db_error($tags, 'UPDATE', __FILE__);
$profile->query('SELECT profile.* ' .
'FROM profile JOIN profile_tag ' .
'ON profile.id = profile_tag.tagged ' .
- 'WHERE profile_tag.tagger = ' . $tagger . ' ' .
- 'AND profile_tag.tag = "' . $tag . '" ');
+ 'WHERE profile_tag.tagger = ' . $profile->escape($tagger) . ' ' .
+ 'AND profile_tag.tag = "' . $profile->escape($tag) . '" ');
$tagged = array();
while ($profile->fetch()) {
$tagged[] = clone($profile);
$profile = new Profile();
- $cnt = $profile->query(sprintf($qry, $this->id, $tag));
+ $cnt = $profile->query(sprintf($qry, $this->id, $profile->escape($tag)));
return $profile;
}
$profile = new Profile();
- $profile->query(sprintf($qry, $this->id, $tag));
+ $profile->query(sprintf($qry, $this->id, $profile->escape($tag)));
return $profile;
}
{
$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);
}
switch ($cls)
{
+ case 'BookmarksAction':
+ case 'BookmarksrssAction':
+ case 'ApiTimelineBookmarksAction':
case 'ShowbookmarkAction':
case 'NewbookmarkAction':
case 'BookmarkpopupAction':
*/
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]+'));
{
$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;
}
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)
*
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's favorite notices
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category API
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Evan Prodromou <evan@status.net>
+ * @author Zach Copley <zach@status.net>
+ * @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 <candrews@integralblue.com>
+ * @author Evan Prodromou <evan@status.net>
+ * @author Zach Copley <zach@status.net>
+ * @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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Give a warm greeting to our friendly user
+ *
+ * PHP version 5
+ *
+ * @category Bookmark
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require_once 'bookmarksnoticestream.php';
+
+/**
+ * List currently logged-in user's bookmakrs
+ *
+ * @category Bookmark
+ * @package StatusNet
+ * @author Stephane Berube <chimo@chromic.org>
+ * @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 <head> 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;
+ }
+}
--- /dev/null
+<?php
+
+class RawBookmarksNoticeStream extends NoticeStream
+{
+ protected $user_id;
+ protected $own;
+
+ function __construct($user_id, $own)
+ {
+ $this->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 <chimo@chromic.org>
+ * @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);
+ }
+}
--- /dev/null
+<?php
+/**
+ * RSS feed for user bookmarks action class.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Robin Millette <millette@status.net>
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <evan@status.net>
+ * @author Robin Millette <millette@status.net>
+ * @author Zach Copley <zach@status.net>
+ * @author Stephane Berube <chimo@chromic.org> (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;
+ }
+
+}
$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;
}
// 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);
+
}
}
function showLocalNav()
{
- $nav = new PublicGroupNav($this);
- $nav->show();
+ // skip
}
function showSections()
{
- ProfileAction::showSections();
- // skip tag cloud
+ // skip
}
function showStatistics()
return false;
case 'TwitterOAuthClient':
case 'TwitterQueueHandler':
+ case 'TweetInQueueHandler':
case 'TwitterImport':
case 'JsonStreamReader':
case 'TwitterStreamReader':
// a new connection if there isn't one already
$conn = &$flink->getDatabaseConnection();
- $this->getTimeline($flink);
+ $this->getTimeline($flink, 'home_timeline');
+ $this->getTimeline($flink, 'mentions_timeline');
$flink->last_friendsync = common_sql_now();
$flink->update();
unset($_DB_DATAOBJECT['CONNECTIONS']);
}
- function getTimeline($flink)
+ function getTimeline($flink, $timelineUri = 'home_timeline')
{
if (empty($flink)) {
- common_log(LOG_WARNING, $this->name() .
+ common_log(LOG_ERR, $this->name() .
" - Can't retrieve Foreign_link for foreign ID $fid");
return;
}
- common_debug($this->name() . ' - Trying to get timeline for Twitter user ' .
- $flink->foreign_id);
+ common_log(LOG_DEBUG, $this->name() . ' - Trying to get ' . $timelineUri .
+ ' timeline for Twitter user ' . $flink->foreign_id);
$client = null;
if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
$token = TwitterOAuthClient::unpackToken($flink->credentials);
$client = new TwitterOAuthClient($token->key, $token->secret);
- common_debug($this->name() . ' - Grabbing friends timeline with OAuth.');
+ common_log(LOG_DEBUG, $this->name() . ' - Grabbing ' . $timelineUri . ' timeline with OAuth.');
} else {
- common_debug("Skipping friends timeline for $flink->foreign_id since not OAuth.");
+ common_log(LOG_ERR, "Skipping " . $timelineUri . " timeline for " .
+ $flink->foreign_id . " since not OAuth.");
}
$timeline = null;
- $lastId = Twitter_synch_status::getLastId($flink->foreign_id, 'home_timeline');
+ $lastId = Twitter_synch_status::getLastId($flink->foreign_id, $timelineUri);
- common_debug("Got lastId value '{$lastId}' for foreign id '{$flink->foreign_id}' and timeline 'home_timeline'");
+ common_log(LOG_DEBUG, "Got lastId value '" . $lastId . "' for foreign id '" .
+ $flink->foreign_id . "' and timeline '" . $timelineUri. "'");
try {
- $timeline = $client->statusesHomeTimeline($lastId);
+ $timeline = $client->statusesTimeline($lastId, $timelineUri);
} catch (Exception $e) {
- common_log(LOG_WARNING, $this->name() .
- ' - Twitter client unable to get friends timeline for user ' .
- $flink->user_id . ' - code: ' .
- $e->getCode() . 'msg: ' . $e->getMessage());
+ common_log(LOG_ERR, $this->name() .
+ ' - Unable to get ' . $timelineUri . ' timeline for user ' . $flink->user_id .
+ ' - code: ' . $e->getCode() . 'msg: ' . $e->getMessage());
}
if (empty($timeline)) {
- common_log(LOG_WARNING, $this->name() . " - Empty timeline.");
+ common_log(LOG_WARNING, $this->name() . " - Empty '" . $timelineUri . "' timeline.");
return;
}
- common_debug(LOG_INFO, $this->name() . ' - Retrieved ' . sizeof($timeline) . ' statuses from Twitter.');
+ common_log(LOG_INFO, $this->name() .
+ ' - Retrieved ' . sizeof($timeline) . ' statuses from ' . $timelineUri . ' timeline' .
+ ' - for user ' . $flink->user_id);
- $importer = new TwitterImport();
-
- // Reverse to preserve order
-
- foreach (array_reverse($timeline) as $status) {
- $notice = $importer->importStatus($status);
-
- if (!empty($notice)) {
- Inbox::insertNotice($flink->user_id, $notice->id);
+ if (!empty($timeline)) {
+ $qm = QueueManager::get();
+
+ // Reverse to preserve order
+ foreach (array_reverse($timeline) as $status) {
+ $data = array(
+ 'status' => $status,
+ 'for_user' => $flink->foreign_id,
+ );
+ $qm->enqueue($data, 'tweetin');
}
- }
- if (!empty($timeline)) {
$lastId = twitter_id($timeline[0]);
- Twitter_synch_status::setLastId($flink->foreign_id, 'home_timeline', $lastId);
- common_debug("Set lastId value '$lastId' for foreign id '{$flink->foreign_id}' and timeline 'home_timeline'");
+ Twitter_synch_status::setLastId($flink->foreign_id, $timelineUri, $lastId);
+ common_debug("Set lastId value '$lastId' for foreign id '{$flink->foreign_id}' and timeline '" .
+ $timelineUri . "'");
}
// Okay, record the time we synced with Twitter for posterity
$debug = true;
}
-$fetcher = new TwitterStatusFetcher($id, 60, 2, $debug);
+$fetcher = new TwitterStatusFetcher($id, POLL_INTERVAL, MAXCHILDREN, $debug);
$fetcher->runOnce();
$importer = new TwitterImport();
$notice = $importer->importStatus($status);
if ($notice) {
- $flink = Foreign_link::getByForeignID(TWITTER_SERVICE, $receiver);
+ $flink = Foreign_link::getByForeignID($receiver, TWITTER_SERVICE);
if ($flink) {
+ common_log(LOG_DEBUG, "TweetInQueueHandler - Got flink so add notice ".
+ $notice->id." to inbox ".$flink->user_id);
// @fixme this should go through more regular channels?
Inbox::insertNotice($flink->user_id, $notice->id);
+ }else {
+ common_log(LOG_DEBUG, "TweetInQueueHandler - No flink found for foreign user ".$receiver);
}
}
{
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;
$path_parts = pathinfo($twitter_user->profile_image_url);
- $img_root = substr($path_parts['basename'], 0, -11);
- $ext = $path_parts['extension'];
- $mediatype = $this->getMediatype($ext);
+ $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(substr($ext, 1));
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);
$mediatype = null;
switch (strtolower($ext)) {
+ case 'jpeg':
case 'jpg':
- $mediatype = 'image/jpg';
+ $mediatype = 'image/jpeg';
break;
case 'gif':
$mediatype = 'image/gif';
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));
- $mediatype = $this->getMediatype($ext);
+ $ext = (isset($path_parts['extension']) ? '.'.$path_parts['extension'] : '');
+ $img_root = basename($path_parts['basename'], '_normal'.$ext);
+ $mediatype = $this->getMediatype(substr($ext, 1));
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);
*/
function verifyCredentials()
{
- $url = 'https://api.twitter.com/1/account/verify_credentials.json';
+ $url = 'https://api.twitter.com/1.1/account/verify_credentials.json';
$response = $this->oAuthGet($url);
$twitter_user = json_decode($response);
return $twitter_user;
*/
function statusesUpdate($status, $params=array())
{
- $url = 'https://api.twitter.com/1/statuses/update.json';
+ $url = 'https://api.twitter.com/1.1/statuses/update.json';
if (is_numeric($params)) {
$params = array('in_reply_to_status_id' => intval($params));
}
/**
* Calls Twitter's /statuses/home_timeline API method
*
- * @param int $since_id show statuses after this id
- * @param int $max_id show statuses before this id
- * @param int $cnt number of statuses to show
- * @param int $page page number
+ * @param int $since_id show statuses after this id
+ * @param string $timelineUri timeline to poll statuses from
+ * @param int $max_id show statuses before this id
+ * @param int $cnt number of statuses to show
+ * @param int $page page number
*
* @return mixed an array of statuses
*/
- function statusesHomeTimeline($since_id = null, $max_id = null,
- $cnt = null, $page = null)
+ function statusesTimeline($since_id = null, $timelineUri = 'home_timeline',
+ $max_id = null, $cnt = 200, $page = null)
{
- $url = 'https://api.twitter.com/1/statuses/home_timeline.json';
+ $url = 'https://api.twitter.com/1.1/statuses/'.$timelineUri.'.json';
- $params = array('include_entities' => 'true');
+ $params = array('include_entities' => 'true',
+ 'include_rts' => 'true');
if (!empty($since_id)) {
$params['since_id'] = $since_id;
function statusesFriends($id = null, $user_id = null, $screen_name = null,
$page = null)
{
- $url = "https://api.twitter.com/1/statuses/friends.json";
+ $url = "https://api.twitter.com/1.1/friends/list.json";
$params = array();
function friendsIds($id = null, $user_id = null, $screen_name = null,
$page = null)
{
- $url = "https://api.twitter.com/1/friends/ids.json";
+ $url = "https://api.twitter.com/1.1/friends/ids.json";
$params = array();
function statusesRetweet($id)
{
- $url = "http://api.twitter.com/1/statuses/retweet/$id.json";
+ $url = "http://api.twitter.com/1.1/statuses/retweet/$id.json";
$response = $this->oAuthPost($url);
$status = json_decode($response);
return $status;
function favoritesCreate($id)
{
- $url = "http://api.twitter.com/1/favorites/create/$id.json";
- $response = $this->oAuthPost($url);
+ $url = "http://api.twitter.com/1.1/favorites/create.json";
+ $params=array();
+ $params['id'] = $id;
+ $response = $this->oAuthPost($url, $params);
$status = json_decode($response);
return $status;
}
function favoritesDestroy($id)
{
- $url = "http://api.twitter.com/1/favorites/destroy/$id.json";
- $response = $this->oAuthPost($url);
+ $url = "http://api.twitter.com/1.1/favorites/destroy.json";
+ $params=array();
+ $params['id'] = $id;
+ $response = $this->oAuthPost($url,$params);
$status = json_decode($response);
return $status;
}
function statusesDestroy($id)
{
- $url = "http://api.twitter.com/1/statuses/destroy/$id.json";
+ $url = "http://api.twitter.com/1.1/statuses/destroy/$id.json";
$response = $this->oAuthPost($url);
$status = json_decode($response);
return $status;
);
}
+ /**
+ * 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)
+ {
+ if( isset($this->server) &&
+ isset($this->port) &&
+ isset($this->user) &&
+ isset($this->password) ){
+
+ array_push(
+ $daemons,
+ INSTALLDIR
+ . '/scripts/imdaemon.php'
+ );
+ }
+
+ return true;
+ }
+
+
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'XMPP',
$daemons[] = INSTALLDIR.'/scripts/queuedaemon.php';
-$daemons[] = INSTALLDIR.'/scripts/imdaemon.php';
-
if (Event::handle('GetValidDaemons', array(&$daemons))) {
foreach ($daemons as $daemon) {
print $daemon . ' ';
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;
}