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