--- /dev/null
+<?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);
+ }
+ }
+ }
+}
--- /dev/null
+<?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;
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show authenticating user's most recent repeats
+ *
+ * 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 authenticating user's most recent repeats
+ *
+ * @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 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;
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show most recent notices that are repeats in user's inbox
+ *
+ * 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';
+
+/**
+ * Show most recent notices that are repeats in user's inbox
+ *
+ * @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 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;
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show authenticating user's most recent notices that have been repeated
+ *
+ * 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 authenticating user's most recent notices that have been repeated
+ *
+ * @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 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;
+ }
+ }
+}
--- /dev/null
+<?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!
+ }
+ }
+}
{
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');
+ }
+ }
}
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)
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);
}
//Null any notices that are replies to this notice
$this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id));
+
+ //Null any notices that are repeats of this notice
+ //XXX: probably need to uncache these, too
+
+ $this->query(sprintf("UPDATE notice set repeat_of = null WHERE repeat_of = %d", $this->id));
+
$related = array('Reply',
'Fave',
'Notice_tag',
$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);
$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);
+
+ $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);
+ }
+
+ $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);
+ }
+
+ $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);
+ }
+ }
+ }
+
function blowConversationCache($blowLast=false)
{
$cache = common_memcache();
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;
+ }
}
}
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);
+ }
}
$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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
}
+
[avatar]
profile_id = 129
original = 17
lon = 1
location_id = 1
location_ns = 1
+repeat_of = 1
[notice__keys]
id = N
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';
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'*/;
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;
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) */
);
$('.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'));
Notices: function() {
if ($('body.user_in').length > 0) {
SN.U.NoticeFavor();
+ SN.U.NoticeRepeat();
SN.U.NoticeReply();
}
}
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();
}
}
- 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':
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)
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;
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();
}
/**
$this->showNoticeSource();
$this->showNoticeLocation();
$this->showContext();
+ $this->showRepeat();
$this->out->elementEnd('div');
}
$this->out->elementStart('div', 'notice-options');
$this->showFaveForm();
$this->showReplyLink();
+ $this->showRepeatForm();
$this->showDeleteLink();
$this->out->elementEnd('div');
}
}
}
+ /**
+ * 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
*
}
}
+ /**
+ * 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
*
--- /dev/null
+<?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';
+ }
+}
'groupblock', 'groupunblock',
'sandbox', 'unsandbox',
'silence', 'unsilence',
+ 'repeat',
'deleteuser');
foreach ($main as $a) {
'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/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)'));
+
$m->connect('api/statuses/friends.:format',
array('action' => 'ApiUserFriends',
'format' => '(xml|json)'));
'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',
}
.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;
-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;