See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body