Commit fd998fc6 authored by Mark Nelson's avatar Mark Nelson
Browse files

MDL-63915 core_message: add the UI to the message/index.php page

parent cb01c45c
......@@ -242,7 +242,6 @@ $string['userisblockingyou'] = 'This user has blocked you from sending messages
$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
$string['userwouldliketocontactyou'] = '{$a} would like to contact you';
$string['viewfullnotification'] = 'View full notification';
$string['viewinganotherusersmessagearea'] = 'You are viewing another user\'s message area.';
$string['viewmessageswith'] = 'View messages with {$a}';
$string['viewnotificationresource'] = 'Go to: {$a}';
$string['viewunreadmessageswith'] = 'View unread messages with {$a}';
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -155,8 +155,9 @@ function(
*
* @param {string} namespace The route namespace.
* @param {Object} root The message drawer container.
* @param {bool} alwaysVisible Is this messaging app always shown?
*/
var registerEventListeners = function(namespace, root) {
var registerEventListeners = function(namespace, root, alwaysVisible) {
CustomEvents.define(root, [CustomEvents.events.activate]);
var paramRegex = /^data-route-param-?(\d*)$/;
......@@ -205,21 +206,23 @@ function(
data.originalEvent.preventDefault();
});
PubSub.subscribe(Events.SHOW, function() {
show(namespace, root);
});
PubSub.subscribe(Events.HIDE, function() {
hide(root);
});
if (!alwaysVisible) {
PubSub.subscribe(Events.SHOW, function() {
show(namespace, root);
});
PubSub.subscribe(Events.TOGGLE_VISIBILITY, function() {
if (isVisible(root)) {
PubSub.subscribe(Events.HIDE, function() {
hide(root);
} else {
show(namespace, root);
}
});
});
PubSub.subscribe(Events.TOGGLE_VISIBILITY, function() {
if (isVisible(root)) {
hide(root);
} else {
show(namespace, root);
}
});
}
PubSub.subscribe(Events.SHOW_CONVERSATION, function(conversationId) {
show(namespace, root);
......@@ -254,11 +257,24 @@ function(
*
* @param {Object} root The message drawer container.
* @param {String} uniqueId Unique identifier for the Routes
* @param {bool} alwaysVisible Should we show the app now, or wait for the user?
* @param {int} sendToUser Should we message someone now?
* @param {int} conversationId The value of the conversation id, null if none
*/
var init = function(root, uniqueId) {
var init = function(root, uniqueId, alwaysVisible, sendToUser, conversationId) {
root = $(root);
createRoutes(uniqueId, root);
registerEventListeners(uniqueId, root);
registerEventListeners(uniqueId, root, alwaysVisible);
if (alwaysVisible) {
show(uniqueId, root);
if (sendToUser) {
if (conversationId) {
Router.go(uniqueId, Routes.VIEW_CONVERSATION, conversationId);
} else {
Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', sendToUser);
}
}
}
};
return {
......
......@@ -642,4 +642,88 @@ class helper {
}
return $tmp;
}
/**
* Renders the messaging widget.
*
* @param bool $isdrawer Are we are rendering the drawer or is this on a full page?
* @param int|null $sendtouser The ID of the user we want to send a message to
* @param int|null $conversationid The ID of the conversation we want to load
* @return string The HTML.
*/
public static function render_messaging_widget(bool $isdrawer, int $sendtouser = null, int $conversationid = null) {
global $USER, $CFG, $PAGE;
// Early bail out conditions.
if (empty($CFG->messaging) || !isloggedin() || isguestuser() || user_not_fully_set_up($USER) ||
get_user_preferences('auth_forcepasswordchange') ||
(!$USER->policyagreed && !is_siteadmin() &&
($manager = new \core_privacy\local\sitepolicy\manager()) && $manager->is_defined())) {
return '';
}
$renderer = $PAGE->get_renderer('core');
$requestcount = \core_message\api::get_received_contact_requests_count($USER->id);
$contactscount = \core_message\api::count_contacts($USER->id);
$choices = [];
$choices[] = [
'value' => \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS,
'text' => get_string('contactableprivacy_onlycontacts', 'message')
];
$choices[] = [
'value' => \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER,
'text' => get_string('contactableprivacy_coursemember', 'message')
];
if (!empty($CFG->messagingallusers)) {
// Add the MESSAGE_PRIVACY_SITE option when site-wide messaging between users is enabled.
$choices[] = [
'value' => \core_message\api::MESSAGE_PRIVACY_SITE,
'text' => get_string('contactableprivacy_site', 'message')
];
}
// Enter to send.
$entertosend = get_user_preferences('message_entertosend', $CFG->messagingdefaultpressenter, $USER);
if ($isdrawer) {
$template = 'core_message/message_drawer';
$messageurl = new \moodle_url('/message/index.php');
} else {
$template = 'core_message/message_index';
$messageurl = null;
}
$templatecontext = [
'contactrequestcount' => $requestcount,
'loggedinuser' => [
'id' => $USER->id,
'midnight' => usergetmidnight(time())
],
'contacts' => [
'sectioncontacts' => [
'placeholders' => array_fill(0, $contactscount > 50 ? 50 : $contactscount, true)
],
'sectionrequests' => [
'placeholders' => array_fill(0, $requestcount > 50 ? 50 : $requestcount, true)
],
],
'settings' => [
'privacy' => $choices,
'entertosend' => $entertosend
],
'overview' => [
'messageurl' => $messageurl
],
'sendtouser' => false,
'conversationid' => false
];
if ($sendtouser) {
$templatecontext['sendtouser'] = $sendtouser;
$templatecontext['conversationid'] = $conversationid;
}
return $renderer->render_from_template($template, $templatecontext);
}
}
......@@ -36,61 +36,35 @@ if (empty($CFG->messaging)) {
// The id of the user we want to view messages from.
$id = optional_param('id', 0, PARAM_INT);
// It's possible a user may come from a link where these parameters are specified.
// We no longer support viewing another user's messaging area (that can be achieved
// via the 'Log-in as' feature). The 'user2' value takes preference over 'id'.
$userid = optional_param('user2', $id, PARAM_INT);
// It's possible for someone with the right capabilities to view a conversation between two other users. For BC
// we are going to accept other URL parameters to figure this out.
$user1id = optional_param('user1', $USER->id, PARAM_INT);
$user2id = optional_param('user2', $id, PARAM_INT);
$contactsfirst = optional_param('contactsfirst', 0, PARAM_INT);
$url = new moodle_url('/message/index.php');
if ($id) {
$url->param('id', $id);
} else {
if ($user1id) {
$url->param('user1', $user1id);
}
if ($user2id) {
$url->param('user2', $user2id);
}
if ($contactsfirst) {
$url->param('contactsfirst', $contactsfirst);
}
if (!core_user::is_real_user($userid)) {
$userid = null;
}
$PAGE->set_url($url);
$user1 = null;
$currentuser = true;
if ($user1id != $USER->id) {
$user1 = core_user::get_user($user1id, '*', MUST_EXIST);
$currentuser = false;
} else {
$user1 = $USER;
}
$user2 = null;
if (!empty($user2id)) {
$user2 = core_user::get_user($user2id, '*', MUST_EXIST);
if ($userid) {
$recipient = new stdClass();
$recipient->id = $userid;
if (!\core_message\api::can_post_message($recipient)) {
throw new moodle_exception('Can not contact user');
}
}
$user2realuser = !empty($user2) && core_user::is_real_user($user2->id);
$systemcontext = context_system::instance();
if ($currentuser === false && !has_capability('moodle/site:readallmessages', $systemcontext)) {
print_error('accessdenied', 'admin');
$url = new moodle_url('/message/index.php');
if ($userid) {
$url->param('id', $userid);
}
$PAGE->set_context(context_user::instance($user1->id));
$PAGE->set_url($url);
$PAGE->set_context(context_user::instance($USER->id));
$PAGE->set_pagelayout('standard');
$strmessages = get_string('messages', 'message');
if ($user2realuser) {
$user2fullname = fullname($user2);
$PAGE->set_title("$strmessages: $user2fullname");
$PAGE->set_heading("$strmessages: $user2fullname");
} else {
$PAGE->set_title("{$SITE->shortname}: $strmessages");
$PAGE->set_heading("{$SITE->shortname}: $strmessages");
}
$PAGE->set_title("$strmessages");
$PAGE->set_heading("$strmessages");
// Remove the user node from the main navigation for this page.
$usernode = $PAGE->navigation->find('users', null);
......@@ -99,152 +73,19 @@ $usernode->remove();
$settings = $PAGE->settingsnav->find('messages', null);
$settings->make_active();
if ($currentuser) {
// We're in the pprocess of deprecating this page however we haven't replaced the functionality
// for the admin (or user with correct capabilities) to view other user's conversations. For the
// time being this page will simply open the message drawer unless it's the admin user case just
// mentioned. In that case we will render the old UI for backwards compatibility.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('messages', 'message'));
$conversationid = empty($user2id) ? null : \core_message\api::get_conversation_between_users([$USER->id, $user2id]);
if (empty($conversationid) && !empty($user2id)) {
$PAGE->requires->js_call_amd('core_message/message_drawer_helper', 'createConversationWithUser', [$user2id]);
} else if (!empty($conversationid)) {
$PAGE->requires->js_call_amd('core_message/message_drawer_helper', 'showConversation', [$conversationid]);
} else {
$PAGE->requires->js_call_amd('core_message/message_drawer_helper', 'show');
}
echo $OUTPUT->footer();
exit();
}
// The only time we should get here is if it's an admin type user viewing another user's messages.
// Get the renderer and the information we are going to be use.
$renderer = $PAGE->get_renderer('core_message');
$requestedconversation = false;
if ($contactsfirst) {
$conversations = \core_message\api::get_contacts($user1->id, 0, 20);
} else {
$conversations = \core_message\api::get_conversations($user1->id, 0, 20);
// Format the conversations in the legacy style, as the get_conversations method has since been changed.
$conversations = \core_message\helper::get_conversations_legacy_formatter($conversations);
}
$messages = [];
if (!$user2realuser) {
// If there are conversations, but the user has not chosen a particular one, then render the most recent one.
$user2 = new stdClass();
$user2->id = null;
if (!empty($conversations)) {
$contact = reset($conversations);
$user2->id = $contact->userid;
}
} else {
// The user has specifically requested to see a conversation. Add the flag to
// the context so that we can render the messaging app appropriately - this is
// used for smaller screens as it allows the UI to be responsive.
$requestedconversation = true;
// Check if there is an existing conversation with the supplied user (if there is one).
$conversationid = null;
if ($userid) {
$conversationid = \core_message\api::get_conversation_between_users([$USER->id, $userid]);
}
// Mark the conversation as read.
if (!empty($user2->id)) {
$hasbeenreadallmessages = false;
if ($currentuser && isset($conversations[$user2->id])) {
// Mark the conversation we are loading as read.
if ($conversationid = \core_message\api::get_conversation_between_users([$user1->id, $user2->id])) {
\core_message\api::mark_all_messages_as_read($user1->id, $conversationid);
$hasbeenreadallmessages = true;
}
// Ensure the UI knows it's read as well.
$conversations[$user2->id]->isread = 1;
}
// Get the conversationid.
if (!isset($conversationid)) {
if (!$conversationid = \core_message\api::get_conversation_between_users([$user1->id, $user2->id])) {
// If the individual conversationid doesn't exist, create it.
$conversation = \core_message\api::create_conversation(
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
[$user1->id, $user2->id]
);
$conversationid = $conversation->id;
}
}
$convmessages = \core_message\api::get_conversation_messages($user1->id, $conversationid, 0, 20, 'timecreated DESC');
$messages = [];
if (!empty($convmessages)) {
$messages = $convmessages['messages'];
// Parse the messages to add missing fields for backward compatibility.
$messages = array_reverse($messages);
// Keeps track of the last day, month and year combo we were viewing.
$day = '';
$month = '';
$year = '';
foreach ($messages as $message) {
// Add useridto.
if (empty($message->useridto)) {
if ($message->useridfrom == $user1->id) {
$message->useridto = $user2->id;
} else {
$message->useridto = $user1->id;
}
}
// Add currentuserid.
$message->currentuserid = $USER->id;
// Check if we are now viewing a different block period.
$message->displayblocktime = false;
$date = usergetdate($message->timecreated);
if ($day != $date['mday'] || $month != $date['month'] || $year != $date['year']) {
$day = $date['mday'];
$month = $date['month'];
$year = $date['year'];
$message->displayblocktime = true;
$message->blocktime = userdate($message->timecreated, get_string('strftimedaydate'));
}
// We don't have this information here so, for now, we leave an empty value or the current time.
// This is a temporary solution because a new UI is being built in MDL-63303.
$message->timeread = 0;
if ($hasbeenreadallmessages && $message->useridfrom != $user1->id) {
// As all the messages sent by the other user have been marked as read previously, we will change
// timeread to the current time to avoid the last message will be duplicated after calling to the
// core_message_data_for_messagearea_messages via javascript.
// We only need to change that to the other user, because for the current user, messages are always
// marked as unread.
$message->timeread = time();
}
}
}
}
$pollmin = !empty($CFG->messagingminpoll) ? $CFG->messagingminpoll : MESSAGE_DEFAULT_MIN_POLL_IN_SECONDS;
$pollmax = !empty($CFG->messagingmaxpoll) ? $CFG->messagingmaxpoll : MESSAGE_DEFAULT_MAX_POLL_IN_SECONDS;
$polltimeout = !empty($CFG->messagingtimeoutpoll) ? $CFG->messagingtimeoutpoll : MESSAGE_DEFAULT_TIMEOUT_POLL_IN_SECONDS;
$messagearea = new \core_message\output\messagearea\message_area($user1->id, $user2->id, $conversations, $messages,
$requestedconversation, $contactsfirst, $pollmin, $pollmax, $polltimeout);
// Now the page contents.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('messages', 'message'));
// Display a message if the messages have not been migrated yet.
if (!get_user_preferences('core_message_migrate_data', false, $user1id)) {
if (!get_user_preferences('core_message_migrate_data', false)) {
$notify = new \core\output\notification(get_string('messagingdatahasnotbeenmigrated', 'message'),
\core\output\notification::NOTIFY_WARNING);
echo $OUTPUT->render($notify);
}
// Display a message that the user is viewing someone else's messages.
if (!$currentuser) {
$notify = new \core\output\notification(get_string('viewinganotherusersmessagearea', 'message'),
\core\output\notification::NOTIFY_WARNING);
echo $OUTPUT->render($notify);
}
echo $renderer->render($messagearea);
echo $OUTPUT->heading(get_string('messages', 'message'));
echo \core_message\helper::render_messaging_widget(false, $userid, $conversationid);
echo $OUTPUT->footer();
......@@ -804,63 +804,10 @@ function core_message_render_navbar_output(\renderer_base $renderer) {
}
/**
* Render the message drawer to be included in the top of the body of
* each page.
* Render the message drawer to be included in the top of the body of each page.
*
* @return string HTML
*/
function core_message_standard_after_main_region_html() {
global $USER, $CFG, $PAGE;
// Early bail out conditions.
if (empty($CFG->messaging) || !isloggedin() || isguestuser() || user_not_fully_set_up($USER) ||
get_user_preferences('auth_forcepasswordchange') ||
(!$USER->policyagreed && !is_siteadmin() &&
($manager = new \core_privacy\local\sitepolicy\manager()) && $manager->is_defined())) {
return '';
}
$renderer = $PAGE->get_renderer('core');
$requestcount = \core_message\api::get_received_contact_requests_count($USER->id);
$contactscount = \core_message\api::count_contacts($USER->id);
$choices = [];
$choices[] = [
'value' => \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS,
'text' => get_string('contactableprivacy_onlycontacts', 'message')
];
$choices[] = [
'value' => \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER,
'text' => get_string('contactableprivacy_coursemember', 'message')
];
if (!empty($CFG->messagingallusers)) {
// Add the MESSAGE_PRIVACY_SITE option when site-wide messaging between users is enabled.
$choices[] = [
'value' => \core_message\api::MESSAGE_PRIVACY_SITE,
'text' => get_string('contactableprivacy_site', 'message')
];
}
// Enter to send.
$entertosend = get_user_preferences('message_entertosend', $CFG->messagingdefaultpressenter, $USER);
return $renderer->render_from_template('core_message/message_drawer', [
'contactrequestcount' => $requestcount,
'loggedinuser' => [
'id' => $USER->id,
'midnight' => usergetmidnight(time())
],
'contacts' => [
'sectioncontacts' => [
'placeholders' => array_fill(0, $contactscount > 50 ? 50 : $contactscount, true)
],
'sectionrequests' => [
'placeholders' => array_fill(0, $requestcount > 50 ? 50 : $requestcount, true)
],
],
'settings' => [
'privacy' => $choices,
'entertosend' => $entertosend
]
]);
return \core_message\helper::render_messaging_widget(true, null, null);
}
......@@ -36,7 +36,7 @@
<div
id="message-drawer-{{uniqid}}"
class="message-drawer bg-light hidden"
class="message-app drawer bg-light hidden"
aria-expanded="false"
aria-hidden="true"
data-region="message-drawer"
......@@ -60,11 +60,12 @@
</div>
<div class="footer-container position-relative" data-region="footer-container">
{{> core_message/message_drawer_view_conversation_footer }}
{{> core_message/message_drawer_view_overview_footer }}
</div>
</div>
{{#js}}
require(['jquery', 'core_message/message_drawer'], function($, MessageDrawer) {
var root = $('#message-drawer-{{uniqid}}');
MessageDrawer.init(root, '{{uniqid}}');
MessageDrawer.init(root, '{{uniqid}}', false);
});
{{/js}}
......@@ -34,7 +34,7 @@
}}
<div class="h-100 view-overview-body" aria-hidden="false" data-region="view-overview" data-user-id="{{loggedinuser.id}}">
<div id="message-drawer-view-overview-container" class="d-flex flex-column h-100" style="overflow-y: auto">
<div id="message-drawer-view-overview-container-{{uniqid}}" class="d-flex flex-column h-100" style="overflow-y: auto">
{{> core_message/message_drawer_view_overview_section_favourites }}
{{> core_message/message_drawer_view_overview_section_group_messages }}
{{> core_message/message_drawer_view_overview_section_messages }}
......
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_message/message_drawer_view_overview_message_page_link
This template will render the body of the overview section of the message drawer.
Classes required for JS:
* none
Data attributes required for JS:
* All data attributes are required
Example context (json):
{}
}}
{{#overview.messageurl}}
<div data-region="view-overview" class="text-center">
<a href="{{overview.messageurl}}">
{{#str}} seeall, core_message {{/str}}
</a>
</div>
{{/overview.messageurl}}
\ No newline at end of file
......@@ -43,9 +43,9 @@
<button
class="btn btn-link w-100 text-left p-2 d-flex align-items-center overview-section-toggle {{^expanded}}collapsed{{/expanded}}"
data-toggle="collapse"
data-target="#{{$region}}{{/region}}-target"
data-target="#{{$region}}{{/region}}-target-{{uniqid}}"
aria-expanded="{{#expanded}}true{{/expanded}}{{^expanded}}false{{/expanded}}"
aria-controls="{{$region}}{{/region}}-target"
aria-controls="{{$region}}{{/region}}-target-{{uniqid}}"
>
<span class="collapsed-icon-container">
{{#pix}} t/collapsedcaret, core {{/pix}}
......@@ -68,9 +68,9 @@
{{< core_message/message_drawer_lazy_load_list }}
{{$rootclasses}}collapse border-bottom {{#expanded}}show{{/expanded}}{{/rootclasses}}
{{$rootattributes}}
id="{{$region}}{{/region}}-target"
id="{{$region}}{{/region}}-target-{{uniqid}}"
aria-labelledby="{{$region}}{{/region}}-toggle"
data-parent="#message-drawer-view-overview-container"
data-parent="#message-drawer-view-overview-container-{{uniqid}}"
{{/rootattributes}}
{{/ core_message/message_drawer_lazy_load_list }}
</div>
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_message/message_drawer
This template will render the message drawer.
Classes required for JS: