Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   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  namespace core_rating;
  18  
  19  use rating_manager;
  20  
  21  defined('MOODLE_INTERNAL') || die();
  22  
  23  // Include all the needed stuff.
  24  global $CFG;
  25  require_once($CFG->dirroot . '/rating/lib.php');
  26  
  27  
  28  /**
  29   * Unit test case for all the rating/lib.php requiring DB mockup & manipulation
  30   *
  31   * @package    core_rating
  32   * @category   test
  33   * @copyright  2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   * @covers     \rating
  36   */
  37  class rating_test extends \advanced_testcase {
  38  
  39      protected $syscontext;
  40      protected $neededcaps = array('view', 'viewall', 'viewany', 'rate');
  41      protected $originaldefaultfrontpageroleid;
  42  
  43      public function setUp(): void {
  44          global $CFG;
  45          parent::setUp();
  46  
  47          $this->resetAfterTest(true);
  48  
  49          $CFG->defaultfrontpageroleid = null;
  50      }
  51  
  52      /**
  53       * Test the current get_ratings method main sql
  54       */
  55      public function test_get_ratings_sql() {
  56          global $DB;
  57  
  58          // We load 3 items. Each is rated twice. For simplicity itemid == user id of the item owner.
  59          $ctxid = \context_system::instance()->id;
  60          $ratings = array(
  61              // User 1's items. Average == 2.
  62              array('contextid' => $ctxid,
  63                    'component' => 'mod_forum',
  64                    'ratingarea' => 'post',
  65                    'itemid' => 1,
  66                    'scaleid' => 10,
  67                    'rating' => 1,
  68                    'userid' => 2,
  69                    'timecreated' => 1,
  70                    'timemodified' => 1),
  71  
  72              array('contextid' => $ctxid,
  73                    'component' => 'mod_forum',
  74                    'ratingarea' => 'post',
  75                    'itemid' => 1,
  76                    'scaleid' => 10,
  77                    'rating' => 3,
  78                    'userid' => 3,
  79                    'timecreated' => 1,
  80                    'timemodified' => 1),
  81  
  82              // User 2's items. Average == 3.
  83              array('contextid' => $ctxid,
  84                    'component' => 'mod_forum',
  85                    'ratingarea' => 'post',
  86                    'itemid' => 2,
  87                    'scaleid' => 10,
  88                    'rating' => 1,
  89                    'userid' => 1,
  90                    'timecreated' => 1,
  91                    'timemodified' => 1),
  92  
  93              array('contextid' => $ctxid,
  94                    'component' => 'mod_forum',
  95                    'ratingarea' => 'post',
  96                    'itemid' => 2,
  97                    'scaleid' => 10,
  98                    'rating' => 4,
  99                    'userid' => 3,
 100                    'timecreated' => 1,
 101                    'timemodified' => 1),
 102  
 103              // User 3's items. Average == 4.
 104              array('contextid' => $ctxid,
 105                    'component' => 'mod_forum',
 106                    'ratingarea' => 'post',
 107                    'itemid' => 3,
 108                    'scaleid' => 10,
 109                    'rating' => 3,
 110                    'userid' => 1,
 111                    'timecreated' => 1,
 112                    'timemodified' => 1),
 113  
 114              array('contextid' => $ctxid,
 115                    'component' => 'mod_forum',
 116                    'ratingarea' => 'post',
 117                    'itemid' => 3,
 118                    'scaleid' => 10,
 119                    'rating' => 5,
 120                    'userid' => 2,
 121                    'timecreated' => 1,
 122                    'timemodified' => 1)
 123          );
 124          foreach ($ratings as $rating) {
 125              $DB->insert_record('rating', $rating);
 126          }
 127  
 128          // A post (item) by user 1 (rated above by user 2 and 3 with average = 2).
 129          $user1posts = array(
 130              (object)array('id' => 1, 'userid' => 1, 'message' => 'hello'));
 131          // A post (item) by user 2 (rated above by user 1 and 3 with average = 3).
 132          $user2posts = array(
 133              (object)array('id' => 2, 'userid' => 2, 'message' => 'world'));
 134          // A post (item) by user 3 (rated above by user 1 and 2 with average = 4).
 135          $user3posts = array(
 136              (object)array('id' => 3, 'userid' => 3, 'message' => 'moodle'));
 137  
 138          // Prepare the default options.
 139          $defaultoptions = array (
 140              'context'    => \context_system::instance(),
 141              'component'  => 'mod_forum',
 142              'ratingarea' => 'post',
 143              'scaleid'    => 10,
 144              'aggregate'  => RATING_AGGREGATE_AVERAGE);
 145  
 146          $rm = new mockup_rating_manager();
 147  
 148          // STEP 1: Retreive ratings using the current user.
 149  
 150          // Get results for user 1's item (expected average 1 + 3 / 2 = 2).
 151          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts));
 152          $result = $rm->get_ratings($toptions);
 153          $this->assertEquals(count($result), count($user1posts));
 154          $this->assertEquals($result[0]->id, $user1posts[0]->id);
 155          $this->assertEquals($result[0]->userid, $user1posts[0]->userid);
 156          $this->assertEquals($result[0]->message, $user1posts[0]->message);
 157          $this->assertEquals($result[0]->rating->count, 2);
 158          $this->assertEquals($result[0]->rating->aggregate, 2);
 159          // Note that $result[0]->rating->rating is somewhat random.
 160          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 161  
 162          // Get results for items of user 2 (expected average 1 + 4 / 2 = 2.5).
 163          $toptions = (object)array_merge($defaultoptions, array('items' => $user2posts));
 164          $result = $rm->get_ratings($toptions);
 165          $this->assertEquals(count($result), count($user2posts));
 166          $this->assertEquals($result[0]->id, $user2posts[0]->id);
 167          $this->assertEquals($result[0]->userid, $user2posts[0]->userid);
 168          $this->assertEquals($result[0]->message, $user2posts[0]->message);
 169          $this->assertEquals($result[0]->rating->count, 2);
 170          $this->assertEquals($result[0]->rating->aggregate, 2.5);
 171          // Note that $result[0]->rating->rating is somewhat random.
 172          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 173  
 174          // Get results for items of user 3 (expected average 3 + 5 / 2 = 4).
 175          $toptions = (object)array_merge($defaultoptions, array('items' => $user3posts));
 176          $result = $rm->get_ratings($toptions);
 177          $this->assertEquals(count($result), count($user3posts));
 178          $this->assertEquals($result[0]->id, $user3posts[0]->id);
 179          $this->assertEquals($result[0]->userid, $user3posts[0]->userid);
 180          $this->assertEquals($result[0]->message, $user3posts[0]->message);
 181          $this->assertEquals($result[0]->rating->count, 2);
 182          $this->assertEquals($result[0]->rating->aggregate, 4);
 183          // Note that $result[0]->rating->rating is somewhat random.
 184          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 185  
 186          // Get results for items of user 1 & 2 together (expected averages are 2 and 2.5, as tested above).
 187          $posts = array_merge($user1posts, $user2posts);
 188          $toptions = (object)array_merge($defaultoptions, array('items' => $posts));
 189          $result = $rm->get_ratings($toptions);
 190          $this->assertEquals(count($result), count($posts));
 191          $this->assertEquals($result[0]->id, $posts[0]->id);
 192          $this->assertEquals($result[0]->userid, $posts[0]->userid);
 193          $this->assertEquals($result[0]->message, $posts[0]->message);
 194          $this->assertEquals($result[0]->rating->count, 2);
 195          $this->assertEquals($result[0]->rating->aggregate, 2);
 196          // Note that $result[0]->rating->rating is somewhat random.
 197          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 198  
 199          $this->assertEquals($result[1]->id, $posts[1]->id);
 200          $this->assertEquals($result[1]->userid, $posts[1]->userid);
 201          $this->assertEquals($result[1]->message, $posts[1]->message);
 202          $this->assertEquals($result[1]->rating->count, 2);
 203          $this->assertEquals($result[1]->rating->aggregate, 2.5);
 204          // Note that $result[0]->rating->rating is somewhat random.
 205          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 206  
 207          // STEP 2: Retrieve ratings by a specified user.
 208          //         We still expect complete aggregations and counts.
 209  
 210          // Get results for items of user 1 rated by user 2 (avg 2, rating 1).
 211          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts, 'userid' => 2));
 212          $result = $rm->get_ratings($toptions);
 213          $this->assertEquals(count($result), count($user1posts));
 214          $this->assertEquals($result[0]->id, $user1posts[0]->id);
 215          $this->assertEquals($result[0]->userid, $user1posts[0]->userid);
 216          $this->assertEquals($result[0]->message, $user1posts[0]->message);
 217          $this->assertEquals($result[0]->rating->count, 2);
 218          $this->assertEquals($result[0]->rating->aggregate, 2);
 219          $this->assertEquals($result[0]->rating->rating, 1); // User 2 rated user 1 "1".
 220          $this->assertEquals($result[0]->rating->userid, $toptions->userid); // Must be the passed userid.
 221  
 222          // Get results for items of user 1 rated by user 3.
 223          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts, 'userid' => 3));
 224          $result = $rm->get_ratings($toptions);
 225          $this->assertEquals(count($result), count($user1posts));
 226          $this->assertEquals($result[0]->id, $user1posts[0]->id);
 227          $this->assertEquals($result[0]->userid, $user1posts[0]->userid);
 228          $this->assertEquals($result[0]->message, $user1posts[0]->message);
 229          $this->assertEquals($result[0]->rating->count, 2);
 230          $this->assertEquals($result[0]->rating->aggregate, 2);
 231          $this->assertEquals($result[0]->rating->rating, 3); // User 3 rated user 1 "3".
 232          $this->assertEquals($result[0]->rating->userid, $toptions->userid); // Must be the passed userid.
 233  
 234          // Get results for items of user 1 & 2 together rated by user 3.
 235          $posts = array_merge($user1posts, $user2posts);
 236          $toptions = (object)array_merge($defaultoptions, array('items' => $posts, 'userid' => 3));
 237          $result = $rm->get_ratings($toptions);
 238          $this->assertEquals(count($result), count($posts));
 239          $this->assertEquals($result[0]->id, $posts[0]->id);
 240          $this->assertEquals($result[0]->userid, $posts[0]->userid);
 241          $this->assertEquals($result[0]->message, $posts[0]->message);
 242          $this->assertEquals($result[0]->rating->count, 2);
 243          $this->assertEquals($result[0]->rating->aggregate, 2);
 244          $this->assertEquals($result[0]->rating->rating, 3); // User 3 rated user 1 "3".
 245          $this->assertEquals($result[0]->rating->userid, $toptions->userid); // Must be the passed userid.
 246  
 247          $this->assertEquals($result[1]->id, $posts[1]->id);
 248          $this->assertEquals($result[1]->userid, $posts[1]->userid);
 249          $this->assertEquals($result[1]->message, $posts[1]->message);
 250          $this->assertEquals($result[1]->rating->count, 2);
 251          $this->assertEquals($result[1]->rating->aggregate, 2.5);
 252          $this->assertEquals($result[0]->rating->rating, 3); // User 3 rated user 2 "5".
 253          $this->assertEquals($result[1]->rating->userid, $toptions->userid); // Must be the passed userid.
 254  
 255          // STEP 3: Some special cases.
 256  
 257          // Get results for user 1's items (expected average 1 + 3 / 2 = 2).
 258          // Supplying a non-existent user id so no rating from that user should be found.
 259          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts));
 260          $toptions->userid = 123456; // Non-existent user.
 261          $result = $rm->get_ratings($toptions);
 262          $this->assertNull($result[0]->rating->userid);
 263          $this->assertNull($result[0]->rating->rating);
 264          $this->assertEquals($result[0]->rating->aggregate, 2); // Should still get the aggregate.
 265  
 266          // Get results for items of user 2 (expected average 1 + 4 / 2 = 2.5).
 267          // Supplying the user id of the user who owns the items so no rating should be found.
 268          $toptions = (object)array_merge($defaultoptions, array('items' => $user2posts));
 269          $toptions->userid = 2; // User 2 viewing the ratings of their own item.
 270          $result = $rm->get_ratings($toptions);
 271          // These should be null as the user is viewing their own item and thus cannot rate.
 272          $this->assertNull($result[0]->rating->userid);
 273          $this->assertNull($result[0]->rating->rating);
 274          $this->assertEquals($result[0]->rating->aggregate, 2.5); // Should still get the aggregate.
 275      }
 276  
 277      /**
 278       * Data provider for get_aggregate_string tests.
 279       *
 280       * @return array
 281       */
 282      public function get_aggregate_string_provider() {
 283          return [
 284              'Non-numeric aggregate produces empty string' => [
 285                  RATING_AGGREGATE_NONE,
 286                  'string',
 287                  null,
 288                  ['Foo', 'Bar'],
 289                  '',
 290              ],
 291              'Aggregate count produces empty string' => [
 292                  RATING_AGGREGATE_COUNT,
 293                  0,
 294                  null,
 295                  ['Foo', 'Bar'],
 296                  '',
 297              ],
 298              'Numeric SUM with non-numeric scale produces returns original value' => [
 299                  RATING_AGGREGATE_SUM,
 300                  10,
 301                  false,
 302                  ['Foo', 'Bar'],
 303                  '10',
 304              ],
 305              'Numeric SUM with non-numeric scale produces returns rounded value' => [
 306                  RATING_AGGREGATE_SUM,
 307                  10.45,
 308                  false,
 309                  ['Foo', 'Bar'],
 310                  '10.5',
 311              ],
 312              'Numeric SUM with numeric scale produces returns rounded value' => [
 313                  RATING_AGGREGATE_SUM,
 314                  10.45,
 315                  true,
 316                  ['Foo', 'Bar'],
 317                  '10.5',
 318              ],
 319              'Numeric AVERAGE with numeric scale produces returns rounded value' => [
 320                  RATING_AGGREGATE_AVERAGE,
 321                  10.45,
 322                  true,
 323                  ['Foo', 'Bar'],
 324                  '10.5',
 325              ],
 326              'Numeric AVERAGE with non-numeric scale produces returns indexed value (0)' => [
 327                  RATING_AGGREGATE_AVERAGE,
 328                  0,
 329                  false,
 330                  ['Foo', 'Bar'],
 331                  'Foo',
 332              ],
 333              'Numeric AVERAGE with non-numeric scale produces returns indexed value (1)' => [
 334                  RATING_AGGREGATE_AVERAGE,
 335                  1,
 336                  false,
 337                  ['Foo', 'Bar'],
 338                  'Bar',
 339              ],
 340          ];
 341      }
 342  
 343      /**
 344       * Test the value returned by get_aggregate_string().
 345       *
 346       * @dataProvider get_aggregate_string_provider
 347       */
 348      public function test_get_aggregate_string($method, $aggregate, $isnumeric, $scaleitems, $expectation) {
 349          $options = new \stdClass();
 350          $options->aggregate = $aggregate;
 351          $options->context = null;
 352          $options->component = null;
 353          $options->ratingarea = null;
 354          $options->itemid = null;
 355          $options->scaleid = null;
 356          $options->userid = null;
 357  
 358          $options->settings = new \stdClass();
 359          $options->settings->aggregationmethod = $method;
 360          $options->settings->scale = new \stdClass();
 361          $options->settings->scale->isnumeric = $isnumeric;
 362          $options->settings->scale->scaleitems = $scaleitems;
 363  
 364          $rating = new \rating($options);
 365          $this->assertEquals($expectation, $rating->get_aggregate_string());
 366      }
 367  }
 368  
 369  /**
 370   * rating_manager subclass for unit testing without requiring capabilities to be loaded
 371   */
 372  class mockup_rating_manager extends rating_manager {
 373  
 374      /**
 375       * Overwrite get_plugin_permissions_array() so it always return granted perms for unit testing
 376       */
 377      public function get_plugin_permissions_array($contextid, $component, $ratingarea) {
 378          return array(
 379              'rate' => true,
 380              'view' => true,
 381              'viewany' => true,
 382              'viewall' => true);
 383      }
 384  
 385  }