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

Commit a51d2ece authored by Evan Prodromou's avatar Evan Prodromou

Merge remote-tracking branch 'origin/1.1.x' into 1.1.x

parents d1fae16c 4e3fb638

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.
......@@ -1425,3 +1425,28 @@ EndUpgrade: when ending a site upgrade; good place to do your own upgrades
HaveIMPlugin: is there an IM plugin loaded?
- &$haveIMPlugin: set me to true if you're loaded!
StartShowNoticeOptionItems: Before showing first controls in a notice list item; inside the div
- $nli: NoticeListItem being shown
EndShowNoticeOptionItems: After showing last controls in a notice list item; inside the div
- $nli: NoticeListItem being shown
StartNoticeInScope: Before checking if a notice should be visible to a user
- $notice: The notice to check
- $profile: The profile to check for scope
- &$bResult: The boolean result; fill this in if you want to skip
EndNoticeInScope: After checking if a notice should be visible to a user
- $notice: The notice to check
- $profile: The profile to check for scope
- &$bResult: The boolean result; overwrite this if you so desire
StartNoticeListPrefill: Before pre-filling a list of notices with extra data
- &$notices: Notices to be pre-filled
- $avatarSize: The avatar size for the list
EndNoticeListPrefill: After pre-filling a list of notices with extra data
- &$notices: Notices that were pre-filled
- &$profiles: Profiles that were pre-filled
- $avatarSize: The avatar size for the list
......@@ -92,8 +92,6 @@ class ApiSearchAtomAction extends ApiPrivateAuthAction
*/
function prepare($args)
{
common_debug("in apisearchatom prepare()");
parent::prepare($args);
$this->query = $this->trimmed('q');
......
......@@ -63,8 +63,6 @@ class ApiSearchJSONAction extends ApiPrivateAuthAction
*/
function prepare($args)
{
common_debug("apisearchjson prepare()");
parent::prepare($args);
$this->query = $this->trimmed('q');
......
......@@ -185,7 +185,7 @@ class ProfilesettingsAction extends SettingsAction
$this->checkbox('autosubscribe',
// TRANS: Checkbox label in form for profile settings.
_('Automatically subscribe to whoever '.
'subscribes to me (best for non-humans).'),
'subscribes to me (best for non-humans)'),
($this->arg('autosubscribe')) ?
$this->boolean('autosubscribe') : $user->autosubscribe);
$this->elementEnd('li');
......
......@@ -159,6 +159,11 @@ class PublicAction extends Action
$this->element('link', array('rel' => 'EditURI',
'type' => 'application/rsd+xml',
'href' => $rsd));
if ($this->page != 1) {
$this->element('link', array('rel' => 'canonical',
'href' => common_local_url('public')));
}
}
/**
......
......@@ -233,4 +233,12 @@ class ShowgroupAction extends GroupAction
$this->raw(common_markup_to_html($m));
$this->elementEnd('div');
}
function extraHead()
{
if ($this->page != 1) {
$this->element('link', array('rel' => 'canonical',
'href' => $this->group->homeUrl()));
}
}
}
......@@ -212,6 +212,11 @@ class ShowstreamAction extends ProfileAction
$this->element('link', array('rel' => 'EditURI',
'type' => 'application/rsd+xml',
'href' => $rsd));
if ($this->page != 1) {
$this->element('link', array('rel' => 'canonical',
'href' => $this->profile->profileurl));
}
}
function showEmptyListMessage()
......
......@@ -29,8 +29,6 @@ class UserrssAction extends Rss10Action
function prepare($args)
{
common_debug("UserrssAction");
parent::prepare($args);
$nickname = $this->trimmed('nickname');
$this->user = User::staticGet('nickname', $nickname);
......
......@@ -217,13 +217,20 @@ class File extends Managed_DataObject
function isRespectsQuota($user,$fileSize) {
if ($fileSize > common_config('attachments', 'file_quota')) {
// TRANS: Message used to be inserted as %2$s in the text "No file may
// TRANS: be larger than %1$d byte and the file you sent was %2$s.".
// TRANS: %1$d is the number of bytes of an uploaded file.
$fileSizeText = sprintf(_m('%1$d byte','%1$d bytes',$fileSize),$fileSize);
$fileQuota = common_config('attachments', 'file_quota');
// TRANS: Message given if an upload is larger than the configured maximum.
// TRANS: %1$d is the byte limit for uploads, %2$d is the byte count for the uploaded file.
// TRANS: %1$s is used for plural.
return sprintf(_m('No file may be larger than %1$d byte and the file you sent was %2$d bytes. Try to upload a smaller version.',
'No file may be larger than %1$d bytes and the file you sent was %2$d bytes. Try to upload a smaller version.',
common_config('attachments', 'file_quota')),
common_config('attachments', 'file_quota'), $fileSize);
// TRANS: %1$d (used for plural) is the byte limit for uploads,
// TRANS: %2$s is the proper form of "n bytes". This is the only ways to have
// TRANS: gettext support multiple plurals in the same message, unfortunately...
return sprintf(_m('No file may be larger than %1$d byte and the file you sent was %2$s. Try to upload a smaller version.',
'No file may be larger than %1$d bytes and the file you sent was %2$s. Try to upload a smaller version.',
$fileQuota),
$fileQuota, $fileSizeText);
}
$query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'";
......
......@@ -1008,10 +1008,7 @@ class Notice extends Managed_DataObject
$users = $ptag->getUserSubscribers();
foreach ($users as $id) {
if (!array_key_exists($id, $ni)) {
$user = User::staticGet('id', $id);
if (!$user->hasBlocked($profile)) {
$ni[$id] = NOTICE_INBOX_SOURCE_PROFILE_TAG;
}
$ni[$id] = NOTICE_INBOX_SOURCE_PROFILE_TAG;
}
}
}
......@@ -1020,23 +1017,24 @@ class Notice extends Managed_DataObject
if (!array_key_exists($recipient, $ni)) {
$ni[$recipient] = NOTICE_INBOX_SOURCE_REPLY;
}
}
// Exclude any deleted, non-local, or blocking recipients.
$profile = $this->getProfile();
$originalProfile = null;
if ($this->repeat_of) {
// Check blocks against the original notice's poster as well.
$original = Notice::staticGet('id', $this->repeat_of);
if ($original) {
$originalProfile = $original->getProfile();
}
// Exclude any deleted, non-local, or blocking recipients.
$profile = $this->getProfile();
$originalProfile = null;
if ($this->repeat_of) {
// Check blocks against the original notice's poster as well.
$original = Notice::staticGet('id', $this->repeat_of);
if ($original) {
$originalProfile = $original->getProfile();
}
foreach ($ni as $id => $source) {
$user = User::staticGet('id', $id);
if (empty($user) || $user->hasBlocked($profile) ||
($originalProfile && $user->hasBlocked($originalProfile))) {
unset($ni[$id]);
}
}
foreach ($ni as $id => $source) {
$user = User::staticGet('id', $id);
if (empty($user) || $user->hasBlocked($profile) ||
($originalProfile && $user->hasBlocked($originalProfile))) {
unset($ni[$id]);
}
}
......@@ -2365,7 +2363,11 @@ class Notice extends Managed_DataObject
$result = self::cacheGet($keypart);
if ($result === false) {
$bResult = $this->_inScope($profile);
$bResult = false;
if (Event::handle('StartNoticeInScope', array($this, $profile, &$bResult))) {
$bResult = $this->_inScope($profile);
Event::handle('EndNoticeInScope', array($this, $profile, &$bResult));
}
$result = ($bResult) ? 1 : 0;
self::cacheSet($keypart, $result, 0, 300);
}
......@@ -2383,75 +2385,100 @@ class Notice extends Managed_DataObject
// If there's no scope, anyone (even anon) is in scope.
if ($scope == 0) {
return true;
}
if ($scope == 0) { // Not private
// If there's scope, anon cannot be in scope
return !$this->isHiddenSpam($profile);
if (empty($profile)) {
return false;
}
} else { // Private, somehow
// Author is always in scope
// If there's scope, anon cannot be in scope
if ($this->profile_id == $profile->id) {
return true;
}
if (empty($profile)) {
return false;
}
// Only for users on this site
// Author is always in scope
if ($scope & Notice::SITE_SCOPE) {
$user = $profile->getUser();
if (empty($user)) {
return false;
if ($this->profile_id == $profile->id) {
return true;
}
}
// Only for users mentioned in the notice
// Only for users on this site
if ($scope & Notice::ADDRESSEE_SCOPE) {
if ($scope & Notice::SITE_SCOPE) {
$user = $profile->getUser();
if (empty($user)) {
return false;
}
}
$repl = Reply::pkeyGet(array('notice_id' => $this->id,
'profile_id' => $profile->id));
// Only for users mentioned in the notice
if ($scope & Notice::ADDRESSEE_SCOPE) {
$repl = Reply::pkeyGet(array('notice_id' => $this->id,
'profile_id' => $profile->id));
if (empty($repl)) {
return false;
if (empty($repl)) {
return false;
}
}
}
// Only for members of the given group
// Only for members of the given group
if ($scope & Notice::GROUP_SCOPE) {
if ($scope & Notice::GROUP_SCOPE) {
// XXX: just query for the single membership
// XXX: just query for the single membership
$groups = $this->getGroups();
$groups = $this->getGroups();
$foundOne = false;
$foundOne = false;
foreach ($groups as $group) {
if ($profile->isMember($group)) {
$foundOne = true;
break;
foreach ($groups as $group) {
if ($profile->isMember($group)) {
$foundOne = true;
break;
}
}
if (!$foundOne) {
return false;
}
}
if (!$foundOne) {
return false;
// Only for followers of the author
$author = null;
if ($scope & Notice::FOLLOWER_SCOPE) {
$author = $this->getProfile();
if (!Subscription::exists($profile, $author)) {
return false;
}
}
return !$this->isHiddenSpam($profile);
}
}
function isHiddenSpam($profile) {
// Hide posts by silenced users from everyone but moderators.
// Only for followers of the author
if (common_config('notice', 'hidespam')) {
if ($scope & Notice::FOLLOWER_SCOPE) {
$author = $this->getProfile();
if (!Subscription::exists($profile, $author)) {
return false;
if ($author->hasRole(Profile_role::SILENCED)) {
if (empty($profile) || !$profile->hasRight(Right::REVIEWSPAM)) {
return true;
}
}
}
return true;
return false;
}
static function groupsFromText($text, $profile)
......
......@@ -1139,11 +1139,27 @@ class Profile extends Managed_DataObject
function silence()
{
$this->grantRole(Profile_role::SILENCED);
if (common_config('notice', 'hidespam')) {
$this->flushVisibility();
}
}
function unsilence()
{
$this->revokeRole(Profile_role::SILENCED);
if (common_config('notice', 'hidespam')) {
$this->flushVisibility();
}
}
function flushVisibility()
{
// Get all notices
$stream = new ProfileNoticeStream($this, $this);
$ids = $stream->getNoticeIds(0, CachingNoticeStream::CACHE_WINDOW);
foreach ($ids as $id) {
self::blow('notice:in-scope-for:%d:null', $id);
}
}
/**
......@@ -1174,6 +1190,8 @@ class Profile extends Managed_DataObject
case Right::SILENCEUSER:
case Right::DELETEUSER:
case Right::DELETEGROUP:
case Right::TRAINSPAM:
case Right::REVIEWSPAM:
$result = $this->hasRole(Profile_role::MODERATOR);
break;
case Right::CONFIGURESITE:
......
......@@ -193,7 +193,7 @@ class Profile_tag extends Managed_DataObject
if ($profile_list->taggedCount() >= common_config('peopletag', 'maxpeople')) {
// TRANS: Client exception thrown when trying to add more people than allowed to a list.
throw new ClientException(sprintf(_('You already have %1$d or more people in list %2$s, ' .
'which is the maximum allowed number.' .
'which is the maximum allowed number. ' .
'Try unlisting others first.'),
common_config('peopletag', 'maxpeople'), $tag));
return false;
......
......@@ -389,9 +389,28 @@ class Activity
$activity['geopoint'] = array(
'type' => 'Point',
'coordinates' => array($loc->lat, $loc->lon)
'coordinates' => array($loc->lat, $loc->lon),
'deprecated' => true,
);
$activity['location'] = array(
'objectType' => 'place',
'position' => sprintf("%+02.5F%+03.5F/", $loc->lat, $loc->lon),
'lat' => $loc->lat,
'lon' => $loc->lon
);
$name = $loc->getName();
if ($name) {
$activity['location']['displayName'] = $name;
}
$url = $loc->getURL();
if ($url) {
$activity['location']['url'] = $url;
}
}
$activity['to'] = $this->context->getToArray();
......@@ -487,15 +506,20 @@ class Activity
/* Purely extensions hereafter */
$tags = array();
// Use an Activity Object for term? Which object? Note?
foreach ($this->categories as $cat) {
$tags[] = $cat->term;
if ($activity['verb'] == 'post') {
$tags = array();
foreach ($this->categories as $cat) {
if (mb_strlen($cat->term) > 0) {
// Couldn't figure out which object type to use, so...
$tags[] = array('objectType' => 'http://activityschema.org/object/hashtag',
'displayName' => $cat->term);
}
}
if (count($tags) > 0) {
$activity['object']['tags'] = $tags;
}
}
$activity['tags'] = $tags;
// XXX: a bit of a hack... Since JSON isn't namespaced we probably
// shouldn't be using 'statusnet:notice_info', but this will work
// for the moment.
......
......@@ -692,9 +692,6 @@ class ActivityObject
// id
$object['id'] = $this->id;
//
// XXX: Should we use URL here? or a crazy tag URI?
$object['id'] = $this->id;
if ($this->type == ActivityObject::PERSON
|| $this->type == ActivityObject::GROUP) {
......@@ -737,14 +734,17 @@ class ActivityObject
// We can probably use the whole schema URL here but probably the
// relative simple name is easier to parse
// @fixme this breaks extension URIs
$object['type'] = substr($this->type, strrpos($this->type, '/') + 1);
// published (probably don't need. Might be useful for repeats.)
$object['objectType'] = substr($this->type, strrpos($this->type, '/') + 1);
// summary
$object['summary'] = $this->summary;
// udpated (probably don't need this)
// summary
$object['content'] = $this->content;
// published (probably don't need. Might be useful for repeats.)
// updated (probably don't need this)
// TODO: upstreamDuplicates
......
......@@ -168,7 +168,7 @@ class ActivityStreamJSONDocument extends JSONActivityCollection
$this->doc['generator'] = 'StatusNet ' . STATUSNET_VERSION; // extension
$this->doc['title'] = $this->title;
$this->doc['url'] = $this->url;
$this->doc['count'] = $this->count;
$this->doc['totalItems'] = $this->count;
$this->doc['items'] = $this->items;
$this->doc['links'] = $this->links; // extension
return json_encode(array_filter($this->doc)); // filter out empty elements
......
......@@ -66,7 +66,7 @@ $default =
'minify' => true, // true to use the minified versions of JS files; false to use orig files. Can aid during development
),
'db' =>
array('database' => 'YOU HAVE TO SET THIS IN config.php',
array('database' => null, // must be set
'schema_location' => INSTALLDIR . '/classes',
'class_location' => INSTALLDIR . '/classes',
'require_prefix' => 'classes/',
......@@ -288,7 +288,8 @@ $default =
'gc_limit' => 1000), // max sessions to expire at a time
'notice' =>
array('contentlimit' => null,
'defaultscope' => null), // null means 1 if site/private, 0 otherwise
'defaultscope' => null, // null means 1 if site/private, 0 otherwise
'hidespam' => false), // Whether to hide silenced users from timelines
'message' =>
array('contentlimit' => null),
'location' =>
......
......@@ -70,6 +70,7 @@ abstract class FilteringNoticeStream extends NoticeStream
// or we get nothing from upstream.
$results = null;
$round = 0;
do {
......@@ -81,14 +82,11 @@ abstract class FilteringNoticeStream extends NoticeStream
break;
}
$notices = $raw->fetchAll();
// XXX: this should probably only be in the scoping one.
Notice::fillGroups($notices);
Notice::fillReplies($notices);
foreach ($notices as $notice) {
$notices = $raw->fetchAll();
$this->prefill($notices);
foreach ($notices as $notice) {
if ($this->filter($notice)) {
$filtered[] = $notice;
if (count($filtered) >= $total) {
......@@ -100,9 +98,20 @@ abstract class FilteringNoticeStream extends NoticeStream
// XXX: make these smarter; factor hit rate into $askFor
$startAt += $askFor;
$askFor = max($total - count($filtered), NOTICES_PER_PAGE);
$hits = count($filtered);
$lastAsk = $askFor;
if ($hits === 0) {
$askFor = max(min(2 * $askFor, NOTICES_PER_PAGE * 50), NOTICES_PER_PAGE);
} else {
$askFor = max(min(intval(ceil(($total - $hits)*$startAt/$hits)), NOTICES_PER_PAGE * 50), NOTICES_PER_PAGE);
}
} while (count($filtered) < $total && $results !== 0);
$round++;
} while (count($filtered) < $total && $results >= $lastAsk);
return new ArrayWrapper(array_slice($filtered, $offset, $limit));
}
......@@ -119,4 +128,9 @@ abstract class FilteringNoticeStream extends NoticeStream
return $ids;
}
function prefill($notices)
{
return;
}
}
......@@ -46,15 +46,49 @@ if (!defined('STATUSNET')) {
*/
class GroupNoticeStream extends ScopingNoticeStream
{
var $group;
var $userProfile;
function __construct($group, $profile = -1)
{
if (is_int($profile) && $profile == -1) {
$profile = Profile::current();
}
$this->group = $group;
$this->userProfile = $profile;
parent::__construct(new CachingNoticeStream(new RawGroupNoticeStream($group),
'user_group:notice_ids:' . $group->id),
$profile);
}
function getNoticeIds($offset, $limit, $since_id, $max_id)