Merge branch 'MDL-64968-master' of https://github.com/lucaboesch/moodle
authorAdrian Greeve <abgreeve@gmail.com>
Mon, 6 May 2019 07:57:48 +0000 (15:57 +0800)
committerAdrian Greeve <abgreeve@gmail.com>
Tue, 7 May 2019 08:24:36 +0000 (16:24 +0800)
38 files changed:
admin/tool/lp/amd/build/competencies.min.js
admin/tool/lp/amd/build/course_competency_settings.min.js
admin/tool/lp/amd/build/module_navigation.min.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencies.js
admin/tool/lp/amd/src/course_competency_settings.js
admin/tool/lp/amd/src/module_navigation.js [new file with mode: 0644]
admin/tool/lp/classes/external.php
admin/tool/lp/classes/output/course_competencies_page.php
admin/tool/lp/classes/output/module_navigation.php [new file with mode: 0644]
admin/tool/lp/classes/output/renderer.php
admin/tool/lp/coursecompetencies.php
admin/tool/lp/lang/en/tool_lp.php
admin/tool/lp/templates/course_competencies_page.mustache
admin/tool/lp/templates/module_navigation.mustache [new file with mode: 0644]
admin/tool/lp/tests/behat/course_competencies.feature [new file with mode: 0644]
admin/tool/lp/tests/externallib_test.php
favourites/tests/repository_test.php
group/tests/privacy_provider_test.php
lib/behat/classes/partial_named_selector.php
lib/classes/hub/registration.php
lib/form/cancel.php
lib/form/submit.php
lib/formslib.php
lib/testing/generator/data_generator.php
message/tests/api_test.php
message/tests/behat/favourite_conversations.feature
message/tests/behat/group_conversation.feature
message/tests/behat/message_delete_conversation.feature
message/tests/behat/message_send_messages.feature
message/tests/behat/self_conversation.feature [new file with mode: 0644]
message/tests/behat/unread_messages.feature
message/tests/externallib_test.php
message/tests/privacy_provider_test.php
mod/quiz/amd/build/modal_quiz_question_bank.min.js
mod/quiz/amd/src/modal_quiz_question_bank.js
mod/quiz/tests/behat/editing_add_from_question_bank.feature
question/type/questionbase.php
report/competency/templates/report.mustache

index 1f5ddbe..6d24fd8 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js and b/admin/tool/lp/amd/build/competencies.min.js differ
index 5fe246e..fe53dc6 100644 (file)
Binary files a/admin/tool/lp/amd/build/course_competency_settings.min.js and b/admin/tool/lp/amd/build/course_competency_settings.min.js differ
diff --git a/admin/tool/lp/amd/build/module_navigation.min.js b/admin/tool/lp/amd/build/module_navigation.min.js
new file mode 100644 (file)
index 0000000..db9d241
Binary files /dev/null and b/admin/tool/lp/amd/build/module_navigation.min.js differ
index 1b82116..e3a5176 100644 (file)
@@ -144,7 +144,7 @@ define(['jquery',
                     });
                     requests.push({
                         methodname: 'tool_lp_data_for_course_competencies_page',
-                        args: {courseid: self.itemid}
+                        args: {courseid: self.itemid, moduleid: 0}
                     });
 
                     pagerender = 'tool_lp/course_competencies_page';
@@ -212,7 +212,7 @@ define(['jquery',
                 {methodname: 'core_competency_remove_competency_from_course',
                     args: {courseid: localthis.itemid, competencyid: deleteid}},
                 {methodname: 'tool_lp_data_for_course_competencies_page',
-                    args: {courseid: localthis.itemid}}
+                    args: {courseid: localthis.itemid, moduleid: 0}}
             ]);
             pagerender = 'tool_lp/course_competencies_page';
             pageregion = 'coursecompetenciespage';
@@ -311,7 +311,7 @@ define(['jquery',
                     {methodname: 'core_competency_set_course_competency_ruleoutcome',
                       args: {coursecompetencyid: coursecompetencyid, ruleoutcome: ruleoutcome}},
                     {methodname: 'tool_lp_data_for_course_competencies_page',
-                      args: {courseid: localthis.itemid}}
+                      args: {courseid: localthis.itemid, moduleid: 0}}
                 ]);
 
                 requests[1].done(function(context) {
index 06af055..ff21ade 100644 (file)
@@ -134,7 +134,7 @@ define(['jquery',
 
         ajax.call([
             {methodname: 'tool_lp_data_for_course_competencies_page',
-              args: {courseid: courseId}}
+              args: {courseid: courseId, moduleid: 0}}
         ])[0].done(function(context) {
             templates.render('tool_lp/course_competencies_page', context).done(function(html, js) {
                 $('[data-region="coursecompetenciespage"]').replaceWith(html);
diff --git a/admin/tool/lp/amd/src/module_navigation.js b/admin/tool/lp/amd/src/module_navigation.js
new file mode 100644 (file)
index 0000000..d08a550
--- /dev/null
@@ -0,0 +1,62 @@
+// 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/>.
+
+/**
+ * Module to navigation between users in a course.
+ *
+ * @package    tool_lp
+ * @copyright  2019 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery'], function($) {
+
+    /**
+     * ModuleNavigation
+     *
+     * @param {String} moduleSelector The selector of the module element.
+     * @param {String} baseUrl The base url for the page (no params).
+     * @param {Number} courseId The course id
+     * @param {Number} moduleId The activity module (filter)
+     */
+    var ModuleNavigation = function(moduleSelector, baseUrl, courseId, moduleId) {
+        this._baseUrl = baseUrl;
+        this._moduleId = moduleId;
+        this._courseId = courseId;
+
+        $(moduleSelector).on('change', this._moduleChanged.bind(this));
+    };
+
+    /**
+     * The module was changed in the select list.
+     *
+     * @method _moduleChanged
+     * @param {Event} e the event
+     */
+    ModuleNavigation.prototype._moduleChanged = function(e) {
+        var newModuleId = $(e.target).val();
+        var queryStr = '?mod=' + newModuleId + '&courseid=' + this._courseId;
+        document.location = this._baseUrl + queryStr;
+    };
+
+    /** @type {Number} The id of the course. */
+    ModuleNavigation.prototype._courseId = null;
+    /** @type {Number} The id of the module. */
+    ModuleNavigation.prototype._moduleId = null;
+    /** @type {String} Plugin base url. */
+    ModuleNavigation.prototype._baseUrl = null;
+
+    return /** @alias module:tool_lp/module_navigation */ ModuleNavigation;
+});
index d79317c..1929036 100644 (file)
@@ -363,7 +363,13 @@ class external extends external_api {
             'The course id',
             VALUE_REQUIRED
         );
-        $params = array('courseid' => $courseid);
+        $moduleid = new external_value(
+            PARAM_INT,
+            'The module id',
+            VALUE_DEFAULT,
+            0
+        );
+        $params = array('courseid' => $courseid, 'moduleid' => $moduleid);
         return new external_function_parameters($params);
     }
 
@@ -371,16 +377,18 @@ class external extends external_api {
      * Loads the data required to render the course_competencies_page template.
      *
      * @param int $courseid The course id to check.
+     * @param int $moduleid The module id to check (0 for no filter).
      * @return boolean
      */
-    public static function data_for_course_competencies_page($courseid) {
+    public static function data_for_course_competencies_page($courseid, $moduleid) {
         global $PAGE;
         $params = self::validate_parameters(self::data_for_course_competencies_page_parameters(), array(
             'courseid' => $courseid,
+            'moduleid' => $moduleid,
         ));
         self::validate_context(context_course::instance($params['courseid']));
 
-        $renderable = new output\course_competencies_page($params['courseid']);
+        $renderable = new output\course_competencies_page($params['courseid'], $params['moduleid']);
         $renderer = $PAGE->get_renderer('tool_lp');
 
         $data = $renderable->export_for_template($renderer);
index 218d8a5..830cf8a 100644 (file)
@@ -57,6 +57,9 @@ class course_competencies_page implements renderable, templatable {
     /** @var int $courseid Course id for this page. */
     protected $courseid = null;
 
+    /** @var int $moduleid Module id for this page. */
+    protected $moduleid = null;
+
     /** @var context $context The context for this page. */
     protected $context = null;
 
@@ -76,10 +79,31 @@ class course_competencies_page implements renderable, templatable {
      * Construct this renderable.
      * @param int $courseid The course record for this page.
      */
-    public function __construct($courseid) {
+    public function __construct($courseid, $moduleid) {
         $this->context = context_course::instance($courseid);
         $this->courseid = $courseid;
+        $this->moduleid = $moduleid;
         $this->coursecompetencylist = api::list_course_competencies($courseid);
+
+        if ($this->moduleid > 0) {
+            $modulecompetencies = api::list_course_module_competencies_in_course_module($this->moduleid);
+            foreach ($this->coursecompetencylist as $ccid => $coursecompetency) {
+                $coursecompetency = $coursecompetency['coursecompetency'];
+                $found = false;
+                foreach ($modulecompetencies as $mcid => $modulecompetency) {
+                    if ($modulecompetency->get('competencyid') == $coursecompetency->get('competencyid')) {
+                        $found = true;
+                        break;
+                    }
+                }
+
+                if (!$found) {
+                    // We need to filter out this competency.
+                    unset($this->coursecompetencylist[$ccid]);
+                }
+            }
+        }
+
         $this->canmanagecoursecompetencies = has_capability('moodle/competency:coursecompetencymanage', $this->context);
         $this->canconfigurecoursecompetencies = has_capability('moodle/competency:coursecompetencyconfigure', $this->context);
         $this->cangradecompetencies = has_capability('moodle/competency:competencygrade', $this->context);
@@ -112,6 +136,7 @@ class course_competencies_page implements renderable, templatable {
 
         $data = new stdClass();
         $data->courseid = $this->courseid;
+        $data->moduleid = $this->moduleid;
         $data->pagecontextid = $this->context->id;
         $data->competencies = array();
         $data->pluginbaseurl = (new moodle_url('/admin/tool/lp'))->out(true);
@@ -120,6 +145,24 @@ class course_competencies_page implements renderable, templatable {
         if ($gradable) {
             $usercompetencycourses = api::list_user_competencies_in_course($this->courseid, $USER->id);
             $data->gradableuserid = $USER->id;
+
+            if ($this->moduleid > 0) {
+                $modulecompetencies = api::list_course_module_competencies_in_course_module($this->moduleid);
+                foreach ($usercompetencycourses as $ucid => $usercoursecompetency) {
+                    $found = false;
+                    foreach ($modulecompetencies as $mcid => $modulecompetency) {
+                        if ($modulecompetency->get('competencyid') == $usercoursecompetency->get('competencyid')) {
+                            $found = true;
+                            break;
+                        }
+                    }
+
+                    if (!$found) {
+                        // We need to filter out this competency.
+                        unset($usercompetencycourses[$ucid]);
+                    }
+                }
+            }
         }
 
         $ruleoutcomelist = course_competency::get_ruleoutcome_list();
diff --git a/admin/tool/lp/classes/output/module_navigation.php b/admin/tool/lp/classes/output/module_navigation.php
new file mode 100644 (file)
index 0000000..68ff9b0
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+// 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/>.
+
+/**
+ * User navigation class.
+ *
+ * @package    tool_lp
+ * @copyright  2019 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_lp\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use renderable;
+use renderer_base;
+use templatable;
+use context_course;
+use core_course\external\course_module_summary_exporter;
+use stdClass;
+
+/**
+ * User course navigation class.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class module_navigation implements renderable, templatable {
+
+    /** @var courseid */
+    protected $courseid;
+
+    /** @var moduleid */
+    protected $moduleid;
+
+    /** @var baseurl */
+    protected $baseurl;
+
+    /**
+     * Construct.
+     *
+     * @param int $courseid
+     * @param int $moduleid
+     * @param string $baseurl
+     */
+    public function __construct($courseid, $moduleid, $baseurl) {
+        $this->courseid = $courseid;
+        $this->moduleid = $moduleid;
+        $this->baseurl = $baseurl;
+    }
+
+    /**
+     * Export the data.
+     *
+     * @param renderer_base $output
+     * @return stdClass
+     */
+    public function export_for_template(renderer_base $output) {
+
+        $context = context_course::instance($this->courseid);
+
+        $data = new stdClass();
+        $data->courseid = $this->courseid;
+        $data->moduleid = $this->moduleid;
+        $data->baseurl = $this->baseurl;
+        $data->hasmodules = false;
+        $data->modules = array();
+
+        $data->hasmodules = true;
+        $data->modules = array();
+        $empty = (object)['id' => 0, 'name' => get_string('nofiltersapplied')];
+        $data->modules[] = $empty;
+
+        $modinfo = get_fast_modinfo($this->courseid);
+        foreach ($modinfo->get_cms() as $cm) {
+            if ($cm->uservisible) {
+                $exporter = new course_module_summary_exporter(null, ['cm' => $cm]);
+                $module = $exporter->export($output);
+                if ($module->id == $this->moduleid) {
+                    $module->selected = true;
+                }
+                $data->modules[] = $module;
+                $data->hasmodules = true;
+            }
+        }
+
+        return $data;
+    }
+}
index ca45e29..3907215 100644 (file)
@@ -263,4 +263,16 @@ class renderer extends plugin_renderer_base {
         $n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
         return $this->render($n);
     }
+
+    /**
+     * Defer to template.
+     *
+     * @param module_navigation $nav
+     * @return string
+     */
+    public function render_module_navigation(module_navigation $nav) {
+        $data = $nav->export_for_template($this);
+        return parent::render_from_template('tool_lp/module_navigation', $data);
+    }
+
 }
index e700a17..f3e65a7 100644 (file)
 require_once(__DIR__ . '/../../../config.php');
 
 $id = required_param('courseid', PARAM_INT);
+$currentmodule = optional_param('mod', null, PARAM_INT);
+if ($currentmodule > 0) {
+    $cm = get_coursemodule_from_id('', $currentmodule, 0, false, MUST_EXIST);
+}
 
 $params = array('id' => $id);
 $course = $DB->get_record('course', $params, '*', MUST_EXIST);
@@ -33,16 +37,22 @@ require_login($course);
 \core_competency\api::require_enabled();
 
 $context = context_course::instance($course->id);
-$urlparams = array('courseid' => $id);
+$urlparams = array('courseid' => $id, 'mod' => $currentmodule);
 
 $url = new moodle_url('/admin/tool/lp/coursecompetencies.php', $urlparams);
 
 list($title, $subtitle) = \tool_lp\page_helper::setup_for_course($url, $course);
+if ($currentmodule > 0) {
+    $title = get_string('filtermodule', 'report_competency', format_string($cm->name));
+}
 
 $output = $PAGE->get_renderer('tool_lp');
-$page = new \tool_lp\output\course_competencies_page($course->id);
+$page = new \tool_lp\output\course_competencies_page($course->id, $currentmodule);
 
 echo $output->header();
+$baseurl = new moodle_url('/admin/tool/lp/coursecompetencies.php');
+$nav = new \tool_lp\output\module_navigation($course->id, $currentmodule, $baseurl);
+echo $output->render($nav);
 echo $output->heading($title);
 
 echo $output->render($page);
index a197960..c4db7ba 100644 (file)
@@ -146,6 +146,7 @@ $string['nfiles'] = '{$a} file(s)';
 $string['noactivities'] = 'No activities';
 $string['nocompetencies'] = 'No competencies have been created in this framework.';
 $string['nocompetenciesincourse'] = 'No competencies have been linked to this course.';
+$string['nocompetenciesinactivity'] = 'No competencies have been linked to this activity or resource.';
 $string['nocompetenciesinevidence'] = 'No competencies have been linked to this evidence.';
 $string['nocompetenciesinlearningplan'] = 'No competencies have been linked to this learning plan.';
 $string['nocompetenciesintemplate'] = 'No competencies have been linked to this learning plan template.';
index b41af9b..8de78a0 100644 (file)
 </table>
 {{^competencies}}
 <p class="alert alert-info">
-    {{#str}}nocompetenciesincourse, tool_lp{{/str}}
+    {{#moduleid}}
+        {{#str}}nocompetenciesinactivity, tool_lp{{/str}}
+    {{/moduleid}}
+    {{^moduleid}}
+        {{#str}}nocompetenciesincourse, tool_lp{{/str}}
+    {{/moduleid}}
 </p>
 {{/competencies}}
 </div>
diff --git a/admin/tool/lp/templates/module_navigation.mustache b/admin/tool/lp/templates/module_navigation.mustache
new file mode 100644 (file)
index 0000000..aa10239
--- /dev/null
@@ -0,0 +1,52 @@
+{{!
+    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 tool_lp/module_navigation
+
+    Show an auto-complete for filtering by competencies linked to a module.
+
+    Context variables required for this template:
+    * hasmodules
+    * modules - array
+      * id
+      * selected
+      * name
+
+    // No example context because the JS is connected to webservices
+}}
+<div class="float-right card p-2">
+<form class="user-competency-course-navigation">
+{{#hasmodules}}
+<span>
+<label for="module-nav-{{uniqid}}" class="accesshide">{{#str}}filterbyactivity, tool_lp{{/str}}</label>
+<select id="module-nav-{{uniqid}}">
+{{#modules}}
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{name}}</option>
+{{/modules}}
+</select>
+</span>
+{{/hasmodules}}
+</form>
+</div>
+{{#js}}
+require(['core/form-autocomplete', 'tool_lp/module_navigation'], function(autocomplete, nav) {
+    (new nav('#module-nav-{{uniqid}}', '{{baseurl}}', {{courseid}}, {{moduleid}}));
+{{#hasmodules}}
+    autocomplete.enhance('#module-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}filterbyactivity, tool_lp{{/ str }}{{/ quote }});
+{{/hasmodules}}
+});
+{{/js}}
diff --git a/admin/tool/lp/tests/behat/course_competencies.feature b/admin/tool/lp/tests/behat/course_competencies.feature
new file mode 100644 (file)
index 0000000..f765213
--- /dev/null
@@ -0,0 +1,63 @@
+@report @javascript @tool_lp
+Feature: See the competencies for an activity on the course competencies page.
+  As a student
+  In order to see only the competencies for an activity in the course competencies page.
+
+  Background:
+    Given the following lp "frameworks" exist:
+      | shortname | idnumber |
+      | Test-Framework | ID-FW1 |
+    And the following lp "competencies" exist:
+      | shortname | framework |
+      | Test-Comp1 | ID-FW1 |
+      | Test-Comp2 | ID-FW1 |
+    Given the following "courses" exist:
+      | shortname | fullname   |
+      | C1        | Course 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | student1 | C1 | student |
+    And the following "activities" exist:
+      | activity | name       | intro      | course | idnumber |
+      | page     | PageName1  | PageDesc1  | C1     | PAGE1    |
+      | page     | PageName2  | PageDesc2  | C1     | PAGE2    |
+    And I log in as "admin"
+    And I am on site homepage
+    And I follow "Course 1"
+    And I follow "Competencies"
+    And I press "Add competencies to course"
+    And "Competency picker" "dialogue" should be visible
+    And I select "Test-Comp1" of the competency tree
+    And I click on "Add" "button" in the "Competency picker" "dialogue"
+    And I press "Add competencies to course"
+    And "Competency picker" "dialogue" should be visible
+    And I select "Test-Comp2" of the competency tree
+    And I click on "Add" "button" in the "Competency picker" "dialogue"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    And I navigate to "Edit settings" in current page administration
+    And I follow "Expand all"
+    And I set the field "Course competencies" to "Test-Comp1"
+    And I press "Save and return to course"
+    And I log out
+
+  @javascript
+  Scenario: Go to the competency course competencies page.
+    When I log in as "student1"
+    And I am on site homepage
+    And I follow "Course 1"
+    And I follow "Competencies"
+    Then I should see "Test-Comp1"
+    And I should see "Test-Comp2"
+    And I set the field "Filter competencies by resource or activity" to "PageName1"
+    And I press key "13" in the field "Filter competencies by resource or activity"
+    And I should see "Test-Comp1"
+    And I should not see "Test-Comp2"
+    And I set the field "Filter competencies by resource or activity" to "PageName2"
+    And I press key "13" in the field "Filter competencies by resource or activity"
+    And I should not see "Test-Comp1"
+    And I should not see "Test-Comp2"
+    And I should see "No competencies have been linked to this activity or resource."
index 44c9521..450570a 100644 (file)
@@ -461,4 +461,33 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('A', $summary->evidence[1]->gradename);
     }
 
+    public function test_data_for_course_competency_page() {
+        $this->setAdminUser();
+
+        $dg = $this->getDataGenerator();
+        $lpg = $dg->get_plugin_generator('core_competency');
+        $f1 = $lpg->create_framework();
+        $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
+        $course1 = $dg->create_course(array('category' => $this->category->id));
+        $cc = api::add_competency_to_course($course1->id, $c1->get('id'));
+
+        $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 1, true);
+        $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 2, true);
+
+        $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page');
+        $page = $pagegenerator->create_instance(array('course' => $course1->id));
+        $page2 = $pagegenerator->create_instance(array('course' => $course1->id));
+
+        $cm = get_coursemodule_from_instance('page', $page->id);
+        $cm2 = get_coursemodule_from_instance('page', $page2->id);
+        // Add the competency to the course module.
+        $ccm = api::add_competency_to_course_module($cm, $c1->get('id'));
+        $summary = external::data_for_course_competencies_page($course1->id, 0);
+        $summary2 = external::data_for_course_competencies_page($course1->id, $cm->id);
+        $summary3 = external::data_for_course_competencies_page($course1->id, $cm2->id);
+
+        $this->assertEquals(count($summary->competencies), 1);
+        $this->assertEquals(count($summary->competencies), count($summary2->competencies));
+        $this->assertEquals(count($summary3->competencies), 0);
+    }
 }
index a99f30f..fd6276c 100644 (file)
@@ -212,8 +212,8 @@ class favourite_repository_testcase extends advanced_testcase {
 
         $favouritesrepo = new favourite_repository($user1context);
 
-        // Verify that for an empty repository, find_all returns an empty array.
-        $this->assertEquals([], $favouritesrepo->find_all());
+        // Verify that only two self-conversations are found.
+        $this->assertCount(2, $favouritesrepo->find_all());
 
         // Save a favourite for 2 courses, in different areas.
         $favourite = new favourite(
@@ -233,9 +233,9 @@ class favourite_repository_testcase extends advanced_testcase {
         $favouritesrepo->add($favourite);
         $favouritesrepo->add($favourite2);
 
-        // Verify that find_all returns both of our favourites.
+        // Verify that find_all returns both of our favourites + two self-conversations.
         $favourites = $favouritesrepo->find_all();
-        $this->assertCount(2, $favourites);
+        $this->assertCount(4, $favourites);
         foreach ($favourites as $fav) {
             $this->assertInstanceOf(favourite::class, $fav);
             $this->assertObjectHasAttribute('id', $fav);
@@ -251,11 +251,11 @@ class favourite_repository_testcase extends advanced_testcase {
 
         $favouritesrepo = new favourite_repository($user1context);
 
-        // Verify that for an empty repository, find_all with any combination of page options returns an empty array.
-        $this->assertEquals([], $favouritesrepo->find_all(0, 0));
-        $this->assertEquals([], $favouritesrepo->find_all(0, 10));
-        $this->assertEquals([], $favouritesrepo->find_all(1, 0));
-        $this->assertEquals([], $favouritesrepo->find_all(1, 10));
+        // Verify that for an empty repository, find_all with any combination of page options returns only self-conversations.
+        $this->assertCount(2, $favouritesrepo->find_all(0, 0));
+        $this->assertCount(2, $favouritesrepo->find_all(0, 10));
+        $this->assertCount(1, $favouritesrepo->find_all(1, 0));
+        $this->assertCount(1, $favouritesrepo->find_all(1, 10));
 
         // Save 10 arbitrary favourites to the repo.
         foreach (range(1, 10) as $i) {
@@ -269,19 +269,19 @@ class favourite_repository_testcase extends advanced_testcase {
             $favouritesrepo->add($favourite);
         }
 
-        // Verify we have 10 favourites.
-        $this->assertEquals(10, $favouritesrepo->count());
+        // Verify we have 10 favourites + 2 self-conversations.
+        $this->assertEquals(12, $favouritesrepo->count());
 
-        // Verify we can fetch the first page of 5 records.
-        $favourites = $favouritesrepo->find_all(0, 5);
-        $this->assertCount(5, $favourites);
+        // Verify we can fetch the first page of 5 records+ 2 self-conversations.
+        $favourites = $favouritesrepo->find_all(0, 6);
+        $this->assertCount(6, $favourites);
 
         // Verify we can fetch the second page.
-        $favourites = $favouritesrepo->find_all(5, 5);
-        $this->assertCount(5, $favourites);
+        $favourites = $favouritesrepo->find_all(6, 6);
+        $this->assertCount(6, $favourites);
 
         // Verify the third page request ends with an empty array.
-        $favourites = $favouritesrepo->find_all(10, 5);
+        $favourites = $favouritesrepo->find_all(12, 6);
         $this->assertCount(0, $favourites);
     }
 
@@ -321,11 +321,11 @@ class favourite_repository_testcase extends advanced_testcase {
 
         $favouritesrepo = new favourite_repository($user1context);
 
-        // Verify that for an empty repository, find_all with any combination of page options returns an empty array.
-        $this->assertEquals([], $favouritesrepo->find_by([], 0, 0));
-        $this->assertEquals([], $favouritesrepo->find_by([], 0, 10));
-        $this->assertEquals([], $favouritesrepo->find_by([], 1, 0));
-        $this->assertEquals([], $favouritesrepo->find_by([], 1, 10));
+        // Verify that by default, find_all with any combination of page options returns only self-conversations.
+        $this->assertCount(2, $favouritesrepo->find_by([], 0, 0));
+        $this->assertCount(2, $favouritesrepo->find_by([], 0, 10));
+        $this->assertCount(1, $favouritesrepo->find_by([], 1, 0));
+        $this->assertCount(1, $favouritesrepo->find_by([], 1, 10));
 
         // Save 10 arbitrary favourites to the repo.
         foreach (range(1, 10) as $i) {
@@ -339,12 +339,12 @@ class favourite_repository_testcase extends advanced_testcase {
             $favouritesrepo->add($favourite);
         }
 
-        // Verify we have 10 favourites.
-        $this->assertEquals(10, $favouritesrepo->count());
+        // Verify we have 10 favourites + 2 self-conversations.
+        $this->assertEquals(12, $favouritesrepo->count());
 
-        // Verify a request for a page, when no criteria match, results in an empty array.
+        // Verify a request for a page, when no criteria match, results in 2 self-conversations array.
         $favourites = $favouritesrepo->find_by(['component' => 'core_message'], 0, 5);
-        $this->assertCount(0, $favourites);
+        $this->assertCount(2, $favourites);
 
         // Verify we can fetch a the first page of 5 records.
         $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 0, 5);
@@ -546,8 +546,8 @@ class favourite_repository_testcase extends advanced_testcase {
         $favourite1 = $favouritesrepo->add($favourite);
         $favourite2 = $favouritesrepo->add($favourite2);
 
-        // Verify we have 2 items in the repo.
-        $this->assertEquals(2, $favouritesrepo->count());
+        // Verify we have 2 items in the repo + 2 self-conversations.
+        $this->assertEquals(4, $favouritesrepo->count());
 
         // Try to delete by a non-existent area, and confirm it doesn't remove anything.
         $favouritesrepo->delete_by(
@@ -557,7 +557,7 @@ class favourite_repository_testcase extends advanced_testcase {
                 'itemtype' => 'donaldduck'
             ]
         );
-        $this->assertEquals(2, $favouritesrepo->count());
+        $this->assertEquals(4, $favouritesrepo->count());
 
         // Try to delete by a non-existent area, and confirm it doesn't remove anything.
         $favouritesrepo->delete_by(
@@ -567,7 +567,7 @@ class favourite_repository_testcase extends advanced_testcase {
                 'itemtype' => 'cat'
             ]
         );
-        $this->assertEquals(2, $favouritesrepo->count());
+        $this->assertEquals(4, $favouritesrepo->count());
 
         // Delete by area, and confirm we have one record left, from the 'core_course/anothertype' area.
         $favouritesrepo->delete_by(
@@ -577,7 +577,7 @@ class favourite_repository_testcase extends advanced_testcase {
                 'itemtype' => 'course'
             ]
         );
-        $this->assertEquals(1, $favouritesrepo->count());
+        $this->assertEquals(3, $favouritesrepo->count());
         $this->assertFalse($favouritesrepo->exists($favourite1->id));
         $this->assertTrue($favouritesrepo->exists($favourite2->id));
     }
index 6fd01fa..33c8aaf 100644 (file)
@@ -714,12 +714,15 @@ class core_group_privacy_provider_testcase extends provider_testcase {
         $coursecontext1 = context_course::instance($course1->id);
         $coursecontext2 = context_course::instance($course2->id);
 
-        // User1 is member of some groups in course1 and course2.
+        // User1 is member of some groups in course1 and course2 + self-conversation.
         $contextlist = provider::get_contexts_for_userid($user1->id);
-        $this->assertCount(2, $contextlist);
+        $contextids = $contextlist->get_contextids();
+        // First user context is the one related to self-conversation. Let's test group contexts.
+        array_pop($contextids);
+        $this->assertCount(3, $contextlist);
         $this->assertEquals(
                 [$coursecontext1->id, $coursecontext2->id],
-                $contextlist->get_contextids(),
+                $contextids,
                 '', 0.0, 10, true);
     }
 
@@ -757,7 +760,7 @@ class core_group_privacy_provider_testcase extends provider_testcase {
         // User is member of some groups in course1 and course2,
         // but only the membership in course1 is directly managed by core_group.
         $contextlist = provider::get_contexts_for_userid($user->id);
-        $this->assertEquals([$coursecontext1->id], $contextlist->get_contextids());
+        $this->assertEquals($coursecontext1->id, $contextlist->get_contextids()[0]);
     }
 
     /**
index 6d73f4a..b9233ef 100644 (file)
@@ -72,6 +72,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
         'table_row' => 'table_row',
         'xpath_element' => 'xpath_element',
         'form_row' => 'form_row',
+        'group_message_header' => 'group_message_header',
     );
 
     /**
@@ -93,6 +94,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
         'group_message_header' => 'group_message_header',
         'group_message_member' => 'group_message_member',
         'group_message_tab' => 'group_message_tab',
+        'group_message_list_area' => 'group_message_list_area',
         'icon' => 'icon',
         'link' => 'link',
         'link_or_button' => 'link_or_button',
@@ -159,7 +161,7 @@ XPATH
             .//*[@data-region='message-drawer' and contains(., %locator%)]//div[@data-region='content-message-container']
 XPATH
     , 'group_message_header' => <<<XPATH
-        .//*[@data-region='message-drawer']//div[@data-region='header-container']//*[text()[contains(., %locator%)]]
+        .//*[@data-region='message-drawer']//div[@data-region='header-container' and contains(., %locator%)]
 XPATH
     , 'group_message_member' => <<<XPATH
         .//*[@data-region='message-drawer']//div[@data-region='group-info-content-container']
@@ -169,6 +171,9 @@ XPATH
 XPATH
     , 'group_message_tab' => <<<XPATH
         .//*[@data-region='message-drawer']//button[@data-toggle='collapse' and contains(string(), %locator%)]
+XPATH
+    , 'group_message_list_area' => <<<XPATH
+        .//*[@data-region='message-drawer']//*[contains(@data-region, concat('view-overview-', %locator%))]
 XPATH
         , 'icon' => <<<XPATH
 .//*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') and ( contains(normalize-space(@title), %locator%))]
index d2a8672..9865782 100644 (file)
@@ -82,7 +82,7 @@ class registration {
         global $DB;
 
         if (self::$registration === null) {
-            self::$registration = $DB->get_record('registration_hubs', ['huburl' => HUB_MOODLEORGHUBURL]);
+            self::$registration = $DB->get_record('registration_hubs', ['huburl' => HUB_MOODLEORGHUBURL]) ?: null;
         }
 
         if (self::$registration && (bool)self::$registration->confirmed == (bool)$confirmed) {
@@ -578,4 +578,4 @@ class registration {
             redirect(new moodle_url('/admin/registration/index.php', ['returnurl' => $returnurl->out_as_local_url(false)]));
         }
     }
-}
\ No newline at end of file
+}
index aebf1c0..b0465fc 100644 (file)
@@ -61,7 +61,8 @@ class MoodleQuickForm_cancel extends MoodleQuickForm_submit
             $value=get_string('cancel');
         }
         parent::__construct($elementName, $value, $attributes);
-        $this->updateAttributes(array('onclick'=>'skipClientValidation = true; return true;'));
+        $this->updateAttributes(array('data-skip-validation' => 1, 'data-cancel' => 1,
+            'onclick' => 'skipClientValidation = true; return true;'));
 
         // Add the class btn-cancel.
         $class = $this->getAttribute('class');
@@ -93,7 +94,7 @@ class MoodleQuickForm_cancel extends MoodleQuickForm_submit
     {
         switch ($event) {
             case 'createElement':
-                static::__construct($arg[0], $arg[1], $arg[2]);
+                parent::onQuickFormEvent($event, $arg, $caller);
                 $caller->_registerCancelButton($this->getName());
                 return true;
                 break;
index d44b1a2..3942eae 100644 (file)
@@ -112,7 +112,7 @@ class MoodleQuickForm_submit extends HTML_QuickForm_submit implements templatabl
                     $onClick = $this->getAttribute('onclick');
                     $skip = 'skipClientValidation = true;';
                     $onClick = ($onClick !== null)?$skip.' '.$onClick:$skip;
-                    $this->updateAttributes(array('onclick'=>$onClick));
+                    $this->updateAttributes(array('data-skip-validation' => 1, 'data-no-submit' => 1, 'onclick' => $onClick));
                 }
                 return true;
                 break;
index 8ed10a4..bc0d581 100644 (file)
@@ -198,7 +198,7 @@ abstract class moodleform {
         $this->_formname = $this->get_form_identifier();
         $this->_ajaxformdata = $ajaxformdata;
 
-        $this->_form = new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes);
+        $this->_form = new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes, $ajaxformdata);
         if (!$editable){
             $this->_form->hardFreeze();
         }
@@ -503,7 +503,7 @@ abstract class moodleform {
             return false;
         }
         foreach ($mform->_noSubmitButtons as $nosubmitbutton){
-            if (optional_param($nosubmitbutton, 0, PARAM_RAW)){
+            if ($this->optional_param($nosubmitbutton, 0, PARAM_RAW)) {
                 $nosubmit = true;
                 break;
             }
@@ -511,6 +511,21 @@ abstract class moodleform {
         return $nosubmit;
     }
 
+    /**
+     * Checks if a parameter was passed in the previous form submission
+     *
+     * @param string $name the name of the page parameter we want
+     * @param mixed  $default the default value to return if nothing is found
+     * @param string $type expected type of parameter
+     * @return mixed
+     */
+    public function optional_param($name, $default, $type) {
+        if (isset($this->_ajaxformdata[$name])) {
+            return clean_param($this->_ajaxformdata[$name], $type);
+        } else {
+            return optional_param($name, $default, $type);
+        }
+    }
 
     /**
      * Check that form data is valid.
@@ -616,7 +631,7 @@ abstract class moodleform {
         $mform =& $this->_form;
         if ($mform->isSubmitted()){
             foreach ($mform->_cancelButtons as $cancelbutton){
-                if (optional_param($cancelbutton, 0, PARAM_RAW)){
+                if ($this->optional_param($cancelbutton, 0, PARAM_RAW)) {
                     return true;
                 }
             }
@@ -1085,8 +1100,8 @@ abstract class moodleform {
         } else {
             $addstring = str_ireplace('{no}', $addfieldsno, $addstring);
         }
-        $repeats = optional_param($repeathiddenname, $repeats, PARAM_INT);
-        $addfields = optional_param($addfieldsname, '', PARAM_TEXT);
+        $repeats = $this->optional_param($repeathiddenname, $repeats, PARAM_INT);
+        $addfields = $this->optional_param($addfieldsname, '', PARAM_TEXT);
         if (!empty($addfields)){
             $repeats += $addfieldsno;
         }
@@ -1205,8 +1220,8 @@ abstract class moodleform {
         }
 
         $mform = $this->_form;
-        $selectvalue = optional_param($checkboxcontrollerparam, null, PARAM_INT);
-        $contollerbutton = optional_param($checkboxcontrollername, null, PARAM_ALPHAEXT);
+        $selectvalue = $this->optional_param($checkboxcontrollerparam, null, PARAM_INT);
+        $contollerbutton = $this->optional_param($checkboxcontrollername, null, PARAM_ALPHAEXT);
 
         $newselectvalue = $selectvalue;
         if (is_null($selectvalue)) {
@@ -1502,6 +1517,9 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
      */
     var $_pageparams = '';
 
+    /** @var array $_ajaxformdata submitted form data when using mforms with ajax */
+    protected $_ajaxformdata;
+
     /**
      * Whether the form contains any client-side validation or not.
      * @var bool
@@ -1527,8 +1545,9 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
      * @param string|moodle_url $action Form's action
      * @param string $target (optional)Form's target defaults to none
      * @param mixed $attributes (optional)Extra attributes for <form> tag
+     * @param array $ajaxformdata Forms submitted via ajax, must pass their data here, instead of relying on _GET and _POST.
      */
-    public function __construct($formName, $method, $action, $target='', $attributes=null) {
+    public function __construct($formName, $method, $action, $target = '', $attributes = null, $ajaxformdata = null) {
         global $CFG, $OUTPUT;
 
         static $formcounter = 1;
@@ -1553,6 +1572,7 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
         $this->updateAttributes($attributes);
 
         // This is custom stuff for Moodle :
+        $this->_ajaxformdata = $ajaxformdata;
         $oldclass=   $this->getAttribute('class');
         if (!empty($oldclass)){
             $this->updateAttributes(array('class'=>$oldclass.' mform'));
@@ -1592,6 +1612,22 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
         }
     }
 
+    /**
+     * Checks if a parameter was passed in the previous form submission
+     *
+     * @param string $name the name of the page parameter we want
+     * @param mixed  $default the default value to return if nothing is found
+     * @param string $type expected type of parameter
+     * @return mixed
+     */
+    public function optional_param($name, $default, $type) {
+        if (isset($this->_ajaxformdata[$name])) {
+            return clean_param($this->_ajaxformdata[$name], $type);
+        } else {
+            return optional_param($name, $default, $type);
+        }
+    }
+
     /**
      * Use this method to indicate that the fieldset should be shown as expanded.
      * The method is applicable to header elements only.
@@ -1617,7 +1653,7 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
         }
         if ($this->getElementType('mform_isexpanded_' . $headerid) === false) {
             // See if the form has been submitted already.
-            $formexpanded = optional_param('mform_isexpanded_' . $headerid, -1, PARAM_INT);
+            $formexpanded = $this->optional_param('mform_isexpanded_' . $headerid, -1, PARAM_INT);
             if (!$ignoreuserstate && $formexpanded != -1) {
                 // Override expanded state with the form variable.
                 $expanded = $formexpanded;
@@ -1641,7 +1677,7 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
         // Add extra hidden element to store advanced items state for each section.
         if ($this->getElementType('mform_showmore_' . $headerid) === false) {
             // See if we the form has been submitted already.
-            $formshowmore = optional_param('mform_showmore_' . $headerid, -1, PARAM_INT);
+            $formshowmore = $this->optional_param('mform_showmore_' . $headerid, -1, PARAM_INT);
             if (!$showmore && $formshowmore != -1) {
                 // Override showmore state with the form variable.
                 $showmore = $formshowmore;
index 24a944a..314ae0c 100644 (file)
@@ -270,6 +270,13 @@ EOD;
 
         if (!$record['deleted']) {
             context_user::instance($userid);
+
+            // All new not deleted users must have a favourite self-conversation.
+            $selfconversation = \core_message\api::create_conversation(
+                \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+                [$userid]
+            );
+            \core_message\api::set_favourite_conversation($selfconversation->id, $userid);
         }
 
         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
index a94c6ca..e7c3405 100644 (file)
@@ -384,17 +384,19 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // Check that we retrieved the correct non-contacts.
         // When site wide messaging is disabled, we expect to see only those users who we share a course with and whose profiles
         // are visible in that course. This excludes users like course contacts.
-        $this->assertCount(2, $noncontacts);
-        $this->assertEquals($users[6]->id, $noncontacts[0]->id);
-        $this->assertEquals($users[7]->id, $noncontacts[1]->id);
+        $this->assertCount(3, $noncontacts);
+        // Self-conversation first.
+        $this->assertEquals($users[1]->id, $noncontacts[0]->id);
+        $this->assertEquals($users[6]->id, $noncontacts[1]->id);
+        $this->assertEquals($users[7]->id, $noncontacts[2]->id);
 
         // Verify the correct conversations were returned for the non-contacts.
-        $this->assertCount(1, $noncontacts[0]->conversations);
+        $this->assertCount(1, $noncontacts[1]->conversations);
         $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
-            $noncontacts[0]->conversations[$ic2->id]->type);
+            $noncontacts[1]->conversations[$ic2->id]->type);
 
-        $this->assertCount(1, $noncontacts[1]->conversations);
-        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[1]->conversations[$gc1->id]->type);
+        $this->assertCount(1, $noncontacts[2]->conversations);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[2]->conversations[$gc1->id]->type);
     }
 
     /**
@@ -436,6 +438,9 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
         set_config('coursecontact', $teacherrole->id);
 
+        // Get self-conversation.
+        $selfconversation = \core_message\api::get_self_conversation($users[1]->id);
+
         // Create individual conversations between some users, one contact and one non-contact.
         $ic1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
             [$users[1]->id, $users[2]->id]);
@@ -474,13 +479,29 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // Check that we retrieved the correct non-contacts.
         // If site wide messaging is enabled, we expect to only be able to search for users whose profiles we can view.
         // In this case, as a student, that's the course contact for course2 and those noncontacts sharing a course with user1.
-        $this->assertCount(3, $noncontacts);
-        $this->assertEquals($users[6]->id, $noncontacts[0]->id);
-        $this->assertEquals($users[7]->id, $noncontacts[1]->id);
-        $this->assertEquals($users[9]->id, $noncontacts[2]->id);
-        $this->assertCount(1, $noncontacts[0]->conversations);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(4, $noncontacts);
+        $this->assertEquals($users[1]->id, $noncontacts[0]->id);
+        $this->assertEquals($users[6]->id, $noncontacts[1]->id);
+        $this->assertEquals($users[7]->id, $noncontacts[2]->id);
+        $this->assertEquals($users[9]->id, $noncontacts[3]->id);
+
+        $this->assertCount(1, $noncontacts[1]->conversations);
+        $this->assertCount(1, $noncontacts[2]->conversations);
+        $this->assertCount(0, $noncontacts[3]->conversations);
+
+        // Verify the correct conversations were returned for the non-contacts.
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+            $noncontacts[0]->conversations[$selfconversation->id]->type);
+
         $this->assertCount(1, $noncontacts[1]->conversations);
-        $this->assertCount(0, $noncontacts[2]->conversations);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
+            $noncontacts[1]->conversations[$ic2->id]->type);
+
+        $this->assertCount(1, $noncontacts[2]->conversations);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[2]->conversations[$gc1->id]->type);
+
+        $this->assertCount(0, $noncontacts[3]->conversations);
     }
 
     /**
@@ -499,8 +520,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $user2->lastname = 'Two';
         $user2 = $this->getDataGenerator()->create_user($user2);
 
-        // Create self-conversation for user1.
-        $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+        // Get self-conversation for user1.
+        $sc1 = \core_message\api::get_self_conversation($user1->id);
         testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Hi myself!');
 
         // Perform a search as user1.
@@ -556,7 +577,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // Enrol the first 9 users in the same course, but leave them as non-contacts.
         $this->setAdminUser();
         $course1 = $this->getDataGenerator()->create_course();
-        foreach (range(1, 9) as $i) {
+        foreach (range(1, 8) as $i) {
             $this->getDataGenerator()->enrol_user($users[$i]->id, $course1->id);
         }
 
@@ -581,10 +602,11 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $this->assertEquals($users[13]->id, $contacts[2]->id);
 
         // Check that we retrieved the correct non-contacts.
+        // Consider first conversations is self-conversation.
         $this->assertCount(3, $noncontacts);
-        $this->assertEquals($users[2]->id, $noncontacts[0]->id);
-        $this->assertEquals($users[3]->id, $noncontacts[1]->id);
-        $this->assertEquals($users[4]->id, $noncontacts[2]->id);
+        $this->assertEquals($users[1]->id, $noncontacts[0]->id);
+        $this->assertEquals($users[2]->id, $noncontacts[1]->id);
+        $this->assertEquals($users[3]->id, $noncontacts[2]->id);
 
         // Now, offset to get the next batch of results.
         // We expect to see 2 contacts, and 3 non-contacts.
@@ -596,9 +618,9 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $this->assertEquals($users[15]->id, $contacts[1]->id);
 
         $this->assertCount(3, $noncontacts);
-        $this->assertEquals($users[5]->id, $noncontacts[0]->id);
-        $this->assertEquals($users[6]->id, $noncontacts[1]->id);
-        $this->assertEquals($users[7]->id, $noncontacts[2]->id);
+        $this->assertEquals($users[4]->id, $noncontacts[0]->id);
+        $this->assertEquals($users[5]->id, $noncontacts[1]->id);
+        $this->assertEquals($users[6]->id, $noncontacts[2]->id);
 
         // Now, offset to get the next batch of results.
         // We expect to see 0 contacts, and 2 non-contacts.
@@ -608,8 +630,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $this->assertCount(0, $contacts);
 
         $this->assertCount(2, $noncontacts);
-        $this->assertEquals($users[8]->id, $noncontacts[0]->id);
-        $this->assertEquals($users[9]->id, $noncontacts[1]->id);
+        $this->assertEquals($users[7]->id, $noncontacts[0]->id);
+        $this->assertEquals($users[8]->id, $noncontacts[1]->id);
     }
 
     /**
@@ -661,9 +683,11 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
 
         // Check that we retrieved the correct non-contacts.
         // Site-wide messaging is disabled, so we expect to be able to search for any users whose profiles we can view.
-        $this->assertCount(2, $noncontacts);
-        $this->assertEquals($users[6]->id, $noncontacts[0]->id);
-        $this->assertEquals($users[7]->id, $noncontacts[1]->id);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(3, $noncontacts);
+        $this->assertEquals($users[1]->id, $noncontacts[0]->id);
+        $this->assertEquals($users[6]->id, $noncontacts[1]->id);
+        $this->assertEquals($users[7]->id, $noncontacts[2]->id);
     }
 
     /**
@@ -781,24 +805,29 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             array($user1->id, $user2->id, $user3->id),
             'Project chat');
 
-        // Create self-conversations.
-        $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
-            array($user1->id));
-        $sc2 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
-            array($user2->id));
-
-        // Send message to self-conversation.
-        testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Message to myself!');
-
+        // Get self-conversations.
         $rsc1 = \core_message\api::get_self_conversation($user1->id);
         $rsc2 = \core_message\api::get_self_conversation($user2->id);
         $rsc3 = \core_message\api::get_self_conversation($user3->id);
 
+        // Send message to self-conversation.
+        testhelper::send_fake_message_to_conversation($user1, $rsc1->id, 'Message to myself!');
+
         // Check that we retrieved the correct conversations.
-        $this->assertEquals($sc1->id, $rsc1->id);
-        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, $sc1->type);
-        $this->assertEquals($sc2->id, $rsc2->id);
-        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, $sc2->type);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, $rsc1->type);
+        $members = \core_message\api::get_conversation_members($user1->id, $rsc1->id);
+        $this->assertCount(1, $members);
+        $member = reset($members);
+        $this->assertEquals($user1->id, $member->id);
+
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, $rsc2->type);
+        $members = \core_message\api::get_conversation_members($user2->id, $rsc2->id);
+        $this->assertCount(1, $members);
+        $member = reset($members);
+        $this->assertEquals($user2->id, $member->id);
+
+        \core_message\api::delete_all_conversation_data($rsc3->id);
+        $rsc3 = \core_message\api::get_self_conversation($user3->id);
         $this->assertFalse($rsc3);
     }
 
@@ -816,8 +845,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // The person doing the search.
         $this->setUser($user1);
 
-        // Create self-conversation.
-        $sc = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+        // Get self-conversation.
+        $sc = \core_message\api::get_self_conversation($user1->id);
 
         // Create group conversation.
         $gc = \core_message\api::create_conversation(
@@ -926,8 +955,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // The person doing the search.
         $this->setUser($user1);
 
-        // No conversations yet.
-        $this->assertEquals([], \core_message\api::get_conversations($user1->id));
+        // Only self-conversation created.
+        $this->assertCount(1, \core_message\api::get_conversations($user1->id));
 
         // Create some conversations for user1.
         $time = 1;
@@ -955,12 +984,21 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context);
         }
 
-        // We should have 3 conversations.
-        $this->assertCount(3, \core_message\api::get_conversations($user1->id));
+        // We should have 4 conversations.
+        // Consider first conversations is self-conversation.
+        $this->assertCount(4, \core_message\api::get_conversations($user1->id));
 
-        // And 2 favourited conversations.
+        // And 3 favourited conversations (self-conversation included).
         $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, true);
-        $this->assertCount(2, $conversations);
+        $this->assertCount(3, $conversations);
+        $conversations = \core_message\api::get_conversations(
+            $user1->id,
+            0,
+            20,
+            \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
+            true
+        );
+        $this->assertCount(1, $conversations);
     }
 
     /**
@@ -976,8 +1014,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // The person doing the search.
         $this->setUser($user1);
 
-        // No conversations yet.
-        $this->assertEquals([], \core_message\api::get_conversations($user1->id));
+        // Only self-conversation created.
+        $this->assertCount(1, \core_message\api::get_conversations($user1->id));
 
         // Create some conversations for user1.
         $time = 1;
@@ -1006,14 +1044,15 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context);
         }
 
+        // Consider first conversations is self-conversation.
         // Get all records, using offset 0 and large limit.
-        $this->assertCount(2, \core_message\api::get_conversations($user1->id, 1, 10, null, true));
+        $this->assertCount(4, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
 
         // Now, get 10 conversations starting at the second record. We should see 2 conversations.
-        $this->assertCount(2, \core_message\api::get_conversations($user1->id, 1, 10, null, true));
+        $this->assertCount(3, \core_message\api::get_conversations($user1->id, 1, 10, null, true));
 
         // Now, try to get favourited conversations using an invalid offset.
-        $this->assertCount(0, \core_message\api::get_conversations($user1->id, 4, 10, null, true));
+        $this->assertCount(0, \core_message\api::get_conversations($user1->id, 5, 10, null, true));
     }
 
     /**
@@ -1054,7 +1093,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, true);
 
         // We should have both conversations, despite the other user being soft-deleted.
-        $this->assertCount(2, $conversations);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(3, $conversations);
 
         // Confirm the conversation is from the non-deleted user.
         $conversation = reset($conversations);
@@ -1086,11 +1126,13 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $conversationid1 = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]);
         $favourite = \core_message\api::set_favourite_conversation($conversationid1, $user1->id);
 
-        // Verify we have a single favourite conversation a user 1.
-        $this->assertCount(1, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
+        // Verify we have two favourite conversations a user 1.
+        // Consider first conversations is self-conversation.
+        $this->assertCount(2, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
 
-        // Verify we have no favourites as user2, despite being a member in that conversation.
-        $this->assertCount(0, \core_message\api::get_conversations($user2->id, 0, 20, null, true));
+        // Verify we have only one favourite as user2, despite being a member in that conversation.
+        // Consider first conversations is self-conversation.
+        $this->assertCount(1, \core_message\api::get_conversations($user2->id, 0, 20, null, true));
 
         // Try to favourite the same conversation again should just return the existing favourite.
         $repeatresult = \core_message\api::set_favourite_conversation($conversationid1, $user1->id);
@@ -1162,16 +1204,16 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         \core_message\api::set_favourite_conversation($conversationid1, $user1->id);
         \core_message\api::set_favourite_conversation($conversationid2, $user3->id);
 
-        // Verify we have a single favourite conversation for both user 1 and user 3.
-        $this->assertCount(1, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
-        $this->assertCount(1, \core_message\api::get_conversations($user3->id, 0, 20, null, true));
+        // Verify we have two favourite conversations for both user 1 and user 3, counting self conversations.
+        $this->assertCount(2, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
+        $this->assertCount(2, \core_message\api::get_conversations($user3->id, 0, 20, null, true));
 
         // Now unfavourite the conversation as user 1.
         \core_message\api::unset_favourite_conversation($conversationid1, $user1->id);
 
-        // Verify we have a single favourite conversation user 3 only, and none for user1.
-        $this->assertCount(1, \core_message\api::get_conversations($user3->id, 0, 20, null, true));
-        $this->assertCount(0, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
+        // Verify we have two favourite conversations user 3 only, and one for user1, counting self conversations.
+        $this->assertCount(2, \core_message\api::get_conversations($user3->id, 0, 20, null, true));
+        $this->assertCount(1, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
 
         // Try to favourite the same conversation again as user 1.
         $this->expectException(\moodle_exception::class);
@@ -1286,9 +1328,10 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
      */
     public function test_get_conversations_no_restrictions() {
         global $DB;
-        // No conversations should exist yet.
+
         $user1 = self::getDataGenerator()->create_user();
-        $this->assertEquals([], \core_message\api::get_conversations($user1->id));
+        // Self-conversation should exists.
+        $this->assertCount(1, \core_message\api::get_conversations($user1->id));
 
         // Get a bunch of conversations, some group, some individual and in different states.
         list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
@@ -1297,18 +1340,20 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // Get all conversations for user1.
         $conversations = core_message\api::get_conversations($user1->id);
 
-        // Verify there are 2 individual conversation, 2 group conversations, and 2 empty group conversations.
+        // Verify there are 2 individual conversation, 2 group conversations, 2 empty group conversations,
+        // and a self-conversation.
         // The conversations with the most recent messages should be listed first, followed by the empty
         // conversations, with the most recently created first.
-        $this->assertCount(6, $conversations);
+        $this->assertCount(7, $conversations);
         $typecounts  = array_count_values(array_column($conversations, 'type'));
         $this->assertEquals(2, $typecounts[1]);
         $this->assertEquals(4, $typecounts[2]);
+        $this->assertEquals(1, $typecounts[3]);
 
-        // Those conversations having messages should be listed first, ordered by most recent message time.
+        // Those conversations having messages should be listed after self-conversation, ordered by most recent message time.
         $this->assertEquals($gc3->id, $conversations[0]->id);
         $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $conversations[0]->type);
-        $this->assertFalse($conversations[0]->isfavourite);
+        $this->assertFalse($conversations[1]->isfavourite);
         $this->assertCount(1, $conversations[0]->members);
         $this->assertEquals(4, $conversations[0]->membercount);
         $this->assertCount(1, $conversations[0]->messages);
@@ -1463,6 +1508,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             return $first->id > $second->id;
         });
 
+        // Consider first conversations is self-conversation.
+        $selfconversation = array_shift($conversations);
         $conv1 = array_shift($conversations);
         $conv2 = array_shift($conversations);
         $conv3 = array_shift($conversations);
@@ -1555,9 +1602,9 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             [$user1->id, $user3->id]);
         testhelper::send_fake_message_to_conversation($user1, $ic1->id, 'Message from user1 to user2');
 
-        // Create some self-conversations.
-        $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
-        $sc4 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user4->id]);
+        // Get some self-conversations.
+        $sc1 = \core_message\api::get_self_conversation($user1->id);
+        $sc4 = \core_message\api::get_self_conversation($user4->id);
         testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Test message to self 1!');
 
         // Verify we are in a 'self' conversation state.
@@ -1591,10 +1638,10 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL);
         $this->assertCount(1, $conversations);
 
-        // Merge self with private conversations for user2 (is the same result than before because user2 hasn't self-conversations).
+        // Merge self with private conversations for user2.
         $conversations = \core_message\api::get_conversations($user2->id, 0, 20,
             \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, null, true);
-        $this->assertCount(1, $conversations);
+        $this->assertCount(2, $conversations);
     }
 
     /**
@@ -1613,7 +1660,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // We no longer delete the user context, but historically we did.
         context_helper::delete_instance(CONTEXT_USER, $user2->id);
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(6, $conversations);
+        // Consider there's a self-conversation (the last one).
+        $this->assertCount(7, $conversations);
         $this->assertEquals($gc3->id, $conversations[0]->id);
         $this->assertcount(1, $conversations[0]->members);
         $this->assertEquals($gc2->id, $conversations[1]->id);
@@ -1627,7 +1675,9 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // This user will still be present in the members array, as will the message in the messages array.
         delete_user($user4);
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(6, $conversations);
+
+        // Consider there's a self-conversation (the last one).
+        $this->assertCount(7, $conversations);
         $this->assertEquals($gc2->id, $conversations[1]->id);
         $this->assertcount(1, $conversations[1]->members);
         $this->assertEquals($user4->id, $conversations[1]->members[$user4->id]->id);
@@ -1639,7 +1689,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // Group conversations are also present, albeit with less members.
         delete_user($user3);
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(6, $conversations);
+        // Consider there's a self-conversation (the last one).
+        $this->assertCount(7, $conversations);
         $this->assertEquals($gc3->id, $conversations[0]->id);
         $this->assertcount(1, $conversations[0]->members);
         $this->assertEquals($gc2->id, $conversations[1]->id);
@@ -1659,7 +1710,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
 
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(6, $conversations);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(7, $conversations);
 
         // Delete all messages from a group conversation the user is in - it should be returned.
         $this->assertTrue(\core_message\api::is_user_in_conversation($user1->id, $gc2->id));
@@ -1669,7 +1721,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             \core_message\api::delete_message($user1->id, $message->id);
         }
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(6, $conversations);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(7, $conversations);
         $this->assertContains($gc2->id, array_column($conversations, 'id'));
 
         // Delete all messages from an individual conversation the user is in - it should not be returned.
@@ -1680,7 +1733,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             \core_message\api::delete_message($user1->id, $message->id);
         }
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(5, $conversations);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(6, $conversations);
         $this->assertNotContains($ic1->id, array_column($conversations, 'id'));
     }
 
@@ -1698,18 +1752,21 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
 
         // Get the conversation, first with no restrictions, confirming the favourite status of the conversations.
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(6, $conversations);
+        // Consider there is a self-conversation.
+        $selfconversation = \core_message\api::get_self_conversation($user1->id);
+        $this->assertCount(7, $conversations);
         foreach ($conversations as $conv) {
-            if (in_array($conv->id, [$ic2->id])) {
+            if (in_array($conv->id, [$ic2->id, $selfconversation->id])) {
                 $this->assertTrue($conv->isfavourite);
             } else {
                 $this->assertFalse($conv->isfavourite);
             }
         }
 
-        // Now, get ONLY favourite conversations.
+        // Now, get ONLY favourite conversations (including self-conversation).
         $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, true);
-        $this->assertCount(1, $conversations);
+        $this->assertCount(2, $conversations);
+        $self = array_pop($conversations);
         foreach ($conversations as $conv) {
             $this->assertTrue($conv->isfavourite);
             $this->assertEquals($ic2->id, $conv->id);
@@ -1737,11 +1794,18 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         list($user1, $user2, $user3, $user4, $ic1, $ic2, $ic3,
             $gc1, $gc2, $gc3, $gc4, $gc5, $gc6) = $this->create_conversation_test_data();
 
+        // Try to get ONLY favourite conversations, when only self-conversation exist.
+        $this->assertCount(1, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
+
+        // Unstar self-conversation.
+        $selfconversation = \core_message\api::get_self_conversation($user1->id);
+        \core_message\api::unset_favourite_conversation($selfconversation->id, $user1->id);
+
         // Try to get ONLY favourite conversations, when no favourites exist.
         $this->assertEquals([], \core_message\api::get_conversations($user1->id, 0, 20, null, true));
 
         // Try to get NO favourite conversations, when no favourites exist.
-        $this->assertCount(6, \core_message\api::get_conversations($user1->id, 0, 20, null, false));
+        $this->assertCount(7, \core_message\api::get_conversations($user1->id, 0, 20, null, false));
 
         // Mark a few conversations as favourites.
         \core_message\api::set_favourite_conversation($ic1->id, $user1->id);
@@ -1751,7 +1815,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
 
         // Get the conversations, first with no restrictions, confirming the favourite status of the conversations.
         $conversations = \core_message\api::get_conversations($user1->id);
-        $this->assertCount(6, $conversations);
+        $this->assertCount(7, $conversations);
         foreach ($conversations as $conv) {
             if (in_array($conv->id, $favouriteids)) {
                 $this->assertTrue($conv->isfavourite);
@@ -1779,7 +1843,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
 
         // And NO favourite conversations.
         $conversations = \core_message\api::get_conversations($user1->id, 0, 20, null, false);
-        $this->assertCount(3, $conversations);
+        $this->assertCount(4, $conversations);
         foreach ($conversations as $conv) {
             $this->assertFalse($conv->isfavourite);
             $this->assertFalse(array_search($conv->id, $favouriteids));
@@ -1892,6 +1956,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
 
         // Verify the group without any image works as expected too.
         $conversations = \core_message\api::get_conversations($user3->id);
+        // Consider first conversations is self-conversation.
         $this->assertEquals(2, $conversations[0]->membercount);
         $this->assertEquals($course1->shortname, $conversations[0]->subname);
         $this->assertEquals('https://www.example.com/moodle/theme/image.php/_s/boost/core/1/g/g1', $conversations[0]->imageurl);
@@ -1899,7 +1964,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // Now, disable the conversation linked to the group and verify it's no longer returned.
         $DB->set_field('message_conversations', 'enabled', 0, ['id' => $conversations[0]->id]);
         $conversations = \core_message\api::get_conversations($user3->id);
-        $this->assertCount(0, $conversations);
+        $this->assertCount(1, $conversations);
     }
 
    /**
@@ -3175,9 +3240,9 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         // The person doing the search.
         $this->setUser($user1);
 
-        // Create self-conversation.
-        $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
-        $sc2 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user2->id]);
+        // Get self-conversation.
+        $sc1 = \core_message\api::get_self_conversation($user1->id);
+        $sc2 = \core_message\api::get_self_conversation($user2->id);
 
         // Send some messages back and forth.
         $time = 1;
@@ -3555,11 +3620,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             ]
         );
 
-        // Create a self-conversation for user1.
-        $sc1 = \core_message\api::create_conversation(
-            \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
-            [$user1->id]
-        );
+        // Get a self-conversation for user1.
+        $sc1 = \core_message\api::get_self_conversation($user1->id);
 
         // For group conversations, there are no user privacy checks, so only membership in the conversation is needed.
         $this->assertTrue(\core_message\api::can_send_message_to_conversation($user1->id, $gc1->id));
@@ -3716,11 +3778,8 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             ]
         );
 
-        // Create a self-conversation for user1.
-        $sc1 = \core_message\api::create_conversation(
-            \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
-            [$user1->id]
-        );
+        // Get a self-conversation for user1.
+        $sc1 = \core_message\api::get_self_conversation($user1->id);
 
         // Verify, non members cannot send a message.
         $this->assertFalse(\core_message\api::can_send_message_to_conversation($user4->id, $gc1->id));
@@ -5978,8 +6037,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
 
         $user1 = self::getDataGenerator()->create_user($lastaccess);
 
-        $selfconversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF,
-            [$user1->id]);
+        $selfconversation = \core_message\api::get_self_conversation($user1->id);
         testhelper::send_fake_message_to_conversation($user1, $selfconversation->id, 'This is a self-message!');
 
         // Get the members for the self-conversation.
@@ -6299,7 +6357,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user5],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6316,7 +6374,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user4],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6333,7 +6391,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6350,7 +6408,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user2],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6367,7 +6425,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user4],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6384,7 +6442,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [0],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6401,7 +6459,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [3],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6418,7 +6476,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [0, 1, 2],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6435,7 +6493,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [3, 4, 5],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6452,7 +6510,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [0, 1, 2],
                 'arguments' => [$user2],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6469,7 +6527,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [3, 4, 5],
                 'arguments' => [$user3],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6486,7 +6544,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [6, 7],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6503,7 +6561,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [6, 7, 8, 9],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6520,7 +6578,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6537,7 +6595,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6554,7 +6612,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user6],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6571,7 +6629,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user7],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
index 3babfe4..4f8b25c 100644 (file)
@@ -29,34 +29,36 @@ Feature: Star and unstar conversations
   Scenario: Star a group conversation
     Given I log in as "student1"
     Then I open messaging
+    And I open the "Group" conversations list
     And "Group 1" "group_message" should exist
     And I select "Group 1" conversation in messaging
     And I open contact menu
     And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element"
     And I go back in "view-conversation" message drawer
     And I open the "Starred" conversations list
-    And I should see "Group 1" in the "//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should see "Group 1" in the "favourites" "group_message_list_area"
     And I open the "Group" conversations list
-    And I should not see "Group 1" in the "//div[@data-region='view-overview-group-messages']" "xpath_element"
+    And I should not see "Group 1" in the "group-messages" "group_message_list_area"
 
   Scenario: Unstar a group conversation
     Given I log in as "student1"
     Then I open messaging
+    And I open the "Group" conversations list
     And "Group 1" "group_message" should exist
     And I select "Group 1" conversation in messaging
     And I open contact menu
     And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element"
     And I go back in "view-conversation" message drawer
     And I open the "Starred" conversations list
-    And I should see "Group 1" in the "//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should see "Group 1" in the "favourites" "group_message_list_area"
     And I select "Group 1" conversation in messaging
     And I open contact menu
     And I click on "Unstar" "link" in the "//div[@data-region='header-container']" "xpath_element"
     And I go back in "view-conversation" message drawer
     And I open the "Starred" conversations list
-    And I should not see "Group 1" in the "//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should not see "Group 1" in the "favourites" "group_message_list_area"
     And I open the "Group" conversations list
-    And I should see "Group 1" in the "//div[@data-region='view-overview-group-messages']" "xpath_element"
+    And I should see "Group 1" in the "group-messages" "group_message_list_area"
 
   Scenario: Star a private conversation
     Given the following "private messages" exist:
@@ -71,9 +73,9 @@ Feature: Star and unstar conversations
     And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element"
     And I go back in "view-conversation" message drawer
     And I open the "Starred" conversations list
-    And I should see "Student 2" in the "//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should see "Student 2" in the "favourites" "group_message_list_area"
     And I open the "Private" conversations list
-    And I should not see "Student 2" in the "//div[@data-region='view-overview-messages']" "xpath_element"
+    And I should not see "Student 2" in the "messages" "group_message_list_area"
 
   Scenario: Unstar a private conversation
     Given the following "private messages" exist:
@@ -84,12 +86,12 @@ Feature: Star and unstar conversations
       | student1 | student2 |
     Then I log in as "student1"
     And I open messaging
-    And I should see "Student 2" in the "//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should see "Student 2" in the "favourites" "group_message_list_area"
     And I select "Student 2" conversation in messaging
     And I open contact menu
     And I click on "Unstar" "link" in the "//div[@data-region='header-container']" "xpath_element"
     And I go back in "view-conversation" message drawer
     And I open the "Starred" conversations list
-    And I should not see "Group 1" in the "//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should not see "Group 1" in the "favourites" "group_message_list_area"
     And I open the "Private" conversations list
-    And I should see "Student 2" in the "//div[@data-region='view-overview-messages']" "xpath_element"
\ No newline at end of file
+    And I should see "Student 2" in the "messages" "group_message_list_area"
\ No newline at end of file
index 422fe3b..105164e 100644 (file)
@@ -43,12 +43,14 @@ Feature: Create conversations for course's groups
   Scenario: Group conversations are restricted to members
     Given I log in as "teacher1"
     Then I open messaging
+    And I open the "Group" conversations list
     And "Group 1" "group_message" should exist
     And "Group 2" "group_message" should exist
     And "Group 3" "group_message" should not exist
     And I log out
     And I log in as "student1"
     And I open messaging
+    And I open the "Group" conversations list
     And "Group 1" "group_message" should exist
     And "Group 2" "group_message" should not exist
     And "Group 3" "group_message" should not exist
@@ -56,15 +58,17 @@ Feature: Create conversations for course's groups
   Scenario: View group conversation's participants numbers
     Given I log in as "teacher1"
     Then I open messaging
+    And I open the "Group" conversations list
     And I select "Group 1" conversation in messaging
-    And "5 participants" "group_message_header" should exist
+    And I should see "5 participants" in the "Group 1" "group_message_header"
     And I go back in "view-conversation" message drawer
     And I select "Group 2" conversation in messaging
-    And "1 participants" "group_message_header" should exist
+    And I should see "1 participants" in the "Group 2" "group_message_header"
 
   Scenario: View group conversation's participants list
     Given I log in as "teacher1"
     Then I open messaging
+    And I open the "Group" conversations list
     # Check Group 1 participants list.
     And I select "Group 1" conversation in messaging
     And I open messaging information
@@ -90,14 +94,15 @@ Feature: Create conversations for course's groups
     And I add "Student 4 (student4@example.com)" user to "Group 1" group members
     And I add "Student 4 (student4@example.com)" user to "Group 2" group members
     And I open messaging
+    And I open the "Group" conversations list
     And I select "Group 1" conversation in messaging
-    And "6 participants" "group_message_header" should exist
+    And I should see "6 participants" in the "Group 1" "group_message_header"
     And I open messaging information
     And "Student 4" "group_message_member" should exist
     And I go back in "group-info-content-container" message drawer
     And I go back in "view-conversation" message drawer
     And I select "Group 2" conversation in messaging
-    And "2 participants" "group_message_header" should exist
+    And I should see "2 participants" in the "Group 2" "group_message_header"
     And I open messaging information
     And "No participants" "group_message_member" should not exist
     And "Student 4" "group_message_member" should exist
index 8d7abe4..02bfe52 100644 (file)
@@ -116,8 +116,8 @@ Feature: Message delete conversations
     And I should not see "Delete"
     And I should not see "Hi!" in the "Student 2" "group_message_conversation"
     And I go back in "view-conversation" message drawer
-    And I should not see "Student 2" in the "//*[@data-region='message-drawer']//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should not see "Student 2" in the "favourites" "group_message_list_area"
     And I send "Hi!" message to "Student 2" user
     And I go back in "view-conversation" message drawer
     And I go back in "view-search" message drawer
-    And I should see "Student 2" in the "//*[@data-region='message-drawer']//div[@data-region='view-overview-favourites']" "xpath_element"
+    And I should see "Student 2" in the "favourites" "group_message_list_area"
index 4a566b3..1596073 100644 (file)
@@ -29,6 +29,7 @@ Feature: Message send messages
   Scenario: Send a message to a group conversation
     Given I log in as "student1"
     And I open messaging
+    And I open the "Group" conversations list
     And "Group 1" "group_message" should exist
     And I select "Group 1" conversation in messaging
     When I send "Hi!" message in the message area
@@ -44,6 +45,7 @@ Feature: Message send messages
   Scenario: Send a message to a starred conversation
     Given I log in as "student1"
     When I open messaging
+    And I open the "Group" conversations list
     Then "Group 1" "group_message" should exist
     And I select "Group 1" conversation in the "group-messages" conversations list
     And I open contact menu
diff --git a/message/tests/behat/self_conversation.feature b/message/tests/behat/self_conversation.feature
new file mode 100644 (file)
index 0000000..14f6a54
--- /dev/null
@@ -0,0 +1,66 @@
+@core @core_message @javascript
+Feature: Self conversation
+  In order to have self-conversations
+  As a user
+  I need to be able to send messages to myself and read them
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | student1 | Student   | 1        | student1@example.com |
+    And the following config values are set as admin:
+      | messaging | 1 |
+
+  Scenario: Self conversation exists
+    Given I log in as "student1"
+    When I open messaging
+    Then "Student 1" "group_message" should exist
+    And I select "Student" conversation in messaging
+    And I should see "Personal space"
+
+  Scenario: Self conversation can be unstarred
+    Given I log in as "student1"
+    When I open messaging
+    Then "Student 1" "group_message" should exist
+    And I select "Student" conversation in messaging
+    And I open contact menu
+    And I click on "Unstar" "link" in the "Student 1" "group_message_header"
+    And I go back in "view-conversation" message drawer
+    And I open the "Starred" conversations list
+    And I should not see "Student 1" in the "favourites" "group_message_list_area"
+    And I open the "Private" conversations list
+    And I should see "Student 1" in the "messages" "group_message_list_area"
+
+  Scenario: Self conversation can be deleted
+    Given I log in as "student1"
+    When I open messaging
+    Then "Student 1" "group_message" should exist
+    And I select "Student 1" conversation in messaging
+    And I open contact menu
+    And I click on "Delete conversation" "link" in the "Student 1" "group_message_header"
+    And I should see "Delete"
+    And I click on "//button[@data-action='confirm-delete-conversation']" "xpath_element"
+    And I should not see "Delete"
+    And I go back in "view-conversation" message drawer
+    And I open the "Starred" conversations list
+    And I should not see "Student 1" in the "favourites" "group_message_list_area"
+    And I open the "Private" conversations list
+    And I should not see "Student 1" in the "messages" "group_message_list_area"
+
+  Scenario: Send a message to a self-conversation via message drawer
+    Given I log in as "student1"
+    When I open messaging
+    Then "Student 1" "group_message" should exist
+    And I select "Student 1" conversation in messaging
+    And I send "Hi!" message in the message area
+    And I should see "Hi!" in the "Student 1" "group_message_conversation"
+    And I should see "##today##j F##" in the "Student 1" "group_message_conversation"
+
+  Scenario: Send a message to a self-conversation via user profile
+    Given I log in as "student1"
+    When I follow "Profile" in the user menu
+    Then I should see "Message"
+    And I click on "Message" "icon"
+    And I send "Hi!" message in the message area
+    And I should see "Hi!" in the "Student 1" "group_message_conversation"
+    And I should see "##today##j F##" in the "Student 1" "group_message_conversation"
index d1ddec0..07b47f5 100644 (file)
@@ -29,6 +29,7 @@ Feature: Unread messages
   Scenario: Unread messages for group conversation
     Given I log in as "student1"
     When I open messaging
+    And I open the "Group" conversations list
     Then "New group" "group_message" should exist
     And I select "New group" conversation in messaging
     And I send "Hi!" message in the message area
index 5099995..14e8587 100644 (file)
@@ -2629,16 +2629,18 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // Check that we retrieved the correct non-contacts.
         // When site wide messaging is disabled, we expect to see only those users who we share a course with and whose profiles
         // are visible in that course. This excludes users like course contacts.
-        $this->assertCount(2, $noncontacts);
-        $this->assertEquals($users[6]->id, $noncontacts[0]['id']);
-        $this->assertEquals($users[7]->id, $noncontacts[1]['id']);
+        $this->assertCount(3, $noncontacts);
+        // Self-conversation first.
+        $this->assertEquals($users[1]->id, $noncontacts[0]['id']);
+        $this->assertEquals($users[6]->id, $noncontacts[1]['id']);
+        $this->assertEquals($users[7]->id, $noncontacts[2]['id']);
 
         // Verify the correct conversations were returned for the non-contacts.
-        $this->assertCount(1, $noncontacts[0]['conversations']);
-        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $noncontacts[0]['conversations'][0]['type']);
-
         $this->assertCount(1, $noncontacts[1]['conversations']);
-        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[1]['conversations'][0]['type']);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $noncontacts[1]['conversations'][0]['type']);
+
+        $this->assertCount(1, $noncontacts[2]['conversations']);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[2]['conversations'][0]['type']);
     }
 
     /**
@@ -2723,17 +2725,18 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // Check that we retrieved the correct non-contacts.
         // If site wide messaging is enabled, we expect to be able to search for any users whose profiles we can view.
         // In this case, as a student, that's the course contact for course2 and those noncontacts sharing a course with user1.
-        $this->assertCount(3, $noncontacts);
-        $this->assertEquals($users[6]->id, $noncontacts[0]['id']);
-        $this->assertEquals($users[7]->id, $noncontacts[1]['id']);
-        $this->assertEquals($users[9]->id, $noncontacts[2]['id']);
+        $this->assertCount(4, $noncontacts);
+        $this->assertEquals($users[1]->id, $noncontacts[0]['id']);
+        $this->assertEquals($users[6]->id, $noncontacts[1]['id']);
+        $this->assertEquals($users[7]->id, $noncontacts[2]['id']);
+        $this->assertEquals($users[9]->id, $noncontacts[3]['id']);
 
         // Verify the correct conversations were returned for the non-contacts.
-        $this->assertCount(1, $noncontacts[0]['conversations']);
         $this->assertCount(1, $noncontacts[1]['conversations']);
-        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $noncontacts[0]['conversations'][0]['type']);
-        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[1]['conversations'][0]['type']);
-        $this->assertCount(0, $noncontacts[2]['conversations']);
+        $this->assertCount(1, $noncontacts[2]['conversations']);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $noncontacts[1]['conversations'][0]['type']);
+        $this->assertEquals(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[2]['conversations'][0]['type']);
+        $this->assertCount(0, $noncontacts[3]['conversations']);
     }
 
     /**
@@ -2752,8 +2755,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $user2->lastname = 'Two';
         $user2 = $this->getDataGenerator()->create_user($user2);
 
-        // Create self-conversation for user1.
-        $sc1 = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]);
+        // Get self-conversation for user1.
+        $sc1 = \core_message\api::get_self_conversation($user1->id);
         testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Hi myself!');
 
         // Perform a search as user1.
@@ -2811,7 +2814,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // Enrol the first 9 users in the same course, but leave them as non-contacts.
         $this->setAdminUser();
         $course1 = $this->getDataGenerator()->create_course();
-        foreach (range(1, 9) as $i) {
+        foreach (range(1, 8) as $i) {
             $this->getDataGenerator()->enrol_user($users[$i]->id, $course1->id);
         }
 
@@ -2837,10 +2840,11 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals($users[13]->id, $contacts[2]['id']);
 
         // Check that we retrieved the correct non-contacts.
+        // Consider first conversation is self-conversation.
         $this->assertCount(3, $noncontacts);
-        $this->assertEquals($users[2]->id, $noncontacts[0]['id']);
-        $this->assertEquals($users[3]->id, $noncontacts[1]['id']);
-        $this->assertEquals($users[4]->id, $noncontacts[2]['id']);
+        $this->assertEquals($users[1]->id, $noncontacts[0]['id']);
+        $this->assertEquals($users[2]->id, $noncontacts[1]['id']);
+        $this->assertEquals($users[3]->id, $noncontacts[2]['id']);
 
         // Now, offset to get the next batch of results.
         // We expect to see 2 contacts, and 3 non-contacts.
@@ -2853,9 +2857,9 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals($users[15]->id, $contacts[1]['id']);
 
         $this->assertCount(3, $noncontacts);
-        $this->assertEquals($users[5]->id, $noncontacts[0]['id']);
-        $this->assertEquals($users[6]->id, $noncontacts[1]['id']);
-        $this->assertEquals($users[7]->id, $noncontacts[2]['id']);
+        $this->assertEquals($users[4]->id, $noncontacts[0]['id']);
+        $this->assertEquals($users[5]->id, $noncontacts[1]['id']);
+        $this->assertEquals($users[6]->id, $noncontacts[2]['id']);
 
         // Now, offset to get the next batch of results.
         // We expect to see 0 contacts, and 2 non-contacts.
@@ -2866,8 +2870,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertCount(0, $contacts);
 
         $this->assertCount(2, $noncontacts);
-        $this->assertEquals($users[8]->id, $noncontacts[0]['id']);
-        $this->assertEquals($users[9]->id, $noncontacts[1]['id']);
+        $this->assertEquals($users[7]->id, $noncontacts[0]['id']);
+        $this->assertEquals($users[8]->id, $noncontacts[1]['id']);
     }
 
     /**
@@ -2920,9 +2924,11 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Check that we retrieved the correct non-contacts.
         // Site-wide messaging is disabled, so we expect to be able to search for any users whose profile we can view.
-        $this->assertCount(2, $noncontacts);
-        $this->assertEquals($users[6]->id, $noncontacts[0]['id']);
-        $this->assertEquals($users[7]->id, $noncontacts[1]['id']);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(3, $noncontacts);
+        $this->assertEquals($users[1]->id, $noncontacts[0]['id']);
+        $this->assertEquals($users[6]->id, $noncontacts[1]['id']);
+        $this->assertEquals($users[7]->id, $noncontacts[2]['id']);
     }
 
     /**
@@ -5230,6 +5236,12 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $conversation2 = \core_message\api::get_conversation_between_users([$user1->id, $user3->id]);
         \core_message\api::set_favourite_conversation($conversation1, $user1->id);
         \core_message\api::set_favourite_conversation($conversation2, $user1->id);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(3, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
+
+        // Unset favourite self-conversation.
+        $selfconversation = \core_message\api::get_self_conversation($user1->id);
+        $result = core_message_external::unset_favourite_conversations($user1->id, [$selfconversation->id]);
         $this->assertCount(2, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
 
         // Now, using the web service, unset the favourite conversations.
@@ -5265,7 +5277,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // Favourite conversation 1 for user1. The current user ($USER) isn't checked for this action.
         $conversation1 = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]);
         \core_message\api::set_favourite_conversation($conversation1, $user1->id);
-        $this->assertCount(1, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
+        // Consider first conversations is self-conversation.
+        $this->assertCount(2, \core_message\api::get_conversations($user1->id, 0, 20, null, true));
 
         // Try to unfavourite conversation 1 for user 2, as user3.
         $conversation1 = \core_message\api::get_conversation_between_users([$user1->id, $user2->id]);
@@ -5375,16 +5388,20 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
         $conversations = $result['conversations'];
 
-        // Verify there are 6 conversations: 2 individual, 2 group with message, and 2 group without messages.
+        $selfconversation = \core_message\api::get_self_conversation($user1->id);
+
+        // Verify there are 7 conversations: 2 individual, 2 group with message, 2 group without messages
+        // and a self-conversation.
         // The conversations with the most recent messages should be listed first, followed by the most newly created
         // conversations without messages.
-        $this->assertCount(6, $conversations);
+        $this->assertCount(7, $conversations);
         $this->assertEquals($gc3->id, $conversations[0]['id']);
         $this->assertEquals($gc2->id, $conversations[1]['id']);
         $this->assertEquals($ic2->id, $conversations[2]['id']);
         $this->assertEquals($ic1->id, $conversations[3]['id']);
         $this->assertEquals($gc5->id, $conversations[4]['id']);
         $this->assertEquals($gc4->id, $conversations[5]['id']);
+        $this->assertEquals($selfconversation->id, $conversations[6]['id']);
 
         foreach ($conversations as $conv) {
             $this->assertArrayHasKey('id', $conv);
@@ -5580,6 +5597,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // The user making the request.
         $this->setUser($user1);
 
+        $selfconversation = \core_message\api::get_self_conversation($user1->id);
+
         // Delete the second user and retrieve the conversations.
         // We should have 6 still, as conversations with soft-deleted users are still returned.
         // Group conversations are also present, albeit with less members.
@@ -5587,7 +5606,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = core_message_external::get_conversations($user1->id);
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
         $conversations = $result['conversations'];
-        $this->assertCount(6, $conversations);
+        $this->assertCount(7, $conversations);
         $this->assertEquals($gc3->id, $conversations[0]['id']);
         $this->assertcount(1, $conversations[0]['members']);
         $this->assertEquals($gc2->id, $conversations[1]['id']);
@@ -5596,6 +5615,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals($ic1->id, $conversations[3]['id']);
         $this->assertEquals($gc5->id, $conversations[4]['id']);
         $this->assertEquals($gc4->id, $conversations[5]['id']);
+        $this->assertEquals($selfconversation->id, $conversations[6]['id']);
 
         // Delete a user from a group conversation where that user had sent the most recent message.
         // This user will still be present in the members array, as will the message in the messages array.
@@ -5603,7 +5623,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = core_message_external::get_conversations($user1->id);
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
         $conversations = $result['conversations'];
-        $this->assertCount(6, $conversations);
+        $this->assertCount(7, $conversations);
         $this->assertEquals($gc2->id, $conversations[1]['id']);
         $this->assertcount(1, $conversations[1]['members']);
         $this->assertEquals($user4->id, $conversations[1]['members'][0]['id']);
@@ -5611,13 +5631,13 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals($user4->id, $conversations[1]['messages'][0]['useridfrom']);
 
         // Delete the third user and retrieve the conversations.
-        // We should have 6 still, as conversations with soft-deleted users are still returned.
+        // We should have 7 still (including self-conversation), as conversations with soft-deleted users are still returned.
         // Group conversations are also present, albeit with less members.
         delete_user($user3);
         $result = core_message_external::get_conversations($user1->id);
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
         $conversations = $result['conversations'];
-        $this->assertCount(6, $conversations);
+        $this->assertCount(7, $conversations);
         $this->assertEquals($gc3->id, $conversations[0]['id']);
         $this->assertcount(1, $conversations[0]['members']);
         $this->assertEquals($gc2->id, $conversations[1]['id']);
@@ -5626,6 +5646,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals($ic1->id, $conversations[3]['id']);
         $this->assertEquals($gc5->id, $conversations[4]['id']);
         $this->assertEquals($gc4->id, $conversations[5]['id']);
+        $this->assertEquals($selfconversation->id, $conversations[6]['id']);
     }
 
     /**
@@ -5696,6 +5717,10 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // The user making the request.
         $this->setUser($user1);
 
+        // Unset favourite self-conversation.
+        $selfconversation = \core_message\api::get_self_conversation($user1->id);
+        \core_message\api::unset_favourite_conversation($selfconversation->id, $user1->id);
+
         // Try to get ONLY favourite conversations, when no favourites exist.
         $result = core_message_external::get_conversations($user1->id, 0, 20, null, true);
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
@@ -5706,7 +5731,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = core_message_external::get_conversations($user1->id, 0, 20, null, false);
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
         $conversations = $result['conversations'];
-        $this->assertCount(6, $conversations);
+        // Consider first conversations is self-conversation.
+        $this->assertCount(7, $conversations);
 
         // Mark a few conversations as favourites.
         \core_message\api::set_favourite_conversation($ic1->id, $user1->id);
@@ -5717,7 +5743,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = core_message_external::get_conversations($user1->id);
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
         $conversations = $result['conversations'];
-        $this->assertCount(6, $conversations);
+        $this->assertCount(7, $conversations);
         foreach ($conversations as $conv) {
             if (in_array($conv['id'], [$ic1->id, $gc2->id, $gc5->id])) {
                 $this->assertTrue($conv['isfavourite']);
@@ -5745,7 +5771,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = core_message_external::get_conversations($user1->id, 0, 20, null, false);
         $result = external_api::clean_returnvalue(core_message_external::get_conversations_returns(), $result);
         $conversations = $result['conversations'];
-        $this->assertCount(3, $conversations);
+        $this->assertCount(4, $conversations);
         foreach ($conversations as $conv) {
             $this->assertFalse($conv['isfavourite']);
         }
@@ -5897,6 +5923,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
             return $first['id'] > $second['id'];
         });
 
+        $selfconversation = array_shift($conversations);
         $conv1 = array_shift($conversations);
         $conv2 = array_shift($conversations);
         $conv3 = array_shift($conversations);
@@ -6627,7 +6654,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user5],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6644,7 +6671,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user4],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6661,7 +6688,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6678,7 +6705,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user2],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6695,7 +6722,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user4],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6712,7 +6739,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [0],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6729,7 +6756,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [3],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6746,7 +6773,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [0, 1, 2],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6763,7 +6790,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [3, 4, 5],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6780,7 +6807,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [0, 1, 2],
                 'arguments' => [$user2],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6797,7 +6824,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [3, 4, 5],
                 'arguments' => [$user3],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 2,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6814,7 +6841,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [6, 7],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6831,7 +6858,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => $user1,
                 'deletemessages' => [6, 7, 8, 9],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6848,7 +6875,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6865,7 +6892,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user1],
-                'expectedcounts' => ['favourites' => 1, 'types' => [
+                'expectedcounts' => ['favourites' => 2, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 1,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6882,7 +6909,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user6],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
@@ -6899,7 +6926,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
                 'deletemessagesuser' => null,
                 'deletemessages' => [],
                 'arguments' => [$user7],
-                'expectedcounts' => ['favourites' => 0, 'types' => [
+                'expectedcounts' => ['favourites' => 1, 'types' => [
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP => 0,
                     \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF => 0
index 28a7dcf..d43050b 100644 (file)
@@ -211,6 +211,12 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->resetAfterTest();
 
         $user = $this->getDataGenerator()->create_user();
+
+        $contextlist = provider::get_contexts_for_userid($user->id);
+        $this->assertCount(1, $contextlist);
+
+        $this->remove_user_self_conversation($user->id);
+
         $contextlist = provider::get_contexts_for_userid($user->id);
         $this->assertEmpty($contextlist);
     }
@@ -225,6 +231,11 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user2 = $this->getDataGenerator()->create_user();
         $user3 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+
         // Test nothing is found before group conversations is created or message is sent.
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(0, $contextlist);
@@ -267,6 +278,12 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user3 = $this->getDataGenerator()->create_user();
         $user4 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+
         // Test nothing is found before group conversations is created or message is sent.
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(0, $contextlist);
@@ -359,6 +376,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1 = $this->getDataGenerator()->create_user();
         $user2 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before notification is created.
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(0, $contextlist);
@@ -393,6 +414,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1 = $this->getDataGenerator()->create_user();
         $user2 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before contact is created.
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(0, $contextlist);
@@ -427,6 +452,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1 = $this->getDataGenerator()->create_user();
         $user2 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before request is created.
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(0, $contextlist);
@@ -461,6 +490,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1 = $this->getDataGenerator()->create_user();
         $user2 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before user is blocked.
         $contextlist = provider::get_contexts_for_userid($user1->id);
         $this->assertCount(0, $contextlist);
@@ -498,6 +531,12 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user3 = $this->getDataGenerator()->create_user();
         $user4 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+
         \core_message\api::add_contact($user1->id, $user2->id);
         \core_message\api::add_contact($user1->id, $user3->id);
         \core_message\api::add_contact($user1->id, $user4->id);
@@ -535,6 +574,12 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user3 = $this->getDataGenerator()->create_user();
         $user4 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+
         \core_message\api::create_contact_request($user1->id, $user2->id);
         \core_message\api::create_contact_request($user3->id, $user1->id);
         \core_message\api::create_contact_request($user1->id, $user4->id);
@@ -574,6 +619,12 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user3 = $this->getDataGenerator()->create_user();
         $user4 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+
         \core_message\api::block_user($user1->id, $user2->id);
         \core_message\api::block_user($user1->id, $user3->id);
         \core_message\api::block_user($user1->id, $user4->id);
@@ -611,6 +662,11 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user2 = $this->getDataGenerator()->create_user();
         $user3 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+
         $now = time();
 
         // Send messages from user 1 to user 2.
@@ -718,6 +774,11 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user3 = $this->getDataGenerator()->create_user();
         $user1context = \context_user::instance($user1->id);
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+
         $course1 = $this->getDataGenerator()->create_course();
         $course2 = $this->getDataGenerator()->create_course();
         $coursecontext1 = \context_course::instance($course1->id);
@@ -814,6 +875,11 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user2 = $this->getDataGenerator()->create_user();
         $user3 = $this->getDataGenerator()->create_user();
 
+        // Remove user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+
         $now = time();
         $timeread = $now - DAYSECS;
 
@@ -906,8 +972,8 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be 2 user actions - one for reading the message, one for deleting.
         $this->assertEquals(2, $DB->count_records('message_user_actions'));
 
-        // There should be 4 conversation members.
-        $this->assertEquals(4, $DB->count_records('message_conversation_members'));
+        // There should be 4 conversation members + 5 self-conversations.
+        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
 
         // There should be 5 notifications (3 from create_notification and 2 from create_contact_request).
         $this->assertEquals(5, $DB->count_records('notifications'));
@@ -943,8 +1009,8 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // And it is not for user1.
         $this->assertEquals(0, $DB->count_records('message_user_actions', ['userid' => $user1->id]));
 
-        // Confirm there are only 3 conversation members left.
-        $this->assertEquals(3, $DB->count_records('message_conversation_members'));
+        // Confirm there are only 3 conversation members left + 4 self-conversations.
+        $this->assertEquals(7, $DB->count_records('message_conversation_members'));
         // And user1 is not in any conversation.
         $this->assertEquals(0, $DB->count_records('message_conversation_members', ['userid' => $user1->id]));
 
@@ -953,6 +1019,18 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // And it is not related to user1.
         $this->assertEquals(0,
                 $DB->count_records_select('notifications', 'useridfrom = ? OR useridto = ? ', [$user1->id, $user1->id]));
+
+        // Delete user self-conversations.
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+        $this->remove_user_self_conversation($user5->id);
+
+        // Confirm there are only 3 conversation members left.
+        $this->assertEquals(3, $DB->count_records('message_conversation_members'));
+        // And user1 is not in any conversation.
+        $this->assertEquals(0, $DB->count_records('message_conversation_members', ['userid' => $user1->id]));
+
     }
 
     /**
@@ -1013,8 +1091,8 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be two user actions - one for reading the message, one for deleting.
         $this->assertEquals(2, $DB->count_records('message_user_actions'));
 
-        // There should be two conversation members.
-        $this->assertEquals(2, $DB->count_records('message_conversation_members'));
+        // There should be two conversation members + 6 self-conversations.
+        $this->assertEquals(8, $DB->count_records('message_conversation_members'));
 
         // There should be 5 notifications (3 from create_notification and 2 from create_contact_request).
         $this->assertEquals(5, $DB->count_records('notifications'));
@@ -1054,7 +1132,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
 
         $this->assertCount(0, $muas);
 
-        $this->assertCount(1, $mcms);
+        $this->assertCount(6, $mcms);
         $mcm = reset($mcms);
         $this->assertEquals($user2->id, $mcm->userid);
 
@@ -1074,6 +1152,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
 
         $user = $this->getDataGenerator()->create_user();
         $usercontext = context_user::instance($user->id);
+        $this->remove_user_self_conversation($user->id);
 
         $userlist = new \core_privacy\local\request\userlist($usercontext, 'core_message');
         \core_message\privacy\provider::get_users_in_context($userlist);
@@ -1093,6 +1172,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1context = context_user::instance($user1->id);
         $user2context = context_user::instance($user2->id);
 
+        // Delete user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before message is sent.
         $userlist = new \core_privacy\local\request\userlist($user1context, 'core_message');
         \core_message\privacy\provider::get_users_in_context($userlist);
@@ -1130,6 +1213,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1context = context_user::instance($user1->id);
         $user2context = context_user::instance($user2->id);
 
+        // Delete user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before notification is created.
         $userlist = new \core_privacy\local\request\userlist($user1context, 'core_message');
         \core_message\privacy\provider::get_users_in_context($userlist);
@@ -1167,6 +1254,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1context = context_user::instance($user1->id);
         $user2context = context_user::instance($user2->id);
 
+        // Delete user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before contact is created.
         $userlist = new \core_privacy\local\request\userlist($user1context, 'core_message');
         \core_message\privacy\provider::get_users_in_context($userlist);
@@ -1204,6 +1295,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1context = context_user::instance($user1->id);
         $user2context = context_user::instance($user2->id);
 
+        // Delete user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before request is created.
         $userlist = new \core_privacy\local\request\userlist($user1context, 'core_message');
         \core_message\privacy\provider::get_users_in_context($userlist);
@@ -1241,6 +1336,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user1context = context_user::instance($user1->id);
         $user2context = context_user::instance($user2->id);
 
+        // Delete user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+
         // Test nothing is found before user is blocked.
         $userlist = new \core_privacy\local\request\userlist($user1context, 'core_message');
         \core_message\privacy\provider::get_users_in_context($userlist);
@@ -1324,8 +1423,8 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be two user actions - one for reading the message, one for deleting.
         $this->assertEquals(2, $DB->count_records('message_user_actions'));
 
-        // There should be two conversation members.
-        $this->assertEquals(2, $DB->count_records('message_conversation_members'));
+        // There should be two conversation members + 6 self-conversations.
+        $this->assertEquals(8, $DB->count_records('message_conversation_members'));
 
         // There should be three notifications + two for the contact requests.
         $this->assertEquals(5, $DB->count_records('notifications'));
@@ -1367,7 +1466,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
 
         $this->assertCount(0, $muas);
 
-        $this->assertCount(1, $mcms);
+        $this->assertCount(6, $mcms);
         $mcm = reset($mcms);
         $this->assertEquals($user2->id, $mcm->userid);
 
@@ -1393,6 +1492,12 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $user3 = $this->getDataGenerator()->create_user();
         $user4 = $this->getDataGenerator()->create_user();
 
+        // Delete user self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+
         // Test nothing is found before group conversations is created or message is sent.
         $contextlist = new contextlist();
         provider::add_contexts_for_conversations($contextlist, $user1->id, $component, $itemtype);
@@ -1871,17 +1976,17 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be 3 muted conversations.
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
 
-        // There should be 4 conversations - 2 individual + 2 group.
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        // There should be 4 conversations - 2 individual + 2 group + 5 self-conversations.
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
 
-        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group.
-        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group + 5 self-conversations.
+        $this->assertEquals(14 , $DB->count_records('message_conversation_members'));
 
         // There should be 5 notifications (3 from create_notification and 2 from create_contact_request).
         $this->assertEquals(5, $DB->count_records('notifications'));
 
-        // There should be 3 favourite conversations.
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        // There should be 3 favourite conversations + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete conversations for all users in course1.
         provider::delete_conversations_for_all_users($coursecontext1, $component, $itemtype);
@@ -1918,16 +2023,16 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         }, $useractions);
         $this->assertNotContains($gm3, $useractions);
 
-        // There should be 3 conversations - 2 individual + 1 group (course2).
-        $this->assertEquals(3, $DB->count_records('message_conversations'));
+        // There should be 3 conversations - 2 individual + 1 group (course2) + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('message_conversations'));
         $conversations = $DB->get_records('message_conversations');
         $this->assertArrayNotHasKey($conversation1->id, $conversations);
 
-        // There should be 6 conversation members - (2 + 2) individual + 2 group.
-        $this->assertEquals(6, $DB->count_records('message_conversation_members'));
+        // There should be 6 conversation members - (2 + 2) individual + 2 group + 5 self-conversations.
+        $this->assertEquals(11, $DB->count_records('message_conversation_members'));
 
-        // There should be 1 favourite conversation - the individual one.
-        $this->assertEquals(1, $DB->count_records('favourite'));
+        // There should be 1 favourite conversation - the individual one + 5 self-conversations.
+        $this->assertEquals(6, $DB->count_records('favourite'));
     }
 
     /**
@@ -2055,17 +2160,17 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be 3 muted conversations.
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
 
-        // There should be 4 conversations - 2 individual + 2 group.
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        // There should be 4 conversations - 2 individual + 2 group + 5 self-conversations.
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
 
-        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group.
-        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group + 5 self-conversations.
+        $this->assertEquals(14, $DB->count_records('message_conversation_members'));
 
         // There should be 5 notifications (3 from create_notification and 2 from create_contact_request).
         $this->assertEquals(5, $DB->count_records('notifications'));
 
-        // There should be 3 favourite conversations.
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        // There should be 3 favourite conversations + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete group conversations for all users in system context.
         provider::delete_conversations_for_all_users($systemcontext, $component, $itemtype);
@@ -2077,10 +2182,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertEquals(8, $DB->count_records('messages'));
         $this->assertEquals(4, $DB->count_records('message_user_actions'));
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
-        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
+        $this->assertEquals(14, $DB->count_records('message_conversation_members'));
         $this->assertEquals(5, $DB->count_records('notifications'));
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete individual conversations for all users in system context.
         provider::delete_conversations_for_all_users($systemcontext, '', '');
@@ -2092,10 +2197,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertEquals(8, $DB->count_records('messages'));
         $this->assertEquals(4, $DB->count_records('message_user_actions'));
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
-        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
+        $this->assertEquals(14, $DB->count_records('message_conversation_members'));
         $this->assertEquals(5, $DB->count_records('notifications'));
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        $this->assertEquals(8, $DB->count_records('favourite'));
     }
 
     /**
@@ -2224,17 +2329,17 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be 3 muted conversations.
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
 
-        // There should be 4 conversations - 2 individual + 2 group.
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        // There should be 4 conversations - 2 individual + 2 group + 5 self-conversations.
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
 
-        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group.
-        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        // There should be 9 conversation members - (2 + 2) individual + (3 + 2) group + 5 self-conversations.
+        $this->assertEquals(14, $DB->count_records('message_conversation_members'));
 
         // There should be 5 notifications (3 from create_notification and 2 from create_contact_request).
         $this->assertEquals(5, $DB->count_records('notifications'));
 
-        // There should be 3 favourite conversations.
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        // There should be 3 favourite conversations + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete group conversations for all users in user context.
         provider::delete_conversations_for_all_users($user1context, $component, $itemtype);
@@ -2246,10 +2351,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertEquals(8, $DB->count_records('messages'));
         $this->assertEquals(4, $DB->count_records('message_user_actions'));
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
-        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
+        $this->assertEquals(14, $DB->count_records('message_conversation_members'));
         $this->assertEquals(5, $DB->count_records('notifications'));
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete individual conversations for all users in user context.
         provider::delete_conversations_for_all_users($user1context, '', '');
@@ -2261,10 +2366,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertEquals(8, $DB->count_records('messages'));
         $this->assertEquals(4, $DB->count_records('message_user_actions'));
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
-        $this->assertEquals(9, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
+        $this->assertEquals(14, $DB->count_records('message_conversation_members'));
         $this->assertEquals(5, $DB->count_records('notifications'));
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        $this->assertEquals(8, $DB->count_records('favourite'));
     }
 
     /**
@@ -2384,19 +2489,19 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be 3 users muting a conversation.
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
 
-        // There should be 3 conversations - 2 private + 1 group.
-        $this->assertEquals(3, $DB->count_records('message_conversations'));
+        // There should be 3 conversations - 2 private + 1 group + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('message_conversations'));
 
-        // There should be 7 conversation members - 2 + 2 private conversations + 3 group conversation.
-        $this->assertEquals(7, $DB->count_records('message_conversation_members'));
+        // There should be 7 conversation members - 2 + 2 private conversations + 3 group conversation + 5 self-conversations.
+        $this->assertEquals(12, $DB->count_records('message_conversation_members'));
         $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
         $members = array_map(function($member) {
                 return $member->userid;
         }, $members);
         $this->assertContains($user1->id, $members);
 
-        // There should be three favourite conversations.
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        // There should be three favourite conversations + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete group conversations for user1 in course1 and course2.
         $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist($user1, 'core_message',
@@ -2434,17 +2539,24 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         }, $useractions);
         $this->assertNotContains($gm3, $useractions);
 
-        // There should be still 3 conversations - 2 private + 1 group.
-        $this->assertEquals(3, $DB->count_records('message_conversations'));
+        // There should be still 3 conversations - 2 private + 1 group + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('message_conversations'));
 
-        // There should be 6 conversation members - 2 + 2 private conversations + 2 group conversation.
-        $this->assertEquals(6, $DB->count_records('message_conversation_members'));
+        // There should be 6 conversation members - 2 + 2 private conversations + 2 group conversation + 5 self-conversations.
+        $this->assertEquals(11, $DB->count_records('message_conversation_members'));
         $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
         $members = array_map(function($member) {
                 return $member->userid;
         }, $members);
         $this->assertNotContains($user1->id, $members);
 
+        // Unset favourite self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+        $this->remove_user_self_conversation($user5->id);
+
         // There should be 2 favourite conversations - 2 group.
         $this->assertEquals(2, $DB->count_records('favourite'));
         $favourites = $DB->get_records('favourite');
@@ -2578,11 +2690,11 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be 3 muted conversation.
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
 
-        // There should be 3 conversations - 2 private + 2 group.
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        // There should be 3 conversations - 2 private + 2 group + 5 self-conversations.
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
 
-        // There should be 8 conversation members - (2 + 2) private + 4 group.
-        $this->assertEquals(8, $DB->count_records('message_conversation_members'));
+        // There should be 8 conversation members - (2 + 2) private + 4 group + 5 self-conversations.
+        $this->assertEquals(13, $DB->count_records('message_conversation_members'));
         $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
         $members = array_map(function($member) {
                 return $member->userid;
@@ -2590,8 +2702,8 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertContains($user1->id, $members);
         $this->assertContains($user4->id, $members);
 
-        // There should be 3 favourite conversations.
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        // There should be 3 favourite conversations + 5 self-conversations.
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete group conversations for user1 and user2 in course2 context.
         $approveduserlist = new \core_privacy\local\request\approved_userlist($coursecontext2, 'core_message',
@@ -2606,9 +2718,9 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertEquals(6, $DB->count_records('messages'));
         $this->assertEquals(4, $DB->count_records('message_user_actions'));
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
-        $this->assertEquals(8, $DB->count_records('message_conversation_members'));
-        $this->assertEquals(3, $DB->count_records('favourite'));
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
+        $this->assertEquals(13, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(8, $DB->count_records('favourite'));
 
         // Delete group conversations for user4 in course1 context.
         $approveduserlist = new \core_privacy\local\request\approved_userlist($coursecontext1, 'core_message',
@@ -2623,10 +2735,10 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertEquals(6, $DB->count_records('messages'));
         $this->assertEquals(4, $DB->count_records('message_user_actions'));
         $this->assertEquals(3, $DB->count_records('message_conversation_actions'));
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
-        $this->assertEquals(3, $DB->count_records('favourite'));
-        // There should be 7 conversation members - (2 + 2) private + 3 group.
-        $this->assertEquals(7, $DB->count_records('message_conversation_members'));
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
+        $this->assertEquals(8, $DB->count_records('favourite'));
+        // There should be 7 conversation members - (2 + 2) private + 3 group + 5 self-conversations.
+        $this->assertEquals(12, $DB->count_records('message_conversation_members'));
 
         // Delete group conversations for user1 and user2 in course1 context.
         $approveduserlist = new \core_privacy\local\request\approved_userlist($coursecontext1, 'core_message',
@@ -2664,11 +2776,11 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         // There should be 1 muted conversation.
         $this->assertEquals(1, $DB->count_records('message_conversation_actions'));
 
-        // There should be still 4 conversations - 2 private + 2 group.
-        $this->assertEquals(4, $DB->count_records('message_conversations'));
+        // There should be still 4 conversations - 2 private + 2 group + 5 self-conversations.
+        $this->assertEquals(9, $DB->count_records('message_conversations'));
 
-        // There should be 5 conversation members - (2 + 2) private + 1 group.
-        $this->assertEquals(5, $DB->count_records('message_conversation_members'));
+        // There should be 5 conversation members - (2 + 2) private + 1 group + 5 self-conversations.
+        $this->assertEquals(10, $DB->count_records('message_conversation_members'));
         $members = $DB->get_records('message_conversation_members', ['conversationid' => $conversation1->id]);
         $members = array_map(function($member) {
                 return $member->userid;
@@ -2676,6 +2788,13 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
         $this->assertNotContains($user1->id, $members);
         $this->assertNotContains($user2->id, $members);
 
+        // Unset favourite self-conversations.
+        $this->remove_user_self_conversation($user1->id);
+        $this->remove_user_self_conversation($user2->id);
+        $this->remove_user_self_conversation($user3->id);
+        $this->remove_user_self_conversation($user4->id);
+        $this->remove_user_self_conversation($user5->id);
+
         // There should be 2 favourite conversations - user1 individual + user3 group.
         $this->assertEquals(2, $DB->count_records('favourite'));
         $favourites = $DB->get_records('favourite');
@@ -2794,4 +2913,17 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
     protected static function sort_contacts($a, $b) {
         return $a->contact > $b->contact;
     }
+
+    /**
+     * Function to unset favourite and delete all conversation data for a user's self-conversation.
+     *
+     * @param int $userid The user id
+     * @return  void
+     * @throws moodle_exception
+     */
+    protected static function remove_user_self_conversation(int $userid) {
+        $selfconversation = \core_message\api::get_self_conversation($userid);
+        \core_message\api::unset_favourite_conversation($selfconversation->id, $userid);
+        \core_message\api::delete_all_conversation_data($selfconversation->id);
+    }
 }
index 63b526e..8e52442 100644 (file)
Binary files a/mod/quiz/amd/build/modal_quiz_question_bank.min.js and b/mod/quiz/amd/build/modal_quiz_question_bank.min.js differ
index 6180645..f7f3535 100644 (file)
@@ -47,6 +47,7 @@ function(
         PREVIEW_CONTAINER: 'td.previewaction',
         SEARCH_OPTIONS: '#advancedsearch',
         DISPLAY_OPTIONS: '#displayoptions',
+        ADD_QUESTIONS_FORM: 'form[action="edit.php"]',
     };
 
     /**
@@ -258,6 +259,18 @@ function(
         // Set up the event handlers for all of the display options.
         this.registerDisplayOptionListeners();
 
+        this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, function(e) {
+            // If the user clicks on the "Add selected questions to the quiz" button to add some questions to the page
+            // then we need to intercept the submit in order to include the correct "add on page id" before the form is
+            // submitted.
+            var formElement = $(e.currentTarget);
+
+            $('<input />').attr('type', 'hidden')
+                .attr('name', "addonpage")
+                .attr('value', this.getAddOnPageId())
+                .appendTo(formElement);
+        }.bind(this));
+
         this.getModal().on('click', SELECTORS.ANCHOR, function(e) {
             var anchorElement = $(e.currentTarget);
 
index 7cd00fc..56e983d 100644 (file)
@@ -80,3 +80,26 @@ Feature: Adding questions to a quiz from the question bank
     Then I should see "question 21 name" in the "categoryquestions" "table"
     And I should see "question 22 name" in the "categoryquestions" "table"
     And I should not see "question 01 name" in the "categoryquestions" "table"
+
+  Scenario: Questions are added in the right place with multiple sections
+    Given the following "questions" exist:
+      | questioncategory | qtype | name             | questiontext     |
+      | Test questions   | essay | question 03 name | question 03 text |
+    And quiz "Quiz 1" contains the following questions:
+      | question         | page |
+      | question 01 name | 1    |
+      | question 02 name | 2    |
+    And quiz "Quiz 1" contains the following sections:
+      | heading   | firstslot | shuffle |
+      | Section 1 | 1         | 0       |
+      | Section 2 | 2         | 0       |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "Quiz 1"
+    When I navigate to "Edit quiz" in current page administration
+    And I open the "Page 1" add to quiz menu
+    And I follow "from question bank"
+    And I set the field with xpath "//tr[contains(normalize-space(.), 'question 03 name')]//input[@type='checkbox']" to "1"
+    And I click on "Add selected questions to the quiz" "button"
+    Then I should see "question 03 name" on quiz page "1"
+    And I should see "question 01 name" before "question 03 name" on the edit quiz page
index 6dfc5db..4e2ea87 100644 (file)
@@ -106,6 +106,9 @@ abstract class question_definition {
     /** @var boolean whethre this question has been deleted/hidden in the question bank. */
     public $hidden = 0;
 
+    /** @var string question idnumber. */
+    public $idnumber;
+
     /** @var integer timestamp when this question was created. */
     public $timecreated;
 
index 24622d7..2671f7d 100644 (file)
@@ -19,8 +19,7 @@
 
     Moodle template competency breakdown report.
 
-    Example context (json):
-    { "pushratingstouserplans": false, "usercompetencies": []}
+    This template includes ajax functionality, so it cannot be shown in the template library.
 }}
 <div data-region="competency-breakdown-report" data-courseid="{{course.id}}" data-userid="{{user.id}}" data-moduleid="{{moduleid}}">
 <div data-region="configurecoursecompetencies">