From a5724cca4956e3fcfe359bd72b4d874832b31393 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 10:10:25 -0500 Subject: do some moving around of forwarding stuff --- actions/repeat.php | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 actions/repeat.php (limited to 'actions') diff --git a/actions/repeat.php b/actions/repeat.php new file mode 100644 index 000000000..194833fe0 --- /dev/null +++ b/actions/repeat.php @@ -0,0 +1,122 @@ + + * @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')) { + exit(1); +} + +/** + * Forward action + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class ForwardAction extends Action +{ + var $user = null; + var $notice = null; + + function prepare($args) + { + parent::prepare($args); + + $this->user = common_current_user(); + + if (empty($this->user)) { + $this->clientError(_("Only logged-in users can forward notices.")); + return false; + } + + $id = $this->trimmed('notice'); + + if (empty($id)) { + $this->clientError(_("No notice specified.")); + return false; + } + + $this->notice = Notice::staticGet('id', $id); + + if (empty($this->notice)) { + $this->clientError(_("No notice specified.")); + return false; + } + + if ($this->user->id == $this->notice->profile_id) { + $this->clientError(_("You can't forward your own notice.")); + return false; + } + + $token = $this->trimmed('token-'.$id); + + if (empty($token) || $token != common_session_token()) { + $this->clientError(_("There was a problem with your session token. Try again, please.")); + return false; + } + + $profile = $this->user->getProfile(); + + if ($profile->hasForwarded($id)) { + $this->clientError(_("You already forwarded that notice.")); + return false; + } + + return true; + } + + /** + * Class handler. + * + * @param array $args query arguments + * + * @return void + */ + + function handle($args) + { + $forward = Forward::saveNew($this->user->id, $this->notice->id); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _('Forwarded')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->element('p', array('id' => 'forward_response'), _('Forwarded!')); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + // FIXME! + } + } +} -- cgit v1.2.3-54-g00ecf From 79f81ad76d413908c097c121912eff233aebc483 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 11:29:51 -0500 Subject: change Notice::saveNew() to use named arguments for little-used options --- actions/apistatusesupdate.php | 29 ++++++++++++++++------------- actions/newnotice.php | 10 ++++++---- classes/Notice.php | 15 ++++++++++++--- lib/command.php | 5 +++-- lib/oauthstore.php | 5 ++--- plugins/Facebook/facebookaction.php | 5 +++-- 6 files changed, 42 insertions(+), 27 deletions(-) (limited to 'actions') diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 85a7c8c08..10bbc5ace 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -231,19 +231,22 @@ class ApiStatusesUpdateAction extends ApiAuthAction } } - $this->notice = Notice::saveNew( - $this->user->id, - html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'), - $this->source, - 1, - $reply_to, - null, - null, - empty($location) ? null : $location->lat, - empty($location) ? null : $location->lon, - empty($location) ? null : $location->location_id, - empty($location) ? null : $location->location_ns - ); + $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'), + + $options = array('reply_to' => $reply_to); + + if (!empty($location)) { + $options['lat'] = $location->lat; + $options['lon'] = $location->lon; + $options['location_id'] = $location->location_id; + $options['location_ns'] = $location->location_ns; + } + + $this->notice = + Notice::saveNew($this->user->id, + $content, + $this->source, + $options); if (isset($upload)) { $upload->attachToNotice($this->notice); diff --git a/actions/newnotice.php b/actions/newnotice.php index dd6da0b01..c6c70e326 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -187,10 +187,12 @@ class NewnoticeAction extends Action } } - $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, - ($replyto == 'false') ? null : $replyto, - null, null, - $lat, $lon, $location_id, $location_ns); + $notice = Notice::saveNew($user->id, $content_shortened, 'web', + array('reply_to' => ($replyto == 'false') ? null : $replyto, + 'lat' => $lat, + 'lon' => $lon, + 'location_id' => $location_id, + 'location_ns' => $location_ns)); if (isset($upload)) { $upload->attachToNotice($notice); diff --git a/classes/Notice.php b/classes/Notice.php index c36c5a9c6..4422866fa 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -167,9 +167,18 @@ class Notice extends Memcached_DataObject } } - static function saveNew($profile_id, $content, $source=null, - $is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null, - $lat=null, $lon=null, $location_id=null, $location_ns=null) { + static function saveNew($profile_id, $content, $source, $options=null) { + + if (!empty($options)) { + extract($options); + if (!isset($reply_to)) { + $reply_to = NULL; + } + } + + if (empty($is_local)) { + $is_local = Notice::LOCAL_PUBLIC; + } $profile = Profile::staticGet($profile_id); diff --git a/lib/command.php b/lib/command.php index 450db9da3..085331f82 100644 --- a/lib/command.php +++ b/lib/command.php @@ -433,8 +433,9 @@ class ReplyCommand extends Command return; } - $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), 1, - $notice->id); + $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), + array('reply_to' => $notice->id)); + if ($notice) { $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname)); } else { diff --git a/lib/oauthstore.php b/lib/oauthstore.php index e34bf8a5e..df63cc151 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -359,9 +359,8 @@ class StatusNetOAuthDataStore extends OAuthDataStore $notice = Notice::saveNew($author->id, $omb_notice->getContent(), 'omb', - false, - null, - $omb_notice->getIdentifierURI()); + array('is_local' => Notice::REMOTE_OMB, + 'uri' => $omb_notice->getIdentifierURI())); common_broadcast_notice($notice, true); } diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php index 705bb2780..24bf215fd 100644 --- a/plugins/Facebook/facebookaction.php +++ b/plugins/Facebook/facebookaction.php @@ -445,8 +445,9 @@ class FacebookAction extends Action $replyto = $this->trimmed('inreplyto'); try { - $notice = Notice::saveNew($user->id, $content, - 'web', 1, ($replyto == 'false') ? null : $replyto); + $notice = Notice::saveNew($user->id, $content, 'web', + array('reply_to' => ($replyto == 'false') ? null : $replyto)); + } catch (Exception $e) { $this->showPage($e->getMessage()); return; -- cgit v1.2.3-54-g00ecf From 89256fa754c1e03b822b8eeac388cc822b3c58ca Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 11:33:26 -0500 Subject: fix typo in API arg creation --- actions/apistatusesupdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actions') diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 10bbc5ace..dabbea92f 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -231,7 +231,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction } } - $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'), + $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'); $options = array('reply_to' => $reply_to); -- cgit v1.2.3-54-g00ecf From afc86a86d36dc8101c81d5d009546e4629db34a3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 11:51:09 -0500 Subject: save repeats from the form --- actions/repeat.php | 20 ++++++++++---------- classes/Notice.php | 23 ++++++++++++++++++++++- lib/router.php | 1 + 3 files changed, 33 insertions(+), 11 deletions(-) (limited to 'actions') diff --git a/actions/repeat.php b/actions/repeat.php index 194833fe0..a1c5f443f 100644 --- a/actions/repeat.php +++ b/actions/repeat.php @@ -1,7 +1,7 @@ user = common_current_user(); if (empty($this->user)) { - $this->clientError(_("Only logged-in users can forward notices.")); + $this->clientError(_("Only logged-in users can repeat notices.")); return false; } @@ -73,7 +73,7 @@ class ForwardAction extends Action } if ($this->user->id == $this->notice->profile_id) { - $this->clientError(_("You can't forward your own notice.")); + $this->clientError(_("You can't repeat your own notice.")); return false; } @@ -86,8 +86,8 @@ class ForwardAction extends Action $profile = $this->user->getProfile(); - if ($profile->hasForwarded($id)) { - $this->clientError(_("You already forwarded that notice.")); + if ($profile->hasRepeated($id)) { + $this->clientError(_("You already repeated that notice.")); return false; } @@ -104,15 +104,15 @@ class ForwardAction extends Action function handle($args) { - $forward = Forward::saveNew($this->user->id, $this->notice->id); + $repeat = $this->notice->repeat($this->user->id, 'web'); if ($this->boolean('ajax')) { $this->startHTML('text/xml;charset=utf-8'); $this->elementStart('head'); - $this->element('title', null, _('Forwarded')); + $this->element('title', null, _('Repeated')); $this->elementEnd('head'); $this->elementStart('body'); - $this->element('p', array('id' => 'forward_response'), _('Forwarded!')); + $this->element('p', array('id' => 'repeat_response'), _('Repeated!')); $this->elementEnd('body'); $this->elementEnd('html'); } else { diff --git a/classes/Notice.php b/classes/Notice.php index 228201188..82753fbdd 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -236,7 +236,14 @@ class Notice extends Memcached_DataObject $notice->source = $source; $notice->uri = $uri; - $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); + // Handle repeat case + + if (isset($repeat_of)) { + $notice->repeat_of = $repeat_of; + $notice->reply_to = $repeat_of; + } else { + $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); + } if (!empty($notice->reply_to)) { $reply = Notice::staticGet('id', $notice->reply_to); @@ -1434,4 +1441,18 @@ class Notice extends Memcached_DataObject return $location; } + + function repeat($repeater_id, $source) + { + $author = Profile::staticGet('id', $this->profile_id); + + // FIXME: truncate on long repeats...? + + $content = sprintf(_('RT @%1$s %2$s'), + $author->nickname, + $this->content); + + return self::saveNew($repeater_id, $content, $source, + array('repeat_of' => $this->id)); + } } diff --git a/lib/router.php b/lib/router.php index 37525319f..142206c16 100644 --- a/lib/router.php +++ b/lib/router.php @@ -99,6 +99,7 @@ class Router 'groupblock', 'groupunblock', 'sandbox', 'unsandbox', 'silence', 'unsilence', + 'repeat', 'deleteuser'); foreach ($main as $a) { -- cgit v1.2.3-54-g00ecf From 0cb1feed51522abc35bfd4252089ca8c4df52bda Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 12:16:11 -0500 Subject: show repeated notice's author on profile page --- actions/showstream.php | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'actions') diff --git a/actions/showstream.php b/actions/showstream.php index 663638c18..74b46cc95 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -269,4 +269,50 @@ class ProfileNoticeListItem extends NoticeListItem { return; } + + /** + * show a link to the author of repeat + * + * @return void + */ + + function showRepeat() + { + if (!empty($this->repeat)) { + + // FIXME: this code is almost identical to default; need to refactor + + $attrs = array('href' => $this->profile->profileurl, + 'class' => 'url'); + + if (!empty($this->profile->fullname)) { + $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')'; + } + + $this->out->elementStart('span', 'repeat'); + + $this->out->elementStart('a', $attrs); + + $avatar = $this->profile->getAvatar(AVATAR_MINI_SIZE); + + $this->out->element('img', array('src' => ($avatar) ? + $avatar->displayUrl() : + Avatar::defaultImage(AVATAR_MINI_SIZE), + 'class' => 'avatar photo', + 'width' => AVATAR_MINI_SIZE, + 'height' => AVATAR_MINI_SIZE, + 'alt' => + ($this->profile->fullname) ? + $this->profile->fullname : + $this->profile->nickname)); + + $this->out->elementEnd('a'); + + $text_link = XMLStringer::estring('a', $attrs, $this->profile->nickname); + + $this->out->raw(sprintf(_('Repeat of %s'), $text_link)); + + $this->out->elementEnd('span'); + } + } } -- cgit v1.2.3-54-g00ecf From 683edfd199ae150afa74993521c4c65d76b1e19d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 11 Dec 2009 12:40:05 -0500 Subject: show repeated notices correctly in API output --- actions/apistatusesretweet.php | 136 +++++++++++++++++++++++++++++++++++++++++ lib/router.php | 5 ++ 2 files changed, 141 insertions(+) create mode 100644 actions/apistatusesretweet.php (limited to 'actions') diff --git a/actions/apistatusesretweet.php b/actions/apistatusesretweet.php new file mode 100644 index 000000000..fc71d2274 --- /dev/null +++ b/actions/apistatusesretweet.php @@ -0,0 +1,136 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @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/apiauth.php'; +require_once INSTALLDIR . '/lib/mediafile.php'; + +/** + * Repeat a notice through the API + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesRetweetAction extends ApiAuthAction +{ + var $original = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError(_('This method requires a POST.'), + 400, $this->format); + return false; + } + + $id = $this->trimmed('id'); + + $this->original = Notice::staticGet('id', $id); + + if (empty($this->original)) { + $this->clientError(_('No such notice'), + 400, $this->format); + return false; + } + + $this->user = $this->auth_user; + + if ($this->user->id == $notice->profile_id) { + $this->clientError(_('Cannot repeat your own notice')); + 400, $this->format); + return false; + } + + $profile = $this->user->getProfile(); + + if ($profile->hasRepeated($id)) { + $this->clientError(_('Already repeated that notice'), + 400, $this->format); + return false; + } + + return true; + } + + /** + * Handle the request + * + * Make a new notice for the update, save it, and show it + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $repeat = $this->original->repeat($this->user->id, $this->source); + + common_broadcast_notice($repeat); + + $this->showNotice($repeat); + } + + /** + * Show the resulting notice + * + * @return void + */ + + function showNotice($notice) + { + if (!empty($notice)) { + if ($this->format == 'xml') { + $this->showSingleXmlStatus($notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($notice); + } + } + } +} diff --git a/lib/router.php b/lib/router.php index 142206c16..835339720 100644 --- a/lib/router.php +++ b/lib/router.php @@ -359,6 +359,11 @@ class Router 'id' => '[0-9]+', 'format' => '(xml|json)')); + $m->connect('api/statuses/retweet/:id.:format', + array('action' => 'ApiStatusesRetweet', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + // users $m->connect('api/users/show.:format', -- cgit v1.2.3-54-g00ecf From 59e5958d3afa0a5613efbc1ed4546ce63946fe43 Mon Sep 17 00:00:00 2001 From: Hue Bastard Date: Tue, 8 Dec 2009 21:28:11 +1000 Subject: ticket 2055: added logos to Atom and RSS feeds --- actions/apitimelinefavorites.php | 6 ++++-- actions/apitimelinefriends.php | 6 ++++-- actions/apitimelinegroup.php | 7 +++++-- actions/apitimelinementions.php | 6 ++++-- actions/apitimelinepublic.php | 5 +++-- actions/apitimelinetag.php | 6 ++++-- actions/apitimelineuser.php | 6 ++++-- lib/api.php | 27 +++++++++++++++++++++------ 8 files changed, 49 insertions(+), 20 deletions(-) (limited to 'actions') diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php index f84d7b4cb..77e5cea1b 100644 --- a/actions/apitimelinefavorites.php +++ b/actions/apitimelinefavorites.php @@ -101,6 +101,7 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction function showTimeline() { $profile = $this->user->getProfile(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); $sitename = common_config('site', 'name'); $title = sprintf( @@ -121,20 +122,21 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction $profile->getBestName(), $this->user->nickname ); + $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo, null, $logo); break; case 'atom': $selfuri = common_root_url() . ltrim($_SERVER['QUERY_STRING'], 'p='); $this->showAtomTimeline( $this->notices, $title, $id, $link, $subtitle, - null, $selfuri + null, $selfuri, $logo ); break; case 'json': diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index e84f77372..09ba7a969 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -110,6 +110,7 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction function showTimeline() { $profile = $this->user->getProfile(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); $sitename = common_config('site', 'name'); $title = sprintf(_("%s and friends"), $this->user->nickname); $taguribase = common_config('integration', 'taguri'); @@ -121,13 +122,14 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction _('Updates from %1$s and friends on %2$s!'), $this->user->nickname, $sitename ); + $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); break; case 'atom': @@ -144,7 +146,7 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction $this->showAtomTimeline( $this->notices, $title, $id, $link, - $subtitle, null, $selfuri + $subtitle, null, $selfuri, $logo ); break; case 'json': diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php index de13e7eb9..22c577f39 100644 --- a/actions/apitimelinegroup.php +++ b/actions/apitimelinegroup.php @@ -105,6 +105,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction function showTimeline() { $sitename = common_config('site', 'name'); + $avatar = $this->group->homepage_logo; $title = sprintf(_("%s timeline"), $this->group->nickname); $taguribase = common_config('integration', 'taguri'); $id = "tag:$taguribase:GroupTimeline:" . $this->group->id; @@ -117,13 +118,14 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction $this->group->nickname, $sitename ); + $logo = ($avatar) ? $avatar : User_group::defaultLogo(AVATAR_PROFILE_SIZE); switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); break; case 'atom': $selfuri = common_root_url() . @@ -136,7 +138,8 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction $link, $subtitle, null, - $selfuri + $selfuri, + $logo ); break; case 'json': diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php index 0956ccdce..19f40aebc 100644 --- a/actions/apitimelinementions.php +++ b/actions/apitimelinementions.php @@ -110,6 +110,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction function showTimeline() { $profile = $this->user->getProfile(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); $sitename = common_config('site', 'name'); $title = sprintf( @@ -126,20 +127,21 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction _('%1$s updates that reply to updates from %2$s / %3$s.'), $sitename, $this->user->nickname, $profile->getBestName() ); + $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); break; case 'atom': $selfuri = common_root_url() . ltrim($_SERVER['QUERY_STRING'], 'p='); $this->showAtomTimeline( $this->notices, $title, $id, $link, $subtitle, - null, $selfuri + null, $selfuri, $logo ); break; case 'json': diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php index 7a8504259..633f3c36e 100644 --- a/actions/apitimelinepublic.php +++ b/actions/apitimelinepublic.php @@ -103,6 +103,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction function showTimeline() { $sitename = common_config('site', 'name'); + $sitelogo = (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'); $title = sprintf(_("%s public timeline"), $sitename); $taguribase = common_config('integration', 'taguri'); $id = "tag:$taguribase:PublicTimeline"; @@ -114,13 +115,13 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo); break; case 'atom': $selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; $this->showAtomTimeline( $this->notices, $title, $id, $link, - $subtitle, null, $selfuri + $subtitle, null, $selfuri, $sitelogo ); break; case 'json': diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php index 452593c11..1a50520f4 100644 --- a/actions/apitimelinetag.php +++ b/actions/apitimelinetag.php @@ -98,6 +98,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction function showTimeline() { $sitename = common_config('site', 'name'); + $sitelogo = (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'); $title = sprintf(_("Notices tagged with %s"), $this->tag); $link = common_local_url( 'tag', @@ -116,7 +117,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo); break; case 'atom': $selfuri = common_root_url() . @@ -129,7 +130,8 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction $link, $subtitle, null, - $selfuri + $selfuri, + $sitelogo ); break; case 'json': diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index ca1d21772..14c62a52e 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -112,6 +112,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction function showTimeline() { $profile = $this->user->getProfile(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); $sitename = common_config('site', 'name'); $title = sprintf(_("%s timeline"), $this->user->nickname); @@ -125,6 +126,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction _('Updates from %1$s on %2$s!'), $this->user->nickname, $sitename ); + $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); // FriendFeed's SUP protocol // Also added RSS and Atom feeds @@ -139,7 +141,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction case 'rss': $this->showRssTimeline( $this->notices, $title, $link, - $subtitle, $suplink + $subtitle, $suplink, $logo ); break; case 'atom': @@ -153,7 +155,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction } $this->showAtomTimeline( $this->notices, $title, $id, $link, - $subtitle, $suplink, $selfuri + $subtitle, $suplink, $selfuri, $logo ); break; case 'json': diff --git a/lib/api.php b/lib/api.php index eacb80dbe..7ebe65dbb 100644 --- a/lib/api.php +++ b/lib/api.php @@ -134,17 +134,19 @@ class ApiAction extends Action $twitter_user['protected'] = false; # not supported by StatusNet yet $twitter_user['followers_count'] = $profile->subscriberCount(); - $user = $profile->getUser(); $design = null; + $user = $profile->getUser(); // Note: some profiles don't have an associated user - $defaultDesign = Design::siteDesign(); - if (!empty($user)) { $design = $user->getDesign(); } + if (empty($design)) { + $design = Design::siteDesign(); + } + $color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor); $twitter_user['profile_background_color'] = ($color == null) ? '' : '#'.$color->hexValue(); $color = Design::toWebColor(empty($design->textcolor) ? $defaultDesign->textcolor : $design->textcolor); @@ -163,7 +165,7 @@ class ApiAction extends Action $timezone = 'UTC'; - if (!empty($user) && !empty($user->timezone)) { + if ($user->timezone) { $timezone = $user->timezone; } @@ -586,7 +588,7 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null) + function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null) { $this->initDocument('rss'); @@ -600,6 +602,15 @@ class ApiAction extends Action 'href' => $suplink, 'type' => 'application/json')); } + + if (!is_null($logo)) { + $this->elementStart('image'); + $this->element('link', null, $link); + $this->element('title', null, $title); + $this->element('url', null, $logo); + $this->elementEnd('image'); + } + $this->element('description', null, $subtitle); $this->element('language', null, 'en-us'); $this->element('ttl', null, '40'); @@ -619,7 +630,7 @@ class ApiAction extends Action $this->endTwitterRss(); } - function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) + function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null) { $this->initDocument('atom'); @@ -628,6 +639,10 @@ class ApiAction extends Action $this->element('id', null, $id); $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); + if (!is_null($logo)) { + $this->element('logo',null,$logo); + } + if (!is_null($suplink)) { # For FriendFeed's SUP protocol $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', -- cgit v1.2.3-54-g00ecf From c24afa986e04794bfd230cb527ebe6fd938f3934 Mon Sep 17 00:00:00 2001 From: Hue Bastard Date: Tue, 8 Dec 2009 21:35:54 +1000 Subject: (fix clumsy double paste in b45be2b actions/apitimelinefavorites.php --- actions/apitimelinefavorites.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actions') diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php index 77e5cea1b..008e04212 100644 --- a/actions/apitimelinefavorites.php +++ b/actions/apitimelinefavorites.php @@ -129,7 +129,7 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction $this->showXmlTimeline($this->notices); break; case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo, null, $logo); + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); break; case 'atom': $selfuri = common_root_url() . -- cgit v1.2.3-54-g00ecf From c622d144400026dac65c39303994d11f114c0f5b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 14:46:24 -0500 Subject: add statuses/retweets to API --- actions/apistatusesretweets.php | 116 ++++++++++++++++++++++++++++++++++++++++ classes/Notice.php | 67 +++++++++++++++++++++++ lib/router.php | 5 ++ 3 files changed, 188 insertions(+) create mode 100644 actions/apistatusesretweets.php (limited to 'actions') diff --git a/actions/apistatusesretweets.php b/actions/apistatusesretweets.php new file mode 100644 index 000000000..c54a374e2 --- /dev/null +++ b/actions/apistatusesretweets.php @@ -0,0 +1,116 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @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/apiauth.php'; +require_once INSTALLDIR . '/lib/mediafile.php'; + +/** + * Show up to 100 repeats of a notice + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesRetweetsAction extends ApiAuthAction +{ + const MAXCOUNT = 100; + + var $original = null; + var $cnt = self::MAXCOUNT; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $id = $this->trimmed('id'); + + $this->original = Notice::staticGet('id', $id); + + if (empty($this->original)) { + $this->clientError(_('No such notice'), + 400, $this->format); + return false; + } + + $cnt = $this->trimmed('count'); + + if (empty($cnt) || !is_integer($cnt)) { + $cnt = 100; + } else { + $this->cnt = min((int)$cnt, self::MAXCOUNT); + } + + return true; + } + + /** + * Handle the request + * + * Make a new notice for the update, save it, and show it + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $strm = $this->original->repeatStream($this->cnt); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index 82753fbdd..ec80f763f 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -441,10 +441,23 @@ class Notice extends Memcached_DataObject $this->blowTagCache($blowLast); $this->blowGroupCache($blowLast); $this->blowConversationCache($blowLast); + $this->blowRepeatCache(); $profile = Profile::staticGet($this->profile_id); $profile->blowNoticeCount(); } + function blowRepeatCache() + { + if (!empty($this->repeat_of)) { + $cache = common_memcache(); + if (!empty($cache)) { + // XXX: only blow if <100 in cache + $ck = common_cache_key('notice:repeats:'.$this->repeat_of); + $result = $cache->delete($ck); + } + } + } + function blowConversationCache($blowLast=false) { $cache = common_memcache(); @@ -1455,4 +1468,58 @@ class Notice extends Memcached_DataObject return self::saveNew($repeater_id, $content, $source, array('repeat_of' => $this->id)); } + + // These are supposed to be in chron order! + + function repeatStream($limit=100) + { + $cache = common_memcache(); + + if (empty($cache)) { + $ids = $this->_repeatStreamDirect($limit); + } else { + $idstr = $cache->get(common_cache_key('notice:repeats:'.$this->id)); + if (!empty($idstr)) { + $ids = explode(',', $idstr); + } else { + $ids = $this->_repeatStreamDirect(100); + $cache->set(common_cache_key('notice:repeats:'.$this->id), implode(',', $ids)); + } + if ($limit < 100) { + // We do a max of 100, so slice down to limit + $ids = array_slice($ids, 0, $limit); + } + } + + return Notice::getStreamByIds($ids); + } + + function _repeatStreamDirect($limit) + { + $notice = new Notice(); + + $notice->selectAdd(); // clears it + $notice->selectAdd('id'); + + $notice->repeat_of = $this->id; + + $notice->orderBy('created'); // NB: asc! + + if (!is_null($offset)) { + $notice->limit($offset, $limit); + } + + $ids = array(); + + if ($notice->find()) { + while ($notice->fetch()) { + $ids[] = $notice->id; + } + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index 835339720..b0b95b080 100644 --- a/lib/router.php +++ b/lib/router.php @@ -364,6 +364,11 @@ class Router 'id' => '[0-9]+', 'format' => '(xml|json)')); + $m->connect('api/statuses/retweets/:id.:format', + array('action' => 'ApiStatusesRetweets', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + // users $m->connect('api/users/show.:format', -- cgit v1.2.3-54-g00ecf From 138ce0cd05e2e59c79b29f5eeea5c11d1e56e931 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 15:35:05 -0500 Subject: add statuses/retweeted_by_me api action --- actions/apitimelineretweetedbyme.php | 126 +++++++++++++++++++++++++++++++++++ classes/Notice.php | 9 +++ classes/User.php | 52 +++++++++++++++ lib/router.php | 4 ++ 4 files changed, 191 insertions(+) create mode 100644 actions/apitimelineretweetedbyme.php (limited to 'actions') diff --git a/actions/apitimelineretweetedbyme.php b/actions/apitimelineretweetedbyme.php new file mode 100644 index 000000000..1e65678ad --- /dev/null +++ b/actions/apitimelineretweetedbyme.php @@ -0,0 +1,126 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @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/apiauth.php'; +require_once INSTALLDIR . '/lib/mediafile.php'; + +/** + * Show authenticating user's most recent repeats + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineRetweetedByMeAction extends ApiAuthAction +{ + const DEFAULTCOUNT = 20; + const MAXCOUNT = 200; + const MAXNOTICES = 3200; + + var $repeats = null; + var $cnt = self::DEFAULTCOUNT; + var $page = 1; + var $since_id = null; + var $max_id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1); + + $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt)); + + $since_id = $this->int('since_id'); + + $max_id = $this->int('max_id'); + + return true; + } + + /** + * Handle the request + * + * show a timeline of the user's repeated notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $offset = ($this->page-1) * $this->cnt; + $limit = $this->cnt; + + $strm = $this->auth_user->repeatedByMe($offset, $limit, $this->since_id, $this->max_id); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + case 'atom': + $profile = $this->auth_user->getProfile(); + + $title = sprintf(_("Repeated by %s"), $this->auth_user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:RepeatedByMe:" . $this->auth_user->id; + $link = common_local_url('showstream', + array('nickname' => $this->auth_user->nickname)); + + $this->showAtomTimeline($strm, $title, $id, $link); + break; + + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index ec80f763f..6a701ae0c 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -454,6 +454,15 @@ class Notice extends Memcached_DataObject // XXX: only blow if <100 in cache $ck = common_cache_key('notice:repeats:'.$this->repeat_of); $result = $cache->delete($ck); + + $user = User::staticGet('id', $this->profile_id); + + if (!empty($user)) { + $uk = common_cache_key('user:repeated_by_me:'.$user->id); + $cache->delete($uk); + $user->free(); + unset($user); + } } } } diff --git a/classes/User.php b/classes/User.php index 2a4fab7d4..4b7365fc7 100644 --- a/classes/User.php +++ b/classes/User.php @@ -741,4 +741,56 @@ class User extends Memcached_DataObject $profile = $this->getProfile(); return $profile->isSilenced(); } + + function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatedByMeDirect'), + array(), + 'user:repeated_by_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatedByMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $notice = new Notice(); + + $notice->selectAdd(); // clears it + $notice->selectAdd('id'); + + $notice->profile_id = $this->id; + $notice->whereAdd('repeat_of IS NOT NULL'); + + $notice->orderBy('id DESC'); + + if (!is_null($offset)) { + $notice->limit($offset, $limit); + } + + if ($since_id != 0) { + $notice->whereAdd('id > ' . $since_id); + } + + if ($max_id != 0) { + $notice->whereAdd('id <= ' . $max_id); + } + + if (!is_null($since)) { + $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); + } + + $ids = array(); + + if ($notice->find()) { + while ($notice->fetch()) { + $ids[] = $notice->id; + } + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index b0b95b080..5c1bd3c4f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -319,6 +319,10 @@ class Router 'id' => '[a-zA-Z0-9]+', 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/retweeted_by_me.:format', + array('action' => 'ApiTimelineRetweetedByMe', + 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/friends.:format', array('action' => 'ApiUserFriends', 'format' => '(xml|json)')); -- cgit v1.2.3-54-g00ecf From cfe67a9c0192129448ea3f0b98aadf49e962fd4d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 16:00:27 -0500 Subject: add statuses/retweets_of_me to API --- actions/apitimelineretweetsofme.php | 126 ++++++++++++++++++++++++++++++++++++ classes/Notice.php | 14 ++++ classes/User.php | 53 +++++++++++++++ lib/router.php | 4 ++ 4 files changed, 197 insertions(+) create mode 100644 actions/apitimelineretweetsofme.php (limited to 'actions') diff --git a/actions/apitimelineretweetsofme.php b/actions/apitimelineretweetsofme.php new file mode 100644 index 000000000..479bff431 --- /dev/null +++ b/actions/apitimelineretweetsofme.php @@ -0,0 +1,126 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @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/apiauth.php'; +require_once INSTALLDIR . '/lib/mediafile.php'; + +/** + * Show authenticating user's most recent notices that have been repeated + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineRetweetsOfMeAction extends ApiAuthAction +{ + const DEFAULTCOUNT = 20; + const MAXCOUNT = 200; + const MAXNOTICES = 3200; + + var $repeats = null; + var $cnt = self::DEFAULTCOUNT; + var $page = 1; + var $since_id = null; + var $max_id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1); + + $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt)); + + $since_id = $this->int('since_id'); + + $max_id = $this->int('max_id'); + + return true; + } + + /** + * Handle the request + * + * show a timeline of the user's repeated notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $offset = ($this->page-1) * $this->cnt; + $limit = $this->cnt; + + $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + case 'atom': + $profile = $this->auth_user->getProfile(); + + $title = sprintf(_("Repeats of %s"), $this->auth_user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id; + $link = common_local_url('showstream', + array('nickname' => $this->auth_user->nickname)); + + $this->showAtomTimeline($strm, $title, $id, $link); + break; + + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index 6a701ae0c..eb611f314 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -463,6 +463,20 @@ class Notice extends Memcached_DataObject $user->free(); unset($user); } + + $original = Notice::staticGet('id', $this->repeat_of); + + if (!empty($original)) { + $originalUser = User::staticGet('id', $original->profile_id); + if (!empty($originalUser)) { + $ouk = common_cache_key('user:repeats_of_me:'.$originalUser->id); + $cache->delete($ouk); + $originalUser->free(); + unset($originalUser); + } + $original->free(); + unset($original); + } } } } diff --git a/classes/User.php b/classes/User.php index 4b7365fc7..b3da448a6 100644 --- a/classes/User.php +++ b/classes/User.php @@ -793,4 +793,57 @@ class User extends Memcached_DataObject return $ids; } + + function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatsOfMeDirect'), + array(), + 'user:repeats_of_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT DISTINCT original.id AS id ' . + 'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' . + 'WHERE original.profile_id = ' . $this->id . ' '; + + if ($since_id != 0) { + $qry .= 'AND original.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND original.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND original.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY original.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index 5c1bd3c4f..732caa1c7 100644 --- a/lib/router.php +++ b/lib/router.php @@ -323,6 +323,10 @@ class Router array('action' => 'ApiTimelineRetweetedByMe', 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/retweets_of_me.:format', + array('action' => 'ApiTimelineRetweetsOfMe', + 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/friends.:format', array('action' => 'ApiUserFriends', 'format' => '(xml|json)')); -- cgit v1.2.3-54-g00ecf From 1ec54d3433a79f56c14c0a99114bb1e607348899 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 12 Dec 2009 16:15:23 -0500 Subject: add statuses/retweeted_to_me to API --- actions/apitimelineretweetedtome.php | 125 +++++++++++++++++++++++++++++++++++ classes/Notice.php | 14 ++++ classes/User.php | 54 +++++++++++++++ lib/router.php | 4 ++ 4 files changed, 197 insertions(+) create mode 100644 actions/apitimelineretweetedtome.php (limited to 'actions') diff --git a/actions/apitimelineretweetedtome.php b/actions/apitimelineretweetedtome.php new file mode 100644 index 000000000..681b0b9e9 --- /dev/null +++ b/actions/apitimelineretweetedtome.php @@ -0,0 +1,125 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @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/apiauth.php'; + +/** + * Show most recent notices that are repeats in user's inbox + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineRetweetedToMeAction extends ApiAuthAction +{ + const DEFAULTCOUNT = 20; + const MAXCOUNT = 200; + const MAXNOTICES = 3200; + + var $repeats = null; + var $cnt = self::DEFAULTCOUNT; + var $page = 1; + var $since_id = null; + var $max_id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1); + + $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt)); + + $since_id = $this->int('since_id'); + + $max_id = $this->int('max_id'); + + return true; + } + + /** + * Handle the request + * + * show a timeline of the user's repeated notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $offset = ($this->page-1) * $this->cnt; + $limit = $this->cnt; + + $strm = $this->auth_user->repeatedToMe($offset, $limit, $this->since_id, $this->max_id); + + switch ($this->format) { + case 'xml': + $this->showXmlTimeline($strm); + break; + case 'json': + $this->showJsonTimeline($strm); + break; + case 'atom': + $profile = $this->auth_user->getProfile(); + + $title = sprintf(_("Repeated to %s"), $this->auth_user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:RepeatedToMe:" . $this->auth_user->id; + $link = common_local_url('all', + array('nickname' => $this->auth_user->nickname)); + + $this->showAtomTimeline($strm, $title, $id, $link); + break; + + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } +} diff --git a/classes/Notice.php b/classes/Notice.php index a7b0f8cdb..7d2b898d2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -483,6 +483,20 @@ class Notice extends Memcached_DataObject $original->free(); unset($original); } + + $ni = new Notice_inbox(); + + $ni->notice_id = $this->id; + + if ($ni->find()) { + while ($ni->fetch()) { + $tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id); + $cache->delete($tmk); + } + } + + $ni->free(); + unset($ni); } } } diff --git a/classes/User.php b/classes/User.php index b3da448a6..9c071e06b 100644 --- a/classes/User.php +++ b/classes/User.php @@ -846,4 +846,58 @@ class User extends Memcached_DataObject return $ids; } + + function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatedToMeDirect'), + array(), + 'user:repeated_to_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatedToMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT notice.id AS id ' . + 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . + 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . + 'AND notice.repeat_of IS NOT NULL '; + + if ($since_id != 0) { + $qry .= 'AND notice.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY notice.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/lib/router.php b/lib/router.php index 732caa1c7..8f68f86ac 100644 --- a/lib/router.php +++ b/lib/router.php @@ -323,6 +323,10 @@ class Router array('action' => 'ApiTimelineRetweetedByMe', 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/retweeted_to_me.:format', + array('action' => 'ApiTimelineRetweetedToMe', + 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/retweets_of_me.:format', array('action' => 'ApiTimelineRetweetsOfMe', 'format' => '(xml|json|atom)')); -- cgit v1.2.3-54-g00ecf From 745e35ac1fcee01298db09a8649f79f410138652 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Sun, 13 Dec 2009 18:55:17 +0100 Subject: (Puctuation) consistency in clientError() calls. --- actions/apistatusesretweet.php | 6 +++--- actions/apistatusesretweets.php | 2 +- actions/file.php | 8 ++++---- actions/grouprss.php | 4 ++-- actions/tagother.php | 4 ++-- actions/userbyid.php | 9 ++++----- lib/profileformaction.php | 4 ++-- plugins/OpenID/openidserver.php | 2 +- plugins/TemplatePlugin.php | 2 +- 9 files changed, 20 insertions(+), 21 deletions(-) (limited to 'actions') diff --git a/actions/apistatusesretweet.php b/actions/apistatusesretweet.php index fc71d2274..85de79d5c 100644 --- a/actions/apistatusesretweet.php +++ b/actions/apistatusesretweet.php @@ -72,7 +72,7 @@ class ApiStatusesRetweetAction extends ApiAuthAction $this->original = Notice::staticGet('id', $id); if (empty($this->original)) { - $this->clientError(_('No such notice'), + $this->clientError(_('No such notice.'), 400, $this->format); return false; } @@ -80,7 +80,7 @@ class ApiStatusesRetweetAction extends ApiAuthAction $this->user = $this->auth_user; if ($this->user->id == $notice->profile_id) { - $this->clientError(_('Cannot repeat your own notice')); + $this->clientError(_('Cannot repeat your own notice.')); 400, $this->format); return false; } @@ -88,7 +88,7 @@ class ApiStatusesRetweetAction extends ApiAuthAction $profile = $this->user->getProfile(); if ($profile->hasRepeated($id)) { - $this->clientError(_('Already repeated that notice'), + $this->clientError(_('Already repeated that notice.'), 400, $this->format); return false; } diff --git a/actions/apistatusesretweets.php b/actions/apistatusesretweets.php index c54a374e2..2efd59b37 100644 --- a/actions/apistatusesretweets.php +++ b/actions/apistatusesretweets.php @@ -69,7 +69,7 @@ class ApiStatusesRetweetsAction extends ApiAuthAction $this->original = Notice::staticGet('id', $id); if (empty($this->original)) { - $this->clientError(_('No such notice'), + $this->clientError(_('No such notice.'), 400, $this->format); return false; } diff --git a/actions/file.php b/actions/file.php index 10c59a961..c6f7b998a 100644 --- a/actions/file.php +++ b/actions/file.php @@ -31,15 +31,15 @@ class FileAction extends Action parent::prepare($args); $this->id = $this->trimmed('notice'); if (empty($this->id)) { - $this->clientError(_('No notice id')); + $this->clientError(_('No notice ID.')); } $notice = Notice::staticGet('id', $this->id); if (empty($notice)) { - $this->clientError(_('No notice')); + $this->clientError(_('No notice.')); } $atts = $notice->attachments(); if (empty($atts)) { - $this->clientError(_('No attachments')); + $this->clientError(_('No attachments.')); } foreach ($atts as $att) { if (!empty($att->filename)) { @@ -48,7 +48,7 @@ class FileAction extends Action } } if (empty($this->filerec)) { - $this->clientError(_('No uploaded attachments')); + $this->clientError(_('No uploaded attachments.')); } return true; } diff --git a/actions/grouprss.php b/actions/grouprss.php index 50e48a67e..866fc66eb 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -88,14 +88,14 @@ class groupRssAction extends Rss10Action } if (!$nickname) { - $this->clientError(_('No nickname'), 404); + $this->clientError(_('No nickname.'), 404); return false; } $this->group = User_group::staticGet('nickname', $nickname); if (!$this->group) { - $this->clientError(_('No such group'), 404); + $this->clientError(_('No such group.'), 404); return false; } diff --git a/actions/tagother.php b/actions/tagother.php index c3f43be8b..e9e13b939 100644 --- a/actions/tagother.php +++ b/actions/tagother.php @@ -30,13 +30,13 @@ class TagotherAction extends Action { parent::prepare($args); if (!common_logged_in()) { - $this->clientError(_('Not logged in'), 403); + $this->clientError(_('Not logged in.'), 403); return false; } $id = $this->trimmed('id'); if (!$id) { - $this->clientError(_('No id argument.')); + $this->clientError(_('No ID argument.')); return false; } diff --git a/actions/userbyid.php b/actions/userbyid.php index ebff7e4a7..f3e1556f3 100644 --- a/actions/userbyid.php +++ b/actions/userbyid.php @@ -47,17 +47,17 @@ class UserbyidAction extends Action { /** * Is read only? - * + * * @return boolean true */ function isReadOnly($args) - { + { return true; } /** * Class handler. - * + * * @param array $args array of arguments * * @return nothing @@ -67,7 +67,7 @@ class UserbyidAction extends Action parent::handle($args); $id = $this->trimmed('id'); if (!$id) { - $this->clientError(_('No id.')); + $this->clientError(_('No ID.')); } $user = User::staticGet($id); if (!$user) { @@ -88,4 +88,3 @@ class UserbyidAction extends Action common_redirect($url, 303); } } - diff --git a/lib/profileformaction.php b/lib/profileformaction.php index 8cb5f6a93..8a934666e 100644 --- a/lib/profileformaction.php +++ b/lib/profileformaction.php @@ -120,7 +120,7 @@ class ProfileFormAction extends Action if ($action) { common_redirect(common_local_url($action, $args), 303); } else { - $this->clientError(_("No return-to arguments")); + $this->clientError(_("No return-to arguments.")); } } @@ -134,6 +134,6 @@ class ProfileFormAction extends Action function handlePost() { - $this->serverError(_("unimplemented method")); + $this->serverError(_("Unimplemented method.")); } } diff --git a/plugins/OpenID/openidserver.php b/plugins/OpenID/openidserver.php index 181cbdf45..afbca553f 100644 --- a/plugins/OpenID/openidserver.php +++ b/plugins/OpenID/openidserver.php @@ -103,7 +103,7 @@ class OpenidserverAction extends Action $response = $this->generateDenyResponse($request); } else { //invalid - $this->clientError(sprintf(_m('You are not authorized to use the identity %s'),$request->identity),$code=403); + $this->clientError(sprintf(_m('You are not authorized to use the identity %s.'),$request->identity),$code=403); } } else { $response = $this->oserver->handleRequest($request); diff --git a/plugins/TemplatePlugin.php b/plugins/TemplatePlugin.php index 5f3ad81f5..18aa8034c 100644 --- a/plugins/TemplatePlugin.php +++ b/plugins/TemplatePlugin.php @@ -300,7 +300,7 @@ class TemplateAction extends Action // verify that user is admin if (!($user->id == 1)) - $this->clientError(_('only User #1 can update the template'), $code = 401); + $this->clientError(_('Only User #1 can update the template.'), $code = 401); // open the old template $tpl_file = $this->templateFolder() . '/index.html'; -- cgit v1.2.3-54-g00ecf From 1ace5c400ebc30d2815182690e5433b918aeaf59 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 14 Dec 2009 15:49:19 -0500 Subject: parse error in retweet method --- actions/apistatusesretweet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actions') diff --git a/actions/apistatusesretweet.php b/actions/apistatusesretweet.php index 85de79d5c..d9d4820c0 100644 --- a/actions/apistatusesretweet.php +++ b/actions/apistatusesretweet.php @@ -80,7 +80,7 @@ class ApiStatusesRetweetAction extends ApiAuthAction $this->user = $this->auth_user; if ($this->user->id == $notice->profile_id) { - $this->clientError(_('Cannot repeat your own notice.')); + $this->clientError(_('Cannot repeat your own notice.'), 400, $this->format); return false; } -- cgit v1.2.3-54-g00ecf From 6ff13d6828d01a142b60b4254c07f6f9ad3f16ba Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 14 Dec 2009 16:10:16 -0500 Subject: move full-featured timeline to apitimelinehome.php --- actions/apitimelinefriends.php | 249 ----------------------------------------- actions/apitimelinehome.php | 249 +++++++++++++++++++++++++++++++++++++++++ lib/router.php | 5 +- 3 files changed, 252 insertions(+), 251 deletions(-) delete mode 100644 actions/apitimelinefriends.php create mode 100644 actions/apitimelinehome.php (limited to 'actions') diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php deleted file mode 100644 index 09ba7a969..000000000 --- a/actions/apitimelinefriends.php +++ /dev/null @@ -1,249 +0,0 @@ -. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Jeffery To - * @author mac65 - * @author Mike Cochrane - * @author Robin Millette - * @author Zach Copley - * @copyright 2009 StatusNet, Inc. - * @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'; - -/** - * Returns the most recent notices (default 20) posted by the target user. - * This is the equivalent of 'You and friends' page accessed via Web. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Jeffery To - * @author mac65 - * @author Mike Cochrane - * @author Robin Millette - * @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 ApiTimelineFriendsAction extends ApiBareAuthAction -{ - var $notices = null; - - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - * - */ - - function prepare($args) - { - parent::prepare($args); - common_debug("api friends_timeline"); - $this->user = $this->getTargetUser($this->arg('id')); - - if (empty($this->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(_("%s and friends"), $this->user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:FriendsTimeline:" . $this->user->id; - $link = common_local_url( - 'all', array('nickname' => $this->user->nickname) - ); - $subtitle = sprintf( - _('Updates from %1$s and friends on %2$s!'), - $this->user->nickname, $sitename - ); - $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); - - switch($this->format) { - case 'xml': - $this->showXmlTimeline($this->notices); - break; - case 'rss': - $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); - break; - case 'atom': - - $target_id = $this->arg('id'); - - if (isset($target_id)) { - $selfuri = common_root_url() . - 'api/statuses/friends_timeline/' . - $target_id . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statuses/friends_timeline.atom'; - } - - $this->showAtomTimeline( - $this->notices, $title, $id, $link, - $subtitle, null, $selfuri, $logo - ); - break; - case 'json': - $this->showJsonTimeline($this->notices); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - break; - } - } - - /** - * Get notices - * - * @return array notices - */ - - function getNotices() - { - $notices = array(); - - if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { - $notice = $this->user->noticeInbox( - ($this->page-1) * $this->count, - $this->count, $this->since_id, - $this->max_id, $this->since - ); - } else { - $notice = $this->user->noticesWithFriends( - ($this->page-1) * $this->count, - $this->count, $this->since_id, - $this->max_id, $this->since - ); - } - - 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_language(), - $this->user->id, - strtotime($this->notices[0]->created), - strtotime($this->notices[$last]->created)) - ) - . '"'; - } - - return null; - } - -} diff --git a/actions/apitimelinehome.php b/actions/apitimelinehome.php new file mode 100644 index 000000000..5f5ea37b1 --- /dev/null +++ b/actions/apitimelinehome.php @@ -0,0 +1,249 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @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'; + +/** + * Returns the most recent notices (default 20) posted by the target user. + * This is the equivalent of 'You and friends' page accessed via Web. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @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 ApiTimelineHomeAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + common_debug("api home_timeline"); + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->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(_("%s and friends"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:HomeTimeline:" . $this->user->id; + $link = common_local_url( + 'all', array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s and friends on %2$s!'), + $this->user->nickname, $sitename + ); + $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); + break; + case 'atom': + + $target_id = $this->arg('id'); + + if (isset($target_id)) { + $selfuri = common_root_url() . + 'api/statuses/home_timeline/' . + $target_id . '.atom'; + } else { + $selfuri = common_root_url() . + 'api/statuses/home_timeline.atom'; + } + + $this->showAtomTimeline( + $this->notices, $title, $id, $link, + $subtitle, null, $selfuri, $logo + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { + $notice = $this->user->noticeInbox( + ($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since + ); + } else { + $notice = $this->user->noticesWithFriends( + ($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since + ); + } + + 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_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/lib/router.php b/lib/router.php index 8f68f86ac..474e05996 100644 --- a/lib/router.php +++ b/lib/router.php @@ -283,12 +283,13 @@ class Router array('action' => 'ApiTimelineFriends', 'id' => '[a-zA-Z0-9]+', 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/home_timeline.:format', - array('action' => 'ApiTimelineFriends', + array('action' => 'ApiTimelineHome', 'format' => '(xml|json|rss|atom)')); $m->connect('api/statuses/home_timeline/:id.:format', - array('action' => 'ApiTimelineFriends', + array('action' => 'ApiTimelineHome', 'id' => '[a-zA-Z0-9]+', 'format' => '(xml|json|rss|atom)')); -- cgit v1.2.3-54-g00ecf From 2a8eee0e0b33ce15289484270d3211f502940920 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 14 Dec 2009 16:41:25 -0500 Subject: add friends_timeline with no repeats in it --- actions/apitimelinefriends.php | 245 +++++++++++++++++++++++++++++++++++++++++ classes/Notice.php | 18 +++ classes/User.php | 71 ++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 actions/apitimelinefriends.php (limited to 'actions') diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php new file mode 100644 index 000000000..9ec7447e6 --- /dev/null +++ b/actions/apitimelinefriends.php @@ -0,0 +1,245 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @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'; + +/** + * Returns the most recent notices (default 20) posted by the target user. + * This is the equivalent of 'You and friends' page accessed via Web. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @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 ApiTimelineFriendsAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + common_debug("api friends_timeline"); + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->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(_("%s and friends"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:FriendsTimeline:" . $this->user->id; + $link = common_local_url( + 'all', array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s and friends on %2$s!'), + $this->user->nickname, $sitename + ); + $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo); + break; + case 'atom': + + $target_id = $this->arg('id'); + + if (isset($target_id)) { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline/' . + $target_id . '.atom'; + } else { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline.atom'; + } + + $this->showAtomTimeline( + $this->notices, $title, $id, $link, + $subtitle, null, $selfuri, $logo + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { + $notice = $this->user->ownFriendsTimeline(($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since); + } else { + $notice = $this->user->friendsTimeline(($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since); + } + + 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_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/classes/Notice.php b/classes/Notice.php index 66d9cf5d4..4aec4ed55 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -530,8 +530,18 @@ class Notice extends Memcached_DataObject if ($member->find()) { while ($member->fetch()) { $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id)); + $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id)); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id)); + $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id)); + } if ($blowLast) { $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last')); + $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id . ';last')); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id . ';last')); + $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id . ';last')); + } } } } @@ -579,9 +589,17 @@ class Notice extends Memcached_DataObject while ($user->fetch()) { $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id)); $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id)); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:'.$user->id)); + $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id)); + } if ($blowLast) { $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last')); $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last')); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:'.$user->id.';last')); + $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id.';last')); + } } } $user->free(); diff --git a/classes/User.php b/classes/User.php index 9c071e06b..d04f7d679 100644 --- a/classes/User.php +++ b/classes/User.php @@ -473,6 +473,77 @@ class User extends Memcached_DataObject return Notice::getStreamByIds($ids); } + function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $ids = Notice::stream(array($this, '_friendsTimelineDirect'), + array(false), + 'user:friends_timeline:'.$this->id, + $offset, $limit, $since_id, $before_id, $since); + + return Notice::getStreamByIds($ids); + } + + function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $ids = Notice::stream(array($this, '_friendsTimelineDirect'), + array(true), + 'user:friends_timeline_own:'.$this->id, + $offset, $limit, $since_id, $before_id, $since); + + return Notice::getStreamByIds($ids); + } + + function _friendsTimelineDirect($own, $offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT notice.id AS id ' . + 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . + 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . + 'AND notice.repeat_of IS NULL '; + + if (!$own) { + // XXX: autoload notice inbox for constant + $inbox = new Notice_inbox(); + + $qry .= 'AND notice_inbox.source != ' . NOTICE_INBOX_SOURCE_GATEWAY . ' '; + } + + if ($since_id != 0) { + $qry .= 'AND notice.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY notice.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } + function blowFavesCache() { $cache = common_memcache(); -- cgit v1.2.3-54-g00ecf From f8b187d5a4ac5eab1f025d61e30d5244cef6123d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 14 Dec 2009 18:09:08 -0500 Subject: Initial representation for repeated notice --- actions/repeat.php | 4 +++- lib/noticelist.php | 4 +++- theme/base/css/display.css | 15 +++++++++------ theme/base/images/icons/icons-01.gif | Bin 3154 -> 3201 bytes theme/base/images/icons/twotone/green/recycle-02.gif | Bin 0 -> 77 bytes theme/default/css/display.css | 6 +++++- theme/identica/css/display.css | 6 +++++- 7 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 theme/base/images/icons/twotone/green/recycle-02.gif (limited to 'actions') diff --git a/actions/repeat.php b/actions/repeat.php index a1c5f443f..38d6efa6c 100644 --- a/actions/repeat.php +++ b/actions/repeat.php @@ -112,7 +112,9 @@ class RepeatAction extends Action $this->element('title', null, _('Repeated')); $this->elementEnd('head'); $this->elementStart('body'); - $this->element('p', array('id' => 'repeat_response'), _('Repeated!')); + $this->element('p', array('id' => 'repeat_response', + 'class' => 'repeated'), + _('Repeated!')); $this->elementEnd('body'); $this->elementEnd('html'); } else { diff --git a/lib/noticelist.php b/lib/noticelist.php index dd0a361c0..2165222ee 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -627,7 +627,9 @@ class NoticeListItem extends Widget if ($user && $user->id != $this->notice->profile_id) { $profile = $user->getProfile(); if ($profile->hasRepeated($this->notice->id)) { - $this->out->text(_('Repeated')); + $this->out->element('span', array('class' => 'repeated', + 'title' => _('Notice repeated')), + _('Repeated')); } else { $rf = new RepeatForm($this->out, $this->notice); $rf->show(); diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 0173d2fbb..2f4636391 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -964,7 +964,7 @@ float:left; font-size:0.95em; margin-left:59px; min-width:60%; -max-width:74%; +max-width:70%; } #showstream .notice div.entry-content, #shownotice .notice div.entry-content { @@ -996,7 +996,7 @@ left:0; .notice-options { position:relative; font-size:0.95em; -width:90px; +width:125px; float:right; } @@ -1007,16 +1007,18 @@ float:left; .notice-options .notice_reply, .notice-options .form_repeat, .notice-options .form_favor, -.notice-options .form_disfavor { +.notice-options .form_disfavor, +.notice-options .repeated { float:left; -margin-left:20%; +margin-left:14%; } .notice-options .form_favor, .notice-options .form_disfavor { margin-left:0; } .notice-options input, -.notice-options a { +.notice-options a, +.notice-options .repeated { text-indent:-9999px; outline:none; } @@ -1047,7 +1049,8 @@ display:none; border:0; padding:0; } -.notice-options a { +.notice-options a, +.notice-options .repeated { width:16px; height:16px; } diff --git a/theme/base/images/icons/icons-01.gif b/theme/base/images/icons/icons-01.gif index 5909d7b19..417327881 100644 Binary files a/theme/base/images/icons/icons-01.gif and b/theme/base/images/icons/icons-01.gif differ diff --git a/theme/base/images/icons/twotone/green/recycle-02.gif b/theme/base/images/icons/twotone/green/recycle-02.gif new file mode 100644 index 000000000..060a8c72f Binary files /dev/null and b/theme/base/images/icons/twotone/green/recycle-02.gif differ diff --git a/theme/default/css/display.css b/theme/default/css/display.css index e0dda3de2..f1d9a9d0b 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -173,7 +173,8 @@ button.close, .entity_moderation p, .entity_sandbox input.submit, .entity_silence input.submit, -.entity_delete input.submit { +.entity_delete input.submit, +.notice-options .repeated { background-image:url(../../base/images/icons/icons-01.gif); background-repeat:no-repeat; background-color:transparent; @@ -338,6 +339,9 @@ background-position:0 -658px; .notice-options form.form_repeat input.submit { background-position:0 -1582px; } +.notice-options .repeated { +background-position:0 -1648px; +} .notices div.entry-content, .notices div.notice-options { diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 999af973c..aa02070ac 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -173,7 +173,8 @@ button.close, .entity_moderation p, .entity_sandbox input.submit, .entity_silence input.submit, -.entity_delete input.submit { +.entity_delete input.submit, +.notice-options .repeated { background-image:url(../../base/images/icons/icons-01.gif); background-repeat:no-repeat; background-color:transparent; @@ -337,6 +338,9 @@ background-position:0 -658px; .notice-options form.form_repeat input.submit { background-position:0 -1582px; } +.notice-options .repeated { +background-position:0 -1648px; +} .notices div.entry-content, .notices div.notice-options { -- cgit v1.2.3-54-g00ecf From 797a0d79fbf4e105ee4ccdce093c1595c1f08a4a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 15 Dec 2009 10:31:25 -0500 Subject: create a method for notification for new messages, and use it --- actions/apidirectmessagenew.php | 2 +- actions/newmessage.php | 8 +------- classes/Message.php | 8 ++++++++ lib/command.php | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) (limited to 'actions') diff --git a/actions/apidirectmessagenew.php b/actions/apidirectmessagenew.php index e6c39ce4a..b9ac92d77 100644 --- a/actions/apidirectmessagenew.php +++ b/actions/apidirectmessagenew.php @@ -175,7 +175,7 @@ class ApiDirectMessageNewAction extends ApiAuthAction return; } - mail_notify_message($message, $this->user, $this->other); + $message->notify(); if ($this->format == 'xml') { $this->showSingleXmlDirectMessage($message); diff --git a/actions/newmessage.php b/actions/newmessage.php index 0db2e7181..350452091 100644 --- a/actions/newmessage.php +++ b/actions/newmessage.php @@ -173,7 +173,7 @@ class NewmessageAction extends Action return; } - $this->notify($user, $this->other, $message); + $message->notify(); if ($this->boolean('ajax')) { $this->startHTML('text/xml;charset=utf-8'); @@ -247,12 +247,6 @@ class NewmessageAction extends Action } } - function notify($from, $to, $message) - { - mail_notify_message($message, $from, $to); - // XXX: Jabber, SMS notifications... probably queued - } - // Do nothing (override) function showNoticeForm() diff --git a/classes/Message.php b/classes/Message.php index 718a9d922..16d0c60b3 100644 --- a/classes/Message.php +++ b/classes/Message.php @@ -89,4 +89,12 @@ class Message extends Memcached_DataObject $contentlimit = self::maxContent(); return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit)); } + + function notify() + { + $from = User::staticGet('id', $this->from_profile); + $to = User::staticGet('id', $this->to_profile); + + mail_notify_message($this, $from, $to); + } } diff --git a/lib/command.php b/lib/command.php index b30780bfb..67140c348 100644 --- a/lib/command.php +++ b/lib/command.php @@ -372,7 +372,7 @@ class MessageCommand extends Command } $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); if ($message) { - mail_notify_message($message, $this->user, $other); + $message->notify(); $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); } else { $channel->error($this->user, _('Error sending direct message.')); -- cgit v1.2.3-54-g00ecf From 90c378a81f78215e3bf85cc64a2224dd07f12d8c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 15 Dec 2009 12:29:37 -0500 Subject: broadcast for repeats --- actions/repeat.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'actions') diff --git a/actions/repeat.php b/actions/repeat.php index 38d6efa6c..b75523498 100644 --- a/actions/repeat.php +++ b/actions/repeat.php @@ -106,6 +106,8 @@ class RepeatAction extends Action { $repeat = $this->notice->repeat($this->user->id, 'web'); + common_broadcast_notice($repeat); + if ($this->boolean('ajax')) { $this->startHTML('text/xml;charset=utf-8'); $this->elementStart('head'); -- cgit v1.2.3-54-g00ecf