diff options
-rw-r--r-- | actions/apistatusesretweet.php | 136 | ||||
-rw-r--r-- | actions/apistatusesretweets.php | 116 | ||||
-rw-r--r-- | actions/repeat.php | 122 | ||||
-rw-r--r-- | actions/showstream.php | 46 | ||||
-rw-r--r-- | classes/Notice.php | 104 | ||||
-rw-r--r-- | classes/Profile.php | 11 | ||||
-rw-r--r-- | classes/statusnet.ini | 2 | ||||
-rw-r--r-- | db/08to09.sql | 4 | ||||
-rw-r--r-- | db/08to09_pg.sql | 1 | ||||
-rw-r--r-- | db/statusnet.sql | 2 | ||||
-rw-r--r-- | db/statusnet_pg.sql | 4 | ||||
-rw-r--r-- | js/util.js | 5 | ||||
-rw-r--r-- | lib/api.php | 23 | ||||
-rw-r--r-- | lib/noticelist.php | 81 | ||||
-rw-r--r-- | lib/repeatform.php | 145 | ||||
-rw-r--r-- | lib/router.php | 11 | ||||
-rw-r--r-- | theme/base/css/display.css | 4 |
17 files changed, 803 insertions, 14 deletions
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 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Repeat a notice through the API + * + * 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 Evan Prodromou <evan@status.net> + * @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 <evan@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 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/actions/apistatusesretweets.php b/actions/apistatusesretweets.php new file mode 100644 index 000000000..c54a374e2 --- /dev/null +++ b/actions/apistatusesretweets.php @@ -0,0 +1,116 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show up to 100 repeats of a notice + * + * 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 Evan Prodromou <evan@status.net> + * @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 <evan@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 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/actions/repeat.php b/actions/repeat.php new file mode 100644 index 000000000..a1c5f443f --- /dev/null +++ b/actions/repeat.php @@ -0,0 +1,122 @@ +<?php + +/** + * Repeat action. + * + * PHP version 5 + * + * @category Action + * @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) 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')) { + exit(1); +} + +/** + * Repeat action + * + * @category Action + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class RepeatAction 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 repeat 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 repeat 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->hasRepeated($id)) { + $this->clientError(_("You already repeated that notice.")); + return false; + } + + return true; + } + + /** + * Class handler. + * + * @param array $args query arguments + * + * @return void + */ + + function handle($args) + { + $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, _('Repeated')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->element('p', array('id' => 'repeat_response'), _('Repeated!')); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + // FIXME! + } + } +} 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'); + } + } } diff --git a/classes/Notice.php b/classes/Notice.php index 4422866fa..ec80f763f 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -55,13 +55,13 @@ class Notice extends Memcached_DataObject public $__table = 'notice'; // table name public $id; // int(4) primary_key not_null - public $profile_id; // int(4) not_null + public $profile_id; // int(4) multiple_key not_null public $uri; // varchar(255) unique_key - public $content; // text() - public $rendered; // text() + public $content; // text + public $rendered; // text public $url; // varchar(255) - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $created; // datetime multiple_key not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $reply_to; // int(4) public $is_local; // tinyint(1) public $source; // varchar(32) @@ -70,9 +70,11 @@ class Notice extends Memcached_DataObject public $lon; // decimal(10,7) public $location_id; // int(4) public $location_ns; // int(4) + public $repeat_of; // int(4) /* Static get */ - function staticGet($k,$v=NULL) { + function staticGet($k,$v=NULL) + { return Memcached_DataObject::staticGet('Notice',$k,$v); } @@ -234,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); @@ -432,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(); @@ -1432,4 +1454,72 @@ 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)); + } + + // 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/classes/Profile.php b/classes/Profile.php index 4b2e09006..03196447b 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -716,4 +716,15 @@ class Profile extends Memcached_DataObject } return $result; } + + function hasRepeated($notice_id) + { + // XXX: not really a pkey, but should work + + $notice = Memcached_DataObject::pkeyGet('Notice', + array('profile_id' => $this->id, + 'repeat_of' => $notice_id)); + + return !empty($notice); + } } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 835faeb0b..ff4ef2c14 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -1,3 +1,4 @@ + [avatar] profile_id = 129 original = 17 @@ -306,6 +307,7 @@ lat = 1 lon = 1 location_id = 1 location_ns = 1 +repeat_of = 1 [notice__keys] id = N diff --git a/db/08to09.sql b/db/08to09.sql index 64640f4ce..28ec3ec16 100644 --- a/db/08to09.sql +++ b/db/08to09.sql @@ -4,8 +4,10 @@ alter table notice add column lon decimal(10,7) comment 'longitude', add column location_id integer comment 'location id if possible', add column location_ns integer comment 'namespace for location', + add column repeat_of integer comment 'notice this is a repeat of' references notice (id), drop index notice_profile_id_idx, - add index notice_profile_id_idx (profile_id,created,id); + add index notice_profile_id_idx (profile_id,created,id), + add index notice_repeatof_idx (repeat_of); alter table message modify column content text comment 'message content'; diff --git a/db/08to09_pg.sql b/db/08to09_pg.sql index 1df8c249b..0398952f6 100644 --- a/db/08to09_pg.sql +++ b/db/08to09_pg.sql @@ -74,6 +74,7 @@ ALTER TABLE notice ADD COLUMN lat decimal(10, 7) /* comment 'latitude'*/; ALTER TABLE notice ADD COLUMN lon decimal(10,7) /* comment 'longitude'*/; ALTER TABLE notice ADD COLUMN location_id integer /* comment 'location id if possible'*/ ; ALTER TABLE notice ADD COLUMN location_ns integer /* comment 'namespace for location'*/; +ALTER TABLE notice ADD COLUMN repeat_of integer / * comment 'notice this is a repeat of' */ references notice (id); ALTER TABLE profile ADD COLUMN lat decimal(10,7) /*comment 'latitude'*/ ; ALTER TABLE profile ADD COLUMN lon decimal(10,7) /*comment 'longitude'*/; diff --git a/db/statusnet.sql b/db/statusnet.sql index 18abcdfdb..6b3c2ca06 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -129,11 +129,13 @@ create table notice ( lon decimal(10,7) comment 'longitude', location_id integer comment 'location id if possible', location_ns integer comment 'namespace for location', + repeat_of integer comment 'notice this is a repeat of' references notice (id), index notice_profile_id_idx (profile_id,created,id), index notice_conversation_idx (conversation), index notice_created_idx (created), index notice_replyto_idx (reply_to), + index notice_repeatof_idx (repeat_of), FULLTEXT(content) ) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; diff --git a/db/statusnet_pg.sql b/db/statusnet_pg.sql index c37fa81de..020bfd967 100644 --- a/db/statusnet_pg.sql +++ b/db/statusnet_pg.sql @@ -135,7 +135,9 @@ create table notice ( lat decimal(10,7) /* comment 'latitude'*/ , lon decimal(10,7) /* comment 'longitude'*/ , location_id integer /* comment 'location id if possible'*/ , - location_ns integer /* comment 'namespace for location'*/ + location_ns integer /* comment 'namespace for location'*/ , + repeat_of integer /* comment 'notice this is a repeat of' */ references notice (id) , + /* FULLTEXT(content) */ ); diff --git a/js/util.js b/js/util.js index 336cd8cfe..f60b5d313 100644 --- a/js/util.js +++ b/js/util.js @@ -315,6 +315,10 @@ var SN = { // StatusNet $('.form_disfavor').each(function() { SN.U.FormXHR($(this)); }); }, + NoticeRepeat: function() { + $('.form_repeat').each(function() { SN.U.FormXHR($(this)); }); + }, + NoticeAttachments: function() { $('.notice a.attachment').each(function() { SN.U.NoticeWithAttachment($(this).closest('.notice')); @@ -448,6 +452,7 @@ var SN = { // StatusNet Notices: function() { if ($('body.user_in').length > 0) { SN.U.NoticeFavor(); + SN.U.NoticeRepeat(); SN.U.NoticeReply(); } diff --git a/lib/api.php b/lib/api.php index eacb80dbe..12e3ba531 100644 --- a/lib/api.php +++ b/lib/api.php @@ -214,6 +214,20 @@ class ApiAction extends Action function twitterStatusArray($notice, $include_user=true) { + $base = $this->twitterSimpleStatusArray($notice, $include_user); + + if (empty($notice->repeat_of)) { + return $base; + } else { + $original = Notice::staticGet('id', $notice->repeat_of); + $original_array = $this->twitterSimpleStatusArray($original, $include_user); + $original_array['retweeted_status'] = $base; + return $original_array; + } + } + + function twitterSimpleStatusArray($notice, $include_user=true) + { $profile = $notice->getProfile(); $twitter_status = array(); @@ -446,9 +460,9 @@ class ApiAction extends Action } } - function showTwitterXmlStatus($twitter_status) + function showTwitterXmlStatus($twitter_status, $tag='status') { - $this->elementStart('status'); + $this->elementStart($tag); foreach($twitter_status as $element => $value) { switch ($element) { case 'user': @@ -463,11 +477,14 @@ class ApiAction extends Action case 'geo': $this->showGeoRSS($value); break; + case 'retweeted_status': + $this->showTwitterXmlStatus($value, 'retweeted_status'); + break; default: $this->element($element, null, $value); } } - $this->elementEnd('status'); + $this->elementEnd($tag); } function showTwitterXmlGroup($twitter_group) diff --git a/lib/noticelist.php b/lib/noticelist.php index 21cec528f..7319a62ea 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -147,6 +147,10 @@ class NoticeListItem extends Widget var $notice = null; + /** The notice that was repeated. */ + + var $repeat = null; + /** The profile of the author of the notice, extracted once for convenience. */ var $profile = null; @@ -162,8 +166,13 @@ class NoticeListItem extends Widget function __construct($notice, $out=null) { parent::__construct($out); - $this->notice = $notice; - $this->profile = $notice->getProfile(); + if (!empty($notice->repeat_of)) { + $this->notice = Notice::staticGet('id', $notice->repeat_of); + $this->repeat = $notice; + } else { + $this->notice = $notice; + } + $this->profile = $this->notice->getProfile(); } /** @@ -202,6 +211,7 @@ class NoticeListItem extends Widget $this->showNoticeSource(); $this->showNoticeLocation(); $this->showContext(); + $this->showRepeat(); $this->out->elementEnd('div'); } @@ -212,6 +222,7 @@ class NoticeListItem extends Widget $this->out->elementStart('div', 'notice-options'); $this->showFaveForm(); $this->showReplyLink(); + $this->showRepeatForm(); $this->showDeleteLink(); $this->out->elementEnd('div'); } @@ -508,6 +519,52 @@ class NoticeListItem extends Widget } /** + * show a link to the author of repeat + * + * @return void + */ + + function showRepeat() + { + if (!empty($this->repeat)) { + + $repeater = Profile::staticGet('id', $this->repeat->profile_id); + + $attrs = array('href' => $repeater->profileurl, + 'class' => 'url'); + + if (!empty($repeater->fullname)) { + $attrs['title'] = $repeater->fullname . ' (' . $repeater->nickname . ')'; + } + + $this->out->elementStart('span', 'repeat'); + + $this->out->elementStart('a', $attrs); + + $avatar = $repeater->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' => + ($repeater->fullname) ? + $repeater->fullname : + $repeater->nickname)); + + $this->out->elementEnd('a'); + + $text_link = XMLStringer::estring('a', $attrs, $repeater->nickname); + + $this->out->raw(sprintf(_('Repeated by %s'), $text_link)); + + $this->out->elementEnd('span'); + } + } + + /** * show a link to reply to the current notice * * Should either do the reply in the current notice form (if available), or @@ -552,6 +609,26 @@ class NoticeListItem extends Widget } /** + * show the form to repeat a notice + * + * @return void + */ + + function showRepeatForm() + { + $user = common_current_user(); + if ($user && $user->id != $this->notice->profile_id) { + $profile = $user->getProfile(); + if ($profile->hasRepeated($this->notice->id)) { + $this->out->text(_('Repeated')); + } else { + $rf = new RepeatForm($this->out, $this->notice); + $rf->show(); + } + } + } + + /** * finish the notice * * Close the last elements in the notice list item diff --git a/lib/repeatform.php b/lib/repeatform.php new file mode 100644 index 000000000..50e5d6dbe --- /dev/null +++ b/lib/repeatform.php @@ -0,0 +1,145 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Form for repeating a notice + * + * 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 Form + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @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); +} + +/** + * Form for repeating a notice + * + * @category Form + * @package StatusNet + * @author Evan Prodromou <evan@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 RepeatForm extends Form +{ + /** + * Notice to repeat + */ + + var $notice = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to repeat + */ + + function __construct($out=null, $notice=null) + { + parent::__construct($out); + + $this->notice = $notice; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'repeat-' . $this->notice->id; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('repeat'); + } + + /** + * Include a session token for CSRF protection + * + * @return void + */ + + function sessionToken() + { + $this->out->hidden('token-' . $this->notice->id, + common_session_token()); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _('Repeat this notice')); + } + + /** + * Data elements + * + * @return void + */ + + function formData() + { + $this->out->hidden('notice-n'.$this->notice->id, + $this->notice->id, + 'notice'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('repeat-submit-' . $this->notice->id, + _('Repeat'), 'submit', null, _('Repeat this notice')); + } + + /** + * Class of the form. + * + * @return string the form's class + */ + + function formClass() + { + return 'form_repeat'; + } +} diff --git a/lib/router.php b/lib/router.php index 37525319f..b0b95b080 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) { @@ -358,6 +359,16 @@ class Router 'id' => '[0-9]+', 'format' => '(xml|json)')); + $m->connect('api/statuses/retweet/:id.:format', + array('action' => 'ApiStatusesRetweet', + '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', diff --git a/theme/base/css/display.css b/theme/base/css/display.css index e88e1f222..7446a42cd 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -994,11 +994,13 @@ float:left; } .notice-options .notice_delete, .notice-options .notice_reply, +.notice-options .form_repeat, .notice-options .form_favor, .notice-options .form_disfavor { float:left; margin-left:20%; } +.notice-options .form_repeat, .notice-options .form_favor, .notice-options .form_disfavor { margin-left:0; @@ -1024,10 +1026,12 @@ border-radius:0; -moz-border-radius:0; -webkit-border-radius:0; } +.notice-options .form_repeat legend, .notice-options .form_favor legend, .notice-options .form_disfavor legend { display:none; } +.notice-options .form_repeat fieldset, .notice-options .form_favor fieldset, .notice-options .form_disfavor fieldset { border:0; |