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