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