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
DB_DataObject (see <http://ur1.ca/7xp>). The ones that you may want to
set are listed below for clarity.
DB_DataObject (see
<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
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
virtual server or with an NFS or SAMBA share. Clients
typically only make 2 connections to a single server at a
time <http://ur1.ca/6ih>, so this can parallelize the job.
Defaults to null.
time <https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4>,
so this can parallelize the job. Defaults to null.
ssl: Whether to access avatars using HTTPS. Defaults to null, meaning
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
the site text limit default.
backup: whether users can backup their own profiles. Defaults to true.
backup: whether users can backup their own profiles. Defaults to false.
restore: whether users can restore their profiles from backup files. Defaults
to true.
to false.
delete: whether users can delete their own accounts. Defaults to false.
move: whether users can move their accounts to another server. Defaults
to true.
@ -672,7 +673,7 @@ Web crawlers. See http://www.robotstxt.org/ for more information
on the format of this file.
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.
disallow: Array of (virtual) directories to disallow. Default is 'main',
'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
run correctly.
- PHP 5.5+ For newer versions, some functions that are used may be
disabled by default, such as the pcntl_* family. See the
section on 'Queues and daemons' for more information.
- MariaDB 5+ GNU Social uses, by default, a MariaDB server for data
storage. Versions 5.x and 10.x have both reportedly
worked well. It is also possible to run MySQL 5.5+.
- Web server Apache, lighttpd and nginx will all work. CGI mode is
recommended and also some variant of 'suexec' (or a
proper setup php-fpm pool)
NOTE: mod_rewrite or its equivalent is extremely useful.
- PHP 5.6+ PHP7.x is also supported.
- MariaDB 5+ MariaDB 10.x is also supported.
- Web server Apache, lighttpd and nginx will all work, see sample
configuration files in the web root. Please use PHP-FPM
and configure mod_rewrite (or equivalent) for an optimal
experience.
Your PHP installation must include the following PHP extensions for a
functional setup of GNU Social:
@ -49,9 +45,22 @@ functional setup of GNU Social:
- php5-mysqlnd The native driver for PHP5 MariaDB connections. If you
use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough.
The above package names are for Debian based systems. In the case of
Arch Linux, PHP is compiled with support for most extensions but they
require manual enabling in the relevant php.ini file (mostly php5-gmp).
Or, for PHP7, some or all of these will be necessary. PHP7 works and on
the development servers we are successful running PHP7.2. This is a good
list of PHP modules you will want installed with PHP7:
php7.0-bcmath
php7.0-curl
php7.0-exif
php7.0-gd
php7.0-intl
php7.0-mbstring
php7.0-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
------------------
@ -61,19 +70,10 @@ For some functionality, you will also need the following extensions:
- opcache Improves performance a _lot_. Included in PHP, must be
enabled manually in php.ini for most distributions. Find
and set at least: opcache.enable=1
- mailparse Efficient parsing of email requires this extension.
Submission by email or SMS-over-email uses this.
- sphinx A client for the sphinx server, an alternative to MySQL
or Postgresql fulltext search. You will also need a
Sphinx server to serve the search queries.
- gettext For multiple languages. Default on many PHP installs;
will be emulated if not present.
- exif For thumbnails to be properly oriented.
You may also experience better performance from your site if you configure
a PHP cache/accelerator. Most distributions come with "opcache" support.
Enable it in your php.ini where it is documented together with its settings.
Installation
============

View File

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

View File

@ -11,6 +11,9 @@ and follow this procedure:
0. Backup your data. The StatusNet upgrade discussions below have some
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
use the queue daemons):
$ bash scripts/stopdaemons.sh

View File

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

View File

@ -170,12 +170,6 @@ class AllAction extends ShowstreamAction
}
$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()
{
$stream = new InboxNoticeStream($this->target);
$stream = new InboxNoticeStream($this->target, $this->scoped);
return $stream->getNotices(0, $this->limit)->fetchAll();
}

View File

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

View File

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

View File

@ -58,7 +58,7 @@ class ApiGroupListAllAction extends ApiPrivateAuthAction
*
* @return boolean success flag
*/
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
@ -77,9 +77,9 @@ class ApiGroupListAllAction extends ApiPrivateAuthAction
*
* @return void
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
$sitename = common_config('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/
*/
if (!defined('STATUSNET')) {
exit(1);
}
if (!defined('GNUSOCIAL')) { exit(1); }
/**
* 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
{
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
function prepare($args)
protected function handle()
{
parent::prepare($args);
return true;
}
/**
* Handle the request
*
* @param array $args $_REQUEST data (unused)
*
* @return void
*/
function handle($args)
{
parent::handle($args);
parent::handle();
if ($this->format == 'xml') {
$this->initDocument('xml');
@ -77,12 +55,8 @@ class ApiHelpTestAction extends ApiPrivateAuthAction
print '"ok"';
$this->endDocument('json');
} else {
$this->clientError(
// TRANS: Client error displayed when coming across a non-supported API method.
_('API method not found.'),
404,
$this->format
);
throw new ClientException(_('API method not found.'), 404);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,7 +46,7 @@
/api/statuses/update.:format
@par Formats (:format)
xml, json
xml, json, atom
@par HTTP Method(s)
POST
@ -174,7 +174,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
foreach (array_unique($matches[0]) as $match) {
try {
$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
} catch (NoResultException $e) {
// 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);
} elseif ($this->format == 'json') {
$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
*/
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
return true;
@ -66,9 +66,9 @@ class ApiTrendsAction extends ApiPrivateAuthAction
*
* @return void
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
$this->showTrends();
}

View File

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

View File

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

View File

@ -96,7 +96,7 @@ class AttachmentAction extends ManagedAction
{
if (empty($this->attachment->filename)) {
// if it's not a local file, gtfo
common_redirect($this->attachment->url, 303);
common_redirect($this->attachment->getUrl(), 303);
}
parent::showPage();
@ -132,9 +132,5 @@ class AttachmentAction extends ManagedAction
function showSections() {
$ns = new AttachmentNoticeSection($this);
$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(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 $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
*
@ -92,16 +106,6 @@ class AvatarsettingsAction extends SettingsAction
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',
'method' => 'post',
'id' => 'form_settings_avatar',
@ -116,7 +120,7 @@ class AvatarsettingsAction extends SettingsAction
if (Event::handle('StartAvatarFormData', array($this))) {
$this->elementStart('ul', 'form_data');
try {
$original = Avatar::getUploaded($profile);
$original = Avatar::getUploaded($this->scoped);
$this->elementStart('li', array('id' => 'avatar_original',
'class' => 'avatar_view'));
@ -126,7 +130,7 @@ class AvatarsettingsAction extends SettingsAction
$this->element('img', array('src' => $original->displayUrl(),
'width' => $original->width,
'height' => $original->height,
'alt' => $user->nickname));
'alt' => $this->scoped->getNickname()));
$this->elementEnd('div');
$this->elementEnd('li');
} catch (NoAvatarException $e) {
@ -134,7 +138,7 @@ class AvatarsettingsAction extends SettingsAction
}
try {
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$avatar = $this->scoped->getAvatar(AVATAR_PROFILE_SIZE);
$this->elementStart('li', array('id' => 'avatar_preview',
'class' => 'avatar_view'));
// 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(),
'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname));
'alt' => $this->scoped->getNickname()));
$this->elementEnd('div');
if (!empty($avatar->filename)) {
// TRANS: Button on avatar upload page to delete current avatar.
@ -180,16 +184,6 @@ class AvatarsettingsAction extends SettingsAction
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',
'id' => 'form_settings_avatar',
'class' => 'form_settings',
@ -211,7 +205,7 @@ class AvatarsettingsAction extends SettingsAction
$this->element('img', array('src' => Avatar::url($this->filedata['filename']),
'width' => $this->filedata['width'],
'height' => $this->filedata['height'],
'alt' => $user->nickname));
'alt' => $this->scoped->getNickname()));
$this->elementEnd('div');
$this->elementEnd('li');
@ -224,7 +218,7 @@ class AvatarsettingsAction extends SettingsAction
$this->element('img', array('src' => Avatar::url($this->filedata['filename']),
'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE,
'alt' => $user->nickname));
'alt' => $this->scoped->getNickname()));
$this->elementEnd('div');
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...
// but we just want it to output to the outputter.
$this->raw($stream->getString());
// Don't print the page HTML
exit(0);
}
public function isReadOnly($args) {

View File

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

View File

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

View File

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

View File

@ -27,9 +27,7 @@
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
if (!defined('GNUSOCIAL')) { exit(1); }
/**
* 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
* @link http://status.net/
*/
class ConfirmaddressAction extends Action
class ConfirmaddressAction extends ManagedAction
{
/** type of confirmation. */
var $address;
protected $address;
/**
* 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)
protected function doPreparation()
{
parent::handle($args);
if (!common_logged_in()) {
common_set_returnto($this->selfUrl());
common_redirect(common_local_url('login'));
@ -70,32 +57,45 @@ class ConfirmaddressAction extends Action
$code = $this->trimmed('code');
if (!$code) {
// 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);
if (!$confirm) {
if (!$confirm instanceof Confirm_address) {
// 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.
$this->clientError(_('That confirmation code is not for you!'));
throw new AuthorizationException(_('That confirmation code is not for you!'));
}
$type = $confirm->address_type;
$transports = array();
Event::handle('GetImTransports', array(&$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')
$this->serverError(sprintf(_('Unrecognized address type %s'), $type));
throw new ServerException(sprintf(_('Unrecognized address type %s'), $type));
}
$this->address = $confirm->address;
$cur = $this->scoped->getUser();
$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) {
// Already verified, so delete the confirm_address entry
$confirm->delete();
// 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);
@ -122,16 +122,18 @@ class ConfirmaddressAction extends Action
$user_im_prefs->user_id = $cur->id;
if ($user_im_prefs->find() && $user_im_prefs->fetch()) {
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.
$this->clientError(_('That address has already been confirmed.'));
throw new AlreadyFulfilledException(_('That address has already been confirmed.'));
}
$user_im_prefs->screenname = $confirm->address;
$result = $user_im_prefs->update();
if (!$result) {
if ($result === false) {
common_log_db_error($user_im_prefs, 'UPDATE', __FILE__);
// 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{
$user_im_prefs = new User_im_prefs();
@ -140,26 +142,18 @@ class ConfirmaddressAction extends Action
$user_im_prefs->user_id = $cur->id;
$result = $user_im_prefs->insert();
if (!$result) {
if ($result === false) {
common_log_db_error($user_im_prefs, 'INSERT', __FILE__);
// 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();
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.'));
}
$confirm->delete();
$cur->query('COMMIT');
$this->showPage();
}
/**
@ -180,8 +174,6 @@ class ConfirmaddressAction extends Action
*/
function showContent()
{
$cur = common_current_user();
$this->element('p', null,
// TRANS: Success message for the contact address confirmation action.
// TRANS: %s can be 'email', 'jabber', or 'sms'.

View File

@ -51,7 +51,7 @@ class DeleteapplicationAction extends Action
*
* @return boolean success flag
*/
function prepare($args)
function prepare(array $args = array())
{
if (!parent::prepare($args)) {
return false;
@ -89,7 +89,7 @@ class DeleteapplicationAction extends Action
*
* @return void
*/
function handle($args)
function handle()
{
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 allow group admins to delete their own groups
*/
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
@ -114,9 +114,9 @@ class DeletegroupAction extends RedirectingAction
*
* @return void
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) {
$this->returnToPrevious();

View File

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

View File

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

View File

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

View File

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

View File

@ -54,7 +54,7 @@ class FeaturedAction extends Action
return true;
}
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
$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();
}

View File

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

View File

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

View File

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

View File

@ -52,7 +52,7 @@ class GroupblockAction extends RedirectingAction
*
* @return boolean success flag
*/
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
if (!common_logged_in()) {
@ -110,9 +110,9 @@ class GroupblockAction extends RedirectingAction
*
* @return void
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) {
$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
* @link http://status.net/
*/
class GroupbyidAction extends ManagedAction
class GroupbyidAction extends ShowgroupAction
{
/** group we're viewing. */
protected $group = null;
@ -55,10 +55,10 @@ class GroupbyidAction extends ManagedAction
protected function doPreparation()
{
$this->group = User_group::getByID($this->arg('id'));
}
$this->target = $this->group->getProfile();
public function showPage()
{
common_redirect($this->group->homeUrl(), 303);
if ($this->target->isLocal()) {
common_redirect($this->target->getUrl());
}
}
}

View File

@ -211,6 +211,10 @@ class GrouplogoAction extends GroupAction
'height' => AVATAR_PROFILE_SIZE,
'alt' => $this->group->nickname));
$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');
}
@ -315,6 +319,8 @@ class GrouplogoAction extends GroupAction
$this->uploadLogo();
} else if ($this->arg('crop')) {
$this->cropLogo();
} else if ($this->arg('delete')) {
$this->deleteLogo();
} else {
// TRANS: Form validation error message when an unsupported argument is used.
$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()
{
if ($this->msg) {

View File

@ -27,12 +27,7 @@
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/profilelist.php');
require_once INSTALLDIR.'/lib/publicgroupnav.php';
if (!defined('GNUSOCIAL')) { exit(1); }
/**
* List of group members
@ -52,15 +47,6 @@ class GroupmembersAction extends GroupAction
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()
{
if ($this->page == 1) {

View File

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

View File

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

View File

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

View File

@ -359,13 +359,7 @@ class ImsettingsAction extends SettingsAction
throw new AlreadyFulfilledException(_('No pending confirmation to cancel.'));
}
$result = $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.'));
}
$confirm->delete();
// TRANS: Message given after successfully canceling IM address confirmation.
return _('IM confirmation cancelled.');

View File

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

View File

@ -54,7 +54,7 @@ class MakeadminAction extends RedirectingAction
* @return boolean success flag
*/
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
if (!common_logged_in()) {
@ -111,9 +111,9 @@ class MakeadminAction extends RedirectingAction
* @return void
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$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 $inreplyto = null;
/**
* Title of the page
*
@ -75,6 +77,11 @@ class NewnoticeAction extends FormAction
}
}
if ($this->int('inreplyto')) {
// Throws exception if the inreplyto Notice is given but not found.
$this->inreplyto = Notice::getByID($this->int('inreplyto'));
}
// Backwards compatibility for "share this" widget things.
// If no 'content', use 'status_textarea'
$this->formOpts['content'] = $this->trimmed('content') ?: $this->trimmed('status_textarea');
@ -115,7 +122,7 @@ class NewnoticeAction extends FormAction
// simply no attached media to the new notice
if (empty($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;
}
if ($this->int('inreplyto')) {
// Throws exception if the inreplyto Notice is given but not found.
$parent = Notice::getByID($this->int('inreplyto'));
} else {
$parent = null;
}
$act = new Activity();
$act->verb = ActivityVerb::POST;
$act->time = time();
@ -157,9 +157,9 @@ class NewnoticeAction extends FormAction
$act->context = new ActivityContext();
if ($parent instanceof Notice) {
$act->context->replyToID = $parent->getUri();
$act->context->replyToUrl = $parent->getUrl(true); // maybe we don't have to send true here to force a URL?
if ($this->inreplyto instanceof Notice) {
$act->context->replyToID = $this->inreplyto->getUri();
$act->context->replyToUrl = $this->inreplyto->getUrl(true); // maybe we don't have to send true here to force a URL?
}
if ($this->scoped->shareLocation()) {
@ -188,14 +188,14 @@ class NewnoticeAction extends FormAction
// FIXME: We should be able to get the attentions from common_render_content!
// and maybe even directly save whether they're local or not!
$act->context->attention = common_get_attentions($content, $this->scoped, $parent);
$act->context->attention = common_get_attentions($content, $this->scoped, $this->inreplyto);
// $options gets filled with possible scoping settings
ToSelector::fillActivity($this, $act, $options);
$actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE;
$actobj->content = common_render_content($content, $this->scoped, $parent);
$actobj->content = common_render_content($content, $this->scoped, $this->inreplyto);
// Finally add the activity object to our activity
$act->objects[] = $actobj;
@ -224,6 +224,9 @@ class NewnoticeAction extends FormAction
if ($this->getInfo() && $this->stored instanceof Notice) {
$this->showNotice($this->stored);
} elseif (!$this->getError()) {
if (!GNUsocial::isAjax() && $this->inreplyto instanceof Notice) {
$this->showNotice($this->inreplyto);
}
parent::showContent();
}
}

View File

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

View File

@ -55,9 +55,9 @@ class NudgeAction extends Action
*
* @return nothing
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
if (!common_logged_in()) {
// 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
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
$type = $this->trimmed('type');
$short_name = '';
if ($type == 'people') {

View File

@ -53,7 +53,7 @@ class OtpAction extends Action
var $returnto;
var $lt;
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
@ -110,9 +110,9 @@ class OtpAction extends Action
return true;
}
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
// success!
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);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
@ -84,9 +84,9 @@ class PeopletagAction extends Action
return true;
}
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
$this->showPage();
}

View File

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

View File

@ -53,7 +53,7 @@ class PeopletaggedAction extends Action
return true;
}
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
$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();
}
@ -167,7 +167,7 @@ class PeopletagMemberList extends ProfileList
$this->peopletag = $peopletag;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new PeopletagMemberListItem($profile, $this->peopletag, $this->action);
}

View File

@ -68,7 +68,7 @@ class PeopletagsbyuserAction extends Action
}
}
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
@ -135,9 +135,9 @@ class PeopletagsbyuserAction extends Action
return true;
}
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
# 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);
@ -95,9 +95,9 @@ class PeopletagsforuserAction extends Action
return true;
}
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
$this->showPage();
}

View File

@ -53,7 +53,7 @@ class PeopletagsubscribersAction extends Action
return true;
}
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
$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();
}
@ -167,7 +167,7 @@ class PeopletagSubscriberList extends ProfileList
$this->peopletag = $peopletag;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new PeopletagSubscriberListItem($profile, $this->peopletag, $this->action);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -120,9 +120,9 @@ class RegisterAction extends Action
*
* @return void
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
if (common_config('site', 'closed')) {
// 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
*/
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
@ -120,7 +120,7 @@ class RemovepeopletagAction extends Action
*
* @return void
*/
function handle($args)
function handle()
{
// Throws exception on error

View File

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

View File

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

View File

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

View File

@ -158,9 +158,9 @@ class SelftagAction extends Action
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
*/
function prepare($args)
function prepare(array $args = array())
{
parent::prepare($args);
@ -101,9 +101,9 @@ class ShowApplicationAction extends Action
*
* @return void
*/
function handle($args)
function handle()
{
parent::handle($args);
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {

View File

@ -28,12 +28,7 @@
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/noticelist.php';
require_once INSTALLDIR.'/lib/feedlist.php';
if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Group main page
@ -48,7 +43,6 @@ class ShowgroupAction extends GroupAction
{
/** page we're viewing. */
var $page = null;
var $userProfile = null;
var $notice = null;
/**
@ -82,85 +76,15 @@ class ShowgroupAction extends GroupAction
}
}
/**
* 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())
public function getStream()
{
parent::prepare($args);
$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);
if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) {
$stream = new GroupNoticeStream($this->group, $this->scoped);
} 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,
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));
return $stream;
}
/**

View File

@ -113,6 +113,18 @@ class ShowstreamAction extends NoticestreamAction
$this->target->getNickname(), $this->tag)));
}
if (!$this->target->isLocal()) {
// remote profiles at least have Atom, but we can't guarantee anything else
return array(
new Feed(Feed::ATOM,
$this->target->getAtomFeed(),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
$this->target->getNickname()))
);
}
return array(new Feed(Feed::JSON,
common_local_url('ApiTimelineUser',
array(
@ -139,10 +151,7 @@ class ShowstreamAction extends NoticestreamAction
sprintf(_('Notice feed for %s (RSS 2.0)'),
$this->target->getNickname())),
new Feed(Feed::ATOM,
common_local_url('ApiTimelineUser',
array(
'id' => $this->target->getID(),
'format' => 'atom')),
$this->target->getAtomFeed(),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
@ -155,6 +164,17 @@ class ShowstreamAction extends NoticestreamAction
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()
{
if ($this->target->bio) {
@ -162,12 +182,6 @@ class ShowstreamAction extends NoticestreamAction
'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',
array('nickname' => $this->target->getNickname()));
@ -210,19 +224,20 @@ class ShowstreamAction extends NoticestreamAction
function showNotices()
{
$pnl = new NoticeList($this->notice, $this);
$pnl = new PrimaryNoticeList($this->notice, $this);
$cnt = $pnl->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
$args = array('nickname' => $this->target->getNickname());
// either nickname or id will be used, depending on which action (showstream, userbyid...)
$args = array('nickname' => $this->target->getNickname(), 'id' => $this->target->getID());
if (!empty($this->tag))
{
$args['tag'] = $this->tag;
}
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
'showstream', $args);
$this->getActionName(), $args);
}
function showAnonymousMessage()
@ -246,15 +261,6 @@ class ShowstreamAction extends NoticestreamAction
$this->elementEnd('div');
}
function showSections()
{
parent::showSections();
if (!common_config('performance', 'high')) {
$cloud = new PersonalTagCloudSection($this->target, $this);
$cloud->show();
}
}
function noticeFormOptions()
{
$options = parent::noticeFormOptions();

View File

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

View File

@ -368,13 +368,7 @@ class SmssettingsAction extends SettingsAction
throw new AlreadyFulfilledException(_('No pending confirmation to cancel.'));
}
$result = $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.'));
}
$confirm->delete();
// TRANS: Message given after successfully canceling SMS phone number confirmation.
return _('SMS confirmation cancelled.');

View File

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

View File

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

View File

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

View File

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

View File

@ -34,7 +34,7 @@ class TagAction extends ManagedAction
$this->tag = common_canonical_tag($taginput);
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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,7 +53,7 @@ class Attention extends Managed_DataObject
{
try {
$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) {
$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.'"');
}
}
self::blow('attention:stream:%d', $target->getID());
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',
AVATAR_STREAM_SIZE => 'stream',
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->address = $address;
$ca->address_type = $addressType;
if ($ca->find(true)) {
return $ca;
if (!$ca->find(true)) {
throw new NoResultException($ca);
}
return null;
return $ca;
}
static function saveNew($user, $address, $addressType, $extra=null)
@ -66,4 +66,96 @@ class Confirm_address extends Managed_DataObject
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 $id; // int(4) primary_key not_null auto_increment
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $url; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
@ -45,6 +46,7 @@ class Conversation extends Managed_DataObject
'fields' => array(
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'),
'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 191, 'description' => 'URI of the conversation'),
'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'Resolvable URL, preferrably remote (local can be generated on the fly)'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
),
@ -89,15 +91,21 @@ class Conversation extends Managed_DataObject
*
* @return Conversation the new conversation DO
*/
static function create($uri=null, $created=null)
static function create(ActivityContext $ctx=null, $created=null)
{
// Be aware that the Notice does not have an id yet since it's not inserted!
$conv = new Conversation();
$conv->created = $created ?: common_sql_now();
$conv->uri = $uri ?: sprintf('%s%s=%s:%s=%s',
if ($ctx instanceof ActivityContext) {
$conv->uri = $ctx->conversation;
$conv->url = $ctx->conversation_url;
} else {
$conv->uri = sprintf('%s%s=%s:%s=%s',
TagURI::mint(),
'objectType', 'thread',
'nonce', common_random_hexstr(8));
$conv->url = null; // locally generated Conversation objects don't get static URLs stored
}
// This insert throws exceptions on failure
$conv->insert();

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.
}
// 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->url = $given_url;
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);
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
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');
}
@ -226,18 +264,19 @@ class File extends Managed_DataObject
public function getFilename()
{
if (!self::validFilename($this->filename)) {
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
return self::tryFilename($this->filename);
}
return $this->filename;
public function getSize()
{
return intval($this->size);
}
// where should the file go?
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.
$origname = basename($origname, ".$ext");
@ -258,19 +297,54 @@ class File extends Managed_DataObject
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 {
// first see if we know the extension for our mimetype
$ext = common_supported_mime_to_ext($mimetype);
// we do, so use it!
return $ext;
} catch (UnknownMimeExtensionException $e) {
// We don't know the extension for this mimetype, but let's guess.
// 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;
}
} catch (Exception $e) {
// We don't support this mimetype, but let's guess the extension
common_log(LOG_INFO, 'Problem when figuring out extension for mimetype: '._ve($e));
}
// 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);
}
$ext = $matches[1];
}
return mb_strtolower($ext);
return mb_strtolower($matches[1]);
}
/**
@ -281,19 +355,27 @@ class File extends Managed_DataObject
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
*/
static function path($filename)
{
if (!self::validFilename($filename)) {
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
}
self::tryFilename($filename);
$dir = common_config('attachments', 'dir');
if ($dir[strlen($dir)-1] != '/') {
$dir .= '/';
if (!in_array($dir[mb_strlen($dir)-1], ['/', '\\'])) {
$dir .= DIRECTORY_SEPARATOR;
}
return $dir . $filename;
@ -301,10 +383,7 @@ class File extends Managed_DataObject
static function url($filename)
{
if (!self::validFilename($filename)) {
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
}
self::tryFilename($filename);
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 $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 $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
*
@ -424,7 +505,13 @@ class File extends Managed_DataObject
// null means "always use file as thumbnail"
// false means you get choice between frozen frame or original when calling getThumbnail
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;
}
public function getUrl($prefer_local=true)
public function getAttachmentUrl()
{
if ($prefer_local && !empty($this->filename)) {
// A locally stored file, so let's generate a URL for our instance.
return self::url($this->filename);
return common_local_url('attachment', array('attachment'=>$this->getID()));
}
/**
* @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
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)
{
$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);
}
@ -593,6 +697,13 @@ class File extends Managed_DataObject
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)
{
if (empty($url)) {
@ -624,16 +735,18 @@ class File extends Managed_DataObject
$dupfile = new File();
// First we find file entries that would be duplicates of this when shortened
// ... and we'll just throw the dupes out the window for now! It's already so borken.
$dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = "%1$s"', $file->shortenedurl));
$dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = %1$s', $dupfile->_quote($file->shortenedurl)));
// Leave one of the URLs in the database by using ->find(true) (fetches first entry)
if ($dupfile->find(true)) {
print "\nShortening url entry for $table id: {$file->id} [";
$orig = clone($dupfile);
$origurl = $dupfile->url; // save for logging purposes
$dupfile->url = $file->shortenedurl; // make sure it's only 191 chars from now on
$dupfile->update($orig);
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
// only start deleting with this fetch.
while($dupfile->fetch()) {
common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl));
print ".";
$dupfile->delete();
}

View File

@ -172,43 +172,69 @@ class File_redirection extends Managed_DataObject
try {
$r = File_redirection::getByUrl($in_url);
if($r instanceof File_redirection) {
try {
$f = File::getKV('id',$r->file_id);
$f = File::getByID($r->file_id);
$r->file = $f;
$r->redir_url = $f->url;
} catch (NoResultException $e) {
// 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();
return self::where($in_url);
}
// File_redirecion and File record found, return both
return $r;
}
} catch (NoResultException $e) {
// File_redirecion record not found, but this might be a direct link to a file
try {
$f = File::getByUrl($in_url);
$redir->file_id = $f->id;
$redir->file = $f;
return $redir;
} 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) {
// try to follow redirects and get the final url
$redir_info = File_redirection::lookupWhere($in_url);
if(is_string($redir_info)) {
$redir_info = array('url' => $redir_info);
}
// Save the file if we don't have it already
$redir->file = File::saveNew($redir_info,$redir_info['url']);
// the last url in the redirection chain can actually be a redirect!
// this is the case with local /attachment/{file_id} links
// in that case we have the file id already
try {
$r = File_redirection::getByUrl($redir_info['url']);
// If this is a redirection, save it
// (if it hasn't been saved yet by some other process while we we
// were running lookupWhere())
if($redir_info['url'] != $in_url) {
$f = File::getKV('id',$r->file_id);
if($f instanceof File) {
$redir->file = $f;
$redir->redir_url = $f->url;
} else {
// Invalid entry in File_redirection, 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 trying again...");
$r->delete();
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) {
@ -419,8 +445,8 @@ class File_redirection extends Managed_DataObject
}
public function getFile() {
if(empty($this->file) && $this->file_id) {
$this->file = File::getKV('id', $this->file_id);
if (!$this->file instanceof File) {
$this->file = File::getByID($this->file_id);
}
return $this->file;

View File

@ -98,6 +98,7 @@ class File_thumbnail extends Managed_DataObject
if ($notNullUrl) {
$thumb->whereAdd('url IS NOT NULL');
}
$thumb->orderBy('modified ASC'); // the first created, a somewhat ugly hack
$thumb->limit(1);
if (!$thumb->find(true)) {
throw new NoResultException($thumb);
@ -129,23 +130,88 @@ class File_thumbnail extends Managed_DataObject
static function path($filename)
{
// TODO: Store thumbnails in their own directory and don't use File::path here
return File::path($filename);
File::tryFilename($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)
{
// TODO: Store thumbnails in their own directory and don't use File::url here
return File::url($filename);
File::tryFilename($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()
{
$filepath = self::path($this->filename);
if (!file_exists($filepath)) {
throw new FileNotFoundException($filepath);
$oldpath = File::path($this->getFilename());
$thumbpath = self::path($this->getFilename());
// 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;
}
return $filepath;
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 $thumbpath;
}
public function getUrl()
@ -188,11 +254,15 @@ class File_thumbnail extends Managed_DataObject
public function delete($useWhere=false)
{
if (!empty($this->filename) && file_exists(File_thumbnail::path($this->filename))) {
$deleted = @unlink(self::path($this->filename));
try {
$thumbpath = self::path($this->getFilename());
// if file does not exist, try to delete it
$deleted = !file_exists($thumbpath) || @unlink($thumbpath);
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);
@ -203,6 +273,10 @@ class File_thumbnail extends Managed_DataObject
return File::getByID($this->file_id);
}
public function getFileId()
{
return $this->file_id;
}
static public function hashurl($url)
{

View File

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

View File

@ -383,14 +383,35 @@ abstract class Managed_DataObject extends Memcached_DataObject
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)) {
throw new EmptyIdException(get_called_class());
throw new EmptyPkeyValueException(get_called_class(), 'id');
}
// getByPK throws exception if id is null
// or if the class does not have a single 'id' column as primary key
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
*
@ -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()');
}
$this->onUpdateKeys($orig);
// do it in a transaction
$this->query('BEGIN');
@ -580,6 +603,11 @@ abstract class Managed_DataObject extends Memcached_DataObject
// NOOP by default
}
protected function onUpdateKeys(Managed_DataObject $orig)
{
// NOOP by default
}
public function insert()
{
$this->onInsert();

View File

@ -67,10 +67,11 @@ class Memcached_DataObject extends Safe_DataObject
* @param string $cls Class to fetch
* @param string $keyCol name of column for key
* @param array $keyVals key values to fetch
* @param boolean $skipNulls skip provided null values
*
* @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;
@ -83,6 +84,14 @@ class Memcached_DataObject extends Safe_DataObject
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);
// 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