Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Unit tests for the core_rating implementation of the Privacy API.
  19   *
  20   * @package    core_rating
  21   * @category   test
  22   * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot . '/rating/lib.php');
  30  
  31  use \core_rating\privacy\provider;
  32  use \core_privacy\local\request\writer;
  33  
  34  /**
  35   * Unit tests for the core_rating implementation of the Privacy API.
  36   *
  37   * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class core_rating_privacy_testcase extends \core_privacy\tests\provider_testcase {
  41  
  42      /**
  43       * Rate something as a user.
  44       *
  45       * @param   int         $userid
  46       * @param   string      $component
  47       * @param   string      $ratingarea
  48       * @param   int         $itemid
  49       * @param   \context    $context
  50       * @param   string      $score
  51       */
  52      protected function rate_as_user($userid, $component, $ratingarea, $itemid, $context, $score) {
  53          // Rate the courses.
  54          $rm = new rating_manager();
  55          $ratingoptions = (object) [
  56              'component'   => $component,
  57              'ratingarea'  => $ratingarea,
  58              'scaleid'     => 100,
  59          ];
  60  
  61          // Rate all courses as u1, and the course category too..
  62          $ratingoptions->itemid = $itemid;
  63          $ratingoptions->userid = $userid;
  64          $ratingoptions->context = $context;
  65          $rating = new \rating($ratingoptions);
  66          $rating->update_rating($score);
  67      }
  68  
  69      /**
  70       * Ensure that the get_sql_join function returns valid SQL which returns the correct list of rated itemids.
  71       */
  72      public function test_get_sql_join() {
  73          global $DB;
  74          $this->resetAfterTest();
  75  
  76          $course1 = $this->getDataGenerator()->create_course();
  77          $course2 = $this->getDataGenerator()->create_course();
  78          $course3 = $this->getDataGenerator()->create_course();
  79  
  80          $u1 = $this->getDataGenerator()->create_user();
  81          $u2 = $this->getDataGenerator()->create_user();
  82          $u3 = $this->getDataGenerator()->create_user();
  83  
  84          // Rate the courses.
  85          $rm = new rating_manager();
  86          $ratingoptions = (object) [
  87              'component'   => 'core_course',
  88              'ratingarea'  => 'course',
  89              'scaleid'     => 100,
  90          ];
  91  
  92          // Rate all courses as u1, and something else in the same context.
  93          $this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
  94          $this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
  95          $this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
  96          $this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
  97  
  98          // Rate course2 as u2, and something else in a different context/component..
  99          $this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
 100          $this->rate_as_user($u2->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 10);
 101  
 102          // Return any course which the u1 has rated.
 103          // u1 rated all three courses.
 104          $ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u1->id);
 105          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 106          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 107  
 108          $this->assertCount(3, $courses);
 109          $this->assertTrue(isset($courses[$course1->id]));
 110          $this->assertTrue(isset($courses[$course2->id]));
 111          $this->assertTrue(isset($courses[$course3->id]));
 112  
 113          // User u1 rated files in course 3 only.
 114          $ratingquery = provider::get_sql_join('r', 'core_course', 'files', 'c.id', $u1->id);
 115          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 116          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 117  
 118          $this->assertCount(1, $courses);
 119          $this->assertFalse(isset($courses[$course1->id]));
 120          $this->assertFalse(isset($courses[$course2->id]));
 121          $this->assertTrue(isset($courses[$course3->id]));
 122  
 123          // Return any course which the u2 has rated.
 124          // User u2 rated only course 2.
 125          $ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u2->id);
 126          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 127          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 128  
 129          $this->assertCount(1, $courses);
 130          $this->assertFalse(isset($courses[$course1->id]));
 131          $this->assertTrue(isset($courses[$course2->id]));
 132          $this->assertFalse(isset($courses[$course3->id]));
 133  
 134          // User u2 rated u3.
 135          $ratingquery = provider::get_sql_join('r', 'user', 'user', 'u.id', $u2->id);
 136          $sql = "SELECT u.id FROM {user} u {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 137          $users = $DB->get_records_sql($sql, $ratingquery->params);
 138  
 139          $this->assertCount(1, $users);
 140          $this->assertFalse(isset($users[$u1->id]));
 141          $this->assertFalse(isset($users[$u2->id]));
 142          $this->assertTrue(isset($users[$u3->id]));
 143  
 144          // Return any course which the u3 has rated.
 145          // User u3 did not rate anything.
 146          $ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u3->id);
 147          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 148          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 149  
 150          $this->assertCount(0, $courses);
 151          $this->assertFalse(isset($courses[$course1->id]));
 152          $this->assertFalse(isset($courses[$course2->id]));
 153          $this->assertFalse(isset($courses[$course3->id]));
 154      }
 155  
 156      /**
 157       * Ensure that the get_sql_join function returns valid SQL which returns the correct list of rated itemids.
 158       * This makes use of the optional inner join argument.
 159       */
 160      public function test_get_sql_join_inner() {
 161          global $DB;
 162          $this->resetAfterTest();
 163  
 164          $course1 = $this->getDataGenerator()->create_course();
 165          $course2 = $this->getDataGenerator()->create_course();
 166          $course3 = $this->getDataGenerator()->create_course();
 167  
 168          $u1 = $this->getDataGenerator()->create_user();
 169          $u2 = $this->getDataGenerator()->create_user();
 170          $u3 = $this->getDataGenerator()->create_user();
 171  
 172          // Rate the courses.
 173          $rm = new rating_manager();
 174          $ratingoptions = (object) [
 175              'component'   => 'core_course',
 176              'ratingarea'  => 'course',
 177              'scaleid'     => 100,
 178          ];
 179  
 180          // Rate all courses as u1, and something else in the same context.
 181          $this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
 182          $this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
 183          $this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
 184          $this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
 185  
 186          // Rate course2 as u2, and something else in a different context/component..
 187          $this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
 188          $this->rate_as_user($u2->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 10);
 189  
 190          // Return any course which the u1 has rated.
 191          // u1 rated all three courses.
 192          $ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u1->id, true);
 193          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 194          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 195  
 196          $this->assertCount(3, $courses);
 197          $this->assertTrue(isset($courses[$course1->id]));
 198          $this->assertTrue(isset($courses[$course2->id]));
 199          $this->assertTrue(isset($courses[$course3->id]));
 200  
 201          // User u1 rated files in course 3 only.
 202          $ratingquery = provider::get_sql_join('r', 'core_course', 'files', 'c.id', $u1->id, true);
 203          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 204          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 205  
 206          $this->assertCount(1, $courses);
 207          $this->assertFalse(isset($courses[$course1->id]));
 208          $this->assertFalse(isset($courses[$course2->id]));
 209          $this->assertTrue(isset($courses[$course3->id]));
 210  
 211          // Return any course which the u2 has rated.
 212          // User u2 rated only course 2.
 213          $ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u2->id, true);
 214          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 215          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 216  
 217          $this->assertCount(1, $courses);
 218          $this->assertFalse(isset($courses[$course1->id]));
 219          $this->assertTrue(isset($courses[$course2->id]));
 220          $this->assertFalse(isset($courses[$course3->id]));
 221  
 222          // User u2 rated u3.
 223          $ratingquery = provider::get_sql_join('r', 'user', 'user', 'u.id', $u2->id, true);
 224          $sql = "SELECT u.id FROM {user} u {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 225          $users = $DB->get_records_sql($sql, $ratingquery->params);
 226  
 227          $this->assertCount(1, $users);
 228          $this->assertFalse(isset($users[$u1->id]));
 229          $this->assertFalse(isset($users[$u2->id]));
 230          $this->assertTrue(isset($users[$u3->id]));
 231  
 232          // Return any course which the u3 has rated.
 233          // User u3 did not rate anything.
 234          $ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u3->id, true);
 235          $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
 236          $courses = $DB->get_records_sql($sql, $ratingquery->params);
 237  
 238          $this->assertCount(0, $courses);
 239          $this->assertFalse(isset($courses[$course1->id]));
 240          $this->assertFalse(isset($courses[$course2->id]));
 241          $this->assertFalse(isset($courses[$course3->id]));
 242      }
 243  
 244      /**
 245       * Ensure that export_area_ratings exports all ratings that a user has made, and all ratings for a users own content.
 246       */
 247      public function test_export_area_ratings() {
 248          global $DB;
 249          $this->resetAfterTest();
 250  
 251          $course1 = $this->getDataGenerator()->create_course();
 252          $course2 = $this->getDataGenerator()->create_course();
 253          $course3 = $this->getDataGenerator()->create_course();
 254  
 255          $u1 = $this->getDataGenerator()->create_user();
 256          $u2 = $this->getDataGenerator()->create_user();
 257          $u3 = $this->getDataGenerator()->create_user();
 258  
 259          // Rate the courses.
 260          $rm = new rating_manager();
 261          $ratingoptions = (object) [
 262              'component'   => 'core_course',
 263              'ratingarea'  => 'course',
 264              'scaleid'     => 100,
 265          ];
 266  
 267          // Rate all courses as u1, and something else in the same context.
 268          $this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
 269          $this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
 270          $this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
 271          $this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
 272          $this->rate_as_user($u1->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 10);
 273  
 274          // Rate course2 as u2, and something else in a different context/component..
 275          $this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
 276          $this->rate_as_user($u2->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 20);
 277  
 278          // Test exports.
 279          // User 1 rated all three courses, and the core_course, and user 3.
 280          // User 1::course1 is stored in [] subcontext.
 281          $context = \context_course::instance($course1->id);
 282          $subcontext = [];
 283          provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'course', $course1->id, true);
 284  
 285          $writer = writer::with_context($context);
 286          $this->assertTrue($writer->has_any_data());
 287          $rating = $writer->get_related_data($subcontext, 'rating');
 288          $this->assert_has_rating($u1, 25, $rating);
 289  
 290          // User 1::course2 is stored in ['foo'] subcontext.
 291          $context = \context_course::instance($course2->id);
 292          $subcontext = ['foo'];
 293          provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'course', $course2->id, true);
 294  
 295          $writer = writer::with_context($context);
 296          $this->assertTrue($writer->has_any_data());
 297          $result = $writer->get_related_data($subcontext, 'rating');
 298          $this->assertCount(1, $result);
 299          $this->assert_has_rating($u1, 50, $result);
 300  
 301          // User 1::course3 is stored in ['foo'] subcontext.
 302          $context = \context_course::instance($course3->id);
 303          $subcontext = ['foo'];
 304          provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'course', $course3->id, true);
 305  
 306          $writer = writer::with_context($context);
 307          $this->assertTrue($writer->has_any_data());
 308          $result = $writer->get_related_data($subcontext, 'rating');
 309          $this->assertCount(1, $result);
 310          $this->assert_has_rating($u1, 75, $result);
 311  
 312          // User 1::course3::files is stored in ['foo', 'files'] subcontext.
 313          $context = \context_course::instance($course3->id);
 314          $subcontext = ['foo', 'files'];
 315          provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'files', $course3->id, true);
 316  
 317          $writer = writer::with_context($context);
 318          $this->assertTrue($writer->has_any_data());
 319          $result = $writer->get_related_data($subcontext, 'rating');
 320          $this->assertCount(1, $result);
 321          $this->assert_has_rating($u1, 99, $result);
 322  
 323          // Both users 1 and 2 rated user 3.
 324          // Exporting the data for user 3 should include both of those ratings.
 325          $context = \context_user::instance($u3->id);
 326          $subcontext = ['user'];
 327          provider::export_area_ratings($u3->id, $context, $subcontext, 'user', 'user', $u3->id, false);
 328  
 329          $writer = writer::with_context($context);
 330          $this->assertTrue($writer->has_any_data());
 331          $result = $writer->get_related_data($subcontext, 'rating');
 332          $this->assertCount(2, $result);
 333          $this->assert_has_rating($u1, 10, $result);
 334          $this->assert_has_rating($u2, 20, $result);
 335      }
 336  
 337      /**
 338       * Test delete_ratings() method.
 339       */
 340      public function test_delete_ratings() {
 341          global $DB;
 342          $this->resetAfterTest();
 343  
 344          $course1 = $this->getDataGenerator()->create_course();
 345          $course2 = $this->getDataGenerator()->create_course();
 346          $course3 = $this->getDataGenerator()->create_course();
 347  
 348          $u1 = $this->getDataGenerator()->create_user();
 349          $u2 = $this->getDataGenerator()->create_user();
 350          $u3 = $this->getDataGenerator()->create_user();
 351  
 352          // Rate all courses as u1, and something else in the same context.
 353          $this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
 354          $this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
 355          $this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
 356          $this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
 357          $this->rate_as_user($u1->id, 'core_user', 'user', $u3->id, \context_user::instance($u3->id), 10);
 358  
 359          // Rate course2 as u2, and something else in a different context/component..
 360          $this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
 361          $this->rate_as_user($u2->id, 'core_user', 'user', $u3->id, \context_user::instance($u3->id), 20);
 362  
 363          // Delete all ratings in course1.
 364          $expectedratingscount = $DB->count_records('rating');
 365          core_rating\privacy\provider::delete_ratings(\context_course::instance($course1->id));
 366          $expectedratingscount -= 1;
 367          $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
 368  
 369          // Delete ratings in course2 specifying wrong component.
 370          core_rating\privacy\provider::delete_ratings(\context_course::instance($course2->id), 'other_component');
 371          $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
 372  
 373          // Delete ratings in course2 specifying correct component.
 374          core_rating\privacy\provider::delete_ratings(\context_course::instance($course2->id), 'core_course');
 375          $expectedratingscount -= 2;
 376          $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
 377  
 378          // Delete user ratings specifyng all attributes.
 379          core_rating\privacy\provider::delete_ratings(\context_user::instance($u3->id), 'core_user', 'user', $u3->id);
 380          $expectedratingscount -= 2;
 381          $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
 382      }
 383  
 384      /**
 385       * Test delete_ratings_select() method.
 386       */
 387      public function test_delete_ratings_select() {
 388          global $DB;
 389          $this->resetAfterTest();
 390  
 391          $course1 = $this->getDataGenerator()->create_course();
 392          $course2 = $this->getDataGenerator()->create_course();
 393          $course3 = $this->getDataGenerator()->create_course();
 394  
 395          $u1 = $this->getDataGenerator()->create_user();
 396          $u2 = $this->getDataGenerator()->create_user();
 397          $u3 = $this->getDataGenerator()->create_user();
 398  
 399          // Rate all courses as u1, and something else in the same context.
 400          $this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
 401          $this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
 402          $this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
 403          $this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
 404          $this->rate_as_user($u1->id, 'core_user', 'user', $u3->id, \context_user::instance($u3->id), 10);
 405  
 406          // Rate course2 as u2, and something else in a different context/component..
 407          $this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
 408          $this->rate_as_user($u2->id, 'core_user', 'user', $u3->id, \context_user::instance($u3->id), 20);
 409  
 410          // Delete ratings in course1.
 411          list($sql, $params) = $DB->get_in_or_equal([$course1->id, $course2->id], SQL_PARAMS_NAMED);
 412          $expectedratingscount = $DB->count_records('rating');
 413          core_rating\privacy\provider::delete_ratings_select(\context_course::instance($course1->id),
 414              'core_course', 'course', $sql, $params);
 415          $expectedratingscount -= 1;
 416          $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
 417      }
 418  
 419      /**
 420       * Assert that a user has the correct rating.
 421       *
 422       * @param   \stdClass   $author The user with the rating
 423       * @param   int         $score The rating that was given
 424       * @param   \stdClass[] The ratings which were found
 425       */
 426      protected function assert_has_rating($author, $score, $actual) {
 427          $found = false;
 428          foreach ($actual as $rating) {
 429              if ($author->id == $rating->author) {
 430                  $found = true;
 431                  $this->assertEquals($score, $rating->rating);
 432              }
 433          }
 434          $this->assertTrue($found);
 435      }
 436  }