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