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.
   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 mod_data;
  18  
  19  use cm_info;
  20  use context_module;
  21  use completion_info;
  22  use data_field_base;
  23  use mod_data_renderer;
  24  use mod_data\event\course_module_viewed;
  25  use mod_data\event\template_viewed;
  26  use mod_data\event\template_updated;
  27  use moodle_page;
  28  use core_component;
  29  use stdClass;
  30  
  31  /**
  32   * Class manager for database activity
  33   *
  34   * @package    mod_data
  35   * @copyright  2022 Ferran Recio <ferran@moodle.com>
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class manager {
  39  
  40      /** Module name. */
  41      const MODULE = 'data';
  42  
  43      /** The plugin name. */
  44      const PLUGINNAME = 'mod_data';
  45  
  46      /** Template list with their files required to save the information of a preset. */
  47      const TEMPLATES_LIST = [
  48          'listtemplate' => 'listtemplate.html',
  49          'singletemplate' => 'singletemplate.html',
  50          'asearchtemplate' => 'asearchtemplate.html',
  51          'addtemplate' => 'addtemplate.html',
  52          'rsstemplate' => 'rsstemplate.html',
  53          'csstemplate' => 'csstemplate.css',
  54          'jstemplate' => 'jstemplate.js',
  55          'listtemplateheader' => 'listtemplateheader.html',
  56          'listtemplatefooter' => 'listtemplatefooter.html',
  57          'rsstitletemplate' => 'rsstitletemplate.html',
  58      ];
  59  
  60      /** @var string plugin path. */
  61      public $path;
  62  
  63      /** @var stdClass course_module record. */
  64      private $instance;
  65  
  66      /** @var context_module the current context. */
  67      private $context;
  68  
  69      /** @var cm_info course_modules record. */
  70      private $cm;
  71  
  72      /** @var array the current data_fields records.
  73       * Do not access this attribute directly, use $this->get_field_records instead
  74       */
  75      private $_fieldrecords = null;
  76  
  77      /**
  78       * Class constructor.
  79       *
  80       * @param cm_info $cm course module info object
  81       * @param stdClass $instance activity instance object.
  82       */
  83      public function __construct(cm_info $cm, stdClass $instance) {
  84          global $CFG;
  85          $this->cm = $cm;
  86          $this->instance = $instance;
  87          $this->context = context_module::instance($cm->id);
  88          $this->instance->cmidnumber = $cm->idnumber;
  89          $this->path = $CFG->dirroot . '/mod/' . self::MODULE;
  90      }
  91  
  92      /**
  93       * Create a manager instance from an instance record.
  94       *
  95       * @param stdClass $instance an activity record
  96       * @return manager
  97       */
  98      public static function create_from_instance(stdClass $instance): self {
  99          $cm = get_coursemodule_from_instance(self::MODULE, $instance->id);
 100          // Ensure that $this->cm is a cm_info object.
 101          $cm = cm_info::create($cm);
 102          return new self($cm, $instance);
 103      }
 104  
 105      /**
 106       * Create a manager instance from a course_modules record.
 107       *
 108       * @param stdClass|cm_info $cm an activity record
 109       * @return manager
 110       */
 111      public static function create_from_coursemodule($cm): self {
 112          global $DB;
 113          // Ensure that $this->cm is a cm_info object.
 114          $cm = cm_info::create($cm);
 115          $instance = $DB->get_record(self::MODULE, ['id' => $cm->instance], '*', MUST_EXIST);
 116          return new self($cm, $instance);
 117      }
 118  
 119      /**
 120       * Create a manager instance from a data_record entry.
 121       *
 122       * @param stdClass $record the data_record record
 123       * @return manager
 124       */
 125      public static function create_from_data_record($record): self {
 126          global $DB;
 127          $instance = $DB->get_record(self::MODULE, ['id' => $record->dataid], '*', MUST_EXIST);
 128          $cm = get_coursemodule_from_instance(self::MODULE, $instance->id);
 129          $cm = cm_info::create($cm);
 130          return new self($cm, $instance);
 131      }
 132  
 133      /**
 134       * Return the current context.
 135       *
 136       * @return context_module
 137       */
 138      public function get_context(): context_module {
 139          return $this->context;
 140      }
 141  
 142      /**
 143       * Return the current instance.
 144       *
 145       * @return stdClass the instance record
 146       */
 147      public function get_instance(): stdClass {
 148          return $this->instance;
 149      }
 150  
 151      /**
 152       * Return the current cm_info.
 153       *
 154       * @return cm_info the course module
 155       */
 156      public function get_coursemodule(): cm_info {
 157          return $this->cm;
 158      }
 159  
 160      /**
 161       * Return the current module renderer.
 162       *
 163       * @param moodle_page|null $page the current page
 164       * @return mod_data_renderer the module renderer
 165       */
 166      public function get_renderer(?moodle_page $page = null): mod_data_renderer {
 167          global $PAGE;
 168          $page = $page ?? $PAGE;
 169          return $page->get_renderer(self::PLUGINNAME);
 170      }
 171  
 172      /**
 173       * Trigger module viewed event and set the module viewed for completion.
 174       *
 175       * @param stdClass $course course object
 176       */
 177      public function set_module_viewed(stdClass $course) {
 178          global $CFG;
 179          require_once($CFG->libdir . '/completionlib.php');
 180  
 181          // Trigger module viewed event.
 182          $event = course_module_viewed::create([
 183              'objectid' => $this->instance->id,
 184              'context' => $this->context,
 185          ]);
 186          $event->add_record_snapshot('course', $course);
 187          $event->add_record_snapshot('course_modules', $this->cm);
 188          $event->add_record_snapshot(self::MODULE, $this->instance);
 189          $event->trigger();
 190  
 191          // Completion.
 192          $completion = new completion_info($course);
 193          $completion->set_module_viewed($this->cm);
 194      }
 195  
 196      /**
 197       * Trigger module template viewed event.
 198       */
 199      public function set_template_viewed() {
 200          // Trigger an event for viewing templates.
 201          $event = template_viewed::create([
 202              'context' => $this->context,
 203              'courseid' => $this->cm->course,
 204              'other' => [
 205                  'dataid' => $this->instance->id,
 206              ],
 207          ]);
 208          $event->add_record_snapshot(self::MODULE, $this->instance);
 209          $event->trigger();
 210      }
 211  
 212      /**
 213       * Return if the database has records.
 214       *
 215       * @return bool true if the database has records
 216       */
 217      public function has_records(): bool {
 218          global $DB;
 219  
 220          return $DB->record_exists('data_records', ['dataid' => $this->instance->id]);
 221      }
 222  
 223      /**
 224       * Return if the database has fields.
 225       *
 226       * @return bool true if the database has fields
 227       */
 228      public function has_fields(): bool {
 229          global $DB;
 230          if ($this->_fieldrecords === null) {
 231              return $DB->record_exists('data_fields', ['dataid' => $this->instance->id]);
 232          }
 233          return !empty($this->_fieldrecords);
 234      }
 235  
 236      /**
 237       * Return the database fields.
 238       *
 239       * @return data_field_base[] the field instances.
 240       */
 241      public function get_fields(): array {
 242          $result = [];
 243          $fieldrecords = $this->get_field_records();
 244          foreach ($fieldrecords as $fieldrecord) {
 245              $result[$fieldrecord->id] = $this->get_field($fieldrecord);
 246          }
 247          return $result;
 248      }
 249  
 250      /**
 251       * Return the field records (the current data_fields records).
 252       *
 253       * @return stdClass[] an array of records
 254       */
 255      public function get_field_records() {
 256          global $DB;
 257          if ($this->_fieldrecords === null) {
 258              $this->_fieldrecords = $DB->get_records('data_fields', ['dataid' => $this->instance->id], 'id');
 259          }
 260          return $this->_fieldrecords;
 261      }
 262  
 263      /**
 264       * Return a specific field instance from a field record.
 265       *
 266       * @param stdClass $fieldrecord the fieldrecord to convert
 267       * @return data_field_base the data field class instance
 268       */
 269      public function get_field(stdClass $fieldrecord): data_field_base {
 270          global $CFG; // Some old field plugins require $CFG to be in the  scope.
 271          $filepath = "{$this->path}/field/{$fieldrecord->type}/field.class.php";
 272          $classname = "data_field_{$fieldrecord->type}";
 273          if (!file_exists($filepath)) {
 274              return new data_field_base($fieldrecord, $this->instance, $this->cm);
 275          }
 276          require_once($filepath);
 277          if (!class_exists($classname)) {
 278              return new data_field_base($fieldrecord, $this->instance, $this->cm);
 279          }
 280          $newfield = new $classname($fieldrecord, $this->instance, $this->cm);
 281          return $newfield;
 282      }
 283  
 284      /**
 285       * Return a specific template.
 286       *
 287       * NOTE: this method returns a default template if the module template is empty.
 288       * However, it won't update the template database field.
 289       *
 290       * Some possible options:
 291       * - search: string with the current searching text.
 292       * - page: integer repesenting the current pagination page numbre (if any)
 293       * - baseurl: a moodle_url object to the current page.
 294       *
 295       * @param string $templatename
 296       * @param array $options extra display options array
 297       * @return template the template instance
 298       */
 299      public function get_template(string $templatename, array $options = []): template {
 300          if ($templatename === 'single') {
 301              $templatename = 'singletemplate';
 302          }
 303          $instance = $this->instance;
 304          $templatecontent = $instance->{$templatename} ?? '';
 305          if (empty($templatecontent)) {
 306              $templatecontent = data_generate_default_template($instance, $templatename, 0, false, false);
 307          }
 308          $options['templatename'] = $templatename;
 309          // Some templates have extra options.
 310          $options = array_merge($options, template::get_default_display_options($templatename));
 311  
 312          return new template($this, $templatecontent, $options);
 313      }
 314  
 315      /** Check if the user can manage templates on the current context.
 316       *
 317       * @param int $userid the user id to check ($USER->id if null).
 318       * @return bool if the user can manage templates on current context.
 319       */
 320      public function can_manage_templates(?int $userid = null): bool {
 321          global $USER;
 322          if (!$userid) {
 323              $userid = $USER->id;
 324          }
 325          return has_capability('mod/data:managetemplates', $this->context, $userid);
 326      }
 327  
 328      /** Check if the user can export entries on the current context.
 329       *
 330       * @param int $userid the user id to check ($USER->id if null).
 331       * @return bool if the user can export entries on current context.
 332       */
 333      public function can_export_entries(?int $userid = null): bool {
 334          global $USER, $DB;
 335  
 336          if (!$userid) {
 337              $userid = $USER->id;
 338          }
 339  
 340          // Exportallentries and exportentry are basically the same capability.
 341          return has_capability('mod/data:exportallentries', $this->context) ||
 342                  has_capability('mod/data:exportentry', $this->context) ||
 343                  (has_capability('mod/data:exportownentry', $this->context) &&
 344                  $DB->record_exists('data_records', ['userid' => $userid, 'dataid' => $this->instance->id]));
 345      }
 346  
 347      /**
 348       * Update the database templates.
 349       *
 350       * @param stdClass $newtemplates an object with all the new templates
 351       * @return bool if updated successfully.
 352       */
 353      public function update_templates(stdClass $newtemplates): bool {
 354          global $DB;
 355          $record = (object)[
 356              'id' => $this->instance->id,
 357          ];
 358          foreach (self::TEMPLATES_LIST as $templatename => $templatefile) {
 359              if (!isset($newtemplates->{$templatename})) {
 360                  continue;
 361              }
 362              $record->{$templatename} = $newtemplates->{$templatename};
 363          }
 364  
 365          // The add entry form cannot repeat tags.
 366          if (isset($record->addtemplate) && !data_tags_check($this->instance->id, $record->addtemplate)) {
 367                  return false;
 368          }
 369  
 370          $DB->update_record(self::MODULE, $record);
 371          $this->instance = $DB->get_record(self::MODULE, ['id' => $this->cm->instance], '*', MUST_EXIST);
 372  
 373          // Trigger an event for saving the templates.
 374          $event = template_updated::create(array(
 375              'context' => $this->context,
 376              'courseid' => $this->cm->course,
 377              'other' => array(
 378                  'dataid' => $this->instance->id,
 379              )
 380          ));
 381          $event->trigger();
 382  
 383          return true;
 384      }
 385  
 386      /**
 387       * Reset all templates.
 388       *
 389       * @return bool if the reset is done or not
 390       */
 391      public function reset_all_templates(): bool {
 392          $newtemplates = new stdClass();
 393          foreach (self::TEMPLATES_LIST as $templatename => $templatefile) {
 394              $newtemplates->{$templatename} = '';
 395          }
 396          return $this->update_templates($newtemplates);
 397      }
 398  
 399      /**
 400       * Reset all templates related to a specific template.
 401       *
 402       * @param string $templatename the template name
 403       * @return bool if the reset is done or not
 404       */
 405      public function reset_template(string $templatename): bool {
 406          $newtemplates = new stdClass();
 407          // Reset the template to default.
 408          $newtemplates->{$templatename} = '';
 409          if ($templatename == 'listtemplate') {
 410              $newtemplates->listtemplateheader = '';
 411              $newtemplates->listtemplatefooter = '';
 412          }
 413          if ($templatename == 'rsstemplate') {
 414              $newtemplates->rsstitletemplate = '';
 415          }
 416          return $this->update_templates($newtemplates);
 417      }
 418  
 419      /** Check if the user can view a specific preset.
 420       *
 421       * @param preset $preset the preset instance.
 422       * @param int $userid the user id to check ($USER->id if null).
 423       * @return bool if the user can view the preset.
 424       */
 425      public function can_view_preset (preset $preset, ?int $userid = null): bool {
 426          global $USER;
 427          if (!$userid) {
 428              $userid = $USER->id;
 429          }
 430          $presetuserid = $preset->get_userid();
 431          if ($presetuserid && $presetuserid != $userid) {
 432              return has_capability('mod/data:viewalluserpresets', $this->context, $userid);
 433          }
 434          return true;
 435      }
 436  
 437      /**
 438       * Returns an array of all the available presets.
 439       *
 440       * @return array A list with the datapreset plugins and the presets saved by users.
 441       */
 442      public function get_available_presets(): array {
 443          // First load the datapreset plugins that exist within the modules preset dir.
 444          $pluginpresets = static::get_available_plugin_presets();
 445  
 446          // Then find the presets that people have saved.
 447          $savedpresets = static::get_available_saved_presets();
 448  
 449          return array_merge($pluginpresets, $savedpresets);
 450      }
 451  
 452      /**
 453       * Returns an array of all the presets that users have saved to the site.
 454       *
 455       * @return array A list with the preset saved by the users.
 456       */
 457      public function get_available_saved_presets(): array {
 458          global $USER;
 459  
 460          $presets = [];
 461  
 462          $fs = get_file_storage();
 463          $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA);
 464          if (empty($files)) {
 465              return $presets;
 466          }
 467          $canviewall = has_capability('mod/data:viewalluserpresets', $this->get_context());
 468          foreach ($files as $file) {
 469              $isnotdirectory = ($file->is_directory() && $file->get_filepath() == '/') || !$file->is_directory();
 470              $userid = $file->get_userid();
 471              $cannotviewfile = !$canviewall && $userid != $USER->id;
 472              if ($isnotdirectory || $cannotviewfile) {
 473                  continue;
 474              }
 475  
 476              $preset = preset::create_from_storedfile($this, $file);
 477              $presets[] = $preset;
 478          }
 479  
 480          return $presets;
 481      }
 482  
 483      /**
 484       * Returns an array of all the available plugin presets.
 485       *
 486       * @return array A list with the datapreset plugins.
 487       */
 488      public static function get_available_plugin_presets(): array {
 489          $presets = [];
 490  
 491          $dirs = core_component::get_plugin_list('datapreset');
 492          foreach ($dirs as $dir => $fulldir) {
 493              if (preset::is_directory_a_preset($fulldir)) {
 494                  $preset = preset::create_from_plugin(null, $dir);
 495                  $presets[] = $preset;
 496              }
 497          }
 498  
 499          return $presets;
 500      }
 501  }