Merge branch 'nightly' into 'master'

GNU social 1.3.0

See merge request diogo/gnu-social!1
This commit is contained in:
Diogo Cordeiro 2018-09-14 21:27:40 +00:00
commit 63ab20d20b
878 changed files with 38870 additions and 55322 deletions

View File

@ -120,8 +120,9 @@ db
-- --
This section is a reference to the configuration options for This section is a reference to the configuration options for
DB_DataObject (see <http://ur1.ca/7xp>). The ones that you may want to DB_DataObject (see
set are listed below for clarity. <http://pear.php.net/manual/en/package.database.db-dataobject.intro-configuration.php>).
The ones that you may want to set are listed below for clarity.
database: a DSN (Data Source Name) for your StatusNet database. This is database: a DSN (Data Source Name) for your StatusNet database. This is
in the format 'protocol://username:password@hostname/databasename', in the format 'protocol://username:password@hostname/databasename',
@ -322,8 +323,8 @@ server: If set, defines another server where avatars are stored in the
the client to speed up page loading, either with another the client to speed up page loading, either with another
virtual server or with an NFS or SAMBA share. Clients virtual server or with an NFS or SAMBA share. Clients
typically only make 2 connections to a single server at a typically only make 2 connections to a single server at a
time <http://ur1.ca/6ih>, so this can parallelize the job. time <https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4>,
Defaults to null. so this can parallelize the job. Defaults to null.
ssl: Whether to access avatars using HTTPS. Defaults to null, meaning ssl: Whether to access avatars using HTTPS. Defaults to null, meaning
to guess based on site-wide SSL settings. to guess based on site-wide SSL settings.
@ -496,9 +497,9 @@ Profile management.
biolimit: max character length of bio; 0 means no limit; null means to use biolimit: max character length of bio; 0 means no limit; null means to use
the site text limit default. 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 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. delete: whether users can delete their own accounts. Defaults to false.
move: whether users can move their accounts to another server. Defaults move: whether users can move their accounts to another server. Defaults
to true. to true.
@ -672,7 +673,7 @@ Web crawlers. See http://www.robotstxt.org/ for more information
on the format of this file. on the format of this file.
crawldelay: if non-empty, this value is provided as the Crawl-Delay: crawldelay: if non-empty, this value is provided as the Crawl-Delay:
for the robots.txt file. see http://ur1.ca/l5a0 for the robots.txt file. see <https://en.wikipedia.org/wiki/Robots_exclusion_standard#Crawl-delay_directive>
for more information. Default is zero, no explicit delay. for more information. Default is zero, no explicit delay.
disallow: Array of (virtual) directories to disallow. Default is 'main', disallow: Array of (virtual) directories to disallow. Default is 'main',
'search', 'message', 'settings', 'admin'. Ignored when site 'search', 'message', 'settings', 'admin'. Ignored when site

44
INSTALL
View File

@ -26,16 +26,12 @@ PHP modules
The following software packages are *required* for this software to The following software packages are *required* for this software to
run correctly. run correctly.
- PHP 5.5+ For newer versions, some functions that are used may be - PHP 5.6+ PHP7.x is also supported.
disabled by default, such as the pcntl_* family. See the - MariaDB 5+ MariaDB 10.x is also supported.
section on 'Queues and daemons' for more information. - Web server Apache, lighttpd and nginx will all work, see sample
- MariaDB 5+ GNU Social uses, by default, a MariaDB server for data configuration files in the web root. Please use PHP-FPM
storage. Versions 5.x and 10.x have both reportedly and configure mod_rewrite (or equivalent) for an optimal
worked well. It is also possible to run MySQL 5.5+. experience.
- 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.
Your PHP installation must include the following PHP extensions for a Your PHP installation must include the following PHP extensions for a
functional setup of GNU Social: functional setup of GNU Social:
@ -49,9 +45,22 @@ functional setup of GNU Social:
- php5-mysqlnd The native driver for PHP5 MariaDB connections. If you - php5-mysqlnd The native driver for PHP5 MariaDB connections. If you
use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough. use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough.
The above package names are for Debian based systems. In the case of Or, for PHP7, some or all of these will be necessary. PHP7 works and on
Arch Linux, PHP is compiled with support for most extensions but they the development servers we are successful running PHP7.2. This is a good
require manual enabling in the relevant php.ini file (mostly php5-gmp). 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-mysql
php7.0-opcache
php7.0-readline
php7.0-xmlwriter
NOTE: In Arch Linux, at least PHP5 requires manual enabling in the
relevant php.ini for some modules, most notably 'gmp'.
Better performance Better performance
------------------ ------------------
@ -61,19 +70,10 @@ For some functionality, you will also need the following extensions:
- opcache Improves performance a _lot_. Included in PHP, must be - opcache Improves performance a _lot_. Included in PHP, must be
enabled manually in php.ini for most distributions. Find enabled manually in php.ini for most distributions. Find
and set at least: opcache.enable=1 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; - gettext For multiple languages. Default on many PHP installs;
will be emulated if not present. will be emulated if not present.
- exif For thumbnails to be properly oriented. - 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 Installation
============ ============

View File

@ -107,6 +107,7 @@ So far it includes the following changes:
- Backing up a user's account is more and more complete. - Backing up a user's account is more and more complete.
- Emojis 😸 (utf8mb4 support) - Emojis 😸 (utf8mb4 support)
- Fully qualified group mentions (!group@example.com)
The last release, 1.1.3, gave us these improvements: The last release, 1.1.3, gave us these improvements:

View File

@ -11,6 +11,9 @@ and follow this procedure:
0. Backup your data. The StatusNet upgrade discussions below have some 0. Backup your data. The StatusNet upgrade discussions below have some
guidelines to back up the database and files (mysqldump and rsync). guidelines to back up the database and files (mysqldump and rsync).
MAKE SURE YOU ARE THE SAME USER THAT RUNS THE PHP FILES WHILE PERFORMING
THE COMMANDS BELOW (I usually prepend the commands with 'sudo -u social')
1. Stop your queue daemons (you can run this command even if you do not 1. Stop your queue daemons (you can run this command even if you do not
use the queue daemons): use the queue daemons):
$ bash scripts/stopdaemons.sh $ bash scripts/stopdaemons.sh

View File

@ -65,7 +65,7 @@ class AddpeopletagAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -119,7 +119,7 @@ class AddpeopletagAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
// Throws exception on error // Throws exception on error
$ptag = Profile_tag::setTag($this->user->id, $this->tagged->id, $ptag = Profile_tag::setTag($this->user->id, $this->tagged->id,

View File

@ -170,12 +170,6 @@ class AllAction extends ShowstreamAction
} }
$ibs->show(); $ibs->show();
} }
// XXX: make this a little more convenient
if (!common_config('performance', 'high')) {
$pop = new InboxTagCloudSection($this, $this->target);
$pop->show();
}
} }
} }

View File

@ -46,7 +46,7 @@ class AllrssAction extends TargetedRss10Action
{ {
protected function getNotices() protected function getNotices()
{ {
$stream = new InboxNoticeStream($this->target); $stream = new InboxNoticeStream($this->target, $this->scoped);
return $stream->getNotices(0, $this->limit)->fetchAll(); return $stream->getNotices(0, $this->limit)->fetchAll();
} }

View File

@ -54,7 +54,7 @@ class ApiAccountUpdateDeliveryDeviceAction extends ApiAuthAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -73,9 +73,9 @@ class ApiAccountUpdateDeliveryDeviceAction extends ApiAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError( $this->clientError(

View File

@ -51,7 +51,7 @@ class ApiAtomServiceAction extends ApiBareAuthAction
* @return boolean success flag * @return boolean success flag
* *
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
$this->user = $this->getTargetUser($this->arg('id')); $this->user = $this->getTargetUser($this->arg('id'));
@ -71,9 +71,9 @@ class ApiAtomServiceAction extends ApiBareAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
header('Content-Type: application/atomsvc+xml'); header('Content-Type: application/atomsvc+xml');

View File

@ -58,7 +58,7 @@ class ApiGroupListAllAction extends ApiPrivateAuthAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -77,9 +77,9 @@ class ApiGroupListAllAction extends ApiPrivateAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$sitename = common_config('site', 'name'); $sitename = common_config('site', 'name');
// TRANS: Message is used as a title when listing the lastest 20 groups. %s is a site name. // TRANS: Message is used as a title when listing the lastest 20 groups. %s is a site name.

View File

@ -28,9 +28,7 @@
* @link http://status.net/ * @link http://status.net/
*/ */
if (!defined('STATUSNET')) { if (!defined('GNUSOCIAL')) { exit(1); }
exit(1);
}
/** /**
* Returns the string "ok" in the requested format with a 200 OK HTTP status code. * Returns the string "ok" in the requested format with a 200 OK HTTP status code.
@ -44,29 +42,9 @@ if (!defined('STATUSNET')) {
*/ */
class ApiHelpTestAction extends ApiPrivateAuthAction class ApiHelpTestAction extends ApiPrivateAuthAction
{ {
/** protected function handle()
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
function prepare($args)
{ {
parent::prepare($args); parent::handle();
return true;
}
/**
* Handle the request
*
* @param array $args $_REQUEST data (unused)
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($this->format == 'xml') { if ($this->format == 'xml') {
$this->initDocument('xml'); $this->initDocument('xml');
@ -77,12 +55,8 @@ class ApiHelpTestAction extends ApiPrivateAuthAction
print '"ok"'; print '"ok"';
$this->endDocument('json'); $this->endDocument('json');
} else { } else {
$this->clientError( // TRANS: Client error displayed when coming across a non-supported API method.
// TRANS: Client error displayed when coming across a non-supported API method. throw new ClientException(_('API method not found.'), 404);
_('API method not found.'),
404,
$this->format
);
} }
} }

View File

@ -33,7 +33,7 @@ class ApiListSubscriberAction extends ApiBareAuthAction
{ {
var $list = null; var $list = null;
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -52,9 +52,9 @@ class ApiListSubscriberAction extends ApiBareAuthAction
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$arr = array('profile_tag_id' => $this->list->id, $arr = array('profile_tag_id' => $this->list->id,
'profile_id' => $this->target->id); 'profile_id' => $this->target->id);

View File

@ -52,9 +52,9 @@ class ApiOAuthAccessTokenAction extends ApiOAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$datastore = new ApiGNUsocialOAuthDataStore(); $datastore = new ApiGNUsocialOAuthDataStore();
$server = new OAuthServer($datastore); $server = new OAuthServer($datastore);

View File

@ -60,7 +60,7 @@ class ApiOAuthAuthorizeAction extends ApiOAuthAction
return false; return false;
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -88,9 +88,9 @@ class ApiOAuthAuthorizeAction extends ApiOAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {

View File

@ -49,7 +49,7 @@ class ApiOAuthRequestTokenAction extends ApiOAuthAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -69,9 +69,9 @@ class ApiOAuthRequestTokenAction extends ApiOAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$datastore = new ApiGNUsocialOAuthDataStore(); $datastore = new ApiGNUsocialOAuthDataStore();
$server = new OAuthServer($datastore); $server = new OAuthServer($datastore);

View File

@ -88,7 +88,7 @@ class ApiSearchAtomAction extends ApiPrivateAuthAction
* *
* @return boolean success * @return boolean success
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -128,9 +128,9 @@ class ApiSearchAtomAction extends ApiPrivateAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
common_debug("In apisearchatom handle()"); common_debug("In apisearchatom handle()");
$this->showAtom(); $this->showAtom();
} }

View File

@ -57,7 +57,7 @@ class ApiSearchJSONAction extends ApiPrivateAuthAction
* *
* @return boolean true if nothing goes wrong * @return boolean true if nothing goes wrong
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -95,9 +95,9 @@ class ApiSearchJSONAction extends ApiPrivateAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showResults(); $this->showResults();
} }

View File

@ -34,9 +34,7 @@
* @link http://status.net/ * @link http://status.net/
*/ */
if (!defined('STATUSNET')) { if (!defined('GNUSOCIAL')) { exit(1); }
exit(1);
}
/** /**
* Deletes one of the authenticating user's statuses (notices). * Deletes one of the authenticating user's statuses (notices).
@ -55,87 +53,46 @@ if (!defined('STATUSNET')) {
*/ */
class ApiStatusesDestroyAction extends ApiAuthAction class ApiStatusesDestroyAction extends ApiAuthAction
{ {
var $status = null; protected function prepare(array $args=array())
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
function prepare($args)
{ {
parent::prepare($args); parent::prepare($args);
$this->user = $this->auth_user; if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
$this->notice_id = (int)$this->trimmed('id'); // TRANS: Client error displayed trying to delete a status not using POST or DELETE.
// TRANS: POST and DELETE should not be translated.
if (empty($notice_id)) { throw new ClientException(_('This method requires a POST or DELETE.'));
$this->notice_id = (int)$this->arg('id');
} }
$this->notice = Notice::getKV((int)$this->notice_id); // FIXME: Return with a Not Acceptable status code?
if (!in_array($this->format, array('xml', 'json'))) {
// TRANS: Client error displayed when coming across a non-supported API method.
throw new ClientException(_('API method not found.'), 404);
}
try {
$this->notice = Notice::getByID($this->trimmed('id'));
} catch (NoResultException $e) {
// TRANS: Client error displayed trying to delete a status with an invalid ID.
throw new ClientException(_('No status found with that ID.'), 404);
}
return true; return true;
} }
/** protected function handle()
* Handle the request
*
* Delete the notice and all related replies
*
* @param array $args $_REQUEST data (unused)
*
* @return void
*/
function handle($args)
{ {
parent::handle($args); parent::handle();
if (!in_array($this->format, array('xml', 'json'))) { if (!$this->scoped->sameAs($this->notice->getProfile()) && !$this->scoped->hasRight(Right::DELETEOTHERSNOTICE)) {
$this->clientError( // TRANS: Client error displayed trying to delete a status of another user.
// TRANS: Client error displayed when coming across a non-supported API method. throw new AuthorizationException(_('You may not delete another user\'s status.'));
_('API method not found.'),
404
);
return;
} }
if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { if (Event::handle('StartDeleteOwnNotice', array($this->scoped->getUser(), $this->notice))) {
$this->clientError( $this->notice->deleteAs($this->scoped);
// TRANS: Client error displayed trying to delete a status not using POST or DELETE. Event::handle('EndDeleteOwnNotice', array($this->scoped->getUser(), $this->notice));
// TRANS: POST and DELETE should not be translated.
_('This method requires a POST or DELETE.'),
400,
$this->format
);
return;
}
if (empty($this->notice)) {
$this->clientError(
// TRANS: Client error displayed trying to delete a status with an invalid ID.
_('No status found with that ID.'),
404, $this->format
);
return;
}
if ($this->user->id == $this->notice->profile_id) {
if (Event::handle('StartDeleteOwnNotice', array($this->user, $this->notice))) {
$this->notice->deleteAs($this->scoped);
Event::handle('EndDeleteOwnNotice', array($this->user, $this->notice));
}
$this->showNotice();
} else {
$this->clientError(
// TRANS: Client error displayed trying to delete a status of another user.
_('You may not delete another user\'s status.'),
403,
$this->format
);
} }
$this->showNotice();
} }
/** /**

View File

@ -46,7 +46,7 @@
/api/statuses/update.:format /api/statuses/update.:format
@par Formats (:format) @par Formats (:format)
xml, json xml, json, atom
@par HTTP Method(s) @par HTTP Method(s)
POST POST
@ -174,7 +174,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
foreach (array_unique($matches[0]) as $match) { foreach (array_unique($matches[0]) as $match) {
try { try {
$this->media_ids[$match] = File::getByID($match); $this->media_ids[$match] = File::getByID($match);
} catch (EmptyIdException $e) { } catch (EmptyPkeyValueException $e) {
// got a zero from the client, at least Twidere does this on occasion // got a zero from the client, at least Twidere does this on occasion
} catch (NoResultException $e) { } catch (NoResultException $e) {
// File ID was not found. Do we abort and report to the client? // File ID was not found. Do we abort and report to the client?
@ -339,6 +339,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$this->showSingleXmlStatus($this->notice); $this->showSingleXmlStatus($this->notice);
} elseif ($this->format == 'json') { } elseif ($this->format == 'json') {
$this->show_single_json_status($this->notice); $this->show_single_json_status($this->notice);
} elseif ($this->format == 'atom') {
$this->showSingleAtomStatus($this->notice);
} }
} }
} }

View File

@ -53,7 +53,7 @@ class ApiTrendsAction extends ApiPrivateAuthAction
* *
* @return boolean false if user doesn't exist * @return boolean false if user doesn't exist
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
return true; return true;
@ -66,9 +66,9 @@ class ApiTrendsAction extends ApiPrivateAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showTrends(); $this->showTrends();
} }

View File

@ -50,7 +50,7 @@ class ApprovegroupAction extends Action
/** /**
* Prepare to run * Prepare to run
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -139,9 +139,9 @@ class ApprovegroupAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
try { try {
if ($this->approve) { if ($this->approve) {

View File

@ -50,7 +50,7 @@ class ApprovesubAction extends Action
/** /**
* Prepare to run * Prepare to run
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -97,9 +97,9 @@ class ApprovesubAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$cur = common_current_user(); $cur = common_current_user();
try { try {

View File

@ -96,7 +96,7 @@ class AttachmentAction extends ManagedAction
{ {
if (empty($this->attachment->filename)) { if (empty($this->attachment->filename)) {
// if it's not a local file, gtfo // if it's not a local file, gtfo
common_redirect($this->attachment->url, 303); common_redirect($this->attachment->getUrl(), 303);
} }
parent::showPage(); parent::showPage();
@ -132,9 +132,5 @@ class AttachmentAction extends ManagedAction
function showSections() { function showSections() {
$ns = new AttachmentNoticeSection($this); $ns = new AttachmentNoticeSection($this);
$ns->show(); $ns->show();
if (!common_config('performance', 'high')) {
$atcs = new AttachmentTagCloudSection($this);
$atcs->show();
}
} }
} }

View File

@ -0,0 +1,20 @@
<?php
if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Download notice attachment
*
* @category Personal
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @license https://www.gnu.org/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https:/gnu.io/social
*/
class Attachment_downloadAction extends AttachmentAction
{
public function showPage()
{
common_redirect($this->attachment->getUrl(), 302);
}
}

View File

@ -62,6 +62,6 @@ class Attachment_thumbnailAction extends AttachmentAction
common_redirect($e->file->getUrl(), 302); common_redirect($e->file->getUrl(), 302);
} }
common_redirect(File_thumbnail::url($thumbnail->filename), 302); common_redirect(File_thumbnail::url($thumbnail->getFilename()), 302);
} }
} }

View File

@ -49,6 +49,20 @@ class AvatarsettingsAction extends SettingsAction
var $imagefile = null; var $imagefile = null;
var $filename = null; var $filename = null;
function prepare(array $args=array())
{
$avatarpath = Avatar::path('');
if (!is_writable($avatarpath)) {
throw new Exception(_("The administrator of your site needs to
add write permissions on the avatar upload folder before
you're able to set one."));
}
parent::prepare($args);
return true;
}
/** /**
* Title of the page * Title of the page
* *
@ -92,16 +106,6 @@ class AvatarsettingsAction extends SettingsAction
function showUploadForm() function showUploadForm()
{ {
$user = common_current_user();
$profile = $user->getProfile();
if (!$profile) {
common_log_db_error($user, 'SELECT', __FILE__);
// TRANS: Error message displayed when referring to a user without a profile.
$this->serverError(_('User has no profile.'));
}
$this->elementStart('form', array('enctype' => 'multipart/form-data', $this->elementStart('form', array('enctype' => 'multipart/form-data',
'method' => 'post', 'method' => 'post',
'id' => 'form_settings_avatar', 'id' => 'form_settings_avatar',
@ -116,7 +120,7 @@ class AvatarsettingsAction extends SettingsAction
if (Event::handle('StartAvatarFormData', array($this))) { if (Event::handle('StartAvatarFormData', array($this))) {
$this->elementStart('ul', 'form_data'); $this->elementStart('ul', 'form_data');
try { try {
$original = Avatar::getUploaded($profile); $original = Avatar::getUploaded($this->scoped);
$this->elementStart('li', array('id' => 'avatar_original', $this->elementStart('li', array('id' => 'avatar_original',
'class' => 'avatar_view')); 'class' => 'avatar_view'));
@ -126,7 +130,7 @@ class AvatarsettingsAction extends SettingsAction
$this->element('img', array('src' => $original->displayUrl(), $this->element('img', array('src' => $original->displayUrl(),
'width' => $original->width, 'width' => $original->width,
'height' => $original->height, 'height' => $original->height,
'alt' => $user->nickname)); 'alt' => $this->scoped->getNickname()));
$this->elementEnd('div'); $this->elementEnd('div');
$this->elementEnd('li'); $this->elementEnd('li');
} catch (NoAvatarException $e) { } catch (NoAvatarException $e) {
@ -134,7 +138,7 @@ class AvatarsettingsAction extends SettingsAction
} }
try { try {
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); $avatar = $this->scoped->getAvatar(AVATAR_PROFILE_SIZE);
$this->elementStart('li', array('id' => 'avatar_preview', $this->elementStart('li', array('id' => 'avatar_preview',
'class' => 'avatar_view')); 'class' => 'avatar_view'));
// TRANS: Header on avatar upload page for thumbnail of to be used rendition of uploaded avatar (h2). // TRANS: Header on avatar upload page for thumbnail of to be used rendition of uploaded avatar (h2).
@ -143,7 +147,7 @@ class AvatarsettingsAction extends SettingsAction
$this->element('img', array('src' => $avatar->displayUrl(), $this->element('img', array('src' => $avatar->displayUrl(),
'width' => AVATAR_PROFILE_SIZE, 'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE, 'height' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname)); 'alt' => $this->scoped->getNickname()));
$this->elementEnd('div'); $this->elementEnd('div');
if (!empty($avatar->filename)) { if (!empty($avatar->filename)) {
// TRANS: Button on avatar upload page to delete current avatar. // TRANS: Button on avatar upload page to delete current avatar.
@ -180,16 +184,6 @@ class AvatarsettingsAction extends SettingsAction
function showCropForm() function showCropForm()
{ {
$user = common_current_user();
$profile = $user->getProfile();
if (!$profile) {
common_log_db_error($user, 'SELECT', __FILE__);
// TRANS: Error message displayed when referring to a user without a profile.
$this->serverError(_('User has no profile.'));
}
$this->elementStart('form', array('method' => 'post', $this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_avatar', 'id' => 'form_settings_avatar',
'class' => 'form_settings', 'class' => 'form_settings',
@ -211,7 +205,7 @@ class AvatarsettingsAction extends SettingsAction
$this->element('img', array('src' => Avatar::url($this->filedata['filename']), $this->element('img', array('src' => Avatar::url($this->filedata['filename']),
'width' => $this->filedata['width'], 'width' => $this->filedata['width'],
'height' => $this->filedata['height'], 'height' => $this->filedata['height'],
'alt' => $user->nickname)); 'alt' => $this->scoped->getNickname()));
$this->elementEnd('div'); $this->elementEnd('div');
$this->elementEnd('li'); $this->elementEnd('li');
@ -224,7 +218,7 @@ class AvatarsettingsAction extends SettingsAction
$this->element('img', array('src' => Avatar::url($this->filedata['filename']), $this->element('img', array('src' => Avatar::url($this->filedata['filename']),
'width' => AVATAR_PROFILE_SIZE, 'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE, 'height' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname)); 'alt' => $this->scoped->getNickname()));
$this->elementEnd('div'); $this->elementEnd('div');
foreach (array('avatar_crop_x', 'avatar_crop_y', foreach (array('avatar_crop_x', 'avatar_crop_y',

View File

@ -74,6 +74,9 @@ class BackupaccountAction extends FormAction
// @fixme atom feed logic is in getString... // @fixme atom feed logic is in getString...
// but we just want it to output to the outputter. // but we just want it to output to the outputter.
$this->raw($stream->getString()); $this->raw($stream->getString());
// Don't print the page HTML
exit(0);
} }
public function isReadOnly($args) { public function isReadOnly($args) {

View File

@ -53,7 +53,7 @@ class BlockAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
return false; return false;
@ -78,7 +78,7 @@ class BlockAction extends ProfileFormAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) { if ($this->arg('no')) {

View File

@ -151,7 +151,7 @@ class GroupBlockList extends ProfileList
$this->group = $group; $this->group = $group;
} }
function newListItem($profile) function newListItem(Profile $profile)
{ {
return new GroupBlockListItem($profile, $this->group, $this->action); return new GroupBlockListItem($profile, $this->group, $this->action);
} }

View File

@ -50,7 +50,7 @@ class CancelgroupAction extends Action
/** /**
* Prepare to run * Prepare to run
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -127,9 +127,9 @@ class CancelgroupAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
try { try {
$this->request->abort(); $this->request->abort();

View File

@ -27,9 +27,7 @@
* @link http://status.net/ * @link http://status.net/
*/ */
if (!defined('STATUSNET') && !defined('LACONICA')) { if (!defined('GNUSOCIAL')) { exit(1); }
exit(1);
}
/** /**
* Confirm an address * Confirm an address
@ -44,25 +42,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class ConfirmaddressAction extends Action class ConfirmaddressAction extends ManagedAction
{ {
/** type of confirmation. */ /** type of confirmation. */
var $address; protected $address;
/** protected function doPreparation()
* Accept a confirmation code
*
* Checks the code and confirms the address in the
* user record
*
* @param args $args $_REQUEST array
*
* @return void
*/
function handle($args)
{ {
parent::handle($args);
if (!common_logged_in()) { if (!common_logged_in()) {
common_set_returnto($this->selfUrl()); common_set_returnto($this->selfUrl());
common_redirect(common_local_url('login')); common_redirect(common_local_url('login'));
@ -70,32 +57,45 @@ class ConfirmaddressAction extends Action
$code = $this->trimmed('code'); $code = $this->trimmed('code');
if (!$code) { if (!$code) {
// TRANS: Client error displayed when not providing a confirmation code in the contact address confirmation action. // TRANS: Client error displayed when not providing a confirmation code in the contact address confirmation action.
$this->clientError(_('No confirmation code.')); throw new ClientException(_('No confirmation code.'));
} }
$confirm = Confirm_address::getKV('code', $code); $confirm = Confirm_address::getKV('code', $code);
if (!$confirm) { if (!$confirm instanceof Confirm_address) {
// TRANS: Client error displayed when providing a non-existing confirmation code in the contact address confirmation action. // TRANS: Client error displayed when providing a non-existing confirmation code in the contact address confirmation action.
$this->clientError(_('Confirmation code not found.')); throw new ClientException(_('Confirmation code not found.'), 404);
} }
$cur = common_current_user();
if ($cur->id != $confirm->user_id) { try {
$profile = Profile::getByID($confirm->user_id);
} catch (NoResultException $e) {
common_log(LOG_INFO, 'Tried to confirm the email for a deleted profile: '._ve(['id'=>$confirm->user_id, 'email'=>$confirm->address]));
$confirm->delete();
throw $e;
}
if (!$profile->sameAs($this->scoped)) {
// TRANS: Client error displayed when not providing a confirmation code for another user in the contact address confirmation action. // TRANS: Client error displayed when not providing a confirmation code for another user in the contact address confirmation action.
$this->clientError(_('That confirmation code is not for you!')); throw new AuthorizationException(_('That confirmation code is not for you!'));
} }
$type = $confirm->address_type; $type = $confirm->address_type;
$transports = array(); $transports = array();
Event::handle('GetImTransports', array(&$transports)); Event::handle('GetImTransports', array(&$transports));
if (!in_array($type, array('email', 'sms')) && !in_array($type, array_keys($transports))) { if (!in_array($type, array('email', 'sms')) && !in_array($type, array_keys($transports))) {
// TRANS: Server error for an unknown address type, which can be 'email', 'sms', or the name of an IM network (such as 'xmpp' or 'aim') // TRANS: Server error for an unknown address type, which can be 'email', 'sms', or the name of an IM network (such as 'xmpp' or 'aim')
$this->serverError(sprintf(_('Unrecognized address type %s'), $type)); throw new ServerException(sprintf(_('Unrecognized address type %s'), $type));
} }
$this->address = $confirm->address; $this->address = $confirm->address;
$cur = $this->scoped->getUser();
$cur->query('BEGIN'); $cur->query('BEGIN');
if (in_array($type, array('email', 'sms'))) if (in_array($type, array('email', 'sms'))) {
{ common_debug("Confirming {$type} address for user {$this->scoped->getID()}");
if ($cur->$type == $confirm->address) { if ($cur->$type == $confirm->address) {
// Already verified, so delete the confirm_address entry
$confirm->delete();
// TRANS: Client error for an already confirmed email/jabber/sms address. // TRANS: Client error for an already confirmed email/jabber/sms address.
$this->clientError(_('That address has already been confirmed.')); throw new AlreadyFulfilledException(_('That address has already been confirmed.'));
} }
$orig_user = clone($cur); $orig_user = clone($cur);
@ -122,16 +122,18 @@ class ConfirmaddressAction extends Action
$user_im_prefs->user_id = $cur->id; $user_im_prefs->user_id = $cur->id;
if ($user_im_prefs->find() && $user_im_prefs->fetch()) { if ($user_im_prefs->find() && $user_im_prefs->fetch()) {
if($user_im_prefs->screenname == $confirm->address){ if($user_im_prefs->screenname == $confirm->address){
// Already verified, so delete the confirm_address entry
$confirm->delete();
// TRANS: Client error for an already confirmed IM address. // TRANS: Client error for an already confirmed IM address.
$this->clientError(_('That address has already been confirmed.')); throw new AlreadyFulfilledException(_('That address has already been confirmed.'));
} }
$user_im_prefs->screenname = $confirm->address; $user_im_prefs->screenname = $confirm->address;
$result = $user_im_prefs->update(); $result = $user_im_prefs->update();
if (!$result) { if ($result === false) {
common_log_db_error($user_im_prefs, 'UPDATE', __FILE__); common_log_db_error($user_im_prefs, 'UPDATE', __FILE__);
// TRANS: Server error displayed when updating IM preferences fails. // TRANS: Server error displayed when updating IM preferences fails.
$this->serverError(_('Could not update user IM preferences.')); throw new ServerException(_('Could not update user IM preferences.'));
} }
}else{ }else{
$user_im_prefs = new User_im_prefs(); $user_im_prefs = new User_im_prefs();
@ -140,26 +142,18 @@ class ConfirmaddressAction extends Action
$user_im_prefs->user_id = $cur->id; $user_im_prefs->user_id = $cur->id;
$result = $user_im_prefs->insert(); $result = $user_im_prefs->insert();
if (!$result) { if ($result === false) {
common_log_db_error($user_im_prefs, 'INSERT', __FILE__); common_log_db_error($user_im_prefs, 'INSERT', __FILE__);
// TRANS: Server error displayed when adding IM preferences fails. // TRANS: Server error displayed when adding IM preferences fails.
$this->serverError(_('Could not insert user IM preferences.')); throw new ServerException(_('Could not insert user IM preferences.'));
} }
} }
} }
$result = $confirm->delete(); $confirm->delete();
if (!$result) {
common_log_db_error($confirm, 'DELETE', __FILE__);
// TRANS: Server error displayed when an address confirmation code deletion from the
// TRANS: database fails in the contact address confirmation action.
$this->serverError(_('Could not delete address confirmation.'));
}
$cur->query('COMMIT'); $cur->query('COMMIT');
$this->showPage();
} }
/** /**
@ -180,8 +174,6 @@ class ConfirmaddressAction extends Action
*/ */
function showContent() function showContent()
{ {
$cur = common_current_user();
$this->element('p', null, $this->element('p', null,
// TRANS: Success message for the contact address confirmation action. // TRANS: Success message for the contact address confirmation action.
// TRANS: %s can be 'email', 'jabber', or 'sms'. // TRANS: %s can be 'email', 'jabber', or 'sms'.

View File

@ -51,7 +51,7 @@ class DeleteapplicationAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
return false; return false;
@ -89,7 +89,7 @@ class DeleteapplicationAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {

View File

@ -55,7 +55,7 @@ class DeletegroupAction extends RedirectingAction
* @fixme merge common setup code with other group actions * @fixme merge common setup code with other group actions
* @fixme allow group admins to delete their own groups * @fixme allow group admins to delete their own groups
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -114,9 +114,9 @@ class DeletegroupAction extends RedirectingAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) { if ($this->arg('no')) {
$this->returnToPrevious(); $this->returnToPrevious();

View File

@ -80,7 +80,7 @@ class DeleteuserAction extends ProfileFormAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) { if ($this->arg('no')) {

View File

@ -57,7 +57,7 @@ class EditApplicationAction extends Action
/** /**
* Prepare to run * Prepare to run
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -94,9 +94,9 @@ class EditApplicationAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost($args); $this->handlePost($args);

View File

@ -60,7 +60,7 @@ class EditpeopletagAction extends Action
* Prepare to run * Prepare to run
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -135,9 +135,9 @@ class EditpeopletagAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->trySave(); $this->trySave();
} else { } else {

View File

@ -369,8 +369,7 @@ class EmailsettingsAction extends SettingsAction
throw new ServerException(_('Could not insert confirmation code.')); throw new ServerException(_('Could not insert confirmation code.'));
} }
common_debug('Sending confirmation address for user '.$user->getID().' to email '.$email); $confirm->sendConfirmation();
mail_confirm_address($user, $confirm->code, $user->getNickname(), $email);
Event::handle('EndAddEmailAddress', array($user, $email)); Event::handle('EndAddEmailAddress', array($user, $email));
} }
@ -401,13 +400,7 @@ class EmailsettingsAction extends SettingsAction
throw new AlreadyFulfilledException(_('No pending confirmation to cancel.')); throw new AlreadyFulfilledException(_('No pending confirmation to cancel.'));
} }
$result = $confirm->delete(); $confirm->delete();
if ($result === false) {
common_log_db_error($confirm, 'DELETE', __FILE__);
// TRANS: Server error thrown on database error canceling e-mail address confirmation.
throw new ServerException(_('Could not delete email confirmation.'));
}
// TRANS: Message given after successfully canceling e-mail address confirmation. // TRANS: Message given after successfully canceling e-mail address confirmation.
return _('Email confirmation cancelled.'); return _('Email confirmation cancelled.');

View File

@ -54,7 +54,7 @@ class FeaturedAction extends Action
return true; return true;
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
@ -74,9 +74,9 @@ class FeaturedAction extends Action
} }
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showPage(); $this->showPage();
} }

View File

@ -35,7 +35,7 @@ class FoafGroupAction extends Action
return true; return true;
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -76,9 +76,9 @@ class FoafGroupAction extends Action
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
header('Content-Type: application/rdf+xml'); header('Content-Type: application/rdf+xml');

View File

@ -47,7 +47,7 @@ class GeocodeAction extends Action
var $lon = null; var $lon = null;
var $location = null; var $location = null;
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
$token = $this->trimmed('token'); $token = $this->trimmed('token');
@ -70,7 +70,7 @@ class GeocodeAction extends Action
* @return nothing * @return nothing
* *
*/ */
function handle($args) function handle()
{ {
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
$location_object = array(); $location_object = array();

View File

@ -49,7 +49,7 @@ class GrantRoleAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
return false; return false;

View File

@ -52,7 +52,7 @@ class GroupblockAction extends RedirectingAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
if (!common_logged_in()) { if (!common_logged_in()) {
@ -110,9 +110,9 @@ class GroupblockAction extends RedirectingAction
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) { if ($this->arg('no')) {
$this->returnToPrevious(); $this->returnToPrevious();

View File

@ -42,7 +42,7 @@ if (!defined('GNUSOCIAL')) { exit(1); }
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class GroupbyidAction extends ManagedAction class GroupbyidAction extends ShowgroupAction
{ {
/** group we're viewing. */ /** group we're viewing. */
protected $group = null; protected $group = null;
@ -55,10 +55,10 @@ class GroupbyidAction extends ManagedAction
protected function doPreparation() protected function doPreparation()
{ {
$this->group = User_group::getByID($this->arg('id')); $this->group = User_group::getByID($this->arg('id'));
} $this->target = $this->group->getProfile();
public function showPage() if ($this->target->isLocal()) {
{ common_redirect($this->target->getUrl());
common_redirect($this->group->homeUrl(), 303); }
} }
} }

View File

@ -211,6 +211,10 @@ class GrouplogoAction extends GroupAction
'height' => AVATAR_PROFILE_SIZE, 'height' => AVATAR_PROFILE_SIZE,
'alt' => $this->group->nickname)); 'alt' => $this->group->nickname));
$this->elementEnd('div'); $this->elementEnd('div');
if (!empty($this->group->homepage_logo)) {
// TRANS: Button on group logo upload page to delete current group logo.
$this->submit('delete', _('Delete'));
}
$this->elementEnd('li'); $this->elementEnd('li');
} }
@ -315,6 +319,8 @@ class GrouplogoAction extends GroupAction
$this->uploadLogo(); $this->uploadLogo();
} else if ($this->arg('crop')) { } else if ($this->arg('crop')) {
$this->cropLogo(); $this->cropLogo();
} else if ($this->arg('delete')) {
$this->deleteLogo();
} else { } else {
// TRANS: Form validation error message when an unsupported argument is used. // TRANS: Form validation error message when an unsupported argument is used.
$this->showForm(_('Unexpected form submission.')); $this->showForm(_('Unexpected form submission.'));
@ -409,6 +415,29 @@ class GrouplogoAction extends GroupAction
} }
} }
/**
* Get rid of the current group logo.
*
* @return void
*/
function deleteLogo()
{
$orig = clone($this->group);
Avatar::deleteFromProfile($this->group->getProfile());
@unlink(Avatar::path(basename($this->group->original_logo)));
@unlink(Avatar::path(basename($this->group->homepage_logo)));
@unlink(Avatar::path(basename($this->group->stream_logo)));
@unlink(Avatar::path(basename($this->group->mini_logo)));
$this->group->original_logo=User_group::defaultLogo(AVATAR_PROFILE_SIZE);
$this->group->homepage_logo=User_group::defaultLogo(AVATAR_PROFILE_SIZE);
$this->group->stream_logo=User_group::defaultLogo(AVATAR_STREAM_SIZE);
$this->group->mini_logo=User_group::defaultLogo(AVATAR_MINI_SIZE);
$this->group->update($orig);
// TRANS: Success message for deleting the group logo.
$this->showForm(_('Logo deleted.'));
}
function showPageNotice() function showPageNotice()
{ {
if ($this->msg) { if ($this->msg) {

View File

@ -27,12 +27,7 @@
* @link http://status.net/ * @link http://status.net/
*/ */
if (!defined('STATUSNET') && !defined('LACONICA')) { if (!defined('GNUSOCIAL')) { exit(1); }
exit(1);
}
require_once(INSTALLDIR.'/lib/profilelist.php');
require_once INSTALLDIR.'/lib/publicgroupnav.php';
/** /**
* List of group members * List of group members
@ -52,15 +47,6 @@ class GroupmembersAction extends GroupAction
return true; return true;
} }
protected function prepare(array $args=array())
{
parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
return true;
}
function title() function title()
{ {
if ($this->page == 1) { if ($this->page == 1) {

View File

@ -153,7 +153,7 @@ class GroupqueueAction extends GroupAction
// @todo FIXME: documentation missing. // @todo FIXME: documentation missing.
class GroupQueueList extends GroupMemberList class GroupQueueList extends GroupMemberList
{ {
function newListItem($profile) function newListItem(Profile $profile)
{ {
return new GroupQueueListItem($profile, $this->group, $this->action); return new GroupQueueListItem($profile, $this->group, $this->action);
} }

View File

@ -67,16 +67,16 @@ class GroupsAction extends Action
} }
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showPage(); $this->showPage();
} }

View File

@ -52,7 +52,7 @@ class GroupunblockAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
if (!common_logged_in()) { if (!common_logged_in()) {
@ -103,9 +103,9 @@ class GroupunblockAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->unblockProfile(); $this->unblockProfile();
} }

View File

@ -359,13 +359,7 @@ class ImsettingsAction extends SettingsAction
throw new AlreadyFulfilledException(_('No pending confirmation to cancel.')); throw new AlreadyFulfilledException(_('No pending confirmation to cancel.'));
} }
$result = $confirm->delete(); $confirm->delete();
if ($result === false) {
common_log_db_error($confirm, 'DELETE', __FILE__);
// TRANS: Server error thrown on database error canceling IM address confirmation.
throw new ServerException(_('Could not delete confirmation.'));
}
// TRANS: Message given after successfully canceling IM address confirmation. // TRANS: Message given after successfully canceling IM address confirmation.
return _('IM confirmation cancelled.'); return _('IM confirmation cancelled.');

View File

@ -38,9 +38,9 @@ class InviteAction extends Action
return false; return false;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if (!common_config('invite', 'enabled')) { if (!common_config('invite', 'enabled')) {
// TRANS: Client error displayed when trying to sent invites while they have been disabled. // TRANS: Client error displayed when trying to sent invites while they have been disabled.
$this->clientError(_('Invites have been disabled.')); $this->clientError(_('Invites have been disabled.'));

View File

@ -54,7 +54,7 @@ class MakeadminAction extends RedirectingAction
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
if (!common_logged_in()) { if (!common_logged_in()) {
@ -111,9 +111,9 @@ class MakeadminAction extends RedirectingAction
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->makeAdmin(); $this->makeAdmin();
} }

View File

@ -1,82 +0,0 @@
<?php
/**
* Microsummary action, see https://wiki.mozilla.org/Microsummaries
*
* PHP version 5
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Robin Millette <millette@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) 2008, 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') && !defined('LACONICA')) {
exit(1);
}
/**
* Microsummary action class.
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Robin Millette <millette@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class MicrosummaryAction extends Action
{
/**
* Class handler.
*
* @param array $args array of arguments
*
* @return nothing
*/
function handle($args)
{
parent::handle($args);
$nickname = common_canonical_nickname($this->arg('nickname'));
$user = User::getKV('nickname', $nickname);
if (!$user) {
// TRANS: Client error displayed trying to make a micro summary without providing a valid user.
$this->clientError(_('No such user.'), 404);
}
$notice = $user->getCurrentNotice();
if (!$notice) {
// TRANS: Client error displayed trying to make a micro summary without providing a status.
$this->clientError(_('No current status.'), 404);
}
header('Content-Type: text/plain');
print $user->nickname . ': ' . $notice->content;
}
function isReadOnly($args)
{
return true;
}
}

View File

@ -47,6 +47,8 @@ class NewnoticeAction extends FormAction
{ {
protected $form = 'Notice'; protected $form = 'Notice';
protected $inreplyto = null;
/** /**
* Title of the page * 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. // Backwards compatibility for "share this" widget things.
// If no 'content', use 'status_textarea' // If no 'content', use 'status_textarea'
$this->formOpts['content'] = $this->trimmed('content') ?: $this->trimmed('status_textarea'); $this->formOpts['content'] = $this->trimmed('content') ?: $this->trimmed('status_textarea');
@ -115,7 +122,7 @@ class NewnoticeAction extends FormAction
// simply no attached media to the new notice // simply no attached media to the new notice
if (empty($content)) { if (empty($content)) {
// TRANS: Client error displayed trying to send a notice without content. // TRANS: Client error displayed trying to send a notice without content.
$this->clientError(_('No content!')); throw new ClientException(_('No content!'));
} }
} }
@ -132,13 +139,6 @@ class NewnoticeAction extends FormAction
return; 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 = new Activity();
$act->verb = ActivityVerb::POST; $act->verb = ActivityVerb::POST;
$act->time = time(); $act->time = time();
@ -157,9 +157,9 @@ class NewnoticeAction extends FormAction
$act->context = new ActivityContext(); $act->context = new ActivityContext();
if ($parent instanceof Notice) { if ($this->inreplyto instanceof Notice) {
$act->context->replyToID = $parent->getUri(); $act->context->replyToID = $this->inreplyto->getUri();
$act->context->replyToUrl = $parent->getUrl(true); // maybe we don't have to send true here to force a URL? $act->context->replyToUrl = $this->inreplyto->getUrl(true); // maybe we don't have to send true here to force a URL?
} }
if ($this->scoped->shareLocation()) { 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! // FIXME: We should be able to get the attentions from common_render_content!
// and maybe even directly save whether they're local or not! // 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 // $options gets filled with possible scoping settings
ToSelector::fillActivity($this, $act, $options); ToSelector::fillActivity($this, $act, $options);
$actobj = new ActivityObject(); $actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE; $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 // Finally add the activity object to our activity
$act->objects[] = $actobj; $act->objects[] = $actobj;
@ -224,6 +224,9 @@ class NewnoticeAction extends FormAction
if ($this->getInfo() && $this->stored instanceof Notice) { if ($this->getInfo() && $this->stored instanceof Notice) {
$this->showNotice($this->stored); $this->showNotice($this->stored);
} elseif (!$this->getError()) { } elseif (!$this->getError()) {
if (!GNUsocial::isAjax() && $this->inreplyto instanceof Notice) {
$this->showNotice($this->inreplyto);
}
parent::showContent(); parent::showContent();
} }
} }

View File

@ -50,7 +50,7 @@ class NoticesearchAction extends SearchAction
{ {
protected $q = null; protected $q = null;
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -65,8 +65,7 @@ class NoticesearchAction extends SearchAction
if (!empty($this->q)) { if (!empty($this->q)) {
$profile = Profile::current(); $stream = new SearchNoticeStream($this->q, $this->scoped);
$stream = new SearchNoticeStream($this->q, $profile);
$page = $this->trimmed('page'); $page = $this->trimmed('page');
if (empty($page)) { if (empty($page)) {
@ -186,7 +185,7 @@ class SearchNoticeList extends NoticeList {
$this->terms = $terms; $this->terms = $terms;
} }
function newListItem($notice) function newListItem(Notice $notice)
{ {
return new SearchNoticeListItem($notice, $this->out, $this->terms); return new SearchNoticeListItem($notice, $this->out, $this->terms);
} }

View File

@ -55,9 +55,9 @@ class NudgeAction extends Action
* *
* @return nothing * @return nothing
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if (!common_logged_in()) { if (!common_logged_in()) {
// TRANS: Error message displayed when trying to perform an action that requires a logged in user. // TRANS: Error message displayed when trying to perform an action that requires a logged in user.

View File

@ -53,9 +53,9 @@ class OpensearchAction extends Action
* *
* @return boolean false if user doesn't exist * @return boolean false if user doesn't exist
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$type = $this->trimmed('type'); $type = $this->trimmed('type');
$short_name = ''; $short_name = '';
if ($type == 'people') { if ($type == 'people') {

View File

@ -53,7 +53,7 @@ class OtpAction extends Action
var $returnto; var $returnto;
var $lt; var $lt;
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -110,9 +110,9 @@ class OtpAction extends Action
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
// success! // success!
if (!common_set_user($this->user)) { if (!common_set_user($this->user)) {

View File

@ -62,7 +62,7 @@ class PeopletagAction extends Action
} }
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
@ -84,9 +84,9 @@ class PeopletagAction extends Action
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showPage(); $this->showPage();
} }

View File

@ -44,7 +44,7 @@ class PeopletagautocompleteAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -112,7 +112,7 @@ class PeopletagautocompleteAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
//common_log(LOG_DEBUG, 'Autocomplete data: ' . json_encode($this->tags)); //common_log(LOG_DEBUG, 'Autocomplete data: ' . json_encode($this->tags));
if ($this->tags) { if ($this->tags) {

View File

@ -53,7 +53,7 @@ class PeopletaggedAction extends Action
return true; return true;
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
@ -117,9 +117,9 @@ class PeopletaggedAction extends Action
} }
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showPage(); $this->showPage();
} }
@ -167,7 +167,7 @@ class PeopletagMemberList extends ProfileList
$this->peopletag = $peopletag; $this->peopletag = $peopletag;
} }
function newListItem($profile) function newListItem(Profile $profile)
{ {
return new PeopletagMemberListItem($profile, $this->peopletag, $this->action); return new PeopletagMemberListItem($profile, $this->peopletag, $this->action);
} }

View File

@ -68,7 +68,7 @@ class PeopletagsbyuserAction extends Action
} }
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -135,9 +135,9 @@ class PeopletagsbyuserAction extends Action
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
# Post from the tag dropdown; redirect to a GET # Post from the tag dropdown; redirect to a GET

View File

@ -54,7 +54,7 @@ class PeopletagsforuserAction extends Action
} }
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -95,9 +95,9 @@ class PeopletagsforuserAction extends Action
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showPage(); $this->showPage();
} }

View File

@ -53,7 +53,7 @@ class PeopletagsubscribersAction extends Action
return true; return true;
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
@ -117,9 +117,9 @@ class PeopletagsubscribersAction extends Action
} }
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showPage(); $this->showPage();
} }
@ -167,7 +167,7 @@ class PeopletagSubscriberList extends ProfileList
$this->peopletag = $peopletag; $this->peopletag = $peopletag;
} }
function newListItem($profile) function newListItem(Profile $profile)
{ {
return new PeopletagSubscriberListItem($profile, $this->peopletag, $this->action); return new PeopletagSubscriberListItem($profile, $this->peopletag, $this->action);
} }

View File

@ -56,7 +56,7 @@ class PeopletagsubscriptionsAction extends Action
} }
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -97,9 +97,9 @@ class PeopletagsubscriptionsAction extends Action
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$this->showPage(); $this->showPage();
} }

View File

@ -64,7 +64,7 @@ class PluginEnableAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -121,7 +121,7 @@ class PluginEnableAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
$key = 'disable-' . $this->plugin; $key = 'disable-' . $this->plugin;
Config::save('plugins', $key, $this->overrideValue()); Config::save('plugins', $key, $this->overrideValue());

View File

@ -68,7 +68,7 @@ class ProfilecompletionAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -120,7 +120,7 @@ class ProfilecompletionAction extends Action
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
$this->msg = null; $this->msg = null;

View File

@ -45,7 +45,7 @@ class ProfiletagbyidAction extends Action
return true; return true;
} }
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -83,7 +83,7 @@ class ProfiletagbyidAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
common_redirect($this->peopletag->homeUrl(), 303); common_redirect($this->peopletag->homeUrl(), 303);
} }

View File

@ -86,12 +86,6 @@ class PublicAction extends SitestreamAction
$ibs->show(); $ibs->show();
} }
$p = Profile::current();
if (!common_config('performance', 'high')) {
$cloud = new PublicTagCloudSection($this);
$cloud->show();
}
$feat = new FeaturedUsersSection($this); $feat = new FeaturedUsersSection($this);
$feat->show(); $feat->show();
} }

View File

@ -29,9 +29,9 @@ class RecoverpasswordAction extends Action
var $msg = null; var $msg = null;
var $success = null; var $success = null;
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if (common_logged_in()) { if (common_logged_in()) {
// TRANS: Client error displayed trying to recover password while already logged in. // TRANS: Client error displayed trying to recover password while already logged in.
$this->clientError(_('You are already logged in!')); $this->clientError(_('You are already logged in!'));
@ -79,13 +79,7 @@ class RecoverpasswordAction extends Action
// Burn this code // Burn this code
$result = $confirm->delete(); $confirm->delete();
if (!$result) {
common_log_db_error($confirm, 'DELETE', __FILE__);
// TRANS: Server error displayed removing a password recovery code from the database.
$this->serverError(_('Error with confirmation code.'));
}
// These should be reaped, but for now we just check mod time // These should be reaped, but for now we just check mod time
// Note: it's still deleted; let's avoid a second attempt! // Note: it's still deleted; let's avoid a second attempt!

View File

@ -63,7 +63,7 @@ class RedirectAction extends Action
* *
* @return nothing * @return nothing
*/ */
function handle($args) function handle()
{ {
common_redirect(common_local_url($this->arg('nextAction'), $this->arg('args'))); common_redirect(common_local_url($this->arg('nextAction'), $this->arg('args')));
} }

View File

@ -120,9 +120,9 @@ class RegisterAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if (common_config('site', 'closed')) { if (common_config('site', 'closed')) {
// TRANS: Client error displayed when trying to register to a closed site. // TRANS: Client error displayed when trying to register to a closed site.

View File

@ -66,7 +66,7 @@ class RemovepeopletagAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -120,7 +120,7 @@ class RemovepeopletagAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
// Throws exception on error // Throws exception on error

View File

@ -49,7 +49,7 @@ class RevokeRoleAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
return false; return false;

View File

@ -85,7 +85,7 @@ class RsdAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -126,7 +126,7 @@ class RsdAction extends Action
* *
* @return nothing * @return nothing
*/ */
function handle($args) function handle()
{ {
header('Content-Type: application/rsd+xml'); header('Content-Type: application/rsd+xml');

View File

@ -49,7 +49,7 @@ class SandboxAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
return false; return false;

View File

@ -158,9 +158,9 @@ class SelftagAction extends Action
class SelfTagProfileList extends ProfileList class SelfTagProfileList extends ProfileList
{ {
function newListItem($profile) function newListItem(Profile $target)
{ {
return new SelfTagProfileListItem($profile, $this->action); return new SelfTagProfileListItem($target, $this->action);
} }
} }

View File

@ -65,7 +65,7 @@ class ShowApplicationAction extends Action
* *
* @return success flag * @return success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -101,9 +101,9 @@ class ShowApplicationAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {

View File

@ -28,12 +28,7 @@
* @link http://status.net/ * @link http://status.net/
*/ */
if (!defined('STATUSNET') && !defined('LACONICA')) { if (!defined('GNUSOCIAL')) { exit(1); }
exit(1);
}
require_once INSTALLDIR.'/lib/noticelist.php';
require_once INSTALLDIR.'/lib/feedlist.php';
/** /**
* Group main page * Group main page
@ -48,7 +43,6 @@ class ShowgroupAction extends GroupAction
{ {
/** page we're viewing. */ /** page we're viewing. */
var $page = null; var $page = null;
var $userProfile = null;
var $notice = null; var $notice = null;
/** /**
@ -82,85 +76,15 @@ class ShowgroupAction extends GroupAction
} }
} }
/** public function getStream()
* Prepare the action
*
* Reads and validates arguments and instantiates the attributes.
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
protected function prepare(array $args=array())
{ {
parent::prepare($args); if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) {
$stream = new GroupNoticeStream($this->group, $this->scoped);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
$this->userProfile = Profile::current();
$user = common_current_user();
if (!empty($user) && $user->streamModeOnly()) {
$stream = new GroupNoticeStream($this->group, $this->userProfile);
} else { } else {
$stream = new ThreadingGroupNoticeStream($this->group, $this->userProfile); $stream = new ThreadingGroupNoticeStream($this->group, $this->scoped);
} }
$this->notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE, return $stream;
NOTICES_PER_PAGE + 1);
common_set_returnto($this->selfUrl());
return true;
}
/**
* Handle the request
*
* Shows a profile for the group, some controls, and a list of
* group notices.
*
* @return void
*/
protected function handle()
{
parent::handle();
$this->showPage();
}
/**
* Show the page content
*
* Shows a group profile and a list of group notices
*/
function showContent()
{
$this->showGroupNotices();
}
/**
* Show the group notices
*
* @return void
*/
function showGroupNotices()
{
$user = common_current_user();
if (!empty($user) && $user->streamModeOnly()) {
$nl = new PrimaryNoticeList($this->notice, $this, array('show_n'=>NOTICES_PER_PAGE));
} else {
$nl = new ThreadedNoticeList($this->notice, $this, $this->userProfile);
}
$cnt = $nl->show();
$this->pagination($this->page > 1,
$cnt > NOTICES_PER_PAGE,
$this->page,
'showgroup',
array('nickname' => $this->group->nickname));
} }
/** /**

View File

@ -113,6 +113,18 @@ class ShowstreamAction extends NoticestreamAction
$this->target->getNickname(), $this->tag))); $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, return array(new Feed(Feed::JSON,
common_local_url('ApiTimelineUser', common_local_url('ApiTimelineUser',
array( array(
@ -139,10 +151,7 @@ class ShowstreamAction extends NoticestreamAction
sprintf(_('Notice feed for %s (RSS 2.0)'), sprintf(_('Notice feed for %s (RSS 2.0)'),
$this->target->getNickname())), $this->target->getNickname())),
new Feed(Feed::ATOM, new Feed(Feed::ATOM,
common_local_url('ApiTimelineUser', $this->target->getAtomFeed(),
array(
'id' => $this->target->getID(),
'format' => 'atom')),
// TRANS: Title for link to notice feed. // TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname. // TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'), sprintf(_('Notice feed for %s (Atom)'),
@ -155,6 +164,17 @@ class ShowstreamAction extends NoticestreamAction
sprintf(_('FOAF for %s'), $this->target->getNickname()))); sprintf(_('FOAF for %s'), $this->target->getNickname())));
} }
public function extraHeaders()
{
parent::extraHeaders();
// Publish all the rel="me" in the HTTP headers on our main profile page
if (get_class($this) == 'ShowstreamAction') {
foreach ($this->target->getRelMes() as $relMe) {
header('Link: <'.htmlspecialchars($relMe['href']).'>; rel="me"', false);
}
}
}
function extraHead() function extraHead()
{ {
if ($this->target->bio) { if ($this->target->bio) {
@ -162,12 +182,6 @@ class ShowstreamAction extends NoticestreamAction
'content' => $this->target->getDescription())); 'content' => $this->target->getDescription()));
} }
// See https://wiki.mozilla.org/Microsummaries
$this->element('link', array('rel' => 'microsummary',
'href' => common_local_url('microsummary',
array('nickname' => $this->target->getNickname()))));
$rsd = common_local_url('rsd', $rsd = common_local_url('rsd',
array('nickname' => $this->target->getNickname())); array('nickname' => $this->target->getNickname()));
@ -210,19 +224,20 @@ class ShowstreamAction extends NoticestreamAction
function showNotices() function showNotices()
{ {
$pnl = new NoticeList($this->notice, $this); $pnl = new PrimaryNoticeList($this->notice, $this);
$cnt = $pnl->show(); $cnt = $pnl->show();
if (0 == $cnt) { if (0 == $cnt) {
$this->showEmptyListMessage(); $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)) if (!empty($this->tag))
{ {
$args['tag'] = $this->tag; $args['tag'] = $this->tag;
} }
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page, $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
'showstream', $args); $this->getActionName(), $args);
} }
function showAnonymousMessage() function showAnonymousMessage()
@ -246,15 +261,6 @@ class ShowstreamAction extends NoticestreamAction
$this->elementEnd('div'); $this->elementEnd('div');
} }
function showSections()
{
parent::showSections();
if (!common_config('performance', 'high')) {
$cloud = new PersonalTagCloudSection($this->target, $this);
$cloud->show();
}
}
function noticeFormOptions() function noticeFormOptions()
{ {
$options = parent::noticeFormOptions(); $options = parent::noticeFormOptions();

View File

@ -110,9 +110,7 @@ class SitenoticeadminpanelAction extends AdminPanelAction
} }
// scrub HTML input // scrub HTML input
require_once INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php'; $siteNotice = common_purify($siteNotice);
$purifier = new HTMLPurifier();
$siteNotice = $purifier->purify($siteNotice);
} }
} }

View File

@ -368,13 +368,7 @@ class SmssettingsAction extends SettingsAction
throw new AlreadyFulfilledException(_('No pending confirmation to cancel.')); throw new AlreadyFulfilledException(_('No pending confirmation to cancel.'));
} }
$result = $confirm->delete(); $confirm->delete();
if ($result === false) {
common_log_db_error($confirm, 'DELETE', __FILE__);
// TRANS: Server error thrown on database error canceling SMS phone number confirmation.
throw new ServerException(_('Could not delete SMS confirmation.'));
}
// TRANS: Message given after successfully canceling SMS phone number confirmation. // TRANS: Message given after successfully canceling SMS phone number confirmation.
return _('SMS confirmation cancelled.'); return _('SMS confirmation cancelled.');

View File

@ -24,7 +24,7 @@ class SubeditAction extends Action
{ {
var $profile = null; var $profile = null;
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -58,9 +58,9 @@ class SubeditAction extends Action
return true; return true;
} }
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$cur = common_current_user(); $cur = common_current_user();

View File

@ -64,7 +64,7 @@ class SubscribeAction extends Action
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -118,7 +118,7 @@ class SubscribeAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
// Throws exception on error // Throws exception on error

View File

@ -50,7 +50,7 @@ class SubscribepeopletagAction extends Action
/** /**
* Prepare to run * Prepare to run
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -106,9 +106,9 @@ class SubscribepeopletagAction extends Action
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$cur = common_current_user(); $cur = common_current_user();

View File

@ -22,9 +22,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
// @todo FIXME: documentation needed. // @todo FIXME: documentation needed.
class SupAction extends Action class SupAction extends Action
{ {
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$seconds = $this->trimmed('seconds'); $seconds = $this->trimmed('seconds');

View File

@ -34,7 +34,7 @@ class TagAction extends ManagedAction
$this->tag = common_canonical_tag($taginput); $this->tag = common_canonical_tag($taginput);
if (empty($this->tag)) { if (empty($this->tag)) {
common_redirect(common_local_url('publictagcloud'), 301); throw new ClientException(_('No valid tag data.'));
} }
// after common_canonical_tag we have a lowercase, no-specials tag string // after common_canonical_tag we have a lowercase, no-specials tag string

View File

@ -44,7 +44,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/ */
class UnblockAction extends ProfileFormAction class UnblockAction extends ProfileFormAction
{ {
function prepare($args) function prepare(array $args = array())
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
return false; return false;

View File

@ -49,7 +49,7 @@ class UnsandboxAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare(array $args = array())
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
return false; return false;

View File

@ -44,9 +44,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/ */
class UnsubscribeAction extends Action class UnsubscribeAction extends Action
{ {
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
if (!common_logged_in()) { if (!common_logged_in()) {
// TRANS: Error message displayed when trying to perform an action that requires a logged in user. // TRANS: Error message displayed when trying to perform an action that requires a logged in user.
$this->clientError(_('Not logged in.')); $this->clientError(_('Not logged in.'));

View File

@ -51,7 +51,7 @@ class UnsubscribepeopletagAction extends Action
* Prepare to run * Prepare to run
*/ */
function prepare($args) function prepare(array $args = array())
{ {
parent::prepare($args); parent::prepare($args);
@ -106,9 +106,9 @@ class UnsubscribepeopletagAction extends Action
* *
* @return void * @return void
*/ */
function handle($args) function handle()
{ {
parent::handle($args); parent::handle();
$cur = common_current_user(); $cur = common_current_user();

View File

@ -53,7 +53,7 @@ class Attention extends Managed_DataObject
{ {
try { try {
$att = Attention::getByKeys(['notice_id'=>$notice->getID(), 'profile_id'=>$target->getID()]); $att = Attention::getByKeys(['notice_id'=>$notice->getID(), 'profile_id'=>$target->getID()]);
throw new AlreadyFulfilledException('Attention already exists with reason: '.var_export($att->reason,true)); throw new AlreadyFulfilledException('Attention already exists with reason: '._ve($att->reason));
} catch (NoResultException $e) { } catch (NoResultException $e) {
$att = new Attention(); $att = new Attention();
@ -67,6 +67,7 @@ class Attention extends Managed_DataObject
throw new Exception('Failed Attention::saveNew for notice id=='.$notice->getID().' target id=='.$target->getID().', reason=="'.$reason.'"'); throw new Exception('Failed Attention::saveNew for notice id=='.$notice->getID().' target id=='.$target->getID().', reason=="'.$reason.'"');
} }
} }
self::blow('attention:stream:%d', $target->getID());
return $att; return $att;
} }
} }

View File

@ -207,8 +207,11 @@ class Avatar extends Managed_DataObject
} }
} }
static function defaultImage($size) static function defaultImage($size=null)
{ {
if (is_null($size)) {
$size = AVATAR_PROFILE_SIZE;
}
static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile', static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
AVATAR_STREAM_SIZE => 'stream', AVATAR_STREAM_SIZE => 'stream',
AVATAR_MINI_SIZE => 'mini'); AVATAR_MINI_SIZE => 'mini');

View File

@ -35,18 +35,18 @@ class Confirm_address extends Managed_DataObject
); );
} }
static function getAddress($address, $addressType) static function getByAddress($address, $addressType)
{ {
$ca = new Confirm_address(); $ca = new Confirm_address();
$ca->address = $address; $ca->address = $address;
$ca->address_type = $addressType; $ca->address_type = $addressType;
if ($ca->find(true)) { if (!$ca->find(true)) {
return $ca; throw new NoResultException($ca);
} }
return null; return $ca;
} }
static function saveNew($user, $address, $addressType, $extra=null) static function saveNew($user, $address, $addressType, $extra=null)
@ -66,4 +66,96 @@ class Confirm_address extends Managed_DataObject
return $ca; return $ca;
} }
public function getAddress()
{
return $this->address;
}
public function getAddressType()
{
return $this->address_type;
}
public function getCode()
{
return $this->code;
}
public function getProfile()
{
return Profile::getByID($this->user_id);
}
public function getUrl()
{
return common_local_url('confirmaddress', array('code' => $this->code));
}
/**
* Supply arguments in $args. Currently known args:
* headers Array with headers (only used for email)
* nickname How we great the user (defaults to nickname, but can be any string)
* sitename Name we sign the email with (defaults to sitename, but can be any string)
* url The confirmation address URL.
*/
public function sendConfirmation(array $args=array())
{
common_debug('Sending confirmation URL for user '._ve($this->user_id).' using '._ve($this->address_type));
$defaults = [
'headers' => array(),
'nickname' => $this->getProfile()->getNickname(),
'sitename' => common_config('site', 'name'),
'url' => $this->getUrl(),
];
foreach (array_keys($defaults) as $key) {
if (!isset($args[$key])) {
$args[$key] = $defaults[$key];
}
}
switch ($this->getAddressType()) {
case 'email':
$this->sendEmailConfirmation($args);
break;
default:
throw ServerException('Unable to handle confirm_address address type: '._ve($this->address_type));
}
}
public function sendEmailConfirmation(array $args=array())
{
// TRANS: Subject for address confirmation email.
$subject = _('Email address confirmation');
// TRANS: Body for address confirmation email.
// TRANS: %1$s is the addressed user's nickname, %2$s is the StatusNet sitename,
// TRANS: %3$s is the URL to confirm at.
$body = sprintf(_("Hey, %1\$s.\n\n".
"Someone just entered this email address on %2\$s.\n\n" .
"If it was you, and you want to confirm your entry, ".
"use the URL below:\n\n\t%3\$s\n\n" .
"If not, just ignore this message.\n\n".
"Thanks for your time, \n%2\$s\n"),
$args['nickname'],
$args['sitename'],
$args['url']);
require_once(INSTALLDIR . '/lib/mail.php');
return mail_to_user($this->getProfile()->getUser(), $subject, $body, $args['headers'], $this->getAddress());
}
public function delete($useWhere=false)
{
$result = parent::delete($useWhere);
if ($result === false) {
common_log_db_error($confirm, 'DELETE', __FILE__);
// TRANS: Server error displayed when an address confirmation code deletion from the
// TRANS: database fails in the contact address confirmation action.
throw new ServerException(_('Could not delete address confirmation.'));
}
return $result;
}
} }

View File

@ -36,6 +36,7 @@ class Conversation extends Managed_DataObject
public $__table = 'conversation'; // table name public $__table = 'conversation'; // table name
public $id; // int(4) primary_key not_null auto_increment public $id; // int(4) primary_key not_null auto_increment
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space 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 $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
@ -45,6 +46,7 @@ class Conversation extends Managed_DataObject
'fields' => array( 'fields' => array(
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'), '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'), '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'), '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'), '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 * @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! // Be aware that the Notice does not have an id yet since it's not inserted!
$conv = new Conversation(); $conv = new Conversation();
$conv->created = $created ?: common_sql_now(); $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(), TagURI::mint(),
'objectType', 'thread', 'objectType', 'thread',
'nonce', common_random_hexstr(8)); 'nonce', common_random_hexstr(8));
$conv->url = null; // locally generated Conversation objects don't get static URLs stored
}
// This insert throws exceptions on failure // This insert throws exceptions on failure
$conv->insert(); $conv->insert();

View File

@ -106,6 +106,40 @@ class File extends Managed_DataObject
// We don't have the file's URL since before, so let's continue. // We don't have the file's URL since before, so let's continue.
} }
// if the given url is an local attachment url and the id already exists, don't
// save a new file record. This should never happen, but let's make it foolproof
// FIXME: how about attachments servers?
$u = parse_url($given_url);
if (isset($u['host']) && $u['host'] === common_config('site', 'server')) {
$r = Router::get();
// Skip the / in the beginning or $r->map won't match
try {
$args = $r->map(mb_substr($u['path'], 1));
if ($args['action'] === 'attachment') {
try {
// $args['attachment'] should always be set if action===attachment, given our routing rules
$file = File::getByID($args['attachment']);
return $file;
} catch (EmptyPkeyValueException $e) {
// ...but $args['attachment'] can also be 0...
} catch (NoResultException $e) {
// apparently this link goes to us, but is _not_ an existing attachment (File) ID?
}
}
} catch (Exception $e) {
// Some other exception was thrown from $r->map, likely a
// ClientException (404) because of some malformed link to
// our own instance. It's still a valid URL however, so we
// won't abort anything... I noticed this when linking:
// https://social.umeahackerspace.se/mmn/foaf' (notice the
// apostrophe in the end, making it unrecognizable for our
// URL routing.
// That specific issue (the apostrophe being part of a link
// is something that may or may not have been fixed since,
// in lib/util.php in common_replace_urls_callback().
}
}
$file = new File; $file = new File;
$file->url = $given_url; $file->url = $given_url;
if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected']; if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected'];
@ -160,10 +194,14 @@ class File extends Managed_DataObject
} }
$redir = File_redirection::where($given_url); $redir = File_redirection::where($given_url);
$file = $redir->getFile(); try {
$file = $redir->getFile();
if (!$file instanceof File || empty($file->id)) { } 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 // 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'); throw new ServerException('URL processing failed without new File object');
} }
@ -226,18 +264,19 @@ class File extends Managed_DataObject
public function getFilename() public function getFilename()
{ {
if (!self::validFilename($this->filename)) { return self::tryFilename($this->filename);
// TRANS: Client exception thrown if a file upload does not have a valid name. }
throw new ClientException(_("Invalid filename."));
} public function getSize()
return $this->filename; {
return intval($this->size);
} }
// where should the file go? // where should the file go?
static function filename(Profile $profile, $origname, $mimetype) static function filename(Profile $profile, $origname, $mimetype)
{ {
$ext = self::guessMimeExtension($mimetype); $ext = self::guessMimeExtension($mimetype, $origname);
// Normalize and make the original filename more URL friendly. // Normalize and make the original filename more URL friendly.
$origname = basename($origname, ".$ext"); $origname = basename($origname, ".$ext");
@ -258,19 +297,54 @@ class File extends Managed_DataObject
return $filename; return $filename;
} }
static function guessMimeExtension($mimetype) /**
* @param $mimetype The mimetype we've discovered for this file.
* @param $filename An optional filename which we can use on failure.
*/
static function guessMimeExtension($mimetype, $filename=null)
{ {
try { try {
// first see if we know the extension for our mimetype
$ext = common_supported_mime_to_ext($mimetype); $ext = common_supported_mime_to_ext($mimetype);
} catch (Exception $e) { // we do, so use it!
// We don't support this mimetype, but let's guess the extension return $ext;
$matches = array(); } catch (UnknownMimeExtensionException $e) {
if (!preg_match('/\/([a-z0-9]+)/', mb_strtolower($mimetype), $matches)) { // We don't know the extension for this mimetype, but let's guess.
throw new Exception('Malformed mimetype: '.$mimetype);
// If we can't recognize the extension from the MIME, we try
// to guess based on filename, if one was supplied.
if (!is_null($filename) && preg_match('/^.+\.([A-Za-z0-9]+)$/', $filename, $matches)) {
// we matched on a file extension, so let's see if it means something.
$ext = mb_strtolower($matches[1]);
$blacklist = common_config('attachments', 'extblacklist');
// If we got an extension from $filename we want to check if it's in a blacklist
// so we avoid people uploading .php files etc.
if (array_key_exists($ext, $blacklist)) {
if (!is_string($blacklist[$ext])) {
// we don't have a safe replacement extension
throw new ClientException(_('Blacklisted file extension.'));
}
common_debug('Found replaced extension for filename '._ve($filename).': '._ve($ext));
// return a safe replacement extension ('php' => 'phps' for example)
return $blacklist[$ext];
}
// the attachment extension based on its filename was not blacklisted so it's ok to use it
return $ext;
} }
$ext = $matches[1]; } catch (Exception $e) {
common_log(LOG_INFO, 'Problem when figuring out extension for mimetype: '._ve($e));
} }
return mb_strtolower($ext);
// If nothing else has given us a result, try to extract it from
// the mimetype value (this turns .jpg to .jpeg for example...)
$matches = array();
// FIXME: try to build a regexp that will get jpeg from image/jpeg as well as json from application/jrd+json
if (!preg_match('/\/([a-z0-9]+)/', mb_strtolower($mimetype), $matches)) {
throw new Exception('Malformed mimetype: '.$mimetype);
}
return mb_strtolower($matches[1]);
} }
/** /**
@ -281,19 +355,27 @@ class File extends Managed_DataObject
return preg_match('/^[A-Za-z0-9._-]+$/', $filename); return preg_match('/^[A-Za-z0-9._-]+$/', $filename);
} }
static function tryFilename($filename)
{
if (!self::validFilename($filename))
{
throw new InvalidFilenameException($filename);
}
// if successful, return the filename for easy if-statementing
return $filename;
}
/** /**
* @throws ClientException on invalid filename * @throws ClientException on invalid filename
*/ */
static function path($filename) static function path($filename)
{ {
if (!self::validFilename($filename)) { self::tryFilename($filename);
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
}
$dir = common_config('attachments', 'dir'); $dir = common_config('attachments', 'dir');
if ($dir[strlen($dir)-1] != '/') { if (!in_array($dir[mb_strlen($dir)-1], ['/', '\\'])) {
$dir .= '/'; $dir .= DIRECTORY_SEPARATOR;
} }
return $dir . $filename; return $dir . $filename;
@ -301,10 +383,7 @@ class File extends Managed_DataObject
static function url($filename) static function url($filename)
{ {
if (!self::validFilename($filename)) { self::tryFilename($filename);
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
}
if (common_config('site','private')) { if (common_config('site','private')) {
@ -409,6 +488,8 @@ class File extends Managed_DataObject
* @param $width int Max width of thumbnail in pixels. (if null, use common_config values) * @param $width int Max width of thumbnail in pixels. (if null, use common_config values)
* @param $height int Max height of thumbnail in pixels. (if null, square-crop to $width) * @param $height int Max height of thumbnail in pixels. (if null, square-crop to $width)
* @param $crop bool Crop to the max-values' aspect ratio * @param $crop bool Crop to the max-values' aspect ratio
* @param $force_still bool Don't allow fallback to showing original (such as animated GIF)
* @param $upscale mixed Whether or not to scale smaller images up to larger thumbnail sizes. (null = site default)
* *
* @return File_thumbnail * @return File_thumbnail
* *
@ -424,7 +505,13 @@ class File extends Managed_DataObject
// null means "always use file as thumbnail" // null means "always use file as thumbnail"
// false means you get choice between frozen frame or original when calling getThumbnail // false means you get choice between frozen frame or original when calling getThumbnail
if (is_null(common_config('thumbnail', 'animated')) || !$force_still) { if (is_null(common_config('thumbnail', 'animated')) || !$force_still) {
throw new UseFileAsThumbnailException($this->id); try {
// remote files with animated GIFs as thumbnails will match this
return File_thumbnail::byFile($this);
} catch (NoResultException $e) {
// and if it's not a remote file, it'll be safe to use the locally stored File
throw new UseFileAsThumbnailException($this);
}
} }
} }
@ -441,13 +528,28 @@ class File extends Managed_DataObject
return $filepath; return $filepath;
} }
public function getUrl($prefer_local=true) public function getAttachmentUrl()
{ {
if ($prefer_local && !empty($this->filename)) { return common_local_url('attachment', array('attachment'=>$this->getID()));
// A locally stored file, so let's generate a URL for our instance. }
return self::url($this->filename);
/**
* @param mixed $use_local true means require local, null means prefer local, false means use whatever is stored
*/
public function getUrl($use_local=null)
{
if ($use_local !== false) {
if (is_string($this->filename) || !empty($this->filename)) {
// A locally stored file, so let's generate a URL for our instance.
return self::url($this->getFilename());
}
if ($use_local) {
// if the file wasn't stored locally (has filename) and we require a local URL
throw new FileNotStoredLocallyException($this);
}
} }
// No local filename available, return the URL we have stored // No local filename available, return the URL we have stored
return $this->url; return $this->url;
} }
@ -524,7 +626,9 @@ class File extends Managed_DataObject
function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{ {
$stream = new FileNoticeStream($this); // FIXME: Try to get the Profile::current() here in some other way to avoid mixing
// the current session user with possibly background/queue processing.
$stream = new FileNoticeStream($this, Profile::current());
return $stream->getNotices($offset, $limit, $since_id, $max_id); return $stream->getNotices($offset, $limit, $since_id, $max_id);
} }
@ -593,6 +697,13 @@ class File extends Managed_DataObject
return $title ?: null; return $title ?: null;
} }
public function setTitle($title)
{
$orig = clone($this);
$this->title = mb_strlen($title) > 0 ? $title : null;
return $this->update($orig);
}
static public function hashurl($url) static public function hashurl($url)
{ {
if (empty($url)) { if (empty($url)) {
@ -624,16 +735,18 @@ class File extends Managed_DataObject
$dupfile = new File(); $dupfile = new File();
// First we find file entries that would be duplicates of this when shortened // 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. // ... 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) // Leave one of the URLs in the database by using ->find(true) (fetches first entry)
if ($dupfile->find(true)) { if ($dupfile->find(true)) {
print "\nShortening url entry for $table id: {$file->id} ["; print "\nShortening url entry for $table id: {$file->id} [";
$orig = clone($dupfile); $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->url = $file->shortenedurl; // make sure it's only 191 chars from now on
$dupfile->update($orig); $dupfile->update($orig);
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} ["; print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
// only start deleting with this fetch. // only start deleting with this fetch.
while($dupfile->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 "."; print ".";
$dupfile->delete(); $dupfile->delete();
} }

View File

@ -172,56 +172,82 @@ class File_redirection extends Managed_DataObject
try { try {
$r = File_redirection::getByUrl($in_url); $r = File_redirection::getByUrl($in_url);
if($r instanceof File_redirection) {
try { try {
$f = File::getKV('id',$r->file_id); $f = File::getByID($r->file_id);
$r->file = $f; $r->file = $f;
$r->redir_url = $f->url; $r->redir_url = $f->url;
} catch (NoResultException $e) { } catch (NoResultException $e) {
// Invalid entry, delete and run again // Invalid entry, delete and run again
common_log(LOG_ERR, "Could not find File with id=".$r->file_id." referenced in File_redirection, deleting File redirection entry and creating new File and File_redirection entries."); common_log(LOG_ERR, "Could not find File with id=".$r->file_id." referenced in File_redirection, deleting File redirection entry and and trying again...");
$r->delete(); $r->delete();
return self::where($in_url); return self::where($in_url);
}
return $r;
} }
// File_redirecion and File record found, return both
return $r;
} catch (NoResultException $e) { } catch (NoResultException $e) {
// File_redirecion record not found, but this might be a direct link to a file
try { try {
$f = File::getByUrl($in_url); $f = File::getByUrl($in_url);
$redir->file_id = $f->id; $redir->file_id = $f->id;
$redir->file = $f; $redir->file = $f;
return $redir; return $redir;
} catch (NoResultException $e) { } catch (NoResultException $e) {
// Oh well, let's keep going // nope, this was not a direct link to a file either, let's keep going
} }
} }
if ($discover) { if ($discover) {
// try to follow redirects and get the final url
$redir_info = File_redirection::lookupWhere($in_url); $redir_info = File_redirection::lookupWhere($in_url);
if(is_string($redir_info)) { if(is_string($redir_info)) {
$redir_info = array('url' => $redir_info); $redir_info = array('url' => $redir_info);
} }
// Save the file if we don't have it already // the last url in the redirection chain can actually be a redirect!
$redir->file = File::saveNew($redir_info,$redir_info['url']); // this is the case with local /attachment/{file_id} links
// in that case we have the file id already
// If this is a redirection, save it try {
// (if it hasn't been saved yet by some other process while we we $r = File_redirection::getByUrl($redir_info['url']);
// were running lookupWhere())
if($redir_info['url'] != $in_url) { $f = File::getKV('id',$r->file_id);
try {
$file_redir = File_redirection::getByUrl($in_url); if($f instanceof File) {
} catch (NoResultException $e) { $redir->file = $f;
$file_redir = new File_redirection(); $redir->redir_url = $f->url;
$file_redir->urlhash = File::hashurl($in_url); } else {
$file_redir->url = $in_url; // Invalid entry in File_redirection, delete and run again
$file_redir->file_id = $redir->file->getID(); common_log(LOG_ERR, "Could not find File with id=".$r->file_id." referenced in File_redirection, deleting File_redirection entry and trying again...");
$file_redir->insert(); $r->delete();
$file_redir->redir_url = $redir->file->url; return self::where($in_url);
} }
} catch (NoResultException $e) {
// save the file now when we know that we don't have it in File_redirection
try {
$redir->file = File::saveNew($redir_info,$redir_info['url']);
} catch (ServerException $e) {
common_log(LOG_ERR, $e);
}
}
// If this is a redirection and we have a file to redirect to, save it
// (if it doesn't exist in File_redirection already)
if($redir->file instanceof File && $redir_info['url'] != $in_url) {
try {
$file_redir = File_redirection::getByUrl($in_url);
} catch (NoResultException $e) {
$file_redir = new File_redirection();
$file_redir->urlhash = File::hashurl($in_url);
$file_redir->url = $in_url;
$file_redir->file_id = $redir->file->getID();
$file_redir->insert();
$file_redir->redir_url = $redir->file->url;
}
$file_redir->file = $redir->file; $file_redir->file = $redir->file;
return $file_redir; return $file_redir;
} }
} }
@ -419,8 +445,8 @@ class File_redirection extends Managed_DataObject
} }
public function getFile() { public function getFile() {
if(empty($this->file) && $this->file_id) { if (!$this->file instanceof File) {
$this->file = File::getKV('id', $this->file_id); $this->file = File::getByID($this->file_id);
} }
return $this->file; return $this->file;

View File

@ -98,6 +98,7 @@ class File_thumbnail extends Managed_DataObject
if ($notNullUrl) { if ($notNullUrl) {
$thumb->whereAdd('url IS NOT NULL'); $thumb->whereAdd('url IS NOT NULL');
} }
$thumb->orderBy('modified ASC'); // the first created, a somewhat ugly hack
$thumb->limit(1); $thumb->limit(1);
if (!$thumb->find(true)) { if (!$thumb->find(true)) {
throw new NoResultException($thumb); throw new NoResultException($thumb);
@ -129,23 +130,88 @@ class File_thumbnail extends Managed_DataObject
static function path($filename) static function path($filename)
{ {
// TODO: Store thumbnails in their own directory and don't use File::path here File::tryFilename($filename);
return File::path($filename);
// NOTE: If this is left empty in default config, it will be set to File::path('thumb')
$dir = common_config('thumbnail', 'dir');
if (!in_array($dir[mb_strlen($dir)-1], ['/', '\\'])) {
$dir .= DIRECTORY_SEPARATOR;
}
return $dir . $filename;
} }
static function url($filename) static function url($filename)
{ {
// TODO: Store thumbnails in their own directory and don't use File::url here File::tryFilename($filename);
return File::url($filename);
// FIXME: private site thumbnails?
$path = common_config('thumbnail', 'path');
if (empty($path)) {
return File::url('thumb')."/{$filename}";
}
$protocol = (GNUsocial::useHTTPS() ? 'https' : 'http');
$server = common_config('thumbnail', 'server') ?: common_config('site', 'server');
if ($path[mb_strlen($path)-1] != '/') {
$path .= '/';
}
if ($path[0] != '/') {
$path = '/'.$path;
}
return $protocol.'://'.$server.$path.$filename;
} }
public function getFilename()
{
return File::tryFilename($this->filename);
}
/**
*
* @return string full filesystem path to the locally stored thumbnail file
* @throws
*/
public function getPath() public function getPath()
{ {
$filepath = self::path($this->filename); $oldpath = File::path($this->getFilename());
if (!file_exists($filepath)) { $thumbpath = self::path($this->getFilename());
throw new FileNotFoundException($filepath);
// If we have a file in our old thumbnail storage path, move (or copy) it to the new one
// (if the if/elseif don't match, we have a $thumbpath just as we should and can return it)
if (file_exists($oldpath) && !file_exists($thumbpath)) {
try {
// let's get the filename of the File, to check below if it happens to be identical
$file_filename = $this->getFile()->getFilename();
} catch (NoResultException $e) {
// reasonably the function calling us will handle the following as "File_thumbnail entry should be deleted"
throw new FileNotFoundException($thumbpath);
} catch (InvalidFilenameException $e) {
// invalid filename in getFile()->getFilename(), just
// means the File object isn't stored locally and that
// means it's safe to move it below.
$file_filename = null;
}
if ($this->getFilename() === $file_filename) {
// special case where thumbnail file exactly matches stored File filename
common_debug('File filename and File_thumbnail filename match on '.$this->file_id.', copying instead');
copy($oldpath, $thumbpath);
} elseif (!rename($oldpath, $thumbpath)) {
common_log(LOG_ERR, 'Could not move thumbnail from '._ve($oldpath).' to '._ve($thumbpath));
throw new ServerException('Could not move thumbnail from old path to new path.');
} else {
common_log(LOG_DEBUG, 'Moved thumbnail '.$this->file_id.' from '._ve($oldpath).' to '._ve($thumbpath));
}
} elseif (!file_exists($thumbpath)) {
throw new FileNotFoundException($thumbpath);
} }
return $filepath;
return $thumbpath;
} }
public function getUrl() public function getUrl()
@ -188,11 +254,15 @@ class File_thumbnail extends Managed_DataObject
public function delete($useWhere=false) public function delete($useWhere=false)
{ {
if (!empty($this->filename) && file_exists(File_thumbnail::path($this->filename))) { try {
$deleted = @unlink(self::path($this->filename)); $thumbpath = self::path($this->getFilename());
// if file does not exist, try to delete it
$deleted = !file_exists($thumbpath) || @unlink($thumbpath);
if (!$deleted) { if (!$deleted) {
common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', self::path($this->filename))); common_log(LOG_ERR, 'Could not unlink existing thumbnail file: '._ve($thumbpath));
} }
} catch (InvalidFilenameException $e) {
common_log(LOG_ERR, 'Deleting object but not attempting deleting file: '._ve($e->getMessage()));
} }
return parent::delete($useWhere); return parent::delete($useWhere);
@ -203,6 +273,10 @@ class File_thumbnail extends Managed_DataObject
return File::getByID($this->file_id); return File::getByID($this->file_id);
} }
public function getFileId()
{
return $this->file_id;
}
static public function hashurl($url) static public function hashurl($url)
{ {

View File

@ -89,7 +89,7 @@ class Foreign_link extends Managed_DataObject
return $flink; return $flink;
} }
function set_flags($noticesend, $noticerecv, $replysync, $friendsync) function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync)
{ {
if ($noticesend) { if ($noticesend) {
$this->noticesync |= FOREIGN_NOTICE_SEND; $this->noticesync |= FOREIGN_NOTICE_SEND;
@ -109,6 +109,12 @@ class Foreign_link extends Managed_DataObject
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY; $this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY;
} }
if ($repeatsync) {
$this->noticesync |= FOREIGN_NOTICE_SEND_REPEAT;
} else {
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPEAT;
}
if ($friendsync) { if ($friendsync) {
$this->friendsync |= FOREIGN_FRIEND_RECV; $this->friendsync |= FOREIGN_FRIEND_RECV;
} else { } else {

View File

@ -383,14 +383,35 @@ abstract class Managed_DataObject extends Memcached_DataObject
static function getByID($id) static function getByID($id)
{ {
if (!property_exists(get_called_class(), 'id')) {
throw new ServerException('Trying to get undefined property of dataobject class.');
}
if (empty($id)) { if (empty($id)) {
throw new EmptyIdException(get_called_class()); throw new EmptyPkeyValueException(get_called_class(), 'id');
} }
// getByPK throws exception if id is null // getByPK throws exception if id is null
// or if the class does not have a single 'id' column as primary key // or if the class does not have a single 'id' column as primary key
return static::getByPK(array('id' => $id)); return static::getByPK(array('id' => $id));
} }
static function getByUri($uri)
{
if (!property_exists(get_called_class(), 'uri')) {
throw new ServerException('Trying to get undefined property of dataobject class.');
}
if (empty($uri)) {
throw new EmptyPkeyValueException(get_called_class(), 'uri');
}
$class = get_called_class();
$obj = new $class();
$obj->uri = $uri;
if (!$obj->find(true)) {
throw new NoResultException($obj);
}
return $obj;
}
/** /**
* Returns an ID, checked that it is set and reasonably valid * Returns an ID, checked that it is set and reasonably valid
* *
@ -483,6 +504,8 @@ abstract class Managed_DataObject extends Memcached_DataObject
throw new ServerException('DataObject must be the result of a query (N>=1) before updateWithKeys()'); throw new ServerException('DataObject must be the result of a query (N>=1) before updateWithKeys()');
} }
$this->onUpdateKeys($orig);
// do it in a transaction // do it in a transaction
$this->query('BEGIN'); $this->query('BEGIN');
@ -580,6 +603,11 @@ abstract class Managed_DataObject extends Memcached_DataObject
// NOOP by default // NOOP by default
} }
protected function onUpdateKeys(Managed_DataObject $orig)
{
// NOOP by default
}
public function insert() public function insert()
{ {
$this->onInsert(); $this->onInsert();

View File

@ -67,10 +67,11 @@ class Memcached_DataObject extends Safe_DataObject
* @param string $cls Class to fetch * @param string $cls Class to fetch
* @param string $keyCol name of column for key * @param string $keyCol name of column for key
* @param array $keyVals key values to fetch * @param array $keyVals key values to fetch
* @param boolean $skipNulls skip provided null values
* *
* @return array Array of objects, in order * @return array Array of objects, in order
*/ */
static function multiGetClass($cls, $keyCol, array $keyVals) static function multiGetClass($cls, $keyCol, array $keyVals, $skipNulls=true)
{ {
$obj = new $cls; $obj = new $cls;
@ -83,6 +84,14 @@ class Memcached_DataObject extends Safe_DataObject
throw new ServerException('Cannot do multiGet on anything but integer columns'); throw new ServerException('Cannot do multiGet on anything but integer columns');
} }
if ($skipNulls) {
foreach ($keyVals as $key=>$val) {
if (is_null($val)) {
unset($keyVals[$key]);
}
}
}
$obj->whereAddIn($keyCol, $keyVals, $colType); $obj->whereAddIn($keyCol, $keyVals, $colType);
// Since we're inputting straight to a query: format and escape // Since we're inputting straight to a query: format and escape

Some files were not shown because too many files have changed in this diff Show More