Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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 * @package mod_data 19 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 20 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 21 */ 22 23 use mod_data\manager; 24 use mod_data\preset; 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 // Some constants 29 define ('DATA_MAX_ENTRIES', 50); 30 define ('DATA_PERPAGE_SINGLE', 1); 31 32 define ('DATA_FIRSTNAME', -1); 33 define ('DATA_LASTNAME', -2); 34 define ('DATA_APPROVED', -3); 35 define ('DATA_TIMEADDED', 0); 36 define ('DATA_TIMEMODIFIED', -4); 37 define ('DATA_TAGS', -5); 38 39 define ('DATA_CAP_EXPORT', 'mod/data:viewalluserpresets'); 40 // Users having assigned the default role "Non-editing teacher" can export database records 41 // Using the mod/data capability "viewalluserpresets" existing in Moodle 1.9.x. 42 // In Moodle >= 2, new roles may be introduced and used instead. 43 44 define('DATA_PRESET_COMPONENT', 'mod_data'); 45 define('DATA_PRESET_FILEAREA', 'site_presets'); 46 define('DATA_PRESET_CONTEXT', SYSCONTEXTID); 47 48 define('DATA_EVENT_TYPE_OPEN', 'open'); 49 define('DATA_EVENT_TYPE_CLOSE', 'close'); 50 51 require_once (__DIR__ . '/deprecatedlib.php'); 52 53 /** 54 * @package mod_data 55 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 56 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 57 */ 58 class data_field_base { // Base class for Database Field Types (see field/*/field.class.php) 59 60 /** @var string Subclasses must override the type with their name */ 61 var $type = 'unknown'; 62 /** @var object The database object that this field belongs to */ 63 var $data = NULL; 64 /** @var object The field object itself, if we know it */ 65 var $field = NULL; 66 /** @var int Width of the icon for this fieldtype */ 67 var $iconwidth = 16; 68 /** @var int Width of the icon for this fieldtype */ 69 var $iconheight = 16; 70 /** @var object course module or cmifno */ 71 var $cm; 72 /** @var object activity context */ 73 var $context; 74 /** @var priority for globalsearch indexing */ 75 protected static $priority = self::NO_PRIORITY; 76 /** priority value for invalid fields regarding indexing */ 77 const NO_PRIORITY = 0; 78 /** priority value for minimum priority */ 79 const MIN_PRIORITY = 1; 80 /** priority value for low priority */ 81 const LOW_PRIORITY = 2; 82 /** priority value for high priority */ 83 const HIGH_PRIORITY = 3; 84 /** priority value for maximum priority */ 85 const MAX_PRIORITY = 4; 86 87 /** @var bool whether the field is used in preview mode. */ 88 protected $preview = false; 89 90 /** 91 * Constructor function 92 * 93 * @global object 94 * @uses CONTEXT_MODULE 95 * @param int $field 96 * @param int $data 97 * @param int $cm 98 */ 99 function __construct($field=0, $data=0, $cm=0) { // Field or data or both, each can be id or object 100 global $DB; 101 102 if (empty($field) && empty($data)) { 103 throw new \moodle_exception('missingfield', 'data'); 104 } 105 106 if (!empty($field)) { 107 if (is_object($field)) { 108 $this->field = $field; // Programmer knows what they are doing, we hope 109 } else if (!$this->field = $DB->get_record('data_fields', array('id'=>$field))) { 110 throw new \moodle_exception('invalidfieldid', 'data'); 111 } 112 if (empty($data)) { 113 if (!$this->data = $DB->get_record('data', array('id'=>$this->field->dataid))) { 114 throw new \moodle_exception('invalidid', 'data'); 115 } 116 } 117 } 118 119 if (empty($this->data)) { // We need to define this properly 120 if (!empty($data)) { 121 if (is_object($data)) { 122 $this->data = $data; // Programmer knows what they are doing, we hope 123 } else if (!$this->data = $DB->get_record('data', array('id'=>$data))) { 124 throw new \moodle_exception('invalidid', 'data'); 125 } 126 } else { // No way to define it! 127 throw new \moodle_exception('missingdata', 'data'); 128 } 129 } 130 131 if ($cm) { 132 $this->cm = $cm; 133 } else { 134 $this->cm = get_coursemodule_from_instance('data', $this->data->id); 135 } 136 137 if (empty($this->field)) { // We need to define some default values 138 $this->define_default_field(); 139 } 140 141 $this->context = context_module::instance($this->cm->id); 142 } 143 144 /** 145 * Return the field type name. 146 * 147 * @return string the filed type. 148 */ 149 public function get_name(): string { 150 return $this->field->name; 151 } 152 153 /** 154 * Return if the field type supports preview. 155 * 156 * Fields without a preview cannot be displayed in the preset preview. 157 * 158 * @return bool if the plugin supports preview. 159 */ 160 public function supports_preview(): bool { 161 return false; 162 } 163 164 /** 165 * Generate a fake data_content for this field to be used in preset previews. 166 * 167 * Data plugins must override this method and support_preview in order to enable 168 * preset preview for this field. 169 * 170 * @param int $recordid the fake record id 171 * @return stdClass the fake record 172 */ 173 public function get_data_content_preview(int $recordid): stdClass { 174 $message = get_string('nopreviewavailable', 'mod_data', $this->field->name); 175 return (object)[ 176 'id' => 0, 177 'fieldid' => $this->field->id, 178 'recordid' => $recordid, 179 'content' => "<span class=\"nopreview\">$message</span>", 180 'content1' => null, 181 'content2' => null, 182 'content3' => null, 183 'content4' => null, 184 ]; 185 } 186 187 /** 188 * Set the field to preview mode. 189 * 190 * @param bool $preview the new preview value 191 */ 192 public function set_preview(bool $preview) { 193 $this->preview = $preview; 194 } 195 196 /** 197 * Get the field preview value. 198 * 199 * @return bool 200 */ 201 public function get_preview(): bool { 202 return $this->preview; 203 } 204 205 206 /** 207 * This field just sets up a default field object 208 * 209 * @return bool 210 */ 211 function define_default_field() { 212 global $OUTPUT; 213 if (empty($this->data->id)) { 214 echo $OUTPUT->notification('Programmer error: dataid not defined in field class'); 215 } 216 $this->field = new stdClass(); 217 $this->field->id = 0; 218 $this->field->dataid = $this->data->id; 219 $this->field->type = $this->type; 220 $this->field->param1 = ''; 221 $this->field->param2 = ''; 222 $this->field->param3 = ''; 223 $this->field->name = ''; 224 $this->field->description = ''; 225 $this->field->required = false; 226 227 return true; 228 } 229 230 /** 231 * Set up the field object according to data in an object. Now is the time to clean it! 232 * 233 * @return bool 234 */ 235 function define_field($data) { 236 $this->field->type = $this->type; 237 $this->field->dataid = $this->data->id; 238 239 $this->field->name = trim($data->name); 240 $this->field->description = trim($data->description); 241 $this->field->required = !empty($data->required) ? 1 : 0; 242 243 if (isset($data->param1)) { 244 $this->field->param1 = trim($data->param1); 245 } 246 if (isset($data->param2)) { 247 $this->field->param2 = trim($data->param2); 248 } 249 if (isset($data->param3)) { 250 $this->field->param3 = trim($data->param3); 251 } 252 if (isset($data->param4)) { 253 $this->field->param4 = trim($data->param4); 254 } 255 if (isset($data->param5)) { 256 $this->field->param5 = trim($data->param5); 257 } 258 259 return true; 260 } 261 262 /** 263 * Insert a new field in the database 264 * We assume the field object is already defined as $this->field 265 * 266 * @global object 267 * @return bool 268 */ 269 function insert_field() { 270 global $DB, $OUTPUT; 271 272 if (empty($this->field)) { 273 echo $OUTPUT->notification('Programmer error: Field has not been defined yet! See define_field()'); 274 return false; 275 } 276 277 $this->field->id = $DB->insert_record('data_fields',$this->field); 278 279 // Trigger an event for creating this field. 280 $event = \mod_data\event\field_created::create(array( 281 'objectid' => $this->field->id, 282 'context' => $this->context, 283 'other' => array( 284 'fieldname' => $this->field->name, 285 'dataid' => $this->data->id 286 ) 287 )); 288 $event->trigger(); 289 290 return true; 291 } 292 293 294 /** 295 * Update a field in the database 296 * 297 * @global object 298 * @return bool 299 */ 300 function update_field() { 301 global $DB; 302 303 $DB->update_record('data_fields', $this->field); 304 305 // Trigger an event for updating this field. 306 $event = \mod_data\event\field_updated::create(array( 307 'objectid' => $this->field->id, 308 'context' => $this->context, 309 'other' => array( 310 'fieldname' => $this->field->name, 311 'dataid' => $this->data->id 312 ) 313 )); 314 $event->trigger(); 315 316 return true; 317 } 318 319 /** 320 * Delete a field completely 321 * 322 * @global object 323 * @return bool 324 */ 325 function delete_field() { 326 global $DB; 327 328 if (!empty($this->field->id)) { 329 $manager = manager::create_from_instance($this->data); 330 331 // Get the field before we delete it. 332 $field = $DB->get_record('data_fields', array('id' => $this->field->id)); 333 334 $this->delete_content(); 335 $DB->delete_records('data_fields', array('id'=>$this->field->id)); 336 337 // Trigger an event for deleting this field. 338 $event = \mod_data\event\field_deleted::create(array( 339 'objectid' => $this->field->id, 340 'context' => $this->context, 341 'other' => array( 342 'fieldname' => $this->field->name, 343 'dataid' => $this->data->id 344 ) 345 )); 346 347 if (!$manager->has_fields() && $manager->has_records()) { 348 $DB->delete_records('data_records', ['dataid' => $this->data->id]); 349 } 350 351 $event->add_record_snapshot('data_fields', $field); 352 $event->trigger(); 353 } 354 355 return true; 356 } 357 358 /** 359 * Print the relevant form element in the ADD template for this field 360 * 361 * @global object 362 * @param int $recordid 363 * @return string 364 */ 365 function display_add_field($recordid=0, $formdata=null) { 366 global $DB, $OUTPUT; 367 368 if ($formdata) { 369 $fieldname = 'field_' . $this->field->id; 370 $content = $formdata->$fieldname; 371 } else if ($recordid) { 372 $content = $DB->get_field('data_content', 'content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid)); 373 } else { 374 $content = ''; 375 } 376 377 // beware get_field returns false for new, empty records MDL-18567 378 if ($content===false) { 379 $content=''; 380 } 381 382 $str = '<div title="' . s($this->field->description) . '">'; 383 $str .= '<label for="field_'.$this->field->id.'"><span class="accesshide">'.$this->field->name.'</span>'; 384 if ($this->field->required) { 385 $image = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form')); 386 $str .= html_writer::div($image, 'inline-req'); 387 } 388 $str .= '</label><input class="basefieldinput form-control d-inline mod-data-input" ' . 389 'type="text" name="field_' . $this->field->id . '" ' . 390 'id="field_' . $this->field->id . '" value="' . s($content) . '" />'; 391 $str .= '</div>'; 392 393 return $str; 394 } 395 396 /** 397 * Print the relevant form element to define the attributes for this field 398 * viewable by teachers only. 399 * 400 * @global object 401 * @global object 402 * @return void Output is echo'd 403 */ 404 function display_edit_field() { 405 global $CFG, $DB, $OUTPUT; 406 407 if (empty($this->field)) { // No field has been defined yet, try and make one 408 $this->define_default_field(); 409 } 410 411 // Throw an exception if field type doen't exist. Anyway user should never access to edit a field with an unknown fieldtype. 412 if ($this->type === 'unknown') { 413 throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name])); 414 } 415 416 echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide'); 417 418 echo '<form id="editfield" action="'.$CFG->wwwroot.'/mod/data/field.php" method="post">'."\n"; 419 echo '<input type="hidden" name="d" value="'.$this->data->id.'" />'."\n"; 420 if (empty($this->field->id)) { 421 echo '<input type="hidden" name="mode" value="add" />'."\n"; 422 } else { 423 echo '<input type="hidden" name="fid" value="'.$this->field->id.'" />'."\n"; 424 echo '<input type="hidden" name="mode" value="update" />'."\n"; 425 } 426 echo '<input type="hidden" name="type" value="'.$this->type.'" />'."\n"; 427 echo '<input name="sesskey" value="'.sesskey().'" type="hidden" />'."\n"; 428 429 echo $OUTPUT->heading($this->name(), 3); 430 431 $filepath = $CFG->dirroot.'/mod/data/field/'.$this->type.'/mod.html'; 432 433 if (!file_exists($filepath)) { 434 throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name])); 435 } else { 436 require_once($filepath); 437 } 438 439 $actionbuttons = html_writer::start_div(); 440 $actionbuttons .= html_writer::tag('input', null, [ 441 'type' => 'submit', 442 'name' => 'cancel', 443 'value' => get_string('cancel'), 444 'class' => 'btn btn-secondary mx-1' 445 ]); 446 $actionbuttons .= html_writer::tag('input', null, [ 447 'type' => 'submit', 448 'value' => get_string('save'), 449 'class' => 'btn btn-primary mx-1' 450 ]); 451 $actionbuttons .= html_writer::end_div(); 452 453 $stickyfooter = new core\output\sticky_footer($actionbuttons); 454 echo $OUTPUT->render($stickyfooter); 455 456 echo '</form>'; 457 458 echo $OUTPUT->box_end(); 459 } 460 461 /** 462 * Validates params of fieldinput data. Overwrite to validate fieldtype specific data. 463 * 464 * You are expected to return an array like ['paramname' => 'Error message for paramname param'] if there is an error, 465 * return an empty array if everything is fine. 466 * 467 * @param stdClass $fieldinput The field input data to check 468 * @return array $errors if empty validation was fine, otherwise contains one or more error messages 469 */ 470 public function validate(stdClass $fieldinput): array { 471 return []; 472 } 473 474 /** 475 * Return the data_content of the field, or generate it if it is in preview mode. 476 * 477 * @param int $recordid the record id 478 * @return stdClass|bool the record data or false if none 479 */ 480 protected function get_data_content(int $recordid) { 481 global $DB; 482 if ($this->preview) { 483 return $this->get_data_content_preview($recordid); 484 } 485 return $DB->get_record( 486 'data_content', 487 ['fieldid' => $this->field->id, 'recordid' => $recordid] 488 ); 489 } 490 491 /** 492 * Display the content of the field in browse mode 493 * 494 * @global object 495 * @param int $recordid 496 * @param object $template 497 * @return bool|string 498 */ 499 function display_browse_field($recordid, $template) { 500 global $DB; 501 $content = $this->get_data_content($recordid); 502 if (!$content || !isset($content->content)) { 503 return ''; 504 } 505 $options = new stdClass(); 506 if ($this->field->param1 == '1') { 507 // We are autolinking this field, so disable linking within us. 508 $options->filter = false; 509 } 510 $options->para = false; 511 $str = format_text($content->content, $content->content1, $options); 512 return $str; 513 } 514 515 /** 516 * Update the content of one data field in the data_content table 517 * @global object 518 * @param int $recordid 519 * @param mixed $value 520 * @param string $name 521 * @return bool 522 */ 523 function update_content($recordid, $value, $name=''){ 524 global $DB; 525 526 $content = new stdClass(); 527 $content->fieldid = $this->field->id; 528 $content->recordid = $recordid; 529 $content->content = clean_param($value, PARAM_NOTAGS); 530 531 if ($oldcontent = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { 532 $content->id = $oldcontent->id; 533 return $DB->update_record('data_content', $content); 534 } else { 535 return $DB->insert_record('data_content', $content); 536 } 537 } 538 539 /** 540 * Delete all content associated with the field 541 * 542 * @global object 543 * @param int $recordid 544 * @return bool 545 */ 546 function delete_content($recordid=0) { 547 global $DB; 548 549 if ($recordid) { 550 $conditions = array('fieldid'=>$this->field->id, 'recordid'=>$recordid); 551 } else { 552 $conditions = array('fieldid'=>$this->field->id); 553 } 554 555 $rs = $DB->get_recordset('data_content', $conditions); 556 if ($rs->valid()) { 557 $fs = get_file_storage(); 558 foreach ($rs as $content) { 559 $fs->delete_area_files($this->context->id, 'mod_data', 'content', $content->id); 560 } 561 } 562 $rs->close(); 563 564 return $DB->delete_records('data_content', $conditions); 565 } 566 567 /** 568 * Check if a field from an add form is empty 569 * 570 * @param mixed $value 571 * @param mixed $name 572 * @return bool 573 */ 574 function notemptyfield($value, $name) { 575 return !empty($value); 576 } 577 578 /** 579 * Just in case a field needs to print something before the whole form 580 */ 581 function print_before_form() { 582 } 583 584 /** 585 * Just in case a field needs to print something after the whole form 586 */ 587 function print_after_form() { 588 } 589 590 591 /** 592 * Returns the sortable field for the content. By default, it's just content 593 * but for some plugins, it could be content 1 - content4 594 * 595 * @return string 596 */ 597 function get_sort_field() { 598 return 'content'; 599 } 600 601 /** 602 * Returns the SQL needed to refer to the column. Some fields may need to CAST() etc. 603 * 604 * @param string $fieldname 605 * @return string $fieldname 606 */ 607 function get_sort_sql($fieldname) { 608 return $fieldname; 609 } 610 611 /** 612 * Returns the name/type of the field 613 * 614 * @return string 615 */ 616 function name() { 617 return get_string('fieldtypelabel', "datafield_$this->type"); 618 } 619 620 /** 621 * Prints the respective type icon 622 * 623 * @global object 624 * @return string 625 */ 626 function image() { 627 global $OUTPUT; 628 629 return $OUTPUT->pix_icon('field/' . $this->type, $this->type, 'data'); 630 } 631 632 /** 633 * Per default, it is assumed that fields support text exporting. 634 * Override this (return false) on fields not supporting text exporting. 635 * 636 * @return bool true 637 */ 638 function text_export_supported() { 639 return true; 640 } 641 642 /** 643 * Per default, it is assumed that fields do not support file exporting. Override this (return true) 644 * on fields supporting file export. You will also have to implement export_file_value(). 645 * 646 * @return bool true if field will export a file, false otherwise 647 */ 648 public function file_export_supported(): bool { 649 return false; 650 } 651 652 /** 653 * Per default, does not return a file (just null). 654 * Override this in fields class, if you want your field to export a file content. 655 * In case you are exporting a file value, export_text_value() should return the corresponding file name. 656 * 657 * @param stdClass $record 658 * @return null|string the file content as string or null, if no file content is being provided 659 */ 660 public function export_file_value(stdClass $record): null|string { 661 return null; 662 } 663 664 /** 665 * Per default, a field does not support the import of files. 666 * 667 * A field type can overwrite this function and return true. In this case it also has to implement the function 668 * import_file_value(). 669 * 670 * @return false means file imports are not supported 671 */ 672 public function file_import_supported(): bool { 673 return false; 674 } 675 676 /** 677 * Returns a stored_file object for exporting a file of a given record. 678 * 679 * @param int $contentid content id 680 * @param string $filecontent the content of the file as string 681 * @param string $filename the filename the file should have 682 */ 683 public function import_file_value(int $contentid, string $filecontent, string $filename): void { 684 return; 685 } 686 687 /** 688 * Per default, return the record's text value only from the "content" field. 689 * Override this in fields class if necessary. 690 * 691 * @param stdClass $record 692 * @return string 693 */ 694 public function export_text_value(stdClass $record) { 695 if ($this->text_export_supported()) { 696 return $record->content; 697 } 698 return ''; 699 } 700 701 /** 702 * @param string $relativepath 703 * @return bool false 704 */ 705 function file_ok($relativepath) { 706 return false; 707 } 708 709 /** 710 * Returns the priority for being indexed by globalsearch 711 * 712 * @return int 713 */ 714 public static function get_priority() { 715 return static::$priority; 716 } 717 718 /** 719 * Returns the presentable string value for a field content. 720 * 721 * The returned string should be plain text. 722 * 723 * @param stdClass $content 724 * @return string 725 */ 726 public static function get_content_value($content) { 727 return trim($content->content, "\r\n "); 728 } 729 730 /** 731 * Return the plugin configs for external functions, 732 * in some cases the configs will need formatting or be returned only if the current user has some capabilities enabled. 733 * 734 * @return array the list of config parameters 735 * @since Moodle 3.3 736 */ 737 public function get_config_for_external() { 738 // Return all the field configs to null (maybe there is a private key for a service or something similar there). 739 $configs = []; 740 for ($i = 1; $i <= 10; $i++) { 741 $configs["param$i"] = null; 742 } 743 return $configs; 744 } 745 } 746 747 748 /** 749 * Given a template and a dataid, generate a default case template 750 * 751 * @param stdClass $data the mod_data record. 752 * @param string $template the template name 753 * @param int $recordid the entry record 754 * @param bool $form print a form instead of data 755 * @param bool $update if the function update the $data object or not 756 * @return string the template content or an empty string if no content is available (for instance, when database has no fields). 757 */ 758 function data_generate_default_template(&$data, $template, $recordid = 0, $form = false, $update = true) { 759 global $DB; 760 761 if (!$data || !$template) { 762 return ''; 763 } 764 765 // These templates are empty by default (they have no content). 766 $emptytemplates = [ 767 'csstemplate', 768 'jstemplate', 769 'listtemplateheader', 770 'listtemplatefooter', 771 'rsstitletemplate', 772 ]; 773 if (in_array($template, $emptytemplates)) { 774 return ''; 775 } 776 777 $manager = manager::create_from_instance($data); 778 if (empty($manager->get_fields())) { 779 // No template will be returned if there are no fields. 780 return ''; 781 } 782 783 $templateclass = \mod_data\template::create_default_template($manager, $template, $form); 784 $templatecontent = $templateclass->get_template_content(); 785 786 if ($update) { 787 // Update the database instance. 788 $newdata = new stdClass(); 789 $newdata->id = $data->id; 790 $newdata->{$template} = $templatecontent; 791 $DB->update_record('data', $newdata); 792 $data->{$template} = $templatecontent; 793 } 794 795 return $templatecontent; 796 } 797 798 /** 799 * Build the form elements to manage tags for a record. 800 * 801 * @param int|bool $recordid 802 * @param string[] $selected raw tag names 803 * @return string 804 */ 805 function data_generate_tag_form($recordid = false, $selected = []) { 806 global $CFG, $DB, $OUTPUT, $PAGE; 807 808 $tagtypestoshow = \core_tag_area::get_showstandard('mod_data', 'data_records'); 809 $showstandard = ($tagtypestoshow != core_tag_tag::HIDE_STANDARD); 810 $typenewtags = ($tagtypestoshow != core_tag_tag::STANDARD_ONLY); 811 812 $str = html_writer::start_tag('div', array('class' => 'datatagcontrol')); 813 814 $namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname'; 815 816 $tagcollid = \core_tag_area::get_collection('mod_data', 'data_records'); 817 $tags = []; 818 $selectedtags = []; 819 820 if ($showstandard) { 821 $tags += $DB->get_records_menu('tag', array('isstandard' => 1, 'tagcollid' => $tagcollid), 822 $namefield, 'id,' . $namefield . ' as fieldname'); 823 } 824 825 if ($recordid) { 826 $selectedtags += core_tag_tag::get_item_tags_array('mod_data', 'data_records', $recordid); 827 } 828 829 if (!empty($selected)) { 830 list($sql, $params) = $DB->get_in_or_equal($selected, SQL_PARAMS_NAMED); 831 $params['tagcollid'] = $tagcollid; 832 $sql = "SELECT id, $namefield FROM {tag} WHERE tagcollid = :tagcollid AND rawname $sql"; 833 $selectedtags += $DB->get_records_sql_menu($sql, $params); 834 } 835 836 $tags += $selectedtags; 837 838 $str .= '<select class="custom-select" name="tags[]" id="tags" multiple>'; 839 foreach ($tags as $tagid => $tag) { 840 $selected = key_exists($tagid, $selectedtags) ? 'selected' : ''; 841 $str .= "<option value='$tag' $selected>$tag</option>"; 842 } 843 $str .= '</select>'; 844 845 if (has_capability('moodle/tag:manage', context_system::instance()) && $showstandard) { 846 $url = new moodle_url('/tag/manage.php', array('tc' => core_tag_area::get_collection('mod_data', 847 'data_records'))); 848 $str .= ' ' . $OUTPUT->action_link($url, get_string('managestandardtags', 'tag')); 849 } 850 851 $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array( 852 '#tags', 853 $typenewtags, 854 '', 855 get_string('entertags', 'tag'), 856 false, 857 $showstandard, 858 get_string('noselection', 'form') 859 ) 860 ); 861 862 $str .= html_writer::end_tag('div'); 863 864 return $str; 865 } 866 867 868 /** 869 * Search for a field name and replaces it with another one in all the 870 * form templates. Set $newfieldname as '' if you want to delete the 871 * field from the form. 872 * 873 * @global object 874 * @param object $data 875 * @param string $searchfieldname 876 * @param string $newfieldname 877 * @return bool 878 */ 879 function data_replace_field_in_templates($data, $searchfieldname, $newfieldname) { 880 global $DB; 881 882 $newdata = (object)['id' => $data->id]; 883 $update = false; 884 $templates = ['listtemplate', 'singletemplate', 'asearchtemplate', 'addtemplate', 'rsstemplate']; 885 foreach ($templates as $templatename) { 886 if (empty($data->$templatename)) { 887 continue; 888 } 889 $search = [ 890 '[[' . $searchfieldname . ']]', 891 '[[' . $searchfieldname . '#id]]', 892 '[[' . $searchfieldname . '#name]]', 893 '[[' . $searchfieldname . '#description]]', 894 ]; 895 if (empty($newfieldname)) { 896 $replace = ['', '', '', '']; 897 } else { 898 $replace = [ 899 '[[' . $newfieldname . ']]', 900 '[[' . $newfieldname . '#id]]', 901 '[[' . $newfieldname . '#name]]', 902 '[[' . $newfieldname . '#description]]', 903 ]; 904 } 905 $newdata->{$templatename} = str_ireplace($search, $replace, $data->{$templatename} ?? ''); 906 $update = true; 907 } 908 if (!$update) { 909 return true; 910 } 911 return $DB->update_record('data', $newdata); 912 } 913 914 915 /** 916 * Appends a new field at the end of the form template. 917 * 918 * @global object 919 * @param object $data 920 * @param string $newfieldname 921 * @return bool if the field has been added or not 922 */ 923 function data_append_new_field_to_templates($data, $newfieldname): bool { 924 global $DB, $OUTPUT; 925 926 $newdata = (object)['id' => $data->id]; 927 $update = false; 928 $templates = ['singletemplate', 'addtemplate', 'rsstemplate']; 929 foreach ($templates as $templatename) { 930 if (empty($data->$templatename) 931 || strpos($data->$templatename, "[[$newfieldname]]") !== false 932 || strpos($data->$templatename, "##otherfields##") !== false 933 ) { 934 continue; 935 } 936 $newdata->$templatename = $data->$templatename; 937 $fields = [[ 938 'fieldname' => '[[' . $newfieldname . '#name]]', 939 'fieldcontent' => '[[' . $newfieldname . ']]', 940 ]]; 941 $newdata->$templatename .= $OUTPUT->render_from_template( 942 'mod_data/fields_otherfields', 943 ['fields' => $fields, 'classes' => 'added_field'] 944 ); 945 $update = true; 946 } 947 if (!$update) { 948 return false; 949 } 950 return $DB->update_record('data', $newdata); 951 } 952 953 954 /** 955 * given a field name 956 * this function creates an instance of the particular subfield class 957 * 958 * @global object 959 * @param string $name 960 * @param object $data 961 * @return object|bool 962 */ 963 function data_get_field_from_name($name, $data){ 964 global $DB; 965 966 $field = $DB->get_record('data_fields', array('name'=>$name, 'dataid'=>$data->id)); 967 968 if ($field) { 969 return data_get_field($field, $data); 970 } else { 971 return false; 972 } 973 } 974 975 /** 976 * given a field id 977 * this function creates an instance of the particular subfield class 978 * 979 * @global object 980 * @param int $fieldid 981 * @param object $data 982 * @return bool|object 983 */ 984 function data_get_field_from_id($fieldid, $data){ 985 global $DB; 986 987 $field = $DB->get_record('data_fields', array('id'=>$fieldid, 'dataid'=>$data->id)); 988 989 if ($field) { 990 return data_get_field($field, $data); 991 } else { 992 return false; 993 } 994 } 995 996 /** 997 * given a field id 998 * this function creates an instance of the particular subfield class 999 * 1000 * @global object 1001 * @param string $type 1002 * @param object $data 1003 * @return object 1004 */ 1005 function data_get_field_new($type, $data) { 1006 global $CFG; 1007 1008 $filepath = $CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php'; 1009 // It should never access this method if the subfield class doesn't exist. 1010 if (!file_exists($filepath)) { 1011 throw new \moodle_exception('invalidfieldtype', 'data'); 1012 } 1013 require_once($filepath); 1014 $newfield = 'data_field_'.$type; 1015 $newfield = new $newfield(0, $data); 1016 return $newfield; 1017 } 1018 1019 /** 1020 * returns a subclass field object given a record of the field, used to 1021 * invoke plugin methods 1022 * input: $param $field - record from db 1023 * 1024 * @global object 1025 * @param stdClass $field the field record 1026 * @param stdClass $data the data instance 1027 * @param stdClass|null $cm optional course module data 1028 * @return data_field_base the field object instance or data_field_base if unkown type 1029 */ 1030 function data_get_field(stdClass $field, stdClass $data, ?stdClass $cm=null): data_field_base { 1031 global $CFG; 1032 if (!isset($field->type)) { 1033 return new data_field_base($field); 1034 } 1035 $filepath = $CFG->dirroot.'/mod/data/field/'.$field->type.'/field.class.php'; 1036 if (!file_exists($filepath)) { 1037 return new data_field_base($field); 1038 } 1039 require_once($filepath); 1040 $newfield = 'data_field_'.$field->type; 1041 $newfield = new $newfield($field, $data, $cm); 1042 return $newfield; 1043 } 1044 1045 1046 /** 1047 * Given record object (or id), returns true if the record belongs to the current user 1048 * 1049 * @global object 1050 * @global object 1051 * @param mixed $record record object or id 1052 * @return bool 1053 */ 1054 function data_isowner($record) { 1055 global $USER, $DB; 1056 1057 if (!isloggedin()) { // perf shortcut 1058 return false; 1059 } 1060 1061 if (!is_object($record)) { 1062 if (!$record = $DB->get_record('data_records', array('id'=>$record))) { 1063 return false; 1064 } 1065 } 1066 1067 return ($record->userid == $USER->id); 1068 } 1069 1070 /** 1071 * has a user reached the max number of entries? 1072 * 1073 * @param object $data 1074 * @return bool 1075 */ 1076 function data_atmaxentries($data){ 1077 if (!$data->maxentries){ 1078 return false; 1079 1080 } else { 1081 return (data_numentries($data) >= $data->maxentries); 1082 } 1083 } 1084 1085 /** 1086 * returns the number of entries already made by this user 1087 * 1088 * @global object 1089 * @global object 1090 * @param object $data 1091 * @return int 1092 */ 1093 function data_numentries($data, $userid=null) { 1094 global $USER, $DB; 1095 if ($userid === null) { 1096 $userid = $USER->id; 1097 } 1098 $sql = 'SELECT COUNT(*) FROM {data_records} WHERE dataid=? AND userid=?'; 1099 return $DB->count_records_sql($sql, array($data->id, $userid)); 1100 } 1101 1102 /** 1103 * function that takes in a dataid and adds a record 1104 * this is used everytime an add template is submitted 1105 * 1106 * @global object 1107 * @global object 1108 * @param object $data 1109 * @param int $groupid 1110 * @param int $userid 1111 * @return bool 1112 */ 1113 function data_add_record($data, $groupid = 0, $userid = null) { 1114 global $USER, $DB; 1115 1116 $cm = get_coursemodule_from_instance('data', $data->id); 1117 $context = context_module::instance($cm->id); 1118 1119 $record = new stdClass(); 1120 $record->userid = $userid ?? $USER->id; 1121 $record->dataid = $data->id; 1122 $record->groupid = $groupid; 1123 $record->timecreated = $record->timemodified = time(); 1124 if (has_capability('mod/data:approve', $context)) { 1125 $record->approved = 1; 1126 } else { 1127 $record->approved = 0; 1128 } 1129 $record->id = $DB->insert_record('data_records', $record); 1130 1131 // Trigger an event for creating this record. 1132 $event = \mod_data\event\record_created::create(array( 1133 'objectid' => $record->id, 1134 'context' => $context, 1135 'other' => array( 1136 'dataid' => $data->id 1137 ) 1138 )); 1139 $event->trigger(); 1140 1141 $course = get_course($cm->course); 1142 data_update_completion_state($data, $course, $cm); 1143 1144 return $record->id; 1145 } 1146 1147 /** 1148 * check the multple existence any tag in a template 1149 * 1150 * check to see if there are 2 or more of the same tag being used. 1151 * 1152 * @global object 1153 * @param int $dataid, 1154 * @param string $template 1155 * @return bool 1156 */ 1157 function data_tags_check($dataid, $template) { 1158 global $DB, $OUTPUT; 1159 1160 // first get all the possible tags 1161 $fields = $DB->get_records('data_fields', array('dataid'=>$dataid)); 1162 // then we generate strings to replace 1163 $tagsok = true; // let's be optimistic 1164 foreach ($fields as $field){ 1165 $pattern="/\[\[" . preg_quote($field->name, '/') . "\]\]/i"; 1166 if (preg_match_all($pattern, $template, $dummy)>1){ 1167 $tagsok = false; 1168 echo $OUTPUT->notification('[['.$field->name.']] - '.get_string('multipletags','data')); 1169 } 1170 } 1171 // else return true 1172 return $tagsok; 1173 } 1174 1175 /** 1176 * Adds an instance of a data 1177 * 1178 * @param stdClass $data 1179 * @param mod_data_mod_form $mform 1180 * @return int intance id 1181 */ 1182 function data_add_instance($data, $mform = null) { 1183 global $DB, $CFG; 1184 require_once($CFG->dirroot.'/mod/data/locallib.php'); 1185 1186 if (empty($data->assessed)) { 1187 $data->assessed = 0; 1188 } 1189 1190 if (empty($data->ratingtime) || empty($data->assessed)) { 1191 $data->assesstimestart = 0; 1192 $data->assesstimefinish = 0; 1193 } 1194 1195 $data->timemodified = time(); 1196 1197 $data->id = $DB->insert_record('data', $data); 1198 1199 // Add calendar events if necessary. 1200 data_set_events($data); 1201 if (!empty($data->completionexpected)) { 1202 \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $data->completionexpected); 1203 } 1204 1205 data_grade_item_update($data); 1206 1207 return $data->id; 1208 } 1209 1210 /** 1211 * updates an instance of a data 1212 * 1213 * @global object 1214 * @param object $data 1215 * @return bool 1216 */ 1217 function data_update_instance($data) { 1218 global $DB, $CFG; 1219 require_once($CFG->dirroot.'/mod/data/locallib.php'); 1220 1221 $data->timemodified = time(); 1222 if (!empty($data->instance)) { 1223 $data->id = $data->instance; 1224 } 1225 1226 if (empty($data->assessed)) { 1227 $data->assessed = 0; 1228 } 1229 1230 if (empty($data->ratingtime) or empty($data->assessed)) { 1231 $data->assesstimestart = 0; 1232 $data->assesstimefinish = 0; 1233 } 1234 1235 if (empty($data->notification)) { 1236 $data->notification = 0; 1237 } 1238 1239 $DB->update_record('data', $data); 1240 1241 // Add calendar events if necessary. 1242 data_set_events($data); 1243 $completionexpected = (!empty($data->completionexpected)) ? $data->completionexpected : null; 1244 \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $completionexpected); 1245 1246 data_grade_item_update($data); 1247 1248 return true; 1249 1250 } 1251 1252 /** 1253 * deletes an instance of a data 1254 * 1255 * @global object 1256 * @param int $id 1257 * @return bool 1258 */ 1259 function data_delete_instance($id) { // takes the dataid 1260 global $DB, $CFG; 1261 1262 if (!$data = $DB->get_record('data', array('id'=>$id))) { 1263 return false; 1264 } 1265 1266 $cm = get_coursemodule_from_instance('data', $data->id); 1267 $context = context_module::instance($cm->id); 1268 1269 // Delete all information related to fields. 1270 $fields = $DB->get_records('data_fields', ['dataid' => $id]); 1271 foreach ($fields as $field) { 1272 $todelete = data_get_field($field, $data, $cm); 1273 $todelete->delete_field(); 1274 } 1275 1276 // Remove old calendar events. 1277 $events = $DB->get_records('event', array('modulename' => 'data', 'instance' => $id)); 1278 foreach ($events as $event) { 1279 $event = calendar_event::load($event); 1280 $event->delete(); 1281 } 1282 1283 // cleanup gradebook 1284 data_grade_item_delete($data); 1285 1286 // Delete the instance itself 1287 // We must delete the module record after we delete the grade item. 1288 $result = $DB->delete_records('data', array('id'=>$id)); 1289 1290 return $result; 1291 } 1292 1293 /** 1294 * returns a summary of data activity of this user 1295 * 1296 * @global object 1297 * @param object $course 1298 * @param object $user 1299 * @param object $mod 1300 * @param object $data 1301 * @return object|null 1302 */ 1303 function data_user_outline($course, $user, $mod, $data) { 1304 global $DB, $CFG; 1305 require_once("$CFG->libdir/gradelib.php"); 1306 1307 $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id); 1308 if (empty($grades->items[0]->grades)) { 1309 $grade = false; 1310 } else { 1311 $grade = reset($grades->items[0]->grades); 1312 } 1313 1314 1315 if ($countrecords = $DB->count_records('data_records', array('dataid'=>$data->id, 'userid'=>$user->id))) { 1316 $result = new stdClass(); 1317 $result->info = get_string('numrecords', 'data', $countrecords); 1318 $lastrecord = $DB->get_record_sql('SELECT id,timemodified FROM {data_records} 1319 WHERE dataid = ? AND userid = ? 1320 ORDER BY timemodified DESC', array($data->id, $user->id), true); 1321 $result->time = $lastrecord->timemodified; 1322 if ($grade) { 1323 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) { 1324 $result->info .= ', ' . get_string('gradenoun') . ': ' . $grade->str_long_grade; 1325 } else { 1326 $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades'); 1327 } 1328 } 1329 return $result; 1330 } else if ($grade) { 1331 $result = (object) [ 1332 'time' => grade_get_date_for_user_grade($grade, $user), 1333 ]; 1334 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) { 1335 $result->info = get_string('gradenoun') . ': ' . $grade->str_long_grade; 1336 } else { 1337 $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades'); 1338 } 1339 1340 return $result; 1341 } 1342 return NULL; 1343 } 1344 1345 /** 1346 * Prints all the records uploaded by this user 1347 * 1348 * @global object 1349 * @param object $course 1350 * @param object $user 1351 * @param object $mod 1352 * @param object $data 1353 */ 1354 function data_user_complete($course, $user, $mod, $data) { 1355 global $DB, $CFG, $OUTPUT; 1356 require_once("$CFG->libdir/gradelib.php"); 1357 1358 $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id); 1359 if (!empty($grades->items[0]->grades)) { 1360 $grade = reset($grades->items[0]->grades); 1361 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) { 1362 echo $OUTPUT->container(get_string('gradenoun') . ': ' . $grade->str_long_grade); 1363 if ($grade->str_feedback) { 1364 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback); 1365 } 1366 } else { 1367 echo $OUTPUT->container(get_string('gradenoun') . ': ' . get_string('hidden', 'grades')); 1368 } 1369 } 1370 $records = $DB->get_records( 1371 'data_records', 1372 ['dataid' => $data->id, 'userid' => $user->id], 1373 'timemodified DESC' 1374 ); 1375 if ($records) { 1376 $manager = manager::create_from_instance($data); 1377 $parser = $manager->get_template('singletemplate'); 1378 echo $parser->parse_entries($records); 1379 } 1380 } 1381 1382 /** 1383 * Return grade for given user or all users. 1384 * 1385 * @global object 1386 * @param object $data 1387 * @param int $userid optional user id, 0 means all users 1388 * @return array array of grades, false if none 1389 */ 1390 function data_get_user_grades($data, $userid=0) { 1391 global $CFG; 1392 1393 require_once($CFG->dirroot.'/rating/lib.php'); 1394 1395 $ratingoptions = new stdClass; 1396 $ratingoptions->component = 'mod_data'; 1397 $ratingoptions->ratingarea = 'entry'; 1398 $ratingoptions->modulename = 'data'; 1399 $ratingoptions->moduleid = $data->id; 1400 1401 $ratingoptions->userid = $userid; 1402 $ratingoptions->aggregationmethod = $data->assessed; 1403 $ratingoptions->scaleid = $data->scale; 1404 $ratingoptions->itemtable = 'data_records'; 1405 $ratingoptions->itemtableusercolumn = 'userid'; 1406 1407 $rm = new rating_manager(); 1408 return $rm->get_user_grades($ratingoptions); 1409 } 1410 1411 /** 1412 * Update activity grades 1413 * 1414 * @category grade 1415 * @param object $data 1416 * @param int $userid specific user only, 0 means all 1417 * @param bool $nullifnone 1418 */ 1419 function data_update_grades($data, $userid=0, $nullifnone=true) { 1420 global $CFG, $DB; 1421 require_once($CFG->libdir.'/gradelib.php'); 1422 1423 if (!$data->assessed) { 1424 data_grade_item_update($data); 1425 1426 } else if ($grades = data_get_user_grades($data, $userid)) { 1427 data_grade_item_update($data, $grades); 1428 1429 } else if ($userid and $nullifnone) { 1430 $grade = new stdClass(); 1431 $grade->userid = $userid; 1432 $grade->rawgrade = NULL; 1433 data_grade_item_update($data, $grade); 1434 1435 } else { 1436 data_grade_item_update($data); 1437 } 1438 } 1439 1440 /** 1441 * Update/create grade item for given data 1442 * 1443 * @category grade 1444 * @param stdClass $data A database instance with extra cmidnumber property 1445 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook 1446 * @return object grade_item 1447 */ 1448 function data_grade_item_update($data, $grades=NULL) { 1449 global $CFG; 1450 require_once($CFG->libdir.'/gradelib.php'); 1451 1452 $params = array('itemname'=>$data->name, 'idnumber'=>$data->cmidnumber); 1453 1454 if (!$data->assessed or $data->scale == 0) { 1455 $params['gradetype'] = GRADE_TYPE_NONE; 1456 1457 } else if ($data->scale > 0) { 1458 $params['gradetype'] = GRADE_TYPE_VALUE; 1459 $params['grademax'] = $data->scale; 1460 $params['grademin'] = 0; 1461 1462 } else if ($data->scale < 0) { 1463 $params['gradetype'] = GRADE_TYPE_SCALE; 1464 $params['scaleid'] = -$data->scale; 1465 } 1466 1467 if ($grades === 'reset') { 1468 $params['reset'] = true; 1469 $grades = NULL; 1470 } 1471 1472 return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, $grades, $params); 1473 } 1474 1475 /** 1476 * Delete grade item for given data 1477 * 1478 * @category grade 1479 * @param object $data object 1480 * @return object grade_item 1481 */ 1482 function data_grade_item_delete($data) { 1483 global $CFG; 1484 require_once($CFG->libdir.'/gradelib.php'); 1485 1486 return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, NULL, array('deleted'=>1)); 1487 } 1488 1489 // junk functions 1490 /** 1491 * takes a list of records, the current data, a search string, 1492 * and mode to display prints the translated template 1493 * 1494 * @deprecated since Moodle 4.1 MDL-75146 - please do not use this function any more. 1495 * @todo MDL-75189 Final deprecation in Moodle 4.5. 1496 * @param string $templatename the template name 1497 * @param array $records the entries records 1498 * @param stdClass $data the database instance object 1499 * @param string $search the current search term 1500 * @param int $page page number for pagination 1501 * @param bool $return if the result should be returned (true) or printed (false) 1502 * @param moodle_url|null $jumpurl a moodle_url by which to jump back to the record list (can be null) 1503 * @return mixed string with all parsed entries or nothing if $return is false 1504 */ 1505 function data_print_template($templatename, $records, $data, $search='', $page=0, $return=false, moodle_url $jumpurl=null) { 1506 debugging( 1507 'data_print_template is deprecated. Use mod_data\\manager::get_template and mod_data\\template::parse_entries instead', 1508 DEBUG_DEVELOPER 1509 ); 1510 1511 $options = [ 1512 'search' => $search, 1513 'page' => $page, 1514 ]; 1515 if ($jumpurl) { 1516 $options['baseurl'] = $jumpurl; 1517 } 1518 $manager = manager::create_from_instance($data); 1519 $parser = $manager->get_template($templatename, $options); 1520 $content = $parser->parse_entries($records); 1521 if ($return) { 1522 return $content; 1523 } 1524 echo $content; 1525 } 1526 1527 /** 1528 * Return rating related permissions 1529 * 1530 * @param string $contextid the context id 1531 * @param string $component the component to get rating permissions for 1532 * @param string $ratingarea the rating area to get permissions for 1533 * @return array an associative array of the user's rating permissions 1534 */ 1535 function data_rating_permissions($contextid, $component, $ratingarea) { 1536 $context = context::instance_by_id($contextid, MUST_EXIST); 1537 if ($component != 'mod_data' || $ratingarea != 'entry') { 1538 return null; 1539 } 1540 return array( 1541 'view' => has_capability('mod/data:viewrating',$context), 1542 'viewany' => has_capability('mod/data:viewanyrating',$context), 1543 'viewall' => has_capability('mod/data:viewallratings',$context), 1544 'rate' => has_capability('mod/data:rate',$context) 1545 ); 1546 } 1547 1548 /** 1549 * Validates a submitted rating 1550 * @param array $params submitted data 1551 * context => object the context in which the rated items exists [required] 1552 * itemid => int the ID of the object being rated 1553 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required] 1554 * rating => int the submitted rating 1555 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required] 1556 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required] 1557 * @return boolean true if the rating is valid. Will throw rating_exception if not 1558 */ 1559 function data_rating_validate($params) { 1560 global $DB, $USER; 1561 1562 // Check the component is mod_data 1563 if ($params['component'] != 'mod_data') { 1564 throw new rating_exception('invalidcomponent'); 1565 } 1566 1567 // Check the ratingarea is entry (the only rating area in data module) 1568 if ($params['ratingarea'] != 'entry') { 1569 throw new rating_exception('invalidratingarea'); 1570 } 1571 1572 // Check the rateduserid is not the current user .. you can't rate your own entries 1573 if ($params['rateduserid'] == $USER->id) { 1574 throw new rating_exception('nopermissiontorate'); 1575 } 1576 1577 $datasql = "SELECT d.id as dataid, d.scale, d.course, r.userid as userid, d.approval, r.approved, r.timecreated, d.assesstimestart, d.assesstimefinish, r.groupid 1578 FROM {data_records} r 1579 JOIN {data} d ON r.dataid = d.id 1580 WHERE r.id = :itemid"; 1581 $dataparams = array('itemid'=>$params['itemid']); 1582 if (!$info = $DB->get_record_sql($datasql, $dataparams)) { 1583 //item doesn't exist 1584 throw new rating_exception('invaliditemid'); 1585 } 1586 1587 if ($info->scale != $params['scaleid']) { 1588 //the scale being submitted doesnt match the one in the database 1589 throw new rating_exception('invalidscaleid'); 1590 } 1591 1592 //check that the submitted rating is valid for the scale 1593 1594 // lower limit 1595 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) { 1596 throw new rating_exception('invalidnum'); 1597 } 1598 1599 // upper limit 1600 if ($info->scale < 0) { 1601 //its a custom scale 1602 $scalerecord = $DB->get_record('scale', array('id' => -$info->scale)); 1603 if ($scalerecord) { 1604 $scalearray = explode(',', $scalerecord->scale); 1605 if ($params['rating'] > count($scalearray)) { 1606 throw new rating_exception('invalidnum'); 1607 } 1608 } else { 1609 throw new rating_exception('invalidscaleid'); 1610 } 1611 } else if ($params['rating'] > $info->scale) { 1612 //if its numeric and submitted rating is above maximum 1613 throw new rating_exception('invalidnum'); 1614 } 1615 1616 if ($info->approval && !$info->approved) { 1617 //database requires approval but this item isnt approved 1618 throw new rating_exception('nopermissiontorate'); 1619 } 1620 1621 // check the item we're rating was created in the assessable time window 1622 if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) { 1623 if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) { 1624 throw new rating_exception('notavailable'); 1625 } 1626 } 1627 1628 $course = $DB->get_record('course', array('id'=>$info->course), '*', MUST_EXIST); 1629 $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST); 1630 $context = context_module::instance($cm->id); 1631 1632 // if the supplied context doesnt match the item's context 1633 if ($context->id != $params['context']->id) { 1634 throw new rating_exception('invalidcontext'); 1635 } 1636 1637 // Make sure groups allow this user to see the item they're rating 1638 $groupid = $info->groupid; 1639 if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used 1640 if (!groups_group_exists($groupid)) { // Can't find group 1641 throw new rating_exception('cannotfindgroup');//something is wrong 1642 } 1643 1644 if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) { 1645 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS 1646 throw new rating_exception('notmemberofgroup'); 1647 } 1648 } 1649 1650 return true; 1651 } 1652 1653 /** 1654 * Can the current user see ratings for a given itemid? 1655 * 1656 * @param array $params submitted data 1657 * contextid => int contextid [required] 1658 * component => The component for this module - should always be mod_data [required] 1659 * ratingarea => object the context in which the rated items exists [required] 1660 * itemid => int the ID of the object being rated [required] 1661 * scaleid => int scale id [optional] 1662 * @return bool 1663 * @throws coding_exception 1664 * @throws rating_exception 1665 */ 1666 function mod_data_rating_can_see_item_ratings($params) { 1667 global $DB; 1668 1669 // Check the component is mod_data. 1670 if (!isset($params['component']) || $params['component'] != 'mod_data') { 1671 throw new rating_exception('invalidcomponent'); 1672 } 1673 1674 // Check the ratingarea is entry (the only rating area in data). 1675 if (!isset($params['ratingarea']) || $params['ratingarea'] != 'entry') { 1676 throw new rating_exception('invalidratingarea'); 1677 } 1678 1679 if (!isset($params['itemid'])) { 1680 throw new rating_exception('invaliditemid'); 1681 } 1682 1683 $datasql = "SELECT d.id as dataid, d.course, r.groupid 1684 FROM {data_records} r 1685 JOIN {data} d ON r.dataid = d.id 1686 WHERE r.id = :itemid"; 1687 $dataparams = array('itemid' => $params['itemid']); 1688 if (!$info = $DB->get_record_sql($datasql, $dataparams)) { 1689 // Item doesn't exist. 1690 throw new rating_exception('invaliditemid'); 1691 } 1692 1693 // User can see ratings of all participants. 1694 if ($info->groupid == 0) { 1695 return true; 1696 } 1697 1698 $course = $DB->get_record('course', array('id' => $info->course), '*', MUST_EXIST); 1699 $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST); 1700 1701 // Make sure groups allow this user to see the item they're rating. 1702 return groups_group_visible($info->groupid, $course, $cm); 1703 } 1704 1705 1706 /** 1707 * function that takes in the current data, number of items per page, 1708 * a search string and prints a preference box in view.php 1709 * 1710 * This preference box prints a searchable advanced search template if 1711 * a) A template is defined 1712 * b) The advanced search checkbox is checked. 1713 * 1714 * @global object 1715 * @global object 1716 * @param object $data 1717 * @param int $perpage 1718 * @param string $search 1719 * @param string $sort 1720 * @param string $order 1721 * @param array $search_array 1722 * @param int $advanced 1723 * @param string $mode 1724 * @return void 1725 */ 1726 function data_print_preference_form($data, $perpage, $search, $sort='', $order='ASC', $search_array = '', $advanced = 0, $mode= ''){ 1727 global $DB, $PAGE, $OUTPUT; 1728 1729 $cm = get_coursemodule_from_instance('data', $data->id); 1730 $context = context_module::instance($cm->id); 1731 echo '<div class="datapreferences my-5">'; 1732 echo '<form id="options" action="view.php" method="get">'; 1733 echo '<div class="d-flex">'; 1734 echo '<div>'; 1735 echo '<input type="hidden" name="d" value="'.$data->id.'" />'; 1736 if ($mode =='asearch') { 1737 $advanced = 1; 1738 echo '<input type="hidden" name="mode" value="list" />'; 1739 } 1740 echo '<label for="pref_perpage">'.get_string('pagesize','data').'</label> '; 1741 $pagesizes = array(2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9,10=>10,15=>15, 1742 20=>20,30=>30,40=>40,50=>50,100=>100,200=>200,300=>300,400=>400,500=>500,1000=>1000); 1743 echo html_writer::select($pagesizes, 'perpage', $perpage, false, array('id' => 'pref_perpage', 'class' => 'custom-select')); 1744 1745 if ($advanced) { 1746 $regsearchclass = 'search_none'; 1747 $advancedsearchclass = 'search_inline'; 1748 } else { 1749 $regsearchclass = 'search_inline'; 1750 $advancedsearchclass = 'search_none'; 1751 } 1752 echo '<div id="reg_search" class="' . $regsearchclass . ' form-inline" > '; 1753 echo '<label for="pref_search">' . get_string('search') . '</label> <input type="text" ' . 1754 'class="form-control" size="16" name="search" id= "pref_search" value="' . s($search) . '" /></div>'; 1755 echo ' <label for="pref_sortby">'.get_string('sortby').'</label> '; 1756 // foreach field, print the option 1757 echo '<select name="sort" id="pref_sortby" class="custom-select mr-1">'; 1758 if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'name')) { 1759 echo '<optgroup label="'.get_string('fields', 'data').'">'; 1760 foreach ($fields as $field) { 1761 if ($field->id == $sort) { 1762 echo '<option value="'.$field->id.'" selected="selected">'.$field->name.'</option>'; 1763 } else { 1764 echo '<option value="'.$field->id.'">'.$field->name.'</option>'; 1765 } 1766 } 1767 echo '</optgroup>'; 1768 } 1769 $options = array(); 1770 $options[DATA_TIMEADDED] = get_string('timeadded', 'data'); 1771 $options[DATA_TIMEMODIFIED] = get_string('timemodified', 'data'); 1772 $options[DATA_FIRSTNAME] = get_string('authorfirstname', 'data'); 1773 $options[DATA_LASTNAME] = get_string('authorlastname', 'data'); 1774 if ($data->approval and has_capability('mod/data:approve', $context)) { 1775 $options[DATA_APPROVED] = get_string('approved', 'data'); 1776 } 1777 echo '<optgroup label="'.get_string('other', 'data').'">'; 1778 foreach ($options as $key => $name) { 1779 if ($key == $sort) { 1780 echo '<option value="'.$key.'" selected="selected">'.$name.'</option>'; 1781 } else { 1782 echo '<option value="'.$key.'">'.$name.'</option>'; 1783 } 1784 } 1785 echo '</optgroup>'; 1786 echo '</select>'; 1787 echo '<label for="pref_order" class="accesshide">'.get_string('order').'</label>'; 1788 echo '<select id="pref_order" name="order" class="custom-select mr-1">'; 1789 if ($order == 'ASC') { 1790 echo '<option value="ASC" selected="selected">'.get_string('ascending','data').'</option>'; 1791 } else { 1792 echo '<option value="ASC">'.get_string('ascending','data').'</option>'; 1793 } 1794 if ($order == 'DESC') { 1795 echo '<option value="DESC" selected="selected">'.get_string('descending','data').'</option>'; 1796 } else { 1797 echo '<option value="DESC">'.get_string('descending','data').'</option>'; 1798 } 1799 echo '</select>'; 1800 1801 if ($advanced) { 1802 $checked = ' checked="checked" '; 1803 } 1804 else { 1805 $checked = ''; 1806 } 1807 $PAGE->requires->js('/mod/data/data.js'); 1808 echo ' <input type="hidden" name="advanced" value="0" />'; 1809 echo ' <input type="hidden" name="filter" value="1" />'; 1810 echo ' <input type="checkbox" id="advancedcheckbox" name="advanced" value="1" ' . $checked . ' ' . 1811 'onchange="showHideAdvSearch(this.checked);" class="mx-1" />' . 1812 '<label for="advancedcheckbox">' . get_string('advancedsearch', 'data') . '</label>'; 1813 echo '</div>'; 1814 echo '<div id="advsearch-save-sec" class="ml-auto '. $regsearchclass . '">'; 1815 echo ' <input type="submit" class="btn btn-secondary" value="' . get_string('savesettings', 'data') . '" />'; 1816 echo '</div>'; 1817 echo '</div>'; 1818 echo '<div>'; 1819 1820 echo '<br />'; 1821 echo '<div class="' . $advancedsearchclass . '" id="data_adv_form">'; 1822 echo '<table class="boxaligncenter">'; 1823 1824 // print ASC or DESC 1825 echo '<tr><td colspan="2"> </td></tr>'; 1826 $i = 0; 1827 1828 // Determine if we are printing all fields for advanced search, or the template for advanced search 1829 // If a template is not defined, use the deafault template and display all fields. 1830 $asearchtemplate = $data->asearchtemplate; 1831 if (empty($asearchtemplate)) { 1832 $asearchtemplate = data_generate_default_template($data, 'asearchtemplate', 0, false, false); 1833 } 1834 1835 static $fields = array(); 1836 static $dataid = null; 1837 1838 if (empty($dataid)) { 1839 $dataid = $data->id; 1840 } else if ($dataid != $data->id) { 1841 $fields = array(); 1842 } 1843 1844 if (empty($fields)) { 1845 $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id)); 1846 foreach ($fieldrecords as $fieldrecord) { 1847 $fields[]= data_get_field($fieldrecord, $data); 1848 } 1849 } 1850 1851 // Replacing tags 1852 $patterns = array(); 1853 $replacement = array(); 1854 1855 // Then we generate strings to replace for normal tags 1856 $otherfields = []; 1857 foreach ($fields as $field) { 1858 $fieldname = $field->field->name; 1859 $fieldname = preg_quote($fieldname, '/'); 1860 $searchfield = data_get_field_from_id($field->field->id, $data); 1861 1862 if ($searchfield->type === 'unknown') { 1863 continue; 1864 } 1865 if (!empty($search_array[$field->field->id]->data)) { 1866 $searchinput = $searchfield->display_search_field($search_array[$field->field->id]->data); 1867 } else { 1868 $searchinput = $searchfield->display_search_field(); 1869 } 1870 $patterns[] = "/\[\[$fieldname\]\]/i"; 1871 $replacement[] = $searchinput; 1872 // Extra field information. 1873 $patterns[] = "/\[\[$fieldname#name\]\]/i"; 1874 $replacement[] = $field->field->name; 1875 $patterns[] = "/\[\[$fieldname#description\]\]/i"; 1876 $replacement[] = $field->field->description; 1877 // Other fields. 1878 if (strpos($asearchtemplate, "[[" . $field->field->name . "]]") === false) { 1879 $otherfields[] = [ 1880 'fieldname' => $searchfield->field->name, 1881 'fieldcontent' => $searchinput, 1882 ]; 1883 } 1884 } 1885 $patterns[] = "/##otherfields##/"; 1886 if (!empty($otherfields)) { 1887 $replacement[] = $OUTPUT->render_from_template( 1888 'mod_data/fields_otherfields', 1889 ['fields' => $otherfields] 1890 ); 1891 } else { 1892 $replacement[] = ''; 1893 } 1894 1895 $fn = !empty($search_array[DATA_FIRSTNAME]->data) ? $search_array[DATA_FIRSTNAME]->data : ''; 1896 $ln = !empty($search_array[DATA_LASTNAME]->data) ? $search_array[DATA_LASTNAME]->data : ''; 1897 $patterns[] = '/##firstname##/'; 1898 $replacement[] = '<label class="accesshide" for="u_fn">' . get_string('authorfirstname', 'data') . '</label>' . 1899 '<input type="text" class="form-control" size="16" id="u_fn" name="u_fn" value="' . s($fn) . '" />'; 1900 $patterns[] = '/##lastname##/'; 1901 $replacement[] = '<label class="accesshide" for="u_ln">' . get_string('authorlastname', 'data') . '</label>' . 1902 '<input type="text" class="form-control" size="16" id="u_ln" name="u_ln" value="' . s($ln) . '" />'; 1903 1904 if (core_tag_tag::is_enabled('mod_data', 'data_records')) { 1905 $patterns[] = "/##tags##/"; 1906 $selectedtags = isset($search_array[DATA_TAGS]->rawtagnames) ? $search_array[DATA_TAGS]->rawtagnames : []; 1907 $replacement[] = data_generate_tag_form(false, $selectedtags); 1908 } 1909 1910 // actual replacement of the tags 1911 1912 $options = new stdClass(); 1913 $options->para=false; 1914 $options->noclean=true; 1915 echo '<tr><td>'; 1916 echo preg_replace($patterns, $replacement, format_text($asearchtemplate, FORMAT_HTML, $options)); 1917 echo '</td></tr>'; 1918 1919 echo '<tr><td colspan="4"><br/>' . 1920 '<input type="submit" class="btn btn-primary mr-1" value="' . get_string('savesettings', 'data') . '" />' . 1921 '<input type="submit" class="btn btn-secondary" name="resetadv" value="' . get_string('resetsettings', 'data') . '" />' . 1922 '</td></tr>'; 1923 echo '</table>'; 1924 echo '</div>'; 1925 echo '</form>'; 1926 echo '</div>'; 1927 echo '<hr/>'; 1928 } 1929 1930 /** 1931 * @global object 1932 * @global object 1933 * @param object $data 1934 * @param object $record 1935 * @param bool $print if the result must be printed or returner. 1936 * @return void Output echo'd 1937 */ 1938 function data_print_ratings($data, $record, bool $print = true) { 1939 global $OUTPUT; 1940 $result = ''; 1941 if (!empty($record->rating)){ 1942 $result = $OUTPUT->render($record->rating); 1943 } 1944 if (!$print) { 1945 return $result; 1946 } 1947 echo $result; 1948 } 1949 1950 /** 1951 * List the actions that correspond to a view of this module. 1952 * This is used by the participation report. 1953 * 1954 * Note: This is not used by new logging system. Event with 1955 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will 1956 * be considered as view action. 1957 * 1958 * @return array 1959 */ 1960 function data_get_view_actions() { 1961 return array('view'); 1962 } 1963 1964 /** 1965 * List the actions that correspond to a post of this module. 1966 * This is used by the participation report. 1967 * 1968 * Note: This is not used by new logging system. Event with 1969 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING 1970 * will be considered as post action. 1971 * 1972 * @return array 1973 */ 1974 function data_get_post_actions() { 1975 return array('add','update','record delete'); 1976 } 1977 1978 /** 1979 * @param string $name 1980 * @param int $dataid 1981 * @param int $fieldid 1982 * @return bool 1983 */ 1984 function data_fieldname_exists($name, $dataid, $fieldid = 0) { 1985 global $DB; 1986 1987 if (!is_numeric($name)) { 1988 $like = $DB->sql_like('df.name', ':name', false); 1989 } else { 1990 $like = "df.name = :name"; 1991 } 1992 $params = array('name'=>$name); 1993 if ($fieldid) { 1994 $params['dataid'] = $dataid; 1995 $params['fieldid1'] = $fieldid; 1996 $params['fieldid2'] = $fieldid; 1997 return $DB->record_exists_sql("SELECT * FROM {data_fields} df 1998 WHERE $like AND df.dataid = :dataid 1999 AND ((df.id < :fieldid1) OR (df.id > :fieldid2))", $params); 2000 } else { 2001 $params['dataid'] = $dataid; 2002 return $DB->record_exists_sql("SELECT * FROM {data_fields} df 2003 WHERE $like AND df.dataid = :dataid", $params); 2004 } 2005 } 2006 2007 /** 2008 * @param array $fieldinput 2009 */ 2010 function data_convert_arrays_to_strings(&$fieldinput) { 2011 foreach ($fieldinput as $key => $val) { 2012 if (is_array($val)) { 2013 $str = ''; 2014 foreach ($val as $inner) { 2015 $str .= $inner . ','; 2016 } 2017 $str = substr($str, 0, -1); 2018 2019 $fieldinput->$key = $str; 2020 } 2021 } 2022 } 2023 2024 2025 /** 2026 * Converts a database (module instance) to use the Roles System 2027 * 2028 * @global object 2029 * @global object 2030 * @uses CONTEXT_MODULE 2031 * @uses CAP_PREVENT 2032 * @uses CAP_ALLOW 2033 * @param object $data a data object with the same attributes as a record 2034 * from the data database table 2035 * @param int $datamodid the id of the data module, from the modules table 2036 * @param array $teacherroles array of roles that have archetype teacher 2037 * @param array $studentroles array of roles that have archetype student 2038 * @param array $guestroles array of roles that have archetype guest 2039 * @param int $cmid the course_module id for this data instance 2040 * @return boolean data module was converted or not 2041 */ 2042 function data_convert_to_roles($data, $teacherroles=array(), $studentroles=array(), $cmid=NULL) { 2043 global $CFG, $DB, $OUTPUT; 2044 2045 if (!isset($data->participants) && !isset($data->assesspublic) 2046 && !isset($data->groupmode)) { 2047 // We assume that this database has already been converted to use the 2048 // Roles System. above fields get dropped the data module has been 2049 // upgraded to use Roles. 2050 return false; 2051 } 2052 2053 if (empty($cmid)) { 2054 // We were not given the course_module id. Try to find it. 2055 if (!$cm = get_coursemodule_from_instance('data', $data->id)) { 2056 echo $OUTPUT->notification('Could not get the course module for the data'); 2057 return false; 2058 } else { 2059 $cmid = $cm->id; 2060 } 2061 } 2062 $context = context_module::instance($cmid); 2063 2064 2065 // $data->participants: 2066 // 1 - Only teachers can add entries 2067 // 3 - Teachers and students can add entries 2068 switch ($data->participants) { 2069 case 1: 2070 foreach ($studentroles as $studentrole) { 2071 assign_capability('mod/data:writeentry', CAP_PREVENT, $studentrole->id, $context->id); 2072 } 2073 foreach ($teacherroles as $teacherrole) { 2074 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id); 2075 } 2076 break; 2077 case 3: 2078 foreach ($studentroles as $studentrole) { 2079 assign_capability('mod/data:writeentry', CAP_ALLOW, $studentrole->id, $context->id); 2080 } 2081 foreach ($teacherroles as $teacherrole) { 2082 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id); 2083 } 2084 break; 2085 } 2086 2087 // $data->assessed: 2088 // 2 - Only teachers can rate posts 2089 // 1 - Everyone can rate posts 2090 // 0 - No one can rate posts 2091 switch ($data->assessed) { 2092 case 0: 2093 foreach ($studentroles as $studentrole) { 2094 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id); 2095 } 2096 foreach ($teacherroles as $teacherrole) { 2097 assign_capability('mod/data:rate', CAP_PREVENT, $teacherrole->id, $context->id); 2098 } 2099 break; 2100 case 1: 2101 foreach ($studentroles as $studentrole) { 2102 assign_capability('mod/data:rate', CAP_ALLOW, $studentrole->id, $context->id); 2103 } 2104 foreach ($teacherroles as $teacherrole) { 2105 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id); 2106 } 2107 break; 2108 case 2: 2109 foreach ($studentroles as $studentrole) { 2110 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id); 2111 } 2112 foreach ($teacherroles as $teacherrole) { 2113 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id); 2114 } 2115 break; 2116 } 2117 2118 // $data->assesspublic: 2119 // 0 - Students can only see their own ratings 2120 // 1 - Students can see everyone's ratings 2121 switch ($data->assesspublic) { 2122 case 0: 2123 foreach ($studentroles as $studentrole) { 2124 assign_capability('mod/data:viewrating', CAP_PREVENT, $studentrole->id, $context->id); 2125 } 2126 foreach ($teacherroles as $teacherrole) { 2127 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id); 2128 } 2129 break; 2130 case 1: 2131 foreach ($studentroles as $studentrole) { 2132 assign_capability('mod/data:viewrating', CAP_ALLOW, $studentrole->id, $context->id); 2133 } 2134 foreach ($teacherroles as $teacherrole) { 2135 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id); 2136 } 2137 break; 2138 } 2139 2140 if (empty($cm)) { 2141 $cm = $DB->get_record('course_modules', array('id'=>$cmid)); 2142 } 2143 2144 switch ($cm->groupmode) { 2145 case NOGROUPS: 2146 break; 2147 case SEPARATEGROUPS: 2148 foreach ($studentroles as $studentrole) { 2149 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id); 2150 } 2151 foreach ($teacherroles as $teacherrole) { 2152 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id); 2153 } 2154 break; 2155 case VISIBLEGROUPS: 2156 foreach ($studentroles as $studentrole) { 2157 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id); 2158 } 2159 foreach ($teacherroles as $teacherrole) { 2160 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id); 2161 } 2162 break; 2163 } 2164 return true; 2165 } 2166 2167 /** 2168 * Returns the best name to show for a preset 2169 * 2170 * @param string $shortname 2171 * @param string $path 2172 * @return string 2173 * @deprecated since Moodle 4.1 MDL-75148 - please, use the preset::get_name_from_plugin() function instead. 2174 * @todo MDL-75189 This will be deleted in Moodle 4.5. 2175 * @see preset::get_name_from_plugin() 2176 */ 2177 function data_preset_name($shortname, $path) { 2178 debugging('data_preset_name() is deprecated. Please use preset::get_name_from_plugin() instead.', DEBUG_DEVELOPER); 2179 2180 return preset::get_name_from_plugin($shortname); 2181 } 2182 2183 /** 2184 * Returns an array of all the available presets. 2185 * 2186 * @return array 2187 * @deprecated since Moodle 4.1 MDL-75148 - please, use the manager::get_available_presets() function instead. 2188 * @todo MDL-75189 This will be deleted in Moodle 4.5. 2189 * @see manager::get_available_presets() 2190 */ 2191 function data_get_available_presets($context) { 2192 debugging('data_get_available_presets() is deprecated. Please use manager::get_available_presets() instead.', DEBUG_DEVELOPER); 2193 2194 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); 2195 $manager = manager::create_from_coursemodule($cm); 2196 return $manager->get_available_presets(); 2197 } 2198 2199 /** 2200 * Gets an array of all of the presets that users have saved to the site. 2201 * 2202 * @param stdClass $context The context that we are looking from. 2203 * @param array $presets 2204 * @return array An array of presets 2205 * @deprecated since Moodle 4.1 MDL-75148 - please, use the manager::get_available_saved_presets() function instead. 2206 * @todo MDL-75189 This will be deleted in Moodle 4.5. 2207 * @see manager::get_available_saved_presets() 2208 */ 2209 function data_get_available_site_presets($context, array $presets=array()) { 2210 debugging( 2211 'data_get_available_site_presets() is deprecated. Please use manager::get_available_saved_presets() instead.', 2212 DEBUG_DEVELOPER 2213 ); 2214 2215 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); 2216 $manager = manager::create_from_coursemodule($cm); 2217 $savedpresets = $manager->get_available_saved_presets(); 2218 return array_merge($presets, $savedpresets); 2219 } 2220 2221 /** 2222 * Deletes a saved preset. 2223 * 2224 * @param string $name 2225 * @return bool 2226 * @deprecated since Moodle 4.1 MDL-75187 - please, use the preset::delete() function instead. 2227 * @todo MDL-75189 This will be deleted in Moodle 4.5. 2228 * @see preset::delete() 2229 */ 2230 function data_delete_site_preset($name) { 2231 debugging('data_delete_site_preset() is deprecated. Please use preset::delete() instead.', DEBUG_DEVELOPER); 2232 2233 $fs = get_file_storage(); 2234 2235 $files = $fs->get_directory_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/'); 2236 if (!empty($files)) { 2237 foreach ($files as $file) { 2238 $file->delete(); 2239 } 2240 } 2241 2242 $dir = $fs->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/', '.'); 2243 if (!empty($dir)) { 2244 $dir->delete(); 2245 } 2246 return true; 2247 } 2248 2249 /** 2250 * Prints the heads for a page 2251 * 2252 * @param stdClass $course 2253 * @param stdClass $cm 2254 * @param stdClass $data 2255 * @param string $currenttab 2256 * @param string $actionbar 2257 */ 2258 function data_print_header($course, $cm, $data, $currenttab='', string $actionbar = '') { 2259 2260 global $CFG, $displaynoticegood, $displaynoticebad, $OUTPUT, $PAGE, $USER; 2261 2262 echo $OUTPUT->header(); 2263 2264 echo $actionbar; 2265 2266 // Print any notices 2267 2268 if (!empty($displaynoticegood)) { 2269 echo $OUTPUT->notification($displaynoticegood, 'notifysuccess'); // good (usually green) 2270 } else if (!empty($displaynoticebad)) { 2271 echo $OUTPUT->notification($displaynoticebad); // bad (usuually red) 2272 } 2273 } 2274 2275 /** 2276 * Can user add more entries? 2277 * 2278 * @param object $data 2279 * @param mixed $currentgroup 2280 * @param int $groupmode 2281 * @param stdClass $context 2282 * @return bool 2283 */ 2284 function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = null) { 2285 global $DB; 2286 2287 // Don't let add entry to a database that has no fields. 2288 if (!$DB->record_exists('data_fields', ['dataid' => $data->id])) { 2289 return false; 2290 } 2291 2292 if (empty($context)) { 2293 $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST); 2294 $context = context_module::instance($cm->id); 2295 } 2296 2297 if (has_capability('mod/data:manageentries', $context)) { 2298 // no entry limits apply if user can manage 2299 2300 } else if (!has_capability('mod/data:writeentry', $context)) { 2301 return false; 2302 2303 } else if (data_atmaxentries($data)) { 2304 return false; 2305 } else if (data_in_readonly_period($data)) { 2306 // Check whether we're in a read-only period 2307 return false; 2308 } 2309 2310 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) { 2311 return true; 2312 } 2313 2314 if ($currentgroup) { 2315 return groups_is_member($currentgroup); 2316 } else { 2317 //else it might be group 0 in visible mode 2318 if ($groupmode == VISIBLEGROUPS){ 2319 return true; 2320 } else { 2321 return false; 2322 } 2323 } 2324 } 2325 2326 /** 2327 * Check whether the current user is allowed to manage the given record considering manageentries capability, 2328 * data_in_readonly_period() result, ownership (determined by data_isowner()) and manageapproved setting. 2329 * @param mixed $record record object or id 2330 * @param object $data data object 2331 * @param object $context context object 2332 * @return bool returns true if the user is allowd to edit the entry, false otherwise 2333 */ 2334 function data_user_can_manage_entry($record, $data, $context) { 2335 global $DB; 2336 2337 if (has_capability('mod/data:manageentries', $context)) { 2338 return true; 2339 } 2340 2341 // Check whether this activity is read-only at present. 2342 $readonly = data_in_readonly_period($data); 2343 2344 if (!$readonly) { 2345 // Get record object from db if just id given like in data_isowner. 2346 // ...done before calling data_isowner() to avoid querying db twice. 2347 if (!is_object($record)) { 2348 if (!$record = $DB->get_record('data_records', array('id' => $record))) { 2349 return false; 2350 } 2351 } 2352 if (data_isowner($record)) { 2353 if ($data->approval && $record->approved) { 2354 return $data->manageapproved == 1; 2355 } else { 2356 return true; 2357 } 2358 } 2359 } 2360 2361 return false; 2362 } 2363 2364 /** 2365 * Check whether the specified database activity is currently in a read-only period 2366 * 2367 * @param object $data 2368 * @return bool returns true if the time fields in $data indicate a read-only period; false otherwise 2369 */ 2370 function data_in_readonly_period($data) { 2371 $now = time(); 2372 if (!$data->timeviewfrom && !$data->timeviewto) { 2373 return false; 2374 } else if (($data->timeviewfrom && $now < $data->timeviewfrom) || ($data->timeviewto && $now > $data->timeviewto)) { 2375 return false; 2376 } 2377 return true; 2378 } 2379 2380 /** 2381 * Check if the files in a directory are the expected for a preset. 2382 * 2383 * @return bool Wheter the defined $directory has or not all the expected preset files. 2384 * 2385 * @deprecated since Moodle 4.1 MDL-75148 - please, use the preset::is_directory_a_preset() function instead. 2386 * @todo MDL-75189 This will be deleted in Moodle 4.5. 2387 * @see manager::is_directory_a_preset() 2388 */ 2389 function is_directory_a_preset($directory) { 2390 debugging('is_directory_a_preset() is deprecated. Please use preset::is_directory_a_preset() instead.', DEBUG_DEVELOPER); 2391 2392 return preset::is_directory_a_preset($directory); 2393 } 2394 2395 /** 2396 * Abstract class used for data preset importers 2397 * 2398 * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more. 2399 * @todo MDL-75189 Final deprecation in Moodle 4.5. 2400 */ 2401 abstract class data_preset_importer { 2402 2403 protected $course; 2404 protected $cm; 2405 protected $module; 2406 protected $directory; 2407 2408 /** 2409 * Constructor 2410 * 2411 * @param stdClass $course 2412 * @param stdClass $cm 2413 * @param stdClass $module 2414 * @param string $directory 2415 */ 2416 public function __construct($course, $cm, $module, $directory) { 2417 debugging( 2418 'data_preset_importer is deprecated. Please use mod\\data\\local\\importer\\preset_importer instead', 2419 DEBUG_DEVELOPER 2420 ); 2421 2422 $this->course = $course; 2423 $this->cm = $cm; 2424 $this->module = $module; 2425 $this->directory = $directory; 2426 } 2427 2428 /** 2429 * Returns the name of the directory the preset is located in 2430 * @return string 2431 */ 2432 public function get_directory() { 2433 return basename($this->directory); 2434 } 2435 2436 /** 2437 * Retreive the contents of a file. That file may either be in a conventional directory of the Moodle file storage 2438 * @param file_storage $filestorage. should be null if using a conventional directory 2439 * @param stored_file $fileobj the directory to look in. null if using a conventional directory 2440 * @param string $dir the directory to look in. null if using the Moodle file storage 2441 * @param string $filename the name of the file we want 2442 * @return string the contents of the file or null if the file doesn't exist. 2443 */ 2444 public function data_preset_get_file_contents(&$filestorage, &$fileobj, $dir, $filename) { 2445 if(empty($filestorage) || empty($fileobj)) { 2446 if (substr($dir, -1)!='/') { 2447 $dir .= '/'; 2448 } 2449 if (file_exists($dir.$filename)) { 2450 return file_get_contents($dir.$filename); 2451 } else { 2452 return null; 2453 } 2454 } else { 2455 if ($filestorage->file_exists(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename)) { 2456 $file = $filestorage->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename); 2457 return $file->get_content(); 2458 } else { 2459 return null; 2460 } 2461 } 2462 2463 } 2464 /** 2465 * Gets the preset settings 2466 * @global moodle_database $DB 2467 * @return stdClass 2468 */ 2469 public function get_preset_settings() { 2470 global $DB, $CFG; 2471 require_once($CFG->libdir.'/xmlize.php'); 2472 2473 $fs = $fileobj = null; 2474 if (!preset::is_directory_a_preset($this->directory)) { 2475 //maybe the user requested a preset stored in the Moodle file storage 2476 2477 $fs = get_file_storage(); 2478 $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA); 2479 2480 //preset name to find will be the final element of the directory 2481 $explodeddirectory = explode('/', $this->directory); 2482 $presettofind = end($explodeddirectory); 2483 2484 //now go through the available files available and see if we can find it 2485 foreach ($files as $file) { 2486 if (($file->is_directory() && $file->get_filepath()=='/') || !$file->is_directory()) { 2487 continue; 2488 } 2489 $presetname = trim($file->get_filepath(), '/'); 2490 if ($presetname==$presettofind) { 2491 $this->directory = $presetname; 2492 $fileobj = $file; 2493 } 2494 } 2495 2496 if (empty($fileobj)) { 2497 throw new \moodle_exception('invalidpreset', 'data', '', $this->directory); 2498 } 2499 } 2500 2501 $allowed_settings = array( 2502 'intro', 2503 'comments', 2504 'requiredentries', 2505 'requiredentriestoview', 2506 'maxentries', 2507 'rssarticles', 2508 'approval', 2509 'defaultsortdir', 2510 'defaultsort'); 2511 2512 $result = new stdClass; 2513 $result->settings = new stdClass; 2514 $result->importfields = array(); 2515 $result->currentfields = $DB->get_records('data_fields', array('dataid'=>$this->module->id)); 2516 if (!$result->currentfields) { 2517 $result->currentfields = array(); 2518 } 2519 2520 2521 /* Grab XML */ 2522 $presetxml = $this->data_preset_get_file_contents($fs, $fileobj, $this->directory,'preset.xml'); 2523 $parsedxml = xmlize($presetxml, 0); 2524 2525 /* First, do settings. Put in user friendly array. */ 2526 $settingsarray = $parsedxml['preset']['#']['settings'][0]['#']; 2527 $result->settings = new StdClass(); 2528 foreach ($settingsarray as $setting => $value) { 2529 if (!is_array($value) || !in_array($setting, $allowed_settings)) { 2530 // unsupported setting 2531 continue; 2532 } 2533 $result->settings->$setting = $value[0]['#']; 2534 } 2535 2536 /* Now work out fields to user friendly array */ 2537 $fieldsarray = $parsedxml['preset']['#']['field']; 2538 foreach ($fieldsarray as $field) { 2539 if (!is_array($field)) { 2540 continue; 2541 } 2542 $f = new StdClass(); 2543 foreach ($field['#'] as $param => $value) { 2544 if (!is_array($value)) { 2545 continue; 2546 } 2547 $f->$param = $value[0]['#']; 2548 } 2549 $f->dataid = $this->module->id; 2550 $f->type = clean_param($f->type, PARAM_ALPHA); 2551 $result->importfields[] = $f; 2552 } 2553 /* Now add the HTML templates to the settings array so we can update d */ 2554 $result->settings->singletemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"singletemplate.html"); 2555 $result->settings->listtemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplate.html"); 2556 $result->settings->listtemplateheader = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplateheader.html"); 2557 $result->settings->listtemplatefooter = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplatefooter.html"); 2558 $result->settings->addtemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"addtemplate.html"); 2559 $result->settings->rsstemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstemplate.html"); 2560 $result->settings->rsstitletemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstitletemplate.html"); 2561 $result->settings->csstemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"csstemplate.css"); 2562 $result->settings->jstemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"jstemplate.js"); 2563 $result->settings->asearchtemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"asearchtemplate.html"); 2564 2565 $result->settings->instance = $this->module->id; 2566 return $result; 2567 } 2568 2569 /** 2570 * Import the preset into the given database module 2571 * @return bool 2572 */ 2573 function import($overwritesettings) { 2574 global $DB, $CFG, $OUTPUT; 2575 2576 $params = $this->get_preset_settings(); 2577 $settings = $params->settings; 2578 $newfields = $params->importfields; 2579 $currentfields = $params->currentfields; 2580 $preservedfields = array(); 2581 2582 /* Maps fields and makes new ones */ 2583 if (!empty($newfields)) { 2584 /* We require an injective mapping, and need to know what to protect */ 2585 foreach ($newfields as $nid => $newfield) { 2586 $cid = optional_param("field_$nid", -1, PARAM_INT); 2587 if ($cid == -1) { 2588 continue; 2589 } 2590 if (array_key_exists($cid, $preservedfields)){ 2591 throw new \moodle_exception('notinjectivemap', 'data'); 2592 } 2593 else $preservedfields[$cid] = true; 2594 } 2595 $missingfieldtypes = []; 2596 foreach ($newfields as $nid => $newfield) { 2597 $cid = optional_param("field_$nid", -1, PARAM_INT); 2598 2599 /* A mapping. Just need to change field params. Data kept. */ 2600 if ($cid != -1 and isset($currentfields[$cid])) { 2601 $fieldobject = data_get_field_from_id($currentfields[$cid]->id, $this->module); 2602 foreach ($newfield as $param => $value) { 2603 if ($param != "id") { 2604 $fieldobject->field->$param = $value; 2605 } 2606 } 2607 unset($fieldobject->field->similarfield); 2608 $fieldobject->update_field(); 2609 unset($fieldobject); 2610 } else { 2611 /* Make a new field */ 2612 $filepath = "field/$newfield->type/field.class.php"; 2613 if (!file_exists($filepath)) { 2614 $missingfieldtypes[] = $newfield->name; 2615 continue; 2616 } 2617 include_once($filepath); 2618 2619 if (!isset($newfield->description)) { 2620 $newfield->description = ''; 2621 } 2622 $classname = 'data_field_'.$newfield->type; 2623 $fieldclass = new $classname($newfield, $this->module); 2624 $fieldclass->insert_field(); 2625 unset($fieldclass); 2626 } 2627 } 2628 if (!empty($missingfieldtypes)) { 2629 echo $OUTPUT->notification(get_string('missingfieldtypeimport', 'data') . html_writer::alist($missingfieldtypes)); 2630 } 2631 } 2632 2633 /* Get rid of all old unused data */ 2634 foreach ($currentfields as $cid => $currentfield) { 2635 if (!array_key_exists($cid, $preservedfields)) { 2636 /* Data not used anymore so wipe! */ 2637 echo "Deleting field $currentfield->name<br />"; 2638 2639 // Delete all information related to fields. 2640 $todelete = data_get_field_from_id($currentfield->id, $this->module); 2641 $todelete->delete_field(); 2642 } 2643 } 2644 2645 // handle special settings here 2646 if (!empty($settings->defaultsort)) { 2647 if (is_numeric($settings->defaultsort)) { 2648 // old broken value 2649 $settings->defaultsort = 0; 2650 } else { 2651 $settings->defaultsort = (int)$DB->get_field('data_fields', 'id', array('dataid'=>$this->module->id, 'name'=>$settings->defaultsort)); 2652 } 2653 } else { 2654 $settings->defaultsort = 0; 2655 } 2656 2657 // do we want to overwrite all current database settings? 2658 if ($overwritesettings) { 2659 // all supported settings 2660 $overwrite = array_keys((array)$settings); 2661 } else { 2662 // only templates and sorting 2663 $overwrite = array('singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter', 2664 'addtemplate', 'rsstemplate', 'rsstitletemplate', 'csstemplate', 'jstemplate', 2665 'asearchtemplate', 'defaultsortdir', 'defaultsort'); 2666 } 2667 2668 // now overwrite current data settings 2669 foreach ($this->module as $prop=>$unused) { 2670 if (in_array($prop, $overwrite)) { 2671 $this->module->$prop = $settings->$prop; 2672 } 2673 } 2674 2675 data_update_instance($this->module); 2676 2677 return $this->cleanup(); 2678 } 2679 2680 /** 2681 * Any clean up routines should go here 2682 * @return bool 2683 */ 2684 public function cleanup() { 2685 return true; 2686 } 2687 } 2688 2689 /** 2690 * Data preset importer for uploaded presets 2691 * 2692 * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more. 2693 * @todo MDL-75189 Final deprecation in Moodle 4.5. 2694 */ 2695 class data_preset_upload_importer extends data_preset_importer { 2696 public function __construct($course, $cm, $module, $filepath) { 2697 global $USER; 2698 2699 debugging( 2700 'data_preset_upload_importer is deprecated. Please use mod\\data\\local\\importer\\preset_upload_importer instead', 2701 DEBUG_DEVELOPER 2702 ); 2703 2704 if (is_file($filepath)) { 2705 $fp = get_file_packer(); 2706 if ($fp->extract_to_pathname($filepath, $filepath.'_extracted')) { 2707 fulldelete($filepath); 2708 } 2709 $filepath .= '_extracted'; 2710 } 2711 parent::__construct($course, $cm, $module, $filepath); 2712 } 2713 2714 public function cleanup() { 2715 return fulldelete($this->directory); 2716 } 2717 } 2718 2719 /** 2720 * Data preset importer for existing presets 2721 * 2722 * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more. 2723 * @todo MDL-75189 Final deprecation in Moodle 4.5. 2724 */ 2725 class data_preset_existing_importer extends data_preset_importer { 2726 protected $userid; 2727 public function __construct($course, $cm, $module, $fullname) { 2728 global $USER; 2729 2730 debugging( 2731 'data_preset_existing_importer is deprecated. Please use mod\\data\\local\\importer\\preset_existing_importer instead', 2732 DEBUG_DEVELOPER 2733 ); 2734 2735 list($userid, $shortname) = explode('/', $fullname, 2); 2736 $context = context_module::instance($cm->id); 2737 if ($userid && ($userid != $USER->id) && !has_capability('mod/data:manageuserpresets', $context) && !has_capability('mod/data:viewalluserpresets', $context)) { 2738 throw new coding_exception('Invalid preset provided'); 2739 } 2740 2741 $this->userid = $userid; 2742 $filepath = data_preset_path($course, $userid, $shortname); 2743 parent::__construct($course, $cm, $module, $filepath); 2744 } 2745 public function get_userid() { 2746 return $this->userid; 2747 } 2748 } 2749 2750 /** 2751 * @global object 2752 * @global object 2753 * @param object $course 2754 * @param int $userid 2755 * @param string $shortname 2756 * @return string 2757 */ 2758 function data_preset_path($course, $userid, $shortname) { 2759 global $USER, $CFG; 2760 2761 $context = context_course::instance($course->id); 2762 2763 $userid = (int)$userid; 2764 2765 $path = null; 2766 if ($userid > 0 && ($userid == $USER->id || has_capability('mod/data:viewalluserpresets', $context))) { 2767 $path = $CFG->dataroot.'/data/preset/'.$userid.'/'.$shortname; 2768 } else if ($userid == 0) { 2769 $path = $CFG->dirroot.'/mod/data/preset/'.$shortname; 2770 } else if ($userid < 0) { 2771 $path = $CFG->tempdir.'/data/'.-$userid.'/'.$shortname; 2772 } 2773 2774 return $path; 2775 } 2776 2777 /** 2778 * Implementation of the function for printing the form elements that control 2779 * whether the course reset functionality affects the data. 2780 * 2781 * @param MoodleQuickForm $mform form passed by reference 2782 */ 2783 function data_reset_course_form_definition(&$mform) { 2784 $mform->addElement('header', 'dataheader', get_string('modulenameplural', 'data')); 2785 $mform->addElement('checkbox', 'reset_data', get_string('deleteallentries','data')); 2786 2787 $mform->addElement('checkbox', 'reset_data_notenrolled', get_string('deletenotenrolled', 'data')); 2788 $mform->disabledIf('reset_data_notenrolled', 'reset_data', 'checked'); 2789 2790 $mform->addElement('checkbox', 'reset_data_ratings', get_string('deleteallratings')); 2791 $mform->disabledIf('reset_data_ratings', 'reset_data', 'checked'); 2792 2793 $mform->addElement('checkbox', 'reset_data_comments', get_string('deleteallcomments')); 2794 $mform->disabledIf('reset_data_comments', 'reset_data', 'checked'); 2795 2796 $mform->addElement('checkbox', 'reset_data_tags', get_string('removealldatatags', 'data')); 2797 $mform->disabledIf('reset_data_tags', 'reset_data', 'checked'); 2798 } 2799 2800 /** 2801 * Course reset form defaults. 2802 * @return array 2803 */ 2804 function data_reset_course_form_defaults($course) { 2805 return array('reset_data'=>0, 'reset_data_ratings'=>1, 'reset_data_comments'=>1, 'reset_data_notenrolled'=>0); 2806 } 2807 2808 /** 2809 * Removes all grades from gradebook 2810 * 2811 * @global object 2812 * @global object 2813 * @param int $courseid 2814 * @param string $type optional type 2815 */ 2816 function data_reset_gradebook($courseid, $type='') { 2817 global $CFG, $DB; 2818 2819 $sql = "SELECT d.*, cm.idnumber as cmidnumber, d.course as courseid 2820 FROM {data} d, {course_modules} cm, {modules} m 2821 WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id AND d.course=?"; 2822 2823 if ($datas = $DB->get_records_sql($sql, array($courseid))) { 2824 foreach ($datas as $data) { 2825 data_grade_item_update($data, 'reset'); 2826 } 2827 } 2828 } 2829 2830 /** 2831 * Actual implementation of the reset course functionality, delete all the 2832 * data responses for course $data->courseid. 2833 * 2834 * @global object 2835 * @global object 2836 * @param object $data the data submitted from the reset course. 2837 * @return array status array 2838 */ 2839 function data_reset_userdata($data) { 2840 global $CFG, $DB; 2841 require_once($CFG->libdir.'/filelib.php'); 2842 require_once($CFG->dirroot.'/rating/lib.php'); 2843 2844 $componentstr = get_string('modulenameplural', 'data'); 2845 $status = array(); 2846 2847 $allrecordssql = "SELECT r.id 2848 FROM {data_records} r 2849 INNER JOIN {data} d ON r.dataid = d.id 2850 WHERE d.course = ?"; 2851 2852 $alldatassql = "SELECT d.id 2853 FROM {data} d 2854 WHERE d.course=?"; 2855 2856 $rm = new rating_manager(); 2857 $ratingdeloptions = new stdClass; 2858 $ratingdeloptions->component = 'mod_data'; 2859 $ratingdeloptions->ratingarea = 'entry'; 2860 2861 // Set the file storage - may need it to remove files later. 2862 $fs = get_file_storage(); 2863 2864 // delete entries if requested 2865 if (!empty($data->reset_data)) { 2866 $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid)); 2867 $DB->delete_records_select('data_content', "recordid IN ($allrecordssql)", array($data->courseid)); 2868 $DB->delete_records_select('data_records', "dataid IN ($alldatassql)", array($data->courseid)); 2869 2870 if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) { 2871 foreach ($datas as $dataid=>$unused) { 2872 if (!$cm = get_coursemodule_from_instance('data', $dataid)) { 2873 continue; 2874 } 2875 $datacontext = context_module::instance($cm->id); 2876 2877 // Delete any files that may exist. 2878 $fs->delete_area_files($datacontext->id, 'mod_data', 'content'); 2879 2880 $ratingdeloptions->contextid = $datacontext->id; 2881 $rm->delete_ratings($ratingdeloptions); 2882 2883 core_tag_tag::delete_instances('mod_data', null, $datacontext->id); 2884 } 2885 } 2886 2887 if (empty($data->reset_gradebook_grades)) { 2888 // remove all grades from gradebook 2889 data_reset_gradebook($data->courseid); 2890 } 2891 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallentries', 'data'), 'error'=>false); 2892 } 2893 2894 // remove entries by users not enrolled into course 2895 if (!empty($data->reset_data_notenrolled)) { 2896 $recordssql = "SELECT r.id, r.userid, r.dataid, u.id AS userexists, u.deleted AS userdeleted 2897 FROM {data_records} r 2898 JOIN {data} d ON r.dataid = d.id 2899 LEFT JOIN {user} u ON r.userid = u.id 2900 WHERE d.course = ? AND r.userid > 0"; 2901 2902 $course_context = context_course::instance($data->courseid); 2903 $notenrolled = array(); 2904 $fields = array(); 2905 $rs = $DB->get_recordset_sql($recordssql, array($data->courseid)); 2906 foreach ($rs as $record) { 2907 if (array_key_exists($record->userid, $notenrolled) or !$record->userexists or $record->userdeleted 2908 or !is_enrolled($course_context, $record->userid)) { 2909 //delete ratings 2910 if (!$cm = get_coursemodule_from_instance('data', $record->dataid)) { 2911 continue; 2912 } 2913 $datacontext = context_module::instance($cm->id); 2914 $ratingdeloptions->contextid = $datacontext->id; 2915 $ratingdeloptions->itemid = $record->id; 2916 $rm->delete_ratings($ratingdeloptions); 2917 2918 // Delete any files that may exist. 2919 if ($contents = $DB->get_records('data_content', array('recordid' => $record->id), '', 'id')) { 2920 foreach ($contents as $content) { 2921 $fs->delete_area_files($datacontext->id, 'mod_data', 'content', $content->id); 2922 } 2923 } 2924 $notenrolled[$record->userid] = true; 2925 2926 core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $record->id); 2927 2928 $DB->delete_records('comments', array('itemid' => $record->id, 'commentarea' => 'database_entry')); 2929 $DB->delete_records('data_content', array('recordid' => $record->id)); 2930 $DB->delete_records('data_records', array('id' => $record->id)); 2931 } 2932 } 2933 $rs->close(); 2934 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'data'), 'error'=>false); 2935 } 2936 2937 // remove all ratings 2938 if (!empty($data->reset_data_ratings)) { 2939 if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) { 2940 foreach ($datas as $dataid=>$unused) { 2941 if (!$cm = get_coursemodule_from_instance('data', $dataid)) { 2942 continue; 2943 } 2944 $datacontext = context_module::instance($cm->id); 2945 2946 $ratingdeloptions->contextid = $datacontext->id; 2947 $rm->delete_ratings($ratingdeloptions); 2948 } 2949 } 2950 2951 if (empty($data->reset_gradebook_grades)) { 2952 // remove all grades from gradebook 2953 data_reset_gradebook($data->courseid); 2954 } 2955 2956 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false); 2957 } 2958 2959 // remove all comments 2960 if (!empty($data->reset_data_comments)) { 2961 $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid)); 2962 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false); 2963 } 2964 2965 // Remove all the tags. 2966 if (!empty($data->reset_data_tags)) { 2967 if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) { 2968 foreach ($datas as $dataid => $unused) { 2969 if (!$cm = get_coursemodule_from_instance('data', $dataid)) { 2970 continue; 2971 } 2972 2973 $context = context_module::instance($cm->id); 2974 core_tag_tag::delete_instances('mod_data', null, $context->id); 2975 2976 } 2977 } 2978 $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'data'), 'error' => false); 2979 } 2980 2981 // updating dates - shift may be negative too 2982 if ($data->timeshift) { 2983 // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. 2984 // See MDL-9367. 2985 shift_course_mod_dates('data', array('timeavailablefrom', 'timeavailableto', 2986 'timeviewfrom', 'timeviewto', 'assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid); 2987 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); 2988 } 2989 2990 return $status; 2991 } 2992 2993 /** 2994 * Returns all other caps used in module 2995 * 2996 * @return array 2997 */ 2998 function data_get_extra_capabilities() { 2999 return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate', 3000 'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete']; 3001 } 3002 3003 /** 3004 * @param string $feature FEATURE_xx constant for requested feature 3005 * @return mixed True if module supports feature, false if not, null if doesn't know or string for the module purpose. 3006 */ 3007 function data_supports($feature) { 3008 switch($feature) { 3009 case FEATURE_GROUPS: return true; 3010 case FEATURE_GROUPINGS: return true; 3011 case FEATURE_MOD_INTRO: return true; 3012 case FEATURE_COMPLETION_TRACKS_VIEWS: return true; 3013 case FEATURE_COMPLETION_HAS_RULES: return true; 3014 case FEATURE_GRADE_HAS_GRADE: return true; 3015 case FEATURE_GRADE_OUTCOMES: return true; 3016 case FEATURE_RATE: return true; 3017 case FEATURE_BACKUP_MOODLE2: return true; 3018 case FEATURE_SHOW_DESCRIPTION: return true; 3019 case FEATURE_COMMENT: return true; 3020 case FEATURE_MOD_PURPOSE: return MOD_PURPOSE_COLLABORATION; 3021 3022 default: return null; 3023 } 3024 } 3025 3026 //////////////////////////////////////////////////////////////////////////////// 3027 // File API // 3028 //////////////////////////////////////////////////////////////////////////////// 3029 3030 /** 3031 * Lists all browsable file areas 3032 * 3033 * @package mod_data 3034 * @category files 3035 * @param stdClass $course course object 3036 * @param stdClass $cm course module object 3037 * @param stdClass $context context object 3038 * @return array 3039 */ 3040 function data_get_file_areas($course, $cm, $context) { 3041 return array('content' => get_string('areacontent', 'mod_data')); 3042 } 3043 3044 /** 3045 * File browsing support for data module. 3046 * 3047 * @param file_browser $browser 3048 * @param array $areas 3049 * @param stdClass $course 3050 * @param cm_info $cm 3051 * @param context $context 3052 * @param string $filearea 3053 * @param int $itemid 3054 * @param string $filepath 3055 * @param string $filename 3056 * @return file_info_stored file_info_stored instance or null if not found 3057 */ 3058 function data_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { 3059 global $CFG, $DB, $USER; 3060 3061 if ($context->contextlevel != CONTEXT_MODULE) { 3062 return null; 3063 } 3064 3065 if (!isset($areas[$filearea])) { 3066 return null; 3067 } 3068 3069 if (is_null($itemid)) { 3070 require_once($CFG->dirroot.'/mod/data/locallib.php'); 3071 return new data_file_info_container($browser, $course, $cm, $context, $areas, $filearea); 3072 } 3073 3074 if (!$content = $DB->get_record('data_content', array('id'=>$itemid))) { 3075 return null; 3076 } 3077 3078 if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) { 3079 return null; 3080 } 3081 3082 if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) { 3083 return null; 3084 } 3085 3086 if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) { 3087 return null; 3088 } 3089 3090 //check if approved 3091 if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) { 3092 return null; 3093 } 3094 3095 // group access 3096 if ($record->groupid) { 3097 $groupmode = groups_get_activity_groupmode($cm, $course); 3098 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { 3099 if (!groups_is_member($record->groupid)) { 3100 return null; 3101 } 3102 } 3103 } 3104 3105 $fieldobj = data_get_field($field, $data, $cm); 3106 3107 $filepath = is_null($filepath) ? '/' : $filepath; 3108 $filename = is_null($filename) ? '.' : $filename; 3109 if (!$fieldobj->file_ok($filepath.$filename)) { 3110 return null; 3111 } 3112 3113 $fs = get_file_storage(); 3114 if (!($storedfile = $fs->get_file($context->id, 'mod_data', $filearea, $itemid, $filepath, $filename))) { 3115 return null; 3116 } 3117 3118 // Checks to see if the user can manage files or is the owner. 3119 // TODO MDL-33805 - Do not use userid here and move the capability check above. 3120 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) { 3121 return null; 3122 } 3123 3124 $urlbase = $CFG->wwwroot.'/pluginfile.php'; 3125 3126 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false); 3127 } 3128 3129 /** 3130 * Serves the data attachments. Implements needed access control ;-) 3131 * 3132 * @package mod_data 3133 * @category files 3134 * @param stdClass $course course object 3135 * @param stdClass $cm course module object 3136 * @param stdClass $context context object 3137 * @param string $filearea file area 3138 * @param array $args extra arguments 3139 * @param bool $forcedownload whether or not force download 3140 * @param array $options additional options affecting the file serving 3141 * @return bool false if file not found, does not return if found - justsend the file 3142 */ 3143 function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { 3144 global $CFG, $DB; 3145 3146 if ($context->contextlevel != CONTEXT_MODULE) { 3147 return false; 3148 } 3149 3150 require_course_login($course, true, $cm); 3151 3152 if ($filearea === 'content') { 3153 $contentid = (int)array_shift($args); 3154 3155 if (!$content = $DB->get_record('data_content', array('id'=>$contentid))) { 3156 return false; 3157 } 3158 3159 if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) { 3160 return false; 3161 } 3162 3163 if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) { 3164 return false; 3165 } 3166 3167 if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) { 3168 return false; 3169 } 3170 3171 if ($data->id != $cm->instance) { 3172 // hacker attempt - context does not match the contentid 3173 return false; 3174 } 3175 3176 //check if approved 3177 if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) { 3178 return false; 3179 } 3180 3181 // group access 3182 if ($record->groupid) { 3183 $groupmode = groups_get_activity_groupmode($cm, $course); 3184 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { 3185 if (!groups_is_member($record->groupid)) { 3186 return false; 3187 } 3188 } 3189 } 3190 3191 $fieldobj = data_get_field($field, $data, $cm); 3192 3193 $relativepath = implode('/', $args); 3194 $fullpath = "/$context->id/mod_data/content/$content->id/$relativepath"; 3195 3196 if (!$fieldobj->file_ok($relativepath)) { 3197 return false; 3198 } 3199 3200 $fs = get_file_storage(); 3201 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 3202 return false; 3203 } 3204 3205 // finally send the file 3206 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! 3207 } 3208 3209 return false; 3210 } 3211 3212 3213 function data_extend_navigation($navigation, $course, $module, $cm) { 3214 global $CFG, $OUTPUT, $USER, $DB; 3215 require_once($CFG->dirroot . '/mod/data/locallib.php'); 3216 3217 $rid = optional_param('rid', 0, PARAM_INT); 3218 3219 $data = $DB->get_record('data', array('id'=>$cm->instance)); 3220 $currentgroup = groups_get_activity_group($cm); 3221 $groupmode = groups_get_activity_groupmode($cm); 3222 3223 $numentries = data_numentries($data); 3224 $canmanageentries = has_capability('mod/data:manageentries', context_module::instance($cm->id)); 3225 3226 if ($data->entriesleft = data_get_entries_left_to_add($data, $numentries, $canmanageentries)) { 3227 $entriesnode = $navigation->add(get_string('entrieslefttoadd', 'data', $data)); 3228 $entriesnode->add_class('note'); 3229 } 3230 3231 $navigation->add(get_string('list', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance))); 3232 if (!empty($rid)) { 3233 $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'rid'=>$rid))); 3234 } else { 3235 $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'single'))); 3236 } 3237 $navigation->add(get_string('search', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'asearch'))); 3238 } 3239 3240 /** 3241 * Adds module specific settings to the settings block 3242 * 3243 * @param settings_navigation $settings The settings navigation object 3244 * @param navigation_node $datanode The node to add module settings to 3245 */ 3246 function data_extend_settings_navigation(settings_navigation $settings, navigation_node $datanode) { 3247 global $DB, $CFG, $USER; 3248 3249 $data = $DB->get_record('data', array("id" => $settings->get_page()->cm->instance)); 3250 3251 $currentgroup = groups_get_activity_group($settings->get_page()->cm); 3252 $groupmode = groups_get_activity_groupmode($settings->get_page()->cm); 3253 3254 // Took out participation list here! 3255 if (data_user_can_add_entry($data, $currentgroup, $groupmode, $settings->get_page()->cm->context)) { 3256 if (empty($editentry)) { //TODO: undefined 3257 $addstring = get_string('add', 'data'); 3258 } else { 3259 $addstring = get_string('editentry', 'data'); 3260 } 3261 $addentrynode = $datanode->add($addstring, 3262 new moodle_url('/mod/data/edit.php', array('d' => $settings->get_page()->cm->instance))); 3263 $addentrynode->set_show_in_secondary_navigation(false); 3264 } 3265 3266 if (has_capability(DATA_CAP_EXPORT, $settings->get_page()->cm->context)) { 3267 // The capability required to Export database records is centrally defined in 'lib.php' 3268 // and should be weaker than those required to edit Templates, Fields and Presets. 3269 $exportentriesnode = $datanode->add(get_string('exportentries', 'data'), 3270 new moodle_url('/mod/data/export.php', array('d' => $data->id))); 3271 $exportentriesnode->set_show_in_secondary_navigation(false); 3272 } 3273 if (has_capability('mod/data:manageentries', $settings->get_page()->cm->context)) { 3274 $importentriesnode = $datanode->add(get_string('importentries', 'data'), 3275 new moodle_url('/mod/data/import.php', array('d' => $data->id))); 3276 $importentriesnode->set_show_in_secondary_navigation(false); 3277 } 3278 3279 if (has_capability('mod/data:managetemplates', $settings->get_page()->cm->context)) { 3280 $currenttab = ''; 3281 if ($currenttab == 'list') { 3282 $defaultemplate = 'listtemplate'; 3283 } else if ($currenttab == 'add') { 3284 $defaultemplate = 'addtemplate'; 3285 } else if ($currenttab == 'asearch') { 3286 $defaultemplate = 'asearchtemplate'; 3287 } else { 3288 $defaultemplate = 'singletemplate'; 3289 } 3290 3291 $datanode->add(get_string('presets', 'data'), new moodle_url('/mod/data/preset.php', array('d' => $data->id))); 3292 $datanode->add(get_string('fields', 'data'), 3293 new moodle_url('/mod/data/field.php', array('d' => $data->id))); 3294 $datanode->add(get_string('templates', 'data'), 3295 new moodle_url('/mod/data/templates.php', array('d' => $data->id))); 3296 } 3297 3298 if (!empty($CFG->enablerssfeeds) && !empty($CFG->data_enablerssfeeds) && $data->rssarticles > 0) { 3299 require_once("$CFG->libdir/rsslib.php"); 3300 3301 $string = get_string('rsstype', 'data'); 3302 3303 $url = new moodle_url(rss_get_url($settings->get_page()->cm->context->id, $USER->id, 'mod_data', $data->id)); 3304 $datanode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', '')); 3305 } 3306 } 3307 3308 /** 3309 * Save the database configuration as a preset. 3310 * 3311 * @param stdClass $course The course the database module belongs to. 3312 * @param stdClass $cm The course module record 3313 * @param stdClass $data The database record 3314 * @param string $path 3315 * @return bool 3316 * @deprecated since Moodle 4.1 MDL-75142 - please, use the preset::save() function instead. 3317 * @todo MDL-75189 This will be deleted in Moodle 4.5. 3318 * @see preset::save() 3319 */ 3320 function data_presets_save($course, $cm, $data, $path) { 3321 debugging('data_presets_save() is deprecated. Please use preset::save() instead.', DEBUG_DEVELOPER); 3322 3323 $manager = manager::create_from_instance($data); 3324 $preset = preset::create_from_instance($manager, $path); 3325 return $preset->save(); 3326 } 3327 3328 /** 3329 * Generates the XML for the database module provided 3330 * 3331 * @global moodle_database $DB 3332 * @param stdClass $course The course the database module belongs to. 3333 * @param stdClass $cm The course module record 3334 * @param stdClass $data The database record 3335 * @return string The XML for the preset 3336 * @deprecated since Moodle 4.1 MDL-75142 - please, use the protected preset::generate_preset_xml() function instead. 3337 * @todo MDL-75189 This will be deleted in Moodle 4.5. 3338 * @see preset::generate_preset_xml() 3339 */ 3340 function data_presets_generate_xml($course, $cm, $data) { 3341 debugging( 3342 'data_presets_generate_xml() is deprecated. Please use the protected preset::generate_preset_xml() instead.', 3343 DEBUG_DEVELOPER 3344 ); 3345 3346 $manager = manager::create_from_instance($data); 3347 $preset = preset::create_from_instance($manager, $data->name); 3348 $reflection = new \ReflectionClass(preset::class); 3349 $method = $reflection->getMethod('generate_preset_xml'); 3350 $method->setAccessible(true); 3351 return $method->invokeArgs($preset, []); 3352 } 3353 3354 /** 3355 * Export current fields and presets. 3356 * 3357 * @param stdClass $course The course the database module belongs to. 3358 * @param stdClass $cm The course module record 3359 * @param stdClass $data The database record 3360 * @param bool $tostorage 3361 * @return string the full path to the exported preset file. 3362 * @deprecated since Moodle 4.1 MDL-75142 - please, use the preset::export() function instead. 3363 * @todo MDL-75189 This will be deleted in Moodle 4.5. 3364 * @see preset::export() 3365 */ 3366 function data_presets_export($course, $cm, $data, $tostorage=false) { 3367 debugging('data_presets_export() is deprecated. Please use preset::export() instead.', DEBUG_DEVELOPER); 3368 3369 $manager = manager::create_from_instance($data); 3370 $preset = preset::create_from_instance($manager, $data->name); 3371 return $preset->export(); 3372 } 3373 3374 /** 3375 * Running addtional permission check on plugin, for example, plugins 3376 * may have switch to turn on/off comments option, this callback will 3377 * affect UI display, not like pluginname_comment_validate only throw 3378 * exceptions. 3379 * Capability check has been done in comment->check_permissions(), we 3380 * don't need to do it again here. 3381 * 3382 * @package mod_data 3383 * @category comment 3384 * 3385 * @param stdClass $comment_param { 3386 * context => context the context object 3387 * courseid => int course id 3388 * cm => stdClass course module object 3389 * commentarea => string comment area 3390 * itemid => int itemid 3391 * } 3392 * @return array 3393 */ 3394 function data_comment_permissions($comment_param) { 3395 global $CFG, $DB; 3396 if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) { 3397 throw new comment_exception('invalidcommentitemid'); 3398 } 3399 if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) { 3400 throw new comment_exception('invalidid', 'data'); 3401 } 3402 if ($data->comments) { 3403 return array('post'=>true, 'view'=>true); 3404 } else { 3405 return array('post'=>false, 'view'=>false); 3406 } 3407 } 3408 3409 /** 3410 * Validate comment parameter before perform other comments actions 3411 * 3412 * @package mod_data 3413 * @category comment 3414 * 3415 * @param stdClass $comment_param { 3416 * context => context the context object 3417 * courseid => int course id 3418 * cm => stdClass course module object 3419 * commentarea => string comment area 3420 * itemid => int itemid 3421 * } 3422 * @return boolean 3423 */ 3424 function data_comment_validate($comment_param) { 3425 global $DB; 3426 // validate comment area 3427 if ($comment_param->commentarea != 'database_entry') { 3428 throw new comment_exception('invalidcommentarea'); 3429 } 3430 // validate itemid 3431 if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) { 3432 throw new comment_exception('invalidcommentitemid'); 3433 } 3434 if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) { 3435 throw new comment_exception('invalidid', 'data'); 3436 } 3437 if (!$course = $DB->get_record('course', array('id'=>$data->course))) { 3438 throw new comment_exception('coursemisconf'); 3439 } 3440 if (!$cm = get_coursemodule_from_instance('data', $data->id, $course->id)) { 3441 throw new comment_exception('invalidcoursemodule'); 3442 } 3443 if (!$data->comments) { 3444 throw new comment_exception('commentsoff', 'data'); 3445 } 3446 $context = context_module::instance($cm->id); 3447 3448 //check if approved 3449 if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) { 3450 throw new comment_exception('notapprovederror', 'data'); 3451 } 3452 3453 // group access 3454 if ($record->groupid) { 3455 $groupmode = groups_get_activity_groupmode($cm, $course); 3456 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { 3457 if (!groups_is_member($record->groupid)) { 3458 throw new comment_exception('notmemberofgroup'); 3459 } 3460 } 3461 } 3462 // validate context id 3463 if ($context->id != $comment_param->context->id) { 3464 throw new comment_exception('invalidcontext'); 3465 } 3466 // validation for comment deletion 3467 if (!empty($comment_param->commentid)) { 3468 if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) { 3469 if ($comment->commentarea != 'database_entry') { 3470 throw new comment_exception('invalidcommentarea'); 3471 } 3472 if ($comment->contextid != $comment_param->context->id) { 3473 throw new comment_exception('invalidcontext'); 3474 } 3475 if ($comment->itemid != $comment_param->itemid) { 3476 throw new comment_exception('invalidcommentitemid'); 3477 } 3478 } else { 3479 throw new comment_exception('invalidcommentid'); 3480 } 3481 } 3482 return true; 3483 } 3484 3485 /** 3486 * Return a list of page types 3487 * @param string $pagetype current page type 3488 * @param stdClass $parentcontext Block's parent context 3489 * @param stdClass $currentcontext Current context of block 3490 */ 3491 function data_page_type_list($pagetype, $parentcontext, $currentcontext) { 3492 $module_pagetype = array('mod-data-*'=>get_string('page-mod-data-x', 'data')); 3493 return $module_pagetype; 3494 } 3495 3496 /** 3497 * Get all of the record ids from a database activity. 3498 * 3499 * @param int $dataid The dataid of the database module. 3500 * @param object $selectdata Contains an additional sql statement for the 3501 * where clause for group and approval fields. 3502 * @param array $params Parameters that coincide with the sql statement. 3503 * @return array $idarray An array of record ids 3504 */ 3505 function data_get_all_recordids($dataid, $selectdata = '', $params = null) { 3506 global $DB; 3507 $initsql = 'SELECT r.id 3508 FROM {data_records} r 3509 WHERE r.dataid = :dataid'; 3510 if ($selectdata != '') { 3511 $initsql .= $selectdata; 3512 $params = array_merge(array('dataid' => $dataid), $params); 3513 } else { 3514 $params = array('dataid' => $dataid); 3515 } 3516 $initsql .= ' GROUP BY r.id'; 3517 $initrecord = $DB->get_recordset_sql($initsql, $params); 3518 $idarray = array(); 3519 foreach ($initrecord as $data) { 3520 $idarray[] = $data->id; 3521 } 3522 // Close the record set and free up resources. 3523 $initrecord->close(); 3524 return $idarray; 3525 } 3526 3527 /** 3528 * Get the ids of all the records that match that advanced search criteria 3529 * This goes and loops through each criterion one at a time until it either 3530 * runs out of records or returns a subset of records. 3531 * 3532 * @param array $recordids An array of record ids. 3533 * @param array $searcharray Contains information for the advanced search criteria 3534 * @param int $dataid The data id of the database. 3535 * @return array $recordids An array of record ids. 3536 */ 3537 function data_get_advance_search_ids($recordids, $searcharray, $dataid) { 3538 // Check to see if we have any record IDs. 3539 if (empty($recordids)) { 3540 // Send back an empty search. 3541 return array(); 3542 } 3543 $searchcriteria = array_keys($searcharray); 3544 // Loop through and reduce the IDs one search criteria at a time. 3545 foreach ($searchcriteria as $key) { 3546 $recordids = data_get_recordids($key, $searcharray, $dataid, $recordids); 3547 // If we don't have anymore IDs then stop. 3548 if (!$recordids) { 3549 break; 3550 } 3551 } 3552 return $recordids; 3553 } 3554 3555 /** 3556 * Gets the record IDs given the search criteria 3557 * 3558 * @param string $alias Record alias. 3559 * @param array $searcharray Criteria for the search. 3560 * @param int $dataid Data ID for the database 3561 * @param array $recordids An array of record IDs. 3562 * @return array $nestarray An arry of record IDs 3563 */ 3564 function data_get_recordids($alias, $searcharray, $dataid, $recordids) { 3565 global $DB; 3566 $searchcriteria = $alias; // Keep the criteria. 3567 $nestsearch = $searcharray[$alias]; 3568 // searching for content outside of mdl_data_content 3569 if ($alias < 0) { 3570 $alias = ''; 3571 } 3572 list($insql, $params) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED); 3573 $nestselect = 'SELECT c' . $alias . '.recordid 3574 FROM {data_content} c' . $alias . ' 3575 INNER JOIN {data_fields} f 3576 ON f.id = c' . $alias . '.fieldid 3577 INNER JOIN {data_records} r 3578 ON r.id = c' . $alias . '.recordid 3579 INNER JOIN {user} u 3580 ON u.id = r.userid '; 3581 $nestwhere = 'WHERE r.dataid = :dataid 3582 AND c' . $alias .'.recordid ' . $insql . ' 3583 AND '; 3584 3585 $params['dataid'] = $dataid; 3586 if (count($nestsearch->params) != 0) { 3587 $params = array_merge($params, $nestsearch->params); 3588 $nestsql = $nestselect . $nestwhere . $nestsearch->sql; 3589 } else if ($searchcriteria == DATA_TIMEMODIFIED) { 3590 $nestsql = $nestselect . $nestwhere . $nestsearch->field . ' >= :timemodified GROUP BY c' . $alias . '.recordid'; 3591 $params['timemodified'] = $nestsearch->data; 3592 } else if ($searchcriteria == DATA_TAGS) { 3593 if (empty($nestsearch->rawtagnames)) { 3594 return []; 3595 } 3596 $i = 0; 3597 $tagwhere = []; 3598 $tagselect = ''; 3599 foreach ($nestsearch->rawtagnames as $tagrawname) { 3600 $tagselect .= " INNER JOIN {tag_instance} ti_$i 3601 ON ti_$i.component = 'mod_data' 3602 AND ti_$i.itemtype = 'data_records' 3603 AND ti_$i.itemid = r.id 3604 INNER JOIN {tag} t_$i 3605 ON ti_$i.tagid = t_$i.id "; 3606 $tagwhere[] = " t_$i.rawname = :trawname_$i "; 3607 $params["trawname_$i"] = $tagrawname; 3608 $i++; 3609 } 3610 $nestsql = $nestselect . $tagselect . $nestwhere . implode(' AND ', $tagwhere); 3611 } else { // First name or last name. 3612 $thing = $DB->sql_like($nestsearch->field, ':search1', false); 3613 $nestsql = $nestselect . $nestwhere . $thing . ' GROUP BY c' . $alias . '.recordid'; 3614 $params['search1'] = "%$nestsearch->data%"; 3615 } 3616 $nestrecords = $DB->get_recordset_sql($nestsql, $params); 3617 $nestarray = array(); 3618 foreach ($nestrecords as $data) { 3619 $nestarray[] = $data->recordid; 3620 } 3621 // Close the record set and free up resources. 3622 $nestrecords->close(); 3623 return $nestarray; 3624 } 3625 3626 /** 3627 * Returns an array with an sql string for advanced searches and the parameters that go with them. 3628 * 3629 * @param int $sort DATA_* 3630 * @param stdClass $data Data module object 3631 * @param array $recordids An array of record IDs. 3632 * @param string $selectdata Information for the where and select part of the sql statement. 3633 * @param string $sortorder Additional sort parameters 3634 * @return array sqlselect sqlselect['sql'] has the sql string, sqlselect['params'] contains an array of parameters. 3635 */ 3636 function data_get_advanced_search_sql($sort, $data, $recordids, $selectdata, $sortorder) { 3637 global $DB; 3638 3639 $userfieldsapi = \core_user\fields::for_userpic()->excluding('id'); 3640 $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; 3641 3642 if ($sort == 0) { 3643 $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ' 3644 FROM {data_content} c, 3645 {data_records} r, 3646 {user} u '; 3647 $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, u.firstname, u.lastname, ' . $namefields; 3648 } else { 3649 // Sorting through 'Other' criteria 3650 if ($sort <= 0) { 3651 switch ($sort) { 3652 case DATA_LASTNAME: 3653 $sortcontentfull = "u.lastname"; 3654 break; 3655 case DATA_FIRSTNAME: 3656 $sortcontentfull = "u.firstname"; 3657 break; 3658 case DATA_APPROVED: 3659 $sortcontentfull = "r.approved"; 3660 break; 3661 case DATA_TIMEMODIFIED: 3662 $sortcontentfull = "r.timemodified"; 3663 break; 3664 case DATA_TIMEADDED: 3665 default: 3666 $sortcontentfull = "r.timecreated"; 3667 } 3668 } else { 3669 $sortfield = data_get_field_from_id($sort, $data); 3670 $sortcontent = $DB->sql_compare_text('c.' . $sortfield->get_sort_field()); 3671 $sortcontentfull = $sortfield->get_sort_sql($sortcontent); 3672 } 3673 3674 $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ', 3675 ' . $sortcontentfull . ' 3676 AS sortorder 3677 FROM {data_content} c, 3678 {data_records} r, 3679 {user} u '; 3680 $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ', ' .$sortcontentfull; 3681 } 3682 3683 // Default to a standard Where statement if $selectdata is empty. 3684 if ($selectdata == '') { 3685 $selectdata = 'WHERE c.recordid = r.id 3686 AND r.dataid = :dataid 3687 AND r.userid = u.id '; 3688 } 3689 3690 // Find the field we are sorting on 3691 if ($sort > 0 or data_get_field_from_id($sort, $data)) { 3692 $selectdata .= ' AND c.fieldid = :sort AND s.recordid = r.id'; 3693 $nestselectsql .= ',{data_content} s '; 3694 } 3695 3696 // If there are no record IDs then return an sql statment that will return no rows. 3697 if (count($recordids) != 0) { 3698 list($insql, $inparam) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED); 3699 } else { 3700 list($insql, $inparam) = $DB->get_in_or_equal(array('-1'), SQL_PARAMS_NAMED); 3701 } 3702 $nestfromsql = $selectdata . ' AND c.recordid ' . $insql . $groupsql; 3703 $sqlselect['sql'] = "$nestselectsql $nestfromsql $sortorder"; 3704 $sqlselect['params'] = $inparam; 3705 return $sqlselect; 3706 } 3707 3708 /** 3709 * Checks to see if the user has permission to delete the preset. 3710 * @param stdClass $context Context object. 3711 * @param stdClass $preset The preset object that we are checking for deletion. 3712 * @return bool Returns true if the user can delete, otherwise false. 3713 * @deprecated since Moodle 4.1 MDL-75187 - please, use the preset::can_manage() function instead. 3714 * @todo MDL-75189 This will be deleted in Moodle 4.5. 3715 * @see preset::can_manage() 3716 */ 3717 function data_user_can_delete_preset($context, $preset) { 3718 global $USER; 3719 3720 debugging('data_user_can_delete_preset() is deprecated. Please use manager::can_manage() instead.', DEBUG_DEVELOPER); 3721 3722 if ($context->contextlevel == CONTEXT_MODULE && isset($preset->name)) { 3723 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); 3724 $manager = manager::create_from_coursemodule($cm); 3725 $todelete = preset::create_from_instance($manager, $preset->name); 3726 return $todelete->can_manage(); 3727 } 3728 3729 if (has_capability('mod/data:manageuserpresets', $context)) { 3730 return true; 3731 } else { 3732 $candelete = false; 3733 $userid = $preset instanceof preset ? $preset->get_userid() : $preset->userid; 3734 if ($userid == $USER->id) { 3735 $candelete = true; 3736 } 3737 return $candelete; 3738 } 3739 } 3740 3741 /** 3742 * Delete a record entry. 3743 * 3744 * @param int $recordid The ID for the record to be deleted. 3745 * @param object $data The data object for this activity. 3746 * @param int $courseid ID for the current course (for logging). 3747 * @param int $cmid The course module ID. 3748 * @return bool True if the record deleted, false if not. 3749 */ 3750 function data_delete_record($recordid, $data, $courseid, $cmid) { 3751 global $DB, $CFG; 3752 3753 if ($deleterecord = $DB->get_record('data_records', array('id' => $recordid))) { 3754 if ($deleterecord->dataid == $data->id) { 3755 if ($contents = $DB->get_records('data_content', array('recordid' => $deleterecord->id))) { 3756 foreach ($contents as $content) { 3757 if ($field = data_get_field_from_id($content->fieldid, $data)) { 3758 $field->delete_content($content->recordid); 3759 } 3760 } 3761 $DB->delete_records('data_content', array('recordid'=>$deleterecord->id)); 3762 $DB->delete_records('data_records', array('id'=>$deleterecord->id)); 3763 3764 // Delete cached RSS feeds. 3765 if (!empty($CFG->enablerssfeeds)) { 3766 require_once($CFG->dirroot.'/mod/data/rsslib.php'); 3767 data_rss_delete_file($data); 3768 } 3769 3770 core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $recordid); 3771 3772 // Trigger an event for deleting this record. 3773 $event = \mod_data\event\record_deleted::create(array( 3774 'objectid' => $deleterecord->id, 3775 'context' => context_module::instance($cmid), 3776 'courseid' => $courseid, 3777 'other' => array( 3778 'dataid' => $deleterecord->dataid 3779 ) 3780 )); 3781 $event->add_record_snapshot('data_records', $deleterecord); 3782 $event->trigger(); 3783 $course = get_course($courseid); 3784 $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST); 3785 data_update_completion_state($data, $course, $cm); 3786 3787 return true; 3788 } 3789 } 3790 } 3791 3792 return false; 3793 } 3794 3795 /** 3796 * Check for required fields, and build a list of fields to be updated in a 3797 * submission. 3798 * 3799 * @param $mod stdClass The current recordid - provided as an optimisation. 3800 * @param $fields array The field data 3801 * @param $datarecord stdClass The submitted data. 3802 * @return stdClass containing: 3803 * * string[] generalnotifications Notifications for the form as a whole. 3804 * * string[] fieldnotifications Notifications for a specific field. 3805 * * bool validated Whether the field was validated successfully. 3806 * * data_field_base[] fields The field objects to be update. 3807 */ 3808 function data_process_submission(stdClass $mod, $fields, stdClass $datarecord) { 3809 $result = new stdClass(); 3810 3811 // Empty form checking - you can't submit an empty form. 3812 $emptyform = true; 3813 $requiredfieldsfilled = true; 3814 $fieldsvalidated = true; 3815 3816 // Store the notifications. 3817 $result->generalnotifications = array(); 3818 $result->fieldnotifications = array(); 3819 3820 // Store the instantiated classes as an optimisation when processing the result. 3821 // This prevents the fields being re-initialised when updating. 3822 $result->fields = array(); 3823 3824 $submitteddata = array(); 3825 foreach ($datarecord as $fieldname => $fieldvalue) { 3826 if (strpos($fieldname, '_')) { 3827 $namearray = explode('_', $fieldname, 3); 3828 $fieldid = $namearray[1]; 3829 if (!isset($submitteddata[$fieldid])) { 3830 $submitteddata[$fieldid] = array(); 3831 } 3832 if (count($namearray) === 2) { 3833 $subfieldid = 0; 3834 } else { 3835 $subfieldid = $namearray[2]; 3836 } 3837 3838 $fielddata = new stdClass(); 3839 $fielddata->fieldname = $fieldname; 3840 $fielddata->value = $fieldvalue; 3841 $submitteddata[$fieldid][$subfieldid] = $fielddata; 3842 } 3843 } 3844 3845 // Check all form fields which have the required are filled. 3846 foreach ($fields as $fieldrecord) { 3847 // Check whether the field has any data. 3848 $fieldhascontent = false; 3849 3850 $field = data_get_field($fieldrecord, $mod); 3851 if (isset($submitteddata[$fieldrecord->id])) { 3852 // Field validation check. 3853 if (method_exists($field, 'field_validation')) { 3854 $errormessage = $field->field_validation($submitteddata[$fieldrecord->id]); 3855 if ($errormessage) { 3856 $result->fieldnotifications[$field->field->name][] = $errormessage; 3857 $fieldsvalidated = false; 3858 } 3859 } 3860 foreach ($submitteddata[$fieldrecord->id] as $fieldname => $value) { 3861 if ($field->notemptyfield($value->value, $value->fieldname)) { 3862 // The field has content and the form is not empty. 3863 $fieldhascontent = true; 3864 $emptyform = false; 3865 } 3866 } 3867 } 3868 3869 // If the field is required, add a notification to that effect. 3870 if ($field->field->required && !$fieldhascontent) { 3871 if (!isset($result->fieldnotifications[$field->field->name])) { 3872 $result->fieldnotifications[$field->field->name] = array(); 3873 } 3874 $result->fieldnotifications[$field->field->name][] = get_string('errormustsupplyvalue', 'data'); 3875 $requiredfieldsfilled = false; 3876 } 3877 3878 // Update the field. 3879 if (isset($submitteddata[$fieldrecord->id])) { 3880 foreach ($submitteddata[$fieldrecord->id] as $value) { 3881 $result->fields[$value->fieldname] = $field; 3882 } 3883 } 3884 } 3885 3886 if ($emptyform) { 3887 // The form is empty. 3888 $result->generalnotifications[] = get_string('emptyaddform', 'data'); 3889 } 3890 3891 $result->validated = $requiredfieldsfilled && !$emptyform && $fieldsvalidated; 3892 3893 return $result; 3894 } 3895 3896 /** 3897 * This standard function will check all instances of this module 3898 * and make sure there are up-to-date events created for each of them. 3899 * If courseid = 0, then every data event in the site is checked, else 3900 * only data events belonging to the course specified are checked. 3901 * This function is used, in its new format, by restore_refresh_events() 3902 * 3903 * @param int $courseid 3904 * @param int|stdClass $instance Data module instance or ID. 3905 * @param int|stdClass $cm Course module object or ID (not used in this module). 3906 * @return bool 3907 */ 3908 function data_refresh_events($courseid = 0, $instance = null, $cm = null) { 3909 global $DB, $CFG; 3910 require_once($CFG->dirroot.'/mod/data/locallib.php'); 3911 3912 // If we have instance information then we can just update the one event instead of updating all events. 3913 if (isset($instance)) { 3914 if (!is_object($instance)) { 3915 $instance = $DB->get_record('data', array('id' => $instance), '*', MUST_EXIST); 3916 } 3917 data_set_events($instance); 3918 return true; 3919 } 3920 3921 if ($courseid) { 3922 if (! $data = $DB->get_records("data", array("course" => $courseid))) { 3923 return true; 3924 } 3925 } else { 3926 if (! $data = $DB->get_records("data")) { 3927 return true; 3928 } 3929 } 3930 3931 foreach ($data as $datum) { 3932 data_set_events($datum); 3933 } 3934 return true; 3935 } 3936 3937 /** 3938 * Fetch the configuration for this database activity. 3939 * 3940 * @param stdClass $database The object returned from the database for this instance 3941 * @param string $key The name of the key to retrieve. If none is supplied, then all configuration is returned 3942 * @param mixed $default The default value to use if no value was found for the specified key 3943 * @return mixed The returned value 3944 */ 3945 function data_get_config($database, $key = null, $default = null) { 3946 if (!empty($database->config)) { 3947 $config = json_decode($database->config); 3948 } else { 3949 $config = new stdClass(); 3950 } 3951 3952 if ($key === null) { 3953 return $config; 3954 } 3955 3956 if (property_exists($config, $key)) { 3957 return $config->$key; 3958 } 3959 return $default; 3960 } 3961 3962 /** 3963 * Update the configuration for this database activity. 3964 * 3965 * @param stdClass $database The object returned from the database for this instance 3966 * @param string $key The name of the key to set 3967 * @param mixed $value The value to set for the key 3968 */ 3969 function data_set_config(&$database, $key, $value) { 3970 // Note: We must pass $database by reference because there may be subsequent calls to update_record and these should 3971 // not overwrite the configuration just set. 3972 global $DB; 3973 3974 $config = data_get_config($database); 3975 3976 if (!isset($config->$key) || $config->$key !== $value) { 3977 $config->$key = $value; 3978 $database->config = json_encode($config); 3979 $DB->set_field('data', 'config', $database->config, ['id' => $database->id]); 3980 } 3981 } 3982 /** 3983 * Sets the automatic completion state for this database item based on the 3984 * count of on its entries. 3985 * @since Moodle 3.3 3986 * @param object $data The data object for this activity 3987 * @param object $course Course 3988 * @param object $cm course-module 3989 */ 3990 function data_update_completion_state($data, $course, $cm) { 3991 // If completion option is enabled, evaluate it and return true/false. 3992 $completion = new completion_info($course); 3993 if ($data->completionentries && $completion->is_enabled($cm)) { 3994 $numentries = data_numentries($data); 3995 // Check the number of entries required against the number of entries already made. 3996 if ($numentries >= $data->completionentries) { 3997 $completion->update_state($cm, COMPLETION_COMPLETE); 3998 } else { 3999 $completion->update_state($cm, COMPLETION_INCOMPLETE); 4000 } 4001 } 4002 } 4003 4004 /** 4005 * Mark the activity completed (if required) and trigger the course_module_viewed event. 4006 * 4007 * @deprecated since Moodle 4.1 MDL-75146 - please do not use this function any more. 4008 * @todo MDL-75189 Final deprecation in Moodle 4.5. 4009 * @param stdClass $data data object 4010 * @param stdClass $course course object 4011 * @param stdClass $cm course module object 4012 * @param stdClass $context context object 4013 * @since Moodle 3.3 4014 */ 4015 function data_view($data, $course, $cm, $context) { 4016 global $CFG; 4017 debugging('data_view is deprecated. Use mod_data\\manager::set_module_viewed instead', DEBUG_DEVELOPER); 4018 require_once($CFG->libdir . '/completionlib.php'); 4019 4020 // Trigger course_module_viewed event. 4021 $params = array( 4022 'context' => $context, 4023 'objectid' => $data->id 4024 ); 4025 4026 $event = \mod_data\event\course_module_viewed::create($params); 4027 $event->add_record_snapshot('course_modules', $cm); 4028 $event->add_record_snapshot('course', $course); 4029 $event->add_record_snapshot('data', $data); 4030 $event->trigger(); 4031 4032 // Completion. 4033 $completion = new completion_info($course); 4034 $completion->set_module_viewed($cm); 4035 } 4036 4037 /** 4038 * Get icon mapping for font-awesome. 4039 */ 4040 function mod_data_get_fontawesome_icon_map() { 4041 return [ 4042 'mod_data:field/checkbox' => 'fa-check-square-o', 4043 'mod_data:field/date' => 'fa-calendar-o', 4044 'mod_data:field/file' => 'fa-file', 4045 'mod_data:field/latlong' => 'fa-globe', 4046 'mod_data:field/menu' => 'fa-bars', 4047 'mod_data:field/multimenu' => 'fa-bars', 4048 'mod_data:field/number' => 'fa-hashtag', 4049 'mod_data:field/picture' => 'fa-picture-o', 4050 'mod_data:field/radiobutton' => 'fa-circle-o', 4051 'mod_data:field/textarea' => 'fa-font', 4052 'mod_data:field/text' => 'fa-i-cursor', 4053 'mod_data:field/url' => 'fa-link', 4054 ]; 4055 } 4056 4057 /* 4058 * Check if the module has any update that affects the current user since a given time. 4059 * 4060 * @param cm_info $cm course module data 4061 * @param int $from the time to check updates from 4062 * @param array $filter if we need to check only specific updates 4063 * @return stdClass an object with the different type of areas indicating if they were updated or not 4064 * @since Moodle 3.2 4065 */ 4066 function data_check_updates_since(cm_info $cm, $from, $filter = array()) { 4067 global $DB, $CFG; 4068 require_once($CFG->dirroot . '/mod/data/locallib.php'); 4069 4070 $updates = course_check_module_updates_since($cm, $from, array(), $filter); 4071 4072 // Check for new entries. 4073 $updates->entries = (object) array('updated' => false); 4074 4075 $data = $DB->get_record('data', array('id' => $cm->instance), '*', MUST_EXIST); 4076 $searcharray = []; 4077 $searcharray[DATA_TIMEMODIFIED] = new stdClass(); 4078 $searcharray[DATA_TIMEMODIFIED]->sql = ''; 4079 $searcharray[DATA_TIMEMODIFIED]->params = array(); 4080 $searcharray[DATA_TIMEMODIFIED]->field = 'r.timemodified'; 4081 $searcharray[DATA_TIMEMODIFIED]->data = $from; 4082 4083 $currentgroup = groups_get_activity_group($cm); 4084 // Teachers should retrieve all entries when not in separate groups. 4085 if (has_capability('mod/data:manageentries', $cm->context) && groups_get_activity_groupmode($cm) != SEPARATEGROUPS) { 4086 $currentgroup = 0; 4087 } 4088 list($entries, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) = 4089 data_search_entries($data, $cm, $cm->context, 'list', $currentgroup, '', null, null, 0, 0, true, $searcharray); 4090 4091 if (!empty($entries)) { 4092 $updates->entries->updated = true; 4093 $updates->entries->itemids = array_keys($entries); 4094 } 4095 4096 return $updates; 4097 } 4098 4099 /** 4100 * This function receives a calendar event and returns the action associated with it, or null if there is none. 4101 * 4102 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event 4103 * is not displayed on the block. 4104 * 4105 * @param calendar_event $event 4106 * @param \core_calendar\action_factory $factory 4107 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). 4108 * @return \core_calendar\local\event\entities\action_interface|null 4109 */ 4110 function mod_data_core_calendar_provide_event_action(calendar_event $event, 4111 \core_calendar\action_factory $factory, 4112 int $userid = 0) { 4113 global $USER; 4114 4115 if (!$userid) { 4116 $userid = $USER->id; 4117 } 4118 4119 $cm = get_fast_modinfo($event->courseid, $userid)->instances['data'][$event->instance]; 4120 4121 if (!$cm->uservisible) { 4122 // The module is not visible to the user for any reason. 4123 return null; 4124 } 4125 4126 $now = time(); 4127 4128 if (!empty($cm->customdata['timeavailableto']) && $cm->customdata['timeavailableto'] < $now) { 4129 // The module has closed so the user can no longer submit anything. 4130 return null; 4131 } 4132 4133 // The module is actionable if we don't have a start time or the start time is 4134 // in the past. 4135 $actionable = (empty($cm->customdata['timeavailablefrom']) || $cm->customdata['timeavailablefrom'] <= $now); 4136 4137 return $factory->create_instance( 4138 get_string('add', 'data'), 4139 new \moodle_url('/mod/data/view.php', array('id' => $cm->id)), 4140 1, 4141 $actionable 4142 ); 4143 } 4144 4145 /** 4146 * Add a get_coursemodule_info function in case any database type wants to add 'extra' information 4147 * for the course (see resource). 4148 * 4149 * Given a course_module object, this function returns any "extra" information that may be needed 4150 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php. 4151 * 4152 * @param stdClass $coursemodule The coursemodule object (record). 4153 * @return cached_cm_info An object on information that the courses 4154 * will know about (most noticeably, an icon). 4155 */ 4156 function data_get_coursemodule_info($coursemodule) { 4157 global $DB; 4158 4159 $dbparams = ['id' => $coursemodule->instance]; 4160 $fields = 'id, name, intro, introformat, completionentries, timeavailablefrom, timeavailableto'; 4161 if (!$data = $DB->get_record('data', $dbparams, $fields)) { 4162 return false; 4163 } 4164 4165 $result = new cached_cm_info(); 4166 $result->name = $data->name; 4167 4168 if ($coursemodule->showdescription) { 4169 // Convert intro to html. Do not filter cached version, filters run at display time. 4170 $result->content = format_module_intro('data', $data, $coursemodule->id, false); 4171 } 4172 4173 // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'. 4174 if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) { 4175 $result->customdata['customcompletionrules']['completionentries'] = $data->completionentries; 4176 } 4177 // Other properties that may be used in calendar or on dashboard. 4178 if ($data->timeavailablefrom) { 4179 $result->customdata['timeavailablefrom'] = $data->timeavailablefrom; 4180 } 4181 if ($data->timeavailableto) { 4182 $result->customdata['timeavailableto'] = $data->timeavailableto; 4183 } 4184 4185 return $result; 4186 } 4187 4188 /** 4189 * Callback which returns human-readable strings describing the active completion custom rules for the module instance. 4190 * 4191 * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules'] 4192 * @return array $descriptions the array of descriptions for the custom rules. 4193 */ 4194 function mod_data_get_completion_active_rule_descriptions($cm) { 4195 // Values will be present in cm_info, and we assume these are up to date. 4196 if (empty($cm->customdata['customcompletionrules']) 4197 || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) { 4198 return []; 4199 } 4200 4201 $descriptions = []; 4202 foreach ($cm->customdata['customcompletionrules'] as $key => $val) { 4203 switch ($key) { 4204 case 'completionentries': 4205 if (!empty($val)) { 4206 $descriptions[] = get_string('completionentriesdesc', 'data', $val); 4207 } 4208 break; 4209 default: 4210 break; 4211 } 4212 } 4213 return $descriptions; 4214 } 4215 4216 /** 4217 * This function calculates the minimum and maximum cutoff values for the timestart of 4218 * the given event. 4219 * 4220 * It will return an array with two values, the first being the minimum cutoff value and 4221 * the second being the maximum cutoff value. Either or both values can be null, which 4222 * indicates there is no minimum or maximum, respectively. 4223 * 4224 * If a cutoff is required then the function must return an array containing the cutoff 4225 * timestamp and error string to display to the user if the cutoff value is violated. 4226 * 4227 * A minimum and maximum cutoff return value will look like: 4228 * [ 4229 * [1505704373, 'The due date must be after the sbumission start date'], 4230 * [1506741172, 'The due date must be before the cutoff date'] 4231 * ] 4232 * 4233 * @param calendar_event $event The calendar event to get the time range for 4234 * @param stdClass $instance The module instance to get the range from 4235 * @return array 4236 */ 4237 function mod_data_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) { 4238 $mindate = null; 4239 $maxdate = null; 4240 4241 if ($event->eventtype == DATA_EVENT_TYPE_OPEN) { 4242 // The start time of the open event can't be equal to or after the 4243 // close time of the database activity. 4244 if (!empty($instance->timeavailableto)) { 4245 $maxdate = [ 4246 $instance->timeavailableto, 4247 get_string('openafterclose', 'data') 4248 ]; 4249 } 4250 } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) { 4251 // The start time of the close event can't be equal to or earlier than the 4252 // open time of the database activity. 4253 if (!empty($instance->timeavailablefrom)) { 4254 $mindate = [ 4255 $instance->timeavailablefrom, 4256 get_string('closebeforeopen', 'data') 4257 ]; 4258 } 4259 } 4260 4261 return [$mindate, $maxdate]; 4262 } 4263 4264 /** 4265 * This function will update the data module according to the 4266 * event that has been modified. 4267 * 4268 * It will set the timeopen or timeclose value of the data instance 4269 * according to the type of event provided. 4270 * 4271 * @throws \moodle_exception 4272 * @param \calendar_event $event 4273 * @param stdClass $data The module instance to get the range from 4274 */ 4275 function mod_data_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $data) { 4276 global $DB; 4277 4278 if (empty($event->instance) || $event->modulename != 'data') { 4279 return; 4280 } 4281 4282 if ($event->instance != $data->id) { 4283 return; 4284 } 4285 4286 if (!in_array($event->eventtype, [DATA_EVENT_TYPE_OPEN, DATA_EVENT_TYPE_CLOSE])) { 4287 return; 4288 } 4289 4290 $courseid = $event->courseid; 4291 $modulename = $event->modulename; 4292 $instanceid = $event->instance; 4293 $modified = false; 4294 4295 $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid]; 4296 $context = context_module::instance($coursemodule->id); 4297 4298 // The user does not have the capability to modify this activity. 4299 if (!has_capability('moodle/course:manageactivities', $context)) { 4300 return; 4301 } 4302 4303 if ($event->eventtype == DATA_EVENT_TYPE_OPEN) { 4304 // If the event is for the data activity opening then we should 4305 // set the start time of the data activity to be the new start 4306 // time of the event. 4307 if ($data->timeavailablefrom != $event->timestart) { 4308 $data->timeavailablefrom = $event->timestart; 4309 $data->timemodified = time(); 4310 $modified = true; 4311 } 4312 } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) { 4313 // If the event is for the data activity closing then we should 4314 // set the end time of the data activity to be the new start 4315 // time of the event. 4316 if ($data->timeavailableto != $event->timestart) { 4317 $data->timeavailableto = $event->timestart; 4318 $modified = true; 4319 } 4320 } 4321 4322 if ($modified) { 4323 $data->timemodified = time(); 4324 $DB->update_record('data', $data); 4325 $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context); 4326 $event->trigger(); 4327 } 4328 } 4329 4330 /** 4331 * Callback to fetch the activity event type lang string. 4332 * 4333 * @param string $eventtype The event type. 4334 * @return lang_string The event type lang string. 4335 */ 4336 function mod_data_core_calendar_get_event_action_string(string $eventtype): string { 4337 $modulename = get_string('modulename', 'data'); 4338 4339 switch ($eventtype) { 4340 case DATA_EVENT_TYPE_OPEN: 4341 $identifier = 'calendarstart'; 4342 break; 4343 case DATA_EVENT_TYPE_CLOSE: 4344 $identifier = 'calendarend'; 4345 break; 4346 default: 4347 return get_string('requiresaction', 'calendar', $modulename); 4348 } 4349 4350 return get_string($identifier, 'data', $modulename); 4351 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body