[DirectMessage] Major plugin rework

This commit does the necessary rework to store private messages
as Notices and to support Federation. The plugin's README presents
some more detail about the changes and future work that is still
required to do.
This commit is contained in:
tenma 2019-08-19 22:51:51 +01:00 committed by Diogo Peralta Cordeiro
parent 23512bea14
commit 0b30d932fc
14 changed files with 952 additions and 705 deletions

View File

@ -576,53 +576,66 @@ function mail_notify_nudge($from, $to)
* This function checks to see if the recipient wants notification
* of DMs and has a configured email address.
*
* @param Message $message message to notify about
* @param User $from user sending message; default to sender
* @param User $to user receiving message; default to recipient
* @param Notice $message message to notify about
* @param User $from user sending message
* @param array $to users receiving the message
*
* @return boolean success code
*/
function mail_notify_message($message, $from=null, $to=null)
function mail_notify_message(Notice $message, Profile $from = null, ?array $to = null)
{
if (is_null($from)) {
$from = User::getKV('id', $message->from_profile);
$from = $message->getProfile();
}
if (is_null($to)) {
$to = User::getKV('id', $message->to_profile);
$to = [];
foreach ($message->getAttentionProfiles() as $attention) {
if ($attention->isLocal()) {
$to[] = $attention;
}
}
}
if (is_null($to->email) || !$to->emailnotifymsg) {
return true;
$success = true;
foreach ($to as $t) {
if (is_null($t->email) || !$t->emailnotifymsg) {
continue;
}
common_switch_locale($t->language);
// TRANS: Subject for direct-message notification email.
// TRANS: %s is the sending user's nickname.
$subject = sprintf(_('New private message from %s'), $from->getNickname());
// TRANS: Body for direct-message notification email.
// TRANS: %1$s is the sending user's long name, %2$s is the sending user's nickname,
// TRANS: %3$s is the message content, %4$s a URL to the message,
$body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n".
"------------------------------------------------------\n".
"%3\$s\n".
"------------------------------------------------------\n\n".
"You can reply to their message here:\n\n".
"%4\$s\n\n".
"Don't reply to this email; it won't get to them."),
$from->getBestName(),
$from->getNickname(),
$message->getContent(),
common_local_url('newmessage', ['to' => $from->getID()])) .
mail_footer_block();
$headers = _mail_prepare_headers('message', $t->getNickname(), $from->getNickname());
common_switch_locale();
if (!mail_to_user($t, $subject, $body, $headers)) {
common_log(LOG_ERR, "Failed to notify user:{$t->getID()} about the new message:{$message->getID()} sent by user:{$from->getID()}");
$success = false;
}
}
common_switch_locale($to->language);
// TRANS: Subject for direct-message notification email.
// TRANS: %s is the sending user's nickname.
$subject = sprintf(_('New private message from %s'), $from->nickname);
$from_profile = $from->getProfile();
// TRANS: Body for direct-message notification email.
// TRANS: %1$s is the sending user's long name, %2$s is the sending user's nickname,
// TRANS: %3$s is the message content, %4$s a URL to the message,
$body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n".
"------------------------------------------------------\n".
"%3\$s\n".
"------------------------------------------------------\n\n".
"You can reply to their message here:\n\n".
"%4\$s\n\n".
"Don't reply to this email; it won't get to them."),
$from_profile->getBestName(),
$from->nickname,
$message->content,
common_local_url('newmessage', array('to' => $from->id))) .
mail_footer_block();
$headers = _mail_prepare_headers('message', $to->nickname, $from->nickname);
common_switch_locale();
return mail_to_user($to, $subject, $body, $headers);
return $success;
}
/**

View File

@ -1,47 +1,66 @@
<?php
/*
* GNU Social - a federating social network
* Copyright (C) 2014, Free Software Foundation, 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('GNUSOCIAL')) { exit(1); }
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* @maintainer Mikael Nordfeldth <mmn@hethane.se>
* GNUsocial implementation of Direct Messages
*
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
// require needed abstractions first
require_once __DIR__ . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'messagelist.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'messagelistitem.php';
// Import plugin libs
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
require_once $filename;
}
// Import plugin models
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
require_once $filename;
}
/**
* @category Plugin
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class DirectMessagePlugin extends Plugin
{
const PLUGIN_VERSION = '2.0.0';
public function onCheckSchema()
{
$schema = Schema::get();
$schema->ensureTable('message', Message::schemaDef());
return true;
}
const PLUGIN_VERSION = '3.0.0';
public function onRouterInitialized(URLMapper $m)
{
// web front-end actions
$m->connect('message/new', ['action' => 'newmessage']);
$m->connect('message/new',
['action' => 'newmessage']);
$m->connect('message/new?to=:to',
['action' => 'newmessage'],
['to' => Nickname::DISPLAY_FMT]);
['to' => '[0-9]+']);
$m->connect('message/:message',
['action' => 'showmessage'],
['action' => 'showmessage'],
['message' => '[0-9]+']);
// direct messages
@ -59,35 +78,14 @@ class DirectMessagePlugin extends Plugin
return true;
}
public function onAppendUserActivityStreamObjects(UserActivityStream $uas, array &$objs)
{
// Messages _from_ the user
$msgMap = Message::listGet('from_profile', array($uas->getUser()->id));
$messages = $msgMap[$uas->getUser()->id];
if (!empty($uas->after)) {
$messages = array_filter($messages, array($uas, 'createdAfter'));
}
foreach ($messages as $message) {
$objs[] = clone($message);
}
// Messages _to_ the user
$msgMap = Message::listGet('to_profile', array($uas->getUser()->id));
$messages = $msgMap[$uas->getUser()->id];
if (!empty($uas->after)) {
$messages = array_filter($messages, array($uas, 'createdAfter'));
}
foreach ($messages as $message) {
$objs[] = clone($message);
}
return true;
}
/**
* Are we allowed to perform a certain command over the API?
*
* @param Command $cmd
* @param bool &$supported
* @return bool hook value
*/
public function onCommandSupportedAPI(Command $cmd, &$supported)
public function onCommandSupportedAPI(Command $cmd, ?bool &$supported) : bool
{
$supported = $supported || $cmd instanceof MessageCommand;
return true;
@ -99,13 +97,12 @@ class DirectMessagePlugin extends Plugin
* @param string $cmd Command being run
* @param string $arg Rest of the message (including address)
* @param User $user User sending the message
* @param Command &$result The resulting command object to be run.
*
* @return boolean hook value
* @param Command|bool &$result The resulting command object to be run.
* @return bool hook value
*/
public function onStartInterpretCommand($cmd, $arg, $user, &$result)
public function onStartInterpretCommand(string $cmd, string $arg, User $user, &$result) : bool
{
$dm_cmds = array('d', 'dm');
$dm_cmds = ['d', 'dm'];
if ($result === false && in_array($cmd, $dm_cmds)) {
if (!empty($arg)) {
@ -119,13 +116,20 @@ class DirectMessagePlugin extends Plugin
return true;
}
public function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped=null)
/**
* Show Message button in someone's left-side navigation menu
*
* @param Menu $menu
* @param Profile $target
* @param Profile $scoped
* @return void
*/
public function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped = null)
{
if ($scoped instanceof Profile && $scoped->id == $target->id
&& !common_config('singleuser', 'enabled')) {
$menu->out->menuItem(common_local_url('inbox', array('nickname' =>
$target->getNickname())),
$menu->out->menuItem(common_local_url('inbox', ['nickname' => $target->getNickname()]),
// TRANS: Menu item in personal group navigation menu.
_m('MENU','Messages'),
// TRANS: Menu item title in personal group navigation menu.
@ -134,46 +138,89 @@ class DirectMessagePlugin extends Plugin
}
}
public function onEndProfilePageActionsElements(HTMLOutputter $out, Profile $profile)
/**
* Show Message button in someone's profile page
*
* @param HTMLOutputter $out
* @param Profile $profile
* @return bool hook flag
*/
public function onEndProfilePageActionsElements(HTMLOutputter $out, Profile $profile) : bool
{
$scoped = Profile::current();
if (!$scoped instanceof Profile) {
if (!$scoped instanceof Profile || $scoped->getID() === $profile->getID()) {
return true;
}
if ($profile->isLocal() && $scoped->mutuallySubscribed($profile)) {
if (!$profile->isLocal() && Event::handle('DirectMessageProfilePageActions', [$profile])) {
// nothing to do if remote profile and no one to validate it
return true;
}
if (!$profile->hasBlocked($scoped)) {
$out->elementStart('li', 'entity_send-a-message');
$out->element('a', array('href' => common_local_url('newmessage', array('to' => $profile->id)),
// TRANS: Link title for link on user profile.
'title' => _('Send a direct message to this user.')),
// TRANS: Link text for link on user profile.
_m('BUTTON','Message'));
$out->element('a',
['href' => common_local_url('newmessage', ['to' => $profile->getID()]),
// TRANS: Link title for link on user profile.
'title' => _('Send a direct message to this user.')],
// TRANS: Link text for link on user profile.
_m('BUTTON','Message'));
$out->elementEnd('li');
}
return true;
}
public function onProfileDeleteRelated(Profile $profile, &$related)
/**
* Notice table is used to store private messages in a newer version of the plugin,
* this ensures we migrate entries from the old message table.
*
* @return bool hook flag
*/
public function onEndUpgrade() : bool
{
$msg = new Message();
$msg->from_profile = $profile->id;
$msg->delete();
try {
$schema = Schema::get();
$schema->getTableDef('message');
} catch (SchemaTableMissingException $e) {
return true;
}
$message = new Message();
$message->selectAdd(); // clears it
$message->selectAdd('id');
$message->orderBy('created ASC');
if ($message->find()) {
while ($message->fetch()) {
$msg = Message::getKV('id', $message->id);
$act = $msg->asActivity();
Notice::saveActivity($act,
$msg->getFrom(),
['source' => 'web',
'scope' => NOTICE::MESSAGE_SCOPE]);
}
}
$message->free();
$message = null;
$schema->dropTable('message');
$msg = new Message();
$msg->to_profile = $profile->id;
$msg->delete();
return true;
}
public function onPluginVersion(array &$versions)
public function onPluginVersion(array &$versions) : bool
{
$versions[] = array('name' => 'Direct Message',
'version' => self::PLUGIN_VERSION,
'author' => 'Mikael Nordfeldth',
'homepage' => 'http://gnu.io/',
'rawdescription' =>
// TRANS: Plugin description.
_m('Direct Message to other local users (broken out of core).'));
$versions[] = ['name' => 'Direct Message',
'version' => self::PLUGIN_VERSION,
'author' => 'Mikael Nordfeldth, Bruno Casteleiro',
'homepage' => 'http://gnu.io/',
'rawdescription' =>
// TRANS: Plugin description.
_m('Direct Message to other local users.')];
return true;
}

View File

@ -0,0 +1,9 @@
FillDirectMessageRecipients: after the plugin populates the recipients select-box; federation plugins must add their own recipients; note that only subscriptions should be added
- User $current: Currently logged user
- array &$recipeints: Profiles to be shown in the select-box
DirectMessageProfilePageActions: when about to show the direct message button in someone's profile; federation plugins must validate their users otherwise the button is ommited
- Profile $target: Profile receiving the message button
SendDirectMessage: after storing a new private message; federation plugins must distribute the message to the remote profiles
- Notice $message: Message to be distributed

View File

@ -1,4 +1,4 @@
The DirectMessage plugin allows users to send Direct Message to other local users
The DirectMessage plugin allows users to send Direct Messages
Installation
============
@ -8,3 +8,28 @@ Settings
========
none
Changes from previous release
=============================
- Migrate from message table to notice table
This change implied the write of upgrading logic, the addition of a new
Notice scope (NOTICE::MESSAGE_SCOPE) and updating the save logic.
- Support Federation
DM is still in charge of local communications-only but it now uses a few new
custom events to allow remote handling of the private messages.
TODO
====
- Review API actions, broken after new update
- Review Command events
- Update messagelistitem (UI) to support multi-recipient. Right now we present only
one of the recipients in the message header.
- Update messagelistitem (UI) to support no-recipient, which happens when a message
is sent to profiles that blocked the sender. Right now we don't present this messages
at all because of the UI requirements, but it is still stored in the database.
- Add delete, like and reply actions. Replies need further changes like adding
support for private-conversations.

View File

@ -1,104 +1,87 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* StatusNet, the distributed open-source microblogging tool
* GNUsocial implementation of Direct Messages
*
* action handler for message inbox
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (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/>.
*
* @category Message
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
if (!defined('GNUSOCIAL')) { exit(1); }
defined('GNUSOCIAL') || die();
/**
* action handler for message inbox
* Action handler for the inbox
*
* @category Message
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @see MailboxAction
* @category Plugin
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class InboxAction extends MailboxAction
{
/**
* Title of the page
* Title of the page.
*
* @return string page title
*/
function title()
function title() : string
{
if ($this->page > 1) {
// TRANS: Title for all but the first page of the inbox page.
// TRANS: %1$s is the user's nickname, %2$s is the page number.
return sprintf(_('Inbox for %1$s - page %2$d'), $this->user->nickname,
return sprintf(_m('Inbox for %1$s - page %2$d'), $this->user->getNickname(),
$this->page);
} else {
// TRANS: Title for the first page of the inbox page.
// TRANS: %s is the user's nickname.
return sprintf(_('Inbox for %s'), $this->user->nickname);
return sprintf(_m('Inbox for %s'), $this->user->getNickname());
}
}
/**
* Retrieve the messages for this user and this page
* Retrieve the messages for this user and this page.
*
* Does a query for the right messages
*
* @return Message data object with stream for messages
*
* @see MailboxAction::getMessages()
* @return Notice data object with stream for messages
*/
function getMessages()
{
$message = new Message();
$message->to_profile = $this->user->id;
$message->orderBy('created DESC, id DESC');
$message->limit((($this->page - 1) * MESSAGES_PER_PAGE),
MESSAGES_PER_PAGE + 1);
if ($message->find()) {
return $message;
} else {
return null;
}
return MessageModel::inboxMessages($this->user, $this->page);
}
/**
* Retrieve inbox MessageList widget
*/
function getMessageList($message)
{
return new InboxMessageList($this, $message);
}
/**
* Instructions for using this page
* Instructions for using this page.
*
* @return string localised instructions for using the page
*/
function getInstructions()
function getInstructions() : string
{
// TRANS: Instructions for user inbox page.
return _('This is your inbox, which lists your incoming private messages.');
return _m('This is your inbox, which lists your incoming private messages.');
}
}

View File

@ -1,66 +1,55 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* StatusNet, the distributed open-source microblogging tool
* GNUsocial implementation of Direct Messages
*
* Handler for posting new messages
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (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/>.
*
* @category Personal
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
if (!defined('GNUSOCIAL')) { exit(1); }
defined('GNUSOCIAL') || die();
/**
* Action for posting new direct messages
*
* @category Personal
* @package StatusNet
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class NewmessageAction extends FormAction
{
var $content = null;
var $to = null;
var $other = null;
protected $form = 'Message'; // will become MessageForm later
protected $form = 'Message';
protected $to = null;
protected $content = null;
/**
* Title of the page
*
* Note that this usually doesn't get called unless something went wrong
* Title of the page.
* Note that this usually doesn't get called unless something went wrong.
*
* @return string page title
*/
function title()
function title() : string
{
// TRANS: Page title for new direct message page.
return _('New message');
@ -68,31 +57,29 @@ class NewmessageAction extends FormAction
protected function doPreparation()
{
$this->content = $this->trimmed('content');
$this->to = $this->trimmed('to');
if ($this->to) {
$this->other = Profile::getKV('id', $this->to);
if (!$this->other instanceof Profile) {
if ($this->trimmed('to')) {
$this->to = Profile::getKV('id', $this->trimmed('to'));
if (!$this->to instanceof Profile) {
// TRANS: Client error displayed trying to send a direct message to a non-existing user.
$this->clientError(_('No such user.'), 404);
}
if (!$this->other->isLocal()) {
// TRANS: Explains that current federation does not support direct, private messages yet.
$this->clientError(_('You cannot send direct messages to federated users yet.'));
}
if (!$this->scoped->mutuallySubscribed($this->other)) {
// TRANS: Client error displayed trying to send a direct message to a user while sender and
// TRANS: receiver are not subscribed to each other.
$this->clientError(_('You cannot send a message to this user.'), 404);
}
$this->formOpts['to'] = $this->to;
}
return true;
if ($this->trimmed('content')) {
$this->content = $this->trimmed('content');
$this->formOpts['content'] = $this->content;
}
if ($this->trimmed('to-box')) {
$selected = explode(':', $this->trimmed('to-box'));
if (sizeof($selected) == 2) {
$this->to = Profile::getKV('id', $selected[1]);
// validating later
}
}
}
protected function doPost()
@ -106,35 +93,43 @@ class NewmessageAction extends FormAction
$content_shortened = $this->scoped->shortenLinks($this->content);
if (Message::contentTooLong($content_shortened)) {
if (MessageModel::contentTooLong($content_shortened)) {
// TRANS: Form validation error displayed when message content is too long.
// TRANS: %d is the maximum number of characters for a message.
$this->clientError(sprintf(_m('That\'s too long. Maximum message size is %d character.',
'That\'s too long. Maximum message size is %d characters.',
Message::maxContent()),
Message::maxContent()));
MessageModel::maxContent()),
MessageModel::maxContent()));
}
if (!$this->other instanceof Profile) {
// TRANS: Form validation error displayed trying to send a direct message without specifying a recipient.
$this->clientError(_('No recipient specified.'));
} else if (!$this->scoped->mutuallySubscribed($this->other)) {
// TRANS: Client error displayed trying to send a direct message to a user while sender and
// TRANS: receiver are not subscribed to each other.
$this->clientError(_('You cannot send a message to this user.'), 404);
} else if ($this->scoped->id == $this->other->id) {
// TRANS: Client error displayed trying to send a direct message to self.
$this->clientError(_('Do not send a message to yourself; ' .
'just say it to yourself quietly instead.'), 403);
// validate recipients
if (!$this->to instanceof Profile) {
$mentions = common_find_mentions($this->content, $this->scoped);
if (empty($mentions)) {
$this->clientError(_('No recipients specified.'));
}
} else {
// push to-box profile to the content message, will be
// detected during Notice save
try {
if ($this->to->isLocal()) {
$this->content = "@{$this->to->getNickname()} {$this->content}";
} else {
$this->content = '@' . substr($this->to->getAcctUri(), 5) . " {$this->content}";
}
} catch (ProfileNoAcctUriException $e) {
// well, I'm no magician
}
}
$message = Message::saveNew($this->scoped->id, $this->other->id, $this->content, 'web');
$message->notify();
$message = MessageModel::saveNew($this->scoped, $this->content);
Event::handle('SendDirectMessage', [$message]);
mail_notify_message($message);
if (GNUsocial::isAjax()) {
// TRANS: Confirmation text after sending a direct message.
// TRANS: %s is the direct message recipient.
return sprintf(_('Direct message to %s sent.'), $this->other->getNickname());
return sprintf(_('Direct message to %s sent.'), $this->to->getNickname());
}
$url = common_local_url('outbox', array('nickname' => $this->scoped->getNickname()));

View File

@ -1,43 +1,39 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* StatusNet, the distributed open-source microblogging tool
* GNUsocial implementation of Direct Messages
*
* action handler for message inbox
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (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/>.
*
* @category Message
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
if (!defined('GNUSOCIAL')) { exit(1); }
defined('GNUSOCIAL') || die();
/**
* action handler for message outbox
* Action handler for the outbox
*
* @category Message
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @see MailboxAction
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class OutboxAction extends MailboxAction
{
@ -46,61 +42,57 @@ class OutboxAction extends MailboxAction
*
* @return string page title
*/
function title()
function title() : string
{
if ($this->page > 1) {
// TRANS: Title for outbox for any but the fist page.
// TRANS: %1$s is the user nickname, %2$d is the page number.
return sprintf(_('Outbox for %1$s - page %2$d'),
$this->user->nickname, $page);
return sprintf(_m('Outbox for %1$s - page %2$d'),
$this->user->getNickname(), $page);
} else {
// TRANS: Title for first page of outbox.
return sprintf(_('Outbox for %s'), $this->user->nickname);
return sprintf(_m('Outbox for %s'), $this->user->getNickname());
}
}
/**
* retrieve the messages for this user and this page
* Retrieve the messages for this user and this page.
*
* Does a query for the right messages
*
* @return Message data object with stream for messages
*
* @see MailboxAction::getMessages()
* @return Notice data object with stream for messages
*/
function getMessages()
{
$message = new Message();
$message->from_profile = $this->user->id;
$message->orderBy('created DESC, id DESC');
$message->limit((($this->page - 1) * MESSAGES_PER_PAGE),
MESSAGES_PER_PAGE + 1);
if ($message->find()) {
return $message;
} else {
return null;
}
return MessageModel::outboxMessages($this->user, $this->page);
}
/**
* Retrieve outbox MessageList widget.
*/
function getMessageList($message)
{
return new OutboxMessageList($this, $message);
}
/**
* instructions for using this page
* Instructions for using this page.
*
* @return string localised instructions for using the page
*/
function getInstructions()
function getInstructions() : string
{
// TRANS: Instructions for outbox.
return _('This is your outbox, which lists private messages you have sent.');
return _m('This is your outbox, which lists private messages you have sent.');
}
}
/**
* Outbox MessageList widget
*
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class OutboxMessageList extends MessageList
{
function newItem($message)
@ -109,15 +101,30 @@ class OutboxMessageList extends MessageList
}
}
/**
* Outbox MessageListItem widget
*
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class OutboxMessageListItem extends MessageListItem
{
/**
* Returns the profile we want to show with the message
*
* @return Profile The profile that matches the message
* Note that the plugin now handles sending for multiple profiles,
* but since the UI isn't changed yet, we still retrieve a single
* profile from this function (or null, if for blocking reasons
* there are no attentions stored).
*
* @return Profile|null
*/
function getMessageProfile()
function getMessageProfile() : ?Profile
{
return $this->message->getTo();
$attentions = $this->message->getAttentionProfiles();
return empty($attentions) ? null : $attentions[0];
}
}

View File

@ -1,130 +1,151 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* StatusNet, the distributed open-source microblogging tool
* GNUsocial implementation of Direct Messages
*
* Show a single message
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
/**
* Action for showing a single message
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (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/>.
*
* @category Personal
* @package StatusNet
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Show a single message
*
* @category Personal
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ShowmessageAction extends Action
{
/**
* Message object to show
*/
var $message = null;
protected $message = null;
protected $from = null;
protected $attentions = null;
protected $user = null;
/**
* The current user
*/
var $user = null;
/**
* Load attributes based on database arguments
*
* Loads all the DB stuff
* Load attributes based on database arguments.
*
* @param array $args $_REQUEST array
*
* @return success flag
* @return bool success flag
*/
function prepare(array $args = array())
function prepare($args = [])
{
parent::prepare($args);
$this->page = 1;
$id = $this->trimmed('message');
$this->message = Message::getKV('id', $id);
if (!$this->message) {
// TRANS: Client error displayed requesting a single message that does not exist.
$this->clientError(_('No such message.'), 404);
if (!$this->trimmed('message')) {
return true;
}
$this->message = Notice::getKV('id', $this->trimmed('message'));
if (!$this->message instanceof Notice) {
// TRANS: Client error displayed requesting a single message that does not exist.
$this->clientError(_m('No such message.'), 404);
}
$this->from = $this->message->getProfile();
$this->attentions = $this->message->getAttentionProfiles();
$this->user = common_current_user();
if (empty($this->user) ||
($this->user->id != $this->message->from_profile &&
$this->user->id != $this->message->to_profile)) {
// TRANS: Client error displayed requesting a single direct message the requesting user was not a party in.
throw new ClientException(_('Only the sender and recipient ' .
'may read this message.'), 403);
if (empty($this->user) || $this->user->getID() != $this->from->getID()) {
$receiver = false;
foreach ($this->attentions as $attention) {
if ($this->user->getID() == $attention->getID()) {
$receiver = true;
break;
}
}
if (!$receiver) {
// TRANS: Client error displayed requesting a single direct message the requesting user was not a party in.
throw new ClientException(_m('Only the sender and recipients may read this message.'), 403);
}
}
return true;
}
/**
* Handler method.
*
* @return void
*/
function handle()
{
$this->showPage();
}
function title()
/**
* Title of the page.
*
* @return string page title
*/
function title() : string
{
if ($this->user->id == $this->message->from_profile) {
$to = $this->message->getTo();
// @todo FIXME: Might be nice if the timestamp could be localised.
// TRANS: Page title for single direct message display when viewing user is the sender.
// TRANS: %1$s is the addressed user's nickname, $2$s is a timestamp.
return sprintf(_('Message to %1$s on %2$s'),
$to->nickname,
common_exact_date($this->message->created));
} else if ($this->user->id == $this->message->to_profile) {
$from = $this->message->getFrom();
if ($this->user->getID() == $this->from->getID()) {
if (sizeof($this->attentions) > 1) {
return sprintf(_m('Message to many on %1$s'), common_exact_date($this->message->getCreated()));
} else {
$to = Profile::getKV('id', $this->attentions[0]->getID());
// @todo FIXME: Might be nice if the timestamp could be localised.
// TRANS: Page title for single direct message display when viewing user is the sender.
// TRANS: %1$s is the addressed user's nickname, $2$s is a timestamp.
return sprintf(_m('Message to %1$s on %2$s'),
$to->getBestName(),
common_exact_date($this->message->getCreated()));
}
} else {
// @todo FIXME: Might be nice if the timestamp could be localised.
// TRANS: Page title for single message display.
// TRANS: %1$s is the sending user's nickname, $2$s is a timestamp.
return sprintf(_('Message from %1$s on %2$s'),
$from->nickname,
common_exact_date($this->message->created));
return sprintf(_m('Message from %1$s on %2$s'),
$this->from->getBestName(),
common_exact_date($this->message->getCreated()));
}
}
/**
* Show content.
*
* @return void
*/
function showContent()
{
$this->elementStart('ul', 'notices messages');
$ml = new ShowMessageListItem($this, $this->message, $this->user);
$ml = new ShowMessageListItem($this, $this->message, $this->user, $this->from, $this->attentions);
$ml->show();
$this->elementEnd('ul');
}
function isReadOnly($args)
/**
* Is this action read-only?
*
* @param array $args other arguments
* @return bool true if read-only action, false otherwise
*/
function isReadOnly($args) : bool
{
return true;
}
@ -134,30 +155,38 @@ class ShowmessageAction extends Action
*
* @return void
*/
function showAside() {
}
}
/**
* showmessage action's MessageListItem widget.
*
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class ShowMessageListItem extends MessageListItem
{
var $user;
protected $user;
protected $from;
protected $attentions;
function __construct($out, $message, $user)
function __construct($out, $message, $user, $from, $attentions)
{
parent::__construct($out, $message);
$this->user = $user;
$this->user = $user;
$this->from = $from;
$this->attentions = $attentions;
}
function getMessageProfile()
function getMessageProfile() : ?Profile
{
if ($this->user->id == $this->message->from_profile) {
return $this->message->getTo();
} else if ($this->user->id == $this->message->to_profile) {
return $this->message->getFrom();
} else {
// This shouldn't happen
return null;
}
return $this->user->getID() == $this->from->getID() ?
$this->attentions[0] : $this->from;
}
}

View File

@ -1,17 +1,49 @@
<?php
if (!defined('GNUSOCIAL')) { exit(1); }
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* Table Definition for message
* GNUsocial implementation of Direct Messages
*
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
/**
* Table definition for message.
*
* Since the new updates this class only has the necessary
* logic to upgrade te plugin.
*
* @category Plugin
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class Message extends Managed_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'message'; // table name
public $__table = 'message'; // table name
public $id; // int(4) primary_key not_null
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $from_profile; // int(4) not_null
@ -69,76 +101,6 @@ class Message extends Managed_DataObject
return Profile::getKV('id', $this->to_profile);
}
static function saveNew($from, $to, $content, $source) {
$sender = Profile::getKV('id', $from);
if (!$sender->hasRight(Right::NEWMESSAGE)) {
// TRANS: Client exception thrown when a user tries to send a direct message while being banned from sending them.
throw new ClientException(_('You are banned from sending direct messages.'));
}
$user = User::getKV('id', $sender->id);
$msg = new Message();
$msg->from_profile = $from;
$msg->to_profile = $to;
if ($user) {
// Use the sender's URL shortening options.
$msg->content = $user->shortenLinks($content);
} else {
$msg->content = common_shorten_links($content);
}
$msg->rendered = common_render_text($msg->content);
$msg->created = common_sql_now();
$msg->source = $source;
$result = $msg->insert();
if (!$result) {
common_log_db_error($msg, 'INSERT', __FILE__);
// TRANS: Message given when a message could not be stored on the server.
throw new ServerException(_('Could not insert message.'));
}
$orig = clone($msg);
$msg->uri = common_local_url('showmessage', array('message' => $msg->id));
$result = $msg->update($orig);
if (!$result) {
common_log_db_error($msg, 'UPDATE', __FILE__);
// TRANS: Message given when a message could not be updated on the server.
throw new ServerException(_('Could not update message with new URI.'));
}
return $msg;
}
static function maxContent()
{
$desclimit = common_config('message', 'contentlimit');
// null => use global limit (distinct from 0!)
if (is_null($desclimit)) {
$desclimit = common_config('site', 'textlimit');
}
return $desclimit;
}
static function contentTooLong($content)
{
$contentlimit = self::maxContent();
return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
}
function notify()
{
$from = User::getKV('id', $this->from_profile);
$to = User::getKV('id', $this->to_profile);
mail_notify_message($this, $from, $to);
}
function getSource()
{
if (empty($this->source)) {
@ -176,38 +138,30 @@ class Message extends Managed_DataObject
$act = new Activity();
if (Event::handle('StartMessageAsActivity', array($this, &$act))) {
$act->verb = ActivityVerb::POST;
$act->time = strtotime($this->created);
$act->id = TagURI::mint(sprintf('activity:message:%d', $this->id));
$act->time = strtotime($this->created);
$act->link = $this->url;
$profile = Profile::getKV('id', $this->from_profile);
if (empty($profile)) {
$actor_profile = $this->getFrom();
if (is_null($actor_profile)) {
throw new Exception(sprintf("Sender profile not found: %d", $this->from_profile));
}
$act->actor = $actor_profile->asActivityObject();
$act->actor = $profile->asActivityObject();
$act->actor->extra[] = $profile->profileInfo();
$act->context = new ActivityContext();
$options = ['source' => $this->source,
'uri' => TagURI::mint(sprintf('activity:message:%d', $this->id)),
'url' => $this->uri,
'scope' => Notice::MESSAGE_SCOPE];
$act->verb = ActivityVerb::POST;
$to_profile = $this->getTo();
if (is_null($to_profile)) {
throw new Exception(sprintf("Receiver profile not found: %d", $this->to_profile));
}
$act->context->attention[$to_profile->getUri()] = ActivityObject::PERSON;
$act->objects[] = ActivityObject::fromMessage($this);
$ctx = new ActivityContext();
$rprofile = Profile::getKV('id', $this->to_profile);
if (empty($rprofile)) {
throw new Exception(sprintf("Receiver profile not found: %d", $this->to_profile));
}
$ctx->attention[$rprofile->getUri()] = ActivityObject::PERSON;
$act->context = $ctx;
$source = $this->getSource();
if ($source instanceof Notice_source) {
$act->generator = ActivityObject::fromNoticeSource($source);
}

View File

@ -1,7 +1,39 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
if (!defined('GNUSOCIAL')) { exit(1); }
/**
* GNUsocial implementation of Direct Messages
*
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
/**
* Inbox MessageListItem widget
*
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class InboxMessageListItem extends MessageListItem
{
/**
@ -9,8 +41,8 @@ class InboxMessageListItem extends MessageListItem
*
* @return Profile The profile that matches the message
*/
function getMessageProfile()
function getMessageProfile(): ?Profile
{
return $this->message->getFrom();
return $this->message->getProfile();
}
}

View File

@ -1,117 +1,106 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* StatusNet, the distributed open-source microblogging tool
* GNUsocial implementation of Direct Messages
*
* Form for posting a direct message
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (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/>.
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
if (!defined('GNUSOCIAL')) { exit(1); }
defined('GNUSOCIAL') || die();
/**
* Form for posting a direct message
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see HTMLOutputter
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class MessageForm extends Form
{
/**
* User to send a direct message to
*/
var $to = null;
protected $to = null;
protected $content = null;
/**
* Pre-filled content of the form
*/
var $content = null;
/**
* Constructor
* Constructor.
*
* @param HTMLOutputter $out output channel
* @param User $to user to send a message to
* @param string $content content to pre-fill
* @param HTMLOutputter $out output channel
* @param array|null $formOpts
*/
function __construct($out=null, $to=null, $content=null)
function __construct(HTMLOutputter $out = null, ?array $formOpts = null)
{
parent::__construct($out);
$this->to = $to;
$this->content = $content;
if (isset($formOpts['to'])) {
$this->to = $formOpts['to'];
}
$this->content = $formOpts['content'] ?? '';
}
/**
* ID of the form
* ID of the form.
*
* @return string ID of the form
*/
function id()
function id(): string
{
return 'form_notice-direct';
}
/**
* Class of the form
/**
* Class of the form.
*
* @return string class of the form
*/
function formClass()
function formClass(): string
{
return 'form_notice ajax-notice';
}
/**
* Action of the form
* Action of the form.
*
* @return string URL of the action
*/
function action()
function action(): string
{
return common_local_url('newmessage');
}
/**
* Legend of the Form
* Legend of the Form.
*
* @return void
*/
function formLegend()
{
// TRANS: Form legend for direct notice.
$this->out->element('legend', null, _('Send a direct notice'));
$this->out->element('legend', null, _m('Send a direct notice'));
}
/**
* Data elements
* Data elements.
*
* @return void
*/
@ -119,87 +108,112 @@ class MessageForm extends Form
{
$user = common_current_user();
$mutual_users = $user->mutuallySubscribedUsers();
$recipients = [];
$default = 'default';
$mutual = array();
// TRANS: Label entry in drop-down selection box in direct-message inbox/outbox.
// TRANS: This is the default entry in the drop-down box, doubling as instructions
// TRANS: and a brake against accidental submissions with the first user in the list.
$mutual[0] = _('Select recipient:');
$subs = $user->getSubscribed();
$n_subs = 0;
while ($mutual_users->fetch()) {
if ($mutual_users->id != $user->id) {
$mutual[$mutual_users->id] = $mutual_users->nickname;
// Add local-subscriptions
while ($subs->fetch()) {
$n_subs++;
if ($subs->isLocal()) {
$value = 'profile:'.$subs->getID();
try {
$recipients[$value] = substr($subs->getAcctUri(), 5) . " [{$subs->getBestName()}]";
} catch (ProfileNoAcctUriException $e) {
$recipients[$value] = "[?@?] " . $e->profile->getBestName();
}
}
}
$mutual_users->free();
unset($mutual_users);
if (count($mutual) == 1) {
// TRANS: Entry in drop-down selection box in direct-message inbox/outbox when no one is available to message.
$mutual[0] = _('No mutual subscribers.');
if (sizeof($recipients) < $n_subs) {
// some subscriptions aren't local and therefore weren't added,
// worth checking if others want to add them
Event::handle('FillDirectMessageRecipients', [$user, &$recipients]);
}
// if we came from a profile page, then lets make the message receiver visible
if (!is_null($this->to)) {
if (isset($recipients['profile:'.$this->to->getID()])) {
$default = 'profile' . $this->to->getID();
} else {
try {
if ($this->to->isLocal()) {
$this->content = "@{$this->to->getNickname()} {$this->content}";
} else {
$this->content = substr($this->to->getAcctUri(), 5) . " {$this->content}";
}
} catch (ProfileNoAcctUriException $e) {
// well, I'm no magician
}
}
}
if ($default === 'default') {
// TRANS: Label entry in drop-down selection box in direct-message inbox/outbox.
// TRANS: This is the default entry in the drop-down box, doubling as instructions
// TRANS: and a brake against accidental submissions with the first user in the list.
$recipients[$default] = empty($recipients) ? _m('No subscriptions') : _m('Select recipient:');
}
asort($recipients);
// TRANS: Dropdown label in direct notice form.
$this->out->dropdown('to', _('To'), $mutual, null, false,
($this->to) ? $this->to->id : null);
$this->out->dropdown('to-box',
_m('To'),
$recipients,
null,
false,
$default);
$this->out->element('textarea', array('class' => 'notice_data-text',
'cols' => 35,
'rows' => 4,
'name' => 'content'),
($this->content) ? $this->content : '');
$this->out->element('textarea',
['class' => 'notice_data-text',
'cols' => 35,
'rows' => 4,
'name' => 'content'],
$this->content);
$contentLimit = Message::maxContent();
$contentLimit = MessageModel::maxContent();
if ($contentLimit > 0) {
$this->out->element('span',
array('class' => 'count'),
['class' => 'count'],
$contentLimit);
}
}
/**
* Action elements
* Action elements.
*
* @return void
*/
function formActions()
{
$this->out->element('input', array('id' => 'notice_action-submit',
'class' => 'submit',
'name' => 'message_send',
'type' => 'submit',
// TRANS: Button text for sending a direct notice.
'value' => _m('Send button for sending notice', 'Send')));
$this->out->element('input',
['id' => 'notice_action-submit',
'class' => 'submit',
'name' => 'message_send',
'type' => 'submit',
// TRANS: Button text for sending a direct notice.
'value' => _m('Send button for direct notice', 'Send')]);
}
/**
* Show the form
*
* Uses a recipe to output the form.
* Show the form.
*
* @return void
* @see Widget::show()
*/
function show()
{
$this->elementStart('div', 'input_forms');
$this->elementStart(
'div',
array(
'id' => 'input_form_direct',
'class' => 'input_form current nonav'
)
);
$this->elementStart('div',
['id' => 'input_form_direct',
'class' => 'input_form current nonav']);
parent::show();
$this->elementEnd('div');
$this->elementEnd('div');
}
}

View File

@ -1,112 +1,102 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
* GNUsocial implementation of Direct Messages
*
* A single list item for showing in a message list
*
* PHP version 5
*
* 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/>.
*
* @category Widget
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
defined('GNUSOCIAL') || die();
/**
* A single item in a message list
*
* @category Widget
* @package StatusNet
* @category Plugin
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
abstract class MessageListItem extends Widget
{
var $message;
protected $message;
/**
* Constructor
* Constructor.
*
* @param HTMLOutputter $out Output context
* @param Message $message Message to show
* @param Notice $message Message to show
*/
function __construct($out, $message)
function __construct(HTMLOutputter $out, $message)
{
parent::__construct($out);
$this->message = $message;
}
/**
* Show the widget
* Show the widget.
*
* @return void
*/
function show()
{
$this->out->elementStart('li', array('class' => 'h-entry notice',
'id' => 'message-' . $this->message->id));
$profile = $this->getMessageProfile();
if (is_null($profile)) {
// null most probably because there are no attention profiles and
// the UI below isn't ready for that, yet.
return;
}
$this->out->elementStart('a', array('href' => $profile->profileurl,
'class' => 'p-author'));
$this->out->elementStart('li', ['class' => 'h-entry notice',
'id' => 'message-' . $this->message->getID()]);
$this->out->elementStart('a', ['href' => $profile->getUrl(),
'class' => 'p-author']);
$avatarUrl = $profile->avatarUrl(AVATAR_STREAM_SIZE);
$this->out->element('img', array('src' => $avatarUrl,
'class' => 'avatar u-photo',
'width' => AVATAR_STREAM_SIZE,
'height' => AVATAR_STREAM_SIZE,
'alt' => $profile->getBestName()));
$this->out->element('span', array('class' => 'nickname fn'), $profile->getNickname());
$this->out->element('img', ['src' => $avatarUrl,
'class' => 'avatar u-photo',
'width' => AVATAR_STREAM_SIZE,
'height' => AVATAR_STREAM_SIZE,
'alt' => $profile->getBestName()]);
$this->out->element('span', ['class' => 'nickname fn'], $profile->getNickname());
$this->out->elementEnd('a');
// FIXME: URL, image, video, audio
$this->out->elementStart('div', array('class' => 'e-content'));
$this->out->raw($this->message->rendered);
$this->out->elementStart('div', ['class' => 'e-content']);
$this->out->raw($this->message->getRendered());
$this->out->elementEnd('div');
$messageurl = common_local_url('showmessage',
array('message' => $this->message->id));
// XXX: we need to figure this out better. Is this right?
if (strcmp($this->message->uri, $messageurl) != 0 &&
preg_match('/^http/', $this->message->uri)) {
$messageurl = $this->message->uri;
}
['message' => $this->message->getID()]);
$this->out->elementStart('div', 'entry-metadata');
$this->out->elementStart('a', array('rel' => 'bookmark',
'class' => 'timestamp',
'href' => $messageurl));
$dt = common_date_iso8601($this->message->created);
$this->out->element('time', array('class' => 'dt-published',
'datetime' => common_date_iso8601($this->message->created),
// TRANS: Timestamp title (tooltip text) for NoticeListItem
'title' => common_exact_date($this->message->created)),
common_date_string($this->message->created));
$this->out->elementStart('a', ['rel' => 'bookmark',
'class' => 'timestamp',
'href' => $messageurl]);
$dt = common_date_iso8601($this->message->getCreated());
$this->out->element('time',
['class' => 'dt-published',
'datetime' => common_date_iso8601($this->message->getCreated()),
// TRANS: Timestamp title (tooltip text) for NoticeListItem
'title' => common_exact_date($this->message->getCreated())],
common_date_string($this->message->getCreated()));
$this->out->elementEnd('a');
if ($this->message->source) {
@ -132,7 +122,7 @@ abstract class MessageListItem extends Widget
{
// A dummy array with messages. These will get extracted by xgettext and
// are used in self::showSource().
$dummy_messages = array(
$dummy_messages = [
// TRANS: A possible notice source (web interface).
_m('SOURCE','web'),
// TRANS: A possible notice source (XMPP).
@ -142,12 +132,12 @@ abstract class MessageListItem extends Widget
// TRANS: A possible notice source (OpenMicroBlogging).
_m('SOURCE','omb'),
// TRANS: A possible notice source (Application Programming Interface).
_m('SOURCE','api'),
);
_m('SOURCE','api')
];
}
/**
* Show the source of the message
* Show the source of the message.
*
* Returns either the name (and link) of the API client that posted the notice,
* or one of other other channels.
@ -156,7 +146,7 @@ abstract class MessageListItem extends Widget
*
* @return void
*/
function showSource($source)
function showSource(string $source)
{
$source_name = _m('SOURCE',$source);
switch ($source) {
@ -171,8 +161,9 @@ abstract class MessageListItem extends Widget
$ns = Notice_source::getKV($source);
if ($ns) {
$this->out->elementStart('span', 'device');
$this->out->element('a', array('href' => $ns->url,
'rel' => 'external'),
$this->out->element('a',
['href' => $ns->url,
'rel' => 'external'],
$ns->name);
$this->out->elementEnd('span');
} else {
@ -184,11 +175,11 @@ abstract class MessageListItem extends Widget
}
/**
* Return the profile to show in the message item
*
* Overridden in sub-classes to show sender, receiver, or whatever
* Return the profile to show in the message item.
*
* Overridden in sub-classes to show sender, receiver, or whatever.
*
* @return Profile profile to show avatar and name of
*/
abstract function getMessageProfile();
abstract function getMessageProfile(): ?Profile;
}

View File

@ -0,0 +1,148 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* GNUsocial implementation of Direct Messages
*
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
/**
* Model for a direct message
*
* @category Plugin
* @package GNUsocial
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class MessageModel
{
/**
* Retrieve size-limit for messages content
*
* @return int size-limit
*/
public static function maxContent(): int
{
$desclimit = common_config('message', 'contentlimit');
// null => use global limit (distinct from 0!)
if (is_null($desclimit) || !is_int($desclimit)) {
$desclimit = common_config('site', 'textlimit');
}
return $desclimit;
}
/**
* Is message-text too long?
*
* @param string $content message-text
* @return bool true if too long, false otherwise
*/
public static function contentTooLong(string $content): bool
{
$contentlimit = self::maxContent();
return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
}
/**
* Return data object of messages received by some user.
*
* @param User $to receiver
* @param int|null $page page limiter
* @return Notice data object with stream for messages
*/
public static function inboxMessages(User $to, ?int $page = null)
{
$attention = new Attention();
$attention->selectAdd('notice_id');
$attention->whereAdd('profile_id = ' . $to->getID());
$ids = $attention->find() ? $attention->fetchAll('notice_id') : [];
$reply = new Reply();
$reply->selectAdd('notice_id');
$reply->whereAdd('profile_id = ' . $to->getID());
if ($reply->find()) {
$ids = array_unique(
array_merge($ids, $reply->fetchAll('notice_id'))
);
} else if (empty($ids)) {
return null;
}
$message = new Notice();
$message->whereAdd('scope = ' . NOTICE::MESSAGE_SCOPE);
$message->whereAddIn('id', $ids, 'int');
$message->orderBy('created DESC, id DESC');
if (!is_null($page) && $page >= 0) {
$page = ($page == 0) ? 1 : $page;
$message->limit(($page - 1) * MESSAGES_PER_PAGE,
MESSAGES_PER_PAGE + 1);
}
return $message->find() ? $message : null;
}
/**
* Return data object of messages sent by some user.
*
* @param User $from sender
* @param int|null $page page limiter
* @return Notice data object with stream for messages
*/
public static function outboxMessages(User $from, ?int $page = null)
{
$message = new Notice();
$message->profile_id = $from->getID();
$message->whereAdd('scope = ' . NOTICE::MESSAGE_SCOPE);
$message->orderBy('created DESC, id DESC');
if (!is_null($page) && $page >= 0) {
$page = ($page == 0) ? 1 : $page;
$message->limit(($page - 1) * MESSAGES_PER_PAGE,
MESSAGES_PER_PAGE + 1);
}
return $message->find() ? $message : null;
}
/**
* Save a new message.
*
* @param Profile $from sender
* @param string $content message-text
* @param string $source message's source
* @return Notice stored message
*/
public static function saveNew(Profile $from, string $content, string $source = 'web'): Notice
{
return Notice::saveNew($from->getID(),
$content,
$source,
['distribute' => false, // using events to handle remote distribution
'scope' => NOTICE::MESSAGE_SCOPE]);
}
}

View File

@ -32,8 +32,8 @@ msgstr "Message"
#. TRANS: Plugin description.
#: DirectMessagePlugin.php:168
msgid "Direct Message to other local users (broken out of core)."
msgstr "Direct Message to other local users (broken out of core)."
msgid "Direct Message to other local users."
msgstr "Direct Message to other local users."
#. TRANS: Form validation error displayed when message content is too long.
#. TRANS: %d is the maximum number of characters for a message.
@ -68,7 +68,7 @@ msgstr[1] "Message too long - maximum is %1$d characters, you sent %2$d."
#. TRANS: Button text for sending a direct notice.
#: lib/messageform.php:175
msgctxt "Send button for sending notice"
msgctxt "Send button for direct notice"
msgid "Send"
msgstr "Send"