Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is 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  declare(strict_types=1);
  18  
  19  namespace core_reportbuilder\privacy;
  20  
  21  use context;
  22  use stdClass;
  23  use core_privacy\local\metadata\collection;
  24  use core_privacy\local\request\approved_contextlist;
  25  use core_privacy\local\request\approved_userlist;
  26  use core_privacy\local\request\contextlist;
  27  use core_privacy\local\request\core_userlist_provider;
  28  use core_privacy\local\request\transform;
  29  use core_privacy\local\request\userlist;
  30  use core_privacy\local\request\user_preference_provider;
  31  use core_privacy\local\request\writer;
  32  use core_reportbuilder\manager;
  33  use core_reportbuilder\local\helpers\user_filter_manager;
  34  use core_reportbuilder\local\helpers\schedule as schedule_helper;
  35  use core_reportbuilder\local\models\audience;
  36  use core_reportbuilder\local\models\column;
  37  use core_reportbuilder\local\models\filter;
  38  use core_reportbuilder\local\models\report;
  39  use core_reportbuilder\local\models\schedule;
  40  
  41  /**
  42   * Privacy Subsystem for core_reportbuilder
  43   *
  44   * @package     core_reportbuilder
  45   * @copyright   2021 David Matamoros <davidmc@moodle.com>
  46   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  47   */
  48  class provider implements
  49      \core_privacy\local\metadata\provider,
  50      \core_privacy\local\request\plugin\provider,
  51      core_userlist_provider,
  52      user_preference_provider {
  53  
  54      /**
  55       * Returns metadata about the component
  56       *
  57       * @param collection $collection
  58       * @return collection
  59       */
  60      public static function get_metadata(collection $collection): collection {
  61          $collection->add_database_table(report::TABLE, [
  62              'name' => 'privacy:metadata:report:name',
  63              'source' => 'privacy:metadata:report:source',
  64              'conditiondata' => 'privacy:metadata:report:conditiondata',
  65              'settingsdata' => 'privacy:metadata:report:settingsdata',
  66              'uniquerows' => 'privacy:metadata:report:uniquerows',
  67              'usercreated' => 'privacy:metadata:report:usercreated',
  68              'usermodified' => 'privacy:metadata:report:usermodified',
  69              'timecreated' => 'privacy:metadata:report:timecreated',
  70              'timemodified' => 'privacy:metadata:report:timemodified',
  71          ], 'privacy:metadata:report');
  72  
  73          $collection->add_database_table(column::TABLE, [
  74              'uniqueidentifier' => 'privacy:metadata:column:uniqueidentifier',
  75              'usercreated' => 'privacy:metadata:column:usercreated',
  76              'usermodified' => 'privacy:metadata:column:usermodified',
  77          ], 'privacy:metadata:column');
  78  
  79          $collection->add_database_table(filter::TABLE, [
  80              'uniqueidentifier' => 'privacy:metadata:filter:uniqueidentifier',
  81              'usercreated' => 'privacy:metadata:filter:usercreated',
  82              'usermodified' => 'privacy:metadata:filter:usermodified',
  83          ], 'privacy:metadata:filter');
  84  
  85          $collection->add_database_table(audience::TABLE, [
  86              'classname' => 'privacy:metadata:audience:classname',
  87              'configdata' => 'privacy:metadata:audience:configdata',
  88              'heading' => 'privacy:metadata:audience:heading',
  89              'usercreated' => 'privacy:metadata:audience:usercreated',
  90              'usermodified' => 'privacy:metadata:audience:usermodified',
  91              'timecreated' => 'privacy:metadata:audience:timecreated',
  92              'timemodified' => 'privacy:metadata:audience:timemodified',
  93          ], 'privacy:metadata:audience');
  94  
  95          $collection->add_database_table(schedule::TABLE, [
  96              'name' => 'privacy:metadata:schedule:name',
  97              'enabled' => 'privacy:metadata:schedule:enabled',
  98              'audiences' => 'privacy:metadata:schedule:audiences',
  99              'format' => 'privacy:metadata:schedule:format',
 100              'subject' => 'privacy:metadata:schedule:subject',
 101              'message' => 'privacy:metadata:schedule:message',
 102              'userviewas' => 'privacy:metadata:schedule:userviewas',
 103              'timescheduled' => 'privacy:metadata:schedule:timescheduled',
 104              'recurrence' => 'privacy:metadata:schedule:recurrence',
 105              'reportempty' => 'privacy:metadata:schedule:reportempty',
 106              'usercreated' => 'privacy:metadata:schedule:usercreated',
 107              'usermodified' => 'privacy:metadata:schedule:usermodified',
 108              'timecreated' => 'privacy:metadata:schedule:timecreated',
 109              'timemodified' => 'privacy:metadata:schedule:timemodified',
 110          ], 'privacy:metadata:schedule');
 111  
 112          $collection->add_user_preference('core_reportbuilder', 'privacy:metadata:preference:reportfilter');
 113  
 114          return $collection;
 115      }
 116  
 117      /**
 118       * Export all user preferences for the component
 119       *
 120       * @param int $userid
 121       */
 122      public static function export_user_preferences(int $userid): void {
 123          $preferencestring = get_string('privacy:metadata:preference:reportfilter', 'core_reportbuilder');
 124  
 125          $filters = user_filter_manager::get_all_for_user($userid);
 126          foreach ($filters as $key => $filter) {
 127              writer::export_user_preference('core_reportbuilder',
 128                  $key,
 129                  json_encode($filter, JSON_PRETTY_PRINT),
 130                  $preferencestring
 131              );
 132          }
 133      }
 134  
 135      /**
 136       * Get export sub context for a report
 137       *
 138       * @param report $report
 139       * @return array
 140       */
 141      public static function get_export_subcontext(report $report): array {
 142          $reportnode = implode('-', [
 143              $report->get('id'),
 144              clean_filename($report->get_formatted_name()),
 145          ]);
 146  
 147          return [get_string('reportbuilder', 'core_reportbuilder'), $reportnode];
 148      }
 149  
 150      /**
 151       * Get the list of contexts that contain user information for the specified user
 152       *
 153       * @param int $userid
 154       * @return contextlist
 155       */
 156      public static function get_contexts_for_userid(int $userid): contextlist {
 157          $contextlist = new contextlist();
 158  
 159          // Locate all contexts for reports the user has created, or reports they have created audience/schedules for.
 160          $sql = '
 161              SELECT r.contextid
 162                FROM {' . report::TABLE . '} r
 163               WHERE r.type = 0
 164                 AND (r.usercreated = ?
 165                   OR r.usermodified = ?
 166                   OR r.id IN (
 167                      SELECT a.reportid
 168                        FROM {' . audience::TABLE . '} a
 169                       WHERE a.usercreated = ? OR a.usermodified = ?
 170                       UNION
 171                      SELECT s.reportid
 172                        FROM {' . schedule::TABLE . '} s
 173                       WHERE s.usercreated = ? OR s.usermodified = ?
 174                      )
 175                     )';
 176  
 177          return $contextlist->add_from_sql($sql, array_fill(0, 6, $userid));
 178      }
 179  
 180      /**
 181       * Get users in context
 182       *
 183       * @param userlist $userlist
 184       */
 185      public static function get_users_in_context(userlist $userlist): void {
 186          $select = 'r.type = :type AND r.contextid = :contextid';
 187          $params = ['type' => 0, 'contextid' => $userlist->get_context()->id];
 188  
 189          // Users who have created reports.
 190          $sql = 'SELECT r.usercreated, r.usermodified
 191                   FROM {' . report::TABLE . '} r
 192                  WHERE ' . $select;
 193          $userlist->add_from_sql('usercreated', $sql, $params);
 194          $userlist->add_from_sql('usermodified', $sql, $params);
 195  
 196          // Users who have created audiences.
 197          $sql = 'SELECT a.usercreated, a.usermodified
 198                    FROM {' . audience::TABLE . '} a
 199                    JOIN {' . report::TABLE . '} r ON r.id = a.reportid
 200                  WHERE ' . $select;
 201          $userlist->add_from_sql('usercreated', $sql, $params);
 202          $userlist->add_from_sql('usermodified', $sql, $params);
 203  
 204          // Users who have created schedules.
 205          $sql = 'SELECT s.usercreated, s.usermodified
 206                    FROM {' . schedule::TABLE . '} s
 207                    JOIN {' . report::TABLE . '} r ON r.id = s.reportid
 208                   WHERE ' . $select;
 209          $userlist->add_from_sql('usercreated', $sql, $params);
 210          $userlist->add_from_sql('usermodified', $sql, $params);
 211      }
 212  
 213      /**
 214       * Export all user data for the specified user in the specified contexts
 215       *
 216       * @param approved_contextlist $contextlist
 217       */
 218      public static function export_user_data(approved_contextlist $contextlist): void {
 219          if (empty($contextlist->count())) {
 220              return;
 221          }
 222  
 223          $user = $contextlist->get_user();
 224  
 225          // We need to get all reports that the user has created, or reports they have created audience/schedules for.
 226          $select = 'type = 0 AND (usercreated = ? OR usermodified = ? OR id IN (
 227              SELECT a.reportid
 228                FROM {' . audience::TABLE . '} a
 229               WHERE a.usercreated = ? OR a.usermodified = ?
 230               UNION
 231              SELECT s.reportid
 232                FROM {' . schedule::TABLE . '} s
 233               WHERE s.usercreated = ? OR s.usermodified = ?
 234          ))';
 235          $params = array_fill(0, 6, $user->id);
 236  
 237          foreach (report::get_records_select($select, $params) as $report) {
 238              $subcontext = static::get_export_subcontext($report);
 239  
 240              self::export_report($subcontext, $report);
 241  
 242              $select = 'reportid = ? AND (usercreated = ? OR usermodified = ?)';
 243              $params = [$report->get('id'), $user->id, $user->id];
 244  
 245              // Audiences.
 246              if ($audiences = audience::get_records_select($select, $params)) {
 247                  static::export_audiences($report->get_context(), $subcontext, $audiences);
 248              }
 249  
 250              // Schedules.
 251              if ($schedules = schedule::get_records_select($select, $params)) {
 252                  static::export_schedules($report->get_context(), $subcontext, $schedules);
 253              }
 254          }
 255      }
 256  
 257      /**
 258       * Delete data for all users in context
 259       *
 260       * @param context $context
 261       */
 262      public static function delete_data_for_all_users_in_context(context $context): void {
 263          // We don't perform any deletion of user data.
 264      }
 265  
 266      /**
 267       * Delete data for user
 268       *
 269       * @param approved_contextlist $contextlist
 270       */
 271      public static function delete_data_for_user(approved_contextlist $contextlist): void {
 272          // We don't perform any deletion of user data.
 273      }
 274  
 275      /**
 276       * Delete data for users
 277       *
 278       * @param approved_userlist $userlist
 279       */
 280      public static function delete_data_for_users(approved_userlist $userlist): void {
 281          // We don't perform any deletion of user data.
 282      }
 283  
 284      /**
 285       * Export given report in context
 286       *
 287       * @param array $subcontext
 288       * @param report $report
 289       */
 290      protected static function export_report(array $subcontext, report $report): void {
 291          // Show the source name, if it exists.
 292          $source = $report->get('source');
 293          if (manager::report_source_exists($source)) {
 294              $source = call_user_func([$source, 'get_name']);
 295          }
 296  
 297          $reportdata = (object) [
 298              'name' => $report->get_formatted_name(),
 299              'source' => $source,
 300              'conditiondata' => $report->get('conditiondata'),
 301              'settingsdata' => $report->get('settingsdata'),
 302              'uniquerows' => transform::yesno($report->get('uniquerows')),
 303              'usercreated' => transform::user($report->get('usercreated')),
 304              'usermodified' => transform::user($report->get('usermodified')),
 305              'timecreated' => transform::datetime($report->get('timecreated')),
 306              'timemodified' => transform::datetime($report->get('timemodified')),
 307          ];
 308  
 309          writer::with_context($report->get_context())->export_data($subcontext, $reportdata);
 310      }
 311  
 312      /**
 313       * Export given audiences in context
 314       *
 315       * @param context $context
 316       * @param array $subcontext
 317       * @param audience[] $audiences
 318       */
 319      protected static function export_audiences(context $context, array $subcontext, array $audiences): void {
 320          $audiencedata = array_map(static function(audience $audience) use ($context): stdClass {
 321              // Show the audience name, if it exists.
 322              $classname = $audience->get('classname');
 323              if (class_exists($classname)) {
 324                  $classname = $classname::instance()->get_name();
 325              }
 326  
 327              return (object) [
 328                  'classname' => $classname,
 329                  'heading' => $audience->get_formatted_heading($context),
 330                  'configdata' => $audience->get('configdata'),
 331                  'usercreated' => transform::user($audience->get('usercreated')),
 332                  'usermodified' => transform::user($audience->get('usermodified')),
 333                  'timecreated' => transform::datetime($audience->get('timecreated')),
 334                  'timemodified' => transform::datetime($audience->get('timemodified')),
 335              ];
 336          }, $audiences);
 337  
 338          writer::with_context($context)->export_related_data($subcontext, 'audiences', (object) ['data' => $audiencedata]);
 339      }
 340  
 341      /**
 342       * Export given schedules in context
 343       *
 344       * @param context $context
 345       * @param array $subcontext
 346       * @param schedule[] $schedules
 347       */
 348      protected static function export_schedules(context $context, array $subcontext, array $schedules): void {
 349          $formatoptions = schedule_helper::get_format_options();
 350          $recurrenceoptions = schedule_helper::get_recurrence_options();
 351          $viewasoptions = schedule_helper::get_viewas_options();
 352          $reportemptyoptions = schedule_helper::get_report_empty_options();
 353  
 354          $scheduledata = array_map(static function(schedule $schedule) use (
 355                  $context, $formatoptions, $recurrenceoptions, $viewasoptions, $reportemptyoptions): stdClass {
 356  
 357              // The "User view as" property will be either creator, recipient or a specific userid.
 358              $userviewas = $schedule->get('userviewas');
 359  
 360              return (object) [
 361                  'name' => $schedule->get_formatted_name($context),
 362                  'enabled' => transform::yesno($schedule->get('enabled')),
 363                  'format' => $formatoptions[$schedule->get('format')],
 364                  'timescheduled' => transform::datetime($schedule->get('timescheduled')),
 365                  'recurrence' => $recurrenceoptions[$schedule->get('recurrence')],
 366                  'userviewas' => $viewasoptions[$userviewas] ?? transform::user($userviewas),
 367                  'audiences' => $schedule->get('audiences'),
 368                  'subject' => $schedule->get('subject'),
 369                  'message' => format_text($schedule->get('message'), $schedule->get('messageformat'), ['context' => $context]),
 370                  'reportempty' => $reportemptyoptions[$schedule->get('reportempty')],
 371                  'usercreated' => transform::user($schedule->get('usercreated')),
 372                  'usermodified' => transform::user($schedule->get('usermodified')),
 373                  'timecreated' => transform::datetime($schedule->get('timecreated')),
 374                  'timemodified' => transform::datetime($schedule->get('timemodified')),
 375              ];
 376          }, $schedules);
 377  
 378          writer::with_context($context)->export_related_data($subcontext, 'schedules', (object) ['data' => $scheduledata]);
 379      }
 380  }