Merge commit 'origin/0.9.x' into 0.9.x

This commit is contained in:
Brenda Wallace 2009-12-12 22:47:12 +00:00
commit 9d3829df9d
85 changed files with 16140 additions and 9804 deletions

View File

@ -290,6 +290,18 @@ StartRegistrationTry: before validating and saving a new user
EndRegistrationTry: after saving a new user (note: no profile or user object!) EndRegistrationTry: after saving a new user (note: no profile or user object!)
- $action: action object being shown - $action: action object being shown
StartAvatarFormData: before displaying avatar form
- $action: action object being shown
EndAvatarFormData: after displaying avatar form
- $action: action object being shown
StartAvatarSaveForm: before saving the avatar
- $action: action object being shown
EndAvatarSaveForm: after saving the avatar
- $action: action object being shown
StartNewQueueManager: before trying to start a new queue manager; good for plugins implementing new queue manager classes StartNewQueueManager: before trying to start a new queue manager; good for plugins implementing new queue manager classes
- $qm: empty queue manager to set - $qm: empty queue manager to set

View File

@ -144,7 +144,7 @@ class AllAction extends ProfileAction
function showContent() function showContent()
{ {
$nl = new InboxNoticeList($this->notice, $this->user, $this); $nl = new NoticeList($this->notice, $this);
$cnt = $nl->show(); $cnt = $nl->show();
@ -168,87 +168,3 @@ class AllAction extends ProfileAction
} }
} }
} }
class InboxNoticeList extends NoticeList
{
var $owner = null;
function __construct($notice, $owner, $out=null)
{
parent::__construct($notice, $out);
$this->owner = $owner;
}
function newListItem($notice)
{
return new InboxNoticeListItem($notice, $this->owner, $this->out);
}
}
class InboxNoticeListItem extends NoticeListItem
{
var $owner = null;
var $ib = null;
function __construct($notice, $owner, $out=null)
{
parent::__construct($notice, $out);
$this->owner = $owner;
$this->ib = Notice_inbox::pkeyGet(array('user_id' => $owner->id,
'notice_id' => $notice->id));
}
function showAuthor()
{
parent::showAuthor();
if ($this->ib->source == NOTICE_INBOX_SOURCE_FORWARD) {
$this->out->element('span', 'forward', _('Fwd'));
}
}
function showEnd()
{
if ($this->ib->source == NOTICE_INBOX_SOURCE_FORWARD) {
$forward = new Forward();
// FIXME: scary join!
$forward->query('SELECT profile_id '.
'FROM forward JOIN subscription ON forward.profile_id = subscription.subscribed '.
'WHERE subscription.subscriber = ' . $this->owner->id . ' '.
'AND forward.notice_id = ' . $this->notice->id . ' '.
'ORDER BY forward.created ');
$n = 0;
$firstForwarder = null;
while ($forward->fetch()) {
if (empty($firstForwarder)) {
$firstForwarder = Profile::staticGet('id', $forward->profile_id);
}
$n++;
}
$forward->free();
unset($forward);
$this->out->elementStart('span', 'forwards');
$link = XMLStringer::estring('a', array('href' => $firstForwarder->profileurl),
$firstForwarder->nickname);
if ($n == 1) {
$this->out->raw(sprintf(_('Forwarded by %s'), $link));
} else {
// XXX: use that cool ngettext thing
$this->out->raw(sprintf(_('Forwarded by %s and %d other(s)'), $link, $n - 1));
}
$this->out->elementEnd('span');
}
parent::showEnd();
}
}

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( $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8');
$this->user->id,
html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'), $options = array('reply_to' => $reply_to);
$this->source,
1, if (!empty($location)) {
$reply_to, $options['lat'] = $location->lat;
null, $options['lon'] = $location->lon;
null, $options['location_id'] = $location->location_id;
empty($location) ? null : $location->lat, $options['location_ns'] = $location->location_ns;
empty($location) ? null : $location->lon, }
empty($location) ? null : $location->location_id,
empty($location) ? null : $location->location_ns $this->notice =
); Notice::saveNew($this->user->id,
$content,
$this->source,
$options);
if (isset($upload)) { if (isset($upload)) {
$upload->attachToNotice($this->notice); $upload->attachToNotice($this->notice);

View File

@ -101,6 +101,7 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf( $title = sprintf(
@ -121,20 +122,21 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
$profile->getBestName(), $profile->getBestName(),
$this->user->nickname $this->user->nickname
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
ltrim($_SERVER['QUERY_STRING'], 'p='); ltrim($_SERVER['QUERY_STRING'], 'p=');
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $subtitle, $this->notices, $title, $id, $link, $subtitle,
null, $selfuri null, $selfuri, $logo
); );
break; break;
case 'json': case 'json':

View File

@ -110,6 +110,7 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf(_("%s and friends"), $this->user->nickname); $title = sprintf(_("%s and friends"), $this->user->nickname);
$taguribase = common_config('integration', 'taguri'); $taguribase = common_config('integration', 'taguri');
@ -121,13 +122,14 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
_('Updates from %1$s and friends on %2$s!'), _('Updates from %1$s and friends on %2$s!'),
$this->user->nickname, $sitename $this->user->nickname, $sitename
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
@ -144,7 +146,7 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $this->notices, $title, $id, $link,
$subtitle, null, $selfuri $subtitle, null, $selfuri, $logo
); );
break; break;
case 'json': case 'json':

View File

@ -105,6 +105,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
function showTimeline() function showTimeline()
{ {
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$avatar = $this->group->homepage_logo;
$title = sprintf(_("%s timeline"), $this->group->nickname); $title = sprintf(_("%s timeline"), $this->group->nickname);
$taguribase = common_config('integration', 'taguri'); $taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:GroupTimeline:" . $this->group->id; $id = "tag:$taguribase:GroupTimeline:" . $this->group->id;
@ -117,13 +118,14 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
$this->group->nickname, $this->group->nickname,
$sitename $sitename
); );
$logo = ($avatar) ? $avatar : User_group::defaultLogo(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
@ -136,7 +138,8 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
$link, $link,
$subtitle, $subtitle,
null, null,
$selfuri $selfuri,
$logo
); );
break; break;
case 'json': case 'json':

View File

@ -110,6 +110,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf( $title = sprintf(
@ -126,20 +127,21 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
_('%1$s updates that reply to updates from %2$s / %3$s.'), _('%1$s updates that reply to updates from %2$s / %3$s.'),
$sitename, $this->user->nickname, $profile->getBestName() $sitename, $this->user->nickname, $profile->getBestName()
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
ltrim($_SERVER['QUERY_STRING'], 'p='); ltrim($_SERVER['QUERY_STRING'], 'p=');
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $subtitle, $this->notices, $title, $id, $link, $subtitle,
null, $selfuri null, $selfuri, $logo
); );
break; break;
case 'json': case 'json':

View File

@ -103,6 +103,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
function showTimeline() function showTimeline()
{ {
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$sitelogo = (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png');
$title = sprintf(_("%s public timeline"), $sitename); $title = sprintf(_("%s public timeline"), $sitename);
$taguribase = common_config('integration', 'taguri'); $taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:PublicTimeline"; $id = "tag:$taguribase:PublicTimeline";
@ -114,13 +115,13 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $this->notices, $title, $id, $link,
$subtitle, null, $selfuri $subtitle, null, $selfuri, $sitelogo
); );
break; break;
case 'json': 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() function showTimeline()
{ {
$sitename = common_config('site', 'name'); $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); $title = sprintf(_("Notices tagged with %s"), $this->tag);
$link = common_local_url( $link = common_local_url(
'tag', 'tag',
@ -116,7 +117,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
$this->showXmlTimeline($this->notices); $this->showXmlTimeline($this->notices);
break; break;
case 'rss': case 'rss':
$this->showRssTimeline($this->notices, $title, $link, $subtitle); $this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo);
break; break;
case 'atom': case 'atom':
$selfuri = common_root_url() . $selfuri = common_root_url() .
@ -129,7 +130,8 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
$link, $link,
$subtitle, $subtitle,
null, null,
$selfuri $selfuri,
$sitelogo
); );
break; break;
case 'json': case 'json':

View File

@ -112,6 +112,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
$title = sprintf(_("%s timeline"), $this->user->nickname); $title = sprintf(_("%s timeline"), $this->user->nickname);
@ -125,6 +126,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
_('Updates from %1$s on %2$s!'), _('Updates from %1$s on %2$s!'),
$this->user->nickname, $sitename $this->user->nickname, $sitename
); );
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
// FriendFeed's SUP protocol // FriendFeed's SUP protocol
// Also added RSS and Atom feeds // Also added RSS and Atom feeds
@ -139,7 +141,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
case 'rss': case 'rss':
$this->showRssTimeline( $this->showRssTimeline(
$this->notices, $title, $link, $this->notices, $title, $link,
$subtitle, $suplink $subtitle, $suplink, $logo
); );
break; break;
case 'atom': case 'atom':
@ -153,7 +155,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
} }
$this->showAtomTimeline( $this->showAtomTimeline(
$this->notices, $title, $id, $link, $this->notices, $title, $id, $link,
$subtitle, $suplink, $selfuri $subtitle, $suplink, $selfuri, $logo
); );
break; break;
case 'json': case 'json':

View File

@ -118,53 +118,56 @@ class AvatarsettingsAction extends AccountSettingsAction
$this->elementStart('fieldset'); $this->elementStart('fieldset');
$this->element('legend', null, _('Avatar settings')); $this->element('legend', null, _('Avatar settings'));
$this->hidden('token', common_session_token()); $this->hidden('token', common_session_token());
if (Event::handle('StartAvatarFormData', array($this))) {
$this->elementStart('ul', 'form_data');
if ($original) {
$this->elementStart('li', array('id' => 'avatar_original',
'class' => 'avatar_view'));
$this->element('h2', null, _("Original"));
$this->elementStart('div', array('id'=>'avatar_original_view'));
$this->element('img', array('src' => $original->url,
'width' => $original->width,
'height' => $original->height,
'alt' => $user->nickname));
$this->elementEnd('div');
$this->elementEnd('li');
}
$this->elementStart('ul', 'form_data'); $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
if ($original) {
$this->elementStart('li', array('id' => 'avatar_original', if ($avatar) {
'class' => 'avatar_view')); $this->elementStart('li', array('id' => 'avatar_preview',
$this->element('h2', null, _("Original")); 'class' => 'avatar_view'));
$this->elementStart('div', array('id'=>'avatar_original_view')); $this->element('h2', null, _("Preview"));
$this->element('img', array('src' => $original->url, $this->elementStart('div', array('id'=>'avatar_preview_view'));
'width' => $original->width, $this->element('img', array('src' => $original->url,
'height' => $original->height, 'width' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname)); 'height' => AVATAR_PROFILE_SIZE,
$this->elementEnd('div'); 'alt' => $user->nickname));
$this->elementEnd('div');
$this->submit('delete', _('Delete'));
$this->elementEnd('li');
}
$this->elementStart('li', array ('id' => 'settings_attach'));
$this->element('input', array('name' => 'avatarfile',
'type' => 'file',
'id' => 'avatarfile'));
$this->element('input', array('name' => 'MAX_FILE_SIZE',
'type' => 'hidden',
'id' => 'MAX_FILE_SIZE',
'value' => ImageFile::maxFileSizeInt()));
$this->elementEnd('li'); $this->elementEnd('li');
} $this->elementEnd('ul');
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); $this->elementStart('ul', 'form_actions');
$this->elementStart('li');
if ($avatar) { $this->submit('upload', _('Upload'));
$this->elementStart('li', array('id' => 'avatar_preview',
'class' => 'avatar_view'));
$this->element('h2', null, _("Preview"));
$this->elementStart('div', array('id'=>'avatar_preview_view'));
$this->element('img', array('src' => $original->url,
'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname));
$this->elementEnd('div');
$this->submit('delete', _('Delete'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementEnd('ul');
} }
Event::handle('EndAvatarFormData', array($this));
$this->elementStart('li', array ('id' => 'settings_attach'));
$this->element('input', array('name' => 'avatarfile',
'type' => 'file',
'id' => 'avatarfile'));
$this->element('input', array('name' => 'MAX_FILE_SIZE',
'type' => 'hidden',
'id' => 'MAX_FILE_SIZE',
'value' => ImageFile::maxFileSizeInt()));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementStart('ul', 'form_actions');
$this->elementStart('li');
$this->submit('upload', _('Upload'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
$this->elementEnd('form'); $this->elementEnd('form');
@ -266,15 +269,18 @@ class AvatarsettingsAction extends AccountSettingsAction
'Try again, please.')); 'Try again, please.'));
return; return;
} }
if ($this->arg('upload')) { if (Event::handle('StartAvatarSaveForm', array($this))) {
$this->uploadAvatar(); if ($this->arg('upload')) {
} else if ($this->arg('crop')) { $this->uploadAvatar();
$this->cropAvatar(); } else if ($this->arg('crop')) {
} else if ($this->arg('delete')) { $this->cropAvatar();
$this->deleteAvatar(); } else if ($this->arg('delete')) {
} else { $this->deleteAvatar();
$this->showForm(_('Unexpected form submission.')); } else {
$this->showForm(_('Unexpected form submission.'));
}
Event::handle('EndAvatarSaveForm', array($this));
} }
} }

View File

@ -77,12 +77,13 @@ class LoginAction extends Action
parent::handle($args); parent::handle($args);
$disabled = common_config('logincommand','disabled'); $disabled = common_config('logincommand','disabled');
$disabled = isset($disabled) && $disabled;
if (common_is_real_login()) { if (common_is_real_login()) {
$this->clientError(_('Already logged in.')); $this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') { } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->checkLogin(); $this->checkLogin();
} else if (!isset($disabled) && isset($args['user_id']) && isset($args['token'])){ } else if (!$disabled && isset($args['user_id']) && isset($args['token'])){
$this->checkLogin($args['user_id'],$args['token']); $this->checkLogin($args['user_id'],$args['token']);
} else { } else {
common_ensure_session(); common_ensure_session();

View File

@ -187,10 +187,12 @@ class NewnoticeAction extends Action
} }
} }
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, $notice = Notice::saveNew($user->id, $content_shortened, 'web',
($replyto == 'false') ? null : $replyto, array('reply_to' => ($replyto == 'false') ? null : $replyto,
null, null, 'lat' => $lat,
$lat, $lon, $location_id, $location_ns); 'lon' => $lon,
'location_id' => $location_id,
'location_ns' => $location_ns));
if (isset($upload)) { if (isset($upload)) {
$upload->attachToNotice($notice); $upload->attachToNotice($notice);

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* Forward action. * Repeat action.
* *
* PHP version 5 * PHP version 5
* *
@ -33,7 +33,7 @@ if (!defined('STATUSNET')) {
} }
/** /**
* Forward action * Repeat action
* *
* @category Action * @category Action
* @package StatusNet * @package StatusNet
@ -42,7 +42,7 @@ if (!defined('STATUSNET')) {
* @link http://status.net/ * @link http://status.net/
*/ */
class ForwardAction extends Action class RepeatAction extends Action
{ {
var $user = null; var $user = null;
var $notice = null; var $notice = null;
@ -54,7 +54,7 @@ class ForwardAction extends Action
$this->user = common_current_user(); $this->user = common_current_user();
if (empty($this->user)) { if (empty($this->user)) {
$this->clientError(_("Only logged-in users can forward notices.")); $this->clientError(_("Only logged-in users can repeat notices."));
return false; return false;
} }
@ -73,7 +73,7 @@ class ForwardAction extends Action
} }
if ($this->user->id == $this->notice->profile_id) { if ($this->user->id == $this->notice->profile_id) {
$this->clientError(_("You can't forward your own notice.")); $this->clientError(_("You can't repeat your own notice."));
return false; return false;
} }
@ -86,8 +86,8 @@ class ForwardAction extends Action
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
if ($profile->hasForwarded($id)) { if ($profile->hasRepeated($id)) {
$this->clientError(_("You already forwarded that notice.")); $this->clientError(_("You already repeated that notice."));
return false; return false;
} }
@ -104,15 +104,15 @@ class ForwardAction extends Action
function handle($args) function handle($args)
{ {
$forward = Forward::saveNew($this->user->id, $this->notice->id); $repeat = $this->notice->repeat($this->user->id, 'web');
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8'); $this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head'); $this->elementStart('head');
$this->element('title', null, _('Forwarded')); $this->element('title', null, _('Repeated'));
$this->elementEnd('head'); $this->elementEnd('head');
$this->elementStart('body'); $this->elementStart('body');
$this->element('p', array('id' => 'forward_response'), _('Forwarded!')); $this->element('p', array('id' => 'repeat_response'), _('Repeated!'));
$this->elementEnd('body'); $this->elementEnd('body');
$this->elementEnd('html'); $this->elementEnd('html');
} else { } else {

View File

@ -269,4 +269,50 @@ class ProfileNoticeListItem extends NoticeListItem
{ {
return; 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

@ -1,126 +0,0 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* Table Definition for location_namespace
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Forward extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'forward'; // table name
public $profile_id; // int(4) primary_key not_null
public $notice_id; // int(4) primary_key not_null
public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00
/* Static get */
function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Forward',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function &pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Forward', $kv);
}
static function saveNew($profile_id, $notice_id)
{
$forward = new Forward();
$forward->profile_id = $profile_id;
$forward->notice_id = $notice_id;
$forward->created = common_sql_now();
$forward->query('BEGIN');
if (!$forward->insert()) {
throw new ServerException(_("Couldn't insert forward."));
}
$ni = $forward->addToInboxes();
$forward->query('COMMIT');
$forward->blowCache($ni);
return $forward;
}
function addToInboxes()
{
$inbox = new Notice_inbox();
$user = new User();
$user->query('SELECT user.* FROM user JOIN subscription ON user.id = subscription.subscriber '.
'WHERE subscription.subscribed = '.$this->profile_id);
$ni = array();
$notice = Notice::staticGet('id', $this->notice_id);
$author = Profile::staticGet('id', $notice->profile_id);
while ($user->fetch()) {
$inbox = Notice_inbox::pkeyGet(array('user_id' => $user->id,
'notice_id' => $this->notice_id));
if (empty($inbox)) {
if (!$user->hasBlocked($author)) {
$ni[$user->id] = NOTICE_INBOX_SOURCE_FORWARD;
}
} else {
$inbox->free();
}
}
$user->free();
$author->free();
unset($user);
unset($author);
Notice_inbox::bulkInsert($this->notice_id, $this->created, $ni);
return $ni;
}
function blowCache($ni)
{
$cache = common_memcache();
if (!empty($cache)) {
foreach ($ni as $id => $source) {
$cache->delete(common_cache_key('notice_inbox:by_user:'.$id));
$cache->delete(common_cache_key('notice_inbox:by_user_own:'.$id));
$cache->delete(common_cache_key('notice_inbox:by_user:'.$id.';last'));
$cache->delete(common_cache_key('notice_inbox:by_user_own:'.$id.';last'));
}
}
}
}

View File

@ -39,4 +39,17 @@ class Login_token extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
/*
DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function.
In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but
DB_DataObject's sequenceKey() will incorrectly think it is. Then, since the sequenceKey() is a numeric
type, but is not set to autoincrement in the database, DB_DataObject will create a _seq table and
manage the sequence itself. This is not the correct behavior for the user_id in this class.
So we override that incorrect behavior, and simply say there is no sequence key.
*/
function sequenceKey()
{
return array(false,false);
}
} }

View File

@ -55,13 +55,13 @@ class Notice extends Memcached_DataObject
public $__table = 'notice'; // table name public $__table = 'notice'; // table name
public $id; // int(4) primary_key not_null 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 $uri; // varchar(255) unique_key
public $content; // text() public $content; // text
public $rendered; // text() public $rendered; // text
public $url; // varchar(255) public $url; // varchar(255)
public $created; // datetime() not_null public $created; // datetime multiple_key not_null default_0000-00-00%2000%3A00%3A00
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
public $reply_to; // int(4) public $reply_to; // int(4)
public $is_local; // tinyint(1) public $is_local; // tinyint(1)
public $source; // varchar(32) public $source; // varchar(32)
@ -70,9 +70,11 @@ class Notice extends Memcached_DataObject
public $lon; // decimal(10,7) public $lon; // decimal(10,7)
public $location_id; // int(4) public $location_id; // int(4)
public $location_ns; // int(4) public $location_ns; // int(4)
public $repeat_of; // int(4)
/* Static get */ /* Static get */
function staticGet($k,$v=NULL) { function staticGet($k,$v=NULL)
{
return Memcached_DataObject::staticGet('Notice',$k,$v); 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 //Null any notices that are replies to this notice
$this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id)); $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', $related = array('Reply',
'Fave', 'Fave',
'Notice_tag', 'Notice_tag',
@ -167,9 +175,18 @@ class Notice extends Memcached_DataObject
} }
} }
static function saveNew($profile_id, $content, $source=null, static function saveNew($profile_id, $content, $source, $options=null) {
$is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null,
$lat=null, $lon=null, $location_id=null, $location_ns=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); $profile = Profile::staticGet($profile_id);
@ -225,7 +242,14 @@ class Notice extends Memcached_DataObject
$notice->source = $source; $notice->source = $source;
$notice->uri = $uri; $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)) { if (!empty($notice->reply_to)) {
$reply = Notice::staticGet('id', $notice->reply_to); $reply = Notice::staticGet('id', $notice->reply_to);
@ -423,10 +447,60 @@ class Notice extends Memcached_DataObject
$this->blowTagCache($blowLast); $this->blowTagCache($blowLast);
$this->blowGroupCache($blowLast); $this->blowGroupCache($blowLast);
$this->blowConversationCache($blowLast); $this->blowConversationCache($blowLast);
$this->blowRepeatCache();
$profile = Profile::staticGet($this->profile_id); $profile = Profile::staticGet($this->profile_id);
$profile->blowNoticeCount(); $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) function blowConversationCache($blowLast=false)
{ {
$cache = common_memcache(); $cache = common_memcache();
@ -581,193 +655,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) function getStreamByIds($ids)
{ {
$cache = common_memcache(); $cache = common_memcache();
@ -788,10 +675,24 @@ class Notice extends Memcached_DataObject
return $notice; return $notice;
} }
$notice->whereAdd('id in (' . implode(', ', $ids) . ')'); $notice->whereAdd('id in (' . implode(', ', $ids) . ')');
$notice->orderBy('id DESC');
$notice->find(); $notice->find();
return $notice;
$temp = array();
while ($notice->fetch()) {
$temp[$notice->id] = clone($notice);
}
$wrapped = array();
foreach ($ids as $id) {
if (array_key_exists($id, $temp)) {
$wrapped[] = $temp[$id];
}
}
return new ArrayWrapper($wrapped);
} }
} }
@ -1409,4 +1310,72 @@ class Notice extends Memcached_DataObject
return $location; 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

@ -717,11 +717,14 @@ class Profile extends Memcached_DataObject
return $result; return $result;
} }
function hasForwarded($notice_id) function hasRepeated($notice_id)
{ {
$forward = Forward::pkeyGet(array('profile_id' => $this->id, // XXX: not really a pkey, but should work
'notice_id' => $notice_id));
return (!empty($forward)); $notice = Memcached_DataObject::pkeyGet('Notice',
array('profile_id' => $this->id,
'repeat_of' => $notice_id));
return !empty($notice);
} }
} }

View File

@ -741,4 +741,163 @@ class User extends Memcached_DataObject
$profile = $this->getProfile(); $profile = $this->getProfile();
return $profile->isSilenced(); 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

@ -196,15 +196,6 @@ id = K
service = K service = K
uri = U uri = U
[forward]
profile_id = 129
notice_id = 129
created = 142
[forward__keys]
profile_id = K
notice_id = K
[group_alias] [group_alias]
alias = 130 alias = 130
group_id = 129 group_id = 129
@ -316,6 +307,7 @@ lat = 1
lon = 1 lon = 1
location_id = 1 location_id = 1
location_ns = 1 location_ns = 1
repeat_of = 1
[notice__keys] [notice__keys]
id = N id = N

View File

@ -4,8 +4,10 @@ alter table notice
add column lon decimal(10,7) comment 'longitude', add column lon decimal(10,7) comment 'longitude',
add column location_id integer comment 'location id if possible', add column location_id integer comment 'location id if possible',
add column location_ns integer comment 'namespace for location', 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, 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 alter table message
modify column content text comment 'message content'; modify column content text comment 'message content';
@ -82,15 +84,3 @@ create table login_token (
constraint primary key (user_id) constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table forward (
profile_id integer not null comment 'profile who forwarded the notice' references profile (id),
notice_id integer not null comment 'notice they forwarded' references notice (id),
created datetime not null comment 'date this record was created',
constraint primary key (profile_id, notice_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -39,6 +39,16 @@ create table profile_role (
); );
create table location_namespace (
id integer /*comment 'identity for this namespace'*/,
description text /* comment 'description of the namespace'*/ ,
created integer not null /*comment 'date the record was created*/ ,
/* modified timestamp comment 'date this record was modified',*/
primary key (id)
);
create table login_token ( create table login_token (
user_id integer not null /* comment 'user owning this token'*/ references "user" (id), user_id integer not null /* comment 'user owning this token'*/ references "user" (id),
token char(32) not null /* comment 'token useable for logging in'*/, token char(32) not null /* comment 'token useable for logging in'*/,
@ -64,8 +74,10 @@ 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 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_id integer /* comment 'location id if possible'*/ ;
ALTER TABLE notice ADD COLUMN location_ns integer /* comment 'namespace for location'*/; 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 lat decimal(10,7) /*comment 'latitude'*/ ;
ALTER TABLE profile ADD COLUMN lon decimal(10,7) /*comment 'longitude'*/; ALTER TABLE profile ADD COLUMN lon decimal(10,7) /*comment 'longitude'*/;
ALTER TABLE profile ADD COLUMN location_id integer /* comment 'location id if possible'*/; ALTER TABLE profile ADD COLUMN location_id integer /* comment 'location id if possible'*/;
ALTER TABLE profile ADD COLUMN location_ns integer /* comment 'namespace for location'*/; ALTER TABLE profile ADD COLUMN location_ns integer /* comment 'namespace for location'*/;

View File

@ -129,11 +129,13 @@ create table notice (
lon decimal(10,7) comment 'longitude', lon decimal(10,7) comment 'longitude',
location_id integer comment 'location id if possible', 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),
index notice_profile_id_idx (profile_id,created,id), index notice_profile_id_idx (profile_id,created,id),
index notice_conversation_idx (conversation), index notice_conversation_idx (conversation),
index notice_created_idx (created), index notice_created_idx (created),
index notice_replyto_idx (reply_to), index notice_replyto_idx (reply_to),
index notice_repeatof_idx (repeat_of),
FULLTEXT(content) FULLTEXT(content)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; ) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
@ -585,14 +587,3 @@ create table login_token (
constraint primary key (user_id) constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table forward (
profile_id integer not null comment 'profile who forwarded the notice' references profile (id),
notice_id integer not null comment 'notice they forwarded' references notice (id),
created datetime not null comment 'date this record was created',
constraint primary key (profile_id, notice_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -64,7 +64,7 @@ create table "user" (
emailnotifyfav integer default 1 /* comment 'Notify by email of favorites' */, emailnotifyfav integer default 1 /* comment 'Notify by email of favorites' */,
emailnotifynudge integer default 1 /* comment 'Notify by email of nudges' */, emailnotifynudge integer default 1 /* comment 'Notify by email of nudges' */,
emailnotifymsg integer default 1 /* comment 'Notify by email of direct messages' */, emailnotifymsg integer default 1 /* comment 'Notify by email of direct messages' */,
emailnotifyattn integer default 1 /* command 'Notify by email of @-replies' */, emailnotifyattn integer default 1 /* command 'Notify by email of @-replies' */,
emailmicroid integer default 1 /* comment 'whether to publish email microid' */, emailmicroid integer default 1 /* comment 'whether to publish email microid' */,
language varchar(50) /* comment 'preferred language' */, language varchar(50) /* comment 'preferred language' */,
timezone varchar(50) /* comment 'timezone' */, timezone varchar(50) /* comment 'timezone' */,
@ -82,7 +82,7 @@ create table "user" (
uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */, uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */,
autosubscribe integer default 0 /* comment 'automatically subscribe to users who subscribe to us' */, autosubscribe integer default 0 /* comment 'automatically subscribe to users who subscribe to us' */,
urlshorteningservice varchar(50) default 'ur1.ca' /* comment 'service to use for auto-shortening URLs' */, urlshorteningservice varchar(50) default 'ur1.ca' /* comment 'service to use for auto-shortening URLs' */,
inboxed integer default 0 /* comment 'has an inbox been created for this user?' */, inboxed integer default 0 /* comment 'has an inbox been created for this user?' */,
design_id integer /* comment 'id of a design' */references design(id), design_id integer /* comment 'id of a design' */references design(id),
viewdesigns integer default 1 /* comment 'whether to view user-provided designs'*/, viewdesigns integer default 1 /* comment 'whether to view user-provided designs'*/,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
@ -135,7 +135,9 @@ create table notice (
lat decimal(10,7) /* comment 'latitude'*/ , lat decimal(10,7) /* comment 'latitude'*/ ,
lon decimal(10,7) /* comment 'longitude'*/ , lon decimal(10,7) /* comment 'longitude'*/ ,
location_id integer /* comment 'location id if possible'*/ , 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) */ /* FULLTEXT(content) */
); );
@ -298,7 +300,7 @@ create table foreign_user (
nickname varchar(255) /* comment 'nickname on foreign service' */, nickname varchar(255) /* comment 'nickname on foreign service' */,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */, modified timestamp /* comment 'date this record was modified' */,
primary key (id, service) primary key (id, service)
); );
@ -308,7 +310,7 @@ create table foreign_link (
service int not null /* comment 'foreign key to service' */ references foreign_service (id), service int not null /* comment 'foreign key to service' */ references foreign_service (id),
credentials varchar(255) /* comment 'authc credentials, typically a password' */, credentials varchar(255) /* comment 'authc credentials, typically a password' */,
noticesync int not null default 1 /* comment 'notice synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies' */, noticesync int not null default 1 /* comment 'notice synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies' */,
friendsync int not null default 2 /* comment 'friend synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming */, friendsync int not null default 2 /* comment 'friend synchronisation, bit 1 = sync outgoing, bit 2 = sync incoming */,
profilesync int not null default 1 /* comment 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming' */, profilesync int not null default 1 /* comment 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming' */,
last_noticesync timestamp default null /* comment 'last time notices were imported' */, last_noticesync timestamp default null /* comment 'last time notices were imported' */,
last_friendsync timestamp default null /* comment 'last time friends were imported' */, last_friendsync timestamp default null /* comment 'last time friends were imported' */,
@ -324,7 +326,7 @@ create table foreign_subscription (
subscriber int not null /* comment 'subscriber on foreign service' */ , subscriber int not null /* comment 'subscriber on foreign service' */ ,
subscribed int not null /* comment 'subscribed user' */ , subscribed int not null /* comment 'subscribed user' */ ,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
primary key (service, subscriber, subscribed) primary key (service, subscriber, subscribed)
); );
create index foreign_subscription_subscriber_idx on foreign_subscription using btree(subscriber); create index foreign_subscription_subscriber_idx on foreign_subscription using btree(subscriber);
@ -354,7 +356,7 @@ create table message (
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */, modified timestamp /* comment 'date this record was modified' */,
source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */ source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */
); );
create index message_from_idx on message using btree(from_profile); create index message_from_idx on message using btree(from_profile);
create index message_to_idx on message using btree(to_profile); create index message_to_idx on message using btree(to_profile);
@ -409,7 +411,6 @@ create table user_group (
mini_logo varchar(255) /* comment 'mini logo' */, mini_logo varchar(255) /* comment 'mini logo' */,
design_id integer /*comment 'id of a design' */ references design(id), design_id integer /*comment 'id of a design' */ references design(id),
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */ modified timestamp /* comment 'date this record was modified' */
@ -447,16 +448,15 @@ create table group_inbox (
); );
create index group_inbox_created_idx on group_inbox using btree(created); create index group_inbox_created_idx on group_inbox using btree(created);
/*attachments and URLs stuff */ /*attachments and URLs stuff */
create sequence file_seq; create sequence file_seq;
create table file ( create table file (
id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */, id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */,
url varchar(255) unique, url varchar(255) unique,
mimetype varchar(50), mimetype varchar(50),
size integer, size integer,
title varchar(255), title varchar(255),
date integer, date integer,
protected integer, protected integer,
filename text /* comment 'if a local file, name of the file' */, filename text /* comment 'if a local file, name of the file' */,
modified timestamp default CURRENT_TIMESTAMP /* comment 'date this record was modified'*/ modified timestamp default CURRENT_TIMESTAMP /* comment 'date this record was modified'*/
@ -467,38 +467,38 @@ create table file_oembed (
file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */, file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
version varchar(20), version varchar(20),
type varchar(20), type varchar(20),
mimetype varchar(50), mimetype varchar(50),
provider varchar(50), provider varchar(50),
provider_url varchar(255), provider_url varchar(255),
width integer, width integer,
height integer, height integer,
html text, html text,
title varchar(255), title varchar(255),
author_name varchar(50), author_name varchar(50),
author_url varchar(255), author_url varchar(255),
url varchar(255) url varchar(255)
); );
create sequence file_redirection_seq; create sequence file_redirection_seq;
create table file_redirection ( create table file_redirection (
url varchar(255) primary key, url varchar(255) primary key,
file_id bigint, file_id bigint,
redirections integer, redirections integer,
httpcode integer httpcode integer
); );
create sequence file_thumbnail_seq; create sequence file_thumbnail_seq;
create table file_thumbnail ( create table file_thumbnail (
file_id bigint primary key, file_id bigint primary key,
url varchar(255) unique, url varchar(255) unique,
width integer, width integer,
height integer height integer
); );
create sequence file_to_post_seq; create sequence file_to_post_seq;
create table file_to_post ( create table file_to_post (
file_id bigint, file_id bigint,
post_id bigint, post_id bigint,
primary key (file_id, post_id) primary key (file_id, post_id)
); );
@ -527,7 +527,7 @@ create table session (
id varchar(32) primary key /* comment 'session ID'*/, id varchar(32) primary key /* comment 'session ID'*/,
session_data text /* comment 'session data'*/, session_data text /* comment 'session data'*/,
created timestamp not null DEFAULT CURRENT_TIMESTAMP /* comment 'date this record was created'*/, created timestamp not null DEFAULT CURRENT_TIMESTAMP /* comment 'date this record was created'*/,
modified integer DEFAULT extract(epoch from CURRENT_TIMESTAMP) /* comment 'date this record was modified'*/ modified integer DEFAULT extract(epoch from CURRENT_TIMESTAMP) /* comment 'date this record was modified'*/
); );
create index session_modified_idx on session (modified); create index session_modified_idx on session (modified);
@ -543,7 +543,6 @@ create table deleted_notice (
CREATE index deleted_notice_profile_id_idx on deleted_notice (profile_id); CREATE index deleted_notice_profile_id_idx on deleted_notice (profile_id);
/* Textsearch stuff */ /* Textsearch stuff */
create index textsearch_idx on profile using gist(textsearch); create index textsearch_idx on profile using gist(textsearch);
@ -551,7 +550,6 @@ create index noticecontent_idx on notice using gist(to_tsvector('english',conten
create trigger textsearchupdate before insert or update on profile for each row create trigger textsearchupdate before insert or update on profile for each row
execute procedure tsvector_update_trigger(textsearch, 'pg_catalog.english', nickname, fullname, location, bio, homepage); execute procedure tsvector_update_trigger(textsearch, 'pg_catalog.english', nickname, fullname, location, bio, homepage);
create table config ( create table config (
section varchar(32) /* comment 'configuration section'*/, section varchar(32) /* comment 'configuration section'*/,
@ -572,6 +570,16 @@ create table profile_role (
); );
create table location_namespace (
id integer /*comment 'identity for this namespace'*/,
description text /* comment 'description of the namespace'*/ ,
created integer not null /*comment 'date the record was created*/ ,
/* modified timestamp comment 'date this record was modified',*/
primary key (id)
);
create table login_token ( create table login_token (
user_id integer not null /* comment 'user owning this token'*/ references "user" (id), user_id integer not null /* comment 'user owning this token'*/ references "user" (id),
token char(32) not null /* comment 'token useable for logging in'*/, token char(32) not null /* comment 'token useable for logging in'*/,

View File

@ -57,21 +57,31 @@ var SN = { // StatusNet
U: { // Utils U: { // Utils
FormNoticeEnhancements: function(form) { FormNoticeEnhancements: function(form) {
form_id = form.attr('id'); form_id = form.attr('id');
$('#'+form_id+' #'+SN.C.S.NoticeDataText).unbind('keyup');
$('#'+form_id+' #'+SN.C.S.NoticeDataText).unbind('keydown'); if (jQuery.data(form[0], 'ElementData') === undefined) {
if (maxLength > 0) { MaxLength = $('#'+form_id+' #'+SN.C.S.NoticeTextCount).text();
$('#'+form_id+' #'+SN.C.S.NoticeDataText).bind('keyup', function(e) { if (typeof(MaxLength) == 'undefined') {
MaxLength = SN.C.I.MaxLength;
}
jQuery.data(form[0], 'ElementData', {MaxLength:MaxLength});
SN.U.Counter(form);
NDT = $('#'+form_id+' #'+SN.C.S.NoticeDataText);
NDT.bind('keyup', function(e) {
SN.U.Counter(form); SN.U.Counter(form);
}); });
// run once in case there's something in there
SN.U.Counter(form); NDT.bind('keydown', function(e) {
SN.U.SubmitOnReturn(e, form);
});
}
else {
$('#'+form_id+' #'+SN.C.S.NoticeTextCount).text(jQuery.data(form[0], 'ElementData').MaxLength);
} }
$('#'+form_id+' #'+SN.C.S.NoticeDataText).bind('keydown', function(e) { if ($('body')[0].id != 'conversation') {
SN.U.SubmitOnReturn(e, form);
});
if($('body')[0].id != 'conversation') {
$('#'+form_id+' textarea').focus(); $('#'+form_id+' textarea').focus();
} }
}, },
@ -91,15 +101,14 @@ var SN = { // StatusNet
Counter: function(form) { Counter: function(form) {
SN.C.I.FormNoticeCurrent = form; SN.C.I.FormNoticeCurrent = form;
form_id = form.attr('id'); form_id = form.attr('id');
if (typeof(maxLength) == "undefined") {
maxLength = SN.C.I.MaxLength;
}
if (maxLength <= 0) { var MaxLength = jQuery.data(form[0], 'ElementData').MaxLength;
if (MaxLength <= 0) {
return; return;
} }
var remaining = maxLength - $('#'+form_id+' #'+SN.C.S.NoticeDataText).val().length; var remaining = MaxLength - $('#'+form_id+' #'+SN.C.S.NoticeDataText).val().length;
var counter = $('#'+form_id+' #'+SN.C.S.NoticeTextCount); var counter = $('#'+form_id+' #'+SN.C.S.NoticeTextCount);
if (remaining.toString() != counter.text()) { if (remaining.toString() != counter.text()) {
@ -306,8 +315,8 @@ var SN = { // StatusNet
$('.form_disfavor').each(function() { SN.U.FormXHR($(this)); }); $('.form_disfavor').each(function() { SN.U.FormXHR($(this)); });
}, },
NoticeForward: function() { NoticeRepeat: function() {
$('.form_forward').each(function() { SN.U.FormXHR($(this)); }); $('.form_repeat').each(function() { SN.U.FormXHR($(this)); });
}, },
NoticeAttachments: function() { NoticeAttachments: function() {
@ -443,7 +452,7 @@ var SN = { // StatusNet
Notices: function() { Notices: function() {
if ($('body.user_in').length > 0) { if ($('body.user_in').length > 0) {
SN.U.NoticeFavor(); SN.U.NoticeFavor();
SN.U.NoticeForward(); SN.U.NoticeRepeat();
SN.U.NoticeReply(); SN.U.NoticeReply();
} }
@ -457,6 +466,8 @@ var SN = { // StatusNet
$('.form_group_join').each(function() { SN.U.FormXHR($(this)); }); $('.form_group_join').each(function() { SN.U.FormXHR($(this)); });
$('.form_group_leave').each(function() { SN.U.FormXHR($(this)); }); $('.form_group_leave').each(function() { SN.U.FormXHR($(this)); });
$('.form_user_nudge').each(function() { SN.U.FormXHR($(this)); }); $('.form_user_nudge').each(function() { SN.U.FormXHR($(this)); });
SN.U.NewDirectMessage();
} }
} }
} }

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 * Server error
* *

View File

@ -134,17 +134,19 @@ class ApiAction extends Action
$twitter_user['protected'] = false; # not supported by StatusNet yet $twitter_user['protected'] = false; # not supported by StatusNet yet
$twitter_user['followers_count'] = $profile->subscriberCount(); $twitter_user['followers_count'] = $profile->subscriberCount();
$user = $profile->getUser();
$design = null; $design = null;
$user = $profile->getUser();
// Note: some profiles don't have an associated user // Note: some profiles don't have an associated user
$defaultDesign = Design::siteDesign();
if (!empty($user)) { if (!empty($user)) {
$design = $user->getDesign(); $design = $user->getDesign();
} }
if (empty($design)) {
$design = Design::siteDesign();
}
$color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor); $color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor);
$twitter_user['profile_background_color'] = ($color == null) ? '' : '#'.$color->hexValue(); $twitter_user['profile_background_color'] = ($color == null) ? '' : '#'.$color->hexValue();
$color = Design::toWebColor(empty($design->textcolor) ? $defaultDesign->textcolor : $design->textcolor); $color = Design::toWebColor(empty($design->textcolor) ? $defaultDesign->textcolor : $design->textcolor);
@ -163,7 +165,7 @@ class ApiAction extends Action
$timezone = 'UTC'; $timezone = 'UTC';
if (!empty($user) && !empty($user->timezone)) { if ($user->timezone) {
$timezone = $user->timezone; $timezone = $user->timezone;
} }
@ -213,6 +215,20 @@ class ApiAction extends Action
} }
function twitterStatusArray($notice, $include_user=true) function twitterStatusArray($notice, $include_user=true)
{
$base = $this->twitterSimpleStatusArray($notice, $include_user);
if (empty($notice->repeat_of)) {
return $base;
} else {
$original = Notice::staticGet('id', $notice->repeat_of);
$original_array = $this->twitterSimpleStatusArray($original, $include_user);
$original_array['retweeted_status'] = $base;
return $original_array;
}
}
function twitterSimpleStatusArray($notice, $include_user=true)
{ {
$profile = $notice->getProfile(); $profile = $notice->getProfile();
@ -446,9 +462,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) { foreach($twitter_status as $element => $value) {
switch ($element) { switch ($element) {
case 'user': case 'user':
@ -463,11 +479,14 @@ class ApiAction extends Action
case 'geo': case 'geo':
$this->showGeoRSS($value); $this->showGeoRSS($value);
break; break;
case 'retweeted_status':
$this->showTwitterXmlStatus($value, 'retweeted_status');
break;
default: default:
$this->element($element, null, $value); $this->element($element, null, $value);
} }
} }
$this->elementEnd('status'); $this->elementEnd($tag);
} }
function showTwitterXmlGroup($twitter_group) function showTwitterXmlGroup($twitter_group)
@ -586,7 +605,7 @@ class ApiAction extends Action
$this->endDocument('xml'); $this->endDocument('xml');
} }
function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null) function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null)
{ {
$this->initDocument('rss'); $this->initDocument('rss');
@ -600,6 +619,15 @@ class ApiAction extends Action
'href' => $suplink, 'href' => $suplink,
'type' => 'application/json')); '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('description', null, $subtitle);
$this->element('language', null, 'en-us'); $this->element('language', null, 'en-us');
$this->element('ttl', null, '40'); $this->element('ttl', null, '40');
@ -619,7 +647,7 @@ class ApiAction extends Action
$this->endTwitterRss(); $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'); $this->initDocument('atom');
@ -628,6 +656,10 @@ class ApiAction extends Action
$this->element('id', null, $id); $this->element('id', null, $id);
$this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); $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)) { if (!is_null($suplink)) {
# For FriendFeed's SUP protocol # For FriendFeed's SUP protocol
$this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',

View File

@ -433,8 +433,9 @@ class ReplyCommand extends Command
return; return;
} }
$notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), 1, $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(),
$notice->id); array('reply_to' => $notice->id));
if ($notice) { if ($notice) {
$channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname)); $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname));
} else { } else {
@ -584,7 +585,8 @@ class LoginCommand extends Command
function execute($channel) function execute($channel)
{ {
$disabled = common_config('logincommand','disabled'); $disabled = common_config('logincommand','disabled');
if(isset($disabled)) { $disabled = isset($disabled) && $disabled;
if($disabled) {
$channel->error($this->user, _('Login command is disabled')); $channel->error($this->user, _('Login command is disabled'));
return; return;
} }

View File

@ -153,7 +153,7 @@ if (!function_exists('npgettext')) {
*/ */
function _m($msg/*, ...*/) function _m($msg/*, ...*/)
{ {
$domain = _mdomain(debug_backtrace(false)); $domain = _mdomain(debug_backtrace());
$args = func_get_args(); $args = func_get_args();
switch(count($args)) { switch(count($args)) {
case 1: return dgettext($domain, $msg); case 1: return dgettext($domain, $msg);
@ -272,6 +272,7 @@ function get_nice_language_list()
function get_all_languages() { function get_all_languages() {
return array( return array(
'ar' => array('q' => 0.8, 'lang' => 'ar', 'name' => 'Arabic', 'direction' => 'rtl'), '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'), 'bg' => array('q' => 0.8, 'lang' => 'bg', 'name' => 'Bulgarian', 'direction' => 'ltr'),
'ca' => array('q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'), 'ca' => array('q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'),
'cs' => array('q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'), 'cs' => array('q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'),

View File

@ -154,8 +154,6 @@ class MessageForm extends Form
$contentLimit = Message::maxContent(); $contentLimit = Message::maxContent();
$this->out->inlineScript('maxLength = ' . $contentLimit . ';');
if ($contentLimit > 0) { if ($contentLimit > 0) {
$this->out->elementStart('dl', 'form_note'); $this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters')); $this->out->element('dt', null, _('Available characters'));

View File

@ -178,8 +178,6 @@ class NoticeForm extends Form
$contentLimit = Notice::maxContent(); $contentLimit = Notice::maxContent();
$this->out->inlineScript('maxLength = ' . $contentLimit . ';');
if ($contentLimit > 0) { if ($contentLimit > 0) {
$this->out->elementStart('dl', 'form_note'); $this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters')); $this->out->element('dt', null, _('Available characters'));

View File

@ -147,6 +147,10 @@ class NoticeListItem extends Widget
var $notice = null; var $notice = null;
/** The notice that was repeated. */
var $repeat = null;
/** The profile of the author of the notice, extracted once for convenience. */ /** The profile of the author of the notice, extracted once for convenience. */
var $profile = null; var $profile = null;
@ -162,8 +166,18 @@ class NoticeListItem extends Widget
function __construct($notice, $out=null) function __construct($notice, $out=null)
{ {
parent::__construct($out); parent::__construct($out);
$this->notice = $notice; if (!empty($notice->repeat_of)) {
$this->profile = $notice->getProfile(); $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->showNoticeSource();
$this->showNoticeLocation(); $this->showNoticeLocation();
$this->showContext(); $this->showContext();
$this->showRepeat();
$this->out->elementEnd('div'); $this->out->elementEnd('div');
} }
@ -212,7 +227,7 @@ class NoticeListItem extends Widget
$this->out->elementStart('div', 'notice-options'); $this->out->elementStart('div', 'notice-options');
$this->showFaveForm(); $this->showFaveForm();
$this->showReplyLink(); $this->showReplyLink();
$this->showForwardForm(); $this->showRepeatForm();
$this->showDeleteLink(); $this->showDeleteLink();
$this->out->elementEnd('div'); $this->out->elementEnd('div');
} }
@ -508,6 +523,52 @@ class NoticeListItem extends Widget
} }
} }
/**
* show a link to the author of repeat
*
* @return void
*/
function showRepeat()
{
if (!empty($this->repeat)) {
$repeater = Profile::staticGet('id', $this->repeat->profile_id);
$attrs = array('href' => $repeater->profileurl,
'class' => 'url');
if (!empty($repeater->fullname)) {
$attrs['title'] = $repeater->fullname . ' (' . $repeater->nickname . ')';
}
$this->out->elementStart('span', 'repeat');
$this->out->elementStart('a', $attrs);
$avatar = $repeater->getAvatar(AVATAR_MINI_SIZE);
$this->out->element('img', array('src' => ($avatar) ?
$avatar->displayUrl() :
Avatar::defaultImage(AVATAR_MINI_SIZE),
'class' => 'avatar photo',
'width' => AVATAR_MINI_SIZE,
'height' => AVATAR_MINI_SIZE,
'alt' =>
($repeater->fullname) ?
$repeater->fullname :
$repeater->nickname));
$this->out->elementEnd('a');
$text_link = XMLStringer::estring('a', $attrs, $repeater->nickname);
$this->out->raw(sprintf(_('Repeated by %s'), $text_link));
$this->out->elementEnd('span');
}
}
/** /**
* show a link to reply to the current notice * show a link to reply to the current notice
* *
@ -531,26 +592,6 @@ class NoticeListItem extends Widget
} }
} }
/**
* show the form to forward a notice
*
* @return void
*/
function showForwardForm()
{
$user = common_current_user();
if ($user && $user->id != $this->notice->profile_id) {
$profile = $user->getProfile();
if ($profile->hasForwarded($this->notice->id)) {
$this->out->text(_('Forwarded'));
} else {
$ff = new ForwardForm($this->out, $this->notice);
$ff->show();
}
}
}
/** /**
* if the user is the author, let them delete the notice * if the user is the author, let them delete the notice
* *
@ -572,6 +613,26 @@ class NoticeListItem extends Widget
} }
} }
/**
* show the form to repeat a notice
*
* @return void
*/
function showRepeatForm()
{
$user = common_current_user();
if ($user && $user->id != $this->notice->profile_id) {
$profile = $user->getProfile();
if ($profile->hasRepeated($this->notice->id)) {
$this->out->text(_('Repeated'));
} else {
$rf = new RepeatForm($this->out, $this->notice);
$rf->show();
}
}
}
/** /**
* finish the notice * finish the notice
* *

View File

@ -359,9 +359,8 @@ class StatusNetOAuthDataStore extends OAuthDataStore
$notice = Notice::saveNew($author->id, $notice = Notice::saveNew($author->id,
$omb_notice->getContent(), $omb_notice->getContent(),
'omb', 'omb',
false, array('is_local' => Notice::REMOTE_OMB,
null, 'uri' => $omb_notice->getIdentifierURI()));
$omb_notice->getIdentifierURI());
common_broadcast_notice($notice, true); common_broadcast_notice($notice, true);
} }

View File

@ -2,7 +2,7 @@
/** /**
* StatusNet, the distributed open-source microblogging tool * StatusNet, the distributed open-source microblogging tool
* *
* Form for forwarding a notice * Form for repeating a notice
* *
* PHP version 5 * PHP version 5
* *
@ -27,14 +27,12 @@
* @link http://status.net/ * @link http://status.net/
*/ */
if (!defined('STATUSNET') && !defined('LACONICA')) { if (!defined('STATUSNET')) {
exit(1); exit(1);
} }
require_once INSTALLDIR.'/lib/form.php';
/** /**
* Form for forwarding a notice * Form for repeating a notice
* *
* @category Form * @category Form
* @package StatusNet * @package StatusNet
@ -43,10 +41,10 @@ require_once INSTALLDIR.'/lib/form.php';
* @link http://status.net/ * @link http://status.net/
*/ */
class ForwardForm extends Form class RepeatForm extends Form
{ {
/** /**
* Notice to forward * Notice to repeat
*/ */
var $notice = null; var $notice = null;
@ -55,7 +53,7 @@ class ForwardForm extends Form
* Constructor * Constructor
* *
* @param HTMLOutputter $out output channel * @param HTMLOutputter $out output channel
* @param Notice $notice notice to forward * @param Notice $notice notice to repeat
*/ */
function __construct($out=null, $notice=null) function __construct($out=null, $notice=null)
@ -73,7 +71,7 @@ class ForwardForm extends Form
function id() function id()
{ {
return 'forward-' . $this->notice->id; return 'repeat-' . $this->notice->id;
} }
/** /**
@ -84,7 +82,7 @@ class ForwardForm extends Form
function action() function action()
{ {
return common_local_url('forward'); return common_local_url('repeat');
} }
/** /**
@ -106,7 +104,7 @@ class ForwardForm extends Form
*/ */
function formLegend() function formLegend()
{ {
$this->out->element('legend', null, _('Forward this notice')); $this->out->element('legend', null, _('Repeat this notice'));
} }
/** /**
@ -130,8 +128,8 @@ class ForwardForm extends Form
function formActions() function formActions()
{ {
$this->out->submit('forward-submit-' . $this->notice->id, $this->out->submit('repeat-submit-' . $this->notice->id,
_('Forward'), 'submit', null, _('Forward this notice')); _('Repeat'), 'submit', null, _('Repeat this notice'));
} }
/** /**
@ -142,6 +140,6 @@ class ForwardForm extends Form
function formClass() function formClass()
{ {
return 'form_forward'; return 'form_repeat';
} }
} }

View File

@ -99,7 +99,8 @@ class Router
'groupblock', 'groupunblock', 'groupblock', 'groupunblock',
'sandbox', 'unsandbox', 'sandbox', 'unsandbox',
'silence', 'unsilence', 'silence', 'unsilence',
'deleteuser', 'forward'); 'repeat',
'deleteuser');
foreach ($main as $a) { foreach ($main as $a) {
$m->connect('main/'.$a, array('action' => $a)); $m->connect('main/'.$a, array('action' => $a));
@ -318,6 +319,18 @@ class Router
'id' => '[a-zA-Z0-9]+', 'id' => '[a-zA-Z0-9]+',
'format' => '(xml|json|rss|atom)')); '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', $m->connect('api/statuses/friends.:format',
array('action' => 'ApiUserFriends', array('action' => 'ApiUserFriends',
'format' => '(xml|json)')); 'format' => '(xml|json)'));
@ -358,6 +371,16 @@ class Router
'id' => '[0-9]+', 'id' => '[0-9]+',
'format' => '(xml|json)')); '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 // users
$m->connect('api/users/show.:format', $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

View File

@ -43,4 +43,19 @@ class User_username extends Memcached_DataObject
return false; return false;
} }
} }
function table() {
return array(
'user_id' => DB_DATAOBJECT_INT,
'username' => DB_DATAOBJECT_STR,
'provider_name' => DB_DATAOBJECT_STR ,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME
);
}
// now define the keys.
function keys() {
return array('provider_name', 'username');
}
} }

View File

@ -445,8 +445,9 @@ class FacebookAction extends Action
$replyto = $this->trimmed('inreplyto'); $replyto = $this->trimmed('inreplyto');
try { try {
$notice = Notice::saveNew($user->id, $content, $notice = Notice::saveNew($user->id, $content, 'web',
'web', 1, ($replyto == 'false') ? null : $replyto); array('reply_to' => ($replyto == 'false') ? null : $replyto));
} catch (Exception $e) { } catch (Exception $e) {
$this->showPage($e->getMessage()); $this->showPage($e->getMessage());
return; return;

View File

@ -0,0 +1,188 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @package GravatarPlugin
* @maintainer Eric Helgeson <erichelgeson@gmail.com>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
class GravatarPlugin extends Plugin
{
function onInitializePlugin() {
return true;
}
function onStartAvatarFormData($action) {
$user = common_current_user();
$hasGravatar = $this->hasGravatar($user->id);
if($hasGravatar) {
return false;
}
}
function onEndAvatarFormData(&$action) {
$user = common_current_user();
$hasGravatar = $this->hasGravatar($user->id);
if(!empty($user->email) && !$hasGravatar) { //and not gravatar already set
$action->elementStart('form', array('method' => 'post',
'id' => 'form_settings_gravatar_add',
'class' => 'form_settings',
'action' =>
common_local_url('avatarsettings')));
$action->elementStart('fieldset', array('id' => 'settings_gravatar_add'));
$action->element('legend', null, _m('Set Gravatar'));
$action->hidden('token', common_session_token());
$action->element('p', 'form_guide',
_m('If you want to use your Gravatar image, click "Add".'));
$action->element('input', array('type' => 'submit',
'id' => 'settings_gravatar_add_action-submit',
'name' => 'add',
'class' => 'submit',
'value' => _m('Add')));
$action->elementEnd('fieldset');
$action->elementEnd('form');
} elseif($hasGravatar) {
$action->elementStart('form', array('method' => 'post',
'id' => 'form_settings_gravatar_remove',
'class' => 'form_settings',
'action' =>
common_local_url('avatarsettings')));
$action->elementStart('fieldset', array('id' => 'settings_gravatar_remove'));
$action->element('legend', null, _m('Remove Gravatar'));
$action->hidden('token', common_session_token());
$action->element('p', 'form_guide',
_m('If you want to remove your Gravatar image, click "Remove".'));
$action->element('input', array('type' => 'submit',
'id' => 'settings_gravatar_remove_action-submit',
'name' => 'remove',
'class' => 'submit',
'value' => _m('Remove')));
$action->elementEnd('fieldset');
$action->elementEnd('form');
} else {
$action->element('p', 'form_guide',
_m('To use a Gravatar first enter in an email address.'));
}
}
function onStartAvatarSaveForm($action) {
if ($action->arg('add')) {
$result = $this->gravatar_save();
if($result['success']===true) {
common_broadcast_profile(common_current_user()->getProfile());
}
$action->showForm($result['message'], $result['success']);
return false;
} else if ($action->arg('remove')) {
$result = $this->gravatar_remove();
if($result['success']===true) {
common_broadcast_profile(common_current_user()->getProfile());
}
$action->showForm($result['message'], $result['success']);
return false;
} else {
return true;
}
}
function hasGravatar($id) {
$avatar = new Avatar();
$avatar->profile_id = $id;
if ($avatar->find()) {
while ($avatar->fetch()) {
if($avatar->filename == null) {
return true;
}
}
}
return false;
}
function gravatar_save()
{
$cur = common_current_user();
if(empty($cur->email)) {
return array('message' => _m('You do not have a email set in your profile.'),
'success' => false);
}
//Get rid of previous Avatar
$this->gravatar_remove();
foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
$gravatar = new Avatar();
$gravatar->profile_id = $cur->id;
$gravatar->width = $size;
$gravatar->height = $size;
$gravatar->original = false; //No file, so no original
$gravatar->mediatype = 'img';//XXX: Unsure what to put here
//$gravatar->filename = null;//No filename. Remote
$gravatar->url = $this->gravatar_url($cur->email, $size);
$gravatar->created = DB_DataObject_Cast::dateTime(); # current time
if (!$gravatar->insert()) {
return array('message' => _m('Failed to save Gravatar to the DB.'),
'success' => false);
}
}
return array('message' => _m('Gravatar added.'),
'success' => true);
}
function gravatar_remove()
{
$user = common_current_user();
$profile = $user->getProfile();
$avatar = $profile->getOriginalAvatar();
if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
if($avatar) $avatar->delete();
return array('message' => _m('Gravatar removed.'),
'success' => true);
}
function gravatar_url($email, $size) {
$url = "http://www.gravatar.com/avatar.php?gravatar_id=".
md5(strtolower($email)).
"&default=".urlencode(Avatar::defaultImage($size)).
"&size=".$size;
return $url;
}
}

13
plugins/Gravatar/README Normal file
View File

@ -0,0 +1,13 @@
GravatarPlugin 0.1
About
This will allow users to use their Gravatar Avatar with your StatusNet install.
Configuration
add this to your config.php:
addPlugin('Gravatar', array());
ToDo:
Site default all on for gravatar by default
Migration Script
Localize

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

11
plugins/Realtime/README Normal file
View File

@ -0,0 +1,11 @@
== TODO ==
* i18n
* Change in context URL to conversation (try not to construct the URL in JS)
* Update mark behaviour (on notice send)
* Pause, Send a notice ~ should not update counter
* Pause ~ retain up to 50-100 most recent notices
* Add geo data
* Make it work for Conversation page (perhaps a little tricky)
* IE is updating the counter in document title all the time (Not sure if this is still an issue)
* Reconsider the timestamp approach

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

@ -670,8 +670,7 @@ display:block;
text-align:left; text-align:left;
width:100%; width:100%;
} }
.entity_actions a, .entity_actions a {
.entity_remote_subscribe {
text-decoration:none; text-decoration:none;
font-weight:bold; font-weight:bold;
display:block; display:block;
@ -686,7 +685,8 @@ border-radius:4px;
.entity_actions a, .entity_actions a,
.entity_actions input, .entity_actions input,
.entity_actions p { .entity_actions p {
border:0; border-width:2px;
border-style:solid;
padding-left:23px; padding-left:23px;
} }
@ -695,14 +695,6 @@ padding-left:23px;
padding:2px 4px 1px 26px; padding:2px 4px 1px 26px;
} }
.entity_remote_subscribe {
padding:4px;
border-width:2px;
border-style:solid;
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
}
.entity_actions .accept { .entity_actions .accept {
margin-bottom:18px; margin-bottom:18px;
} }
@ -1002,13 +994,13 @@ float:left;
} }
.notice-options .notice_delete, .notice-options .notice_delete,
.notice-options .notice_reply, .notice-options .notice_reply,
.notice-options .form_forward, .notice-options .form_repeat,
.notice-options .form_favor, .notice-options .form_favor,
.notice-options .form_disfavor { .notice-options .form_disfavor {
float:left; float:left;
margin-left:20%; margin-left:20%;
} }
.notice-options .form_forward, .notice-options .form_repeat,
.notice-options .form_favor, .notice-options .form_favor,
.notice-options .form_disfavor { .notice-options .form_disfavor {
margin-left:0; margin-left:0;
@ -1034,12 +1026,12 @@ border-radius:0;
-moz-border-radius:0; -moz-border-radius:0;
-webkit-border-radius:0; -webkit-border-radius:0;
} }
.notice-options .form_forward legend, .notice-options .form_repeat legend,
.notice-options .form_favor legend, .notice-options .form_favor legend,
.notice-options .form_disfavor legend { .notice-options .form_disfavor legend {
display:none; display:none;
} }
.notice-options .form_forward fieldset, .notice-options .form_repeat fieldset,
.notice-options .form_favor fieldset, .notice-options .form_favor fieldset,
.notice-options .form_disfavor fieldset { .notice-options .form_disfavor fieldset {
border:0; border:0;

View File

@ -30,8 +30,7 @@ border-radius:4px;
input, textarea, select, option { input, textarea, select, option {
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
} }
input, textarea, select, input, textarea, select {
.entity_remote_subscribe {
border-color:#AAAAAA; border-color:#AAAAAA;
} }
@ -56,15 +55,12 @@ background:none;
} }
.form_notice.warning #notice_text-count, .form_notice.warning #notice_text-count,
.form_settings .form_note, .form_settings .form_note {
.entity_remote_subscribe,
.entity_actions .form_notice input.submit {
background-color:#9BB43E; background-color:#9BB43E;
} }
input.submit, input.submit,
.form_notice.warning #notice_text-count, .form_notice.warning #notice_text-count,
.form_settings .form_note, .form_settings .form_note,
.entity_remote_subscribe,
.entity_actions a, .entity_actions a,
.entity_actions input, .entity_actions input,
.entity_moderation p, .entity_moderation p,
@ -82,16 +78,18 @@ background-color:transparent;
input:focus, textarea:focus, select:focus, input:focus, textarea:focus, select:focus,
.form_notice.warning #notice_data-text, .form_notice.warning #notice_data-text,
.form_notice.warning #notice_text-count, .form_notice.warning #notice_text-count,
.form_settings .form_note, .form_settings .form_note {
.entity_remote_subscribe {
border-color:#9BB43E; border-color:#9BB43E;
} }
input.submit, input.submit {
.entity_remote_subscribe,
.entity_actions .form_notice input.submit {
color:#FFFFFF; color:#FFFFFF;
} }
input.submit { .entity_actions input.submit {
border-color:transparent;
text-shadow:none;
}
input.submit,
.form_notice input.submit {
background:#AAAAAA url(../../base/images/illustrations/illu_pattern-01.png) 0 0 repeat-x; background:#AAAAAA url(../../base/images/illustrations/illu_pattern-01.png) 0 0 repeat-x;
text-shadow:0 1px 0 #FFFFFF; text-shadow:0 1px 0 #FFFFFF;
color:#000000; color:#000000;
@ -109,9 +107,6 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.1);
-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.1); -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.1);
text-shadow:none; text-shadow:none;
} }
.entity_actions input.submit {
text-shadow:none;
}
a, a,
.form_settings input.form_action-primary, .form_settings input.form_action-primary,

View File

@ -30,8 +30,7 @@ border-radius:4px;
input, textarea, select, option { input, textarea, select, option {
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
} }
input, textarea, select, input, textarea, select {
.entity_remote_subscribe {
border-color:#AAAAAA; border-color:#AAAAAA;
} }
@ -56,15 +55,12 @@ background:none;
} }
.form_notice.warning #notice_text-count, .form_notice.warning #notice_text-count,
.form_settings .form_note, .form_settings .form_note {
.entity_remote_subscribe,
.entity_actions .form_notice input.submit {
background-color:#9BB43E; background-color:#9BB43E;
} }
input.submit, input.submit,
.form_notice.warning #notice_text-count, .form_notice.warning #notice_text-count,
.form_settings .form_note, .form_settings .form_note,
.entity_remote_subscribe,
.entity_actions a, .entity_actions a,
.entity_actions input, .entity_actions input,
.entity_moderation p, .entity_moderation p,
@ -82,16 +78,18 @@ background-color:transparent;
input:focus, textarea:focus, select:focus, input:focus, textarea:focus, select:focus,
.form_notice.warning #notice_data-text, .form_notice.warning #notice_data-text,
.form_notice.warning #notice_text-count, .form_notice.warning #notice_text-count,
.form_settings .form_note, .form_settings .form_note {
.entity_remote_subscribe {
border-color:#9BB43E; border-color:#9BB43E;
} }
input.submit, input.submit {
.entity_remote_subscribe,
.entity_actions .form_notice input.submit {
color:#FFFFFF; color:#FFFFFF;
} }
input.submit { .entity_actions input.submit {
border-color:transparent;
text-shadow:none;
}
input.submit,
.form_notice input.submit {
background:#AAAAAA url(../../base/images/illustrations/illu_pattern-01.png) 0 0 repeat-x; background:#AAAAAA url(../../base/images/illustrations/illu_pattern-01.png) 0 0 repeat-x;
text-shadow:0 1px 0 #FFFFFF; text-shadow:0 1px 0 #FFFFFF;
color:#000000; color:#000000;
@ -109,9 +107,6 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.1);
-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.1); -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.1);
text-shadow:none; text-shadow:none;
} }
.entity_actions input.submit {
text-shadow:none;
}
a, a,
.form_settings input.form_action-primary, .form_settings input.form_action-primary,