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