Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 311 and 403] [Versions 400 and 403] [Versions 401 and 403]

   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 privacy.
  19   *
  20   * @package   core_analytics
  21   * @copyright 2018 David MonllaĆ³ {@link http://www.davidmonllao.com}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace core_analytics\privacy;
  25  
  26  use core_analytics\privacy\provider;
  27  use core_privacy\local\request\transform;
  28  use core_privacy\local\request\writer;
  29  use core_privacy\local\request\approved_contextlist;
  30  use core_privacy\local\request\approved_userlist;
  31  
  32  defined('MOODLE_INTERNAL') || die();
  33  
  34  require_once (__DIR__ . '/../fixtures/test_indicator_max.php');
  35  require_once (__DIR__ . '/../fixtures/test_indicator_min.php');
  36  require_once (__DIR__ . '/../fixtures/test_target_site_users.php');
  37  require_once (__DIR__ . '/../fixtures/test_target_course_users.php');
  38  
  39  /**
  40   * Unit tests for privacy.
  41   *
  42   * @package   core_analytics
  43   * @copyright 2018 David MonllaĆ³ {@link http://www.davidmonllao.com}
  44   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  class provider_test extends \core_privacy\tests\provider_testcase {
  47  
  48      /** @var \core_analytics\model Store Model 1. */
  49      protected $model1;
  50  
  51      /** @var \core_analytics\model Store Model 2. */
  52      protected $model2;
  53  
  54      /** @var \stdClass $modelobj1 Store Model 1 object. */
  55      protected $modelobj1;
  56  
  57      /** @var \stdClass $modelobj2 Store Model 2 object. */
  58      protected $modelobj2;
  59  
  60      /** @var \stdClass $u1 User 1 record. */
  61      protected $u1;
  62  
  63      /** @var \stdClass $u2 User 2 record. */
  64      protected $u2;
  65  
  66      /** @var \stdClass $u3 User 3 record. */
  67      protected $u3;
  68  
  69      /** @var \stdClass $u4 User 4 record. */
  70      protected $u4;
  71  
  72      /** @var \stdClass $u5 User 5 record. */
  73      protected $u5;
  74  
  75      /** @var \stdClass $u6 User 6 record. */
  76      protected $u6;
  77  
  78      /** @var \stdClass $u7 User 7 record. */
  79      protected $u7;
  80  
  81      /** @var \stdClass $u8 User 8 record. */
  82      protected $u8;
  83  
  84      /** @var \stdClass $c1 Course 1 record. */
  85      protected $c1;
  86  
  87      /** @var \stdClass $c2 Course 2 record. */
  88      protected $c2;
  89  
  90      public function setUp(): void {
  91  
  92          $this->resetAfterTest(true);
  93          $this->setAdminUser();
  94  
  95          $timesplittingid = '\core\analytics\time_splitting\single_range';
  96          $target = \core_analytics\manager::get_target('test_target_site_users');
  97          $indicators = array('test_indicator_max');
  98          foreach ($indicators as $key => $indicator) {
  99              $indicators[$key] = \core_analytics\manager::get_indicator($indicator);
 100          }
 101          $this->model1 = \core_analytics\model::create($target, $indicators, $timesplittingid);
 102          $this->modelobj1 = $this->model1->get_model_obj();
 103  
 104          $target = \core_analytics\manager::get_target('test_target_course_users');
 105          $indicators = array('test_indicator_min');
 106          foreach ($indicators as $key => $indicator) {
 107              $indicators[$key] = \core_analytics\manager::get_indicator($indicator);
 108          }
 109          $this->model2 = \core_analytics\model::create($target, $indicators, $timesplittingid);
 110          $this->modelobj2 = $this->model1->get_model_obj();
 111  
 112          $this->u1 = $this->getDataGenerator()->create_user(['firstname' => 'a111111111111', 'lastname' => 'a']);
 113          $this->u2 = $this->getDataGenerator()->create_user(['firstname' => 'a222222222222', 'lastname' => 'a']);
 114          $this->u3 = $this->getDataGenerator()->create_user(['firstname' => 'b333333333333', 'lastname' => 'b']);
 115          $this->u4 = $this->getDataGenerator()->create_user(['firstname' => 'b444444444444', 'lastname' => 'b']);
 116          $this->u5 = $this->getdatagenerator()->create_user(['firstname' => 'a555555555555', 'lastname' => 'a']);
 117          $this->u6 = $this->getdatagenerator()->create_user(['firstname' => 'a666666666666', 'lastname' => 'a']);
 118          $this->u7 = $this->getdatagenerator()->create_user(['firstname' => 'b777777777777', 'lastname' => 'b']);
 119          $this->u8 = $this->getDataGenerator()->create_user(['firstname' => 'b888888888888', 'lastname' => 'b']);
 120  
 121          $this->c1 = $this->getDataGenerator()->create_course(['visible' => false]);
 122          $this->c2 = $this->getDataGenerator()->create_course();
 123  
 124          $this->getDataGenerator()->enrol_user($this->u1->id, $this->c1->id, 'student');
 125          $this->getDataGenerator()->enrol_user($this->u2->id, $this->c1->id, 'student');
 126          $this->getDataGenerator()->enrol_user($this->u3->id, $this->c1->id, 'student');
 127          $this->getDataGenerator()->enrol_user($this->u4->id, $this->c1->id, 'student');
 128          $this->getDataGenerator()->enrol_user($this->u5->id, $this->c1->id, 'student');
 129          $this->getDataGenerator()->enrol_user($this->u6->id, $this->c1->id, 'student');
 130          $this->getDataGenerator()->enrol_user($this->u7->id, $this->c1->id, 'student');
 131          $this->getDataGenerator()->enrol_user($this->u8->id, $this->c1->id, 'student');
 132          $this->getDataGenerator()->enrol_user($this->u1->id, $this->c2->id, 'student');
 133          $this->getDataGenerator()->enrol_user($this->u2->id, $this->c2->id, 'student');
 134          $this->getDataGenerator()->enrol_user($this->u3->id, $this->c2->id, 'student');
 135          $this->getDataGenerator()->enrol_user($this->u4->id, $this->c2->id, 'student');
 136          $this->getDataGenerator()->enrol_user($this->u5->id, $this->c2->id, 'student');
 137          $this->getDataGenerator()->enrol_user($this->u6->id, $this->c2->id, 'student');
 138          $this->getDataGenerator()->enrol_user($this->u7->id, $this->c2->id, 'student');
 139          $this->getDataGenerator()->enrol_user($this->u8->id, $this->c2->id, 'student');
 140  
 141          $this->setAdminUser();
 142  
 143          $this->model1->enable();
 144          $this->model1->train();
 145          $this->model1->predict();
 146          $this->model2->enable();
 147          $this->model2->train();
 148          $this->model2->predict();
 149  
 150          list($total, $predictions) = $this->model2->get_predictions(\context_course::instance($this->c1->id));
 151  
 152          $this->setUser($this->u3);
 153          $prediction = reset($predictions);
 154          $prediction->action_executed(\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED, $this->model2->get_target());
 155  
 156          $this->setAdminUser();
 157      }
 158  
 159      /**
 160       * Test fetching users within a context.
 161       */
 162      public function test_get_users_in_context() {
 163          global $CFG;
 164  
 165          $component = 'core_analytics';
 166          $course1context = \context_course::instance($this->c1->id);
 167          $course2context = \context_course::instance($this->c2->id);
 168          $systemcontext = \context_system::instance();
 169          $expected = [$this->u1->id, $this->u2->id, $this->u3->id, $this->u4->id, $this->u5->id, $this->u6->id,
 170              $this->u7->id, $this->u8->id];
 171  
 172          // Check users exist in the relevant contexts.
 173          $userlist = new \core_privacy\local\request\userlist($course1context, $component);
 174          provider::get_users_in_context($userlist);
 175          $actual = $userlist->get_userids();
 176          sort($actual);
 177          $this->assertEquals($expected, $actual);
 178  
 179          $userlist = new \core_privacy\local\request\userlist($course2context, $component);
 180          provider::get_users_in_context($userlist);
 181          $actual = $userlist->get_userids();
 182          sort($actual);
 183          $this->assertEquals($expected, $actual);
 184  
 185          // System context will also find guest and admin user, add to expected before testing.
 186          $expected = array_merge($expected, [$CFG->siteguest, get_admin()->id]);
 187          sort($expected);
 188  
 189          $userlist = new \core_privacy\local\request\userlist($systemcontext, $component);
 190          provider::get_users_in_context($userlist);
 191          $actual = $userlist->get_userids();
 192          sort($actual);
 193          $this->assertEquals($expected, $actual);
 194      }
 195  
 196      /**
 197       * Test delete a context.
 198       *
 199       * @return null
 200       */
 201      public function test_delete_context_data() {
 202          global $DB;
 203  
 204          // We have 4 predictions for model1 and 8 predictions for model2.
 205          $this->assertEquals(12, $DB->count_records('analytics_predictions'));
 206          $this->assertEquals(26, $DB->count_records('analytics_indicator_calc'));
 207  
 208          // We have 1 prediction action.
 209          $this->assertEquals(1, $DB->count_records('analytics_prediction_actions'));
 210  
 211          $coursecontext = \context_course::instance($this->c1->id);
 212  
 213          // Delete the course that was used for prediction.
 214          provider::delete_data_for_all_users_in_context($coursecontext);
 215  
 216          // The course1 predictions are deleted.
 217          $this->assertEquals(8, $DB->count_records('analytics_predictions'));
 218  
 219          // Calculations related to that context are deleted.
 220          $this->assertEmpty($DB->count_records('analytics_indicator_calc', ['contextid' => $coursecontext->id]));
 221  
 222          // The deleted context prediction actions are deleted as well.
 223          $this->assertEquals(0, $DB->count_records('analytics_prediction_actions'));
 224      }
 225  
 226      /**
 227       * Test delete a user.
 228       *
 229       * @return null
 230       */
 231      public function test_delete_user_data() {
 232          global $DB;
 233  
 234          $usercontexts = provider::get_contexts_for_userid($this->u3->id);
 235          $contextlist = new \core_privacy\local\request\approved_contextlist($this->u3, 'core_analytics',
 236                                                                              $usercontexts->get_contextids());
 237          provider::delete_data_for_user($contextlist);
 238  
 239          // The site level prediction for u3 was deleted.
 240          $this->assertEquals(9, $DB->count_records('analytics_predictions'));
 241          $this->assertEquals(0, $DB->count_records('analytics_prediction_actions'));
 242  
 243          $usercontexts = provider::get_contexts_for_userid($this->u1->id);
 244          $contextlist = new \core_privacy\local\request\approved_contextlist($this->u1, 'core_analytics',
 245                                                                              $usercontexts->get_contextids());
 246          provider::delete_data_for_user($contextlist);
 247          // We have nothing for u1.
 248          $this->assertEquals(9, $DB->count_records('analytics_predictions'));
 249  
 250          $usercontexts = provider::get_contexts_for_userid($this->u4->id);
 251          $contextlist = new \core_privacy\local\request\approved_contextlist($this->u4, 'core_analytics',
 252                                                                              $usercontexts->get_contextids());
 253          provider::delete_data_for_user($contextlist);
 254          $this->assertEquals(6, $DB->count_records('analytics_predictions'));
 255      }
 256  
 257      /**
 258       * Test deleting multiple users in a context.
 259       */
 260      public function test_delete_data_for_users() {
 261          global $DB;
 262  
 263          $component = 'core_analytics';
 264          $course1context = \context_course::instance($this->c1->id);
 265          $course2context = \context_course::instance($this->c2->id);
 266          $systemcontext = \context_system::instance();
 267  
 268          // Ensure all records exist in expected contexts.
 269          $expectedcontexts = [$course1context->id, $course2context->id, $systemcontext->id];
 270          sort($expectedcontexts);
 271  
 272          $actualcontexts = [
 273              $this->u1->id => provider::get_contexts_for_userid($this->u1->id)->get_contextids(),
 274              $this->u2->id => provider::get_contexts_for_userid($this->u2->id)->get_contextids(),
 275              $this->u3->id => provider::get_contexts_for_userid($this->u3->id)->get_contextids(),
 276              $this->u4->id => provider::get_contexts_for_userid($this->u4->id)->get_contextids(),
 277              $this->u5->id => provider::get_contexts_for_userid($this->u5->id)->get_contextids(),
 278              $this->u6->id => provider::get_contexts_for_userid($this->u6->id)->get_contextids(),
 279              $this->u7->id => provider::get_contexts_for_userid($this->u7->id)->get_contextids(),
 280              $this->u8->id => provider::get_contexts_for_userid($this->u8->id)->get_contextids(),
 281          ];
 282  
 283          foreach ($actualcontexts as $userid => $unused) {
 284              sort($actualcontexts[$userid]);
 285              $this->assertEquals($expectedcontexts, $actualcontexts[$userid]);
 286          }
 287  
 288          // Test initial record counts are as expected.
 289          $this->assertEquals(12, $DB->count_records('analytics_predictions'));
 290          $this->assertEquals(1, $DB->count_records('analytics_prediction_actions'));
 291          $this->assertEquals(26, $DB->count_records('analytics_indicator_calc'));
 292  
 293          // Delete u1 and u3 from system context.
 294          $approveduserids = [$this->u1->id, $this->u3->id];
 295          $approvedlist = new approved_userlist($systemcontext, $component, $approveduserids);
 296          provider::delete_data_for_users($approvedlist);
 297  
 298          // Ensure u1 and u3 system context data deleted only.
 299          $expectedcontexts = [
 300              $this->u1->id => [$course1context->id, $course2context->id],
 301              $this->u2->id => [$systemcontext->id, $course1context->id, $course2context->id],
 302              $this->u3->id => [$course1context->id, $course2context->id],
 303              $this->u4->id => [$systemcontext->id, $course1context->id, $course2context->id],
 304              $this->u5->id => [$systemcontext->id, $course1context->id, $course2context->id],
 305              $this->u6->id => [$systemcontext->id, $course1context->id, $course2context->id],
 306              $this->u7->id => [$systemcontext->id, $course1context->id, $course2context->id],
 307              $this->u8->id => [$systemcontext->id, $course1context->id, $course2context->id],
 308          ];
 309  
 310          $actualcontexts = [
 311              $this->u1->id => provider::get_contexts_for_userid($this->u1->id)->get_contextids(),
 312              $this->u2->id => provider::get_contexts_for_userid($this->u2->id)->get_contextids(),
 313              $this->u3->id => provider::get_contexts_for_userid($this->u3->id)->get_contextids(),
 314              $this->u4->id => provider::get_contexts_for_userid($this->u4->id)->get_contextids(),
 315              $this->u5->id => provider::get_contexts_for_userid($this->u5->id)->get_contextids(),
 316              $this->u6->id => provider::get_contexts_for_userid($this->u6->id)->get_contextids(),
 317              $this->u7->id => provider::get_contexts_for_userid($this->u7->id)->get_contextids(),
 318              $this->u8->id => provider::get_contexts_for_userid($this->u8->id)->get_contextids(),
 319          ];
 320  
 321          foreach ($actualcontexts as $userid => $unused) {
 322              sort($expectedcontexts[$userid]);
 323              sort($actualcontexts[$userid]);
 324              $this->assertEquals($expectedcontexts[$userid], $actualcontexts[$userid]);
 325          }
 326  
 327          // Test expected number of records have been deleted.
 328          $this->assertEquals(11, $DB->count_records('analytics_predictions'));
 329          $this->assertEquals(1, $DB->count_records('analytics_prediction_actions'));
 330          $this->assertEquals(24, $DB->count_records('analytics_indicator_calc'));
 331  
 332          // Delete for all 8 users in course 2 context.
 333          $approveduserids = [$this->u1->id, $this->u2->id, $this->u3->id, $this->u4->id, $this->u5->id, $this->u6->id,
 334              $this->u7->id, $this->u8->id];
 335          $approvedlist = new approved_userlist($course2context, $component, $approveduserids);
 336          provider::delete_data_for_users($approvedlist);
 337  
 338          // Ensure all course 2 context data deleted for all 4 users.
 339          $expectedcontexts = [
 340              $this->u1->id => [$course1context->id],
 341              $this->u2->id => [$systemcontext->id, $course1context->id],
 342              $this->u3->id => [$course1context->id],
 343              $this->u4->id => [$systemcontext->id, $course1context->id],
 344              $this->u5->id => [$systemcontext->id, $course1context->id],
 345              $this->u6->id => [$systemcontext->id, $course1context->id],
 346              $this->u7->id => [$systemcontext->id, $course1context->id],
 347              $this->u8->id => [$systemcontext->id, $course1context->id],
 348          ];
 349  
 350          $actualcontexts = [
 351              $this->u1->id => provider::get_contexts_for_userid($this->u1->id)->get_contextids(),
 352              $this->u2->id => provider::get_contexts_for_userid($this->u2->id)->get_contextids(),
 353              $this->u3->id => provider::get_contexts_for_userid($this->u3->id)->get_contextids(),
 354              $this->u4->id => provider::get_contexts_for_userid($this->u4->id)->get_contextids(),
 355              $this->u5->id => provider::get_contexts_for_userid($this->u5->id)->get_contextids(),
 356              $this->u6->id => provider::get_contexts_for_userid($this->u6->id)->get_contextids(),
 357              $this->u7->id => provider::get_contexts_for_userid($this->u7->id)->get_contextids(),
 358              $this->u8->id => provider::get_contexts_for_userid($this->u8->id)->get_contextids(),
 359          ];
 360  
 361          foreach ($actualcontexts as $userid => $unused) {
 362              sort($actualcontexts[$userid]);
 363              sort($expectedcontexts[$userid]);
 364              $this->assertEquals($expectedcontexts[$userid], $actualcontexts[$userid]);
 365          }
 366  
 367          // Test expected number of records have been deleted.
 368          $this->assertEquals(7, $DB->count_records('analytics_predictions'));
 369          $this->assertEquals(1, $DB->count_records('analytics_prediction_actions'));
 370          $this->assertEquals(16, $DB->count_records('analytics_indicator_calc'));
 371  
 372          $approveduserids = [$this->u3->id];
 373          $approvedlist = new approved_userlist($course1context, $component, $approveduserids);
 374          provider::delete_data_for_users($approvedlist);
 375  
 376          // Ensure all course 1 context data deleted for u3.
 377          $expectedcontexts = [
 378              $this->u1->id => [$course1context->id],
 379              $this->u2->id => [$systemcontext->id, $course1context->id],
 380              $this->u3->id => [],
 381              $this->u4->id => [$systemcontext->id, $course1context->id],
 382              $this->u5->id => [$systemcontext->id, $course1context->id],
 383              $this->u6->id => [$systemcontext->id, $course1context->id],
 384              $this->u7->id => [$systemcontext->id, $course1context->id],
 385              $this->u8->id => [$systemcontext->id, $course1context->id],
 386          ];
 387  
 388          $actualcontexts = [
 389              $this->u1->id => provider::get_contexts_for_userid($this->u1->id)->get_contextids(),
 390              $this->u2->id => provider::get_contexts_for_userid($this->u2->id)->get_contextids(),
 391              $this->u3->id => provider::get_contexts_for_userid($this->u3->id)->get_contextids(),
 392              $this->u4->id => provider::get_contexts_for_userid($this->u4->id)->get_contextids(),
 393              $this->u5->id => provider::get_contexts_for_userid($this->u5->id)->get_contextids(),
 394              $this->u6->id => provider::get_contexts_for_userid($this->u6->id)->get_contextids(),
 395              $this->u7->id => provider::get_contexts_for_userid($this->u7->id)->get_contextids(),
 396              $this->u8->id => provider::get_contexts_for_userid($this->u8->id)->get_contextids(),
 397          ];
 398          foreach ($actualcontexts as $userid => $unused) {
 399              sort($actualcontexts[$userid]);
 400              sort($expectedcontexts[$userid]);
 401              $this->assertEquals($expectedcontexts[$userid], $actualcontexts[$userid]);
 402          }
 403  
 404          // Test expected number of records have been deleted.
 405          $this->assertEquals(6, $DB->count_records('analytics_predictions'));
 406          $this->assertEquals(0, $DB->count_records('analytics_prediction_actions'));
 407          $this->assertEquals(15, $DB->count_records('analytics_indicator_calc'));
 408      }
 409  
 410      /**
 411       * Test export user data.
 412       *
 413       * @return null
 414       */
 415      public function test_export_data() {
 416          global $DB;
 417  
 418          $system = \context_system::instance();
 419          list($total, $predictions) = $this->model1->get_predictions($system);
 420          foreach ($predictions as $key => $prediction) {
 421              if ($prediction->get_prediction_data()->sampleid !== $this->u3->id) {
 422                  $otheruserprediction = $prediction;
 423                  break;
 424              }
 425          }
 426          $this->setUser($this->u3);
 427          $otheruserprediction->action_executed(\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED, $this->model1->get_target());
 428          $this->setAdminUser();
 429  
 430          $this->export_context_data_for_user($this->u3->id, $system, 'core_analytics');
 431          $writer = \core_privacy\local\request\writer::with_context($system);
 432          $this->assertTrue($writer->has_any_data());
 433  
 434          $u3prediction = $DB->get_record('analytics_predictions', ['contextid' => $system->id, 'sampleid' => $this->u3->id]);
 435          $data = $writer->get_data([get_string('analytics', 'analytics'),
 436              get_string('privacy:metadata:analytics:predictions', 'analytics'), $u3prediction->id]);
 437          $this->assertEquals(get_string('adminhelplogs'), $data->target);
 438          $this->assertEquals(get_string('coresystem'), $data->context);
 439          $this->assertEquals('firstname first char is not A', $data->prediction);
 440  
 441          $u3calculation = $DB->get_record('analytics_indicator_calc', ['contextid' => $system->id, 'sampleid' => $this->u3->id]);
 442          $data = $writer->get_data([get_string('analytics', 'analytics'),
 443              get_string('privacy:metadata:analytics:indicatorcalc', 'analytics'), $u3calculation->id]);
 444          $this->assertEquals('Allow stealth activities', $data->indicator);
 445          $this->assertEquals(get_string('coresystem'), $data->context);
 446          $this->assertEquals(get_string('yes'), $data->calculation);
 447  
 448          $sql = "SELECT apa.id FROM {analytics_prediction_actions} apa
 449                    JOIN {analytics_predictions} ap ON ap.id = apa.predictionid
 450                   WHERE ap.contextid = :contextid AND apa.userid = :userid AND ap.modelid = :modelid";
 451          $params = ['contextid' => $system->id, 'userid' => $this->u3->id, 'modelid' => $this->model1->get_id()];
 452          $u3action = $DB->get_record_sql($sql, $params);
 453          $data = $writer->get_data([get_string('analytics', 'analytics'),
 454              get_string('privacy:metadata:analytics:predictionactions', 'analytics'), $u3action->id]);
 455          $this->assertEquals(get_string('adminhelplogs'), $data->target);
 456          $this->assertEquals(get_string('coresystem'), $data->context);
 457          $this->assertEquals(\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED, $data->action);
 458  
 459      }
 460  }