We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

Commit 32eb4c5e authored by Evan Prodromou's avatar Evan Prodromou

Merge remote branch 'gitorious/0.9.x' into 1.0.x

Conflicts:
	lib/common.php
parents e211e622 c91b080a
......@@ -1058,3 +1058,8 @@ EndImportActivity: when we finish importing an activity
- $activity: The current activity
- $trusted: How "trusted" the process is
StartProfileSettingsActions: when we're showing account-management action list
- $action: Action being shown (use for output)
EndProfileSettingsActions: when we're showing account-management action list
- $action: Action being shown (use for output)
......@@ -125,10 +125,6 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
header('Content-Type: application/atom+xml; charset=utf-8');
try {
$atom->addAuthorRaw($this->group->asAtomAuthor());
$atom->setActivitySubject($this->group->asActivitySubject());
$atom->setId($self);
$atom->setSelfLink($self);
$atom->addEntryFromNotices($this->notices);
$this->raw($atom->getString());
} catch (Atom10FeedException $e) {
......
......@@ -458,27 +458,32 @@ class ProfilesettingsAction extends AccountSettingsAction
$this->elementStart('div', array('id' => 'aside_primary',
'class' => 'aside'));
if ($user->hasRight(Right::BACKUPACCOUNT)) {
$this->elementStart('li');
$this->element('a',
array('href' => common_local_url('backupaccount')),
_('Backup account'));
$this->elementEnd('li');
}
if ($user->hasRight(Right::DELETEACCOUNT)) {
$this->elementStart('li');
$this->element('a',
array('href' => common_local_url('deleteaccount')),
_('Delete account'));
$this->elementEnd('li');
}
if ($user->hasRight(Right::RESTOREACCOUNT)) {
$this->elementStart('li');
$this->element('a',
array('href' => common_local_url('restoreaccount')),
_('Restore account'));
$this->elementEnd('li');
$this->elementStart('ul');
if (Event::handle('StartProfileSettingsActions', array($this))) {
if ($user->hasRight(Right::BACKUPACCOUNT)) {
$this->elementStart('li');
$this->element('a',
array('href' => common_local_url('backupaccount')),
_('Backup account'));
$this->elementEnd('li');
}
if ($user->hasRight(Right::DELETEACCOUNT)) {
$this->elementStart('li');
$this->element('a',
array('href' => common_local_url('deleteaccount')),
_('Delete account'));
$this->elementEnd('li');
}
if ($user->hasRight(Right::RESTOREACCOUNT)) {
$this->elementStart('li');
$this->element('a',
array('href' => common_local_url('restoreaccount')),
_('Restore account'));
$this->elementEnd('li');
}
Event::handle('EndProfileSettingsActions', array($this));
}
$this->elementEnd('ul');
$this->elementEnd('div');
}
}
......@@ -412,4 +412,102 @@ class File extends Memcached_DataObject
{
return File_thumbnail::staticGet('file_id', $this->id);
}
/**
* Blow the cache of notices that link to this URL
*
* @param boolean $last Whether to blow the "last" cache too
*
* @return void
*/
function blowCache($last=false)
{
self::blow('file:notice-ids:%s', $this->url);
if ($last) {
self::blow('file:notice-ids:%s;last', $this->url);
}
self::blow('file:notice-count:%d', $this->id);
}
/**
* Stream of notices linking to this URL
*
* @param integer $offset Offset to show; default is 0
* @param integer $limit Limit of notices to show
* @param integer $since_id Since this notice
* @param integer $max_id Before this notice
*
* @return array ids of notices that link to this file
*/
function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$ids = Notice::stream(array($this, '_streamDirect'),
array(),
'file:notice-ids:'.$this->url,
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids);
}
/**
* Stream of notices linking to this URL
*
* @param integer $offset Offset to show; default is 0
* @param integer $limit Limit of notices to show
* @param integer $since_id Since this notice
* @param integer $max_id Before this notice
*
* @return array ids of notices that link to this file
*/
function _streamDirect($offset, $limit, $since_id, $max_id)
{
$f2p = new File_to_post();
$f2p->selectAdd();
$f2p->selectAdd('post_id');
$f2p->file_id = $this->id;
Notice::addWhereSinceId($f2p, $since_id, 'post_id', 'modified');
Notice::addWhereMaxId($f2p, $max_id, 'post_id', 'modified');
$f2p->orderBy('modified DESC, post_id DESC');
if (!is_null($offset)) {
$f2p->limit($offset, $limit);
}
$ids = array();
if ($f2p->find()) {
while ($f2p->fetch()) {
$ids[] = $f2p->post_id;
}
}
return $ids;
}
function noticeCount()
{
$cacheKey = sprintf('file:notice-count:%d', $this->id);
$count = self::cacheGet($cacheKey);
if ($count === false) {
$f2p = new File_to_post();
$f2p->file_id = $this->id;
$count = $f2p->count();
self::cacheSet($cacheKey, $count);
}
return $count;
}
}
......@@ -52,6 +52,12 @@ class File_to_post extends Memcached_DataObject
$f2p->file_id = $file_id;
$f2p->post_id = $notice_id;
$f2p->insert();
$f = File::staticGet($file_id);
if (!empty($f)) {
$f->blowCache();
}
}
if (empty($seen[$notice_id])) {
......@@ -66,4 +72,13 @@ class File_to_post extends Memcached_DataObject
{
return Memcached_DataObject::pkeyGet('File_to_post', $kv);
}
function delete()
{
$f = File::staticGet('id', $this->file_id);
if (!empty($f)) {
$f->blowCache();
}
return parent::delete();
}
}
......@@ -74,7 +74,7 @@ class Memcached_DataObject extends Safe_DataObject
return $i;
} else {
$i = DB_DataObject::factory($cls);
if (empty($i)) {
if (empty($i) || PEAR::isError($i)) {
return false;
}
foreach ($kv as $k => $v) {
......
......@@ -135,6 +135,7 @@ class Notice extends Memcached_DataObject
$this->clearFaves();
$this->clearTags();
$this->clearGroupInboxes();
$this->clearFiles();
// NOTE: we don't clear inboxes
// NOTE: we don't clear queue items
......@@ -1785,6 +1786,21 @@ class Notice extends Memcached_DataObject
$reply->free();
}
function clearFiles()
{
$f2p = new File_to_post();
$f2p->post_id = $this->id;
if ($f2p->find()) {
while ($f2p->fetch()) {
$f2p->delete();
}
}
// FIXME: decide whether to delete File objects
// ...and related (actual) files
}
function clearRepeats()
{
$repeatNotice = new Notice();
......
......@@ -87,4 +87,19 @@ class Notice_tag extends Memcached_DataObject
{
return Memcached_DataObject::pkeyGet('Notice_tag', $kv);
}
static function url($tag)
{
if (common_config('singleuser', 'enabled')) {
// regular TagAction isn't set up in 1user mode
$nickname = User::singleUserNickname();
$url = common_local_url('showstream',
array('nickname' => $nickname,
'tag' => $tag));
} else {
$url = common_local_url('tag', array('tag' => $tag));
}
return $url;
}
}
......@@ -377,13 +377,7 @@ class Activity
$xs->element('updated', null, $published);
if ($author) {
$xs->elementStart('author');
$xs->element('uri', array(), $this->actor->id);
if ($this->actor->title) {
$xs->element('name', array(), $this->actor->title);
}
$xs->elementEnd('author');
$this->actor->outputTo($xs, 'activity:actor');
$this->actor->outputTo($xs, 'author');
}
if ($this->verb != ActivityVerb::POST || count($this->objects) != 1) {
......@@ -446,7 +440,7 @@ class Activity
}
foreach ($this->categories as $cat) {
$xs->raw($cat->asString());
$cat->outputTo($xs);
}
// can be either URLs or enclosure objects
......
......@@ -172,10 +172,39 @@ class ActivityObject
private function _fromAuthor($element)
{
$this->type = self::PERSON; // XXX: is this fair?
$this->title = $this->_childContent($element, self::NAME);
$this->type = $this->_childContent($element,
Activity::OBJECTTYPE,
Activity::SPEC);
$this->id = $this->_childContent($element, self::URI);
if (empty($this->type)) {
$this->type = self::PERSON; // XXX: is this fair?
}
// start with <atom:title>
$title = ActivityUtils::childHtmlContent($element, self::TITLE);
if (!empty($title)) {
$this->title = html_entity_decode(strip_tags($title), ENT_QUOTES, 'UTF-8');
}
// fall back to <atom:name>
if (empty($this->title)) {
$this->title = $this->_childContent($element, self::NAME);
}
// start with <atom:id>
$this->id = $this->_childContent($element, self::ID);
// fall back to <atom:uri>
if (empty($this->id)) {
$this->id = $this->_childContent($element, self::URI);
}
// fall further back to <atom:email>
if (empty($this->id)) {
$email = $this->_childContent($element, self::EMAIL);
......@@ -184,6 +213,14 @@ class ActivityObject
$this->id = 'mailto:'.$email;
}
}
$this->link = ActivityUtils::getPermalink($element);
// fall finally back to <link rel=alternate>
if (empty($this->id) && !empty($this->link)) { // fallback if there's no ID
$this->id = $this->link;
}
}
private function _fromAtomEntry($element)
......@@ -498,14 +535,21 @@ class ActivityObject
$xo->element('activity:object-type', null, $this->type);
$xo->element(self::ID, null, $this->id);
// <author> uses URI
if ($tag == 'author') {
$xo->element(self::URI, null, $this->id);
} else {
$xo->element(self::ID, null, $this->id);
}
if (!empty($this->title)) {
$xo->element(
self::TITLE,
null,
common_xml_safe_str($this->title)
);
$name = common_xml_safe_str($this->title);
if ($tag == 'author') {
$xo->element(self::NAME, null, $name);
} else {
$xo->element(self::TITLE, null, $name);
}
}
if (!empty($this->summary)) {
......@@ -563,7 +607,7 @@ class ActivityObject
}
if (!empty($this->poco)) {
$xo->raw($this->poco->asString());
$this->poco->outputTo($xo);
}
foreach ($this->extra as $el) {
......
......@@ -146,15 +146,16 @@ class Atom10Feed extends XMLStringer
}
/**
* Add a activity feed subject via raw XML string
* Deprecated <activity:subject>; ignored
*
* @param string $xmlSubject An XML string representation of the subject
*
* @return void
*/
function setActivitySubject($xmlSubject)
{
$this->subject = $xmlSubject;
throw new ServerException(_('Don\'t use this method!'));
}
function getNamespaces()
......
......@@ -59,6 +59,13 @@ class AtomCategory
}
function asString()
{
$xs = new XMLStringer();
$this->outputTo($xs);
return $xs->getString();
}
function outputTo($xo)
{
$attribs = array();
if ($this->term !== null) {
......@@ -70,8 +77,6 @@ class AtomCategory
if ($this->label !== null) {
$attribs['label'] = $this->label;
}
$xs = new XMLStringer();
$xs->element('category', $attribs);
return $xs->getString();
$xo->element('category', $attribs);
}
}
......@@ -85,8 +85,14 @@ class AtomGroupNoticeFeed extends AtomNoticeFeed
$this->setId($self);
$this->setSelfLink($self);
$this->addAuthorRaw($group->asAtomAuthor());
$this->setActivitySubject($group->asActivitySubject());
// For groups, we generate an author _AND_ an <activity:subject>
// Versions of StatusNet under 0.9.7 treat <author> as a person
// XXX: remove this workaround in future versions
$ao = ActivityObject::fromGroup($group);
$this->addAuthorRaw($ao->asString('author').
$ao->asString('activity:subject'));
$this->addLink($group->homeUrl());
}
......
......@@ -60,8 +60,8 @@ class AtomUserNoticeFeed extends AtomNoticeFeed
$this->user = $user;
if (!empty($user)) {
$profile = $user->getProfile();
$this->addAuthor($profile->nickname, $user->uri);
$this->setActivitySubject($profile->asActivityNoun('subject'));
$ao = ActivityObject::fromProfile($profile);
$this->addAuthorRaw($ao->asString('author'));
}
// TRANS: Title in atom user notice feed. %s is a user name.
......
......@@ -299,6 +299,10 @@ class oEmbedHelper
class oEmbedHelper_Exception extends Exception
{
public function __construct($message = "", $code = 0, $previous = null)
{
parent::__construct($message, $code);
}
}
class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception
......
......@@ -211,30 +211,34 @@ class PoCo
function asString()
{
$xs = new XMLStringer(true);
$xs->element(
$this->outputTo($xs);
return $xs->getString();
}
function outputTo($xo)
{
$xo->element(
'poco:preferredUsername',
null,
$this->preferredUsername
);
$xs->element(
$xo->element(
'poco:displayName',
null,
$this->displayName
);
if (!empty($this->note)) {
$xs->element('poco:note', null, common_xml_safe_str($this->note));
$xo->element('poco:note', null, common_xml_safe_str($this->note));
}
if (!empty($this->address)) {
$xs->raw($this->address->asString());
$this->address->outputTo($xo);
}
foreach ($this->urls as $url) {
$xs->raw($url->asString());
$url->outputTo($xo);
}
return $xs->getString();
}
}
......@@ -42,15 +42,18 @@ class PoCoAddress
// @todo Other address fields
function asString()
{
$xs = new XMLStringer(true);
$this->outputTo($xs);
return $xs->getString();
}
function outputTo($xo)
{
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();
$xo->elementStart('poco:address');
$xo->element('poco:formatted', null, common_xml_safe_str($this->formatted));
$xo->elementEnd('poco:address');
}
return null;
}
}
......@@ -53,13 +53,18 @@ class PoCoURL
function asString()
{
$xs = new XMLStringer(true);
$xs->elementStart('poco:urls');
$xs->element('poco:type', null, $this->type);
$xs->element('poco:value', null, $this->value);
$this->outputTo($xs);
return $xs->getString();
}
function outputTo($xo)
{
$xo->elementStart('poco:urls');
$xo->element('poco:type', null, $this->type);
$xo->element('poco:value', null, $this->value);
if (!empty($this->primary)) {
$xs->element('poco:primary', null, 'true');
$xo->element('poco:primary', null, 'true');
}
$xs->elementEnd('poco:urls');
return $xs->getString();
$xo->elementEnd('poco:urls');
}
}
This diff is collapsed.
This diff is collapsed.
.bookmark_tags li { display: inline; }
.bookmark_mentions li { display: inline; }
.bookmark_avatar { float: left }
.bookmark_notice_count { float: right }
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* Form for adding a new bookmark
*
* PHP version 5
*
* 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 Bookmark
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Form to add a new bookmark
*
* @category Bookmark
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class BookmarkForm extends Form
{
private $_title = null;
private $_url = null;
private $_tags = null;
private $_description = null;
/**
* Construct a bookmark form
*
* @param HTMLOutputter $out output channel
* @param string $title Title of the bookmark
* @param string $url URL of the bookmark
* @param string $tags Tags to show
* @param string $description Description of the bookmark
*
* @return void
*/
function __construct($out=null, $title=null, $url=