See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 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 require_once($CFG->dirroot . '/mod/data/lib.php'); 27 require_once($CFG->libdir . '/portfolio/caller.php'); 28 require_once($CFG->libdir . '/filelib.php'); 29 30 /** 31 * The class to handle entry exports of a database module 32 */ 33 class data_portfolio_caller extends portfolio_module_caller_base { 34 35 /** @var int the single record to export */ 36 protected $recordid; 37 38 /** @var object the record from the data table */ 39 private $data; 40 41 /**#@+ @var array the fields used and their fieldtypes */ 42 private $fields; 43 private $fieldtypes; 44 45 /** @var object the records to export */ 46 private $records; 47 48 /** @var int how many records are 'mine' */ 49 private $minecount; 50 51 /** 52 * the required callback arguments for a single-record export 53 * 54 * @return array 55 */ 56 public static function expected_callbackargs() { 57 return array( 58 'id' => true, 59 'recordid' => false, 60 ); 61 } 62 63 /** 64 * @param array $callbackargs the arguments passed through 65 */ 66 public function __construct($callbackargs) { 67 parent::__construct($callbackargs); 68 // set up the list of fields to export 69 $this->selectedfields = array(); 70 foreach ($callbackargs as $key => $value) { 71 if (strpos($key, 'field_') === 0) { 72 $this->selectedfields[] = substr($key, 6); 73 } 74 } 75 } 76 77 /** 78 * load up the data needed for the export 79 * 80 * @global object $DB 81 */ 82 public function load_data() { 83 global $DB, $USER; 84 if (!$this->cm = get_coursemodule_from_id('data', $this->id)) { 85 throw new portfolio_caller_exception('invalidid', 'data'); 86 } 87 if (!$this->data = $DB->get_record('data', array('id' => $this->cm->instance))) { 88 throw new portfolio_caller_exception('invalidid', 'data'); 89 } 90 $fieldrecords = $DB->get_records('data_fields', array('dataid' => $this->cm->instance), 'id'); 91 // populate objets for this databases fields 92 $this->fields = array(); 93 foreach ($fieldrecords as $fieldrecord) { 94 $tmp = data_get_field($fieldrecord, $this->data); 95 $this->fields[] = $tmp; 96 $this->fieldtypes[] = $tmp->type; 97 } 98 99 $this->records = array(); 100 if ($this->recordid) { 101 $tmp = $DB->get_record('data_records', array('id' => $this->recordid)); 102 $tmp->content = $DB->get_records('data_content', array('recordid' => $this->recordid)); 103 $this->records[] = $tmp; 104 } else { 105 $where = array('dataid' => $this->data->id); 106 if (!has_capability('mod/data:exportallentries', context_module::instance($this->cm->id))) { 107 $where['userid'] = $USER->id; // get them all in case, we'll unset ones that aren't ours later if necessary 108 } 109 $tmp = $DB->get_records('data_records', $where); 110 foreach ($tmp as $t) { 111 $t->content = $DB->get_records('data_content', array('recordid' => $t->id)); 112 $this->records[] = $t; 113 } 114 $this->minecount = $DB->count_records('data_records', array('dataid' => $this->data->id, 'userid' => $USER->id)); 115 } 116 117 if ($this->recordid) { 118 list($formats, $files) = self::formats($this->fields, $this->records[0]); 119 $this->set_file_and_format_data($files); 120 } 121 } 122 123 /** 124 * How long we think the export will take 125 * Single entry is probably not too long. 126 * But we check for filesizes 127 * Else base it on the number of records 128 * 129 * @return one of PORTFOLIO_TIME_XX constants 130 */ 131 public function expected_time() { 132 if ($this->recordid) { 133 return $this->expected_time_file(); 134 } else { 135 return portfolio_expected_time_db(count($this->records)); 136 } 137 } 138 139 /** 140 * Calculate the shal1 of this export 141 * Dependent on the export format. 142 * @return string 143 */ 144 public function get_sha1() { 145 // in the case that we're exporting a subclass of 'file' and we have a singlefile, 146 // then we're not exporting any metadata, just the file by itself by mimetype. 147 if ($this->exporter->get('format') instanceof portfolio_format_file && $this->singlefile) { 148 return $this->get_sha1_file(); 149 } 150 // otherwise we're exporting some sort of multipart content so use the data 151 $str = ''; 152 foreach ($this->records as $record) { 153 foreach ($record as $data) { 154 if (is_array($data) || is_object($data)) { 155 $keys = array_keys($data); 156 $testkey = array_pop($keys); 157 if (is_array($data[$testkey]) || is_object($data[$testkey])) { 158 foreach ($data as $d) { 159 $str .= implode(',', (array)$d); 160 } 161 } else { 162 $str .= implode(',', (array)$data); 163 } 164 } else { 165 $str .= $data; 166 } 167 } 168 } 169 return sha1($str . ',' . $this->exporter->get('formatclass')); 170 } 171 172 /** 173 * Prepare the package for export 174 * 175 * @return stored_file object 176 */ 177 public function prepare_package() { 178 global $DB; 179 $leapwriter = null; 180 $content = ''; 181 $filename = ''; 182 $uid = $this->exporter->get('user')->id; 183 $users = array(); //cache 184 $onlymine = $this->get_export_config('mineonly'); 185 if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) { 186 $leapwriter = $this->exporter->get('format')->leap2a_writer(); 187 $ids = array(); 188 } 189 190 if ($this->exporter->get('format') instanceof portfolio_format_file && $this->singlefile) { 191 return $this->get('exporter')->copy_existing_file($this->singlefile); 192 } 193 foreach ($this->records as $key => $record) { 194 if ($onlymine && $record->userid != $uid) { 195 unset($this->records[$key]); // sha1 196 continue; 197 } 198 list($tmpcontent, $files) = $this->exportentry($record); 199 $content .= $tmpcontent; 200 if ($leapwriter) { 201 $entry = new portfolio_format_leap2a_entry('dataentry' . $record->id, $this->data->name, 'resource', $tmpcontent); 202 $entry->published = $record->timecreated; 203 $entry->updated = $record->timemodified; 204 if ($record->userid != $uid) { 205 if (!array_key_exists($record->userid, $users)) { 206 $users[$record->userid] = $DB->get_record('user', array('id' => $record->userid), 'id,firstname,lastname'); 207 } 208 $entry->author = $users[$record->userid]; 209 } 210 $ids[] = $entry->id; 211 $leapwriter->link_files($entry, $files, 'dataentry' . $record->id . 'file'); 212 $leapwriter->add_entry($entry); 213 } 214 } 215 if ($leapwriter) { 216 if (count($this->records) > 1) { // make a selection element to tie them all together 217 $selection = new portfolio_format_leap2a_entry('datadb' . $this->data->id, 218 get_string('entries', 'data') . ': ' . $this->data->name, 'selection'); 219 $leapwriter->add_entry($selection); 220 $leapwriter->make_selection($selection, $ids, 'Grouping'); 221 } 222 $filename = $this->exporter->get('format')->manifest_name(); 223 $content = $leapwriter->to_xml(); 224 } else { 225 if (count($this->records) == 1) { 226 $filename = clean_filename($this->cm->name . '-entry.html'); 227 } else { 228 $filename = clean_filename($this->cm->name . '-full.html'); 229 } 230 } 231 return $this->exporter->write_new_file( 232 $content, 233 $filename, 234 ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH) // if we have associate files, this is a 'manifest' 235 ); 236 } 237 238 /** 239 * Verify the user can still export this entry 240 * 241 * @return bool 242 */ 243 public function check_permissions() { 244 if ($this->recordid) { 245 if (data_isowner($this->recordid)) { 246 return has_capability('mod/data:exportownentry', context_module::instance($this->cm->id)); 247 } 248 return has_capability('mod/data:exportentry', context_module::instance($this->cm->id)); 249 } 250 if ($this->has_export_config() && !$this->get_export_config('mineonly')) { 251 return has_capability('mod/data:exportallentries', context_module::instance($this->cm->id)); 252 } 253 return has_capability('mod/data:exportownentry', context_module::instance($this->cm->id)); 254 } 255 256 /** 257 * @return string 258 */ 259 public static function display_name() { 260 return get_string('modulename', 'data'); 261 } 262 263 /** 264 * @global object 265 * @return bool|void 266 */ 267 public function __wakeup() { 268 global $CFG; 269 if (empty($CFG)) { 270 return true; // too early yet 271 } 272 foreach ($this->fieldtypes as $key => $field) { 273 $filepath = $CFG->dirroot . '/mod/data/field/' . $field .'/field.class.php'; 274 if (!file_exists($filepath)) { 275 continue; 276 } 277 require_once($filepath); 278 $this->fields[$key] = unserialize(serialize($this->fields[$key])); 279 } 280 } 281 282 /** 283 * Prepare a single entry for export, replacing all the content etc 284 * 285 * @param stdclass $record the entry to export 286 * 287 * @return array with key 0 = the html content, key 1 = array of attachments 288 */ 289 private function exportentry($record) { 290 // Replacing tags 291 $patterns = array(); 292 $replacement = array(); 293 294 $files = array(); 295 // Then we generate strings to replace for normal tags 296 $format = $this->get('exporter')->get('format'); 297 foreach ($this->fields as $field) { 298 $patterns[]='[['.$field->field->name.']]'; 299 if (is_callable(array($field, 'get_file'))) { 300 if (!$file = $field->get_file($record->id)) { 301 $replacement[] = ''; 302 continue; // probably left empty 303 } 304 $replacement[] = $format->file_output($file); 305 $this->get('exporter')->copy_existing_file($file); 306 $files[] = $file; 307 } else { 308 $replacement[] = $field->display_browse_field($record->id, 'singletemplate'); 309 } 310 } 311 312 // Replacing special tags (##Edit##, ##Delete##, ##More##) 313 $patterns[]='##edit##'; 314 $patterns[]='##delete##'; 315 $patterns[]='##export##'; 316 $patterns[]='##more##'; 317 $patterns[]='##moreurl##'; 318 $patterns[]='##user##'; 319 $patterns[]='##approve##'; 320 $patterns[]='##disapprove##'; 321 $patterns[]='##comments##'; 322 $patterns[] = '##timeadded##'; 323 $patterns[] = '##timemodified##'; 324 $replacement[] = ''; 325 $replacement[] = ''; 326 $replacement[] = ''; 327 $replacement[] = ''; 328 $replacement[] = ''; 329 $replacement[] = ''; 330 $replacement[] = ''; 331 $replacement[] = ''; 332 $replacement[] = ''; 333 $replacement[] = userdate($record->timecreated); 334 $replacement[] = userdate($record->timemodified); 335 336 if (empty($this->data->singletemplate)) { 337 // Use default template if the template is not created. 338 $this->data->singletemplate = data_generate_default_template($this->data, 'singletemplate', 0, false, false); 339 } 340 341 // actual replacement of the tags 342 return array(str_ireplace($patterns, $replacement, $this->data->singletemplate), $files); 343 } 344 345 /** 346 * Given the fields being exported, and the single record, 347 * work out which export format(s) we can use 348 * 349 * @param array $fields array of field objects 350 * @param object $record The data record object 351 * 352 * @uses PORTFOLIO_FORMAT_PLAINHTML 353 * @uses PORTFOLIO_FORMAT_RICHHTML 354 * 355 * @return array of PORTFOLIO_XX constants 356 */ 357 public static function formats($fields, $record) { 358 $formats = array(PORTFOLIO_FORMAT_PLAINHTML); 359 $includedfiles = array(); 360 foreach ($fields as $singlefield) { 361 if (is_callable(array($singlefield, 'get_file'))) { 362 if ($file = $singlefield->get_file($record->id)) { 363 $includedfiles[] = $file; 364 } 365 } 366 } 367 if (count($includedfiles) == 1 && count($fields) == 1) { 368 $formats = array(portfolio_format_from_mimetype($includedfiles[0]->get_mimetype())); 369 } else if (count($includedfiles) > 0) { 370 $formats = array(PORTFOLIO_FORMAT_RICHHTML); 371 } 372 return array($formats, $includedfiles); 373 } 374 375 public static function has_files($data) { 376 global $DB; 377 $fieldrecords = $DB->get_records('data_fields', array('dataid' => $data->id), 'id'); 378 // populate objets for this databases fields 379 foreach ($fieldrecords as $fieldrecord) { 380 $field = data_get_field($fieldrecord, $data); 381 if (is_callable(array($field, 'get_file'))) { 382 return true; 383 } 384 } 385 return false; 386 } 387 388 /** 389 * base supported formats before we know anything about the export 390 */ 391 public static function base_supported_formats() { 392 return array(PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A); 393 } 394 395 public function has_export_config() { 396 // if we're exporting more than just a single entry, 397 // and we have the capability to export all entries, 398 // then ask whether we want just our own, or all of them 399 return (empty($this->recordid) // multi-entry export 400 && $this->minecount > 0 // some of them are mine 401 && $this->minecount != count($this->records) // not all of them are mine 402 && has_capability('mod/data:exportallentries', context_module::instance($this->cm->id))); // they actually have a choice in the matter 403 } 404 405 public function export_config_form(&$mform, $instance) { 406 if (!$this->has_export_config()) { 407 return; 408 } 409 $mform->addElement('selectyesno', 'mineonly', get_string('exportownentries', 'data', (object)array('mine' => $this->minecount, 'all' => count($this->records)))); 410 $mform->setDefault('mineonly', 1); 411 } 412 413 public function get_allowed_export_config() { 414 return array('mineonly'); 415 } 416 } 417 418 419 /** 420 * Class representing the virtual node with all itemids in the file browser 421 * 422 * @category files 423 * @copyright 2012 David Mudrak <david@moodle.com> 424 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 425 */ 426 class data_file_info_container extends file_info { 427 /** @var file_browser */ 428 protected $browser; 429 /** @var stdClass */ 430 protected $course; 431 /** @var stdClass */ 432 protected $cm; 433 /** @var string */ 434 protected $component; 435 /** @var stdClass */ 436 protected $context; 437 /** @var array */ 438 protected $areas; 439 /** @var string */ 440 protected $filearea; 441 442 /** 443 * Constructor (in case you did not realize it ;-) 444 * 445 * @param file_browser $browser 446 * @param stdClass $course 447 * @param stdClass $cm 448 * @param stdClass $context 449 * @param array $areas 450 * @param string $filearea 451 */ 452 public function __construct($browser, $course, $cm, $context, $areas, $filearea) { 453 parent::__construct($browser, $context); 454 $this->browser = $browser; 455 $this->course = $course; 456 $this->cm = $cm; 457 $this->component = 'mod_data'; 458 $this->context = $context; 459 $this->areas = $areas; 460 $this->filearea = $filearea; 461 } 462 463 /** 464 * @return array with keys contextid, filearea, itemid, filepath and filename 465 */ 466 public function get_params() { 467 return array( 468 'contextid' => $this->context->id, 469 'component' => $this->component, 470 'filearea' => $this->filearea, 471 'itemid' => null, 472 'filepath' => null, 473 'filename' => null, 474 ); 475 } 476 477 /** 478 * Can new files or directories be added via the file browser 479 * 480 * @return bool 481 */ 482 public function is_writable() { 483 return false; 484 } 485 486 /** 487 * Should this node be considered as a folder in the file browser 488 * 489 * @return bool 490 */ 491 public function is_directory() { 492 return true; 493 } 494 495 /** 496 * Returns localised visible name of this node 497 * 498 * @return string 499 */ 500 public function get_visible_name() { 501 return $this->areas[$this->filearea]; 502 } 503 504 /** 505 * Returns list of children nodes 506 * 507 * @return array of file_info instances 508 */ 509 public function get_children() { 510 return $this->get_filtered_children('*', false, true); 511 } 512 513 /** 514 * Help function to return files matching extensions or their count 515 * 516 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 517 * @param bool|int $countonly if false returns the children, if an int returns just the 518 * count of children but stops counting when $countonly number of children is reached 519 * @param bool $returnemptyfolders if true returns items that don't have matching files inside 520 * @return array|int array of file_info instances or the count 521 */ 522 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) { 523 global $DB; 524 $params = array('contextid' => $this->context->id, 525 'component' => $this->component, 526 'filearea' => $this->filearea); 527 $sql = 'SELECT DISTINCT itemid 528 FROM {files} 529 WHERE contextid = :contextid 530 AND component = :component 531 AND filearea = :filearea'; 532 if (!$returnemptyfolders) { 533 $sql .= ' AND filename <> :emptyfilename'; 534 $params['emptyfilename'] = '.'; 535 } 536 list($sql2, $params2) = $this->build_search_files_sql($extensions); 537 $sql .= ' '.$sql2; 538 $params = array_merge($params, $params2); 539 if ($countonly === false) { 540 $sql .= ' ORDER BY itemid DESC'; 541 } 542 543 $rs = $DB->get_recordset_sql($sql, $params); 544 $children = array(); 545 foreach ($rs as $record) { 546 if ($child = $this->browser->get_file_info($this->context, 'mod_data', $this->filearea, $record->itemid)) { 547 $children[] = $child; 548 } 549 if ($countonly !== false && count($children) >= $countonly) { 550 break; 551 } 552 } 553 $rs->close(); 554 if ($countonly !== false) { 555 return count($children); 556 } 557 return $children; 558 } 559 560 /** 561 * Returns list of children which are either files matching the specified extensions 562 * or folders that contain at least one such file. 563 * 564 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 565 * @return array of file_info instances 566 */ 567 public function get_non_empty_children($extensions = '*') { 568 return $this->get_filtered_children($extensions, false); 569 } 570 571 /** 572 * Returns the number of children which are either files matching the specified extensions 573 * or folders containing at least one such file. 574 * 575 * @param string|array $extensions, for example '*' or array('.gif','.jpg') 576 * @param int $limit stop counting after at least $limit non-empty children are found 577 * @return int 578 */ 579 public function count_non_empty_children($extensions = '*', $limit = 1) { 580 return $this->get_filtered_children($extensions, $limit); 581 } 582 583 /** 584 * Returns parent file_info instance 585 * 586 * @return file_info or null for root 587 */ 588 public function get_parent() { 589 return $this->browser->get_file_info($this->context); 590 } 591 } 592 593 /** 594 * This creates new calendar events given as timeavailablefrom and timeclose by $data. 595 * 596 * @param stdClass $data 597 * @return void 598 */ 599 function data_set_events($data) { 600 global $DB, $CFG; 601 602 require_once($CFG->dirroot.'/calendar/lib.php'); 603 604 // Get CMID if not sent as part of $data. 605 if (!isset($data->coursemodule)) { 606 $cm = get_coursemodule_from_instance('data', $data->id, $data->course); 607 $data->coursemodule = $cm->id; 608 } 609 // Data start calendar events. 610 $event = new stdClass(); 611 $event->eventtype = DATA_EVENT_TYPE_OPEN; 612 // The DATA_EVENT_TYPE_OPEN event should only be an action event if no close time was specified. 613 $event->type = empty($data->timeavailableto) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD; 614 if ($event->id = $DB->get_field('event', 'id', 615 array('modulename' => 'data', 'instance' => $data->id, 'eventtype' => $event->eventtype))) { 616 if ($data->timeavailablefrom > 0) { 617 // Calendar event exists so update it. 618 $event->name = get_string('calendarstart', 'data', $data->name); 619 $event->description = format_module_intro('data', $data, $data->coursemodule, false); 620 $event->format = FORMAT_HTML; 621 $event->timestart = $data->timeavailablefrom; 622 $event->timesort = $data->timeavailablefrom; 623 $event->visible = instance_is_visible('data', $data); 624 $event->timeduration = 0; 625 $calendarevent = calendar_event::load($event->id); 626 $calendarevent->update($event, false); 627 } else { 628 // Calendar event is on longer needed. 629 $calendarevent = calendar_event::load($event->id); 630 $calendarevent->delete(); 631 } 632 } else { 633 // Event doesn't exist so create one. 634 if (isset($data->timeavailablefrom) && $data->timeavailablefrom > 0) { 635 $event->name = get_string('calendarstart', 'data', $data->name); 636 $event->description = format_module_intro('data', $data, $data->coursemodule, false); 637 $event->format = FORMAT_HTML; 638 $event->courseid = $data->course; 639 $event->groupid = 0; 640 $event->userid = 0; 641 $event->modulename = 'data'; 642 $event->instance = $data->id; 643 $event->timestart = $data->timeavailablefrom; 644 $event->timesort = $data->timeavailablefrom; 645 $event->visible = instance_is_visible('data', $data); 646 $event->timeduration = 0; 647 calendar_event::create($event, false); 648 } 649 } 650 651 // Data end calendar events. 652 $event = new stdClass(); 653 $event->type = CALENDAR_EVENT_TYPE_ACTION; 654 $event->eventtype = DATA_EVENT_TYPE_CLOSE; 655 if ($event->id = $DB->get_field('event', 'id', 656 array('modulename' => 'data', 'instance' => $data->id, 'eventtype' => $event->eventtype))) { 657 if ($data->timeavailableto > 0) { 658 // Calendar event exists so update it. 659 $event->name = get_string('calendarend', 'data', $data->name); 660 $event->description = format_module_intro('data', $data, $data->coursemodule, false); 661 $event->format = FORMAT_HTML; 662 $event->timestart = $data->timeavailableto; 663 $event->timesort = $data->timeavailableto; 664 $event->visible = instance_is_visible('data', $data); 665 $event->timeduration = 0; 666 $calendarevent = calendar_event::load($event->id); 667 $calendarevent->update($event, false); 668 } else { 669 // Calendar event is on longer needed. 670 $calendarevent = calendar_event::load($event->id); 671 $calendarevent->delete(); 672 } 673 } else { 674 // Event doesn't exist so create one. 675 if (isset($data->timeavailableto) && $data->timeavailableto > 0) { 676 $event->name = get_string('calendarend', 'data', $data->name); 677 $event->description = format_module_intro('data', $data, $data->coursemodule, false); 678 $event->format = FORMAT_HTML; 679 $event->courseid = $data->course; 680 $event->groupid = 0; 681 $event->userid = 0; 682 $event->modulename = 'data'; 683 $event->instance = $data->id; 684 $event->timestart = $data->timeavailableto; 685 $event->timesort = $data->timeavailableto; 686 $event->visible = instance_is_visible('data', $data); 687 $event->timeduration = 0; 688 calendar_event::create($event, false); 689 } 690 } 691 } 692 693 /** 694 * Check if a database is available for the current user. 695 * 696 * @param stdClass $data database record 697 * @param boolean $canmanageentries optional, if the user can manage entries 698 * @param stdClass $context Module context, required if $canmanageentries is not set 699 * @return array status (available or not and possible warnings) 700 * @since Moodle 3.3 701 */ 702 function data_get_time_availability_status($data, $canmanageentries = null, $context = null) { 703 $open = true; 704 $closed = false; 705 $warnings = array(); 706 707 if ($canmanageentries === null) { 708 $canmanageentries = has_capability('mod/data:manageentries', $context); 709 } 710 711 if (!$canmanageentries) { 712 $timenow = time(); 713 714 if (!empty($data->timeavailablefrom) and $data->timeavailablefrom > $timenow) { 715 $open = false; 716 } 717 if (!empty($data->timeavailableto) and $timenow > $data->timeavailableto) { 718 $closed = true; 719 } 720 721 if (!$open or $closed) { 722 if (!$open) { 723 $warnings['notopenyet'] = userdate($data->timeavailablefrom); 724 } 725 if ($closed) { 726 $warnings['expired'] = userdate($data->timeavailableto); 727 } 728 return array(false, $warnings); 729 } 730 } 731 732 // Database is available. 733 return array(true, $warnings); 734 } 735 736 /** 737 * Requires a database to be available for the current user. 738 * 739 * @param stdClass $data database record 740 * @param boolean $canmanageentries optional, if the user can manage entries 741 * @param stdClass $context Module context, required if $canmanageentries is not set 742 * @throws moodle_exception 743 * @since Moodle 3.3 744 */ 745 function data_require_time_available($data, $canmanageentries = null, $context = null) { 746 747 list($available, $warnings) = data_get_time_availability_status($data, $canmanageentries, $context); 748 749 if (!$available) { 750 $reason = current(array_keys($warnings)); 751 throw new moodle_exception($reason, 'data', '', $warnings[$reason]); 752 } 753 } 754 755 /** 756 * Return the number of entries left to add to complete the activity. 757 * 758 * @param stdClass $data database object 759 * @param int $numentries the number of entries the current user has created 760 * @param bool $canmanageentries whether the user can manage entries (teachers, managers) 761 * @return int the number of entries left, 0 if no entries left or if is not required 762 * @since Moodle 3.3 763 */ 764 function data_get_entries_left_to_add($data, $numentries, $canmanageentries) { 765 if ($data->requiredentries > 0 && $numentries < $data->requiredentries && !$canmanageentries) { 766 return $data->requiredentries - $numentries; 767 } 768 return 0; 769 } 770 771 /** 772 * Return the number of entires left to add to view other users entries.. 773 * 774 * @param stdClass $data database object 775 * @param int $numentries the number of entries the current user has created 776 * @param bool $canmanageentries whether the user can manage entries (teachers, managers) 777 * @return int the number of entries left, 0 if no entries left or if is not required 778 * @since Moodle 3.3 779 */ 780 function data_get_entries_left_to_view($data, $numentries, $canmanageentries) { 781 if ($data->requiredentriestoview > 0 && $numentries < $data->requiredentriestoview && !$canmanageentries) { 782 return $data->requiredentriestoview - $numentries; 783 } 784 return 0; 785 } 786 787 /** 788 * Returns data records tagged with a specified tag. 789 * 790 * This is a callback used by the tag area mod_data/data_records to search for data records 791 * tagged with a specific tag. 792 * 793 * @param core_tag_tag $tag 794 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag 795 * are displayed on the page and the per-page limit may be bigger 796 * @param int $fromctx context id where the link was displayed, may be used by callbacks 797 * to display items in the same context first 798 * @param int $ctx context id where to search for records 799 * @param bool $rec search in subcontexts as well 800 * @param int $page 0-based number of page being displayed 801 * @return \core_tag\output\tagindex 802 */ 803 function mod_data_get_tagged_records($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = true, $page = 0) { 804 global $DB, $OUTPUT, $USER; 805 $perpage = $exclusivemode ? 20 : 5; 806 807 // Build the SQL query. 808 $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); 809 $query = "SELECT dr.id, dr.dataid, dr.approved, d.timeviewfrom, d.timeviewto, dr.groupid, d.approval, dr.userid, 810 d.requiredentriestoview, cm.id AS cmid, c.id AS courseid, c.shortname, c.fullname, $ctxselect 811 FROM {data_records} dr 812 JOIN {data} d 813 ON d.id = dr.dataid 814 JOIN {modules} m 815 ON m.name = 'data' 816 JOIN {course_modules} cm 817 ON cm.module = m.id AND cm.instance = d.id 818 JOIN {tag_instance} tt 819 ON dr.id = tt.itemid 820 JOIN {course} c 821 ON cm.course = c.id 822 JOIN {context} ctx 823 ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel 824 WHERE tt.itemtype = :itemtype 825 AND tt.tagid = :tagid 826 AND tt.component = :component 827 AND cm.deletioninprogress = 0 828 AND dr.id %ITEMFILTER% 829 AND c.id %COURSEFILTER%"; 830 831 $params = array( 832 'itemtype' => 'data_records', 833 'tagid' => $tag->id, 834 'component' => 'mod_data', 835 'coursemodulecontextlevel' => CONTEXT_MODULE 836 ); 837 838 if ($ctx) { 839 $context = $ctx ? context::instance_by_id($ctx) : context_system::instance(); 840 $query .= $rec ? ' AND (ctx.id = :contextid OR ctx.path LIKE :path)' : ' AND ctx.id = :contextid'; 841 $params['contextid'] = $context->id; 842 $params['path'] = $context->path . '/%'; 843 } 844 845 $query .= " ORDER BY "; 846 if ($fromctx) { 847 // In order-clause specify that modules from inside "fromctx" context should be returned first. 848 $fromcontext = context::instance_by_id($fromctx); 849 $query .= ' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),'; 850 $params['fromcontextid'] = $fromcontext->id; 851 $params['frompath'] = $fromcontext->path . '/%'; 852 } 853 $query .= ' c.sortorder, cm.id, dr.id'; 854 855 $totalpages = $page + 1; 856 857 // Use core_tag_index_builder to build and filter the list of items. 858 $builder = new core_tag_index_builder('mod_data', 'data_records', $query, $params, $page * $perpage, $perpage + 1); 859 $now = time(); 860 $entrycount = []; 861 $activitygroupmode = []; 862 $usergroups = []; 863 $titlefields = []; 864 while ($item = $builder->has_item_that_needs_access_check()) { 865 context_helper::preload_from_record($item); 866 $modinfo = get_fast_modinfo($item->courseid); 867 $cm = $modinfo->get_cm($item->cmid); 868 $context = \context_module::instance($cm->id); 869 $courseid = $item->courseid; 870 871 if (!$builder->can_access_course($courseid)) { 872 $builder->set_accessible($item, false); 873 continue; 874 } 875 876 if (!$cm->uservisible) { 877 $builder->set_accessible($item, false); 878 continue; 879 } 880 881 if (!has_capability('mod/data:viewentry', $context)) { 882 $builder->set_accessible($item, false); 883 continue; 884 } 885 886 if ($USER->id != $item->userid && (($item->timeviewfrom && $now < $item->timeviewfrom) 887 || ($item->timeviewto && $now > $item->timeviewto))) { 888 $builder->set_accessible($item, false); 889 continue; 890 } 891 892 if ($USER->id != $item->userid && $item->approval && !$item->approved) { 893 $builder->set_accessible($item, false); 894 continue; 895 } 896 897 if ($item->requiredentriestoview) { 898 if (!isset($entrycount[$item->dataid])) { 899 $entrycount[$item->dataid] = $DB->count_records('data_records', array('dataid' => $item->dataid)); 900 } 901 $sufficiententries = $item->requiredentriestoview > $entrycount[$item->dataid]; 902 $builder->set_accessible($item, $sufficiententries); 903 } 904 905 if (!isset($activitygroupmode[$cm->id])) { 906 $activitygroupmode[$cm->id] = groups_get_activity_groupmode($cm); 907 } 908 909 if (!isset($usergroups[$item->groupid])) { 910 $usergroups[$item->groupid] = groups_is_member($item->groupid, $USER->id); 911 } 912 913 if ($activitygroupmode[$cm->id] == SEPARATEGROUPS && !$usergroups[$item->groupid]) { 914 $builder->set_accessible($item, false); 915 continue; 916 } 917 918 $builder->set_accessible($item, true); 919 } 920 921 $items = $builder->get_items(); 922 if (count($items) > $perpage) { 923 $totalpages = $page + 2; // We don't need exact page count, just indicate that the next page exists. 924 array_pop($items); 925 } 926 927 // Build the display contents. 928 if ($items) { 929 $tagfeed = new core_tag\output\tagfeed(); 930 foreach ($items as $item) { 931 context_helper::preload_from_record($item); 932 $modinfo = get_fast_modinfo($item->courseid); 933 $cm = $modinfo->get_cm($item->cmid); 934 $pageurl = new moodle_url('/mod/data/view.php', array( 935 'rid' => $item->id, 936 'd' => $item->dataid 937 )); 938 939 if (!isset($titlefields[$item->dataid])) { 940 $titlefields[$item->dataid] = data_get_tag_title_field($item->dataid); 941 } 942 943 $pagename = data_get_tag_title_for_entry($titlefields[$item->dataid], $item); 944 $pagename = html_writer::link($pageurl, $pagename); 945 $courseurl = course_get_url($item->courseid, $cm->sectionnum); 946 $cmname = html_writer::link($cm->url, $cm->get_formatted_name()); 947 $coursename = format_string($item->fullname, true, array('context' => context_course::instance($item->courseid))); 948 $coursename = html_writer::link($courseurl, $coursename); 949 $icon = html_writer::link($pageurl, html_writer::empty_tag('img', array('src' => $cm->get_icon_url()))); 950 $tagfeed->add($icon, $pagename, $cmname . '<br>' . $coursename); 951 } 952 $content = $OUTPUT->render_from_template('core_tag/tagfeed', $tagfeed->export_for_template($OUTPUT)); 953 954 return new core_tag\output\tagindex($tag, 'mod_data', 'data_records', $content, $exclusivemode, 955 $fromctx, $ctx, $rec, $page, $totalpages); 956 } 957 } 958 959 /** 960 * Get the title of a field to show when displaying tag results. 961 * 962 * @param int $dataid The id of the data field 963 * @return stdClass The field data from the 'data_fields' table as well as it's priority 964 */ 965 function data_get_tag_title_field($dataid) { 966 global $DB, $CFG; 967 968 $validfieldtypes = array('text', 'textarea', 'menu', 'radiobutton', 'checkbox', 'multimenu', 'url'); 969 $fields = $DB->get_records('data_fields', ['dataid' => $dataid]); 970 $template = $DB->get_field('data', 'addtemplate', ['id' => $dataid]); 971 if (empty($template)) { 972 $data = $DB->get_record('data', ['id' => $dataid]); 973 $template = data_generate_default_template($data, 'addtemplate', 0, false, false); 974 } 975 976 $filteredfields = []; 977 978 foreach ($fields as $field) { 979 if (!in_array($field->type, $validfieldtypes)) { 980 continue; 981 } 982 $field->addtemplateposition = strpos($template, '[['.$field->name.']]'); 983 if ($field->addtemplateposition === false) { 984 continue; 985 } 986 $filepath = $CFG->dirroot . '/mod/data/field/' . $field->type . '/field.class.php'; 987 if (!file_exists($filepath)) { 988 continue; 989 } 990 require_once($filepath); 991 $classname = 'data_field_' . $field->type; 992 $field->priority = $classname::get_priority(); 993 $filteredfields[] = $field; 994 } 995 996 $sort = function($record1, $record2) { 997 // If a content's fieldtype is compulsory in the database than it would have priority than any other non-compulsory content. 998 if (($record1->required && $record2->required) || (!$record1->required && !$record2->required)) { 999 if ($record1->priority === $record2->priority) { 1000 return $record1->id < $record2->id ? 1 : -1; 1001 } 1002 1003 return $record1->priority < $record2->priority ? -1 : 1; 1004 } else if ($record1->required && !$record2->required) { 1005 return 1; 1006 } else { 1007 return -1; 1008 } 1009 }; 1010 1011 usort($filteredfields, $sort); 1012 1013 return array_shift($filteredfields); 1014 } 1015 1016 /** 1017 * Get the title of an entry to show when displaying tag results. 1018 * 1019 * @param stdClass $field The field from the 'data_fields' table 1020 * @param stdClass $entry The entry from the 'data_records' table 1021 * @return string|null It will return the title of the entry or null if the field type is not available. 1022 */ 1023 function data_get_tag_title_for_entry($field, $entry) { 1024 global $CFG, $DB; 1025 1026 if (!isset($field->type)) { 1027 return null; 1028 } 1029 $filepath = $CFG->dirroot . '/mod/data/field/' . $field->type . '/field.class.php'; 1030 if (!file_exists($filepath)) { 1031 return null; 1032 } 1033 require_once($filepath); 1034 1035 $classname = 'data_field_' . $field->type; 1036 $sql = "SELECT dc.* 1037 FROM {data_content} dc 1038 INNER JOIN {data_fields} df 1039 ON dc.fieldid = df.id 1040 WHERE df.id = :fieldid 1041 AND dc.recordid = :recordid"; 1042 $fieldcontents = $DB->get_record_sql($sql, array('recordid' => $entry->id, 'fieldid' => $field->id)); 1043 1044 return $classname::get_content_value($fieldcontents); 1045 } 1046 1047 /** 1048 * Search entries in a database. 1049 * 1050 * @param stdClass $data database object 1051 * @param stdClass $cm course module object 1052 * @param stdClass $context context object 1053 * @param stdClass $mode in which mode we are viewing the database (list, single) 1054 * @param int $currentgroup the current group being used 1055 * @param str $search search for this text in the entry data 1056 * @param str $sort the field to sort by 1057 * @param str $order the order to use when sorting 1058 * @param int $page for pagination, the current page 1059 * @param int $perpage entries per page 1060 * @param bool $advanced whether we are using or not advanced search 1061 * @param array $searcharray when using advanced search, the advanced data to use 1062 * @param stdClass $record if we jsut want this record after doing all the access checks 1063 * @return array the entries found among other data related to the search 1064 * @since Moodle 3.3 1065 */ 1066 function data_search_entries($data, $cm, $context, $mode, $currentgroup, $search = '', $sort = null, $order = null, $page = 0, 1067 $perpage = 0, $advanced = null, $searcharray = null, $record = null) { 1068 global $DB, $USER; 1069 1070 if ($sort === null) { 1071 $sort = $data->defaultsort; 1072 } 1073 if ($order === null) { 1074 $order = ($data->defaultsortdir == 0) ? 'ASC' : 'DESC'; 1075 } 1076 if ($searcharray === null) { 1077 $searcharray = array(); 1078 } 1079 1080 if (core_text::strlen($search) < 2) { 1081 $search = ''; 1082 } 1083 1084 $approvecap = has_capability('mod/data:approve', $context); 1085 $canmanageentries = has_capability('mod/data:manageentries', $context); 1086 1087 // If a student is not part of a group and seperate groups is enabled, we don't 1088 // want them seeing all records. 1089 $groupmode = groups_get_activity_groupmode($cm); 1090 if ($currentgroup == 0 && $groupmode == 1 && !$canmanageentries) { 1091 $canviewallrecords = false; 1092 } else { 1093 $canviewallrecords = true; 1094 } 1095 1096 $numentries = data_numentries($data); 1097 $requiredentriesallowed = true; 1098 if (data_get_entries_left_to_view($data, $numentries, $canmanageentries)) { 1099 $requiredentriesallowed = false; 1100 } 1101 1102 // Initialise the first group of params for advanced searches. 1103 $initialparams = array(); 1104 $params = array(); // Named params array. 1105 1106 // Setup group and approve restrictions. 1107 if (!$approvecap && $data->approval) { 1108 if (isloggedin()) { 1109 $approveselect = ' AND (r.approved=1 OR r.userid=:myid1) '; 1110 $params['myid1'] = $USER->id; 1111 $initialparams['myid1'] = $params['myid1']; 1112 } else { 1113 $approveselect = ' AND r.approved=1 '; 1114 } 1115 } else { 1116 $approveselect = ' '; 1117 } 1118 1119 if ($currentgroup) { 1120 $groupselect = " AND (r.groupid = :currentgroup OR r.groupid = 0)"; 1121 $params['currentgroup'] = $currentgroup; 1122 $initialparams['currentgroup'] = $params['currentgroup']; 1123 } else { 1124 if ($canviewallrecords) { 1125 $groupselect = ' '; 1126 } else { 1127 // If separate groups are enabled and the user isn't in a group or 1128 // a teacher, manager, admin etc, then just show them entries for 'All participants'. 1129 $groupselect = " AND r.groupid = 0"; 1130 } 1131 } 1132 1133 // Init some variables to be used by advanced search. 1134 $advsearchselect = ''; 1135 $advwhere = ''; 1136 $advtables = ''; 1137 $advparams = array(); 1138 // This is used for the initial reduction of advanced search results with required entries. 1139 $entrysql = ''; 1140 $userfieldsapi = \core_user\fields::for_userpic()->excluding('id'); 1141 $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; 1142 1143 // Find the field we are sorting on. 1144 if ($sort <= 0 || !($sortfield = data_get_field_from_id($sort, $data))) { 1145 1146 switch ($sort) { 1147 case DATA_LASTNAME: 1148 $ordering = "u.lastname $order, u.firstname $order"; 1149 break; 1150 case DATA_FIRSTNAME: 1151 $ordering = "u.firstname $order, u.lastname $order"; 1152 break; 1153 case DATA_APPROVED: 1154 $ordering = "r.approved $order, r.timecreated $order"; 1155 break; 1156 case DATA_TIMEMODIFIED: 1157 $ordering = "r.timemodified $order"; 1158 break; 1159 case DATA_TIMEADDED: 1160 default: 1161 $sort = 0; 1162 $ordering = "r.timecreated $order"; 1163 } 1164 1165 $what = ' DISTINCT r.id, r.approved, r.timecreated, r.timemodified, r.userid, r.groupid, r.dataid, ' . $namefields; 1166 $count = ' COUNT(DISTINCT c.recordid) '; 1167 $tables = '{data_content} c,{data_records} r, {user} u '; 1168 $where = 'WHERE c.recordid = r.id 1169 AND r.dataid = :dataid 1170 AND r.userid = u.id '; 1171 $params['dataid'] = $data->id; 1172 $sortorder = " ORDER BY $ordering, r.id $order"; 1173 $searchselect = ''; 1174 1175 // If requiredentries is not reached, only show current user's entries. 1176 if (!$requiredentriesallowed) { 1177 $where .= ' AND u.id = :myid2 '; 1178 $entrysql = ' AND r.userid = :myid3 '; 1179 $params['myid2'] = $USER->id; 1180 $initialparams['myid3'] = $params['myid2']; 1181 } 1182 1183 if ($search) { 1184 $searchselect = " AND (".$DB->sql_like('c.content', ':search1', false)." 1185 OR ".$DB->sql_like('u.firstname', ':search2', false)." 1186 OR ".$DB->sql_like('u.lastname', ':search3', false)." ) "; 1187 $params['search1'] = "%$search%"; 1188 $params['search2'] = "%$search%"; 1189 $params['search3'] = "%$search%"; 1190 } else { 1191 $searchselect = ' '; 1192 } 1193 1194 } else { 1195 1196 $sortcontent = $DB->sql_compare_text('s.' . $sortfield->get_sort_field()); 1197 $sortcontentfull = $sortfield->get_sort_sql($sortcontent); 1198 1199 $what = ' DISTINCT r.id, r.approved, r.timecreated, r.timemodified, r.userid, r.groupid, r.dataid, ' . $namefields . ', 1200 ' . $sortcontentfull . ' AS sortorder '; 1201 $count = ' COUNT(DISTINCT c.recordid) '; 1202 $tables = '{data_content} c, {data_records} r, {user} u '; 1203 $where = 'WHERE c.recordid = r.id 1204 AND r.dataid = :dataid 1205 AND r.userid = u.id '; 1206 if (!$advanced) { 1207 $where .= 'AND s.fieldid = :sort AND s.recordid = r.id'; 1208 $tables .= ',{data_content} s '; 1209 } 1210 $params['dataid'] = $data->id; 1211 $params['sort'] = $sort; 1212 $sortorder = ' ORDER BY sortorder '.$order.' , r.id ASC '; 1213 $searchselect = ''; 1214 1215 // If requiredentries is not reached, only show current user's entries. 1216 if (!$requiredentriesallowed) { 1217 $where .= ' AND u.id = :myid2'; 1218 $entrysql = ' AND r.userid = :myid3'; 1219 $params['myid2'] = $USER->id; 1220 $initialparams['myid3'] = $params['myid2']; 1221 } 1222 1223 if ($search) { 1224 $searchselect = " AND (".$DB->sql_like('c.content', ':search1', false)." OR 1225 ".$DB->sql_like('u.firstname', ':search2', false)." OR 1226 ".$DB->sql_like('u.lastname', ':search3', false)." ) "; 1227 $params['search1'] = "%$search%"; 1228 $params['search2'] = "%$search%"; 1229 $params['search3'] = "%$search%"; 1230 } else { 1231 $searchselect = ' '; 1232 } 1233 } 1234 1235 // To actually fetch the records. 1236 1237 $fromsql = "FROM $tables $advtables $where $advwhere $groupselect $approveselect $searchselect $advsearchselect"; 1238 $allparams = array_merge($params, $advparams); 1239 1240 // Provide initial sql statements and parameters to reduce the number of total records. 1241 $initialselect = $groupselect . $approveselect . $entrysql; 1242 1243 $recordids = data_get_all_recordids($data->id, $initialselect, $initialparams); 1244 $newrecordids = data_get_advance_search_ids($recordids, $searcharray, $data->id); 1245 $selectdata = $where . $groupselect . $approveselect; 1246 1247 if (!empty($advanced)) { 1248 $advancedsearchsql = data_get_advanced_search_sql($sort, $data, $newrecordids, $selectdata, $sortorder); 1249 $sqlselect = $advancedsearchsql['sql']; 1250 $allparams = array_merge($allparams, $advancedsearchsql['params']); 1251 $totalcount = count($newrecordids); 1252 } else { 1253 $sqlselect = "SELECT $what $fromsql $sortorder"; 1254 $sqlcountselect = "SELECT $count $fromsql"; 1255 $totalcount = $DB->count_records_sql($sqlcountselect, $allparams); 1256 } 1257 1258 // Work out the paging numbers and counts. 1259 if (empty($searchselect) && empty($advsearchselect)) { 1260 $maxcount = $totalcount; 1261 } else { 1262 $maxcount = count($recordids); 1263 } 1264 1265 if ($record) { // We need to just show one, so where is it in context? 1266 $nowperpage = 1; 1267 $mode = 'single'; 1268 $page = 0; 1269 // TODO MDL-33797 - Reduce this or consider redesigning the paging system. 1270 if ($allrecordids = $DB->get_fieldset_sql($sqlselect, $allparams)) { 1271 $page = (int)array_search($record->id, $allrecordids); 1272 unset($allrecordids); 1273 } 1274 } else if ($mode == 'single') { // We rely on ambient $page settings 1275 $nowperpage = 1; 1276 1277 } else { 1278 $nowperpage = $perpage; 1279 } 1280 1281 // Get the actual records. 1282 if (!$records = $DB->get_records_sql($sqlselect, $allparams, $page * $nowperpage, $nowperpage)) { 1283 // Nothing to show! 1284 if ($record) { // Something was requested so try to show that at least (bug 5132) 1285 if (data_can_view_record($data, $record, $currentgroup, $canmanageentries)) { 1286 // OK, we can show this one 1287 $records = array($record->id => $record); 1288 $totalcount = 1; 1289 } 1290 } 1291 1292 } 1293 1294 return [$records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode]; 1295 } 1296 1297 /** 1298 * Check if the current user can view the given record. 1299 * 1300 * @param stdClass $data database record 1301 * @param stdClass $record the record (entry) to check 1302 * @param int $currentgroup current group 1303 * @param bool $canmanageentries if the user can manage entries 1304 * @return bool true if the user can view the entry 1305 * @since Moodle 3.3 1306 */ 1307 function data_can_view_record($data, $record, $currentgroup, $canmanageentries) { 1308 global $USER; 1309 1310 if ($canmanageentries || empty($data->approval) || 1311 $record->approved || (isloggedin() && $record->userid == $USER->id)) { 1312 1313 if (!$currentgroup || $record->groupid == $currentgroup || $record->groupid == 0) { 1314 return true; 1315 } 1316 } 1317 return false; 1318 } 1319 1320 /** 1321 * Return all the field instances for a given database. 1322 * 1323 * @param stdClass $data database object 1324 * @return array field instances 1325 * @since Moodle 3.3 1326 */ 1327 function data_get_field_instances($data) { 1328 global $DB; 1329 1330 $instances = []; 1331 if ($fields = $DB->get_records('data_fields', array('dataid' => $data->id), 'id')) { 1332 foreach ($fields as $field) { 1333 $instances[] = data_get_field($field, $data); 1334 } 1335 } 1336 return $instances; 1337 } 1338 1339 /** 1340 * Build the search array. 1341 * 1342 * @param stdClass $data the database object 1343 * @param bool $paging if paging is being used 1344 * @param array $searcharray the current search array (saved by session) 1345 * @param array $defaults default values for the searchable fields 1346 * @param str $fn the first name to search (optional) 1347 * @param str $ln the last name to search (optional) 1348 * @return array the search array and plain search build based on the different elements 1349 * @since Moodle 3.3 1350 */ 1351 function data_build_search_array($data, $paging, $searcharray, $defaults = null, $fn = '', $ln = '') { 1352 global $DB; 1353 1354 $search = ''; 1355 $vals = array(); 1356 $fields = $DB->get_records('data_fields', array('dataid' => $data->id)); 1357 1358 if (!empty($fields)) { 1359 foreach ($fields as $field) { 1360 $searchfield = data_get_field_from_id($field->id, $data); 1361 // Get field data to build search sql with. If paging is false, get from user. 1362 // If paging is true, get data from $searcharray which is obtained from the $SESSION (see line 116). 1363 if (!$paging && $searchfield->type != 'unknown') { 1364 $val = $searchfield->parse_search_field($defaults); 1365 } else { 1366 // Set value from session if there is a value @ the required index. 1367 if (isset($searcharray[$field->id])) { 1368 $val = $searcharray[$field->id]->data; 1369 } else { // If there is not an entry @ the required index, set value to blank. 1370 $val = ''; 1371 } 1372 } 1373 if (!empty($val)) { 1374 $searcharray[$field->id] = new stdClass(); 1375 list($searcharray[$field->id]->sql, $searcharray[$field->id]->params) = $searchfield->generate_sql('c'.$field->id, $val); 1376 $searcharray[$field->id]->data = $val; 1377 $vals[] = $val; 1378 } else { 1379 // Clear it out. 1380 unset($searcharray[$field->id]); 1381 } 1382 } 1383 } 1384 1385 $rawtagnames = optional_param_array('tags', false, PARAM_TAGLIST); 1386 1387 if ($rawtagnames) { 1388 $searcharray[DATA_TAGS] = new stdClass(); 1389 $searcharray[DATA_TAGS]->params = []; 1390 $searcharray[DATA_TAGS]->rawtagnames = $rawtagnames; 1391 $searcharray[DATA_TAGS]->sql = ''; 1392 } else { 1393 unset($searcharray[DATA_TAGS]); 1394 } 1395 1396 if (!$paging) { 1397 // Name searching. 1398 $fn = optional_param('u_fn', $fn, PARAM_NOTAGS); 1399 $ln = optional_param('u_ln', $ln, PARAM_NOTAGS); 1400 } else { 1401 $fn = isset($searcharray[DATA_FIRSTNAME]) ? $searcharray[DATA_FIRSTNAME]->data : ''; 1402 $ln = isset($searcharray[DATA_LASTNAME]) ? $searcharray[DATA_LASTNAME]->data : ''; 1403 } 1404 if (!empty($fn)) { 1405 $searcharray[DATA_FIRSTNAME] = new stdClass(); 1406 $searcharray[DATA_FIRSTNAME]->sql = ''; 1407 $searcharray[DATA_FIRSTNAME]->params = array(); 1408 $searcharray[DATA_FIRSTNAME]->field = 'u.firstname'; 1409 $searcharray[DATA_FIRSTNAME]->data = $fn; 1410 $vals[] = $fn; 1411 } else { 1412 unset($searcharray[DATA_FIRSTNAME]); 1413 } 1414 if (!empty($ln)) { 1415 $searcharray[DATA_LASTNAME] = new stdClass(); 1416 $searcharray[DATA_LASTNAME]->sql = ''; 1417 $searcharray[DATA_LASTNAME]->params = array(); 1418 $searcharray[DATA_LASTNAME]->field = 'u.lastname'; 1419 $searcharray[DATA_LASTNAME]->data = $ln; 1420 $vals[] = $ln; 1421 } else { 1422 unset($searcharray[DATA_LASTNAME]); 1423 } 1424 1425 // In case we want to switch to simple search later - there might be multiple values there ;-). 1426 if ($vals) { 1427 $val = reset($vals); 1428 if (is_string($val)) { 1429 $search = $val; 1430 } 1431 } 1432 return [$searcharray, $search]; 1433 } 1434 1435 /** 1436 * Approves or unapproves an entry. 1437 * 1438 * @param int $entryid the entry to approve or unapprove. 1439 * @param bool $approve Whether to approve or unapprove (true for approve false otherwise). 1440 * @since Moodle 3.3 1441 */ 1442 function data_approve_entry($entryid, $approve) { 1443 global $DB; 1444 1445 $newrecord = new stdClass(); 1446 $newrecord->id = $entryid; 1447 $newrecord->approved = $approve ? 1 : 0; 1448 $DB->update_record('data_records', $newrecord); 1449 } 1450 1451 /** 1452 * Populate the field contents of a new record with the submitted data. 1453 * An event has been previously triggered upon the creation of the new record in data_add_record(). 1454 * 1455 * @param stdClass $data database object 1456 * @param stdClass $context context object 1457 * @param int $recordid the new record id 1458 * @param array $fields list of fields of the database 1459 * @param stdClass $datarecord the submitted data 1460 * @param stdClass $processeddata pre-processed submitted fields 1461 * @since Moodle 3.3 1462 */ 1463 function data_add_fields_contents_to_new_record($data, $context, $recordid, $fields, $datarecord, $processeddata) { 1464 global $DB; 1465 1466 // Insert a whole lot of empty records to make sure we have them. 1467 $records = array(); 1468 foreach ($fields as $field) { 1469 $content = new stdClass(); 1470 $content->recordid = $recordid; 1471 $content->fieldid = $field->id; 1472 $records[] = $content; 1473 } 1474 1475 // Bulk insert the records now. Some records may have no data but all must exist. 1476 $DB->insert_records('data_content', $records); 1477 1478 // Add all provided content. 1479 foreach ($processeddata->fields as $fieldname => $field) { 1480 $field->update_content($recordid, $datarecord->$fieldname, $fieldname); 1481 } 1482 } 1483 1484 /** 1485 * Updates the fields contents of an existing record. 1486 * 1487 * @param stdClass $data database object 1488 * @param stdClass $record record to update object 1489 * @param stdClass $context context object 1490 * @param stdClass $datarecord the submitted data 1491 * @param stdClass $processeddata pre-processed submitted fields 1492 * @since Moodle 3.3 1493 */ 1494 function data_update_record_fields_contents($data, $record, $context, $datarecord, $processeddata) { 1495 global $DB; 1496 1497 // Reset the approved flag after edit if the user does not have permission to approve their own entries. 1498 if (!has_capability('mod/data:approve', $context)) { 1499 $record->approved = 0; 1500 } 1501 1502 // Update the parent record. 1503 $record->timemodified = time(); 1504 $DB->update_record('data_records', $record); 1505 1506 // Update all content. 1507 foreach ($processeddata->fields as $fieldname => $field) { 1508 $field->update_content($record->id, $datarecord->$fieldname, $fieldname); 1509 } 1510 1511 // Trigger an event for updating this record. 1512 $event = \mod_data\event\record_updated::create(array( 1513 'objectid' => $record->id, 1514 'context' => $context, 1515 'courseid' => $data->course, 1516 'other' => array( 1517 'dataid' => $data->id 1518 ) 1519 )); 1520 $event->add_record_snapshot('data', $data); 1521 $event->trigger(); 1522 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body