diff --git a/favourites/classes/local/service/user_favourite_service.php b/favourites/classes/local/service/user_favourite_service.php index 16718b82f00..a63692a675c 100644 --- a/favourites/classes/local/service/user_favourite_service.php +++ b/favourites/classes/local/service/user_favourite_service.php @@ -156,4 +156,49 @@ class user_favourite_service { ] ); } + + /** + * Get the favourite. + * + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param int $itemid the id of the item which was favourited (not the favourite's id). + * @param \context $context the context of the item which was favourited. + * @return favourite|null + */ + public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) { + try { + return $this->repo->find_favourite( + $this->userid, + $component, + $itemtype, + $itemid, + $context->id + ); + } catch (\dml_missing_record_exception $e) { + return null; + } + } + + /** + * Count the favourite by item type. + * + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param \context|null $context the context of the item which was favourited. + * @return favourite|null + */ + public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) { + $criteria = [ + 'userid' => $this->userid, + 'component' => $component, + 'itemtype' => $itemtype + ]; + + if ($context) { + $criteria['contextid'] = $context->id; + } + + return $this->repo->count_by($criteria); + } } diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index 939e5446629..5ead879fff7 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -111,7 +111,7 @@ class user_favourite_service_testcase extends advanced_testcase { return $fakerow; } } - throw new \moodle_exception("Item not found"); + throw new \dml_missing_record_exception("Item not found"); }) ); $mockrepo->expects($this->any()) @@ -127,16 +127,17 @@ class user_favourite_service_testcase extends advanced_testcase { }) ); $mockrepo->expects($this->any()) - ->method('exists_by') + ->method('count_by') ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { + $count = 0; // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. foreach ($mockstore as $index => $mockrow) { $mockrowarr = (array)$mockrow; if (array_diff($criteria, $mockrowarr) == []) { - return true; + $count++; } } - return false; + return $count; }) ); $mockrepo->expects($this->any()) @@ -149,6 +150,19 @@ class user_favourite_service_testcase extends advanced_testcase { } }) ); + $mockrepo->expects($this->any()) + ->method('exists_by') + ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { + // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. + foreach ($mockstore as $index => $mockrow) { + $mockrowarr = (array)$mockrow; + if (array_diff($criteria, $mockrowarr) == []) { + return true; + } + } + return false; + }) + ); return $mockrepo; } @@ -352,4 +366,67 @@ class user_favourite_service_testcase extends advanced_testcase { ) ); } + + /** + * Test confirming the behaviour of the get_favourite() method. + */ + public function test_get_favourite() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourite_service for the user. + $repo = $this->get_mock_repository([]); + $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); + + // Favourite a course. + $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + + $result = $service->get_favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context + ); + // Verify we can get the favourite. + $this->assertEquals($fav1->id, $result->id); + + // And one that we know doesn't exist. + $this->assertNull( + $service->get_favourite( + 'core_course', + 'someothertype', + $course1context->instanceid, + $course1context + ) + ); + } + + /** + * Test confirming the behaviour of the count_favourites_by_type() method. + */ + public function test_count_favourites_by_type() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourite_service for the user. + $repo = $this->get_mock_repository([]); + $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); + + $this->assertEquals(0, $service->count_favourites_by_type('core_course', 'course', $course1context)); + // Favourite a course. + $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + + $this->assertEquals(1, $service->count_favourites_by_type('core_course', 'course', $course1context)); + + // Favourite another course. + $service->create_favourite('core_course', 'course', $course2context->instanceid, $course1context); + + $this->assertEquals(2, $service->count_favourites_by_type('core_course', 'course', $course1context)); + + // Favourite a course in another context. + $service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context); + + // Doesn't affect original context. + $this->assertEquals(2, $service->count_favourites_by_type('core_course', 'course', $course1context)); + // Gets counted if we include all contexts. + $this->assertEquals(3, $service->count_favourites_by_type('core_course', 'course')); + } } diff --git a/lang/en/message.php b/lang/en/message.php index 1c1f5baf55e..77a748ee70a 100644 --- a/lang/en/message.php +++ b/lang/en/message.php @@ -22,13 +22,20 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['acceptandaddcontact'] = 'Accept and add to contacts'; $string['addcontact'] = 'Add contact'; +$string['addcontactconfirm'] = 'Are you sure you want to add {$a} to your contacts?'; $string['addtoyourcontacts'] = 'Add to your contacts'; +$string['addtoyourcontactsandmessage'] = 'Add to your contacts and message'; +$string['addtofavourites'] = 'Add to favourites'; $string['ago'] = '{$a} ago'; $string['allusers'] = 'All messages from all users'; +$string['backto'] = 'Back to {$a}'; $string['backupmessageshelp'] = 'If enabled, then instant messages will be included in SITE automated backups'; $string['blockcontact'] = 'Block contact'; $string['blockedusers'] = 'Blocked users'; +$string['blockuser'] = 'Block user'; +$string['blockuserconfirm'] = 'Are you sure you want to block {$a}?'; $string['blocknoncontacts'] = 'Prevent non-contacts from messaging me'; $string['canceledit'] = 'Cancel editing messages'; $string['contactableprivacy'] = 'Accept messages from:'; @@ -37,12 +44,16 @@ $string['contactableprivacy_coursemember'] = 'My contacts and anyone in my cours $string['contactableprivacy_site'] = 'Anyone on the site'; $string['contactblocked'] = 'Contact blocked'; $string['contactrequests'] = 'Contact requests'; +$string['contactrequestsent'] = 'Contact request sent'; $string['contacts'] = 'Contacts'; +$string['decline'] = 'Decline'; $string['defaultmessageoutputs'] = 'Default message outputs'; $string['defaults'] = 'Defaults'; $string['deleteallconfirm'] = "Are you sure you would like to delete this entire conversation?"; $string['deleteallmessages'] = "Delete all messages"; +$string['deleteconversation'] = "Delete conversation"; $string['deleteselectedmessages'] = 'Delete selected messages'; +$string['deleteselectedmessagesconfirm'] = 'Are you sure you would like to delete the selected messages?'; $string['disableall'] = 'Disable notifications'; $string['disabled'] = 'Messaging is disabled on this site'; $string['disallowed'] = 'Disallowed'; @@ -64,8 +75,13 @@ $string['eventmessagesent'] = 'Message sent'; $string['forced'] = 'Locked'; $string['guestnoeditmessage'] = 'Guest user can not edit messaging options'; $string['guestnoeditmessageother'] = 'Guest user can not edit other user messaging options'; +$string['groupinfo'] = 'Group info'; +$string['groupmessages'] = 'Group messages'; $string['hidemessagewindow'] = 'Hide message window'; $string['hidenotificationwindow'] = 'Hide notification window'; +$string['info'] = 'Info'; +$string['isnotinyourcontacts'] = '{$a} is not in your contacts'; +$string['loadmore'] = 'Load more'; $string['loggedin'] = 'Online'; $string['loggedin_help'] = 'Configure how you would like to receive notifications when you are logged into Moodle'; $string['loggedindescription'] = 'When you are logged into Moodle'; @@ -78,24 +94,40 @@ $string['messagepreferences'] = 'Message preferences'; $string['message'] = 'Message'; $string['messagecontactrequestsnotification'] = '{$a} wants to be added as a contact'; $string['messagecontactrequestsnotificationsubject'] = '{$a} wants to be added as a contact'; +$string['messagedrawerviewcontact'] = 'User details for {$a}'; +$string['messagedrawerviewcontacts'] = 'Message contacts'; +$string['messagedrawerviewconversation'] = 'Conversation with {$a}'; +$string['messagedrawerviewgroupinfo'] = 'Group details for {$a}'; +$string['messagedrawerviewoverview'] = 'Messages overview'; +$string['messagedrawerviewsearch'] = 'Messages search results for {$a}'; +$string['messagedrawerviewsettings'] = 'Message settings'; $string['messagepreferences'] = 'Message preferences'; $string['messages'] = 'Messages'; +$string['messagesselected:'] = 'Messages selected:'; $string['messagingdatahasnotbeenmigrated'] = 'Your messages are temporarily unavailable due to upgrades in the messaging infrastructure. Please wait for them to be migrated.'; $string['newonlymsg'] = 'Show only new'; $string['newmessage'] = 'New message'; $string['newmessagesearch'] = 'Select or search for a contact to send a new message.'; +$string['nofavourites'] = 'No favourites'; $string['noframesjs'] = 'Use more accessible interface'; +$string['nocontactrequests'] = 'No contact requests'; $string['nocontacts'] = 'No contacts'; +$string['nocontactsgetstarted'] = "Try searching for someone to add them as a contact"; +$string['nogroupmessages'] = 'No group messages'; $string['nomessages'] = 'No messages'; $string['nomessagesfound'] = 'No messages were found'; $string['noreply'] = 'Do not reply to this message'; $string['noncontacts'] = 'Non-contacts'; $string['nonotifications'] = 'You have no notifications'; +$string['noparticipants'] = 'No participants'; $string['notificationdatahasnotbeenmigrated'] = 'Your notifications are temporarily unavailable due to upgrades in the notification infrastructure. Please wait for them to be migrated.'; $string['notificationwindow'] = 'Notification window'; $string['notificationpreferences'] = 'Notification preferences'; $string['notificationimage'] = 'Notification image'; $string['notifications'] = 'Notifications'; +$string['notincontactsheading'] = '{$a} is not in your contacts'; +$string['notincontacts'] = 'You need to add {$a} to your contacts to be able to send them messages.'; +$string['numparticipants'] = '{$a} participants'; $string['off'] = 'Off'; $string['offline'] = 'Offline'; $string['on'] = 'On'; @@ -105,7 +137,10 @@ $string['outputdoesnotexist'] = 'Message output does not exists'; $string['outputenabled'] = 'Output enabled'; $string['outputnotavailable'] = 'Not available'; $string['outputnotconfigured'] = 'Not configured'; +$string['participants'] = 'Participants'; $string['permitted'] = 'Permitted'; +$string['privacy'] = 'Privacy'; +$string['privacy_desc'] = 'You can restrict who can message you'; $string['privacy:metadata:messages'] = 'Messages'; $string['privacy:metadata:messages:conversationid'] = 'The ID of the conversation'; $string['privacy:metadata:messages:fullmessage'] = 'The full message'; @@ -154,12 +189,18 @@ $string['privacy:metadata:preference:core_message_settings'] = 'Settings related $string['privacy:request:preference:set'] = 'The value of the setting \'{$a->name}\' was \'{$a->value}\''; $string['processorsettings'] = 'Processor settings'; $string['removecontact'] = 'Remove contact'; +$string['removecontactconfirm'] = 'Are you sure you want to remove {$a} from your contacts?'; $string['removecoursefilter'] = 'Remove filter for course {$a}'; $string['removefromyourcontacts'] = 'Remove from your contacts'; +$string['removefromfavourites'] = 'Remove from favourites'; +$string['requirecontacttomessage'] = 'You need to request {$a} to add you as a contact to be able to message'; $string['requiresconfiguration'] = 'Requires configuration'; $string['searchforuser'] = 'Search for a user'; $string['searchforuserorcourse'] = 'Search for a user or course'; $string['searchmessages'] = 'Search messages'; +$string['searchnocontactsfound'] = 'No contacts found'; +$string['searchnomessagesfound'] = 'No messages found'; +$string['searchnononcontactsfound'] = 'No non contacts found'; $string['searchcombined'] = 'Search people and messages'; $string['seeall'] = 'See all'; $string['selectmessagestodelete'] = 'Select messages to delete'; @@ -167,6 +208,7 @@ $string['selectnotificationtoview'] = 'Select from the list of notifications on $string['send'] = 'Send'; $string['sendingvia'] = 'Sending "{$a->provider}" via "{$a->processor}"'; $string['sendingviawhen'] = 'Sending "{$a->provider}" via "{$a->processor}" when {$a->state}'; +$string['sendcontactrequest'] = 'Send contact request'; $string['sendmessage'] = 'Send message'; $string['sendbulkmessage'] = 'Send message to {$a} people'; $string['sendbulkmessagesingle'] = 'Send message to 1 person'; @@ -182,19 +224,30 @@ $string['shownotificationwindowwithcount'] = 'Show notification window with {$a} $string['togglenotificationmenu'] = 'Toggle notifications menu'; $string['togglemessagemenu'] = 'Toggle messages menu'; $string['touserdoesntexist'] = 'You can not send a message to a user id ({$a}) that doesn\'t exist'; +$string['unabletomessage'] = 'You are unable to message this user'; +$string['unblock'] = 'Unblock'; $string['unblockcontact'] = 'Unblock contact'; +$string['unblockuser'] = 'Unblock user'; +$string['unblockuserconfirm'] = 'Are you sure you want to unblock {$a}?'; $string['unknownuser'] = 'Unknown user'; $string['unreadnotification'] = 'Unread notification: {$a}'; $string['unreadnewgroupconversationmessage'] = 'New message from {$a->name} in {$a->conversationname}'; $string['unreadnewmessage'] = 'New message from {$a}'; +$string['useentertosend'] = 'Use enter to send'; $string['usercantbemessaged'] = 'You can\'t message {$a} due to their message preferences. Try adding them as a contact.'; +$string['userisblockingyou'] = 'This user has blocked you from sending messages to them'; +$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}'; $string['writeamessage'] = 'Write a message...'; +$string['wouldliketocontactyou'] = 'Would like to contact you'; $string['you'] = 'You:'; +$string['youhaveblockeduser'] = 'You have blocked this user in the past'; +$string['yourcontactrequestpending'] = 'Your contact request is pending with {$a}'; // Deprecated since Moodle 3.6. $string['eventmessagecontactblocked'] = 'Message contact blocked'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 163e3d89671..93ebebeee21 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -808,6 +808,7 @@ $string['expandcategory'] = 'Expand {$a}'; $string['explanation'] = 'Explanation'; $string['explanationdigitalminor'] = 'This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.'; $string['extendperiod'] = 'Extended period'; +$string['favourites'] = 'Favourites'; $string['failedloginattempts'] = '{$a->attempts} failed logins since your last login'; $string['feedback'] = 'Feedback'; $string['file'] = 'File'; @@ -1656,6 +1657,7 @@ $string['requestcourse'] = 'Request a course'; $string['requestedby'] = 'Requested by'; $string['requestedcourses'] = 'Requested courses'; $string['requestreason'] = 'Reason for course request'; +$string['requests'] = 'Requests'; $string['required'] = 'Required'; $string['requirespayment'] = 'This course requires payment for access'; $string['resendemail'] = 'Resend email'; diff --git a/lib/amd/build/auto_rows.min.js b/lib/amd/build/auto_rows.min.js index edc87d988a2..f884f9d7769 100644 --- a/lib/amd/build/auto_rows.min.js +++ b/lib/amd/build/auto_rows.min.js @@ -1 +1 @@ -define(["jquery"],function(a){var b={ELEMENT:"[data-auto-rows]"},c={ROW_CHANGE:"autorows:rowchange"},d=function(a){var b=a.attr("rows"),c=a.data("min-rows"),d=a.attr("data-max-rows"),e=a.height(),f=a.innerHeight(),g=f-e,h=a[0].scrollHeight,i=(h-g)/(e/b);return a.css("height",""),i=d?d:i},e=function(b){var e=a(b.target),f=e.data("min-rows"),g=e.attr("rows");"undefined"==typeof f&&e.data("min-rows",g);var h=d(e);h!=g&&(e.attr("rows",h),e.trigger(c.ROW_CHANGE))},f=function(c){a(c).data("auto-rows")?a(c).on("input propertychange",e.bind(this)):a(c).on("input propertychange",b.ELEMENT,e.bind(this))};return{init:f,events:c}}); \ No newline at end of file +define(["jquery"],function(a){var b={ELEMENT:"[data-auto-rows]"},c={ROW_CHANGE:"autorows:rowchange"},d=function(a){var b=a.attr("rows"),c=a.data("min-rows"),d=a.attr("data-max-rows"),e=a.height(),f=a.innerHeight(),g=f-e,h=a[0].scrollHeight,i=(h-g)/(e/b);return a.css("height",""),i=d?d:i},e=function(b){var e=a(b.target),f=e.data("min-rows"),g=e.attr("rows");"undefined"==typeof f&&e.data("min-rows",g),e.attr("rows",1);var h=d(e);e.attr("rows",h),h!=g&&e.trigger(c.ROW_CHANGE)},f=function(c){a(c).data("auto-rows")?a(c).on("input propertychange",e.bind(this)):a(c).on("input propertychange",b.ELEMENT,e.bind(this))};return{init:f,events:c}}); \ No newline at end of file diff --git a/lib/amd/build/key_codes.min.js b/lib/amd/build/key_codes.min.js index 04bace5c914..18a820abbba 100644 --- a/lib/amd/build/key_codes.min.js +++ b/lib/amd/build/key_codes.min.js @@ -1 +1 @@ -define(function(){return{tab:9,enter:13,escape:27,space:32,end:35,home:36,arrowLeft:37,arrowUp:38,arrowRight:39,arrowDown:40,8:56,asterix:106,pageUp:33,pageDown:34}}); \ No newline at end of file +define(function(){return{tab:9,enter:13,shift:16,ctrl:17,alt:18,escape:27,space:32,end:35,home:36,arrowLeft:37,arrowUp:38,arrowRight:39,arrowDown:40,8:56,asterix:106,pageUp:33,pageDown:34}}); \ No newline at end of file diff --git a/lib/amd/src/auto_rows.js b/lib/amd/src/auto_rows.js index 644d3a906cb..0814b48c4ee 100644 --- a/lib/amd/src/auto_rows.js +++ b/lib/amd/src/auto_rows.js @@ -80,10 +80,14 @@ define(['jquery'], function($) { if (typeof minRows === "undefined") { element.data('min-rows', currentRows); } + + // Reset element to single row so that the scroll height of the + // element is correctly calculated each time. + element.attr('rows', 1); var rows = calculateRows(element); + element.attr('rows', rows); if (rows != currentRows) { - element.attr('rows', rows); element.trigger(EVENTS.ROW_CHANGE); } }; diff --git a/lib/amd/src/key_codes.js b/lib/amd/src/key_codes.js index b64529b1cc4..aaaf296c02e 100644 --- a/lib/amd/src/key_codes.js +++ b/lib/amd/src/key_codes.js @@ -28,6 +28,9 @@ define(function() { return /** @alias module:core/key_codes */ { 'tab': 9, 'enter': 13, + 'shift': 16, + 'ctrl': 17, + 'alt': 18, 'escape': 27, 'space': 32, 'end': 35, diff --git a/lib/behat/classes/partial_named_selector.php b/lib/behat/classes/partial_named_selector.php index 67abfef22f4..def514b3550 100644 --- a/lib/behat/classes/partial_named_selector.php +++ b/lib/behat/classes/partial_named_selector.php @@ -185,13 +185,13 @@ XPATH .//*[self::label or self::div[contains(concat(' ', @class, ' '), ' fstaticlabel ')]][contains(., %locator%)]/ancestor::*[contains(concat(' ', @class, ' '), ' fitem ')] XPATH , 'message_area_region' => << << << << 'fa-calendar-check-o', 'core:i/search' => 'fa-search', 'core:i/section' => 'fa-folder-o', + 'core:i/sendmessage' => 'fa-paper-plane', 'core:i/settings' => 'fa-cog', 'core:i/show' => 'fa-eye-slash', 'core:i/siteevent' => 'fa-globe', @@ -302,6 +303,7 @@ class icon_system_fontawesome extends icon_system_font { 'core:i/stats' => 'fa-line-chart', 'core:i/switch' => 'fa-exchange', 'core:i/switchrole' => 'fa-user-secret', + 'core:i/trash' => 'fa-trash', 'core:i/twoway' => 'fa-arrows-h', 'core:i/unchecked' => 'fa-square-o', 'core:i/unflagged' => 'fa-flag-o', @@ -312,6 +314,7 @@ class icon_system_fontawesome extends icon_system_font { 'core:i/users' => 'fa-users', 'core:i/valid' => 'fa-check text-success', 'core:i/warning' => 'fa-exclamation text-warning', + 'core:i/window_close' => 'fa-window-close', 'core:i/withsubcat' => 'fa-plus-square', 'core:m/USD' => 'fa-usd', 'core:t/addcontact' => 'fa-address-card', @@ -332,6 +335,7 @@ class icon_system_fontawesome extends icon_system_font { 'core:t/collapsed_empty' => 'fa-plus-square-o', 'core:t/collapsed_rtl' => 'fa-plus-square', 'core:t/collapsed' => 'fa-plus-square', + 'core:t/collapsedcaret' => 'fa-caret-right', 'core:t/contextmenu' => 'fa-cog', 'core:t/copy' => 'fa-copy', 'core:t/delete' => 'fa-trash', @@ -376,6 +380,7 @@ class icon_system_fontawesome extends icon_system_font { 'core:t/reset' => 'fa-repeat', 'core:t/restore' => 'fa-arrow-circle-up', 'core:t/right' => 'fa-arrow-right', + 'core:t/sendmessage' => 'fa-paper-plane', 'core:t/show' => 'fa-eye-slash', 'core:t/sort_asc' => 'fa-sort-asc', 'core:t/sort_desc' => 'fa-sort-desc', diff --git a/lib/db/services.php b/lib/db/services.php index 1f4483dc6dc..9f7392362d9 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1086,6 +1086,25 @@ $functions = array( 'description' => 'Retrieve a list of conversations for a user', 'type' => 'read', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + 'ajax' => true + ), + 'core_message_get_conversation' => array( + 'classname' => 'core_message_external', + 'methodname' => 'get_conversation', + 'classpath' => 'message/externallib.php', + 'description' => 'Retrieve a conversation for a user', + 'type' => 'read', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + 'ajax' => true + ), + 'core_message_get_conversation_between_users' => array( + 'classname' => 'core_message_external', + 'methodname' => 'get_conversation_between_users', + 'classpath' => 'message/externallib.php', + 'description' => 'Retrieve a conversation for a user between another user', + 'type' => 'read', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + 'ajax' => true ), 'core_message_get_messages' => array( 'classname' => 'core_message_external', @@ -1105,6 +1124,15 @@ $functions = array( 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), 'ajax' => true, ), + 'core_message_get_member_info' => array( + 'classname' => 'core_message_external', + 'methodname' => 'get_member_info', + 'classpath' => 'message/externallib.php', + 'description' => 'Retrieve a user message profiles', + 'type' => 'read', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + 'ajax' => true, + ), 'core_message_get_unread_conversations_count' => array( 'classname' => 'core_message_external', 'methodname' => 'get_unread_conversations_count', @@ -1257,6 +1285,7 @@ $functions = array( 'classpath' => 'message/externallib.php', 'description' => 'Mark a conversation or group of conversations as favourites/starred conversations.', 'type' => 'write', + 'ajax' => true, 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), 'core_message_unset_favourite_conversations' => array( @@ -1265,6 +1294,7 @@ $functions = array( 'classpath' => 'message/externallib.php', 'description' => 'Unset a conversation or group of conversations as favourites/starred conversations.', 'type' => 'write', + 'ajax' => true, 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), 'core_notes_create_notes' => array( @@ -1541,6 +1571,7 @@ $functions = array( 'classpath' => 'user/externallib.php', 'description' => 'Return user preferences.', 'type' => 'read', + 'ajax' => true, 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), 'core_user_update_picture' => array( diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 75f8e3af5de..5e60efd74fb 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -705,6 +705,14 @@ class core_renderer extends renderer_base { $output .= "\n".$CFG->additionalhtmltopofbody; } + // Give subsystems an opportunity to inject extra html content. The callback + // must always return a string containing valid html. + foreach (\core_component::get_core_subsystems() as $name => $path) { + if ($path) { + $output .= component_callback($name, 'before_standard_top_of_body_html', [], ''); + } + } + // Give plugins an opportunity to inject extra html content. The callback // must always return a string containing valid html. $pluginswithfunction = get_plugins_with_function('before_standard_top_of_body_html', 'lib.php'); @@ -938,6 +946,39 @@ class core_renderer extends renderer_base { return $output; } + /** + * The standard HTML that should be output just before the