Merge branch '0.9.x' into testing

This commit is contained in:
Evan Prodromou 2009-12-15 16:24:52 -05:00
commit 2a1468ec8b
99 changed files with 27670 additions and 11340 deletions

29
README
View File

@ -358,7 +358,7 @@ It's possible to configure the software so it looks like this instead:
These "fancy URLs" are more readable and memorable for users. To use
fancy URLs, you must either have Apache 2.x with .htaccess enabled and
mod_redirect enabled, -OR- know how to configure "url redirection" in
mod_rewrite enabled, -OR- know how to configure "url redirection" in
your server.
1. Copy the htaccess.sample file to .htaccess in your StatusNet
@ -384,6 +384,18 @@ like:
If you changed your HTTP server configuration, you may need to restart
the server first.
If it doesn't work, double-check that AllowOverride for the StatusNet
directory is 'All' in your Apache configuration file. This is usually
/etc/httpd.conf, /etc/apache/httpd.conf, or (on Debian and Ubuntu)
/etc/apache2/sites-available/default. See the Apache documentation for
.htaccess files for more details:
http://httpd.apache.org/docs/2.2/howto/htaccess.html
Also, check that mod_rewrite is installed and enabled:
http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html
Sphinx
------
@ -1407,6 +1419,21 @@ contentlimit: max length of the plain-text content of a message.
Default is null, meaning to use the site-wide text limit.
0 means no limit.
logincommand
------------
Configuration options for the login command.
disabled: whether to enable this command. If enabled, users who send
the text 'login' to the site through any channel will
receive a link to login to the site automatically in return.
Possibly useful for users who primarily use an XMPP or SMS
interface and can't be bothered to remember their site
password. Note that the security implications of this are
pretty serious and have not been thoroughly tested. You
should enable it only after you've convinced yourself that
it is safe. Default is 'false'.
Plugins
=======

View File

@ -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);

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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);
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':

View File

@ -110,24 +110,26 @@ 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');
$id = "tag:$taguribase:FriendsTimeline:" . $this->user->id;
$link = common_local_url(
'all', array('nickname' => $this->user->nickname)
);
'all', array('nickname' => $this->user->nickname)
);
$subtitle = sprintf(
_('Updates from %1$s and friends on %2$s!'),
$this->user->nickname, $sitename
);
_('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':
@ -135,17 +137,17 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
if (isset($target_id)) {
$selfuri = common_root_url() .
'api/statuses/friends_timeline/' .
$target_id . '.atom';
'api/statuses/friends_timeline/' .
$target_id . '.atom';
} else {
$selfuri = common_root_url() .
'api/statuses/friends_timeline.atom';
'api/statuses/friends_timeline.atom';
}
$this->showAtomTimeline(
$this->notices, $title, $id, $link,
$subtitle, null, $selfuri
);
$this->notices, $title, $id, $link,
$subtitle, null, $selfuri, $logo
);
break;
case 'json':
$this->showJsonTimeline($this->notices);
@ -167,17 +169,13 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
$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
);
$notice = $this->user->ownFriendsTimeline(($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
);
$notice = $this->user->friendsTimeline(($this->page-1) * $this->count,
$this->count, $this->since_id,
$this->max_id, $this->since);
}
while ($notice->fetch()) {
@ -231,14 +229,14 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
$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))
)
. '"';
':',
array($this->arg('action'),
common_language(),
$this->user->id,
strtotime($this->notices[0]->created),
strtotime($this->notices[$last]->created))
)
. '"';
}
return null;

View File

@ -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':

249
actions/apitimelinehome.php Normal file
View File

@ -0,0 +1,249 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Show the home timeline
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category API
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @author Evan Prodromou <evan@status.net>
* @author Jeffery To <jeffery.to@gmail.com>
* @author mac65 <mac65@mac65.com>
* @author Mike Cochrane <mikec@mikenz.geek.nz>
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@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/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 <candrews@integralblue.com>
* @author Evan Prodromou <evan@status.net>
* @author Jeffery To <jeffery.to@gmail.com>
* @author mac65 <mac65@mac65.com>
* @author Mike Cochrane <mikec@mikenz.geek.nz>
* @author Robin Millette <robin@millette.info>
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class 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;
}
}

View File

@ -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':

View File

@ -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':

View File

@ -0,0 +1,126 @@
<?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;
}
}
}

View File

@ -0,0 +1,125 @@
<?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;
}
}
}

View File

@ -0,0 +1,126 @@
<?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;
}
}
}

View File

@ -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':

View File

@ -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':

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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()

View File

@ -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);

126
actions/repeat.php Normal file
View File

@ -0,0 +1,126 @@
<?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');
common_broadcast_notice($repeat);
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',
'class' => 'repeated'),
_('Repeated!'));
$this->elementEnd('body');
$this->elementEnd('html');
} else {
// FIXME!
}
}
}

View File

@ -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');
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -23,6 +23,20 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Memcached_DataObject extends DB_DataObject
{
/**
* Destructor to free global memory resources associated with
* this data object when it's unset or goes out of scope.
* DB_DataObject doesn't do this yet by itself.
*/
function __destruct()
{
$this->free();
if (method_exists('DB_DataObject', '__destruct')) {
parent::__destruct();
}
}
function &staticGet($cls, $k, $v=null)
{
if (is_null($v)) {

View File

@ -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);
}
}

View File

@ -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);
}
@ -113,6 +115,12 @@ class Notice extends Memcached_DataObject
//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',
@ -167,9 +175,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);
@ -225,7 +242,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);
@ -423,10 +447,60 @@ 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);
$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();
@ -456,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'));
}
}
}
}
@ -505,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();
@ -581,193 +673,6 @@ class Notice extends Memcached_DataObject
}
}
# XXX: too many args; we need to move to named params or even a separate
# class for notice streams
static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) {
if (common_config('memcached', 'enabled')) {
# Skip the cache if this is a since, since_id or max_id qry
if ($since_id > 0 || $max_id > 0 || $since) {
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
} else {
return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
}
}
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
}
static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) {
$needAnd = false;
$needWhere = true;
if (preg_match('/\bWHERE\b/i', $qry)) {
$needWhere = false;
$needAnd = true;
}
if ($since_id > 0) {
if ($needWhere) {
$qry .= ' WHERE ';
$needWhere = false;
} else {
$qry .= ' AND ';
}
$qry .= ' notice.id > ' . $since_id;
}
if ($max_id > 0) {
if ($needWhere) {
$qry .= ' WHERE ';
$needWhere = false;
} else {
$qry .= ' AND ';
}
$qry .= ' notice.id <= ' . $max_id;
}
if ($since) {
if ($needWhere) {
$qry .= ' WHERE ';
$needWhere = false;
} else {
$qry .= ' AND ';
}
$qry .= ' notice.created > \'' . date('Y-m-d H:i:s', $since) . '\'';
}
# Allow ORDER override
if ($order) {
$qry .= $order;
} else {
$qry .= ' ORDER BY notice.created DESC, notice.id DESC ';
}
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
$notice = new Notice();
$notice->query($qry);
return $notice;
}
# XXX: this is pretty long and should probably be broken up into
# some helper functions
static function getCachedStream($qry, $cachekey, $offset, $limit, $order) {
# If outside our cache window, just go to the DB
if ($offset + $limit > NOTICE_CACHE_WINDOW) {
return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null);
}
# Get the cache; if we can't, just go to the DB
$cache = common_memcache();
if (empty($cache)) {
return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null);
}
# Get the notices out of the cache
$notices = $cache->get(common_cache_key($cachekey));
# On a cache hit, return a DB-object-like wrapper
if ($notices !== false) {
$wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit));
return $wrapper;
}
# If the cache was invalidated because of new data being
# added, we can try and just get the new stuff. We keep an additional
# copy of the data at the key + ';last'
# No cache hit. Try to get the *last* cached version
$last_notices = $cache->get(common_cache_key($cachekey) . ';last');
if ($last_notices) {
# Reverse-chron order, so last ID is last.
$last_id = $last_notices[0]->id;
# XXX: this assumes monotonically increasing IDs; a fair
# bet with our DB.
$new_notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW,
$last_id, null, $order, null);
if ($new_notice) {
$new_notices = array();
while ($new_notice->fetch()) {
$new_notices[] = clone($new_notice);
}
$new_notice->free();
$notices = array_slice(array_merge($new_notices, $last_notices),
0, NOTICE_CACHE_WINDOW);
# Store the array in the cache for next time
$result = $cache->set(common_cache_key($cachekey), $notices);
$result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
# return a wrapper of the array for use now
return new ArrayWrapper(array_slice($notices, $offset, $limit));
}
}
# Otherwise, get the full cache window out of the DB
$notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, null, null, $order, null);
# If there are no hits, just return the value
if (empty($notice)) {
return $notice;
}
# Pack results into an array
$notices = array();
while ($notice->fetch()) {
$notices[] = clone($notice);
}
$notice->free();
# Store the array in the cache for next time
$result = $cache->set(common_cache_key($cachekey), $notices);
$result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
# return a wrapper of the array for use now
$wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit));
return $wrapper;
}
function getStreamByIds($ids)
{
$cache = common_memcache();
@ -1423,4 +1328,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;
}
}

View File

@ -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);
}
}

View File

@ -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();
@ -741,4 +812,163 @@ 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;
}
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;
}
}

View File

@ -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

View File

@ -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';

View File

@ -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'*/;

View File

@ -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;

View File

@ -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) */
);

View File

@ -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();
}

View File

@ -951,6 +951,36 @@ class Action extends HTMLOutputter // lawsuit
}
}
/**
* Integer value of an argument
*
* @param string $key query key we're interested in
* @param string $defValue optional default value (default null)
* @param string $maxValue optional max value (default null)
* @param string $minValue optional min value (default null)
*
* @return integer integer value
*/
function int($key, $defValue=null, $maxValue=null, $minValue=null)
{
$arg = strtolower($this->trimmed($key));
if (is_null($arg) || !is_integer($arg)) {
return $defValue;
}
if (!is_null($maxValue)) {
$arg = min($arg, $maxValue);
}
if (!is_null($minValue)) {
$arg = max($arg, $minValue);
}
return $arg;
}
/**
* Server error
*

View File

@ -53,13 +53,14 @@ if (!defined('STATUSNET')) {
class ApiAction extends Action
{
var $format = null;
var $user = null;
var $page = null;
var $count = null;
var $max_id = null;
var $since_id = null;
var $since = null;
var $format = null;
var $user = null;
var $auth_user = null;
var $page = null;
var $count = null;
var $max_id = null;
var $since_id = null;
var $since = null;
/**
* Initialization.
@ -134,17 +135,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 +166,7 @@ class ApiAction extends Action
$timezone = 'UTC';
if (!empty($user) && !empty($user->timezone)) {
if ($user->timezone) {
$timezone = $user->timezone;
}
@ -188,13 +191,14 @@ class ApiAction extends Action
$twitter_user['following'] = false;
$twitter_user['notifications'] = false;
if (isset($apidata['user'])) {
if (isset($this->auth_user)) {
$twitter_user['following'] = $apidata['user']->isSubscribed($profile);
$twitter_user['following'] = $this->auth_user->isSubscribed($profile);
// Notifications on?
$sub = Subscription::pkeyGet(array('subscriber' =>
$apidata['user']->id, 'subscribed' => $profile->id));
$this->auth_user->id,
'subscribed' => $profile->id));
if ($sub) {
$twitter_user['notifications'] = ($sub->jabber || $sub->sms);
@ -213,6 +217,21 @@ class ApiAction extends Action
}
function twitterStatusArray($notice, $include_user=true)
{
$base = $this->twitterSimpleStatusArray($notice, $include_user);
if (!empty($notice->repeat_of)) {
$original = Notice::staticGet('id', $notice->repeat_of);
if (!empty($original)) {
$original_array = $this->twitterSimpleStatusArray($original, $include_user);
$base['retweeted_status'] = $original_array;
}
}
return $base;
}
function twitterSimpleStatusArray($notice, $include_user=true)
{
$profile = $notice->getProfile();
@ -446,9 +465,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 +482,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)
@ -586,7 +608,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 +622,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 +650,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 +659,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',

View File

@ -53,8 +53,6 @@ require_once INSTALLDIR . '/lib/api.php';
class ApiAuthAction extends ApiAction
{
var $auth_user = null;
/**
* Take arguments for running, and output basic auth header if needed
*

View File

@ -372,6 +372,7 @@ class MessageCommand extends Command
}
$message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source());
if ($message) {
$message->notify();
$channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other));
} else {
$channel->error($this->user, _('Error sending direct message.'));
@ -379,6 +380,65 @@ class MessageCommand extends Command
}
}
class RepeatCommand extends Command
{
var $other = null;
function __construct($user, $other)
{
parent::__construct($user);
$this->other = $other;
}
function execute($channel)
{
if(substr($this->other,0,1)=='#'){
//repeating a specific notice_id
$notice = Notice::staticGet(substr($this->other,1));
if (!$notice) {
$channel->error($this->user, _('Notice with that id does not exist'));
return;
}
$recipient = $notice->getProfile();
}else{
//repeating a given user's last notice
$recipient =
common_relative_profile($this->user, common_canonical_nickname($this->other));
if (!$recipient) {
$channel->error($this->user, _('No such user.'));
return;
}
$notice = $recipient->getCurrentNotice();
if (!$notice) {
$channel->error($this->user, _('User has no last notice'));
return;
}
}
if($this->user->id == $notice->profile_id)
{
$channel->error($this->user, _('Cannot repeat your own notice'));
return;
}
if ($recipient->hasRepeated($notice->id)) {
$channel->error($this->user, _('Already repeated that notice'));
return;
}
$repeat = $notice->repeat($this->user->id, $channel->source);
if ($repeat) {
common_broadcast_notice($repeat);
$channel->output($this->user, sprintf(_('Notice from %s repeated'), $recipient->nickname));
} else {
$channel->error($this->user, _('Error repeating notice.'));
}
}
}
class ReplyCommand extends Command
{
var $other = null;
@ -433,8 +493,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 {
@ -695,6 +756,8 @@ class HelpCommand extends Command
"whois <nickname> - get profile info on user\n".
"fav <nickname> - add user's last notice as a 'fave'\n".
"fav #<notice_id> - add notice with the given id as a 'fave'\n".
"repeat #<notice_id> - repeat a notice with a given id\n".
"repeat <nickname> - repeat the last notice from user\n".
"reply #<notice_id> - reply to notice with a given id\n".
"reply <nickname> - reply to the last notice from user\n".
"join <group> - join group\n".

View File

@ -169,6 +169,19 @@ class CommandInterpreter
} else {
return new ReplyCommand($user, $other, $extra);
}
case 'repeat':
case 'rp':
case 'rt':
case 'rd':
if (!$arg) {
return null;
}
list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
return new RepeatCommand($user, $other);
}
case 'whois':
if (!$arg) {
return null;

View File

@ -229,4 +229,6 @@ $default =
array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth
'omb' =>
array('timeout' => 5), // HTTP request timeout in seconds when contacting remote hosts for OMB updates
'logincommand' =>
array('disabled' => true),
);

View File

@ -272,6 +272,7 @@ function get_nice_language_list()
function get_all_languages() {
return array(
'ar' => array('q' => 0.8, 'lang' => 'ar', 'name' => 'Arabic', 'direction' => 'rtl'),
'arz' => array('q' => 0.8, 'lang' => 'arz', 'name' => 'Egyptian Spoken Arabic', 'direction' => 'rtl'),
'bg' => array('q' => 0.8, 'lang' => 'bg', 'name' => 'Bulgarian', 'direction' => 'ltr'),
'ca' => array('q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'),
'cs' => array('q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'),
@ -286,6 +287,7 @@ function get_all_languages() {
'ga' => array('q' => 0.5, 'lang' => 'ga', 'name' => 'Galician', 'direction' => 'ltr'),
'he' => array('q' => 0.5, 'lang' => 'he', 'name' => 'Hebrew', 'direction' => 'rtl'),
'hsb' => array('q' => 0.8, 'lang' => 'hsb', 'name' => 'Upper Sorbian', 'direction' => 'ltr'),
'ia' => array('q' => 0.8, 'lang' => 'ia', 'name' => 'Interlingua', 'direction' => 'ltr'),
'is' => array('q' => 0.1, 'lang' => 'is', 'name' => 'Icelandic', 'direction' => 'ltr'),
'it' => array('q' => 1, 'lang' => 'it', 'name' => 'Italian', 'direction' => 'ltr'),
'jp' => array('q' => 0.5, 'lang' => 'ja', 'name' => 'Japanese', 'direction' => 'ltr'),

View File

@ -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,18 @@ class NoticeListItem extends Widget
function __construct($notice, $out=null)
{
parent::__construct($out);
$this->notice = $notice;
$this->profile = $notice->getProfile();
if (!empty($notice->repeat_of)) {
$original = Notice::staticGet('id', $notice->repeat_of);
if (empty($original)) { // could have been deleted
$this->notice = $notice;
} else {
$this->notice = $original;
$this->repeat = $notice;
}
} else {
$this->notice = $notice;
}
$this->profile = $this->notice->getProfile();
}
/**
@ -202,6 +216,7 @@ class NoticeListItem extends Widget
$this->showNoticeSource();
$this->showNoticeLocation();
$this->showContext();
$this->showRepeat();
$this->out->elementEnd('div');
}
@ -212,6 +227,7 @@ class NoticeListItem extends Widget
$this->out->elementStart('div', 'notice-options');
$this->showFaveForm();
$this->showReplyLink();
$this->showRepeatForm();
$this->showDeleteLink();
$this->out->elementEnd('div');
}
@ -227,8 +243,9 @@ class NoticeListItem extends Widget
{
// XXX: RDFa
// TODO: add notice_type class e.g., notice_video, notice_image
$id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id;
$this->out->elementStart('li', array('class' => 'hentry notice',
'id' => 'notice-' . $this->notice->id));
'id' => 'notice-' . $id));
}
/**
@ -507,6 +524,40 @@ 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 vcard');
$this->out->raw(_('Repeated by'));
$avatar = $repeater->getAvatar(AVATAR_MINI_SIZE);
$this->out->elementStart('a', $attrs);
$this->out->element('span', 'nickname', $repeater->nickname);
$this->out->elementEnd('a');
$this->out->elementEnd('span');
}
}
/**
* show a link to reply to the current notice
*
@ -540,17 +591,41 @@ class NoticeListItem extends Widget
{
$user = common_current_user();
$todel = (empty($this->repeat)) ? $this->notice : $this->repeat;
if (!empty($user) &&
($this->notice->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) {
($todel->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) {
$deleteurl = common_local_url('deletenotice',
array('notice' => $this->notice->id));
array('notice' => $todel->id));
$this->out->element('a', array('href' => $deleteurl,
'class' => 'notice_delete',
'title' => _('Delete this notice')), _('Delete'));
}
}
/**
* 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->element('span', array('class' => 'repeated',
'title' => _('Notice repeated')),
_('Repeated'));
} else {
$rf = new RepeatForm($this->out, $this->notice);
$rf->show();
}
}
}
/**
* finish the notice
*

View File

@ -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);
}

View File

@ -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."));
}
}

145
lib/repeatform.php Normal file
View File

@ -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';
}
}

View File

@ -99,6 +99,7 @@ class Router
'groupblock', 'groupunblock',
'sandbox', 'unsandbox',
'silence', 'unsilence',
'repeat',
'deleteuser');
foreach ($main as $a) {
@ -282,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)'));
@ -318,6 +320,18 @@ 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/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)'));
@ -358,6 +372,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',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -0,0 +1,61 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-12-11 16:27-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: GravatarPlugin.php:57
msgid "Set Gravatar"
msgstr ""
#: GravatarPlugin.php:60
msgid "If you want to use your Gravatar image, click \"Add\"."
msgstr ""
#: GravatarPlugin.php:65
msgid "Add"
msgstr ""
#: GravatarPlugin.php:75
msgid "Remove Gravatar"
msgstr ""
#: GravatarPlugin.php:78
msgid "If you want to remove your Gravatar image, click \"Remove\"."
msgstr ""
#: GravatarPlugin.php:83
msgid "Remove"
msgstr ""
#: GravatarPlugin.php:88
msgid "To use a Gravatar first enter in an email address."
msgstr ""
#: GravatarPlugin.php:137
msgid "You do not have a email set in your profile."
msgstr ""
#: GravatarPlugin.php:155
msgid "Failed to save Gravatar to the DB."
msgstr ""
#: GravatarPlugin.php:159
msgid "Gravatar added."
msgstr ""
#: GravatarPlugin.php:177
msgid "Gravatar removed."
msgstr ""

View File

@ -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);

View File

@ -59,6 +59,7 @@ class RealtimePlugin extends Plugin
{
$this->replyurl = common_local_url('newnotice');
$this->favorurl = common_local_url('favor');
$this->repeaturl = common_local_url('repeat');
// FIXME: need to find a better way to pass this pattern in
$this->deleteurl = common_local_url('deletenotice',
array('notice' => '0000000000'));
@ -266,6 +267,24 @@ class RealtimePlugin extends Plugin
$profile = $notice->getProfile();
$arr['user']['profile_url'] = $profile->profileurl;
// Add needed repeat data
if (!empty($notice->repeat_of)) {
$original = Notice::staticGet('id', $notice->repeat_of);
if (!empty($original)) {
$arr['retweeted_status']['url'] = $original->bestUrl();
$arr['retweeted_status']['html'] = htmlspecialchars($original->rendered);
$arr['retweeted_status']['source'] = htmlspecialchars($original->source);
$originalProfile = $original->getProfile();
$arr['retweeted_status']['user']['profile_url'] = $originalProfile->profileurl;
if (!empty($original->reply_to)) {
$originalReply = Notice::staticGet('id', $original->reply_to);
$arr['retweeted_status']['in_reply_to_status_url'] = $originalReply->bestUrl();
}
}
$original = null;
}
return $arr;
}
@ -297,7 +316,7 @@ class RealtimePlugin extends Plugin
function _updateInitialize($timeline, $user_id)
{
return "RealtimeUpdate.init($user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->deleteurl\"); ";
return "RealtimeUpdate.init($user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->repeaturl\", \"$this->deleteurl\"); ";
}
function _connect()

View File

@ -32,6 +32,7 @@ RealtimeUpdate = {
_userid: 0,
_replyurl: '',
_favorurl: '',
_repeaturl: '',
_deleteurl: '',
_updatecounter: 0,
_maxnotices: 50,
@ -40,11 +41,12 @@ RealtimeUpdate = {
_paused:false,
_queuedNotices:[],
init: function(userid, replyurl, favorurl, deleteurl)
init: function(userid, replyurl, favorurl, repeaturl, deleteurl)
{
RealtimeUpdate._userid = userid;
RealtimeUpdate._replyurl = replyurl;
RealtimeUpdate._favorurl = favorurl;
RealtimeUpdate._repeaturl = repeaturl;
RealtimeUpdate._deleteurl = deleteurl;
RealtimeUpdate._documenttitle = document.title;
@ -95,6 +97,7 @@ RealtimeUpdate = {
SN.U.FormXHR($('#'+noticeItemID+' .form_favor'));
SN.U.NoticeReplyTo($('#'+noticeItemID));
SN.U.FormXHR($('#'+noticeItemID+' .form_repeat'));
SN.U.NoticeWithAttachment($('#'+noticeItemID));
},
@ -113,11 +116,24 @@ RealtimeUpdate = {
makeNoticeItem: function(data)
{
if (data.hasOwnProperty('retweeted_status')) {
original = data['retweeted_status'];
repeat = data;
data = original;
unique = repeat['id'];
responsible = repeat['user'];
} else {
original = null;
repeat = null;
unique = data['id'];
responsible = data['user'];
}
user = data['user'];
html = data['html'].replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"');
source = data['source'].replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"');
ni = "<li class=\"hentry notice\" id=\"notice-"+data['id']+"\">"+
ni = "<li class=\"hentry notice\" id=\"notice-"+unique+"\">"+
"<div class=\"entry-title\">"+
"<span class=\"vcard author\">"+
"<a href=\""+user['profile_url']+"\" class=\"url\">"+
@ -139,20 +155,31 @@ RealtimeUpdate = {
ni = ni+" <a class=\"response\" href=\""+data['in_reply_to_status_url']+"\">in context</a>";
}
ni = ni+"</div>"+
"<div class=\"notice-options\">";
if (repeat) {
ru = repeat['user'];
ni = ni + "<span class=\"repeat vcard\">Repeated by " +
"<a href=\"" + ru['profile_url'] + "\" class=\"url\">" +
"<span class=\"nickname\">"+ ru['screen_name'] + "</span></a></span>";
}
ni = ni+"</div>";
ni = ni + "<div class=\"notice-options\">";
if (RealtimeUpdate._userid != 0) {
var input = $("form#form_notice fieldset input#token");
var session_key = input.val();
ni = ni+RealtimeUpdate.makeFavoriteForm(data['id'], session_key);
ni = ni+RealtimeUpdate.makeReplyLink(data['id'], data['user']['screen_name']);
if (RealtimeUpdate._userid == data['user']['id']) {
if (RealtimeUpdate._userid == responsible['id']) {
ni = ni+RealtimeUpdate.makeDeleteLink(data['id']);
} else if (RealtimeUpdate._userid != user['id']) {
ni = ni+RealtimeUpdate.makeRepeatForm(data['id'], session_key);
}
}
ni = ni+"</div>"+
ni = ni+"</div>";
"</li>";
return ni;
},
@ -177,7 +204,22 @@ RealtimeUpdate = {
var rl;
rl = "<a class=\"notice_reply\" href=\""+RealtimeUpdate._replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span></a>";
return rl;
},
},
makeRepeatForm: function(id, session_key)
{
var rf;
rf = "<form id=\"repeat-"+id+"\" class=\"form_repeat\" method=\"post\" action=\""+RealtimeUpdate._repeaturl+"\">"+
"<fieldset>"+
"<legend>Favor this notice</legend>"+
"<input name=\"token-"+id+"\" type=\"hidden\" id=\"token-"+id+"\" value=\""+session_key+"\"/>"+
"<input name=\"notice\" type=\"hidden\" id=\"notice-n"+id+"\" value=\""+id+"\"/>"+
"<input type=\"submit\" id=\"repeat-submit-"+id+"\" name=\"repeat-submit-"+id+"\" class=\"submit\" value=\"Favor\" title=\"Repeat this notice\"/>"+
"</fieldset>"+
"</form>";
return rf;
},
makeDeleteLink: function(id)
{
@ -309,7 +351,7 @@ RealtimeUpdate = {
{
$('.notices .entry-title a, .notices .entry-content a').bind('click', function() {
window.open(this.href, '');
return false;
});

View File

@ -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';

View File

@ -179,7 +179,7 @@ function broadcast_oauth($notice, $flink) {
try {
$status = $client->statusesUpdate($statustxt);
} catch (OAuthClientException $e) {
return process_error($e, $flink);
return process_error($e, $flink, $notice);
}
if (empty($status)) {
@ -188,8 +188,11 @@ function broadcast_oauth($notice, $flink) {
// or the Twitter API might just be behaving flakey.
$errmsg = sprintf('Twitter bridge - No data returned by Twitter API when ' .
'trying to send update for %1$s (user id %2$s).',
$user->nickname, $user->id);
'trying to post notice %d for User %s (user id %d).',
$notice->id,
$user->nickname,
$user->id);
common_log(LOG_WARNING, $errmsg);
return false;
@ -197,8 +200,12 @@ function broadcast_oauth($notice, $flink) {
// Notice crossed the great divide
$msg = sprintf('Twitter bridge - posted notice %s to Twitter using OAuth.',
$notice->id);
$msg = sprintf('Twitter bridge - posted notice %d to Twitter using ' .
'OAuth for User %s (user id %d).',
$notice->id,
$user->nickname,
$user->id);
common_log(LOG_INFO, $msg);
return true;
@ -215,62 +222,69 @@ function broadcast_basicauth($notice, $flink)
try {
$status = $client->statusesUpdate($statustxt);
} catch (HTTP_Request2_Exception $e) {
return process_error($e, $flink);
} catch (BasicAuthException $e) {
return process_error($e, $flink, $notice);
}
if (empty($status)) {
$errmsg = sprintf('Twitter bridge - No data returned by Twitter API when ' .
'trying to send update for %1$s (user id %2$s).',
$user->nickname, $user->id);
'trying to post notice %d for %s (user id %d).',
$notice->id,
$user->nickname,
$user->id);
common_log(LOG_WARNING, $errmsg);
$errmsg = sprintf('No data returned by Twitter API when ' .
'trying to send update for %1$s (user id %2$s).',
$user->nickname, $user->id);
common_log(LOG_WARNING, $errmsg);
$errmsg = sprintf('No data returned by Twitter API when ' .
'trying to post notice %d for %s (user id %d).',
$notice->id,
$user->nickname,
$user->id);
common_log(LOG_WARNING, $errmsg);
return false;
}
$msg = sprintf('Twitter bridge - posted notice %s to Twitter using basic auth.',
$notice->id);
$msg = sprintf('Twitter bridge - posted notice %d to Twitter using ' .
'HTTP basic auth for User %s (user id %d).',
$notice->id,
$user->nickname,
$user->id);
common_log(LOG_INFO, $msg);
return true;
}
function process_error($e, $flink)
function process_error($e, $flink, $notice)
{
$user = $flink->getUser();
$errmsg = $e->getMessage();
$delivered = false;
$user = $flink->getUser();
$code = $e->getCode();
switch($errmsg) {
case 'The requested URL returned error: 401':
$logmsg = sprintf('Twiter bridge - User %1$s (user id: %2$s) has an invalid ' .
'Twitter screen_name/password combo or an invalid acesss token.',
$user->nickname, $user->id);
$delivered = true;
remove_twitter_link($flink);
break;
case 'The requested URL returned error: 403':
$logmsg = sprintf('Twitter bridge - User %1$s (user id: %2$s) has exceeded ' .
'his/her Twitter request limit.',
$user->nickname, $user->id);
break;
default:
$logmsg = sprintf('Twitter bridge - cURL error trying to send notice to Twitter ' .
'for user %1$s (user id: %2$s) - ' .
'code: %3$s message: %4$s.',
$user->nickname, $user->id,
$e->getCode(), $e->getMessage());
break;
}
$logmsg = sprintf('Twitter bridge - %d posting notice %d for ' .
'User %s (user id: %d): %s.',
$code,
$notice->id,
$user->nickname,
$user->id,
$e->getMessage());
common_log(LOG_WARNING, $logmsg);
return $delivered;
if ($code == 401) {
// Probably a revoked or otherwise bad access token - nuke!
remove_twitter_link($flink);
return true;
} else {
// For every other case, it's probably some flakiness so try
// sending the notice again later (requeue).
return false;
}
}
function format_status($notice)

View File

@ -31,6 +31,20 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* General Exception wrapper for HTTP basic auth errors
*
* @category Integration
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
*/
class BasicAuthException extends Exception
{
}
/**
* Class for talking to the Twitter API with HTTP Basic Auth.
*
@ -169,12 +183,13 @@ class TwitterBasicAuthClient
}
/**
* Make a HTTP request using cURL.
* Make an HTTP request
*
* @param string $url Where to make the request
* @param array $params post parameters
*
* @return mixed the request
* @throws BasicAuthException
*/
function httpRequest($url, $params = null, $auth = true)
{
@ -199,6 +214,12 @@ class TwitterBasicAuthClient
$response = $request->get($url);
}
$code = $response->getStatus();
if ($code < 200 || $code >= 400) {
throw new BasicAuthException($response->getBody(), $code);
}
return $response->getBody();
}

View File

@ -73,7 +73,7 @@ function read_input_line($prompt)
*/
function readline_emulation($prompt)
{
if(file_exists(trim(shell_exec('which bash')))) {
if(CONSOLE_INTERACTIVE && file_exists(trim(shell_exec('which bash')))) {
$encPrompt = escapeshellarg($prompt);
$command = "read -er -p $encPrompt && echo \"\$REPLY\"";
$encCommand = escapeshellarg($command);
@ -103,7 +103,9 @@ function readline_emulation($prompt)
if (feof(STDIN)) {
return false;
}
print $prompt;
if (CONSOLE_INTERACTIVE) {
print $prompt;
}
return fgets(STDIN);
}
@ -123,13 +125,16 @@ function console_help()
print "Type ctrl+D or enter 'exit' to exit.\n";
}
print "StatusNet interactive PHP console... type ctrl+D or enter 'exit' to exit.\n";
$prompt = common_config('site', 'name') . '> ';
if (CONSOLE_INTERACTIVE) {
print "StatusNet interactive PHP console... type ctrl+D or enter 'exit' to exit.\n";
$prompt = common_config('site', 'name') . '> ';
}
while (!feof(STDIN)) {
$line = read_input_line($prompt);
if ($line === false) {
print "\n";
if (CONSOLE_INTERACTIVE) {
print "\n";
}
break;
} elseif ($line !== '') {
try {
@ -154,5 +159,7 @@ while (!feof(STDIN)) {
print get_class($e) . ": " . $e->getMessage() . "\n";
}
}
print "\n";
if (CONSOLE_INTERACTIVE) {
print "\n";
}
}

77
scripts/useremail.php Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a 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/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'i:n:e:';
$longoptions = array('id=', 'nickname=', 'email=');
$helptext = <<<END_OF_USEREMAIL_HELP
useremail.php [options]
Queries a user's registered email address, or queries the users with a given registered email.
-i --id id of the user to query
-n --nickname nickname of the user to query
-e --email email address to query
END_OF_USEREMAIL_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
if (have_option('i', 'id')) {
$id = get_option_value('i', 'id');
$user = User::staticGet('id', $id);
if (empty($user)) {
print "Can't find user with ID $id\n";
exit(1);
}
} else if (have_option('n', 'nickname')) {
$nickname = get_option_value('n', 'nickname');
$user = User::staticGet('nickname', $nickname);
if (empty($user)) {
print "Can't find user with nickname '$nickname'\n";
exit(1);
}
}
if (!empty($user)) {
if (empty($user->email)) {
print "No email registered for user '$user->nickname'\n";
} else {
print "$user->email\n";
}
exit(0);
}
if (have_option('e', 'email')) {
$user = new User();
$user->email = get_option_value('e', 'email');
$user->find(false);
if (!$user->fetch()) {
print "No users with email $user->email\n";
exit(0);
}
do {
print "$user->id $user->nickname\n";
} while ($user->fetch());
} else {
print "You must provide either an ID, email, or a nickname.\n";
exit(1);
}

View File

@ -195,17 +195,6 @@ class XMPPDaemon extends Daemon
} else if ($this->is_otr($pl['body'])) {
$this->log(LOG_INFO, 'Ignoring OTR from ' . $from);
return;
} else if ($this->is_direct($pl['body'])) {
$this->log(LOG_INFO, 'Got a direct message ' . $from);
preg_match_all('/d[\ ]*([a-z0-9]{1,64})/', $pl['body'], $to);
$to = preg_replace('/^d([\ ])*/', '', $to[0][0]);
$body = preg_replace('/d[\ ]*('. $to .')[\ ]*/', '', $pl['body']);
$this->log(LOG_INFO, 'Direct message from '. $user->nickname . ' to ' . $to);
$this->add_direct($user, $body, $to, $from);
} else {
$this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
@ -284,15 +273,6 @@ class XMPPDaemon extends Daemon
}
}
function is_direct($txt)
{
if (strtolower(substr($txt, 0, 2))=='d ') {
return true;
} else {
return false;
}
}
function from_site($address, $msg)
{
$text = '['.common_config('site', 'name') . '] ' . $msg;

View File

@ -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 {
@ -982,10 +982,21 @@ font-size:1.025em;
display:inline-block;
}
.entry-content .repeat {
display:block;
}
.entry-content .repeat .photo {
float:none;
margin-right:1px;
position:relative;
top:4px;
left:0;
}
.notice-options {
position:relative;
font-size:0.95em;
width:90px;
width:125px;
float:right;
}
@ -994,17 +1005,20 @@ float:left;
}
.notice-options .notice_delete,
.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;
}
@ -1024,16 +1038,19 @@ 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;
padding:0;
}
.notice-options a {
.notice-options a,
.notice-options .repeated {
width:16px;
height:16px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

View File

@ -160,6 +160,7 @@ opacity:0;
.notice-options form.form_favor input.submit,
.notice-options form.form_disfavor input.submit,
.notice-options .notice_delete,
.notice-options form.form_repeat input.submit,
#new_group a,
.pagination .nav_prev a,
.pagination .nav_next a,
@ -172,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;
@ -334,6 +336,12 @@ background-position:0 -526px;
.notice-options .notice_delete {
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 {

View File

@ -160,6 +160,7 @@ opacity:0;
.notice-options form.form_favor input.submit,
.notice-options form.form_disfavor input.submit,
.notice-options .notice_delete,
.notice-options form.form_repeat input.submit,
#new_group a,
.pagination .nav_prev a,
.pagination .nav_next a,
@ -172,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;
@ -333,6 +335,12 @@ background-position:0 -526px;
.notice-options .notice_delete {
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 {