Merge branch '0.9.x' of git@gitorious.org:statusnet/mainline into 1.0.x
Conflicts: lib/channel.php scripts/imdaemon.php
This commit is contained in:
commit
e89908f261
4
README
4
README
|
@ -137,7 +137,9 @@ run correctly.
|
|||
|
||||
- PHP 5.2.3+. It may be possible to run this software on earlier
|
||||
versions of PHP, but many of the functions used are only available
|
||||
in PHP 5.2 or above.
|
||||
in PHP 5.2 or above. 5.2.6 or later is needed for XMPP background
|
||||
daemons on 64-bit platforms. PHP 5.3.x should work but is known
|
||||
to cause some failures for OpenID.
|
||||
- MySQL 5.x. The StatusNet database is stored, by default, in a MySQL
|
||||
server. It has been primarily tested on 5.x servers, although it may
|
||||
be possible to install on earlier (or later!) versions. The server
|
||||
|
|
141
actions/apimediaupload.php
Normal file
141
actions/apimediaupload.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Upload an image via 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
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 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';
|
||||
|
||||
/**
|
||||
* Upload an image via the API. Returns a shortened URL for the image
|
||||
* to the user.
|
||||
*
|
||||
* @category API
|
||||
* @package StatusNet
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class ApiMediaUploadAction extends ApiAuthAction
|
||||
{
|
||||
/**
|
||||
* Handle the request
|
||||
*
|
||||
* Grab the file from the 'media' param, then store, and shorten
|
||||
*
|
||||
* @todo Upload throttle!
|
||||
*
|
||||
* @param array $args $_REQUEST data (unused)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
$this->clientError(
|
||||
_('This method requires a POST.'),
|
||||
400, $this->format
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for PHP returning empty $_POST and $_FILES when POST
|
||||
// length > post_max_size in php.ini
|
||||
|
||||
if (empty($_FILES)
|
||||
&& empty($_POST)
|
||||
&& ($_SERVER['CONTENT_LENGTH'] > 0)
|
||||
) {
|
||||
$msg = _('The server was unable to handle that much POST ' .
|
||||
'data (%s bytes) due to its current configuration.');
|
||||
|
||||
$this->clientError(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
|
||||
return;
|
||||
}
|
||||
|
||||
$upload = null;
|
||||
|
||||
try {
|
||||
$upload = MediaFile::fromUpload('media', $this->auth_user);
|
||||
} catch (ClientException $ce) {
|
||||
$this->clientError($ce->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($upload)) {
|
||||
$this->showResponse($upload);
|
||||
} else {
|
||||
$this->clientError('Upload failed.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a Twitpic-like response with the ID of the media file
|
||||
* and a (hopefully) shortened URL for it.
|
||||
*
|
||||
* @param File $upload the uploaded file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function showResponse($upload)
|
||||
{
|
||||
$this->initDocument();
|
||||
$this->elementStart('rsp', array('stat' => 'ok'));
|
||||
$this->element('mediaid', null, $upload->fileRecord->id);
|
||||
$this->element('mediaurl', null, $upload->shortUrl());
|
||||
$this->elementEnd('rsp');
|
||||
$this->endDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrided clientError to show a more Twitpic-like error
|
||||
*
|
||||
* @param String $msg an error message
|
||||
*
|
||||
*/
|
||||
function clientError($msg)
|
||||
{
|
||||
$this->initDocument();
|
||||
$this->elementStart('rsp', array('stat' => 'fail'));
|
||||
|
||||
// @todo add in error code
|
||||
$errAttr = array('msg' => $msg);
|
||||
|
||||
$this->element('err', $errAttr, null);
|
||||
$this->elementEnd('rsp');
|
||||
$this->endDocument();
|
||||
}
|
||||
|
||||
}
|
|
@ -244,11 +244,17 @@ class ApiStatusesUpdateAction extends ApiAuthAction
|
|||
$options = array_merge($options, $locOptions);
|
||||
}
|
||||
|
||||
$this->notice =
|
||||
Notice::saveNew($this->auth_user->id,
|
||||
try {
|
||||
$this->notice = Notice::saveNew(
|
||||
$this->auth_user->id,
|
||||
$content,
|
||||
$this->source,
|
||||
$options);
|
||||
$options
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$this->clientError($e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($upload)) {
|
||||
$upload->attachToNotice($this->notice);
|
||||
|
|
|
@ -97,8 +97,6 @@ class ApiStatusnetConfigAction extends ApiAction
|
|||
|
||||
// XXX: check that all sections and settings are legal XML elements
|
||||
|
||||
common_debug(var_export($this->keys, true));
|
||||
|
||||
foreach ($this->keys as $section => $settings) {
|
||||
$this->elementStart($section);
|
||||
foreach ($settings as $setting) {
|
||||
|
@ -110,6 +108,14 @@ class ApiStatusnetConfigAction extends ApiAction
|
|||
} else if ($value === true) {
|
||||
$value = 'true';
|
||||
}
|
||||
|
||||
// return theme logo if there's no site specific one
|
||||
if (empty($value)) {
|
||||
if ($section == 'site' && $setting == 'logo') {
|
||||
$value = Theme::path('logo.png');
|
||||
}
|
||||
}
|
||||
|
||||
$this->element($setting, null, $value);
|
||||
}
|
||||
$this->elementEnd($section);
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
* @package StatusNet
|
||||
* @author Craig Andrews <candrews@integralblue.com>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net> * @copyright 2009 StatusNet, Inc.
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2009-2010 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/
|
||||
*/
|
||||
|
@ -123,22 +124,26 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
|
|||
? $avatar->displayUrl()
|
||||
: Avatar::defaultImage(AVATAR_PROFILE_SIZE);
|
||||
|
||||
$link = common_local_url(
|
||||
'showfavorites',
|
||||
array('nickname' => $this->user->nickname)
|
||||
);
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
|
||||
switch($this->format) {
|
||||
case 'xml':
|
||||
$this->showXmlTimeline($this->notices);
|
||||
break;
|
||||
case 'rss':
|
||||
$link = common_local_url(
|
||||
'showfavorites',
|
||||
array('nickname' => $this->user->nickname)
|
||||
);
|
||||
$this->showRssTimeline(
|
||||
$this->notices,
|
||||
$title,
|
||||
$link,
|
||||
$subtitle,
|
||||
null,
|
||||
$logo
|
||||
$logo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
@ -153,23 +158,8 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
|
|||
$atom->setLogo($logo);
|
||||
$atom->setUpdated('now');
|
||||
|
||||
$atom->addLink(
|
||||
common_local_url(
|
||||
'showfavorites',
|
||||
array('nickname' => $this->user->nickname)
|
||||
)
|
||||
);
|
||||
|
||||
$id = $this->arg('id');
|
||||
$aargs = array('format' => 'atom');
|
||||
if (!empty($id)) {
|
||||
$aargs['id'] = $id;
|
||||
}
|
||||
|
||||
$atom->addLink(
|
||||
$this->getSelfUri('ApiTimelineFavorites', $aargs),
|
||||
array('rel' => 'self', 'type' => 'application/atom+xml')
|
||||
);
|
||||
$atom->addLink($link);
|
||||
$atom->setSelfLink($self);
|
||||
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
|
||||
|
|
|
@ -117,9 +117,17 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
|
|||
|
||||
$subtitle = sprintf(
|
||||
_('Updates from %1$s and friends on %2$s!'),
|
||||
$this->user->nickname, $sitename
|
||||
$this->user->nickname,
|
||||
$sitename
|
||||
);
|
||||
|
||||
$link = common_local_url(
|
||||
'all',
|
||||
array('nickname' => $this->user->nickname)
|
||||
);
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
|
||||
$logo = (!empty($avatar))
|
||||
? $avatar->displayUrl()
|
||||
: Avatar::defaultImage(AVATAR_PROFILE_SIZE);
|
||||
|
@ -130,19 +138,14 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
|
|||
break;
|
||||
case 'rss':
|
||||
|
||||
$link = common_local_url(
|
||||
'all', array(
|
||||
'nickname' => $this->user->nickname
|
||||
)
|
||||
);
|
||||
|
||||
$this->showRssTimeline(
|
||||
$this->notices,
|
||||
$title,
|
||||
$link,
|
||||
$subtitle,
|
||||
null,
|
||||
$logo
|
||||
$logo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
@ -156,24 +159,8 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
|
|||
$atom->setSubtitle($subtitle);
|
||||
$atom->setLogo($logo);
|
||||
$atom->setUpdated('now');
|
||||
|
||||
$atom->addLink(
|
||||
common_local_url(
|
||||
'all',
|
||||
array('nickname' => $this->user->nickname)
|
||||
)
|
||||
);
|
||||
|
||||
$id = $this->arg('id');
|
||||
$aargs = array('format' => 'atom');
|
||||
if (!empty($id)) {
|
||||
$aargs['id'] = $id;
|
||||
}
|
||||
|
||||
$atom->addLink(
|
||||
$this->getSelfUri('ApiTimelineFriends', $aargs),
|
||||
array('rel' => 'self', 'type' => 'application/atom+xml')
|
||||
);
|
||||
$atom->addLink($link);
|
||||
$atom->setSelfLink($self);
|
||||
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
|
||||
|
|
|
@ -107,6 +107,8 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
|
|||
// We'll pull common formatting out of this for other formats
|
||||
$atom = new AtomGroupNoticeFeed($this->group);
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
|
||||
switch($this->format) {
|
||||
case 'xml':
|
||||
$this->showXmlTimeline($this->notices);
|
||||
|
@ -118,7 +120,8 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
|
|||
$this->group->homeUrl(),
|
||||
$atom->subtitle,
|
||||
null,
|
||||
$atom->logo
|
||||
$atom->logo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
@ -126,24 +129,12 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
|
|||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
try {
|
||||
|
||||
$atom->addAuthorRaw($this->group->asAtomAuthor());
|
||||
$atom->setActivitySubject($this->group->asActivitySubject());
|
||||
|
||||
$id = $this->arg('id');
|
||||
$aargs = array('format' => 'atom');
|
||||
if (!empty($id)) {
|
||||
$aargs['id'] = $id;
|
||||
}
|
||||
$self = $this->getSelfUri('ApiTimelineGroup', $aargs);
|
||||
|
||||
$atom->setId($self);
|
||||
$atom->setSelfLink($self);
|
||||
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
|
||||
$this->raw($atom->getString());
|
||||
|
||||
} catch (Atom10FeedException $e) {
|
||||
$this->serverError(
|
||||
'Could not generate feed for group - ' . $e->getMessage()
|
||||
|
|
|
@ -72,7 +72,7 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
|
|||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
common_debug("api home_timeline");
|
||||
|
||||
$this->user = $this->getTargetUser($this->arg('id'));
|
||||
|
||||
if (empty($this->user)) {
|
||||
|
@ -121,6 +121,13 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
|
|||
$this->user->nickname, $sitename
|
||||
);
|
||||
|
||||
$link = common_local_url(
|
||||
'all',
|
||||
array('nickname' => $this->user->nickname)
|
||||
);
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
|
||||
$logo = (!empty($avatar))
|
||||
? $avatar->displayUrl()
|
||||
: Avatar::defaultImage(AVATAR_PROFILE_SIZE);
|
||||
|
@ -130,17 +137,14 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
|
|||
$this->showXmlTimeline($this->notices);
|
||||
break;
|
||||
case 'rss':
|
||||
$link = common_local_url(
|
||||
'all',
|
||||
array('nickname' => $this->user->nickname)
|
||||
);
|
||||
$this->showRssTimeline(
|
||||
$this->notices,
|
||||
$title,
|
||||
$link,
|
||||
$subtitle,
|
||||
null,
|
||||
$logo
|
||||
$logo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
@ -155,23 +159,8 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
|
|||
$atom->setLogo($logo);
|
||||
$atom->setUpdated('now');
|
||||
|
||||
$atom->addLink(
|
||||
common_local_url(
|
||||
'all',
|
||||
array('nickname' => $this->user->nickname)
|
||||
)
|
||||
);
|
||||
|
||||
$id = $this->arg('id');
|
||||
$aargs = array('format' => 'atom');
|
||||
if (!empty($id)) {
|
||||
$aargs['id'] = $id;
|
||||
}
|
||||
|
||||
$atom->addLink(
|
||||
$this->getSelfUri('ApiTimelineHome', $aargs),
|
||||
array('rel' => 'self', 'type' => 'application/atom+xml')
|
||||
);
|
||||
$atom->addLink($link);
|
||||
$atom->setSelfLink($self);
|
||||
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
$this->raw($atom->getString());
|
||||
|
|
|
@ -123,6 +123,9 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
|
|||
'replies',
|
||||
array('nickname' => $this->user->nickname)
|
||||
);
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
|
||||
$subtitle = sprintf(
|
||||
_('%1$s updates that reply to updates from %2$s / %3$s.'),
|
||||
$sitename, $this->user->nickname, $profile->getBestName()
|
||||
|
@ -134,10 +137,20 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
|
|||
$this->showXmlTimeline($this->notices);
|
||||
break;
|
||||
case 'rss':
|
||||
$this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $logo);
|
||||
$this->showRssTimeline(
|
||||
$this->notices,
|
||||
$title,
|
||||
$link,
|
||||
$subtitle,
|
||||
null,
|
||||
$logo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
$atom = new AtomNoticeFeed();
|
||||
|
||||
$atom->setId($id);
|
||||
|
@ -146,23 +159,8 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
|
|||
$atom->setLogo($logo);
|
||||
$atom->setUpdated('now');
|
||||
|
||||
$atom->addLink(
|
||||
common_local_url(
|
||||
'replies',
|
||||
array('nickname' => $this->user->nickname)
|
||||
)
|
||||
);
|
||||
|
||||
$id = $this->arg('id');
|
||||
$aargs = array('format' => 'atom');
|
||||
if (!empty($id)) {
|
||||
$aargs['id'] = $id;
|
||||
}
|
||||
|
||||
$atom->addLink(
|
||||
$this->getSelfUri('ApiTimelineMentions', $aargs),
|
||||
array('rel' => 'self', 'type' => 'application/atom+xml')
|
||||
);
|
||||
$atom->addLink($link);
|
||||
$atom->setSelfLink($self);
|
||||
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
$this->raw($atom->getString());
|
||||
|
|
|
@ -107,7 +107,8 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
|
|||
$title = sprintf(_("%s public timeline"), $sitename);
|
||||
$taguribase = TagURI::base();
|
||||
$id = "tag:$taguribase:PublicTimeline";
|
||||
$link = common_root_url();
|
||||
$link = common_local_url('public');
|
||||
$self = $this->getSelfUri();
|
||||
$subtitle = sprintf(_("%s updates from everyone!"), $sitename);
|
||||
|
||||
switch($this->format) {
|
||||
|
@ -115,10 +116,20 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
|
|||
$this->showXmlTimeline($this->notices);
|
||||
break;
|
||||
case 'rss':
|
||||
$this->showRssTimeline($this->notices, $title, $link, $subtitle, null, $sitelogo);
|
||||
$this->showRssTimeline(
|
||||
$this->notices,
|
||||
$title,
|
||||
$link,
|
||||
$subtitle,
|
||||
null,
|
||||
$sitelogo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
$atom = new AtomNoticeFeed();
|
||||
|
||||
$atom->setId($id);
|
||||
|
@ -126,16 +137,8 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
|
|||
$atom->setSubtitle($subtitle);
|
||||
$atom->setLogo($sitelogo);
|
||||
$atom->setUpdated('now');
|
||||
|
||||
$atom->addLink(common_local_url('public'));
|
||||
|
||||
$atom->addLink(
|
||||
$this->getSelfUri(
|
||||
'ApiTimelinePublic', array('format' => 'atom')
|
||||
),
|
||||
array('rel' => 'self', 'type' => 'application/atom+xml')
|
||||
);
|
||||
|
||||
$atom->setSelfLink($self);
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
|
||||
$this->raw($atom->getString());
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Jeffery To <jeffery.to@gmail.com>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2009 StatusNet, Inc.
|
||||
* @copyright 2009-2010 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/
|
||||
*/
|
||||
|
@ -67,6 +67,8 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
|
|||
{
|
||||
parent::prepare($args);
|
||||
|
||||
common_debug("apitimelinetag prepare()");
|
||||
|
||||
$this->tag = $this->arg('tag');
|
||||
$this->notices = $this->getNotices();
|
||||
|
||||
|
@ -108,22 +110,28 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
|
|||
$taguribase = TagURI::base();
|
||||
$id = "tag:$taguribase:TagTimeline:".$tag;
|
||||
|
||||
$link = common_local_url(
|
||||
'tag',
|
||||
array('tag' => $this->tag)
|
||||
);
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
|
||||
common_debug("self link is: $self");
|
||||
|
||||
switch($this->format) {
|
||||
case 'xml':
|
||||
$this->showXmlTimeline($this->notices);
|
||||
break;
|
||||
case 'rss':
|
||||
$link = common_local_url(
|
||||
'tag',
|
||||
array('tag' => $this->tag)
|
||||
);
|
||||
$this->showRssTimeline(
|
||||
$this->notices,
|
||||
$title,
|
||||
$link,
|
||||
$subtitle,
|
||||
null,
|
||||
$sitelogo
|
||||
$sitelogo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
@ -138,22 +146,8 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
|
|||
$atom->setLogo($logo);
|
||||
$atom->setUpdated('now');
|
||||
|
||||
$atom->addLink(
|
||||
common_local_url(
|
||||
'tag',
|
||||
array('tag' => $this->tag)
|
||||
)
|
||||
);
|
||||
|
||||
$aargs = array('format' => 'atom');
|
||||
if (!empty($this->tag)) {
|
||||
$aargs['tag'] = $this->tag;
|
||||
}
|
||||
|
||||
$atom->addLink(
|
||||
$this->getSelfUri('ApiTimelineTag', $aargs),
|
||||
array('rel' => 'self', 'type' => 'application/atom+xml')
|
||||
);
|
||||
$atom->addLink($link);
|
||||
$atom->setSelfLink($self);
|
||||
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
$this->raw($atom->getString());
|
||||
|
|
|
@ -116,13 +116,13 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
|||
// We'll use the shared params from the Atom stub
|
||||
// for other feed types.
|
||||
$atom = new AtomUserNoticeFeed($this->user);
|
||||
$title = $atom->title;
|
||||
|
||||
$link = common_local_url(
|
||||
'showstream',
|
||||
array('nickname' => $this->user->nickname)
|
||||
);
|
||||
$subtitle = $atom->subtitle;
|
||||
$logo = $atom->logo;
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
|
||||
// FriendFeed's SUP protocol
|
||||
// Also added RSS and Atom feeds
|
||||
|
@ -136,25 +136,22 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
|||
break;
|
||||
case 'rss':
|
||||
$this->showRssTimeline(
|
||||
$this->notices, $title, $link,
|
||||
$subtitle, $suplink, $logo
|
||||
$this->notices,
|
||||
$atom->title,
|
||||
$link,
|
||||
$atom->subtitle,
|
||||
$suplink,
|
||||
$atom->logo,
|
||||
$self
|
||||
);
|
||||
break;
|
||||
case 'atom':
|
||||
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
$id = $this->arg('id');
|
||||
$aargs = array('format' => 'atom');
|
||||
if (!empty($id)) {
|
||||
$aargs['id'] = $id;
|
||||
}
|
||||
$self = $this->getSelfUri('ApiTimelineUser', $aargs);
|
||||
$atom->setId($self);
|
||||
$atom->setSelfLink($self);
|
||||
|
||||
$atom->addEntryFromNotices($this->notices);
|
||||
|
||||
$this->raw($atom->getString());
|
||||
|
||||
break;
|
||||
|
|
|
@ -301,6 +301,10 @@ class AvatarsettingsAction extends AccountSettingsAction
|
|||
$this->showForm($e->getMessage());
|
||||
return;
|
||||
}
|
||||
if ($imagefile === null) {
|
||||
$this->showForm(_('No file uploaded.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$cur = common_current_user();
|
||||
|
||||
|
|
|
@ -162,7 +162,15 @@ class DeleteuserAction extends ProfileFormAction
|
|||
function handlePost()
|
||||
{
|
||||
if (Event::handle('StartDeleteUser', array($this, $this->user))) {
|
||||
$this->user->delete();
|
||||
// Mark the account as deleted and shove low-level deletion tasks
|
||||
// to background queues. Removing a lot of posts can take a while...
|
||||
if (!$this->user->hasRole(Profile_role::DELETED)) {
|
||||
$this->user->grantRole(Profile_role::DELETED);
|
||||
}
|
||||
|
||||
$qm = QueueManager::get();
|
||||
$qm->enqueue($this->user, 'deluser');
|
||||
|
||||
Event::handle('EndDeleteUser', array($this, $this->user));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* @link http://status.net/
|
||||
*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, 2009, StatusNet, Inc.
|
||||
* Copyright (C) 2008-2010, 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
|
||||
|
@ -168,15 +168,29 @@ class DocAction extends Action
|
|||
|
||||
function getFilename()
|
||||
{
|
||||
$localDef = null;
|
||||
$local = null;
|
||||
|
||||
$site = StatusNet::currentSite();
|
||||
|
||||
if (!empty($site) && file_exists(INSTALLDIR.'/local/doc-src/'.$site.'/'.$this->title)) {
|
||||
$localDef = INSTALLDIR.'/local/doc-src/'.$site.'/'.$this->title;
|
||||
|
||||
$local = glob(INSTALLDIR.'/local/doc-src/'.$site.'/'.$this->title.'.*');
|
||||
if ($local === false) {
|
||||
// Some systems return false, others array(), if dir didn't exist.
|
||||
$local = array();
|
||||
}
|
||||
} else {
|
||||
if (file_exists(INSTALLDIR.'/local/doc-src/'.$this->title)) {
|
||||
$localDef = INSTALLDIR.'/local/doc-src/'.$this->title;
|
||||
}
|
||||
|
||||
$local = glob(INSTALLDIR.'/local/doc-src/'.$this->title.'.*');
|
||||
if ($local === false) {
|
||||
// Some systems return false, others array(), if dir didn't exist.
|
||||
$local = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (count($local) || isset($localDef)) {
|
||||
return $this->negotiateLanguage($local, $localDef);
|
||||
|
|
|
@ -251,7 +251,7 @@ class FoafAction extends Action
|
|||
}
|
||||
|
||||
// Their account
|
||||
$this->elementStart('holdsAccount');
|
||||
$this->elementStart('account');
|
||||
$this->elementStart('OnlineAccount', $attr);
|
||||
if ($service) {
|
||||
$this->element('accountServiceHomepage', array('rdf:resource' =>
|
||||
|
@ -306,7 +306,7 @@ class FoafAction extends Action
|
|||
}
|
||||
|
||||
$this->elementEnd('OnlineAccount');
|
||||
$this->elementEnd('holdsAccount');
|
||||
$this->elementEnd('account');
|
||||
|
||||
return $person;
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@ class FoafGroupAction extends Action
|
|||
{
|
||||
$this->elementStart('Agent', array('rdf:about' => $uri));
|
||||
$this->element('nick', null, $details['nickname']);
|
||||
$this->elementStart('holdsAccount');
|
||||
$this->elementStart('account');
|
||||
$this->elementStart('sioc:User', array('rdf:about'=>$uri.'#acct'));
|
||||
$this->elementStart('sioc:has_function');
|
||||
$this->elementStart('statusnet:GroupAdminRole');
|
||||
|
@ -154,7 +154,7 @@ class FoafGroupAction extends Action
|
|||
$this->elementEnd('statusnet:GroupAdminRole');
|
||||
$this->elementEnd('sioc:has_function');
|
||||
$this->elementEnd('sioc:User');
|
||||
$this->elementEnd('holdsAccount');
|
||||
$this->elementEnd('account');
|
||||
$this->elementEnd('Agent');
|
||||
}
|
||||
else
|
||||
|
|
|
@ -126,6 +126,8 @@ class OtpAction extends Action
|
|||
$this->lt->delete();
|
||||
$this->lt = null;
|
||||
|
||||
common_real_login(true);
|
||||
|
||||
if ($this->rememberme) {
|
||||
common_rememberme($this->user);
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ class PublictagcloudAction extends Action
|
|||
$cutoff = sprintf("notice_tag.created > '%s'",
|
||||
common_sql_date(time() - common_config('tag', 'cutoff')));
|
||||
$tags->selectAdd($calc . ' as weight');
|
||||
$tags->addWhere($cutoff);
|
||||
$tags->whereAdd($cutoff);
|
||||
$tags->groupBy('tag');
|
||||
$tags->orderBy('weight DESC');
|
||||
|
||||
|
|
|
@ -262,10 +262,20 @@ class RecoverpasswordAction extends Action
|
|||
# See if it's an unconfirmed email address
|
||||
|
||||
if (!$user) {
|
||||
$confirm_email = Confirm_address::staticGet('address', common_canonical_email($nore));
|
||||
if ($confirm_email && $confirm_email->address_type == 'email') {
|
||||
// Warning: it may actually be legit to have multiple folks
|
||||
// who have claimed, but not yet confirmed, the same address.
|
||||
// We'll only send to the first one that comes up.
|
||||
$confirm_email = new Confirm_address();
|
||||
$confirm_email->address = common_canonical_email($nore);
|
||||
$confirm_email->address_type = 'email';
|
||||
$confirm_email->find();
|
||||
if ($confirm_email->fetch()) {
|
||||
$user = User::staticGet($confirm_email->user_id);
|
||||
} else {
|
||||
$confirm_email = null;
|
||||
}
|
||||
} else {
|
||||
$confirm_email = null;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
|
@ -276,9 +286,11 @@ class RecoverpasswordAction extends Action
|
|||
# Try to get an unconfirmed email address if they used a user name
|
||||
|
||||
if (!$user->email && !$confirm_email) {
|
||||
$confirm_email = Confirm_address::staticGet('user_id', $user->id);
|
||||
if ($confirm_email && $confirm_email->address_type != 'email') {
|
||||
# Skip non-email confirmations
|
||||
$confirm_email = new Confirm_address();
|
||||
$confirm_email->user_id = $user->id;
|
||||
$confirm_email->address_type = 'email';
|
||||
$confirm_email->find();
|
||||
if (!$confirm_email->fetch()) {
|
||||
$confirm_email = null;
|
||||
}
|
||||
}
|
||||
|
@ -294,7 +306,7 @@ class RecoverpasswordAction extends Action
|
|||
$confirm->code = common_confirmation_code(128);
|
||||
$confirm->address_type = 'recover';
|
||||
$confirm->user_id = $user->id;
|
||||
$confirm->address = (isset($user->email)) ? $user->email : $confirm_email->address;
|
||||
$confirm->address = (!empty($user->email)) ? $user->email : $confirm_email->address;
|
||||
|
||||
if (!$confirm->insert()) {
|
||||
common_log_db_error($confirm, 'INSERT', __FILE__);
|
||||
|
@ -319,7 +331,8 @@ class RecoverpasswordAction extends Action
|
|||
$body .= common_config('site', 'name');
|
||||
$body .= "\n";
|
||||
|
||||
mail_to_user($user, _('Password recovery requested'), $body, $confirm->address);
|
||||
$headers = _mail_prepare_headers('recoverpassword', $user->nickname, $user->nickname);
|
||||
mail_to_user($user, _('Password recovery requested'), $body, $headers, $confirm->address);
|
||||
|
||||
$this->mode = 'sent';
|
||||
$this->msg = _('Instructions for recovering your password ' .
|
||||
|
|
|
@ -221,7 +221,8 @@ class ShowgroupAction extends GroupDesignAction
|
|||
|
||||
function showGroupProfile()
|
||||
{
|
||||
$this->elementStart('div', 'entity_profile vcard author');
|
||||
$this->elementStart('div', array('id' => 'i',
|
||||
'class' => 'entity_profile vcard author'));
|
||||
|
||||
$this->element('h2', null, _('Group profile'));
|
||||
|
||||
|
|
|
@ -103,11 +103,6 @@ class ShownoticeAction extends OwnerDesignAction
|
|||
|
||||
$this->user = User::staticGet('id', $this->profile->id);
|
||||
|
||||
if ($this->notice->is_local == Notice::REMOTE_OMB) {
|
||||
common_redirect($this->notice->uri);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
|
||||
|
||||
return true;
|
||||
|
@ -198,13 +193,20 @@ class ShownoticeAction extends OwnerDesignAction
|
|||
|
||||
if ($this->notice->is_local == Notice::REMOTE_OMB) {
|
||||
if (!empty($this->notice->url)) {
|
||||
common_redirect($this->notice->url, 301);
|
||||
$target = $this->notice->url;
|
||||
} else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) {
|
||||
common_redirect($this->notice->uri, 301);
|
||||
}
|
||||
// Old OMB posts saved the remote URL only into the URI field.
|
||||
$target = $this->notice->uri;
|
||||
} else {
|
||||
$this->showPage();
|
||||
// Shouldn't happen.
|
||||
$target = false;
|
||||
}
|
||||
if ($target && $target != $this->selfUrl()) {
|
||||
common_redirect($target, 301);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->showPage();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -161,8 +161,8 @@ class SiteadminpanelAction extends AdminPanelAction
|
|||
|
||||
// Validate text limit
|
||||
|
||||
if (!Validate::number($values['site']['textlimit'], array('min' => 140))) {
|
||||
$this->clientError(_("Minimum text limit is 140 characters."));
|
||||
if (!Validate::number($values['site']['textlimit'], array('min' => 0))) {
|
||||
$this->clientError(_("Minimum text limit is 0 (unlimited)."));
|
||||
}
|
||||
|
||||
// Validate dupe limit
|
||||
|
|
|
@ -93,7 +93,7 @@ class SitenoticeadminpanelAction extends AdminPanelAction
|
|||
// assert(all values are valid);
|
||||
// This throws an exception on validation errors
|
||||
|
||||
$this->validate(&$siteNotice);
|
||||
$this->validate($siteNotice);
|
||||
|
||||
$config = new Config();
|
||||
|
||||
|
|
|
@ -67,7 +67,14 @@ class File extends Memcached_DataObject
|
|||
return $att;
|
||||
}
|
||||
|
||||
function saveNew($redir_data, $given_url) {
|
||||
/**
|
||||
* Save a new file record.
|
||||
*
|
||||
* @param array $redir_data lookup data eg from File_redirection::where()
|
||||
* @param string $given_url
|
||||
* @return File
|
||||
*/
|
||||
function saveNew(array $redir_data, $given_url) {
|
||||
$x = new File;
|
||||
$x->url = $given_url;
|
||||
if (!empty($redir_data['protected'])) $x->protected = $redir_data['protected'];
|
||||
|
@ -77,19 +84,36 @@ class File extends Memcached_DataObject
|
|||
if (isset($redir_data['time']) && $redir_data['time'] > 0) $x->date = intval($redir_data['time']);
|
||||
$file_id = $x->insert();
|
||||
|
||||
$x->saveOembed($redir_data, $given_url);
|
||||
return $x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save embedding information for this file, if applicable.
|
||||
*
|
||||
* Normally this won't need to be called manually, as File::saveNew()
|
||||
* takes care of it.
|
||||
*
|
||||
* @param array $redir_data lookup data eg from File_redirection::where()
|
||||
* @param string $given_url
|
||||
* @return boolean success
|
||||
*/
|
||||
public function saveOembed($redir_data, $given_url)
|
||||
{
|
||||
if (isset($redir_data['type'])
|
||||
&& (('text/html' === substr($redir_data['type'], 0, 9) || 'application/xhtml+xml' === substr($redir_data['type'], 0, 21)))
|
||||
&& ($oembed_data = File_oembed::_getOembed($given_url))) {
|
||||
|
||||
$fo = File_oembed::staticGet('file_id', $file_id);
|
||||
$fo = File_oembed::staticGet('file_id', $this->id);
|
||||
|
||||
if (empty($fo)) {
|
||||
File_oembed::saveNew($oembed_data, $file_id);
|
||||
File_oembed::saveNew($oembed_data, $this->id);
|
||||
return true;
|
||||
} else {
|
||||
common_log(LOG_WARNING, "Strangely, a File_oembed object exists for new file $file_id", __FILE__);
|
||||
}
|
||||
}
|
||||
return $x;
|
||||
return false;
|
||||
}
|
||||
|
||||
function processNew($given_url, $notice_id=null) {
|
||||
|
@ -105,6 +129,7 @@ class File extends Memcached_DataObject
|
|||
$redir_url = $redir_data['url'];
|
||||
} elseif (is_string($redir_data)) {
|
||||
$redir_url = $redir_data;
|
||||
$redir_data = array();
|
||||
} else {
|
||||
throw new ServerException("Can't process url '$given_url'");
|
||||
}
|
||||
|
@ -260,7 +285,7 @@ class File extends Memcached_DataObject
|
|||
$enclosure->mimetype=$this->mimetype;
|
||||
|
||||
if(! isset($this->filename)){
|
||||
$notEnclosureMimeTypes = array('text/html','application/xhtml+xml');
|
||||
$notEnclosureMimeTypes = array(null,'text/html','application/xhtml+xml');
|
||||
$mimetype = strtolower($this->mimetype);
|
||||
$semicolon = strpos($mimetype,';');
|
||||
if($semicolon){
|
||||
|
|
|
@ -81,6 +81,12 @@ class File_oembed extends Memcached_DataObject
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save embedding info for a new file.
|
||||
*
|
||||
* @param object $data Services_oEmbed_Object_*
|
||||
* @param int $file_id
|
||||
*/
|
||||
function saveNew($data, $file_id) {
|
||||
$file_oembed = new File_oembed;
|
||||
$file_oembed->file_id = $file_id;
|
||||
|
|
|
@ -58,24 +58,30 @@ class File_redirection extends Memcached_DataObject
|
|||
return $request;
|
||||
}
|
||||
|
||||
function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) {
|
||||
/**
|
||||
* Check if this URL is a redirect and return redir info.
|
||||
*
|
||||
* Most code should call File_redirection::where instead, to check if we
|
||||
* already know that redirection and avoid extra hits to the web.
|
||||
*
|
||||
* The URL is hit and any redirects are followed, up to 10 levels or until
|
||||
* a protected URL is reached.
|
||||
*
|
||||
* @param string $in_url
|
||||
* @return mixed one of:
|
||||
* string - target URL, if this is a direct link or can't be followed
|
||||
* array - redirect info if this is an *unknown* redirect:
|
||||
* associative array with the following elements:
|
||||
* code: HTTP status code
|
||||
* redirects: count of redirects followed
|
||||
* url: URL string of final target
|
||||
* type (optional): MIME type from Content-Type header
|
||||
* size (optional): byte size from Content-Length header
|
||||
* time (optional): timestamp from Last-Modified header
|
||||
*/
|
||||
public function lookupWhere($short_url, $redirs = 10, $protected = false) {
|
||||
if ($redirs < 0) return false;
|
||||
|
||||
// let's see if we know this...
|
||||
$a = File::staticGet('url', $short_url);
|
||||
|
||||
if (!empty($a)) {
|
||||
// this is a direct link to $a->url
|
||||
return $a->url;
|
||||
} else {
|
||||
$b = File_redirection::staticGet('url', $short_url);
|
||||
if (!empty($b)) {
|
||||
// this is a redirect to $b->file_id
|
||||
$a = File::staticGet('id', $b->file_id);
|
||||
return $a->url;
|
||||
}
|
||||
}
|
||||
|
||||
if(strpos($short_url,'://') === false){
|
||||
return $short_url;
|
||||
}
|
||||
|
@ -93,12 +99,13 @@ class File_redirection extends Memcached_DataObject
|
|||
}
|
||||
} catch (Exception $e) {
|
||||
// Invalid URL or failure to reach server
|
||||
common_log(LOG_ERR, "Error while following redirects for $short_url: " . $e->getMessage());
|
||||
return $short_url;
|
||||
}
|
||||
|
||||
if ($response->getRedirectCount() && File::isProtected($response->getUrl())) {
|
||||
// Bump back up the redirect chain until we find a non-protected URL
|
||||
return self::_redirectWhere_imp($short_url, $response->getRedirectCount() - 1, true);
|
||||
return self::lookupWhere($short_url, $response->getRedirectCount() - 1, true);
|
||||
}
|
||||
|
||||
$ret = array('code' => $response->getStatus()
|
||||
|
@ -115,11 +122,60 @@ class File_redirection extends Memcached_DataObject
|
|||
return $ret;
|
||||
}
|
||||
|
||||
function where($in_url) {
|
||||
$ret = File_redirection::_redirectWhere_imp($in_url);
|
||||
/**
|
||||
* Check if this URL is a redirect and return redir info.
|
||||
* If a File record is present for this URL, it is not considered a redirect.
|
||||
* If a File_redirection record is present for this URL, the recorded target is returned.
|
||||
*
|
||||
* If no File or File_redirect record is present, the URL is hit and any
|
||||
* redirects are followed, up to 10 levels or until a protected URL is
|
||||
* reached.
|
||||
*
|
||||
* @param string $in_url
|
||||
* @return mixed one of:
|
||||
* string - target URL, if this is a direct link or a known redirect
|
||||
* array - redirect info if this is an *unknown* redirect:
|
||||
* associative array with the following elements:
|
||||
* code: HTTP status code
|
||||
* redirects: count of redirects followed
|
||||
* url: URL string of final target
|
||||
* type (optional): MIME type from Content-Type header
|
||||
* size (optional): byte size from Content-Length header
|
||||
* time (optional): timestamp from Last-Modified header
|
||||
*/
|
||||
public function where($in_url) {
|
||||
// let's see if we know this...
|
||||
$a = File::staticGet('url', $in_url);
|
||||
|
||||
if (!empty($a)) {
|
||||
// this is a direct link to $a->url
|
||||
return $a->url;
|
||||
} else {
|
||||
$b = File_redirection::staticGet('url', $in_url);
|
||||
if (!empty($b)) {
|
||||
// this is a redirect to $b->file_id
|
||||
$a = File::staticGet('id', $b->file_id);
|
||||
return $a->url;
|
||||
}
|
||||
}
|
||||
|
||||
$ret = File_redirection::lookupWhere($in_url);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten a URL with the current user's configured shortening
|
||||
* options, if applicable.
|
||||
*
|
||||
* If it cannot be shortened or the "short" URL is longer than the
|
||||
* original, the original is returned.
|
||||
*
|
||||
* If the referenced item has not been seen before, embedding data
|
||||
* may be saved.
|
||||
*
|
||||
* @param string $long_url
|
||||
* @return string
|
||||
*/
|
||||
function makeShort($long_url) {
|
||||
|
||||
$canon = File_redirection::_canonUrl($long_url);
|
||||
|
@ -141,12 +197,21 @@ class File_redirection extends Memcached_DataObject
|
|||
// store it
|
||||
$file = File::staticGet('url', $long_url);
|
||||
if (empty($file)) {
|
||||
// Check if the target URL is itself a redirect...
|
||||
$redir_data = File_redirection::where($long_url);
|
||||
if (is_array($redir_data)) {
|
||||
// We haven't seen the target URL before.
|
||||
// Save file and embedding data about it!
|
||||
$file = File::saveNew($redir_data, $long_url);
|
||||
$file_id = $file->id;
|
||||
if (!empty($redir_data['oembed']['json'])) {
|
||||
File_oembed::saveNew($redir_data['oembed']['json'], $file_id);
|
||||
}
|
||||
} else if (is_string($redir_data)) {
|
||||
// The file is a known redirect target.
|
||||
$file = File::staticGet('url', $redir_data);
|
||||
$file_id = $file->id;
|
||||
}
|
||||
} else {
|
||||
$file_id = $file->id;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ class Foreign_user extends Memcached_DataObject
|
|||
|
||||
function updateKeys(&$orig)
|
||||
{
|
||||
$this->_connect();
|
||||
$parts = array();
|
||||
foreach (array('id', 'service', 'uri', 'nickname') as $k) {
|
||||
if (strcmp($this->$k, $orig->$k) != 0) {
|
||||
|
|
|
@ -119,6 +119,9 @@ class Notice extends Memcached_DataObject
|
|||
// NOTE: we don't clear queue items
|
||||
|
||||
$result = parent::delete();
|
||||
|
||||
$this->blowOnDelete();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -421,6 +424,18 @@ class Notice extends Memcached_DataObject
|
|||
$profile->blowNoticeCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache entries related to this notice at delete time.
|
||||
* Necessary to avoid breaking paging on public, profile timelines.
|
||||
*/
|
||||
function blowOnDelete()
|
||||
{
|
||||
$this->blowOnInsert();
|
||||
|
||||
self::blow('profile:notice_ids:%d;last', $this->profile_id);
|
||||
self::blow('public;last');
|
||||
}
|
||||
|
||||
/** save all urls in the notice to the db
|
||||
*
|
||||
* follow redirects and save all available file information
|
||||
|
@ -589,7 +604,6 @@ class Notice extends Memcached_DataObject
|
|||
array(),
|
||||
'public',
|
||||
$offset, $limit, $since_id, $max_id);
|
||||
|
||||
return Notice::getStreamByIds($ids);
|
||||
}
|
||||
|
||||
|
@ -1128,6 +1142,7 @@ class Notice extends Memcached_DataObject
|
|||
|
||||
if ($source) {
|
||||
$xs->elementStart('source');
|
||||
$xs->element('id', null, $profile->profileurl);
|
||||
$xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
|
||||
$xs->element('link', array('href' => $profile->profileurl));
|
||||
$user = User::staticGet('id', $profile->id);
|
||||
|
@ -1143,13 +1158,14 @@ class Notice extends Memcached_DataObject
|
|||
}
|
||||
|
||||
$xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE));
|
||||
$xs->element('updated', null, common_date_w3dtf($this->created));
|
||||
}
|
||||
|
||||
if ($source) {
|
||||
$xs->elementEnd('source');
|
||||
}
|
||||
|
||||
$xs->element('title', null, $this->content);
|
||||
$xs->element('title', null, common_xml_safe_str($this->content));
|
||||
|
||||
if ($author) {
|
||||
$xs->raw($profile->asAtomAuthor());
|
||||
|
@ -1225,7 +1241,11 @@ class Notice extends Memcached_DataObject
|
|||
}
|
||||
}
|
||||
|
||||
$xs->element('content', array('type' => 'html'), $this->rendered);
|
||||
$xs->element(
|
||||
'content',
|
||||
array('type' => 'html'),
|
||||
common_xml_safe_str($this->rendered)
|
||||
);
|
||||
|
||||
$tag = new Notice_tag();
|
||||
$tag->notice_id = $this->id;
|
||||
|
|
|
@ -147,14 +147,16 @@ class Profile extends Memcached_DataObject
|
|||
return ($this->fullname) ? $this->fullname : $this->nickname;
|
||||
}
|
||||
|
||||
# Get latest notice on or before date; default now
|
||||
function getCurrentNotice($dt=null)
|
||||
/**
|
||||
* Get the most recent notice posted by this user, if any.
|
||||
*
|
||||
* @return mixed Notice or null
|
||||
*/
|
||||
function getCurrentNotice()
|
||||
{
|
||||
$notice = new Notice();
|
||||
$notice->profile_id = $this->id;
|
||||
if ($dt) {
|
||||
$notice->whereAdd('created < "' . $dt . '"');
|
||||
}
|
||||
// @fixme change this to sort on notice.id only when indexes are updated
|
||||
$notice->orderBy('created DESC, notice.id DESC');
|
||||
$notice->limit(1);
|
||||
if ($notice->find(true)) {
|
||||
|
@ -730,6 +732,9 @@ class Profile extends Memcached_DataObject
|
|||
function hasRight($right)
|
||||
{
|
||||
$result = false;
|
||||
if ($this->hasRole(Profile_role::DELETED)) {
|
||||
return false;
|
||||
}
|
||||
if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
|
||||
switch ($right)
|
||||
{
|
||||
|
|
|
@ -53,6 +53,7 @@ class Profile_role extends Memcached_DataObject
|
|||
const ADMINISTRATOR = 'administrator';
|
||||
const SANDBOXED = 'sandboxed';
|
||||
const SILENCED = 'silenced';
|
||||
const DELETED = 'deleted'; // Pending final deletion of notices...
|
||||
|
||||
public static function isValid($role)
|
||||
{
|
||||
|
|
|
@ -42,6 +42,25 @@ class Safe_DataObject extends DB_DataObject
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic function called at clone() time.
|
||||
*
|
||||
* We use this to drop connection with some global resources.
|
||||
* This supports the fairly common pattern where individual
|
||||
* items being read in a loop via a single object are cloned
|
||||
* for individual processing, then fall out of scope when the
|
||||
* loop comes around again.
|
||||
*
|
||||
* As that triggers the destructor, we want to make sure that
|
||||
* the original object doesn't have its database result killed.
|
||||
* It will still be freed properly when the original object
|
||||
* gets destroyed.
|
||||
*/
|
||||
function __clone()
|
||||
{
|
||||
$this->_DB_resultid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic function called at serialize() time.
|
||||
*
|
||||
|
@ -77,6 +96,30 @@ class Safe_DataObject extends DB_DataObject
|
|||
$this->_link_loaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic function called when someone attempts to call a method
|
||||
* that doesn't exist. DB_DataObject uses this to implement
|
||||
* setters and getters for fields, but neglects to throw an error
|
||||
* when you just misspell an actual method name. This leads to
|
||||
* silent failures which can cause all kinds of havoc.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $params
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
function __call($method, $params)
|
||||
{
|
||||
$return = null;
|
||||
// Yes, that's _call with one underscore, which does the
|
||||
// actual implementation.
|
||||
if ($this->_call($method, $params, $return)) {
|
||||
return $return;
|
||||
} else {
|
||||
throw new Exception('Call to undefined method ' .
|
||||
get_class($this) . '::' . $method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Work around memory-leak bugs...
|
||||
|
|
|
@ -62,6 +62,14 @@ class Subscription extends Memcached_DataObject
|
|||
|
||||
static function start($subscriber, $other)
|
||||
{
|
||||
// @fixme should we enforce this as profiles in callers instead?
|
||||
if ($subscriber instanceof User) {
|
||||
$subscriber = $subscriber->getProfile();
|
||||
}
|
||||
if ($other instanceof User) {
|
||||
$other = $other->getProfile();
|
||||
}
|
||||
|
||||
if (!$subscriber->hasRight(Right::SUBSCRIBE)) {
|
||||
throw new Exception(_('You have been banned from subscribing.'));
|
||||
}
|
||||
|
@ -75,11 +83,46 @@ class Subscription extends Memcached_DataObject
|
|||
}
|
||||
|
||||
if (Event::handle('StartSubscribe', array($subscriber, $other))) {
|
||||
$sub = self::saveNew($subscriber->id, $other->id);
|
||||
$sub->notify();
|
||||
|
||||
self::blow('user:notices_with_friends:%d', $subscriber->id);
|
||||
|
||||
$subscriber->blowSubscriptionCount();
|
||||
$other->blowSubscriberCount();
|
||||
|
||||
$otherUser = User::staticGet('id', $other->id);
|
||||
|
||||
if (!empty($otherUser) &&
|
||||
$otherUser->autosubscribe &&
|
||||
!self::exists($other, $subscriber) &&
|
||||
!$subscriber->hasBlocked($other)) {
|
||||
|
||||
try {
|
||||
self::start($other, $subscriber);
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
Event::handle('EndSubscribe', array($subscriber, $other));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Low-level subscription save.
|
||||
* Outside callers should use Subscription::start()
|
||||
*/
|
||||
protected function saveNew($subscriber_id, $other_id)
|
||||
{
|
||||
$sub = new Subscription();
|
||||
|
||||
$sub->subscriber = $subscriber->id;
|
||||
$sub->subscribed = $other->id;
|
||||
$sub->subscriber = $subscriber_id;
|
||||
$sub->subscribed = $other_id;
|
||||
$sub->jabber = 1;
|
||||
$sub->sms = 1;
|
||||
$sub->created = common_sql_now();
|
||||
|
||||
$result = $sub->insert();
|
||||
|
@ -89,40 +132,7 @@ class Subscription extends Memcached_DataObject
|
|||
throw new Exception(_('Could not save subscription.'));
|
||||
}
|
||||
|
||||
$sub->notify();
|
||||
|
||||
self::blow('user:notices_with_friends:%d', $subscriber->id);
|
||||
|
||||
$subscriber->blowSubscriptionsCount();
|
||||
$other->blowSubscribersCount();
|
||||
|
||||
$otherUser = User::staticGet('id', $other->id);
|
||||
|
||||
if (!empty($otherUser) &&
|
||||
$otherUser->autosubscribe &&
|
||||
!self::exists($other, $subscriber) &&
|
||||
!$subscriber->hasBlocked($other)) {
|
||||
|
||||
$auto = new Subscription();
|
||||
|
||||
$auto->subscriber = $subscriber->id;
|
||||
$auto->subscribed = $other->id;
|
||||
$auto->created = common_sql_now();
|
||||
|
||||
$result = $auto->insert();
|
||||
|
||||
if (!$result) {
|
||||
common_log_db_error($auto, 'INSERT', __FILE__);
|
||||
throw new Exception(_('Could not save subscription.'));
|
||||
}
|
||||
|
||||
$auto->notify();
|
||||
}
|
||||
|
||||
Event::handle('EndSubscribe', array($subscriber, $other));
|
||||
}
|
||||
|
||||
return true;
|
||||
return $sub;
|
||||
}
|
||||
|
||||
function notify()
|
||||
|
@ -203,8 +213,8 @@ class Subscription extends Memcached_DataObject
|
|||
|
||||
self::blow('user:notices_with_friends:%d', $subscriber->id);
|
||||
|
||||
$subscriber->blowSubscriptionsCount();
|
||||
$other->blowSubscribersCount();
|
||||
$subscriber->blowSubscriptionCount();
|
||||
$other->blowSubscriberCount();
|
||||
|
||||
Event::handle('EndUnsubscribe', array($subscriber, $other));
|
||||
}
|
||||
|
|
|
@ -70,7 +70,11 @@ class User extends Memcached_DataObject
|
|||
|
||||
function getProfile()
|
||||
{
|
||||
return Profile::staticGet('id', $this->id);
|
||||
$profile = Profile::staticGet('id', $this->id);
|
||||
if (empty($profile)) {
|
||||
throw new UserNoProfileException($this);
|
||||
}
|
||||
return $profile;
|
||||
}
|
||||
|
||||
function isSubscribed($other)
|
||||
|
@ -82,6 +86,7 @@ class User extends Memcached_DataObject
|
|||
|
||||
function updateKeys(&$orig)
|
||||
{
|
||||
$this->_connect();
|
||||
$parts = array();
|
||||
foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) {
|
||||
if (strcmp($this->$k, $orig->$k) != 0) {
|
||||
|
@ -127,13 +132,15 @@ class User extends Memcached_DataObject
|
|||
return !in_array($nickname, $blacklist);
|
||||
}
|
||||
|
||||
function getCurrentNotice($dt=null)
|
||||
/**
|
||||
* Get the most recent notice posted by this user, if any.
|
||||
*
|
||||
* @return mixed Notice or null
|
||||
*/
|
||||
function getCurrentNotice()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
if (!$profile) {
|
||||
return null;
|
||||
}
|
||||
return $profile->getCurrentNotice($dt);
|
||||
return $profile->getCurrentNotice();
|
||||
}
|
||||
|
||||
function getCarrier()
|
||||
|
@ -141,19 +148,12 @@ class User extends Memcached_DataObject
|
|||
return Sms_carrier::staticGet('id', $this->carrier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use Subscription::start($sub, $other);
|
||||
*/
|
||||
function subscribeTo($other)
|
||||
{
|
||||
$sub = new Subscription();
|
||||
$sub->subscriber = $this->id;
|
||||
$sub->subscribed = $other->id;
|
||||
|
||||
$sub->created = common_sql_now(); // current time
|
||||
|
||||
if (!$sub->insert()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return Subscription::start($this->getProfile(), $other);
|
||||
}
|
||||
|
||||
function hasBlocked($other)
|
||||
|
@ -334,17 +334,7 @@ class User extends Memcached_DataObject
|
|||
common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick),
|
||||
__FILE__);
|
||||
} else {
|
||||
$defsub = new Subscription();
|
||||
$defsub->subscriber = $user->id;
|
||||
$defsub->subscribed = $defuser->id;
|
||||
$defsub->created = $user->created;
|
||||
|
||||
$result = $defsub->insert();
|
||||
|
||||
if (!$result) {
|
||||
common_log_db_error($defsub, 'INSERT', __FILE__);
|
||||
return false;
|
||||
}
|
||||
Subscription::start($user, $defuser);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,22 +450,14 @@ class User extends Memcached_DataObject
|
|||
|
||||
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) {
|
||||
$profile = $this->getProfile();
|
||||
if (!$profile) {
|
||||
return null;
|
||||
} else {
|
||||
return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id);
|
||||
}
|
||||
}
|
||||
|
||||
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
if (!$profile) {
|
||||
return null;
|
||||
} else {
|
||||
return $profile->getNotices($offset, $limit, $since_id, $before_id);
|
||||
}
|
||||
}
|
||||
|
||||
function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE, $own=false)
|
||||
{
|
||||
|
@ -615,14 +597,12 @@ class User extends Memcached_DataObject
|
|||
function getSubscriptions($offset=0, $limit=null)
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
assert(!empty($profile));
|
||||
return $profile->getSubscriptions($offset, $limit);
|
||||
}
|
||||
|
||||
function getSubscribers($offset=0, $limit=null)
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
assert(!empty($profile));
|
||||
return $profile->getSubscribers($offset, $limit);
|
||||
}
|
||||
|
||||
|
@ -686,9 +666,7 @@ class User extends Memcached_DataObject
|
|||
function delete()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
if ($profile) {
|
||||
$profile->delete();
|
||||
}
|
||||
|
||||
$related = array('Fave',
|
||||
'Confirm_address',
|
||||
|
|
|
@ -371,16 +371,15 @@ class User_group extends Memcached_DataObject
|
|||
|
||||
if ($source) {
|
||||
$xs->elementStart('source');
|
||||
$xs->element('id', null, $this->permalink());
|
||||
$xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
|
||||
$xs->element('link', array('href' => $this->permalink()));
|
||||
}
|
||||
|
||||
if ($source) {
|
||||
$xs->element('updated', null, $this->modified);
|
||||
$xs->elementEnd('source');
|
||||
}
|
||||
|
||||
$xs->element('title', null, $this->nickname);
|
||||
$xs->element('summary', null, $this->description);
|
||||
$xs->element('summary', null, common_xml_safe_str($this->description));
|
||||
|
||||
$xs->element('link', array('rel' => 'alternate',
|
||||
'href' => $this->permalink()));
|
||||
|
@ -390,7 +389,11 @@ class User_group extends Memcached_DataObject
|
|||
$xs->element('published', null, common_date_w3dtf($this->created));
|
||||
$xs->element('updated', null, common_date_w3dtf($this->modified));
|
||||
|
||||
$xs->element('content', array('type' => 'html'), $this->description);
|
||||
$xs->element(
|
||||
'content',
|
||||
array('type' => 'html'),
|
||||
common_xml_safe_str($this->description)
|
||||
);
|
||||
|
||||
$xs->elementEnd('entry');
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ $config['sphinx']['port'] = 3312;
|
|||
//
|
||||
// $config['twitterimport']['enabled'] = true;
|
||||
|
||||
// Twitter OAuth settings
|
||||
// Twitter OAuth settings. Documentation is at http://apiwiki.twitter.com/OAuth-FAQ
|
||||
// $config['twitter']['consumer_key'] = 'YOURKEY';
|
||||
// $config['twitter']['consumer_secret'] = 'YOURSECRET';
|
||||
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
|
||||
RewriteBase /mublog/
|
||||
|
||||
## Uncomment these if having trouble with API authentication
|
||||
## when PHP is running in CGI or FastCGI mode.
|
||||
#RewriteCond %{HTTP:Authorization} ^(.*)
|
||||
#RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule (.*) index.php?p=$1 [L,QSA]
|
||||
|
|
35
index.php
35
index.php
|
@ -37,6 +37,8 @@ define('INSTALLDIR', dirname(__FILE__));
|
|||
define('STATUSNET', true);
|
||||
define('LACONICA', true); // compatibility
|
||||
|
||||
require_once INSTALLDIR . '/lib/common.php';
|
||||
|
||||
$user = null;
|
||||
$action = null;
|
||||
|
||||
|
@ -66,15 +68,13 @@ function getPath($req)
|
|||
*/
|
||||
function handleError($error)
|
||||
{
|
||||
try {
|
||||
|
||||
if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
|
||||
return;
|
||||
}
|
||||
|
||||
$logmsg = "PEAR error: " . $error->getMessage();
|
||||
if ($error instanceof PEAR_Exception && common_config('site', 'logdebug')) {
|
||||
$logmsg .= " : ". $error->toText();
|
||||
if (common_config('site', 'logdebug')) {
|
||||
$logmsg .= " : ". $error->getDebugInfo();
|
||||
}
|
||||
// DB queries often end up with a lot of newlines; merge to a single line
|
||||
// for easier grepability...
|
||||
|
@ -83,21 +83,14 @@ function handleError($error)
|
|||
|
||||
// @fixme backtrace output should be consistent with exception handling
|
||||
if (common_config('site', 'logdebug')) {
|
||||
$bt = $error->getTrace();
|
||||
$bt = $error->getBacktrace();
|
||||
foreach ($bt as $n => $line) {
|
||||
common_log(LOG_ERR, formatBacktraceLine($n, $line));
|
||||
}
|
||||
}
|
||||
if ($error instanceof DB_DataObject_Error
|
||||
|| $error instanceof DB_Error
|
||||
|| ($error instanceof PEAR_Exception && $error->getCode() == -24)
|
||||
) {
|
||||
//If we run into a DB error, assume we can't connect to the DB at all
|
||||
//so set the current user to null, so we don't try to access the DB
|
||||
//while rendering the error page.
|
||||
global $_cur;
|
||||
$_cur = null;
|
||||
|
||||
$msg = sprintf(
|
||||
_(
|
||||
'The database for %s isn\'t responding correctly, '.
|
||||
|
@ -118,17 +111,9 @@ function handleError($error)
|
|||
|
||||
$dac = new DBErrorAction($msg, 500);
|
||||
$dac->showPage();
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo _('An error occurred.');
|
||||
}
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
set_exception_handler('handleError');
|
||||
|
||||
require_once INSTALLDIR . '/lib/common.php';
|
||||
|
||||
/**
|
||||
* Format a backtrace line for debug output roughly like debug_print_backtrace() does.
|
||||
* Exceptions already have this built in, but PEAR error objects just give us the array.
|
||||
|
@ -200,7 +185,7 @@ function checkMirror($action_obj, $args)
|
|||
|
||||
function isLoginAction($action)
|
||||
{
|
||||
static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp');
|
||||
static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch');
|
||||
|
||||
$login = null;
|
||||
|
||||
|
@ -253,6 +238,10 @@ function main()
|
|||
return;
|
||||
}
|
||||
|
||||
// For database errors
|
||||
|
||||
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError');
|
||||
|
||||
// Make sure RW database is setup
|
||||
|
||||
setupRW();
|
||||
|
@ -335,10 +324,10 @@ function main()
|
|||
$cac = new ClientErrorAction($cex->getMessage(), $cex->getCode());
|
||||
$cac->showPage();
|
||||
} catch (ServerException $sex) { // snort snort guffaw
|
||||
$sac = new ServerErrorAction($sex->getMessage(), $sex->getCode());
|
||||
$sac = new ServerErrorAction($sex->getMessage(), $sex->getCode(), $sex);
|
||||
$sac->showPage();
|
||||
} catch (Exception $ex) {
|
||||
$sac = new ServerErrorAction($ex->getMessage());
|
||||
$sac = new ServerErrorAction($ex->getMessage(), 500, $ex);
|
||||
$sac->showPage();
|
||||
}
|
||||
}
|
||||
|
|
28
install.php
28
install.php
|
@ -483,6 +483,7 @@ function showForm()
|
|||
$dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo<<<E_O_T
|
||||
</ul>
|
||||
</dd>
|
||||
|
@ -559,6 +560,11 @@ function showForm()
|
|||
<input id="admin_email" name="admin_email" value="{$post->value('admin_email')}" />
|
||||
<p class="form_guide">Optional email address for the initial StatusNet user (administrator)</p>
|
||||
</li>
|
||||
<li>
|
||||
<label for="admin_updates">Subscribe to announcements</label>
|
||||
<input type="checkbox" id="admin_updates" name="admin_updates" value="true" checked="checked" />
|
||||
<p class="form_guide">Release and security feed from <a href="http://update.status.net/">update@status.net</a> (recommended)</p>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<input type="submit" name="submit" class="submit" value="Submit" />
|
||||
|
@ -583,10 +589,11 @@ function handlePost()
|
|||
$sitename = $_POST['sitename'];
|
||||
$fancy = !empty($_POST['fancy']);
|
||||
|
||||
$adminNick = $_POST['admin_nickname'];
|
||||
$adminNick = strtolower($_POST['admin_nickname']);
|
||||
$adminPass = $_POST['admin_password'];
|
||||
$adminPass2 = $_POST['admin_password2'];
|
||||
$adminEmail = $_POST['admin_email'];
|
||||
$adminUpdates = $_POST['admin_updates'];
|
||||
|
||||
$server = $_SERVER['HTTP_HOST'];
|
||||
$path = substr(dirname($_SERVER['PHP_SELF']), 1);
|
||||
|
@ -623,6 +630,19 @@ STR;
|
|||
updateStatus("No initial StatusNet user nickname specified.", true);
|
||||
$fail = true;
|
||||
}
|
||||
if ($adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $adminNick)) {
|
||||
updateStatus('The user nickname "' . htmlspecialchars($adminNick) .
|
||||
'" is invalid; should be plain letters and numbers no longer than 64 characters.', true);
|
||||
$fail = true;
|
||||
}
|
||||
// @fixme hardcoded list; should use User::allowed_nickname()
|
||||
// if/when it's safe to have loaded the infrastructure here
|
||||
$blacklist = array('main', 'admin', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook');
|
||||
if (in_array($adminNick, $blacklist)) {
|
||||
updateStatus('The user nickname "' . htmlspecialchars($adminNick) .
|
||||
'" is reserved.', true);
|
||||
$fail = true;
|
||||
}
|
||||
|
||||
if (empty($adminPass)) {
|
||||
updateStatus("No initial StatusNet user password specified.", true);
|
||||
|
@ -657,7 +677,7 @@ STR;
|
|||
}
|
||||
|
||||
// Okay, cross fingers and try to register an initial user
|
||||
if (registerInitialUser($adminNick, $adminPass, $adminEmail)) {
|
||||
if (registerInitialUser($adminNick, $adminPass, $adminEmail, $adminUpdates)) {
|
||||
updateStatus(
|
||||
"An initial user with the administrator role has been created."
|
||||
);
|
||||
|
@ -854,7 +874,7 @@ function runDbScript($filename, $conn, $type = 'mysqli')
|
|||
return true;
|
||||
}
|
||||
|
||||
function registerInitialUser($nickname, $password, $email)
|
||||
function registerInitialUser($nickname, $password, $email, $adminUpdates)
|
||||
{
|
||||
define('STATUSNET', true);
|
||||
define('LACONICA', true); // compatibility
|
||||
|
@ -882,7 +902,7 @@ function registerInitialUser($nickname, $password, $email)
|
|||
// Attempt to do a remote subscribe to update@status.net
|
||||
// Will fail if instance is on a private network.
|
||||
|
||||
if (class_exists('Ostatus_profile')) {
|
||||
if (class_exists('Ostatus_profile') && $adminUpdates) {
|
||||
try {
|
||||
$oprofile = Ostatus_profile::ensureProfile('http://update.status.net/');
|
||||
Subscription::start($user->getProfile(), $oprofile->localProfile());
|
||||
|
|
31
js/util.js
31
js/util.js
|
@ -61,10 +61,8 @@ var SN = { // StatusNet
|
|||
|
||||
U: { // Utils
|
||||
FormNoticeEnhancements: function(form) {
|
||||
form_id = form.attr('id');
|
||||
|
||||
if (jQuery.data(form[0], 'ElementData') === undefined) {
|
||||
MaxLength = $('#'+form_id+' #'+SN.C.S.NoticeTextCount).text();
|
||||
MaxLength = form.find('#'+SN.C.S.NoticeTextCount).text();
|
||||
if (typeof(MaxLength) == 'undefined') {
|
||||
MaxLength = SN.C.I.MaxLength;
|
||||
}
|
||||
|
@ -72,7 +70,7 @@ var SN = { // StatusNet
|
|||
|
||||
SN.U.Counter(form);
|
||||
|
||||
NDT = $('#'+form_id+' #'+SN.C.S.NoticeDataText);
|
||||
NDT = form.find('#'+SN.C.S.NoticeDataText);
|
||||
|
||||
NDT.bind('keyup', function(e) {
|
||||
SN.U.Counter(form);
|
||||
|
@ -83,11 +81,11 @@ var SN = { // StatusNet
|
|||
});
|
||||
}
|
||||
else {
|
||||
$('#'+form_id+' #'+SN.C.S.NoticeTextCount).text(jQuery.data(form[0], 'ElementData').MaxLength);
|
||||
form.find('#'+SN.C.S.NoticeTextCount).text(jQuery.data(form[0], 'ElementData').MaxLength);
|
||||
}
|
||||
|
||||
if ($('body')[0].id != 'conversation') {
|
||||
$('#'+form_id+' textarea').focus();
|
||||
if ($('body')[0].id != 'conversation' && window.location.hash.length === 0) {
|
||||
form.find('textarea').focus();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -105,7 +103,6 @@ var SN = { // StatusNet
|
|||
|
||||
Counter: function(form) {
|
||||
SN.C.I.FormNoticeCurrent = form;
|
||||
form_id = form.attr('id');
|
||||
|
||||
var MaxLength = jQuery.data(form[0], 'ElementData').MaxLength;
|
||||
|
||||
|
@ -113,8 +110,8 @@ var SN = { // StatusNet
|
|||
return;
|
||||
}
|
||||
|
||||
var remaining = MaxLength - $('#'+form_id+' #'+SN.C.S.NoticeDataText).val().length;
|
||||
var counter = $('#'+form_id+' #'+SN.C.S.NoticeTextCount);
|
||||
var remaining = MaxLength - form.find('#'+SN.C.S.NoticeDataText).val().length;
|
||||
var counter = form.find('#'+SN.C.S.NoticeTextCount);
|
||||
|
||||
if (remaining.toString() != counter.text()) {
|
||||
if (!SN.C.I.CounterBlackout || remaining === 0) {
|
||||
|
@ -174,7 +171,6 @@ var SN = { // StatusNet
|
|||
|
||||
FormNoticeXHR: function(form) {
|
||||
SN.C.I.NoticeDataGeo = {};
|
||||
form_id = form.attr('id');
|
||||
form.append('<input type="hidden" name="ajax" value="1"/>');
|
||||
form.ajaxForm({
|
||||
dataType: 'xml',
|
||||
|
@ -403,6 +399,18 @@ var SN = { // StatusNet
|
|||
return;
|
||||
}
|
||||
|
||||
var attachment_more = notice.find('.attachment.more');
|
||||
if (attachment_more.length > 0) {
|
||||
attachment_more.click(function() {
|
||||
$(this).addClass(SN.C.S.Processing);
|
||||
$.get($(this).attr('href')+'/ajax', null, function(data) {
|
||||
notice.find('.entry-title .entry-content').html($(data).find('#attachment_view .entry-content').html());
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
$.fn.jOverlay.options = {
|
||||
method : 'GET',
|
||||
data : '',
|
||||
|
@ -456,6 +464,7 @@ var SN = { // StatusNet
|
|||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
NoticeDataAttach: function() {
|
||||
|
|
|
@ -798,11 +798,14 @@ class Action extends HTMLOutputter // lawsuit
|
|||
{
|
||||
$this->element('dt', array('id' => 'site_statusnet_license'), _('StatusNet software license'));
|
||||
$this->elementStart('dd', null);
|
||||
// @fixme drop the final spaces in the messages when at good spot
|
||||
// to let translations get updated.
|
||||
if (common_config('site', 'broughtby')) {
|
||||
$instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%). ');
|
||||
} else {
|
||||
$instr = _('**%%site.name%%** is a microblogging service. ');
|
||||
}
|
||||
$instr .= ' ';
|
||||
$instr .= sprintf(_('It runs the [StatusNet](http://status.net/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), STATUSNET_VERSION);
|
||||
$output = common_markup_to_html($instr);
|
||||
$this->raw($output);
|
||||
|
|
1114
lib/activity.php
1114
lib/activity.php
File diff suppressed because it is too large
Load Diff
121
lib/activitycontext.php
Normal file
121
lib/activitycontext.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class ActivityContext
|
||||
{
|
||||
public $replyToID;
|
||||
public $replyToUrl;
|
||||
public $location;
|
||||
public $attention = array();
|
||||
public $conversation;
|
||||
|
||||
const THR = 'http://purl.org/syndication/thread/1.0';
|
||||
const GEORSS = 'http://www.georss.org/georss';
|
||||
const OSTATUS = 'http://ostatus.org/schema/1.0';
|
||||
|
||||
const INREPLYTO = 'in-reply-to';
|
||||
const REF = 'ref';
|
||||
const HREF = 'href';
|
||||
|
||||
const POINT = 'point';
|
||||
|
||||
const ATTENTION = 'ostatus:attention';
|
||||
const CONVERSATION = 'ostatus:conversation';
|
||||
|
||||
function __construct($element)
|
||||
{
|
||||
$replyToEl = ActivityUtils::child($element, self::INREPLYTO, self::THR);
|
||||
|
||||
if (!empty($replyToEl)) {
|
||||
$this->replyToID = $replyToEl->getAttribute(self::REF);
|
||||
$this->replyToUrl = $replyToEl->getAttribute(self::HREF);
|
||||
}
|
||||
|
||||
$this->location = $this->getLocation($element);
|
||||
|
||||
$this->conversation = ActivityUtils::getLink($element, self::CONVERSATION);
|
||||
|
||||
// Multiple attention links allowed
|
||||
|
||||
$links = $element->getElementsByTagNameNS(ActivityUtils::ATOM, ActivityUtils::LINK);
|
||||
|
||||
for ($i = 0; $i < $links->length; $i++) {
|
||||
|
||||
$link = $links->item($i);
|
||||
|
||||
$linkRel = $link->getAttribute(ActivityUtils::REL);
|
||||
|
||||
if ($linkRel == self::ATTENTION) {
|
||||
$this->attention[] = $link->getAttribute(self::HREF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse location given as a GeoRSS-simple point, if provided.
|
||||
* http://www.georss.org/simple
|
||||
*
|
||||
* @param feed item $entry
|
||||
* @return mixed Location or false
|
||||
*/
|
||||
function getLocation($dom)
|
||||
{
|
||||
$points = $dom->getElementsByTagNameNS(self::GEORSS, self::POINT);
|
||||
|
||||
for ($i = 0; $i < $points->length; $i++) {
|
||||
$point = $points->item($i)->textContent;
|
||||
return self::locationFromPoint($point);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// XXX: Move to ActivityUtils or Location?
|
||||
static function locationFromPoint($point)
|
||||
{
|
||||
$point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace"
|
||||
$point = preg_replace('/\s+/', ' ', $point);
|
||||
$point = trim($point);
|
||||
$coords = explode(' ', $point);
|
||||
if (count($coords) == 2) {
|
||||
list($lat, $lon) = $coords;
|
||||
if (is_numeric($lat) && is_numeric($lon)) {
|
||||
common_log(LOG_INFO, "Looking up location for $lat $lon from georss point");
|
||||
return Location::fromLatLon($lat, $lon);
|
||||
}
|
||||
}
|
||||
common_log(LOG_ERR, "Ignoring bogus georss:point value $point");
|
||||
return null;
|
||||
}
|
||||
}
|
548
lib/activityobject.php
Normal file
548
lib/activityobject.php
Normal file
|
@ -0,0 +1,548 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* A noun-ish thing in the activity universe
|
||||
*
|
||||
* The activity streams spec talks about activity objects, while also having
|
||||
* a tag activity:object, which is in fact an activity object. Aaaaaah!
|
||||
*
|
||||
* This is just a thing in the activity universe. Can be the subject, object,
|
||||
* or indirect object (target!) of an activity verb. Rotten name, and I'm
|
||||
* propagating it. *sigh*
|
||||
*
|
||||
* @category OStatus
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class ActivityObject
|
||||
{
|
||||
const ARTICLE = 'http://activitystrea.ms/schema/1.0/article';
|
||||
const BLOGENTRY = 'http://activitystrea.ms/schema/1.0/blog-entry';
|
||||
const NOTE = 'http://activitystrea.ms/schema/1.0/note';
|
||||
const STATUS = 'http://activitystrea.ms/schema/1.0/status';
|
||||
const FILE = 'http://activitystrea.ms/schema/1.0/file';
|
||||
const PHOTO = 'http://activitystrea.ms/schema/1.0/photo';
|
||||
const ALBUM = 'http://activitystrea.ms/schema/1.0/photo-album';
|
||||
const PLAYLIST = 'http://activitystrea.ms/schema/1.0/playlist';
|
||||
const VIDEO = 'http://activitystrea.ms/schema/1.0/video';
|
||||
const AUDIO = 'http://activitystrea.ms/schema/1.0/audio';
|
||||
const BOOKMARK = 'http://activitystrea.ms/schema/1.0/bookmark';
|
||||
const PERSON = 'http://activitystrea.ms/schema/1.0/person';
|
||||
const GROUP = 'http://activitystrea.ms/schema/1.0/group';
|
||||
const PLACE = 'http://activitystrea.ms/schema/1.0/place';
|
||||
const COMMENT = 'http://activitystrea.ms/schema/1.0/comment';
|
||||
// ^^^^^^^^^^ tea!
|
||||
|
||||
// Atom elements we snarf
|
||||
|
||||
const TITLE = 'title';
|
||||
const SUMMARY = 'summary';
|
||||
const ID = 'id';
|
||||
const SOURCE = 'source';
|
||||
|
||||
const NAME = 'name';
|
||||
const URI = 'uri';
|
||||
const EMAIL = 'email';
|
||||
|
||||
const POSTEROUS = 'http://posterous.com/help/rss/1.0';
|
||||
const AUTHOR = 'author';
|
||||
const USERIMAGE = 'userImage';
|
||||
const PROFILEURL = 'profileUrl';
|
||||
const NICKNAME = 'nickName';
|
||||
const DISPLAYNAME = 'displayName';
|
||||
|
||||
public $element;
|
||||
public $type;
|
||||
public $id;
|
||||
public $title;
|
||||
public $summary;
|
||||
public $content;
|
||||
public $link;
|
||||
public $source;
|
||||
public $avatarLinks = array();
|
||||
public $geopoint;
|
||||
public $poco;
|
||||
public $displayName;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* This probably needs to be refactored
|
||||
* to generate a local class (ActivityPerson, ActivityFile, ...)
|
||||
* based on the object type.
|
||||
*
|
||||
* @param DOMElement $element DOM thing to turn into an Activity thing
|
||||
*/
|
||||
|
||||
function __construct($element = null)
|
||||
{
|
||||
if (empty($element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->element = $element;
|
||||
|
||||
$this->geopoint = $this->_childContent(
|
||||
$element,
|
||||
ActivityContext::POINT,
|
||||
ActivityContext::GEORSS
|
||||
);
|
||||
|
||||
if ($element->tagName == 'author') {
|
||||
$this->_fromAuthor($element);
|
||||
} else if ($element->tagName == 'item') {
|
||||
$this->_fromRssItem($element);
|
||||
} else {
|
||||
$this->_fromAtomEntry($element);
|
||||
}
|
||||
|
||||
// Some per-type attributes...
|
||||
if ($this->type == self::PERSON || $this->type == self::GROUP) {
|
||||
$this->displayName = $this->title;
|
||||
|
||||
$photos = ActivityUtils::getLinks($element, 'photo');
|
||||
if (count($photos)) {
|
||||
foreach ($photos as $link) {
|
||||
$this->avatarLinks[] = new AvatarLink($link);
|
||||
}
|
||||
} else {
|
||||
$avatars = ActivityUtils::getLinks($element, 'avatar');
|
||||
foreach ($avatars as $link) {
|
||||
$this->avatarLinks[] = new AvatarLink($link);
|
||||
}
|
||||
}
|
||||
|
||||
$this->poco = new PoCo($element);
|
||||
}
|
||||
}
|
||||
|
||||
private function _fromAuthor($element)
|
||||
{
|
||||
$this->type = self::PERSON; // XXX: is this fair?
|
||||
$this->title = $this->_childContent($element, self::NAME);
|
||||
|
||||
$id = $this->_childContent($element, self::URI);
|
||||
if (ActivityUtils::validateUri($id)) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
if (empty($this->id)) {
|
||||
$email = $this->_childContent($element, self::EMAIL);
|
||||
if (!empty($email)) {
|
||||
// XXX: acct: ?
|
||||
$this->id = 'mailto:'.$email;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function _fromAtomEntry($element)
|
||||
{
|
||||
if ($element->localName == 'actor') {
|
||||
// Old-fashioned <activity:actor>...
|
||||
// First pull anything from <author>, then we'll add on top.
|
||||
$author = ActivityUtils::child($element->parentNode, 'author');
|
||||
if ($author) {
|
||||
$this->_fromAuthor($author);
|
||||
}
|
||||
}
|
||||
|
||||
$this->type = $this->_childContent($element, Activity::OBJECTTYPE,
|
||||
Activity::SPEC);
|
||||
|
||||
if (empty($this->type)) {
|
||||
$this->type = ActivityObject::NOTE;
|
||||
}
|
||||
|
||||
$id = $this->_childContent($element, self::ID);
|
||||
if (ActivityUtils::validateUri($id)) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
$this->summary = ActivityUtils::childHtmlContent($element, self::SUMMARY);
|
||||
$this->content = ActivityUtils::getContent($element);
|
||||
|
||||
// We don't like HTML in our titles, although it's technically allowed
|
||||
|
||||
$title = ActivityUtils::childHtmlContent($element, self::TITLE);
|
||||
|
||||
$this->title = html_entity_decode(strip_tags($title));
|
||||
|
||||
$this->source = $this->_getSource($element);
|
||||
|
||||
$this->link = ActivityUtils::getPermalink($element);
|
||||
}
|
||||
|
||||
// @fixme rationalize with Activity::_fromRssItem()
|
||||
|
||||
private function _fromRssItem($item)
|
||||
{
|
||||
$this->title = ActivityUtils::childContent($item, ActivityObject::TITLE, Activity::RSS);
|
||||
|
||||
$contentEl = ActivityUtils::child($item, ActivityUtils::CONTENT, Activity::CONTENTNS);
|
||||
|
||||
if (!empty($contentEl)) {
|
||||
$this->content = htmlspecialchars_decode($contentEl->textContent, ENT_QUOTES);
|
||||
} else {
|
||||
$descriptionEl = ActivityUtils::child($item, Activity::DESCRIPTION, Activity::RSS);
|
||||
if (!empty($descriptionEl)) {
|
||||
$this->content = htmlspecialchars_decode($descriptionEl->textContent, ENT_QUOTES);
|
||||
}
|
||||
}
|
||||
|
||||
$this->link = ActivityUtils::childContent($item, ActivityUtils::LINK, Activity::RSS);
|
||||
|
||||
$guidEl = ActivityUtils::child($item, Activity::GUID, Activity::RSS);
|
||||
|
||||
if (!empty($guidEl)) {
|
||||
$this->id = $guidEl->textContent;
|
||||
|
||||
if ($guidEl->hasAttribute('isPermaLink')) {
|
||||
// overwrites <link>
|
||||
$this->link = $this->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromRssAuthor($el)
|
||||
{
|
||||
$text = $el->textContent;
|
||||
|
||||
if (preg_match('/^(.*?) \((.*)\)$/', $text, $match)) {
|
||||
$email = $match[1];
|
||||
$name = $match[2];
|
||||
} else if (preg_match('/^(.*?) <(.*)>$/', $text, $match)) {
|
||||
$name = $match[1];
|
||||
$email = $match[2];
|
||||
} else if (preg_match('/.*@.*/', $text)) {
|
||||
$email = $text;
|
||||
$name = null;
|
||||
} else {
|
||||
$name = $text;
|
||||
$email = null;
|
||||
}
|
||||
|
||||
// Not really enough info
|
||||
|
||||
$obj = new ActivityObject();
|
||||
|
||||
$obj->element = $el;
|
||||
|
||||
$obj->type = ActivityObject::PERSON;
|
||||
$obj->title = $name;
|
||||
|
||||
if (!empty($email)) {
|
||||
$obj->id = 'mailto:'.$email;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function fromDcCreator($el)
|
||||
{
|
||||
// Not really enough info
|
||||
|
||||
$text = $el->textContent;
|
||||
|
||||
$obj = new ActivityObject();
|
||||
|
||||
$obj->element = $el;
|
||||
|
||||
$obj->title = $text;
|
||||
$obj->type = ActivityObject::PERSON;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function fromRssChannel($el)
|
||||
{
|
||||
$obj = new ActivityObject();
|
||||
|
||||
$obj->element = $el;
|
||||
|
||||
$obj->type = ActivityObject::PERSON; // @fixme guess better
|
||||
|
||||
$obj->title = ActivityUtils::childContent($el, ActivityObject::TITLE, Activity::RSS);
|
||||
$obj->link = ActivityUtils::childContent($el, ActivityUtils::LINK, Activity::RSS);
|
||||
$obj->id = ActivityUtils::getLink($el, Activity::SELF);
|
||||
|
||||
if (empty($obj->id)) {
|
||||
$obj->id = $obj->link;
|
||||
}
|
||||
|
||||
$desc = ActivityUtils::childContent($el, Activity::DESCRIPTION, Activity::RSS);
|
||||
|
||||
if (!empty($desc)) {
|
||||
$obj->content = htmlspecialchars_decode($desc, ENT_QUOTES);
|
||||
}
|
||||
|
||||
$imageEl = ActivityUtils::child($el, Activity::IMAGE, Activity::RSS);
|
||||
|
||||
if (!empty($imageEl)) {
|
||||
$url = ActivityUtils::childContent($imageEl, Activity::URL, Activity::RSS);
|
||||
$al = new AvatarLink();
|
||||
$al->url = $url;
|
||||
$obj->avatarLinks[] = $al;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function fromPosterousAuthor($el)
|
||||
{
|
||||
$obj = new ActivityObject();
|
||||
|
||||
$obj->type = ActivityObject::PERSON; // @fixme any others...?
|
||||
|
||||
$userImage = ActivityUtils::childContent($el, self::USERIMAGE, self::POSTEROUS);
|
||||
|
||||
if (!empty($userImage)) {
|
||||
$al = new AvatarLink();
|
||||
$al->url = $userImage;
|
||||
$obj->avatarLinks[] = $al;
|
||||
}
|
||||
|
||||
$obj->link = ActivityUtils::childContent($el, self::PROFILEURL, self::POSTEROUS);
|
||||
$obj->id = $obj->link;
|
||||
|
||||
$obj->poco = new PoCo();
|
||||
|
||||
$obj->poco->preferredUsername = ActivityUtils::childContent($el, self::NICKNAME, self::POSTEROUS);
|
||||
$obj->poco->displayName = ActivityUtils::childContent($el, self::DISPLAYNAME, self::POSTEROUS);
|
||||
|
||||
$obj->title = $obj->poco->displayName;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
private function _childContent($element, $tag, $namespace=ActivityUtils::ATOM)
|
||||
{
|
||||
return ActivityUtils::childContent($element, $tag, $namespace);
|
||||
}
|
||||
|
||||
// Try to get a unique id for the source feed
|
||||
|
||||
private function _getSource($element)
|
||||
{
|
||||
$sourceEl = ActivityUtils::child($element, 'source');
|
||||
|
||||
if (empty($sourceEl)) {
|
||||
return null;
|
||||
} else {
|
||||
$href = ActivityUtils::getLink($sourceEl, 'self');
|
||||
if (!empty($href)) {
|
||||
return $href;
|
||||
} else {
|
||||
return ActivityUtils::childContent($sourceEl, 'id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function fromNotice(Notice $notice)
|
||||
{
|
||||
$object = new ActivityObject();
|
||||
|
||||
$object->type = ActivityObject::NOTE;
|
||||
|
||||
$object->id = $notice->uri;
|
||||
$object->title = $notice->content;
|
||||
$object->content = $notice->rendered;
|
||||
$object->link = $notice->bestUrl();
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
static function fromProfile(Profile $profile)
|
||||
{
|
||||
$object = new ActivityObject();
|
||||
|
||||
$object->type = ActivityObject::PERSON;
|
||||
$object->id = $profile->getUri();
|
||||
$object->title = $profile->getBestName();
|
||||
$object->link = $profile->profileurl;
|
||||
|
||||
$orig = $profile->getOriginalAvatar();
|
||||
|
||||
if (!empty($orig)) {
|
||||
$object->avatarLinks[] = AvatarLink::fromAvatar($orig);
|
||||
}
|
||||
|
||||
$sizes = array(
|
||||
AVATAR_PROFILE_SIZE,
|
||||
AVATAR_STREAM_SIZE,
|
||||
AVATAR_MINI_SIZE
|
||||
);
|
||||
|
||||
foreach ($sizes as $size) {
|
||||
|
||||
$alink = null;
|
||||
$avatar = $profile->getAvatar($size);
|
||||
|
||||
if (!empty($avatar)) {
|
||||
$alink = AvatarLink::fromAvatar($avatar);
|
||||
} else {
|
||||
$alink = new AvatarLink();
|
||||
$alink->type = 'image/png';
|
||||
$alink->height = $size;
|
||||
$alink->width = $size;
|
||||
$alink->url = Avatar::defaultImage($size);
|
||||
}
|
||||
|
||||
$object->avatarLinks[] = $alink;
|
||||
}
|
||||
|
||||
if (isset($profile->lat) && isset($profile->lon)) {
|
||||
$object->geopoint = (float)$profile->lat
|
||||
. ' ' . (float)$profile->lon;
|
||||
}
|
||||
|
||||
$object->poco = PoCo::fromProfile($profile);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
static function fromGroup($group)
|
||||
{
|
||||
$object = new ActivityObject();
|
||||
|
||||
$object->type = ActivityObject::GROUP;
|
||||
$object->id = $group->getUri();
|
||||
$object->title = $group->getBestName();
|
||||
$object->link = $group->getUri();
|
||||
|
||||
$object->avatarLinks[] = AvatarLink::fromFilename(
|
||||
$group->homepage_logo,
|
||||
AVATAR_PROFILE_SIZE
|
||||
);
|
||||
|
||||
$object->avatarLinks[] = AvatarLink::fromFilename(
|
||||
$group->stream_logo,
|
||||
AVATAR_STREAM_SIZE
|
||||
);
|
||||
|
||||
$object->avatarLinks[] = AvatarLink::fromFilename(
|
||||
$group->mini_logo,
|
||||
AVATAR_MINI_SIZE
|
||||
);
|
||||
|
||||
$object->poco = PoCo::fromGroup($group);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
function asString($tag='activity:object')
|
||||
{
|
||||
$xs = new XMLStringer(true);
|
||||
|
||||
$xs->elementStart($tag);
|
||||
|
||||
$xs->element('activity:object-type', null, $this->type);
|
||||
|
||||
$xs->element(self::ID, null, $this->id);
|
||||
|
||||
if (!empty($this->title)) {
|
||||
$xs->element(
|
||||
self::TITLE,
|
||||
null,
|
||||
common_xml_safe_str($this->title)
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($this->summary)) {
|
||||
$xs->element(
|
||||
self::SUMMARY,
|
||||
null,
|
||||
common_xml_safe_str($this->summary)
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($this->content)) {
|
||||
// XXX: assuming HTML content here
|
||||
$xs->element(
|
||||
ActivityUtils::CONTENT,
|
||||
array('type' => 'html'),
|
||||
common_xml_safe_str($this->content)
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($this->link)) {
|
||||
$xs->element(
|
||||
'link',
|
||||
array(
|
||||
'rel' => 'alternate',
|
||||
'type' => 'text/html',
|
||||
'href' => $this->link
|
||||
),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->type == ActivityObject::PERSON
|
||||
|| $this->type == ActivityObject::GROUP) {
|
||||
|
||||
foreach ($this->avatarLinks as $avatar) {
|
||||
$xs->element(
|
||||
'link', array(
|
||||
'rel' => 'avatar',
|
||||
'type' => $avatar->type,
|
||||
'media:width' => $avatar->width,
|
||||
'media:height' => $avatar->height,
|
||||
'href' => $avatar->url
|
||||
),
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->geopoint)) {
|
||||
$xs->element(
|
||||
'georss:point',
|
||||
null,
|
||||
$this->geopoint
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($this->poco)) {
|
||||
$xs->raw($this->poco->asString());
|
||||
}
|
||||
|
||||
$xs->elementEnd($tag);
|
||||
|
||||
return $xs->getString();
|
||||
}
|
||||
}
|
265
lib/activityutils.php
Normal file
265
lib/activityutils.php
Normal file
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities for turning DOMish things into Activityish things
|
||||
*
|
||||
* Some common functions that I didn't have the bandwidth to try to factor
|
||||
* into some kind of reasonable superclass, so just dumped here. Might
|
||||
* be useful to have an ActivityObject parent class or something.
|
||||
*
|
||||
* @category OStatus
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class ActivityUtils
|
||||
{
|
||||
const ATOM = 'http://www.w3.org/2005/Atom';
|
||||
|
||||
const LINK = 'link';
|
||||
const REL = 'rel';
|
||||
const TYPE = 'type';
|
||||
const HREF = 'href';
|
||||
|
||||
const CONTENT = 'content';
|
||||
const SRC = 'src';
|
||||
|
||||
/**
|
||||
* Get the permalink for an Activity object
|
||||
*
|
||||
* @param DOMElement $element A DOM element
|
||||
*
|
||||
* @return string related link, if any
|
||||
*/
|
||||
|
||||
static function getPermalink($element)
|
||||
{
|
||||
return self::getLink($element, 'alternate', 'text/html');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permalink for an Activity object
|
||||
*
|
||||
* @param DOMElement $element A DOM element
|
||||
*
|
||||
* @return string related link, if any
|
||||
*/
|
||||
|
||||
static function getLink(DOMNode $element, $rel, $type=null)
|
||||
{
|
||||
$els = $element->childNodes;
|
||||
|
||||
foreach ($els as $link) {
|
||||
|
||||
if (!($link instanceof DOMElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($link->localName == self::LINK && $link->namespaceURI == self::ATOM) {
|
||||
|
||||
$linkRel = $link->getAttribute(self::REL);
|
||||
$linkType = $link->getAttribute(self::TYPE);
|
||||
|
||||
if ($linkRel == $rel &&
|
||||
(is_null($type) || $linkType == $type)) {
|
||||
return $link->getAttribute(self::HREF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static function getLinks(DOMNode $element, $rel, $type=null)
|
||||
{
|
||||
$els = $element->childNodes;
|
||||
$out = array();
|
||||
|
||||
foreach ($els as $link) {
|
||||
if ($link->localName == self::LINK && $link->namespaceURI == self::ATOM) {
|
||||
|
||||
$linkRel = $link->getAttribute(self::REL);
|
||||
$linkType = $link->getAttribute(self::TYPE);
|
||||
|
||||
if ($linkRel == $rel &&
|
||||
(is_null($type) || $linkType == $type)) {
|
||||
$out[] = $link;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first child element with the given tag
|
||||
*
|
||||
* @param DOMElement $element element to pick at
|
||||
* @param string $tag tag to look for
|
||||
* @param string $namespace Namespace to look under
|
||||
*
|
||||
* @return DOMElement found element or null
|
||||
*/
|
||||
|
||||
static function child(DOMNode $element, $tag, $namespace=self::ATOM)
|
||||
{
|
||||
$els = $element->childNodes;
|
||||
if (empty($els) || $els->length == 0) {
|
||||
return null;
|
||||
} else {
|
||||
for ($i = 0; $i < $els->length; $i++) {
|
||||
$el = $els->item($i);
|
||||
if ($el->localName == $tag && $el->namespaceURI == $namespace) {
|
||||
return $el;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab the text content of a DOM element child of the current element
|
||||
*
|
||||
* @param DOMElement $element Element whose children we examine
|
||||
* @param string $tag Tag to look up
|
||||
* @param string $namespace Namespace to use, defaults to Atom
|
||||
*
|
||||
* @return string content of the child
|
||||
*/
|
||||
|
||||
static function childContent(DOMNode $element, $tag, $namespace=self::ATOM)
|
||||
{
|
||||
$el = self::child($element, $tag, $namespace);
|
||||
|
||||
if (empty($el)) {
|
||||
return null;
|
||||
} else {
|
||||
return $el->textContent;
|
||||
}
|
||||
}
|
||||
|
||||
static function childHtmlContent(DOMNode $element, $tag, $namespace=self::ATOM)
|
||||
{
|
||||
$el = self::child($element, $tag, $namespace);
|
||||
|
||||
if (empty($el)) {
|
||||
return null;
|
||||
} else {
|
||||
return self::textConstruct($el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of an atom:entry-like object
|
||||
*
|
||||
* @param DOMElement $element The element to examine.
|
||||
*
|
||||
* @return string unencoded HTML content of the element, like "This -< is <b>HTML</b>."
|
||||
*
|
||||
* @todo handle remote content
|
||||
* @todo handle embedded XML mime types
|
||||
* @todo handle base64-encoded non-XML and non-text mime types
|
||||
*/
|
||||
|
||||
static function getContent($element)
|
||||
{
|
||||
return self::childHtmlContent($element, self::CONTENT, self::ATOM);
|
||||
}
|
||||
|
||||
static function textConstruct($el)
|
||||
{
|
||||
$src = $el->getAttribute(self::SRC);
|
||||
|
||||
if (!empty($src)) {
|
||||
throw new ClientException(_("Can't handle remote content yet."));
|
||||
}
|
||||
|
||||
$type = $el->getAttribute(self::TYPE);
|
||||
|
||||
// slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3
|
||||
|
||||
if (empty($type) || $type == 'text') {
|
||||
return $el->textContent;
|
||||
} else if ($type == 'html') {
|
||||
$text = $el->textContent;
|
||||
return htmlspecialchars_decode($text, ENT_QUOTES);
|
||||
} else if ($type == 'xhtml') {
|
||||
$divEl = ActivityUtils::child($el, 'div', 'http://www.w3.org/1999/xhtml');
|
||||
if (empty($divEl)) {
|
||||
return null;
|
||||
}
|
||||
$doc = $divEl->ownerDocument;
|
||||
$text = '';
|
||||
$children = $divEl->childNodes;
|
||||
|
||||
for ($i = 0; $i < $children->length; $i++) {
|
||||
$child = $children->item($i);
|
||||
$text .= $doc->saveXML($child);
|
||||
}
|
||||
return trim($text);
|
||||
} else if (in_array($type, array('text/xml', 'application/xml')) ||
|
||||
preg_match('#(+|/)xml$#', $type)) {
|
||||
throw new ClientException(_("Can't handle embedded XML content yet."));
|
||||
} else if (strncasecmp($type, 'text/', 5)) {
|
||||
return $el->textContent;
|
||||
} else {
|
||||
throw new ClientException(_("Can't handle embedded Base64 content yet."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a valid URI for remote profile/notice identification?
|
||||
* Does not have to be a resolvable URL.
|
||||
* @param string $uri
|
||||
* @return boolean
|
||||
*/
|
||||
static function validateUri($uri)
|
||||
{
|
||||
if (Validate::uri($uri)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Possibly an upstream bug; tag: URIs aren't validated properly
|
||||
// unless you explicitly ask for them. All other schemes are accepted
|
||||
// for basic URI validation without asking.
|
||||
if (Validate::uri($uri, array('allowed_scheme' => array('tag')))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
66
lib/activityverb.php
Normal file
66
lib/activityverb.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to hold a bunch of constant defining default verb types
|
||||
*
|
||||
* @category OStatus
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class ActivityVerb
|
||||
{
|
||||
const POST = 'http://activitystrea.ms/schema/1.0/post';
|
||||
const SHARE = 'http://activitystrea.ms/schema/1.0/share';
|
||||
const SAVE = 'http://activitystrea.ms/schema/1.0/save';
|
||||
const FAVORITE = 'http://activitystrea.ms/schema/1.0/favorite';
|
||||
const PLAY = 'http://activitystrea.ms/schema/1.0/play';
|
||||
const FOLLOW = 'http://activitystrea.ms/schema/1.0/follow';
|
||||
const FRIEND = 'http://activitystrea.ms/schema/1.0/make-friend';
|
||||
const JOIN = 'http://activitystrea.ms/schema/1.0/join';
|
||||
const TAG = 'http://activitystrea.ms/schema/1.0/tag';
|
||||
|
||||
// Custom OStatus verbs for the flipside until they're standardized
|
||||
const DELETE = 'http://ostatus.org/schema/1.0/unfollow';
|
||||
const UNFAVORITE = 'http://ostatus.org/schema/1.0/unfavorite';
|
||||
const UNFOLLOW = 'http://ostatus.org/schema/1.0/unfollow';
|
||||
const LEAVE = 'http://ostatus.org/schema/1.0/leave';
|
||||
|
||||
// For simple profile-update pings; no content to share.
|
||||
const UPDATE_PROFILE = 'http://ostatus.org/schema/1.0/update-profile';
|
||||
}
|
|
@ -491,7 +491,7 @@ class ApiAction extends Action
|
|||
$this->showXmlAttachments($twitter_status['attachments']);
|
||||
break;
|
||||
case 'geo':
|
||||
$this->showGeoRSS($value);
|
||||
$this->showGeoXML($value);
|
||||
break;
|
||||
case 'retweeted_status':
|
||||
$this->showTwitterXmlStatus($value, 'retweeted_status');
|
||||
|
@ -539,7 +539,7 @@ class ApiAction extends Action
|
|||
}
|
||||
}
|
||||
|
||||
function showGeoRSS($geo)
|
||||
function showGeoXML($geo)
|
||||
{
|
||||
if (empty($geo)) {
|
||||
// empty geo element
|
||||
|
@ -551,6 +551,17 @@ class ApiAction extends Action
|
|||
}
|
||||
}
|
||||
|
||||
function showGeoRSS($geo)
|
||||
{
|
||||
if (!empty($geo)) {
|
||||
$this->element(
|
||||
'georss:point',
|
||||
null,
|
||||
$geo['coordinates'][0] . ' ' . $geo['coordinates'][1]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function showTwitterRssItem($entry)
|
||||
{
|
||||
$this->elementStart('item');
|
||||
|
@ -619,13 +630,25 @@ class ApiAction extends Action
|
|||
$this->endDocument('xml');
|
||||
}
|
||||
|
||||
function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null)
|
||||
function showRssTimeline($notice, $title, $link, $subtitle, $suplink = null, $logo = null, $self = null)
|
||||
{
|
||||
|
||||
$this->initDocument('rss');
|
||||
|
||||
$this->element('title', null, $title);
|
||||
$this->element('link', null, $link);
|
||||
|
||||
if (!is_null($self)) {
|
||||
$this->element(
|
||||
'atom:link',
|
||||
array(
|
||||
'type' => 'application/rss+xml',
|
||||
'href' => $self,
|
||||
'rel' => 'self'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_null($suplink)) {
|
||||
// For FriendFeed's SUP protocol
|
||||
$this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
|
||||
|
@ -732,8 +755,12 @@ class ApiAction extends Action
|
|||
function showTwitterAtomEntry($entry)
|
||||
{
|
||||
$this->elementStart('entry');
|
||||
$this->element('title', null, $entry['title']);
|
||||
$this->element('content', array('type' => 'html'), $entry['content']);
|
||||
$this->element('title', null, common_xml_safe_str($entry['title']));
|
||||
$this->element(
|
||||
'content',
|
||||
array('type' => 'html'),
|
||||
common_xml_safe_str($entry['content'])
|
||||
);
|
||||
$this->element('id', null, $entry['id']);
|
||||
$this->element('published', null, $entry['published']);
|
||||
$this->element('updated', null, $entry['updated']);
|
||||
|
@ -848,7 +875,7 @@ class ApiAction extends Action
|
|||
|
||||
$this->initDocument('atom');
|
||||
|
||||
$this->element('title', null, $title);
|
||||
$this->element('title', null, common_xml_safe_str($title));
|
||||
$this->element('id', null, $id);
|
||||
$this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
|
||||
|
||||
|
@ -858,7 +885,7 @@ class ApiAction extends Action
|
|||
}
|
||||
|
||||
$this->element('updated', null, common_date_iso8601('now'));
|
||||
$this->element('subtitle', null, $subtitle);
|
||||
$this->element('subtitle', null, common_xml_safe_str($subtitle));
|
||||
|
||||
if (is_array($group)) {
|
||||
foreach ($group as $g) {
|
||||
|
@ -1138,7 +1165,14 @@ class ApiAction extends Action
|
|||
function initTwitterRss()
|
||||
{
|
||||
$this->startXML();
|
||||
$this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom'));
|
||||
$this->elementStart(
|
||||
'rss',
|
||||
array(
|
||||
'version' => '2.0',
|
||||
'xmlns:atom' => 'http://www.w3.org/2005/Atom',
|
||||
'xmlns:georss' => 'http://www.georss.org/georss'
|
||||
)
|
||||
);
|
||||
$this->elementStart('channel');
|
||||
Event::handle('StartApiRss', array($this));
|
||||
}
|
||||
|
@ -1336,8 +1370,27 @@ class ApiAction extends Action
|
|||
}
|
||||
}
|
||||
|
||||
function getSelfUri($action, $aargs)
|
||||
/**
|
||||
* Calculate the complete URI that called up this action. Used for
|
||||
* Atom rel="self" links. Warning: this is funky.
|
||||
*
|
||||
* @return string URL a URL suitable for rel="self" Atom links
|
||||
*/
|
||||
function getSelfUri()
|
||||
{
|
||||
$action = mb_substr(get_class($this), 0, -6); // remove 'Action'
|
||||
|
||||
$id = $this->arg('id');
|
||||
$aargs = array('format' => $this->format);
|
||||
if (!empty($id)) {
|
||||
$aargs['id'] = $id;
|
||||
}
|
||||
|
||||
$tag = $this->arg('tag');
|
||||
if (!empty($tag)) {
|
||||
$aargs['tag'] = $tag;
|
||||
}
|
||||
|
||||
parse_str($_SERVER['QUERY_STRING'], $params);
|
||||
$pstring = '';
|
||||
if (!empty($params)) {
|
||||
|
|
|
@ -235,9 +235,13 @@ class ApiAuthAction extends ApiAction
|
|||
{
|
||||
$this->basicAuthProcessHeader();
|
||||
|
||||
$realm = common_config('site', 'name') . ' API';
|
||||
$realm = common_config('api', 'realm');
|
||||
|
||||
if (!isset($this->auth_user_nickname) && $required) {
|
||||
if (empty($realm)) {
|
||||
$realm = common_config('site', 'name') . ' API';
|
||||
}
|
||||
|
||||
if (empty($this->auth_user_nickname) && $required) {
|
||||
header('WWW-Authenticate: Basic realm="' . $realm . '"');
|
||||
|
||||
// show error if the user clicks 'cancel'
|
||||
|
@ -290,11 +294,15 @@ class ApiAuthAction extends ApiAction
|
|||
|
||||
function basicAuthProcessHeader()
|
||||
{
|
||||
if (isset($_SERVER['AUTHORIZATION'])
|
||||
|| isset($_SERVER['HTTP_AUTHORIZATION'])
|
||||
) {
|
||||
$authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])
|
||||
? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
|
||||
$authHeaders = array('AUTHORIZATION',
|
||||
'HTTP_AUTHORIZATION',
|
||||
'REDIRECT_HTTP_AUTHORIZATION'); // rewrite for CGI
|
||||
$authorization_header = null;
|
||||
foreach ($authHeaders as $header) {
|
||||
if (isset($_SERVER[$header])) {
|
||||
$authorization_header = $_SERVER[$header];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
||||
|
|
|
@ -178,7 +178,7 @@ class Atom10Feed extends XMLStringer
|
|||
|
||||
$this->element(
|
||||
'generator', array(
|
||||
'url' => 'http://status.net',
|
||||
'uri' => 'http://status.net',
|
||||
'version' => STATUSNET_VERSION
|
||||
),
|
||||
'StatusNet'
|
||||
|
|
77
lib/atomcategory.php
Normal file
77
lib/atomcategory.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class AtomCategory
|
||||
{
|
||||
public $term;
|
||||
public $scheme;
|
||||
public $label;
|
||||
|
||||
function __construct($element=null)
|
||||
{
|
||||
if ($element && $element->attributes) {
|
||||
$this->term = $this->extract($element, 'term');
|
||||
$this->scheme = $this->extract($element, 'scheme');
|
||||
$this->label = $this->extract($element, 'label');
|
||||
}
|
||||
}
|
||||
|
||||
protected function extract($element, $attrib)
|
||||
{
|
||||
$node = $element->attributes->getNamedItemNS(Activity::ATOM, $attrib);
|
||||
if ($node) {
|
||||
return trim($node->textContent);
|
||||
}
|
||||
$node = $element->attributes->getNamedItem($attrib);
|
||||
if ($node) {
|
||||
return trim($node->textContent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function asString()
|
||||
{
|
||||
$attribs = array();
|
||||
if ($this->term !== null) {
|
||||
$attribs['term'] = $this->term;
|
||||
}
|
||||
if ($this->scheme !== null) {
|
||||
$attribs['scheme'] = $this->scheme;
|
||||
}
|
||||
if ($this->label !== null) {
|
||||
$attribs['label'] = $this->label;
|
||||
}
|
||||
$xs = new XMLStringer();
|
||||
$xs->element('category', $attribs);
|
||||
return $xs->asString();
|
||||
}
|
||||
}
|
|
@ -248,9 +248,7 @@ class Attachment extends AttachmentListItem
|
|||
$this->out->elementStart('div', array('id' => 'attachment_view',
|
||||
'class' => 'hentry'));
|
||||
$this->out->elementStart('div', 'entry-title');
|
||||
$this->out->elementStart('a', $this->linkAttr());
|
||||
$this->out->element('span', null, $this->linkTitle());
|
||||
$this->out->elementEnd('a');
|
||||
$this->out->element('a', $this->linkAttr(), $this->linkTitle());
|
||||
$this->out->elementEnd('div');
|
||||
|
||||
$this->out->elementStart('div', 'entry-content');
|
||||
|
@ -296,7 +294,7 @@ class Attachment extends AttachmentListItem
|
|||
}
|
||||
|
||||
function linkAttr() {
|
||||
return array('class' => 'external', 'href' => $this->attachment->url);
|
||||
return array('rel' => 'external', 'href' => $this->attachment->url);
|
||||
}
|
||||
|
||||
function linkTitle() {
|
||||
|
@ -306,7 +304,7 @@ class Attachment extends AttachmentListItem
|
|||
function showRepresentation() {
|
||||
if (empty($this->oembed->type)) {
|
||||
if (empty($this->attachment->mimetype)) {
|
||||
$this->out->element('pre', null, 'oh well... not sure how to handle the following: ' . print_r($this->attachment, true));
|
||||
$this->showFallback();
|
||||
} else {
|
||||
switch ($this->attachment->mimetype) {
|
||||
case 'image/gif':
|
||||
|
@ -332,6 +330,17 @@ class Attachment extends AttachmentListItem
|
|||
$this->out->element('param', array('name' => 'autoStart', 'value' => 1));
|
||||
$this->out->elementEnd('object');
|
||||
break;
|
||||
|
||||
case 'text/html':
|
||||
if ($this->attachment->filename) {
|
||||
// Locally-uploaded HTML. Scrub and display inline.
|
||||
$this->showHtmlFile($this->attachment);
|
||||
break;
|
||||
}
|
||||
// Fall through to default
|
||||
|
||||
default:
|
||||
$this->showFallback();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -354,9 +363,76 @@ class Attachment extends AttachmentListItem
|
|||
break;
|
||||
|
||||
default:
|
||||
$this->out->element('pre', null, 'oh well... not sure how to handle the following oembed: ' . print_r($this->oembed, true));
|
||||
}
|
||||
$this->showFallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function showHtmlFile(File $attachment)
|
||||
{
|
||||
$body = $this->scrubHtmlFile($attachment);
|
||||
if ($body) {
|
||||
$this->out->raw($body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed false on failure, HTML fragment string on success
|
||||
*/
|
||||
protected function scrubHtmlFile(File $attachment)
|
||||
{
|
||||
$path = File::path($attachment->filename);
|
||||
if (!file_exists($path) || !is_readable($path)) {
|
||||
common_log(LOG_ERR, "Missing local HTML attachment $path");
|
||||
return false;
|
||||
}
|
||||
$raw = file_get_contents($path);
|
||||
|
||||
// Normalize...
|
||||
$dom = new DOMDocument();
|
||||
if(!$dom->loadHTML($raw)) {
|
||||
common_log(LOG_ERR, "Bad HTML in local HTML attachment $path");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove <script>s or htmlawed will dump their contents into output!
|
||||
// Note: removing child nodes while iterating seems to mess things up,
|
||||
// hence the double loop.
|
||||
$scripts = array();
|
||||
foreach ($dom->getElementsByTagName('script') as $script) {
|
||||
$scripts[] = $script;
|
||||
}
|
||||
foreach ($scripts as $script) {
|
||||
common_log(LOG_DEBUG, $script->textContent);
|
||||
$script->parentNode->removeChild($script);
|
||||
}
|
||||
|
||||
// Trim out everything outside the body...
|
||||
$body = $dom->saveHTML();
|
||||
$body = preg_replace('/^.*<body[^>]*>/is', '', $body);
|
||||
$body = preg_replace('/<\/body[^>]*>.*$/is', '', $body);
|
||||
|
||||
require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
|
||||
$config = array('safe' => 1,
|
||||
'deny_attribute' => 'id,style,on*',
|
||||
'comment' => 1); // remove comments
|
||||
$scrubbed = htmLawed($body, $config);
|
||||
|
||||
return $scrubbed;
|
||||
}
|
||||
|
||||
function showFallback()
|
||||
{
|
||||
// If we don't know how to display an attachment inline, we probably
|
||||
// shouldn't have gotten to this point.
|
||||
//
|
||||
// But, here we are... displaying details on a file or remote URL
|
||||
// either on the main view or in an ajax-loaded lightbox. As a lesser
|
||||
// of several evils, we'll try redirecting to the actual target via
|
||||
// client-side JS.
|
||||
|
||||
common_log(LOG_ERR, "Empty or unknown type for file id {$this->attachment->id}; falling back to client-side redirect.");
|
||||
$this->out->raw('<script>window.location = ' . json_encode($this->attachment->url) . ';</script>');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ abstract class AuthorizationPlugin extends Plugin
|
|||
|
||||
//------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\
|
||||
|
||||
function onStartSetUser(&$user) {
|
||||
function onStartSetUser($user) {
|
||||
$loginAllowed = $this->loginAllowed($user);
|
||||
if($loginAllowed === true){
|
||||
return;
|
||||
|
@ -84,7 +84,7 @@ abstract class AuthorizationPlugin extends Plugin
|
|||
}
|
||||
}
|
||||
|
||||
function onStartSetApiUser(&$user) {
|
||||
function onStartSetApiUser($user) {
|
||||
return $this->onStartSetUser($user);
|
||||
}
|
||||
|
||||
|
|
102
lib/avatarlink.php
Normal file
102
lib/avatarlink.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// XXX: Arg! This wouldn't be necessary if we used Avatars conistently
|
||||
class AvatarLink
|
||||
{
|
||||
public $url;
|
||||
public $type;
|
||||
public $size;
|
||||
public $width;
|
||||
public $height;
|
||||
|
||||
function __construct($element=null)
|
||||
{
|
||||
if ($element) {
|
||||
// @fixme use correct namespaces
|
||||
$this->url = $element->getAttribute('href');
|
||||
$this->type = $element->getAttribute('type');
|
||||
$width = $element->getAttribute('media:width');
|
||||
if ($width != null) {
|
||||
$this->width = intval($width);
|
||||
}
|
||||
$height = $element->getAttribute('media:height');
|
||||
if ($height != null) {
|
||||
$this->height = intval($height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function fromAvatar($avatar)
|
||||
{
|
||||
if (empty($avatar)) {
|
||||
return null;
|
||||
}
|
||||
$alink = new AvatarLink();
|
||||
$alink->type = $avatar->mediatype;
|
||||
$alink->height = $avatar->height;
|
||||
$alink->width = $avatar->width;
|
||||
$alink->url = $avatar->displayUrl();
|
||||
return $alink;
|
||||
}
|
||||
|
||||
static function fromFilename($filename, $size)
|
||||
{
|
||||
$alink = new AvatarLink();
|
||||
$alink->url = $filename;
|
||||
$alink->height = $size;
|
||||
if (!empty($filename)) {
|
||||
$alink->width = $size;
|
||||
$alink->type = self::mediatype($filename);
|
||||
} else {
|
||||
$alink->url = User_group::defaultLogo($size);
|
||||
$alink->type = 'image/png';
|
||||
}
|
||||
return $alink;
|
||||
}
|
||||
|
||||
// yuck!
|
||||
static function mediatype($filename) {
|
||||
$ext = strtolower(end(explode('.', $filename)));
|
||||
if ($ext == 'jpeg') {
|
||||
$ext = 'jpg';
|
||||
}
|
||||
// hope we don't support any others
|
||||
$types = array('png', 'gif', 'jpg', 'jpeg');
|
||||
if (in_array($ext, $types)) {
|
||||
return 'image/' . $ext;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -47,6 +47,25 @@ class Channel
|
|||
}
|
||||
}
|
||||
|
||||
class CLIChannel extends Channel
|
||||
{
|
||||
function source()
|
||||
{
|
||||
return 'cli';
|
||||
}
|
||||
|
||||
function output($user, $text)
|
||||
{
|
||||
$site = common_config('site', 'name');
|
||||
print "[{$user->nickname}@{$site}] $text\n";
|
||||
}
|
||||
|
||||
function error($user, $text)
|
||||
{
|
||||
$this->output($user, $text);
|
||||
}
|
||||
}
|
||||
|
||||
class WebChannel extends Channel
|
||||
{
|
||||
var $out = null;
|
||||
|
|
335
lib/command.php
335
lib/command.php
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, 2009, StatusNet, Inc.
|
||||
* Copyright (C) 2008, 2009, 2010 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
|
||||
|
@ -31,15 +31,147 @@ class Command
|
|||
$this->user = $user;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
/**
|
||||
* Execute the command and send success or error results
|
||||
* back via the given communications channel.
|
||||
*
|
||||
* @param Channel
|
||||
*/
|
||||
public function execute($channel)
|
||||
{
|
||||
try {
|
||||
$this->handle($channel);
|
||||
} catch (CommandException $e) {
|
||||
$channel->error($this->user, $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, "Error handling " . get_class($this) . ": " . $e->getMessage());
|
||||
$channel->error($this->user, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override this with the meat!
|
||||
*
|
||||
* An error to send back to the user may be sent by throwing
|
||||
* a CommandException with a formatted message.
|
||||
*
|
||||
* @param Channel
|
||||
* @throws CommandException
|
||||
*/
|
||||
function handle($channel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a notice from an argument, by poster's name to get last post
|
||||
* or notice_id prefixed with #.
|
||||
*
|
||||
* @return Notice
|
||||
* @throws CommandException
|
||||
*/
|
||||
function getNotice($arg)
|
||||
{
|
||||
$notice = null;
|
||||
if (Event::handle('StartCommandGetNotice', array($this, $arg, &$notice))) {
|
||||
if(substr($this->other,0,1)=='#'){
|
||||
// A specific notice_id #123
|
||||
|
||||
$notice = Notice::staticGet(substr($arg,1));
|
||||
if (!$notice) {
|
||||
throw new CommandException(_('Notice with that id does not exist'));
|
||||
}
|
||||
}
|
||||
|
||||
if (Validate::uri($this->other)) {
|
||||
// A specific notice by URI lookup
|
||||
$notice = Notice::staticGet('uri', $arg);
|
||||
}
|
||||
|
||||
if (!$notice) {
|
||||
// Local or remote profile name to get their last notice.
|
||||
// May throw an exception and report 'no such user'
|
||||
$recipient = $this->getProfile($arg);
|
||||
|
||||
$notice = $recipient->getCurrentNotice();
|
||||
if (!$notice) {
|
||||
throw new CommandException(_('User has no last notice'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::handle('EndCommandGetNotice', array($this, $arg, &$notice));
|
||||
if (!$notice) {
|
||||
throw new CommandException(_('Notice with that id does not exist'));
|
||||
}
|
||||
return $notice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a local or remote profile by nickname.
|
||||
*
|
||||
* @return Profile
|
||||
* @throws CommandException
|
||||
*/
|
||||
function getProfile($arg)
|
||||
{
|
||||
$profile = null;
|
||||
if (Event::handle('StartCommandGetProfile', array($this, $arg, &$profile))) {
|
||||
$profile =
|
||||
common_relative_profile($this->user, common_canonical_nickname($arg));
|
||||
}
|
||||
Event::handle('EndCommandGetProfile', array($this, $arg, &$profile));
|
||||
if (!$profile) {
|
||||
throw new CommandException(sprintf(_('Could not find a user with nickname %s'), $arg));
|
||||
}
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a local user by name
|
||||
* @return User
|
||||
* @throws CommandException
|
||||
*/
|
||||
function getUser($arg)
|
||||
{
|
||||
$user = null;
|
||||
if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) {
|
||||
$user = User::staticGet('nickname', $arg);
|
||||
}
|
||||
Event::handle('EndCommandGetUser', array($this, $arg, &$user));
|
||||
if (!$user){
|
||||
throw new CommandException(sprintf(_('Could not find a local user with nickname %s'),
|
||||
$arg));
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a local or remote group by name.
|
||||
* @return User_group
|
||||
* @throws CommandException
|
||||
*/
|
||||
function getGroup($arg)
|
||||
{
|
||||
$group = null;
|
||||
if (Event::handle('StartCommandGetGroup', array($this, $arg, &$group))) {
|
||||
$group = User_group::getForNickname($arg, $this->user->getProfile());
|
||||
}
|
||||
Event::handle('EndCommandGetGroup', array($this, $arg, &$group));
|
||||
if (!$group) {
|
||||
throw new CommandException(_('No such group.'));
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
}
|
||||
|
||||
class CommandException extends Exception
|
||||
{
|
||||
}
|
||||
|
||||
class UnimplementedCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$channel->error($this->user, _("Sorry, this command is not yet implemented."));
|
||||
}
|
||||
|
@ -81,15 +213,12 @@ class NudgeCommand extends Command
|
|||
parent::__construct($user);
|
||||
$this->other = $other;
|
||||
}
|
||||
function execute($channel)
|
||||
|
||||
function handle($channel)
|
||||
{
|
||||
$recipient = User::staticGet('nickname', $this->other);
|
||||
if(! $recipient){
|
||||
$channel->error($this->user, sprintf(_('Could not find a user with nickname %s'),
|
||||
$this->other));
|
||||
}else{
|
||||
$recipient = $this->getUser($this->other);
|
||||
if ($recipient->id == $this->user->id) {
|
||||
$channel->error($this->user, _('It does not make a lot of sense to nudge yourself!'));
|
||||
throw new CommandException(_('It does not make a lot of sense to nudge yourself!'));
|
||||
} else {
|
||||
if ($recipient->email && $recipient->emailnotifynudge) {
|
||||
mail_notify_nudge($this->user, $recipient);
|
||||
|
@ -101,7 +230,6 @@ class NudgeCommand extends Command
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InviteCommand extends UnimplementedCommand
|
||||
{
|
||||
|
@ -115,7 +243,7 @@ class InviteCommand extends UnimplementedCommand
|
|||
|
||||
class StatsCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$profile = $this->user->getProfile();
|
||||
|
||||
|
@ -142,34 +270,9 @@ class FavCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
if(substr($this->other,0,1)=='#'){
|
||||
//favoriting a specific notice_id
|
||||
|
||||
$notice = Notice::staticGet(substr($this->other,1));
|
||||
if (!$notice) {
|
||||
$channel->error($this->user, _('Notice with that id does not exist'));
|
||||
return;
|
||||
}
|
||||
$recipient = $notice->getProfile();
|
||||
}else{
|
||||
//favoriting a given user's last notice
|
||||
|
||||
$recipient =
|
||||
common_relative_profile($this->user, common_canonical_nickname($this->other));
|
||||
|
||||
if (!$recipient) {
|
||||
$channel->error($this->user, _('No such user.'));
|
||||
return;
|
||||
}
|
||||
$notice = $recipient->getCurrentNotice();
|
||||
if (!$notice) {
|
||||
$channel->error($this->user, _('User has no last notice'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$notice = $this->getNotice($this->other);
|
||||
$fave = Fave::addNew($this->user, $notice);
|
||||
|
||||
if (!$fave) {
|
||||
|
@ -177,7 +280,10 @@ class FavCommand extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
$other = User::staticGet('id', $recipient->id);
|
||||
// @fixme favorite notification should be triggered
|
||||
// at a lower level
|
||||
|
||||
$other = User::staticGet('id', $notice->profile_id);
|
||||
|
||||
if ($other && $other->id != $user->id) {
|
||||
if ($other->email && $other->emailnotifyfav) {
|
||||
|
@ -191,6 +297,7 @@ class FavCommand extends Command
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
class JoinCommand extends Command
|
||||
{
|
||||
var $other = null;
|
||||
|
@ -201,18 +308,11 @@ class JoinCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
|
||||
$nickname = common_canonical_nickname($this->other);
|
||||
$group = User_group::staticGet('nickname', $nickname);
|
||||
$group = $this->getGroup($this->other);
|
||||
$cur = $this->user;
|
||||
|
||||
if (!$group) {
|
||||
$channel->error($cur, _('No such group.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cur->isMember($group)) {
|
||||
$channel->error($cur, _('You are already a member of that group'));
|
||||
return;
|
||||
|
@ -249,11 +349,9 @@ class DropCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
|
||||
$nickname = common_canonical_nickname($this->other);
|
||||
$group = User_group::staticGet('nickname', $nickname);
|
||||
$group = $this->getGroup($this->other);
|
||||
$cur = $this->user;
|
||||
|
||||
if (!$group) {
|
||||
|
@ -293,15 +391,9 @@ class WhoisCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$recipient =
|
||||
common_relative_profile($this->user, common_canonical_nickname($this->other));
|
||||
|
||||
if (!$recipient) {
|
||||
$channel->error($this->user, _('No such user.'));
|
||||
return;
|
||||
}
|
||||
$recipient = $this->getProfile($this->other);
|
||||
|
||||
$whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname,
|
||||
$recipient->profileurl);
|
||||
|
@ -332,9 +424,18 @@ class MessageCommand extends Command
|
|||
$this->text = $text;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$other = User::staticGet('nickname', common_canonical_nickname($this->other));
|
||||
try {
|
||||
$other = $this->getUser($this->other);
|
||||
} catch (CommandException $e) {
|
||||
try {
|
||||
$profile = $this->getProfile($this->other);
|
||||
} catch (CommandException $f) {
|
||||
throw $e;
|
||||
}
|
||||
throw new CommandException(sprintf(_('%s is a remote profile; you can only send direct messages to users on the same server.'), $this->other));
|
||||
}
|
||||
|
||||
$len = mb_strlen($this->text);
|
||||
|
||||
|
@ -380,33 +481,9 @@ class RepeatCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
if(substr($this->other,0,1)=='#'){
|
||||
//repeating a specific notice_id
|
||||
|
||||
$notice = Notice::staticGet(substr($this->other,1));
|
||||
if (!$notice) {
|
||||
$channel->error($this->user, _('Notice with that id does not exist'));
|
||||
return;
|
||||
}
|
||||
$recipient = $notice->getProfile();
|
||||
}else{
|
||||
//repeating a given user's last notice
|
||||
|
||||
$recipient =
|
||||
common_relative_profile($this->user, common_canonical_nickname($this->other));
|
||||
|
||||
if (!$recipient) {
|
||||
$channel->error($this->user, _('No such user.'));
|
||||
return;
|
||||
}
|
||||
$notice = $recipient->getCurrentNotice();
|
||||
if (!$notice) {
|
||||
$channel->error($this->user, _('User has no last notice'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
$notice = $this->getNotice($this->other);
|
||||
|
||||
if($this->user->id == $notice->profile_id)
|
||||
{
|
||||
|
@ -414,7 +491,7 @@ class RepeatCommand extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
if ($recipient->hasRepeated($notice->id)) {
|
||||
if ($this->user->getProfile()->hasRepeated($notice->id)) {
|
||||
$channel->error($this->user, _('Already repeated that notice'));
|
||||
return;
|
||||
}
|
||||
|
@ -441,33 +518,10 @@ class ReplyCommand extends Command
|
|||
$this->text = $text;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
if(substr($this->other,0,1)=='#'){
|
||||
//replying to a specific notice_id
|
||||
|
||||
$notice = Notice::staticGet(substr($this->other,1));
|
||||
if (!$notice) {
|
||||
$channel->error($this->user, _('Notice with that id does not exist'));
|
||||
return;
|
||||
}
|
||||
$notice = $this->getNotice($this->other);
|
||||
$recipient = $notice->getProfile();
|
||||
}else{
|
||||
//replying to a given user's last notice
|
||||
|
||||
$recipient =
|
||||
common_relative_profile($this->user, common_canonical_nickname($this->other));
|
||||
|
||||
if (!$recipient) {
|
||||
$channel->error($this->user, _('No such user.'));
|
||||
return;
|
||||
}
|
||||
$notice = $recipient->getCurrentNotice();
|
||||
if (!$notice) {
|
||||
$channel->error($this->user, _('User has no last notice'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$len = mb_strlen($this->text);
|
||||
|
||||
|
@ -507,17 +561,10 @@ class GetCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$target_nickname = common_canonical_nickname($this->other);
|
||||
$target = $this->getProfile($this->other);
|
||||
|
||||
$target =
|
||||
common_relative_profile($this->user, $target_nickname);
|
||||
|
||||
if (!$target) {
|
||||
$channel->error($this->user, _('No such user.'));
|
||||
return;
|
||||
}
|
||||
$notice = $target->getCurrentNotice();
|
||||
if (!$notice) {
|
||||
$channel->error($this->user, _('User has no last notice'));
|
||||
|
@ -525,7 +572,7 @@ class GetCommand extends Command
|
|||
}
|
||||
$notice_content = $notice->content;
|
||||
|
||||
$channel->output($this->user, $target_nickname . ": " . $notice_content);
|
||||
$channel->output($this->user, $target->nickname . ": " . $notice_content);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,7 +587,7 @@ class SubCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
|
||||
if (!$this->other) {
|
||||
|
@ -548,16 +595,16 @@ class SubCommand extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
$otherUser = User::staticGet('nickname', $this->other);
|
||||
$target = $this->getProfile($this->other);
|
||||
|
||||
if (empty($otherUser)) {
|
||||
$channel->error($this->user, _('No such user'));
|
||||
return;
|
||||
$remote = Remote_profile::staticGet('id', $target->id);
|
||||
if ($remote) {
|
||||
throw new CommandException(_("Can't subscribe to OMB profiles by command."));
|
||||
}
|
||||
|
||||
try {
|
||||
Subscription::start($this->user->getProfile(),
|
||||
$otherUser->getProfile());
|
||||
$target);
|
||||
$channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other));
|
||||
} catch (Exception $e) {
|
||||
$channel->error($this->user, $e->getMessage());
|
||||
|
@ -576,22 +623,18 @@ class UnsubCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
if(!$this->other) {
|
||||
$channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
|
||||
return;
|
||||
}
|
||||
|
||||
$otherUser = User::staticGet('nickname', $this->other);
|
||||
|
||||
if (empty($otherUser)) {
|
||||
$channel->error($this->user, _('No such user'));
|
||||
}
|
||||
$target = $this->getProfile($this->other);
|
||||
|
||||
try {
|
||||
Subscription::cancel($this->user->getProfile(),
|
||||
$otherUser->getProfile());
|
||||
$target);
|
||||
$channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other));
|
||||
} catch (Exception $e) {
|
||||
$channel->error($this->user, $e->getMessage());
|
||||
|
@ -607,7 +650,7 @@ class OffCommand extends Command
|
|||
parent::__construct($user);
|
||||
$this->other = $other;
|
||||
}
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
if ($this->other) {
|
||||
$channel->error($this->user, _("Command not yet implemented."));
|
||||
|
@ -630,7 +673,7 @@ class OnCommand extends Command
|
|||
$this->other = $other;
|
||||
}
|
||||
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
if ($this->other) {
|
||||
$channel->error($this->user, _("Command not yet implemented."));
|
||||
|
@ -646,7 +689,7 @@ class OnCommand extends Command
|
|||
|
||||
class LoginCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$disabled = common_config('logincommand','disabled');
|
||||
$disabled = isset($disabled) && $disabled;
|
||||
|
@ -686,7 +729,7 @@ class LoseCommand extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
$result=subs_unsubscribe_from($this->user, $this->other);
|
||||
$result = Subscription::cancel($this->getProfile($this->other), $this->user->getProfile());
|
||||
|
||||
if ($result) {
|
||||
$channel->output($this->user, sprintf(_('Unsubscribed %s'), $this->other));
|
||||
|
@ -698,7 +741,7 @@ class LoseCommand extends Command
|
|||
|
||||
class SubscriptionsCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$profile = $this->user->getSubscriptions(0);
|
||||
$nicknames=array();
|
||||
|
@ -720,7 +763,7 @@ class SubscriptionsCommand extends Command
|
|||
|
||||
class SubscribersCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$profile = $this->user->getSubscribers();
|
||||
$nicknames=array();
|
||||
|
@ -742,7 +785,7 @@ class SubscribersCommand extends Command
|
|||
|
||||
class GroupsCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$group = $this->user->getGroups();
|
||||
$groups=array();
|
||||
|
@ -763,7 +806,7 @@ class GroupsCommand extends Command
|
|||
|
||||
class HelpCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
function handle($channel)
|
||||
{
|
||||
$channel->output($this->user,
|
||||
_("Commands:\n".
|
||||
|
|
|
@ -71,7 +71,6 @@ if (!function_exists('dl')) {
|
|||
# global configuration object
|
||||
|
||||
require_once('PEAR.php');
|
||||
require_once('PEAR/Exception.php');
|
||||
require_once('DB/DataObject.php');
|
||||
require_once('DB/DataObject/Cast.php'); # for dates
|
||||
|
||||
|
@ -124,22 +123,10 @@ require_once INSTALLDIR.'/lib/util.php';
|
|||
require_once INSTALLDIR.'/lib/action.php';
|
||||
require_once INSTALLDIR.'/lib/mail.php';
|
||||
require_once INSTALLDIR.'/lib/subs.php';
|
||||
require_once INSTALLDIR.'/lib/activity.php';
|
||||
|
||||
require_once INSTALLDIR.'/lib/clientexception.php';
|
||||
require_once INSTALLDIR.'/lib/serverexception.php';
|
||||
|
||||
|
||||
//set PEAR error handling to use regular PHP exceptions
|
||||
function PEAR_ErrorToPEAR_Exception($err)
|
||||
{
|
||||
if ($err->getCode()) {
|
||||
throw new PEAR_Exception($err->getMessage(), $err->getCode());
|
||||
}
|
||||
throw new PEAR_Exception($err->getMessage());
|
||||
}
|
||||
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception');
|
||||
|
||||
try {
|
||||
StatusNet::init(@$server, @$path, @$conffile);
|
||||
} catch (NoConfigException $e) {
|
||||
|
|
|
@ -294,4 +294,6 @@ $default =
|
|||
array('crawldelay' => 0,
|
||||
'disallow' => array('main', 'settings', 'admin', 'search', 'message')
|
||||
),
|
||||
'api' =>
|
||||
array('realm' => null),
|
||||
);
|
||||
|
|
95
lib/deluserqueuehandler.php
Normal file
95
lib/deluserqueuehandler.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Background job to delete prolific users without disrupting front-end too much.
|
||||
*
|
||||
* Up to 50 messages are deleted on each run through; when all messages are gone,
|
||||
* the actual account is deleted.
|
||||
*
|
||||
* @package QueueHandler
|
||||
* @maintainer Brion Vibber <brion@status.net>
|
||||
*/
|
||||
|
||||
class DelUserQueueHandler extends QueueHandler
|
||||
{
|
||||
const DELETION_WINDOW = 50;
|
||||
|
||||
public function transport()
|
||||
{
|
||||
return 'deluser';
|
||||
}
|
||||
|
||||
public function handle($user)
|
||||
{
|
||||
if (!($user instanceof User)) {
|
||||
common_log(LOG_ERR, "Got a bogus user, not deleting");
|
||||
return true;
|
||||
}
|
||||
|
||||
$user = User::staticGet('id', $user->id);
|
||||
if (!$user) {
|
||||
common_log(LOG_INFO, "User {$user->nickname} was deleted before we got here.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$user->hasRole(Profile_role::DELETED)) {
|
||||
common_log(LOG_INFO, "User {$user->nickname} is not pending deletion; aborting.");
|
||||
return true;
|
||||
}
|
||||
|
||||
$notice = $this->getNextBatch($user);
|
||||
if ($notice->N) {
|
||||
common_log(LOG_INFO, "Deleting next {$notice->N} notices by {$user->nickname}");
|
||||
while ($notice->fetch()) {
|
||||
$del = clone($notice);
|
||||
$del->delete();
|
||||
}
|
||||
|
||||
// @todo improve reliability in case we died during the above deletions
|
||||
// with a fatal error. If the job is lost, we should perform some kind
|
||||
// of garbage collection later.
|
||||
|
||||
// Queue up the next batch.
|
||||
$qm = QueueManager::get();
|
||||
$qm->enqueue($user, 'deluser');
|
||||
} else {
|
||||
// Out of notices? Let's finish deleting this guy!
|
||||
$user->delete();
|
||||
common_log(LOG_INFO, "User $user->id $user->nickname deleted.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the next self::DELETION_WINDOW messages for this user.
|
||||
* @return Notice
|
||||
*/
|
||||
protected function getNextBatch(User $user)
|
||||
{
|
||||
$notice = new Notice();
|
||||
$notice->profile_id = $user->id;
|
||||
$notice->limit(self::DELETION_WINDOW);
|
||||
$notice->find();
|
||||
return $notice;
|
||||
}
|
||||
|
||||
}
|
|
@ -120,6 +120,16 @@ class HTTPClient extends HTTP_Request2
|
|||
{
|
||||
$this->config['max_redirs'] = 10;
|
||||
$this->config['follow_redirects'] = true;
|
||||
|
||||
// We've had some issues with keepalive breaking with
|
||||
// HEAD requests, such as to youtube which seems to be
|
||||
// emitting chunked encoding info for an empty body
|
||||
// instead of not emitting anything. This may be a
|
||||
// bug on YouTube's end, but the upstream libray
|
||||
// ought to be investigated to see if we can handle
|
||||
// it gracefully in that case as well.
|
||||
$this->config['protocol_version'] = '1.0';
|
||||
|
||||
parent::__construct($url, $method, $config);
|
||||
$this->setHeader('User-Agent', $this->userAgent());
|
||||
}
|
||||
|
|
|
@ -60,6 +60,19 @@ class ImageFile
|
|||
$this->filepath = $filepath;
|
||||
|
||||
$info = @getimagesize($this->filepath);
|
||||
|
||||
if (!(
|
||||
($info[2] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) ||
|
||||
($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
|
||||
$info[2] == IMAGETYPE_BMP ||
|
||||
($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
|
||||
($info[2] == IMAGETYPE_XBM && function_exists('imagecreatefromxbm')) ||
|
||||
($info[2] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')))) {
|
||||
|
||||
throw new Exception(_('Unsupported image file format.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->type = ($info) ? $info[2]:$type;
|
||||
$this->width = ($info) ? $info[0]:$width;
|
||||
$this->height = ($info) ? $info[1]:$height;
|
||||
|
@ -97,19 +110,6 @@ class ImageFile
|
|||
return;
|
||||
}
|
||||
|
||||
if ($info[2] !== IMAGETYPE_GIF &&
|
||||
$info[2] !== IMAGETYPE_JPEG &&
|
||||
$info[2] !== IMAGETYPE_BMP &&
|
||||
$info[2] !== IMAGETYPE_WBMP &&
|
||||
$info[2] !== IMAGETYPE_XBM &&
|
||||
$info[2] !== IMAGETYPE_XPM &&
|
||||
$info[2] !== IMAGETYPE_PNG) {
|
||||
|
||||
@unlink($_FILES[$param]['tmp_name']);
|
||||
throw new Exception(_('Unsupported image file format.'));
|
||||
return;
|
||||
}
|
||||
|
||||
return new ImageFile(null, $_FILES[$param]['tmp_name']);
|
||||
}
|
||||
|
||||
|
@ -159,9 +159,6 @@ class ImageFile
|
|||
case IMAGETYPE_XBM:
|
||||
$image_src = imagecreatefromxbm($this->filepath);
|
||||
break;
|
||||
case IMAGETYPE_XPM:
|
||||
$image_src = imagecreatefromxpm($this->filepath);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(_('Unknown file type'));
|
||||
return;
|
||||
|
@ -204,10 +201,6 @@ class ImageFile
|
|||
//we don't want to save XBM... it's a rare format that we can't guarantee clients will support
|
||||
//save png instead
|
||||
$this->type = IMAGETYPE_PNG;
|
||||
} else if($this->type == IMAGETYPE_XPM) {
|
||||
//we don't want to save XPM... it's a rare format that we can't guarantee clients will support
|
||||
//save png instead
|
||||
$this->type = IMAGETYPE_PNG;
|
||||
}
|
||||
|
||||
$outname = Avatar::filename($this->id,
|
||||
|
|
|
@ -202,17 +202,20 @@ function _mdomain($backtrace)
|
|||
static $cached;
|
||||
$path = $backtrace[0]['file'];
|
||||
if (!isset($cached[$path])) {
|
||||
$final = 'statusnet'; // assume default domain
|
||||
if (DIRECTORY_SEPARATOR !== '/') {
|
||||
$path = strtr($path, DIRECTORY_SEPARATOR, '/');
|
||||
}
|
||||
$cut = strpos($path, '/plugins/') + 9;
|
||||
$cut = strpos($path, '/plugins/');
|
||||
if ($cut) {
|
||||
$cut += strlen('/plugins/');
|
||||
$cut2 = strpos($path, '/', $cut);
|
||||
if ($cut && $cut2) {
|
||||
$cached[$path] = substr($path, $cut, $cut2 - $cut);
|
||||
} else {
|
||||
return null;
|
||||
$final = substr($path, $cut, $cut2 - $cut);
|
||||
}
|
||||
}
|
||||
$cached[$path] = $final;
|
||||
}
|
||||
return $cached[$path];
|
||||
}
|
||||
|
||||
|
|
|
@ -90,15 +90,24 @@ class MysqlSchema extends Schema
|
|||
* @param string $name Name of the table to get
|
||||
*
|
||||
* @return TableDef tabledef for that table.
|
||||
* @throws SchemaTableMissingException
|
||||
*/
|
||||
|
||||
public function getTableDef($name)
|
||||
{
|
||||
$res = $this->conn->query('DESCRIBE ' . $name);
|
||||
$query = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS " .
|
||||
"WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'";
|
||||
$schema = $this->conn->dsn['database'];
|
||||
$sql = sprintf($query, $schema, $name);
|
||||
$res = $this->conn->query($sql);
|
||||
|
||||
if (PEAR::isError($res)) {
|
||||
throw new Exception($res->getMessage());
|
||||
}
|
||||
if ($res->numRows() == 0) {
|
||||
$res->free();
|
||||
throw new SchemaTableMissingException("No such table: $name");
|
||||
}
|
||||
|
||||
$td = new TableDef();
|
||||
|
||||
|
@ -111,9 +120,9 @@ class MysqlSchema extends Schema
|
|||
|
||||
$cd = new ColumnDef();
|
||||
|
||||
$cd->name = $row['Field'];
|
||||
$cd->name = $row['COLUMN_NAME'];
|
||||
|
||||
$packed = $row['Type'];
|
||||
$packed = $row['COLUMN_TYPE'];
|
||||
|
||||
if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) {
|
||||
$cd->type = $match[1];
|
||||
|
@ -122,17 +131,57 @@ class MysqlSchema extends Schema
|
|||
$cd->type = $packed;
|
||||
}
|
||||
|
||||
$cd->nullable = ($row['Null'] == 'YES') ? true : false;
|
||||
$cd->key = $row['Key'];
|
||||
$cd->default = $row['Default'];
|
||||
$cd->extra = $row['Extra'];
|
||||
$cd->nullable = ($row['IS_NULLABLE'] == 'YES') ? true : false;
|
||||
$cd->key = $row['COLUMN_KEY'];
|
||||
$cd->default = $row['COLUMN_DEFAULT'];
|
||||
$cd->extra = $row['EXTRA'];
|
||||
|
||||
// Autoincrement is stuck into the extra column.
|
||||
// Pull it out so we don't accidentally mod it every time...
|
||||
$extra = preg_replace('/(^|\s)auto_increment(\s|$)/i', '$1$2', $cd->extra);
|
||||
if ($extra != $cd->extra) {
|
||||
$cd->extra = trim($extra);
|
||||
$cd->auto_increment = true;
|
||||
}
|
||||
|
||||
// mysql extensions -- not (yet) used by base class
|
||||
$cd->charset = $row['CHARACTER_SET_NAME'];
|
||||
$cd->collate = $row['COLLATION_NAME'];
|
||||
|
||||
$td->columns[] = $cd;
|
||||
}
|
||||
$res->free();
|
||||
|
||||
return $td;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the given table properties from INFORMATION_SCHEMA.
|
||||
* Most of the good stuff is MySQL extensions.
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception if table info can't be looked up
|
||||
*/
|
||||
|
||||
function getTableProperties($table, $props)
|
||||
{
|
||||
$query = "SELECT %s FROM INFORMATION_SCHEMA.TABLES " .
|
||||
"WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'";
|
||||
$schema = $this->conn->dsn['database'];
|
||||
$sql = sprintf($query, implode(',', $props), $schema, $table);
|
||||
$res = $this->conn->query($sql);
|
||||
|
||||
$row = array();
|
||||
$ok = $res->fetchInto($row, DB_FETCHMODE_ASSOC);
|
||||
$res->free();
|
||||
|
||||
if ($ok) {
|
||||
return $row;
|
||||
} else {
|
||||
throw new SchemaTableMissingException("No such table: $table");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a ColumnDef object for a single column.
|
||||
*
|
||||
|
@ -185,35 +234,26 @@ class MysqlSchema extends Schema
|
|||
}
|
||||
|
||||
$sql .= $this->_columnSql($cd);
|
||||
|
||||
switch ($cd->key) {
|
||||
case 'UNI':
|
||||
$uniques[] = $cd->name;
|
||||
break;
|
||||
case 'PRI':
|
||||
$primary[] = $cd->name;
|
||||
break;
|
||||
case 'MUL':
|
||||
$indices[] = $cd->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($primary) > 0) { // it really should be...
|
||||
$sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")";
|
||||
$idx = $this->_indexList($columns);
|
||||
|
||||
if ($idx['primary']) {
|
||||
$sql .= ",\nconstraint primary key (" . implode(',', $idx['primary']) . ")";
|
||||
}
|
||||
|
||||
foreach ($uniques as $u) {
|
||||
$sql .= ",\nunique index {$name}_{$u}_idx ($u)";
|
||||
foreach ($idx['uniques'] as $u) {
|
||||
$key = $this->_uniqueKey($name, $u);
|
||||
$sql .= ",\nunique index $key ($u)";
|
||||
}
|
||||
|
||||
foreach ($indices as $i) {
|
||||
$sql .= ",\nindex {$name}_{$i}_idx ($i)";
|
||||
foreach ($idx['indices'] as $i) {
|
||||
$key = $this->_key($name, $i);
|
||||
$sql .= ",\nindex $key ($i)";
|
||||
}
|
||||
|
||||
$sql .= "); ";
|
||||
$sql .= ") ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ";
|
||||
|
||||
common_log(LOG_INFO, $sql);
|
||||
$res = $this->conn->query($sql);
|
||||
|
||||
if (PEAR::isError($res)) {
|
||||
|
@ -223,6 +263,47 @@ class MysqlSchema extends Schema
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look over a list of column definitions and list up which
|
||||
* indices will be present
|
||||
*/
|
||||
private function _indexList(array $columns)
|
||||
{
|
||||
$list = array('uniques' => array(),
|
||||
'primary' => array(),
|
||||
'indices' => array());
|
||||
foreach ($columns as $cd) {
|
||||
switch ($cd->key) {
|
||||
case 'UNI':
|
||||
$list['uniques'][] = $cd->name;
|
||||
break;
|
||||
case 'PRI':
|
||||
$list['primary'][] = $cd->name;
|
||||
break;
|
||||
case 'MUL':
|
||||
$list['indices'][] = $cd->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique index key name for a given column on this table
|
||||
*/
|
||||
function _uniqueKey($tableName, $columnName)
|
||||
{
|
||||
return $this->_key($tableName, $columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index key name for a given column on this table
|
||||
*/
|
||||
function _key($tableName, $columnName)
|
||||
{
|
||||
return "{$tableName}_{$columnName}_idx";
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a table from the schema
|
||||
*
|
||||
|
@ -394,21 +475,20 @@ class MysqlSchema extends Schema
|
|||
|
||||
try {
|
||||
$td = $this->getTableDef($tableName);
|
||||
} catch (Exception $e) {
|
||||
if (preg_match('/no such table/', $e->getMessage())) {
|
||||
} catch (SchemaTableMissingException $e) {
|
||||
return $this->createTable($tableName, $columns);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$cur = $this->_names($td->columns);
|
||||
$new = $this->_names($columns);
|
||||
|
||||
$dropIndex = array();
|
||||
$toadd = array_diff($new, $cur);
|
||||
$todrop = array_diff($cur, $new);
|
||||
$same = array_intersect($new, $cur);
|
||||
$tomod = array();
|
||||
$addIndex = array();
|
||||
$tableProps = array();
|
||||
|
||||
foreach ($same as $m) {
|
||||
$curCol = $this->_byName($td->columns, $m);
|
||||
|
@ -416,10 +496,64 @@ class MysqlSchema extends Schema
|
|||
|
||||
if (!$newCol->equals($curCol)) {
|
||||
$tomod[] = $newCol->name;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Earlier versions may have accidentally left tables at default
|
||||
// charsets which might be latin1 or other freakish things.
|
||||
if ($this->_isString($curCol)) {
|
||||
if ($curCol->charset != 'utf8') {
|
||||
$tomod[] = $newCol->name;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($toadd) + count($todrop) + count($tomod) == 0) {
|
||||
// Find any indices we have to change...
|
||||
$curIdx = $this->_indexList($td->columns);
|
||||
$newIdx = $this->_indexList($columns);
|
||||
|
||||
if ($curIdx['primary'] != $newIdx['primary']) {
|
||||
if ($curIdx['primary']) {
|
||||
$dropIndex[] = 'drop primary key';
|
||||
}
|
||||
if ($newIdx['primary']) {
|
||||
$keys = implode(',', $newIdx['primary']);
|
||||
$addIndex[] = "add constraint primary key ($keys)";
|
||||
}
|
||||
}
|
||||
|
||||
$dropUnique = array_diff($curIdx['uniques'], $newIdx['uniques']);
|
||||
$addUnique = array_diff($newIdx['uniques'], $curIdx['uniques']);
|
||||
foreach ($dropUnique as $columnName) {
|
||||
$dropIndex[] = 'drop key ' . $this->_uniqueKey($tableName, $columnName);
|
||||
}
|
||||
foreach ($addUnique as $columnName) {
|
||||
$addIndex[] = 'add constraint unique key ' . $this->_uniqueKey($tableName, $columnName) . " ($columnName)";;
|
||||
}
|
||||
|
||||
$dropMultiple = array_diff($curIdx['indices'], $newIdx['indices']);
|
||||
$addMultiple = array_diff($newIdx['indices'], $curIdx['indices']);
|
||||
foreach ($dropMultiple as $columnName) {
|
||||
$dropIndex[] = 'drop key ' . $this->_key($tableName, $columnName);
|
||||
}
|
||||
foreach ($addMultiple as $columnName) {
|
||||
$addIndex[] = 'add key ' . $this->_key($tableName, $columnName) . " ($columnName)";
|
||||
}
|
||||
|
||||
// Check for table properties: make sure we're using a sane
|
||||
// engine type and charset/collation.
|
||||
// @fixme make the default engine configurable?
|
||||
$oldProps = $this->getTableProperties($tableName, array('ENGINE', 'TABLE_COLLATION'));
|
||||
if (strtolower($oldProps['ENGINE']) != 'innodb') {
|
||||
$tableProps['ENGINE'] = 'InnoDB';
|
||||
}
|
||||
if (strtolower($oldProps['TABLE_COLLATION']) != 'utf8_bin') {
|
||||
$tableProps['DEFAULT CHARSET'] = 'utf8';
|
||||
$tableProps['COLLATE'] = 'utf8_bin';
|
||||
}
|
||||
|
||||
if (count($dropIndex) + count($toadd) + count($todrop) + count($tomod) + count($addIndex) + count($tableProps) == 0) {
|
||||
// nothing to do
|
||||
return true;
|
||||
}
|
||||
|
@ -429,6 +563,10 @@ class MysqlSchema extends Schema
|
|||
|
||||
$phrase = array();
|
||||
|
||||
foreach ($dropIndex as $indexSql) {
|
||||
$phrase[] = $indexSql;
|
||||
}
|
||||
|
||||
foreach ($toadd as $columnName) {
|
||||
$cd = $this->_byName($columns, $columnName);
|
||||
|
||||
|
@ -445,8 +583,17 @@ class MysqlSchema extends Schema
|
|||
$phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd);
|
||||
}
|
||||
|
||||
foreach ($addIndex as $indexSql) {
|
||||
$phrase[] = $indexSql;
|
||||
}
|
||||
|
||||
foreach ($tableProps as $key => $val) {
|
||||
$phrase[] = "$key=$val";
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase);
|
||||
|
||||
common_log(LOG_DEBUG, __METHOD__ . ': ' . $sql);
|
||||
$res = $this->conn->query($sql);
|
||||
|
||||
if (PEAR::isError($res)) {
|
||||
|
@ -519,6 +666,10 @@ class MysqlSchema extends Schema
|
|||
$sql .= "{$cd->type} ";
|
||||
}
|
||||
|
||||
if ($this->_isString($cd)) {
|
||||
$sql .= " CHARACTER SET utf8 ";
|
||||
}
|
||||
|
||||
if (!empty($cd->default)) {
|
||||
$sql .= "default {$cd->default} ";
|
||||
} else {
|
||||
|
@ -535,4 +686,13 @@ class MysqlSchema extends Schema
|
|||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this column a string type?
|
||||
*/
|
||||
private function _isString(ColumnDef $cd)
|
||||
{
|
||||
$strings = array('char', 'varchar', 'text');
|
||||
return in_array(strtolower($cd->type), $strings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -442,11 +442,14 @@ class NoticeListItem extends Widget
|
|||
'title' => $latlon),
|
||||
$name);
|
||||
} else {
|
||||
$this->out->elementStart('a', array('href' => $url));
|
||||
$this->out->element('abbr', array('class' => 'geo',
|
||||
$xstr = new XMLStringer(false);
|
||||
$xstr->elementStart('a', array('href' => $url,
|
||||
'rel' => 'external'));
|
||||
$xstr->element('abbr', array('class' => 'geo',
|
||||
'title' => $latlon),
|
||||
$name);
|
||||
$this->out->elementEnd('a');
|
||||
$xstr->elementEnd('a');
|
||||
$this->out->raw($xstr->getString());
|
||||
}
|
||||
$this->out->elementEnd('span');
|
||||
}
|
||||
|
|
|
@ -61,7 +61,8 @@ class PgsqlSchema extends Schema
|
|||
|
||||
public function getTableDef($name)
|
||||
{
|
||||
$res = $this->conn->query("select *, column_default as default, is_nullable as Null, udt_name as Type, column_name AS Field from INFORMATION_SCHEMA.COLUMNS where table_name = '$name'");
|
||||
$res = $this->conn->query("SELECT *, column_default as default, is_nullable as Null,
|
||||
udt_name as Type, column_name AS Field from INFORMATION_SCHEMA.COLUMNS where table_name = '$name'");
|
||||
|
||||
if (PEAR::isError($res)) {
|
||||
throw new Exception($res->getMessage());
|
||||
|
@ -72,6 +73,9 @@ class PgsqlSchema extends Schema
|
|||
$td->name = $name;
|
||||
$td->columns = array();
|
||||
|
||||
if ($res->numRows() == 0 ) {
|
||||
throw new Exception('no such table'); //pretend to be the msyql error. yeah, this sucks.
|
||||
}
|
||||
$row = array();
|
||||
|
||||
while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) {
|
||||
|
@ -166,12 +170,10 @@ class PgsqlSchema extends Schema
|
|||
}
|
||||
|
||||
if (count($primary) > 0) { // it really should be...
|
||||
$sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")";
|
||||
$sql .= ",\n primary key (" . implode(',', $primary) . ")";
|
||||
}
|
||||
|
||||
foreach ($uniques as $u) {
|
||||
$sql .= ",\nunique index {$name}_{$u}_idx ($u)";
|
||||
}
|
||||
|
||||
|
||||
foreach ($indices as $i) {
|
||||
$sql .= ",\nindex {$name}_{$i}_idx ($i)";
|
||||
|
@ -179,6 +181,10 @@ class PgsqlSchema extends Schema
|
|||
|
||||
$sql .= "); ";
|
||||
|
||||
|
||||
foreach ($uniques as $u) {
|
||||
$sql .= "\n CREATE index {$name}_{$u}_idx ON {$name} ($u); ";
|
||||
}
|
||||
$res = $this->conn->query($sql);
|
||||
|
||||
if (PEAR::isError($res)) {
|
||||
|
@ -209,6 +215,22 @@ class PgsqlSchema extends Schema
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the (mostly) mysql-ish column types into somethings more standard
|
||||
* @param string column type
|
||||
*
|
||||
* @return string postgres happy column type
|
||||
*/
|
||||
private function _columnTypeTranslation($type) {
|
||||
$map = array(
|
||||
'datetime' => 'timestamp'
|
||||
);
|
||||
if(!empty($map[$type])) {
|
||||
return $map[$type];
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an index to a table.
|
||||
*
|
||||
|
@ -359,6 +381,7 @@ class PgsqlSchema extends Schema
|
|||
|
||||
try {
|
||||
$td = $this->getTableDef($tableName);
|
||||
|
||||
} catch (Exception $e) {
|
||||
if (preg_match('/no such table/', $e->getMessage())) {
|
||||
return $this->createTable($tableName, $columns);
|
||||
|
@ -477,11 +500,12 @@ class PgsqlSchema extends Schema
|
|||
private function _columnSql($cd)
|
||||
{
|
||||
$sql = "{$cd->name} ";
|
||||
$type = $this->_columnTypeTranslation($cd->type);
|
||||
|
||||
if (!empty($cd->size)) {
|
||||
$sql .= "{$cd->type}({$cd->size}) ";
|
||||
$sql .= "{$type}({$cd->size}) ";
|
||||
} else {
|
||||
$sql .= "{$cd->type} ";
|
||||
$sql .= "{$type} ";
|
||||
}
|
||||
|
||||
if (!empty($cd->default)) {
|
||||
|
|
240
lib/poco.php
Normal file
240
lib/poco.php
Normal file
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class PoCo
|
||||
{
|
||||
const NS = 'http://portablecontacts.net/spec/1.0';
|
||||
|
||||
const USERNAME = 'preferredUsername';
|
||||
const DISPLAYNAME = 'displayName';
|
||||
const NOTE = 'note';
|
||||
|
||||
public $preferredUsername;
|
||||
public $displayName;
|
||||
public $note;
|
||||
public $address;
|
||||
public $urls = array();
|
||||
|
||||
function __construct($element = null)
|
||||
{
|
||||
if (empty($element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->preferredUsername = ActivityUtils::childContent(
|
||||
$element,
|
||||
self::USERNAME,
|
||||
self::NS
|
||||
);
|
||||
|
||||
$this->displayName = ActivityUtils::childContent(
|
||||
$element,
|
||||
self::DISPLAYNAME,
|
||||
self::NS
|
||||
);
|
||||
|
||||
$this->note = ActivityUtils::childContent(
|
||||
$element,
|
||||
self::NOTE,
|
||||
self::NS
|
||||
);
|
||||
|
||||
$this->address = $this->_getAddress($element);
|
||||
$this->urls = $this->_getURLs($element);
|
||||
}
|
||||
|
||||
private function _getURLs($element)
|
||||
{
|
||||
$urlEls = $element->getElementsByTagnameNS(self::NS, PoCoURL::URLS);
|
||||
$urls = array();
|
||||
|
||||
foreach ($urlEls as $urlEl) {
|
||||
|
||||
$type = ActivityUtils::childContent(
|
||||
$urlEl,
|
||||
PoCoURL::TYPE,
|
||||
PoCo::NS
|
||||
);
|
||||
|
||||
$value = ActivityUtils::childContent(
|
||||
$urlEl,
|
||||
PoCoURL::VALUE,
|
||||
PoCo::NS
|
||||
);
|
||||
|
||||
$primary = ActivityUtils::childContent(
|
||||
$urlEl,
|
||||
PoCoURL::PRIMARY,
|
||||
PoCo::NS
|
||||
);
|
||||
|
||||
$isPrimary = false;
|
||||
|
||||
if (isset($primary) && $primary == 'true') {
|
||||
$isPrimary = true;
|
||||
}
|
||||
|
||||
// @todo check to make sure a primary hasn't already been added
|
||||
|
||||
array_push($urls, new PoCoURL($type, $value, $isPrimary));
|
||||
}
|
||||
return $urls;
|
||||
}
|
||||
|
||||
private function _getAddress($element)
|
||||
{
|
||||
$addressEl = ActivityUtils::child(
|
||||
$element,
|
||||
PoCoAddress::ADDRESS,
|
||||
PoCo::NS
|
||||
);
|
||||
|
||||
if (!empty($addressEl)) {
|
||||
$formatted = ActivityUtils::childContent(
|
||||
$addressEl,
|
||||
PoCoAddress::FORMATTED,
|
||||
self::NS
|
||||
);
|
||||
|
||||
if (!empty($formatted)) {
|
||||
$address = new PoCoAddress();
|
||||
$address->formatted = $formatted;
|
||||
return $address;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function fromProfile($profile)
|
||||
{
|
||||
if (empty($profile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$poco = new PoCo();
|
||||
|
||||
$poco->preferredUsername = $profile->nickname;
|
||||
$poco->displayName = $profile->getBestName();
|
||||
|
||||
$poco->note = $profile->bio;
|
||||
|
||||
$paddy = new PoCoAddress();
|
||||
$paddy->formatted = $profile->location;
|
||||
$poco->address = $paddy;
|
||||
|
||||
if (!empty($profile->homepage)) {
|
||||
array_push(
|
||||
$poco->urls,
|
||||
new PoCoURL(
|
||||
'homepage',
|
||||
$profile->homepage,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $poco;
|
||||
}
|
||||
|
||||
function fromGroup($group)
|
||||
{
|
||||
if (empty($group)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$poco = new PoCo();
|
||||
|
||||
$poco->preferredUsername = $group->nickname;
|
||||
$poco->displayName = $group->getBestName();
|
||||
|
||||
$poco->note = $group->description;
|
||||
|
||||
$paddy = new PoCoAddress();
|
||||
$paddy->formatted = $group->location;
|
||||
$poco->address = $paddy;
|
||||
|
||||
if (!empty($group->homepage)) {
|
||||
array_push(
|
||||
$poco->urls,
|
||||
new PoCoURL(
|
||||
'homepage',
|
||||
$group->homepage,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $poco;
|
||||
}
|
||||
|
||||
function getPrimaryURL()
|
||||
{
|
||||
foreach ($this->urls as $url) {
|
||||
if ($url->primary) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function asString()
|
||||
{
|
||||
$xs = new XMLStringer(true);
|
||||
$xs->element(
|
||||
'poco:preferredUsername',
|
||||
null,
|
||||
$this->preferredUsername
|
||||
);
|
||||
|
||||
$xs->element(
|
||||
'poco:displayName',
|
||||
null,
|
||||
$this->displayName
|
||||
);
|
||||
|
||||
if (!empty($this->note)) {
|
||||
$xs->element('poco:note', null, common_xml_safe_str($this->note));
|
||||
}
|
||||
|
||||
if (!empty($this->address)) {
|
||||
$xs->raw($this->address->asString());
|
||||
}
|
||||
|
||||
foreach ($this->urls as $url) {
|
||||
$xs->raw($url->asString());
|
||||
}
|
||||
|
||||
return $xs->getString();
|
||||
}
|
||||
}
|
56
lib/pocoaddress.php
Normal file
56
lib/pocoaddress.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class PoCoAddress
|
||||
{
|
||||
const ADDRESS = 'address';
|
||||
const FORMATTED = 'formatted';
|
||||
|
||||
public $formatted;
|
||||
|
||||
// @todo Other address fields
|
||||
|
||||
function asString()
|
||||
{
|
||||
if (!empty($this->formatted)) {
|
||||
$xs = new XMLStringer(true);
|
||||
$xs->elementStart('poco:address');
|
||||
$xs->element('poco:formatted', null, common_xml_safe_str($this->formatted));
|
||||
$xs->elementEnd('poco:address');
|
||||
return $xs->getString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
65
lib/pocourl.php
Normal file
65
lib/pocourl.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* An activity
|
||||
*
|
||||
* 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 Feed
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class PoCoURL
|
||||
{
|
||||
const URLS = 'urls';
|
||||
const TYPE = 'type';
|
||||
const VALUE = 'value';
|
||||
const PRIMARY = 'primary';
|
||||
|
||||
public $type;
|
||||
public $value;
|
||||
public $primary;
|
||||
|
||||
function __construct($type, $value, $primary = false)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->value = $value;
|
||||
$this->primary = $primary;
|
||||
}
|
||||
|
||||
function asString()
|
||||
{
|
||||
$xs = new XMLStringer(true);
|
||||
$xs->elementStart('poco:urls');
|
||||
$xs->element('poco:type', null, $this->type);
|
||||
$xs->element('poco:value', null, $this->value);
|
||||
if (!empty($this->primary)) {
|
||||
$xs->element('poco:primary', null, 'true');
|
||||
}
|
||||
$xs->elementEnd('poco:urls');
|
||||
return $xs->getString();
|
||||
}
|
||||
}
|
84
lib/processmanager.php
Normal file
84
lib/processmanager.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* i/o manager to watch for a dead parent process
|
||||
*
|
||||
* 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 QueueManager
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 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/
|
||||
*/
|
||||
|
||||
class ProcessManager extends IoManager
|
||||
{
|
||||
protected $socket;
|
||||
|
||||
public static function get()
|
||||
{
|
||||
throw new Exception("Must pass ProcessManager per-instance");
|
||||
}
|
||||
|
||||
public function __construct($socket)
|
||||
{
|
||||
$this->socket = $socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the i/o queue master if and how we can handle multi-site
|
||||
* processes.
|
||||
*
|
||||
* Return one of:
|
||||
* IoManager::SINGLE_ONLY
|
||||
* IoManager::INSTANCE_PER_SITE
|
||||
* IoManager::INSTANCE_PER_PROCESS
|
||||
*/
|
||||
public static function multiSite()
|
||||
{
|
||||
return IoManager::INSTANCE_PER_PROCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* We won't get any input on it, but if it's broken we'll
|
||||
* know something's gone horribly awry.
|
||||
*
|
||||
* @return array of resources
|
||||
*/
|
||||
function getSockets()
|
||||
{
|
||||
return array($this->socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the parent died and request a shutdown...
|
||||
*
|
||||
* @param resource $socket
|
||||
* @return boolean success
|
||||
*/
|
||||
function handleInput($socket)
|
||||
{
|
||||
if (feof($socket)) {
|
||||
common_log(LOG_INFO, "Parent process exited; shutting down child.");
|
||||
$this->master->requestShutdown();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -239,6 +239,9 @@ abstract class QueueManager extends IoManager
|
|||
$this->connect('sms', 'SmsQueueHandler');
|
||||
}
|
||||
|
||||
// Background user management tasks...
|
||||
$this->connect('deluser', 'DelUserQueueHandler');
|
||||
|
||||
// Broadcasting profile updates to OMB remote subscribers
|
||||
$this->connect('profile', 'ProfileQueueHandler');
|
||||
|
||||
|
|
|
@ -628,6 +628,12 @@ class Router
|
|||
array('action' => 'ApiTimelineTag',
|
||||
'format' => '(xmljson|rss|atom)'));
|
||||
|
||||
// media related
|
||||
$m->connect(
|
||||
'api/statusnet/media/upload',
|
||||
array('action' => 'ApiMediaUpload')
|
||||
);
|
||||
|
||||
// search
|
||||
$m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
|
||||
$m->connect('api/search.json', array('action' => 'twitapisearchjson'));
|
||||
|
|
|
@ -485,3 +485,9 @@ class Schema
|
|||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
class SchemaTableMissingException extends Exception
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
|
|
|
@ -62,15 +62,18 @@ class ServerErrorAction extends ErrorAction
|
|||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported');
|
||||
|
||||
function __construct($message='Error', $code=500)
|
||||
function __construct($message='Error', $code=500, $ex=null)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
|
||||
$this->default = 500;
|
||||
|
||||
// Server errors must be logged.
|
||||
|
||||
common_log(LOG_ERR, "ServerErrorAction: $code $message");
|
||||
$log = "ServerErrorAction: $code $message";
|
||||
if ($ex) {
|
||||
$log .= "\n" . $ex->getTraceAsString();
|
||||
}
|
||||
common_log(LOG_ERR, $log);
|
||||
}
|
||||
|
||||
// XXX: Should these error actions even be invokable via URI?
|
||||
|
|
|
@ -71,6 +71,8 @@ abstract class SpawningDaemon extends Daemon
|
|||
*/
|
||||
function run()
|
||||
{
|
||||
$this->initPipes();
|
||||
|
||||
$children = array();
|
||||
for ($i = 1; $i <= $this->threads; $i++) {
|
||||
$pid = pcntl_fork();
|
||||
|
@ -128,6 +130,34 @@ abstract class SpawningDaemon extends Daemon
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IPC socket pair which child processes can use to detect
|
||||
* if the parent process has been killed.
|
||||
*/
|
||||
function initPipes()
|
||||
{
|
||||
$sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
|
||||
if ($sockets) {
|
||||
$this->parentWriter = $sockets[0];
|
||||
$this->parentReader = $sockets[1];
|
||||
} else {
|
||||
$this->log(LOG_ERROR, "Couldn't create inter-process sockets");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an IOManager that simply ensures that we have a connection
|
||||
* to the parent process open. If it breaks, the child process will
|
||||
* die.
|
||||
*
|
||||
* @return ProcessManager
|
||||
*/
|
||||
public function processManager()
|
||||
{
|
||||
return new ProcessManager($this->parentReader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to respawn an exited subprocess based on its exit code.
|
||||
* Otherwise we'll respawn all exits by default.
|
||||
|
@ -152,6 +182,8 @@ abstract class SpawningDaemon extends Daemon
|
|||
*/
|
||||
protected function initAndRunChild($thread)
|
||||
{
|
||||
// Close the writer end of our parent<->children pipe.
|
||||
fclose($this->parentWriter);
|
||||
$this->set_id($this->get_id() . "." . $thread);
|
||||
$this->resetDb();
|
||||
$exitCode = $this->runThread();
|
||||
|
|
|
@ -341,12 +341,8 @@ class StatusNet
|
|||
// Backwards compatibility
|
||||
if (array_key_exists('memcached', $config)) {
|
||||
if ($config['memcached']['enabled']) {
|
||||
if(class_exists('Memcached')) {
|
||||
addPlugin('Memcached', array('servers' => $config['memcached']['server']));
|
||||
} else {
|
||||
addPlugin('Memcache', array('servers' => $config['memcached']['server']));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($config['memcached']['base'])) {
|
||||
$config['cache']['base'] = $config['memcached']['base'];
|
||||
|
|
43
lib/subs.php
43
lib/subs.php
|
@ -43,46 +43,3 @@ function subs_unsubscribe_to($user, $other)
|
|||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function subs_unsubscribe_from($user, $other){
|
||||
$local = User::staticGet("nickname",$other);
|
||||
if($local){
|
||||
return subs_unsubscribe_to($local,$user);
|
||||
} else {
|
||||
try {
|
||||
$remote = Profile::staticGet("nickname",$other);
|
||||
if(is_string($remote)){
|
||||
return $remote;
|
||||
}
|
||||
if (Event::handle('StartUnsubscribe', array($remote,$user))) {
|
||||
|
||||
$sub = DB_DataObject::factory('subscription');
|
||||
|
||||
$sub->subscriber = $remote->id;
|
||||
$sub->subscribed = $user->id;
|
||||
|
||||
$sub->find(true);
|
||||
|
||||
// note we checked for existence above
|
||||
|
||||
if (!$sub->delete())
|
||||
return _('Couldn\'t delete subscription.');
|
||||
|
||||
$cache = common_memcache();
|
||||
|
||||
if ($cache) {
|
||||
$cache->delete(common_cache_key('user:notices_with_friends:' . $remote->id));
|
||||
}
|
||||
|
||||
|
||||
$user->blowSubscribersCount();
|
||||
$remote->blowSubscribersCount();
|
||||
|
||||
Event::handle('EndUnsubscribe', array($remote, $user));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
74
lib/usernoprofileexception.php
Normal file
74
lib/usernoprofileexception.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* class for an exception when the user profile is missing
|
||||
*
|
||||
* 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 Exception
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for an exception when the user profile is missing
|
||||
*
|
||||
* @category Exception
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class UserNoProfileException extends ServerException
|
||||
{
|
||||
var $user = null;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param User $user User that's missing a profile
|
||||
*/
|
||||
|
||||
public function __construct($user)
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
$message = sprintf(_("User %s (%d) has no profile record."),
|
||||
$user->nickname, $user->id);
|
||||
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for user
|
||||
*
|
||||
* @return User the user that triggered this exception
|
||||
*/
|
||||
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
|
@ -71,7 +71,8 @@ class UserProfile extends Widget
|
|||
{
|
||||
if (Event::handle('StartProfilePageProfileSection', array(&$this->out, $this->profile))) {
|
||||
|
||||
$this->out->elementStart('div', 'entity_profile vcard author');
|
||||
$this->out->elementStart('div', array('id' => 'i',
|
||||
'class' => 'entity_profile vcard author'));
|
||||
$this->out->element('h2', null, _('User profile'));
|
||||
|
||||
if (Event::handle('StartProfilePageProfileElements', array(&$this->out, $this->profile))) {
|
||||
|
@ -228,6 +229,17 @@ class UserProfile extends Widget
|
|||
|
||||
function showEntityActions()
|
||||
{
|
||||
if ($this->profile->hasRole(Profile_role::DELETED)) {
|
||||
$this->out->elementStart('div', 'entity_actions');
|
||||
$this->out->element('h2', null, _('User actions'));
|
||||
$this->out->elementStart('ul');
|
||||
$this->out->elementStart('p', array('class' => 'profile_deleted'));
|
||||
$this->out->text(_('User deletion in progress...'));
|
||||
$this->out->elementEnd('p');
|
||||
$this->out->elementEnd('ul');
|
||||
$this->out->elementEnd('div');
|
||||
return;
|
||||
}
|
||||
if (Event::handle('StartProfilePageActionsSection', array(&$this->out, $this->profile))) {
|
||||
|
||||
$cur = common_current_user();
|
||||
|
|
53
lib/util.php
53
lib/util.php
|
@ -52,17 +52,43 @@ function common_init_language()
|
|||
{
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
// gettext seems very picky... We first need to setlocale()
|
||||
// to a locale which _does_ exist on the system, and _then_
|
||||
// we can set in another locale that may not be set up
|
||||
// (say, ga_ES for Galego/Galician) it seems to take it.
|
||||
common_init_locale("en_US");
|
||||
|
||||
// Note that this setlocale() call may "fail" but this is harmless;
|
||||
// gettext will still select the right language.
|
||||
$language = common_language();
|
||||
$locale_set = common_init_locale($language);
|
||||
|
||||
if (!$locale_set) {
|
||||
// The requested locale doesn't exist on the system.
|
||||
//
|
||||
// gettext seems very picky... We first need to setlocale()
|
||||
// to a locale which _does_ exist on the system, and _then_
|
||||
// we can set in another locale that may not be set up
|
||||
// (say, ga_ES for Galego/Galician) it seems to take it.
|
||||
//
|
||||
// For some reason C and POSIX which are guaranteed to work
|
||||
// don't do the job. en_US.UTF-8 should be there most of the
|
||||
// time, but not guaranteed.
|
||||
$ok = common_init_locale("en_US");
|
||||
if (!$ok) {
|
||||
// Try to find a complete, working locale...
|
||||
// @fixme shelling out feels awfully inefficient
|
||||
// but I don't think there's a more standard way.
|
||||
$all = `locale -a`;
|
||||
foreach (explode("\n", $all) as $locale) {
|
||||
if (preg_match('/\.utf[-_]?8$/i', $locale)) {
|
||||
$ok = setlocale(LC_ALL, $locale);
|
||||
if ($ok) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$ok) {
|
||||
common_log(LOG_ERR, "Unable to find a UTF-8 locale on this system; UI translations may not work.");
|
||||
}
|
||||
}
|
||||
$locale_set = common_init_locale($language);
|
||||
}
|
||||
|
||||
setlocale(LC_CTYPE, 'C');
|
||||
// So we do not have to make people install the gettext locales
|
||||
$path = common_config('site','locale_path');
|
||||
|
@ -133,6 +159,11 @@ function common_munge_password($password, $id)
|
|||
|
||||
function common_check_user($nickname, $password)
|
||||
{
|
||||
// empty nickname always unacceptable
|
||||
if (empty($nickname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$authenticatedUser = false;
|
||||
|
||||
if (Event::handle('StartCheckPassword', array($nickname, $password, &$authenticatedUser))) {
|
||||
|
@ -1453,7 +1484,15 @@ function common_copy_args($from)
|
|||
$to = array();
|
||||
$strip = get_magic_quotes_gpc();
|
||||
foreach ($from as $k => $v) {
|
||||
$to[$k] = ($strip) ? stripslashes($v) : $v;
|
||||
if($strip) {
|
||||
if(is_array($v)) {
|
||||
$to[$k] = common_copy_args($v);
|
||||
} else {
|
||||
$to[$k] = stripslashes($v);
|
||||
}
|
||||
} else {
|
||||
$to[$k] = $v;
|
||||
}
|
||||
}
|
||||
return $to;
|
||||
}
|
||||
|
|
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
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user