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

Commit d3b10959 authored by Evan Prodromou's avatar Evan Prodromou

Merge branch '0.9.x' into noactor

parents c78170a2 26afe79e
......@@ -969,9 +969,12 @@ EndRevokeRole: when a role has been revoked
StartAtomPubNewActivity: When a new activity comes in through Atom Pub API
- &$activity: received activity
- $user: user publishing the entry
- &$notice: notice created; initially null, can be set
EndAtomPubNewActivity: When a new activity comes in through Atom Pub API
- $activity: received activity
- $user: user publishing the entry
- $notice: notice that was created
StartXrdActionAliases: About to set aliases for the XRD object for a user
......@@ -1023,3 +1026,22 @@ StartActivityObjectFromGroup: When converting a group to an activity:object
EndActivityObjectFromGroup: After converting a group to an activity:object
- $group: The group being converted
- &$object: The finished object. Tweak as needed.
StartImportActivity: when we start to import an activity
- $user: User to make the author import
- $author: Author of the feed; good for comparisons
- $activity: The current activity
- $trusted: How "trusted" the process is
- &$done: Return value; whether to continue
EndImportActivity: when we finish importing an activity
- $user: User to make the author import
- $author: Author of the feed; good for comparisons
- $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)
......@@ -324,7 +324,9 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$activity = new Activity($dom->documentElement);
if (Event::handle('StartAtomPubNewActivity', array(&$activity))) {
$saved = null;
if (Event::handle('StartAtomPubNewActivity', array(&$activity, $this->user, &$saved))) {
if ($activity->verb != ActivityVerb::POST) {
// TRANS: Client error displayed when not using the POST verb.
......@@ -347,7 +349,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$saved = $this->postNote($activity);
Event::handle('EndAtomPubNewActivity', array($activity, $saved));
Event::handle('EndAtomPubNewActivity', array($activity, $this->user, $saved));
}
if (!empty($saved)) {
......
......@@ -66,6 +66,13 @@ class NewgroupAction extends Action
return false;
}
$user = common_current_user();
$profile = $user->getProfile();
if (!$profile->hasRight(Right::CREATEGROUP)) {
// TRANS: Client exception thrown when a user tries to create a group while banned.
throw new ClientException(_('You are not allowed to create groups on this site.'), 403);
}
return true;
}
......
......@@ -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) {
......
......@@ -109,6 +109,11 @@ class Notice extends Memcached_DataObject
// @fixme we have some cases where things get re-run and so the
// insert fails.
$deleted = Deleted_notice::staticGet('id', $this->id);
if (!$deleted) {
$deleted = Deleted_notice::staticGet('uri', $this->uri);
}
if (!$deleted) {
$deleted = new Deleted_notice();
......@@ -130,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
......@@ -1780,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();
......@@ -2033,7 +2054,7 @@ class Notice extends Memcached_DataObject
*/
public static function addWhereSinceId(DB_DataObject $obj, $id, $idField='id', $createdField='created')
{
$since = self::whereSinceId($id);
$since = self::whereSinceId($id, $idField, $createdField);
if ($since) {
$obj->whereAdd($since);
}
......@@ -2072,7 +2093,7 @@ class Notice extends Memcached_DataObject
*/
public static function addWhereMaxId(DB_DataObject $obj, $id, $idField='id', $createdField='created')
{
$max = self::whereMaxId($id);
$max = self::whereMaxId($id, $idField, $createdField);
if ($max) {
$obj->whereAdd($max);
}
......
......@@ -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;
}
}
......@@ -850,6 +850,7 @@ class Profile extends Memcached_DataObject
case Right::NEWNOTICE:
case Right::NEWMESSAGE:
case Right::SUBSCRIBE:
case Right::CREATEGROUP:
$result = !$this->isSilenced();
break;
case Right::PUBLICNOTICE:
......
......@@ -476,6 +476,16 @@ class User_group extends Memcached_DataObject
}
static function register($fields) {
if (!empty($fields['userid'])) {
$profile = Profile::staticGet('id', $fields['userid']);
if ($profile && !$profile->hasRight(Right::CREATEGROUP)) {
common_log(LOG_WARNING, "Attempted group creation from banned user: " . $profile->nickname);
// TRANS: Client exception thrown when a user tries to create a group while banned.
throw new ClientException(_('You are not allowed to create groups on this site.'), 403);
}
}
// MAGICALLY put fields into current scope
extract($fields);
......
......@@ -63,31 +63,40 @@ class ActivityImporter extends QueueHandler
$this->trusted = $trusted;
try {
switch ($activity->verb) {
case ActivityVerb::FOLLOW:
$this->subscribeProfile($user, $author, $activity);
break;
case ActivityVerb::JOIN:
$this->joinGroup($user, $activity);
break;
case ActivityVerb::POST:
$this->postNote($user, $author, $activity);
break;
default:
throw new Exception("Unknown verb: {$activity->verb}");
$done = null;
if (Event::handle('StartImportActivity',
array($user, $author, $activity, $trusted, &$done))) {
try {
switch ($activity->verb) {
case ActivityVerb::FOLLOW:
$this->subscribeProfile($user, $author, $activity);
break;
case ActivityVerb::JOIN:
$this->joinGroup($user, $activity);
break;
case ActivityVerb::POST:
$this->postNote($user, $author, $activity);
break;
default:
throw new ClientException("Unknown verb: {$activity->verb}");
}
Event::handle('EndImportActivity',
array($user, $author, $activity, $trusted));
$done = true;
} catch (ClientException $ce) {
common_log(LOG_WARNING, $ce->getMessage());
$done = true;
} catch (ServerException $se) {
common_log(LOG_ERR, $se->getMessage());
$done = false;
} catch (Exception $e) {
common_log(LOG_ERR, $e->getMessage());
$done = false;
}
} catch (ClientException $ce) {
common_log(LOG_WARNING, $ce->getMessage());
return true;
} catch (ServerException $se) {
common_log(LOG_ERR, $se->getMessage());
return false;
} catch (Exception $e) {
common_log(LOG_ERR, $e->getMessage());
return false;
}
return true;
return $done;
}
function subscribeProfile($user, $author, $activity)
......
......@@ -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
......
......@@ -65,5 +65,6 @@ class Right
const RESTOREACCOUNT = 'restoreaccount';
const DELETEACCOUNT = 'deleteaccount';
const MOVEACCOUNT = 'moveaccount';
const CREATEGROUP = 'creategroup';
}
<?php
/**
* Data class to mark notices as bookmarks
*
* PHP version 5
*
* @category Data
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* For storing the fact that a notice is a bookmark
*
* @category Bookmark
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
* @see DB_DataObject
*/
class Bookmark extends Memcached_DataObject
{
public $__table = 'bookmark'; // table name
public $profile_id; // int(4) primary_key not_null
public $url; // varchar(255) primary_key not_null
public $title; // varchar(255)
public $description; // text
public $uri; // varchar(255)
public $url_crc32; // int(4) not_null
public $created; // datetime
/**
* Get an instance by key
*
* This is a utility method to get a single instance with a given key value.
*
* @param string $k Key to use to lookup (usually 'user_id' for this class)
* @param mixed $v Value to lookup
*
* @return User_greeting_count object found, or null for no hits
*
*/
function staticGet($k, $v=null)
{
return Memcached_DataObject::staticGet('Bookmark', $k, $v);
}
/**
* Get an instance by compound key
*
* This is a utility method to get a single instance with a given set of
* key-value pairs. Usually used for the primary key for a compound key; thus
* the name.
*
* @param array $kv array of key-value mappings
*
* @return Bookmark object found, or null for no hits
*
*/
function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Bookmark', $kv);
}
/**
* return table definition for DB_DataObject
*
* DB_DataObject needs to know something about the table to manipulate
* instances. This method provides all the DB_DataObject needs to know.
*
* @return array array of column definitions
*/
function table()
{
return array('profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'url' => DB_DATAOBJECT_STR,
'title' => DB_DATAOBJECT_STR,
'description' => DB_DATAOBJECT_STR,
'uri' => DB_DATAOBJECT_STR,
'url_crc32' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE +
DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
}
/**
* return key definitions for DB_DataObject
*
* @return array list of key field names
*/
function keys()
{
return array_keys($this->keyTypes());
}
/**
* return key definitions for Memcached_DataObject
*
* @return array associative array of key definitions
*/
function keyTypes()
{
return array('profile_id' => 'K',
'url' => 'K',
'uri' => 'U');
}
/**
* Magic formula for non-autoincrementing integer primary keys
*
* @return array magic three-false array that stops auto-incrementing.
*/
function sequenceKey()
{
return array(false, false, false);
}
/**
* Get a bookmark based on a notice
*
* @param Notice $notice Notice to check for
*
* @return Bookmark found bookmark or null
*/
function getByNotice($notice)
{
return self::staticGet('uri', $notice->uri);
}
/**
* Get the bookmark that a user made for an URL
*
* @param Profile $profile Profile to check for
* @param string $url URL to check for
*
* @return Bookmark bookmark found or null
*/
static function getByURL($profile, $url)
{
return self::pkeyGet(array('profile_id' => $profile->id,
'url' => $url));
return null;
}
/**
* Get the bookmark that a user made for an URL
*
* @param Profile $profile Profile to check for
* @param integer $crc32 CRC-32 of URL to check for
*
* @return array Bookmark objects found (usually 1 or 0)
*/
static function getByCRC32($profile, $crc32)
{
$bookmarks = array();