diff --git a/lib/mail.php b/lib/mail.php index 497637eb44..b51406dbad 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -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; } /** diff --git a/plugins/DirectMessage/DirectMessagePlugin.php b/plugins/DirectMessage/DirectMessagePlugin.php index 57492189df..63f822fd54 100644 --- a/plugins/DirectMessage/DirectMessagePlugin.php +++ b/plugins/DirectMessage/DirectMessagePlugin.php @@ -1,47 +1,66 @@ . - */ - -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 . /** - * @maintainer Mikael Nordfeldth + * GNUsocial implementation of Direct Messages + * + * @package GNUsocial + * @author Mikael Nordfeldth + * @author Bruno Casteleiro + * @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 + * @author Bruno Casteleiro + * @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; } diff --git a/plugins/DirectMessage/EVENTS.txt b/plugins/DirectMessage/EVENTS.txt new file mode 100644 index 0000000000..13459f1028 --- /dev/null +++ b/plugins/DirectMessage/EVENTS.txt @@ -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 \ No newline at end of file diff --git a/plugins/DirectMessage/README b/plugins/DirectMessage/README index b908a2472f..4d5cd06b71 100755 --- a/plugins/DirectMessage/README +++ b/plugins/DirectMessage/README @@ -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. \ No newline at end of file diff --git a/plugins/DirectMessage/actions/inbox.php b/plugins/DirectMessage/actions/inbox.php index b3dc88598b..cd92b7d4d3 100644 --- a/plugins/DirectMessage/actions/inbox.php +++ b/plugins/DirectMessage/actions/inbox.php @@ -1,104 +1,87 @@ . + /** - * 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 . - * - * @category Message - * @package StatusNet - * @author Evan Prodromou - * @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 + * @author Bruno Casteleiro + * @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 - * @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 + * @author Bruno Casteleiro + * @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.'); } } diff --git a/plugins/DirectMessage/actions/newmessage.php b/plugins/DirectMessage/actions/newmessage.php index 8d1e5920fa..4e3d7039a5 100644 --- a/plugins/DirectMessage/actions/newmessage.php +++ b/plugins/DirectMessage/actions/newmessage.php @@ -1,66 +1,55 @@ . + /** - * 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 . - * - * @category Personal - * @package StatusNet - * @author Evan Prodromou - * @author Zach Copley - * @author Sarven Capadisli - * @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 + * @author Bruno Casteleiro + * @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 * @author Zach Copley * @author Sarven Capadisli - * @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 + * @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())); diff --git a/plugins/DirectMessage/actions/outbox.php b/plugins/DirectMessage/actions/outbox.php index 5b11a1fd22..215778110f 100644 --- a/plugins/DirectMessage/actions/outbox.php +++ b/plugins/DirectMessage/actions/outbox.php @@ -1,43 +1,39 @@ . + /** - * 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 . - * - * @category Message - * @package StatusNet - * @author Evan Prodromou - * @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 + * @author Bruno Casteleiro + * @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 - * @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 + * @author Bruno Casteleiro + * @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 + * @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 + * @author Bruno Casteleiro + * @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]; } } diff --git a/plugins/DirectMessage/actions/showmessage.php b/plugins/DirectMessage/actions/showmessage.php index 7c4b2a0f73..38d6239d99 100644 --- a/plugins/DirectMessage/actions/showmessage.php +++ b/plugins/DirectMessage/actions/showmessage.php @@ -1,130 +1,151 @@ . + /** - * StatusNet, the distributed open-source microblogging tool + * GNUsocial implementation of Direct Messages * - * Show a single message + * @package GNUsocial + * @author Mikael Nordfeldth + * @author Bruno Casteleiro + * @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 . - * - * @category Personal - * @package StatusNet + * @category Plugin + * @package GNUsocial * @author Evan Prodromou - * @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 + * @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 - * @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 + * @author Bruno Casteleiro + * @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; } } diff --git a/plugins/DirectMessage/classes/Message.php b/plugins/DirectMessage/classes/Message.php index 5f8d27b4f1..1fe2ccc1a0 100644 --- a/plugins/DirectMessage/classes/Message.php +++ b/plugins/DirectMessage/classes/Message.php @@ -1,17 +1,49 @@ . /** - * Table Definition for message + * GNUsocial implementation of Direct Messages + * + * @package GNUsocial + * @author Mikael Nordfeldth + * @author Bruno Casteleiro + * @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 + * @author Bruno Casteleiro + * @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); } diff --git a/plugins/DirectMessage/lib/inboxmessagelistitem.php b/plugins/DirectMessage/lib/inboxmessagelistitem.php index 929c67018f..7631264ce9 100644 --- a/plugins/DirectMessage/lib/inboxmessagelistitem.php +++ b/plugins/DirectMessage/lib/inboxmessagelistitem.php @@ -1,7 +1,39 @@ . -if (!defined('GNUSOCIAL')) { exit(1); } +/** + * GNUsocial implementation of Direct Messages + * + * @package GNUsocial + * @author Mikael Nordfeldth + * @author Bruno Casteleiro + * @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 + * @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(); } } diff --git a/plugins/DirectMessage/lib/messageform.php b/plugins/DirectMessage/lib/messageform.php index 96059e4fac..04d095a2c9 100644 --- a/plugins/DirectMessage/lib/messageform.php +++ b/plugins/DirectMessage/lib/messageform.php @@ -1,117 +1,106 @@ . + /** - * 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 . - * - * @category Form - * @package StatusNet - * @author Evan Prodromou - * @author Sarven Capadisli - * @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 + * @author Bruno Casteleiro + * @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 - * @author Sarven Capadisli - * @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 + * @author Sarven Capadisli + * @author Bruno Casteleiro + * @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'); - } } diff --git a/plugins/DirectMessage/lib/messagelistitem.php b/plugins/DirectMessage/lib/messagelistitem.php index c9f4c6042b..679abc2602 100644 --- a/plugins/DirectMessage/lib/messagelistitem.php +++ b/plugins/DirectMessage/lib/messagelistitem.php @@ -1,112 +1,102 @@ . + /** - * 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 . - * - * @category Widget - * @package StatusNet - * @author Evan Prodromou - * @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 + * @author Bruno Casteleiro + * @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 - * @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; } diff --git a/plugins/DirectMessage/lib/models/MessageModel.php b/plugins/DirectMessage/lib/models/MessageModel.php new file mode 100644 index 0000000000..716ecc730f --- /dev/null +++ b/plugins/DirectMessage/lib/models/MessageModel.php @@ -0,0 +1,148 @@ +. + +/** + * GNUsocial implementation of Direct Messages + * + * @package GNUsocial + * @author Mikael Nordfeldth + * @author Bruno Casteleiro + * @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 + * @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]); + } +} diff --git a/plugins/DirectMessage/locale/en_GB/LC_MESSAGES/DirectMessage.po b/plugins/DirectMessage/locale/en_GB/LC_MESSAGES/DirectMessage.po index 7cd2025bac..2cbcec9157 100644 --- a/plugins/DirectMessage/locale/en_GB/LC_MESSAGES/DirectMessage.po +++ b/plugins/DirectMessage/locale/en_GB/LC_MESSAGES/DirectMessage.po @@ -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"