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