GitHost.io will be shut down on June 1, 2019. At that point this instance will be unreachable and all data will be irrevocably deleted. More details at https://about.gitlab.com/gitlab-hosted/#githost-is-shutting-down-on-june-1st-2019

Commit 250221ff authored by vinz's avatar vinz

Merge remote-tracking branch 'upstream/nightly' into nightly

parents 3e5ae79c ec504ec4
avatar/*
files/*
file/*
local/*
_darcs/*
logs/*
log/*
run/*
avatar/
files/
file/
local/
_darcs/
logs/
log/
run/
config.php
.htaccess
httpd.conf
......
......@@ -497,9 +497,9 @@ Profile management.
biolimit: max character length of bio; 0 means no limit; null means to use
the site text limit default.
backup: whether users can backup their own profiles. Defaults to true.
backup: whether users can backup their own profiles. Defaults to false.
restore: whether users can restore their profiles from backup files. Defaults
to true.
to false.
delete: whether users can delete their own accounts. Defaults to false.
move: whether users can move their accounts to another server. Defaults
to true.
......
......@@ -26,16 +26,12 @@ PHP modules
The following software packages are *required* for this software to
run correctly.
- PHP 5.5+ For newer versions, some functions that are used may be
disabled by default, such as the pcntl_* family. See the
section on 'Queues and daemons' for more information.
- MariaDB 5+ GNU Social uses, by default, a MariaDB server for data
storage. Versions 5.x and 10.x have both reportedly
worked well. It is also possible to run MySQL 5.5+.
- Web server Apache, lighttpd and nginx will all work. CGI mode is
recommended and also some variant of 'suexec' (or a
proper setup php-fpm pool)
NOTE: mod_rewrite or its equivalent is extremely useful.
- PHP 5.6+ PHP7.x is also supported.
- MariaDB 5+ MariaDB 10.x is also supported.
- Web server Apache, lighttpd and nginx will all work, see sample
configuration files in the web root. Please use PHP-FPM
and configure mod_rewrite (or equivalent) for an optimal
experience.
Your PHP installation must include the following PHP extensions for a
functional setup of GNU Social:
......@@ -49,22 +45,22 @@ functional setup of GNU Social:
- php5-mysqlnd The native driver for PHP5 MariaDB connections. If you
use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough.
Or, for PHP7, some or all of these will be necessary. PHP7 support is still
experimental and not necessarily working:
Or, for PHP7, some or all of these will be necessary. PHP7 works and on
the development servers we are successful running PHP7.2. This is a good
list of PHP modules you will want installed with PHP7:
php7.0-bcmath
php7.0-curl
php7.0-exif
php7.0-gd
php7.0-intl
php7.0-mbstring
php7.0-mysqlnd
php7.0-mysql
php7.0-opcache
php7.0-readline
php7.0-xmlwriter
The above package names are for Debian based systems. In the case of
Arch Linux, PHP is compiled with support for most extensions but they
require manual enabling in the relevant php.ini file (mostly php5-gmp).
NOTE: In Arch Linux, at least PHP5 requires manual enabling in the
relevant php.ini for some modules, most notably 'gmp'.
Better performance
------------------
......@@ -74,19 +70,10 @@ For some functionality, you will also need the following extensions:
- opcache Improves performance a _lot_. Included in PHP, must be
enabled manually in php.ini for most distributions. Find
and set at least: opcache.enable=1
- mailparse Efficient parsing of email requires this extension.
Submission by email or SMS-over-email uses this.
- sphinx A client for the sphinx server, an alternative to MySQL
or Postgresql fulltext search. You will also need a
Sphinx server to serve the search queries.
- gettext For multiple languages. Default on many PHP installs;
will be emulated if not present.
- exif For thumbnails to be properly oriented.
You may also experience better performance from your site if you configure
a PHP cache/accelerator. Most distributions come with "opcache" support.
Enable it in your php.ini where it is documented together with its settings.
Installation
============
......
......@@ -107,6 +107,7 @@ So far it includes the following changes:
- Backing up a user's account is more and more complete.
- Emojis 😸 (utf8mb4 support)
- Fully qualified group mentions (!group@example.com)
The last release, 1.1.3, gave us these improvements:
......
......@@ -46,7 +46,7 @@
/api/statuses/update.:format
@par Formats (:format)
xml, json
xml, json, atom
@par HTTP Method(s)
POST
......@@ -339,6 +339,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$this->showSingleXmlStatus($this->notice);
} elseif ($this->format == 'json') {
$this->show_single_json_status($this->notice);
} elseif ($this->format == 'atom') {
$this->showSingleAtomStatus($this->notice);
}
}
}
......
......@@ -151,7 +151,7 @@ class GroupBlockList extends ProfileList
$this->group = $group;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new GroupBlockListItem($profile, $this->group, $this->action);
}
......
......@@ -153,7 +153,7 @@ class GroupqueueAction extends GroupAction
// @todo FIXME: documentation missing.
class GroupQueueList extends GroupMemberList
{
function newListItem($profile)
function newListItem(Profile $profile)
{
return new GroupQueueListItem($profile, $this->group, $this->action);
}
......
......@@ -47,6 +47,8 @@ class NewnoticeAction extends FormAction
{
protected $form = 'Notice';
protected $inreplyto = null;
/**
* Title of the page
*
......@@ -75,6 +77,11 @@ class NewnoticeAction extends FormAction
}
}
if ($this->int('inreplyto')) {
// Throws exception if the inreplyto Notice is given but not found.
$this->inreplyto = Notice::getByID($this->int('inreplyto'));
}
// Backwards compatibility for "share this" widget things.
// If no 'content', use 'status_textarea'
$this->formOpts['content'] = $this->trimmed('content') ?: $this->trimmed('status_textarea');
......@@ -132,13 +139,6 @@ class NewnoticeAction extends FormAction
return;
}
if ($this->int('inreplyto')) {
// Throws exception if the inreplyto Notice is given but not found.
$parent = Notice::getByID($this->int('inreplyto'));
} else {
$parent = null;
}
$act = new Activity();
$act->verb = ActivityVerb::POST;
$act->time = time();
......@@ -157,9 +157,9 @@ class NewnoticeAction extends FormAction
$act->context = new ActivityContext();
if ($parent instanceof Notice) {
$act->context->replyToID = $parent->getUri();
$act->context->replyToUrl = $parent->getUrl(true); // maybe we don't have to send true here to force a URL?
if ($this->inreplyto instanceof Notice) {
$act->context->replyToID = $this->inreplyto->getUri();
$act->context->replyToUrl = $this->inreplyto->getUrl(true); // maybe we don't have to send true here to force a URL?
}
if ($this->scoped->shareLocation()) {
......@@ -188,14 +188,14 @@ class NewnoticeAction extends FormAction
// FIXME: We should be able to get the attentions from common_render_content!
// and maybe even directly save whether they're local or not!
$act->context->attention = common_get_attentions($content, $this->scoped, $parent);
$act->context->attention = common_get_attentions($content, $this->scoped, $this->inreplyto);
// $options gets filled with possible scoping settings
ToSelector::fillActivity($this, $act, $options);
$actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE;
$actobj->content = common_render_content($content, $this->scoped, $parent);
$actobj->content = common_render_content($content, $this->scoped, $this->inreplyto);
// Finally add the activity object to our activity
$act->objects[] = $actobj;
......@@ -224,6 +224,9 @@ class NewnoticeAction extends FormAction
if ($this->getInfo() && $this->stored instanceof Notice) {
$this->showNotice($this->stored);
} elseif (!$this->getError()) {
if (!GNUsocial::isAjax() && $this->inreplyto instanceof Notice) {
$this->showNotice($this->inreplyto);
}
parent::showContent();
}
}
......
......@@ -185,7 +185,7 @@ class SearchNoticeList extends NoticeList {
$this->terms = $terms;
}
function newListItem($notice)
function newListItem(Notice $notice)
{
return new SearchNoticeListItem($notice, $this->out, $this->terms);
}
......
......@@ -167,7 +167,7 @@ class PeopletagMemberList extends ProfileList
$this->peopletag = $peopletag;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new PeopletagMemberListItem($profile, $this->peopletag, $this->action);
}
......
......@@ -167,7 +167,7 @@ class PeopletagSubscriberList extends ProfileList
$this->peopletag = $peopletag;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new PeopletagSubscriberListItem($profile, $this->peopletag, $this->action);
}
......
......@@ -113,6 +113,18 @@ class ShowstreamAction extends NoticestreamAction
$this->target->getNickname(), $this->tag)));
}
if (!$this->target->isLocal()) {
// remote profiles at least have Atom, but we can't guarantee anything else
return array(
new Feed(Feed::ATOM,
$this->target->getAtomFeed(),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
$this->target->getNickname()))
);
}
return array(new Feed(Feed::JSON,
common_local_url('ApiTimelineUser',
array(
......@@ -139,10 +151,7 @@ class ShowstreamAction extends NoticestreamAction
sprintf(_('Notice feed for %s (RSS 2.0)'),
$this->target->getNickname())),
new Feed(Feed::ATOM,
common_local_url('ApiTimelineUser',
array(
'id' => $this->target->getID(),
'format' => 'atom')),
$this->target->getAtomFeed(),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
......@@ -221,13 +230,14 @@ class ShowstreamAction extends NoticestreamAction
$this->showEmptyListMessage();
}
$args = array('nickname' => $this->target->getNickname());
// either nickname or id will be used, depending on which action (showstream, userbyid...)
$args = array('nickname' => $this->target->getNickname(), 'id' => $this->target->getID());
if (!empty($this->tag))
{
$args['tag'] = $this->tag;
}
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
'showstream', $args);
$this->getActionName(), $args);
}
function showAnonymousMessage()
......
......@@ -36,6 +36,7 @@ class Conversation extends Managed_DataObject
public $__table = 'conversation'; // table name
public $id; // int(4) primary_key not_null auto_increment
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $url; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
......@@ -45,6 +46,7 @@ class Conversation extends Managed_DataObject
'fields' => array(
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'),
'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 191, 'description' => 'URI of the conversation'),
'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'Resolvable URL, preferrably remote (local can be generated on the fly)'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
),
......@@ -89,15 +91,21 @@ class Conversation extends Managed_DataObject
*
* @return Conversation the new conversation DO
*/
static function create($uri=null, $created=null)
static function create(ActivityContext $ctx=null, $created=null)
{
// Be aware that the Notice does not have an id yet since it's not inserted!
$conv = new Conversation();
$conv->created = $created ?: common_sql_now();
$conv->uri = $uri ?: sprintf('%s%s=%s:%s=%s',
if ($ctx instanceof ActivityContext) {
$conv->uri = $ctx->conversation;
$conv->url = $ctx->conversation_url;
} else {
$conv->uri = sprintf('%s%s=%s:%s=%s',
TagURI::mint(),
'objectType', 'thread',
'nonce', common_random_hexstr(8));
$conv->url = null; // locally generated Conversation objects don't get static URLs stored
}
// This insert throws exceptions on failure
$conv->insert();
......
......@@ -194,10 +194,14 @@ class File extends Managed_DataObject
}
$redir = File_redirection::where($given_url);
$file = $redir->getFile();
if (!$file instanceof File || empty($file->id)) {
try {
$file = $redir->getFile();
} catch (EmptyPkeyValueException $e) {
common_log(LOG_ERR, 'File_redirection::where gave object with empty file_id for given_url '._ve($given_url));
throw new ServerException('URL processing failed without new File object');
} catch (NoResultException $e) {
// This should not happen
common_log(LOG_ERR, 'File_redirection after discovery could still not return a File object.');
throw new ServerException('URL processing failed without new File object');
}
......@@ -731,16 +735,18 @@ class File extends Managed_DataObject
$dupfile = new File();
// First we find file entries that would be duplicates of this when shortened
// ... and we'll just throw the dupes out the window for now! It's already so borken.
$dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = "%1$s"', $file->shortenedurl));
$dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = %1$s', $dupfile->_quote($file->shortenedurl)));
// Leave one of the URLs in the database by using ->find(true) (fetches first entry)
if ($dupfile->find(true)) {
print "\nShortening url entry for $table id: {$file->id} [";
$orig = clone($dupfile);
$origurl = $dupfile->url; // save for logging purposes
$dupfile->url = $file->shortenedurl; // make sure it's only 191 chars from now on
$dupfile->update($orig);
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
// only start deleting with this fetch.
while($dupfile->fetch()) {
common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl));
print ".";
$dupfile->delete();
}
......
......@@ -445,8 +445,8 @@ class File_redirection extends Managed_DataObject
}
public function getFile() {
if(empty($this->file) && $this->file_id) {
$this->file = File::getKV('id', $this->file_id);
if (!$this->file instanceof File) {
$this->file = File::getByID($this->file_id);
}
return $this->file;
......
......@@ -89,7 +89,7 @@ class Foreign_link extends Managed_DataObject
return $flink;
}
function set_flags($noticesend, $noticerecv, $replysync, $friendsync)
function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync)
{
if ($noticesend) {
$this->noticesync |= FOREIGN_NOTICE_SEND;
......@@ -109,6 +109,12 @@ class Foreign_link extends Managed_DataObject
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY;
}
if ($repeatsync) {
$this->noticesync |= FOREIGN_NOTICE_SEND_REPEAT;
} else {
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPEAT;
}
if ($friendsync) {
$this->friendsync |= FOREIGN_FRIEND_RECV;
} else {
......
......@@ -320,6 +320,21 @@ class Notice extends Managed_DataObject
}
}
public function getSelfLink()
{
if ($this->isLocal()) {
return common_local_url('ApiStatusesShow', array('id' => $this->getID(), 'format' => 'atom'));
}
$selfLink = $this->getPref('ostatus', 'self');
if (!common_valid_http_url($selfLink)) {
throw new InvalidUrlException($selfLink);
}
return $selfLink;
}
public function getObjectType($canonical=false) {
if (is_null($this->object_type) || $this->object_type==='') {
throw new NoObjectTypeException($this);
......@@ -442,6 +457,7 @@ class Notice extends Managed_DataObject
static function saveNew($profile_id, $content, $source, array $options=null) {
$defaults = array('uri' => null,
'url' => null,
'self' => null,
'conversation' => null, // URI of conversation
'reply_to' => null, // This will override convo URI if the parent is known
'repeat_of' => null, // This will override convo URI if the repeated notice is known
......@@ -624,8 +640,13 @@ class Notice extends Managed_DataObject
} else {
// Conversation entry with specified URI was not found, so we must create it.
common_debug('Conversation URI not found, so we will create it with the URI given in the options to Notice::saveNew: '.$options['conversation']);
$convctx = new ActivityContext();
$convctx->conversation = $options['conversation'];
if (array_key_exists('conversation_url', $options)) {
$convctx->conversation_url = $options['conversation_url'];
}
// The insert in Conversation::create throws exception on failure
$conv = Conversation::create($options['conversation'], $notice->created);
$conv = Conversation::create($convctx, $notice->created);
}
$notice->conversation = $conv->getID();
unset($conv);
......@@ -706,6 +727,10 @@ class Notice extends Managed_DataObject
}
}
if ($self && common_valid_http_url($self)) {
$notice->setPref('ostatus', 'self', $self);
}
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves.
......@@ -765,6 +790,9 @@ class Notice extends Managed_DataObject
// implied object
$options['uri'] = $act->id;
$options['url'] = $act->link;
if ($act->selfLink) {
$options['self'] = $act->selfLink;
}
} else {
$actobj = count($act->objects)===1 ? $act->objects[0] : null;
if (!is_null($actobj) && !empty($actobj->id)) {
......@@ -775,6 +803,9 @@ class Notice extends Managed_DataObject
$options['url'] = $actobj->id;
}
}
if ($actobj->selfLink) {
$options['self'] = $actobj->selfLink;
}
}
$defaults = array(
......@@ -784,6 +815,7 @@ class Notice extends Managed_DataObject
'reply_to' => null,
'repeat_of' => null,
'scope' => null,
'self' => null,
'source' => 'unknown',
'tags' => array(),
'uri' => null,
......@@ -921,7 +953,7 @@ class Notice extends Managed_DataObject
// Conversation entry with specified URI was not found, so we must create it.
common_debug('Conversation URI not found, so we will create it with the URI given in the context of the activity: '.$act->context->conversation);
// The insert in Conversation::create throws exception on failure
$conv = Conversation::create($act->context->conversation, $stored->created);
$conv = Conversation::create($act->context, $stored->created);
}
$stored->conversation = $conv->getID();
unset($conv);
......@@ -1020,6 +1052,14 @@ class Notice extends Managed_DataObject
throw new ServerException('Supposedly saved Notice has no ID.');
}
if ($self && common_valid_http_url($self)) {
$stored->setPref('ostatus', 'self', $self);
}
if ($self && common_valid_http_url($self)) {
$stored->setPref('ostatus', 'self', $self);
}
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves.
......@@ -1578,12 +1618,12 @@ class Notice extends Managed_DataObject
if (common_config('group', 'addtag')) {
// we automatically add a tag for every group name, too
$tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->nickname),
'notice_id' => $this->id));
common_debug('Adding hashtag matching group nickname: '._ve($group->getNickname()));
$tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->getNickname()),
'notice_id' => $this->getID()));
if (is_null($tag)) {
$this->saveTag($group->nickname);
$this->saveTag($group->getNickname());
}
}
......@@ -2008,6 +2048,7 @@ class Notice extends Managed_DataObject
$conv = Conversation::getKV('id', $this->conversation);
if ($conv instanceof Conversation) {
$ctx->conversation = $conv->uri;
$ctx->conversation_url = $conv->url;
}
}
......@@ -2070,9 +2111,12 @@ class Notice extends Managed_DataObject
}
}
try {
$act->selfLink = $this->getSelfLink();
} catch (InvalidUrlException $e) {
$act->selfLink = null;
}
if ($this->isLocal()) {
$act->selfLink = common_local_url('ApiStatusesShow', array('id' => $this->id,
'format' => 'atom'));
$act->editLink = $act->selfLink;
}
......@@ -2170,8 +2214,13 @@ class Notice extends Managed_DataObject
$object->title = sprintf('New %1$s by %2$s', ActivityObject::canonicalType($object->type), $this->getProfile()->getNickname());
$object->content = $this->getRendered();
$object->link = $this->getUrl();
try {
$object->selfLink = $this->getSelfLink();
} catch (InvalidUrlException $e) {
$object->selfLink = null;
}
$object->extra[] = array('status_net', array('notice_id' => $this->id));
$object->extra[] = array('statusnet:notice_id', null, $this->id);
Event::handle('EndActivityObjectFromNotice', array($this, &$object));
}
......@@ -3195,4 +3244,27 @@ class Notice extends Managed_DataObject
}
}
}
public function delPref($namespace, $topic) {
return Notice_prefs::setData($this, $namespace, $topic, null);
}
public function getPref($namespace, $topic, $default=null) {
// If you want an exception to be thrown, call Notice_prefs::getData directly
try {
return Notice_prefs::getData($this, $namespace, $topic, $default);
} catch (NoResultException $e) {
return null;
}
}
// The same as getPref but will fall back to common_config value for the same namespace/topic
public function getConfigPref($namespace, $topic)
{
return Notice_prefs::getConfigData($this, $namespace, $topic);
}
public function setPref($namespace, $topic, $data) {
return Notice_prefs::setData($this, $namespace, $topic, $data);
}
}
<?php
/**
* GNU social
*
* Data class for Notice preferences
*
* 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