Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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.

Differences Between: [Versions 39 and 310]

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