MDL-80880 quiz: change display of previous attempts summary
[moodle.git] / mod / data / lib.php
blob321aa13c5ee986761726f23bbaed472146f9e6bc
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * @package mod_data
19 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
20 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 use mod_data\manager;
24 use mod_data\preset;
26 defined('MOODLE_INTERNAL') || die();
28 // Some constants
29 define ('DATA_MAX_ENTRIES', 50);
30 define ('DATA_PERPAGE_SINGLE', 1);
32 define ('DATA_FIRSTNAME', -1);
33 define ('DATA_LASTNAME', -2);
34 define ('DATA_APPROVED', -3);
35 define ('DATA_TIMEADDED', 0);
36 define ('DATA_TIMEMODIFIED', -4);
37 define ('DATA_TAGS', -5);
39 define ('DATA_CAP_EXPORT', 'mod/data:viewalluserpresets');
40 // Users having assigned the default role "Non-editing teacher" can export database records
41 // Using the mod/data capability "viewalluserpresets" existing in Moodle 1.9.x.
42 // In Moodle >= 2, new roles may be introduced and used instead.
44 define('DATA_PRESET_COMPONENT', 'mod_data');
45 define('DATA_PRESET_FILEAREA', 'site_presets');
46 define('DATA_PRESET_CONTEXT', SYSCONTEXTID);
48 define('DATA_EVENT_TYPE_OPEN', 'open');
49 define('DATA_EVENT_TYPE_CLOSE', 'close');
51 require_once(__DIR__ . '/deprecatedlib.php');
53 /**
54 * @package mod_data
55 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
56 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
58 class data_field_base { // Base class for Database Field Types (see field/*/field.class.php)
60 /** @var string Subclasses must override the type with their name */
61 var $type = 'unknown';
62 /** @var object The database object that this field belongs to */
63 var $data = NULL;
64 /** @var object The field object itself, if we know it */
65 var $field = NULL;
66 /** @var int Width of the icon for this fieldtype */
67 var $iconwidth = 16;
68 /** @var int Width of the icon for this fieldtype */
69 var $iconheight = 16;
70 /** @var object course module or cmifno */
71 var $cm;
72 /** @var object activity context */
73 var $context;
74 /** @var priority for globalsearch indexing */
75 protected static $priority = self::NO_PRIORITY;
76 /** priority value for invalid fields regarding indexing */
77 const NO_PRIORITY = 0;
78 /** priority value for minimum priority */
79 const MIN_PRIORITY = 1;
80 /** priority value for low priority */
81 const LOW_PRIORITY = 2;
82 /** priority value for high priority */
83 const HIGH_PRIORITY = 3;
84 /** priority value for maximum priority */
85 const MAX_PRIORITY = 4;
87 /** @var bool whether the field is used in preview mode. */
88 protected $preview = false;
90 /**
91 * Constructor function
93 * @global object
94 * @uses CONTEXT_MODULE
95 * @param int $field
96 * @param int $data
97 * @param int $cm
99 function __construct($field=0, $data=0, $cm=0) { // Field or data or both, each can be id or object
100 global $DB;
102 if (empty($field) && empty($data)) {
103 throw new \moodle_exception('missingfield', 'data');
106 if (!empty($field)) {
107 if (is_object($field)) {
108 $this->field = $field; // Programmer knows what they are doing, we hope
109 } else if (!$this->field = $DB->get_record('data_fields', array('id'=>$field))) {
110 throw new \moodle_exception('invalidfieldid', 'data');
112 if (empty($data)) {
113 if (!$this->data = $DB->get_record('data', array('id'=>$this->field->dataid))) {
114 throw new \moodle_exception('invalidid', 'data');
119 if (empty($this->data)) { // We need to define this properly
120 if (!empty($data)) {
121 if (is_object($data)) {
122 $this->data = $data; // Programmer knows what they are doing, we hope
123 } else if (!$this->data = $DB->get_record('data', array('id'=>$data))) {
124 throw new \moodle_exception('invalidid', 'data');
126 } else { // No way to define it!
127 throw new \moodle_exception('missingdata', 'data');
131 if ($cm) {
132 $this->cm = $cm;
133 } else {
134 $this->cm = get_coursemodule_from_instance('data', $this->data->id);
137 if (empty($this->field)) { // We need to define some default values
138 $this->define_default_field();
141 $this->context = context_module::instance($this->cm->id);
145 * Return the field type name.
147 * @return string the filed type.
149 public function get_name(): string {
150 return $this->field->name;
154 * Return if the field type supports preview.
156 * Fields without a preview cannot be displayed in the preset preview.
158 * @return bool if the plugin supports preview.
160 public function supports_preview(): bool {
161 return false;
165 * Generate a fake data_content for this field to be used in preset previews.
167 * Data plugins must override this method and support_preview in order to enable
168 * preset preview for this field.
170 * @param int $recordid the fake record id
171 * @return stdClass the fake record
173 public function get_data_content_preview(int $recordid): stdClass {
174 $message = get_string('nopreviewavailable', 'mod_data', $this->field->name);
175 return (object)[
176 'id' => 0,
177 'fieldid' => $this->field->id,
178 'recordid' => $recordid,
179 'content' => "<span class=\"nopreview\">$message</span>",
180 'content1' => null,
181 'content2' => null,
182 'content3' => null,
183 'content4' => null,
188 * Set the field to preview mode.
190 * @param bool $preview the new preview value
192 public function set_preview(bool $preview) {
193 $this->preview = $preview;
197 * Get the field preview value.
199 * @return bool
201 public function get_preview(): bool {
202 return $this->preview;
207 * This field just sets up a default field object
209 * @return bool
211 function define_default_field() {
212 global $OUTPUT;
213 if (empty($this->data->id)) {
214 echo $OUTPUT->notification('Programmer error: dataid not defined in field class');
216 $this->field = new stdClass();
217 $this->field->id = 0;
218 $this->field->dataid = $this->data->id;
219 $this->field->type = $this->type;
220 $this->field->param1 = '';
221 $this->field->param2 = '';
222 $this->field->param3 = '';
223 $this->field->name = '';
224 $this->field->description = '';
225 $this->field->required = false;
227 return true;
231 * Set up the field object according to data in an object. Now is the time to clean it!
233 * @return bool
235 function define_field($data) {
236 $this->field->type = $this->type;
237 $this->field->dataid = $this->data->id;
239 $this->field->name = trim($data->name);
240 $this->field->description = trim($data->description);
241 $this->field->required = !empty($data->required) ? 1 : 0;
243 if (isset($data->param1)) {
244 $this->field->param1 = trim($data->param1);
246 if (isset($data->param2)) {
247 $this->field->param2 = trim($data->param2);
249 if (isset($data->param3)) {
250 $this->field->param3 = trim($data->param3);
252 if (isset($data->param4)) {
253 $this->field->param4 = trim($data->param4);
255 if (isset($data->param5)) {
256 $this->field->param5 = trim($data->param5);
259 return true;
263 * Insert a new field in the database
264 * We assume the field object is already defined as $this->field
266 * @global object
267 * @return bool
269 function insert_field() {
270 global $DB, $OUTPUT;
272 if (empty($this->field)) {
273 echo $OUTPUT->notification('Programmer error: Field has not been defined yet! See define_field()');
274 return false;
277 $this->field->id = $DB->insert_record('data_fields',$this->field);
279 // Trigger an event for creating this field.
280 $event = \mod_data\event\field_created::create(array(
281 'objectid' => $this->field->id,
282 'context' => $this->context,
283 'other' => array(
284 'fieldname' => $this->field->name,
285 'dataid' => $this->data->id
288 $event->trigger();
290 return true;
295 * Update a field in the database
297 * @global object
298 * @return bool
300 function update_field() {
301 global $DB;
303 $DB->update_record('data_fields', $this->field);
305 // Trigger an event for updating this field.
306 $event = \mod_data\event\field_updated::create(array(
307 'objectid' => $this->field->id,
308 'context' => $this->context,
309 'other' => array(
310 'fieldname' => $this->field->name,
311 'dataid' => $this->data->id
314 $event->trigger();
316 return true;
320 * Delete a field completely
322 * @global object
323 * @return bool
325 function delete_field() {
326 global $DB;
328 if (!empty($this->field->id)) {
329 $manager = manager::create_from_instance($this->data);
331 // Get the field before we delete it.
332 $field = $DB->get_record('data_fields', array('id' => $this->field->id));
334 $this->delete_content();
335 $DB->delete_records('data_fields', array('id'=>$this->field->id));
337 // Trigger an event for deleting this field.
338 $event = \mod_data\event\field_deleted::create(array(
339 'objectid' => $this->field->id,
340 'context' => $this->context,
341 'other' => array(
342 'fieldname' => $this->field->name,
343 'dataid' => $this->data->id
347 if (!$manager->has_fields() && $manager->has_records()) {
348 $DB->delete_records('data_records', ['dataid' => $this->data->id]);
351 $event->add_record_snapshot('data_fields', $field);
352 $event->trigger();
355 return true;
359 * Print the relevant form element in the ADD template for this field
361 * @global object
362 * @param int $recordid
363 * @return string
365 function display_add_field($recordid=0, $formdata=null) {
366 global $DB, $OUTPUT;
368 if ($formdata) {
369 $fieldname = 'field_' . $this->field->id;
370 $content = $formdata->$fieldname;
371 } else if ($recordid) {
372 $content = $DB->get_field('data_content', 'content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid));
373 } else {
374 $content = '';
377 // beware get_field returns false for new, empty records MDL-18567
378 if ($content===false) {
379 $content='';
382 $str = '<div title="' . s($this->field->description) . '">';
383 $str .= '<label for="field_'.$this->field->id.'"><span class="accesshide">'.$this->field->name.'</span>';
384 if ($this->field->required) {
385 $image = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
386 $str .= html_writer::div($image, 'inline-req');
388 $str .= '</label><input class="basefieldinput form-control d-inline mod-data-input" ' .
389 'type="text" name="field_' . $this->field->id . '" ' .
390 'id="field_' . $this->field->id . '" value="' . s($content) . '" />';
391 $str .= '</div>';
393 return $str;
397 * Print the relevant form element to define the attributes for this field
398 * viewable by teachers only.
400 * @global object
401 * @global object
402 * @return void Output is echo'd
404 function display_edit_field() {
405 global $CFG, $DB, $OUTPUT;
407 if (empty($this->field)) { // No field has been defined yet, try and make one
408 $this->define_default_field();
411 // Throw an exception if field type doen't exist. Anyway user should never access to edit a field with an unknown fieldtype.
412 if ($this->type === 'unknown') {
413 throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name]));
416 echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide');
418 echo '<form id="editfield" action="'.$CFG->wwwroot.'/mod/data/field.php" method="post">'."\n";
419 echo '<input type="hidden" name="d" value="'.$this->data->id.'" />'."\n";
420 if (empty($this->field->id)) {
421 echo '<input type="hidden" name="mode" value="add" />'."\n";
422 } else {
423 echo '<input type="hidden" name="fid" value="'.$this->field->id.'" />'."\n";
424 echo '<input type="hidden" name="mode" value="update" />'."\n";
426 echo '<input type="hidden" name="type" value="'.$this->type.'" />'."\n";
427 echo '<input name="sesskey" value="'.sesskey().'" type="hidden" />'."\n";
429 echo $OUTPUT->heading($this->name(), 3);
431 $filepath = $CFG->dirroot.'/mod/data/field/'.$this->type.'/mod.html';
433 if (!file_exists($filepath)) {
434 throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name]));
435 } else {
436 require_once($filepath);
439 $actionbuttons = html_writer::start_div();
440 $actionbuttons .= html_writer::tag('input', null, [
441 'type' => 'submit',
442 'name' => 'cancel',
443 'value' => get_string('cancel'),
444 'class' => 'btn btn-secondary mx-1'
446 $actionbuttons .= html_writer::tag('input', null, [
447 'type' => 'submit',
448 'value' => get_string('save'),
449 'class' => 'btn btn-primary mx-1'
451 $actionbuttons .= html_writer::end_div();
453 $stickyfooter = new core\output\sticky_footer($actionbuttons);
454 echo $OUTPUT->render($stickyfooter);
456 echo '</form>';
458 echo $OUTPUT->box_end();
462 * Validates params of fieldinput data. Overwrite to validate fieldtype specific data.
464 * You are expected to return an array like ['paramname' => 'Error message for paramname param'] if there is an error,
465 * return an empty array if everything is fine.
467 * @param stdClass $fieldinput The field input data to check
468 * @return array $errors if empty validation was fine, otherwise contains one or more error messages
470 public function validate(stdClass $fieldinput): array {
471 return [];
475 * Return the data_content of the field, or generate it if it is in preview mode.
477 * @param int $recordid the record id
478 * @return stdClass|bool the record data or false if none
480 protected function get_data_content(int $recordid) {
481 global $DB;
482 if ($this->preview) {
483 return $this->get_data_content_preview($recordid);
485 return $DB->get_record(
486 'data_content',
487 ['fieldid' => $this->field->id, 'recordid' => $recordid]
492 * Display the content of the field in browse mode
494 * @global object
495 * @param int $recordid
496 * @param object $template
497 * @return bool|string
499 function display_browse_field($recordid, $template) {
500 global $DB;
501 $content = $this->get_data_content($recordid);
502 if (!$content || !isset($content->content)) {
503 return '';
505 $options = new stdClass();
506 if ($this->field->param1 == '1') {
507 // We are autolinking this field, so disable linking within us.
508 $options->filter = false;
510 $options->para = false;
511 $str = format_text($content->content, $content->content1, $options);
512 return $str;
516 * Update the content of one data field in the data_content table
517 * @global object
518 * @param int $recordid
519 * @param mixed $value
520 * @param string $name
521 * @return bool
523 function update_content($recordid, $value, $name=''){
524 global $DB;
526 $content = new stdClass();
527 $content->fieldid = $this->field->id;
528 $content->recordid = $recordid;
529 $content->content = clean_param($value, PARAM_NOTAGS);
531 if ($oldcontent = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
532 $content->id = $oldcontent->id;
533 return $DB->update_record('data_content', $content);
534 } else {
535 return $DB->insert_record('data_content', $content);
540 * Delete all content associated with the field
542 * @global object
543 * @param int $recordid
544 * @return bool
546 function delete_content($recordid=0) {
547 global $DB;
549 if ($recordid) {
550 $conditions = array('fieldid'=>$this->field->id, 'recordid'=>$recordid);
551 } else {
552 $conditions = array('fieldid'=>$this->field->id);
555 $rs = $DB->get_recordset('data_content', $conditions);
556 if ($rs->valid()) {
557 $fs = get_file_storage();
558 foreach ($rs as $content) {
559 $fs->delete_area_files($this->context->id, 'mod_data', 'content', $content->id);
562 $rs->close();
564 return $DB->delete_records('data_content', $conditions);
568 * Check if a field from an add form is empty
570 * @param mixed $value
571 * @param mixed $name
572 * @return bool
574 function notemptyfield($value, $name) {
575 return !empty($value);
579 * Just in case a field needs to print something before the whole form
581 function print_before_form() {
585 * Just in case a field needs to print something after the whole form
587 function print_after_form() {
592 * Returns the sortable field for the content. By default, it's just content
593 * but for some plugins, it could be content 1 - content4
595 * @return string
597 function get_sort_field() {
598 return 'content';
602 * Returns the SQL needed to refer to the column. Some fields may need to CAST() etc.
604 * @param string $fieldname
605 * @return string $fieldname
607 function get_sort_sql($fieldname) {
608 return $fieldname;
612 * Returns the name/type of the field
614 * @return string
616 function name() {
617 return get_string('fieldtypelabel', "datafield_$this->type");
621 * Prints the respective type icon
623 * @global object
624 * @return string
626 function image() {
627 global $OUTPUT;
629 return $OUTPUT->pix_icon('field/' . $this->type, $this->type, 'data');
633 * Per default, it is assumed that fields support text exporting.
634 * Override this (return false) on fields not supporting text exporting.
636 * @return bool true
638 function text_export_supported() {
639 return true;
643 * Per default, it is assumed that fields do not support file exporting. Override this (return true)
644 * on fields supporting file export. You will also have to implement export_file_value().
646 * @return bool true if field will export a file, false otherwise
648 public function file_export_supported(): bool {
649 return false;
653 * Per default, does not return a file (just null).
654 * Override this in fields class, if you want your field to export a file content.
655 * In case you are exporting a file value, export_text_value() should return the corresponding file name.
657 * @param stdClass $record
658 * @return null|string the file content as string or null, if no file content is being provided
660 public function export_file_value(stdClass $record): null|string {
661 return null;
665 * Per default, a field does not support the import of files.
667 * A field type can overwrite this function and return true. In this case it also has to implement the function
668 * import_file_value().
670 * @return false means file imports are not supported
672 public function file_import_supported(): bool {
673 return false;
677 * Returns a stored_file object for exporting a file of a given record.
679 * @param int $contentid content id
680 * @param string $filecontent the content of the file as string
681 * @param string $filename the filename the file should have
683 public function import_file_value(int $contentid, string $filecontent, string $filename): void {
684 return;
688 * Per default, return the record's text value only from the "content" field.
689 * Override this in fields class if necessary.
691 * @param stdClass $record
692 * @return string
694 public function export_text_value(stdClass $record) {
695 if ($this->text_export_supported()) {
696 return $record->content;
698 return '';
702 * @param string $relativepath
703 * @return bool false
705 function file_ok($relativepath) {
706 return false;
710 * Returns the priority for being indexed by globalsearch
712 * @return int
714 public static function get_priority() {
715 return static::$priority;
719 * Returns the presentable string value for a field content.
721 * The returned string should be plain text.
723 * @param stdClass $content
724 * @return string
726 public static function get_content_value($content) {
727 return trim($content->content, "\r\n ");
731 * Return the plugin configs for external functions,
732 * in some cases the configs will need formatting or be returned only if the current user has some capabilities enabled.
734 * @return array the list of config parameters
735 * @since Moodle 3.3
737 public function get_config_for_external() {
738 // Return all the field configs to null (maybe there is a private key for a service or something similar there).
739 $configs = [];
740 for ($i = 1; $i <= 10; $i++) {
741 $configs["param$i"] = null;
743 return $configs;
749 * Given a template and a dataid, generate a default case template
751 * @param stdClass $data the mod_data record.
752 * @param string $template the template name
753 * @param int $recordid the entry record
754 * @param bool $form print a form instead of data
755 * @param bool $update if the function update the $data object or not
756 * @return string the template content or an empty string if no content is available (for instance, when database has no fields).
758 function data_generate_default_template(&$data, $template, $recordid = 0, $form = false, $update = true) {
759 global $DB;
761 if (!$data || !$template) {
762 return '';
765 // These templates are empty by default (they have no content).
766 $emptytemplates = [
767 'csstemplate',
768 'jstemplate',
769 'listtemplateheader',
770 'listtemplatefooter',
771 'rsstitletemplate',
773 if (in_array($template, $emptytemplates)) {
774 return '';
777 $manager = manager::create_from_instance($data);
778 if (empty($manager->get_fields())) {
779 // No template will be returned if there are no fields.
780 return '';
783 $templateclass = \mod_data\template::create_default_template($manager, $template, $form);
784 $templatecontent = $templateclass->get_template_content();
786 if ($update) {
787 // Update the database instance.
788 $newdata = new stdClass();
789 $newdata->id = $data->id;
790 $newdata->{$template} = $templatecontent;
791 $DB->update_record('data', $newdata);
792 $data->{$template} = $templatecontent;
795 return $templatecontent;
799 * Build the form elements to manage tags for a record.
801 * @param int|bool $recordid
802 * @param string[] $selected raw tag names
803 * @return string
805 function data_generate_tag_form($recordid = false, $selected = []) {
806 global $CFG, $DB, $OUTPUT, $PAGE;
808 $tagtypestoshow = \core_tag_area::get_showstandard('mod_data', 'data_records');
809 $showstandard = ($tagtypestoshow != core_tag_tag::HIDE_STANDARD);
810 $typenewtags = ($tagtypestoshow != core_tag_tag::STANDARD_ONLY);
812 $str = html_writer::start_tag('div', array('class' => 'datatagcontrol'));
814 $namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
816 $tagcollid = \core_tag_area::get_collection('mod_data', 'data_records');
817 $tags = [];
818 $selectedtags = [];
820 if ($showstandard) {
821 $tags += $DB->get_records_menu('tag', array('isstandard' => 1, 'tagcollid' => $tagcollid),
822 $namefield, 'id,' . $namefield . ' as fieldname');
825 if ($recordid) {
826 $selectedtags += core_tag_tag::get_item_tags_array('mod_data', 'data_records', $recordid);
829 if (!empty($selected)) {
830 list($sql, $params) = $DB->get_in_or_equal($selected, SQL_PARAMS_NAMED);
831 $params['tagcollid'] = $tagcollid;
832 $sql = "SELECT id, $namefield FROM {tag} WHERE tagcollid = :tagcollid AND rawname $sql";
833 $selectedtags += $DB->get_records_sql_menu($sql, $params);
836 $tags += $selectedtags;
838 $str .= '<select class="custom-select" name="tags[]" id="tags" multiple>';
839 foreach ($tags as $tagid => $tag) {
840 $selected = key_exists($tagid, $selectedtags) ? 'selected' : '';
841 $str .= "<option value='$tag' $selected>$tag</option>";
843 $str .= '</select>';
845 if (has_capability('moodle/tag:manage', context_system::instance()) && $showstandard) {
846 $url = new moodle_url('/tag/manage.php', array('tc' => core_tag_area::get_collection('mod_data',
847 'data_records')));
848 $str .= ' ' . $OUTPUT->action_link($url, get_string('managestandardtags', 'tag'));
851 $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array(
852 '#tags',
853 $typenewtags,
855 get_string('entertags', 'tag'),
856 false,
857 $showstandard,
858 get_string('noselection', 'form')
862 $str .= html_writer::end_tag('div');
864 return $str;
869 * Search for a field name and replaces it with another one in all the
870 * form templates. Set $newfieldname as '' if you want to delete the
871 * field from the form.
873 * @global object
874 * @param object $data
875 * @param string $searchfieldname
876 * @param string $newfieldname
877 * @return bool
879 function data_replace_field_in_templates($data, $searchfieldname, $newfieldname) {
880 global $DB;
882 $newdata = (object)['id' => $data->id];
883 $update = false;
884 $templates = ['listtemplate', 'singletemplate', 'asearchtemplate', 'addtemplate', 'rsstemplate'];
885 foreach ($templates as $templatename) {
886 if (empty($data->$templatename)) {
887 continue;
889 $search = [
890 '[[' . $searchfieldname . ']]',
891 '[[' . $searchfieldname . '#id]]',
892 '[[' . $searchfieldname . '#name]]',
893 '[[' . $searchfieldname . '#description]]',
895 if (empty($newfieldname)) {
896 $replace = ['', '', '', ''];
897 } else {
898 $replace = [
899 '[[' . $newfieldname . ']]',
900 '[[' . $newfieldname . '#id]]',
901 '[[' . $newfieldname . '#name]]',
902 '[[' . $newfieldname . '#description]]',
905 $newdata->{$templatename} = str_ireplace($search, $replace, $data->{$templatename} ?? '');
906 $update = true;
908 if (!$update) {
909 return true;
911 return $DB->update_record('data', $newdata);
916 * Appends a new field at the end of the form template.
918 * @global object
919 * @param object $data
920 * @param string $newfieldname
921 * @return bool if the field has been added or not
923 function data_append_new_field_to_templates($data, $newfieldname): bool {
924 global $DB, $OUTPUT;
926 $newdata = (object)['id' => $data->id];
927 $update = false;
928 $templates = ['singletemplate', 'addtemplate', 'rsstemplate'];
929 foreach ($templates as $templatename) {
930 if (empty($data->$templatename)
931 || strpos($data->$templatename, "[[$newfieldname]]") !== false
932 || strpos($data->$templatename, "##otherfields##") !== false
934 continue;
936 $newdata->$templatename = $data->$templatename;
937 $fields = [[
938 'fieldname' => '[[' . $newfieldname . '#name]]',
939 'fieldcontent' => '[[' . $newfieldname . ']]',
941 $newdata->$templatename .= $OUTPUT->render_from_template(
942 'mod_data/fields_otherfields',
943 ['fields' => $fields, 'classes' => 'added_field']
945 $update = true;
947 if (!$update) {
948 return false;
950 return $DB->update_record('data', $newdata);
955 * given a field name
956 * this function creates an instance of the particular subfield class
958 * @global object
959 * @param string $name
960 * @param object $data
961 * @return object|bool
963 function data_get_field_from_name($name, $data){
964 global $DB;
966 $field = $DB->get_record('data_fields', array('name'=>$name, 'dataid'=>$data->id));
968 if ($field) {
969 return data_get_field($field, $data);
970 } else {
971 return false;
976 * given a field id
977 * this function creates an instance of the particular subfield class
979 * @global object
980 * @param int $fieldid
981 * @param object $data
982 * @return bool|object
984 function data_get_field_from_id($fieldid, $data){
985 global $DB;
987 $field = $DB->get_record('data_fields', array('id'=>$fieldid, 'dataid'=>$data->id));
989 if ($field) {
990 return data_get_field($field, $data);
991 } else {
992 return false;
997 * given a field id
998 * this function creates an instance of the particular subfield class
1000 * @global object
1001 * @param string $type
1002 * @param object $data
1003 * @return object
1005 function data_get_field_new($type, $data) {
1006 global $CFG;
1008 $filepath = $CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php';
1009 // It should never access this method if the subfield class doesn't exist.
1010 if (!file_exists($filepath)) {
1011 throw new \moodle_exception('invalidfieldtype', 'data');
1013 require_once($filepath);
1014 $newfield = 'data_field_'.$type;
1015 $newfield = new $newfield(0, $data);
1016 return $newfield;
1020 * returns a subclass field object given a record of the field, used to
1021 * invoke plugin methods
1022 * input: $param $field - record from db
1024 * @global object
1025 * @param stdClass $field the field record
1026 * @param stdClass $data the data instance
1027 * @param stdClass|null $cm optional course module data
1028 * @return data_field_base the field object instance or data_field_base if unkown type
1030 function data_get_field(stdClass $field, stdClass $data, ?stdClass $cm=null): data_field_base {
1031 global $CFG;
1032 if (!isset($field->type)) {
1033 return new data_field_base($field);
1035 $filepath = $CFG->dirroot.'/mod/data/field/'.$field->type.'/field.class.php';
1036 if (!file_exists($filepath)) {
1037 return new data_field_base($field);
1039 require_once($filepath);
1040 $newfield = 'data_field_'.$field->type;
1041 $newfield = new $newfield($field, $data, $cm);
1042 return $newfield;
1047 * Given record object (or id), returns true if the record belongs to the current user
1049 * @global object
1050 * @global object
1051 * @param mixed $record record object or id
1052 * @return bool
1054 function data_isowner($record) {
1055 global $USER, $DB;
1057 if (!isloggedin()) { // perf shortcut
1058 return false;
1061 if (!is_object($record)) {
1062 if (!$record = $DB->get_record('data_records', array('id'=>$record))) {
1063 return false;
1067 return ($record->userid == $USER->id);
1071 * has a user reached the max number of entries?
1073 * @param object $data
1074 * @return bool
1076 function data_atmaxentries($data){
1077 if (!$data->maxentries){
1078 return false;
1080 } else {
1081 return (data_numentries($data) >= $data->maxentries);
1086 * returns the number of entries already made by this user
1088 * @global object
1089 * @global object
1090 * @param object $data
1091 * @return int
1093 function data_numentries($data, $userid=null) {
1094 global $USER, $DB;
1095 if ($userid === null) {
1096 $userid = $USER->id;
1098 $sql = 'SELECT COUNT(*) FROM {data_records} WHERE dataid=? AND userid=?';
1099 return $DB->count_records_sql($sql, array($data->id, $userid));
1103 * function that takes in a dataid and adds a record
1104 * this is used everytime an add template is submitted
1106 * @global object
1107 * @global object
1108 * @param object $data
1109 * @param int $groupid
1110 * @param int $userid
1111 * @return bool
1113 function data_add_record($data, $groupid = 0, $userid = null) {
1114 global $USER, $DB;
1116 $cm = get_coursemodule_from_instance('data', $data->id);
1117 $context = context_module::instance($cm->id);
1119 $record = new stdClass();
1120 $record->userid = $userid ?? $USER->id;
1121 $record->dataid = $data->id;
1122 $record->groupid = $groupid;
1123 $record->timecreated = $record->timemodified = time();
1124 if (has_capability('mod/data:approve', $context)) {
1125 $record->approved = 1;
1126 } else {
1127 $record->approved = 0;
1129 $record->id = $DB->insert_record('data_records', $record);
1131 // Trigger an event for creating this record.
1132 $event = \mod_data\event\record_created::create(array(
1133 'objectid' => $record->id,
1134 'context' => $context,
1135 'other' => array(
1136 'dataid' => $data->id
1139 $event->trigger();
1141 $course = get_course($cm->course);
1142 data_update_completion_state($data, $course, $cm);
1144 return $record->id;
1148 * check the multple existence any tag in a template
1150 * check to see if there are 2 or more of the same tag being used.
1152 * @global object
1153 * @param int $dataid,
1154 * @param string $template
1155 * @return bool
1157 function data_tags_check($dataid, $template) {
1158 global $DB, $OUTPUT;
1160 // first get all the possible tags
1161 $fields = $DB->get_records('data_fields', array('dataid'=>$dataid));
1162 // then we generate strings to replace
1163 $tagsok = true; // let's be optimistic
1164 foreach ($fields as $field){
1165 $pattern="/\[\[" . preg_quote($field->name, '/') . "\]\]/i";
1166 if (preg_match_all($pattern, $template, $dummy)>1){
1167 $tagsok = false;
1168 echo $OUTPUT->notification('[['.$field->name.']] - '.get_string('multipletags','data'));
1171 // else return true
1172 return $tagsok;
1176 * Adds an instance of a data
1178 * @param stdClass $data
1179 * @param mod_data_mod_form $mform
1180 * @return int intance id
1182 function data_add_instance($data, $mform = null) {
1183 global $DB, $CFG;
1184 require_once($CFG->dirroot.'/mod/data/locallib.php');
1186 if (empty($data->assessed)) {
1187 $data->assessed = 0;
1190 if (empty($data->ratingtime) || empty($data->assessed)) {
1191 $data->assesstimestart = 0;
1192 $data->assesstimefinish = 0;
1195 $data->timemodified = time();
1197 $data->id = $DB->insert_record('data', $data);
1199 // Add calendar events if necessary.
1200 data_set_events($data);
1201 if (!empty($data->completionexpected)) {
1202 \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $data->completionexpected);
1205 data_grade_item_update($data);
1207 return $data->id;
1211 * updates an instance of a data
1213 * @global object
1214 * @param object $data
1215 * @return bool
1217 function data_update_instance($data) {
1218 global $DB, $CFG;
1219 require_once($CFG->dirroot.'/mod/data/locallib.php');
1221 $data->timemodified = time();
1222 if (!empty($data->instance)) {
1223 $data->id = $data->instance;
1226 if (empty($data->assessed)) {
1227 $data->assessed = 0;
1230 if (empty($data->ratingtime) or empty($data->assessed)) {
1231 $data->assesstimestart = 0;
1232 $data->assesstimefinish = 0;
1235 if (empty($data->notification)) {
1236 $data->notification = 0;
1239 $DB->update_record('data', $data);
1241 // Add calendar events if necessary.
1242 data_set_events($data);
1243 $completionexpected = (!empty($data->completionexpected)) ? $data->completionexpected : null;
1244 \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $completionexpected);
1246 data_grade_item_update($data);
1248 return true;
1253 * deletes an instance of a data
1255 * @global object
1256 * @param int $id
1257 * @return bool
1259 function data_delete_instance($id) { // takes the dataid
1260 global $DB, $CFG;
1262 if (!$data = $DB->get_record('data', array('id'=>$id))) {
1263 return false;
1266 $cm = get_coursemodule_from_instance('data', $data->id);
1267 $context = context_module::instance($cm->id);
1269 // Delete all information related to fields.
1270 $fields = $DB->get_records('data_fields', ['dataid' => $id]);
1271 foreach ($fields as $field) {
1272 $todelete = data_get_field($field, $data, $cm);
1273 $todelete->delete_field();
1276 // Remove old calendar events.
1277 $events = $DB->get_records('event', array('modulename' => 'data', 'instance' => $id));
1278 foreach ($events as $event) {
1279 $event = calendar_event::load($event);
1280 $event->delete();
1283 // cleanup gradebook
1284 data_grade_item_delete($data);
1286 // Delete the instance itself
1287 // We must delete the module record after we delete the grade item.
1288 $result = $DB->delete_records('data', array('id'=>$id));
1290 return $result;
1294 * returns a summary of data activity of this user
1296 * @global object
1297 * @param object $course
1298 * @param object $user
1299 * @param object $mod
1300 * @param object $data
1301 * @return object|null
1303 function data_user_outline($course, $user, $mod, $data) {
1304 global $DB, $CFG;
1305 require_once("$CFG->libdir/gradelib.php");
1307 $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1308 if (empty($grades->items[0]->grades)) {
1309 $grade = false;
1310 } else {
1311 $grade = reset($grades->items[0]->grades);
1315 if ($countrecords = $DB->count_records('data_records', array('dataid'=>$data->id, 'userid'=>$user->id))) {
1316 $result = new stdClass();
1317 $result->info = get_string('numrecords', 'data', $countrecords);
1318 $lastrecord = $DB->get_record_sql('SELECT id,timemodified FROM {data_records}
1319 WHERE dataid = ? AND userid = ?
1320 ORDER BY timemodified DESC', array($data->id, $user->id), true);
1321 $result->time = $lastrecord->timemodified;
1322 if ($grade) {
1323 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1324 $result->info .= ', ' . get_string('gradenoun') . ': ' . $grade->str_long_grade;
1325 } else {
1326 $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
1329 return $result;
1330 } else if ($grade) {
1331 $result = (object) [
1332 'time' => grade_get_date_for_user_grade($grade, $user),
1334 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1335 $result->info = get_string('gradenoun') . ': ' . $grade->str_long_grade;
1336 } else {
1337 $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
1340 return $result;
1342 return NULL;
1346 * Prints all the records uploaded by this user
1348 * @global object
1349 * @param object $course
1350 * @param object $user
1351 * @param object $mod
1352 * @param object $data
1354 function data_user_complete($course, $user, $mod, $data) {
1355 global $DB, $CFG, $OUTPUT;
1356 require_once("$CFG->libdir/gradelib.php");
1358 $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1359 if (!empty($grades->items[0]->grades)) {
1360 $grade = reset($grades->items[0]->grades);
1361 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1362 echo $OUTPUT->container(get_string('gradenoun') . ': ' . $grade->str_long_grade);
1363 if ($grade->str_feedback) {
1364 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1366 } else {
1367 echo $OUTPUT->container(get_string('gradenoun') . ': ' . get_string('hidden', 'grades'));
1370 $records = $DB->get_records(
1371 'data_records',
1372 ['dataid' => $data->id, 'userid' => $user->id],
1373 'timemodified DESC'
1375 if ($records) {
1376 $manager = manager::create_from_instance($data);
1377 $parser = $manager->get_template('singletemplate');
1378 echo $parser->parse_entries($records);
1383 * Return grade for given user or all users.
1385 * @global object
1386 * @param object $data
1387 * @param int $userid optional user id, 0 means all users
1388 * @return array array of grades, false if none
1390 function data_get_user_grades($data, $userid=0) {
1391 global $CFG;
1393 require_once($CFG->dirroot.'/rating/lib.php');
1395 $ratingoptions = new stdClass;
1396 $ratingoptions->component = 'mod_data';
1397 $ratingoptions->ratingarea = 'entry';
1398 $ratingoptions->modulename = 'data';
1399 $ratingoptions->moduleid = $data->id;
1401 $ratingoptions->userid = $userid;
1402 $ratingoptions->aggregationmethod = $data->assessed;
1403 $ratingoptions->scaleid = $data->scale;
1404 $ratingoptions->itemtable = 'data_records';
1405 $ratingoptions->itemtableusercolumn = 'userid';
1407 $rm = new rating_manager();
1408 return $rm->get_user_grades($ratingoptions);
1412 * Update activity grades
1414 * @category grade
1415 * @param object $data
1416 * @param int $userid specific user only, 0 means all
1417 * @param bool $nullifnone
1419 function data_update_grades($data, $userid=0, $nullifnone=true) {
1420 global $CFG, $DB;
1421 require_once($CFG->libdir.'/gradelib.php');
1423 if (!$data->assessed) {
1424 data_grade_item_update($data);
1426 } else if ($grades = data_get_user_grades($data, $userid)) {
1427 data_grade_item_update($data, $grades);
1429 } else if ($userid and $nullifnone) {
1430 $grade = new stdClass();
1431 $grade->userid = $userid;
1432 $grade->rawgrade = NULL;
1433 data_grade_item_update($data, $grade);
1435 } else {
1436 data_grade_item_update($data);
1441 * Update/create grade item for given data
1443 * @category grade
1444 * @param stdClass $data A database instance with extra cmidnumber property
1445 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1446 * @return object grade_item
1448 function data_grade_item_update($data, $grades=NULL) {
1449 global $CFG;
1450 require_once($CFG->libdir.'/gradelib.php');
1452 $params = array('itemname'=>$data->name, 'idnumber'=>$data->cmidnumber);
1454 if (!$data->assessed or $data->scale == 0) {
1455 $params['gradetype'] = GRADE_TYPE_NONE;
1457 } else if ($data->scale > 0) {
1458 $params['gradetype'] = GRADE_TYPE_VALUE;
1459 $params['grademax'] = $data->scale;
1460 $params['grademin'] = 0;
1462 } else if ($data->scale < 0) {
1463 $params['gradetype'] = GRADE_TYPE_SCALE;
1464 $params['scaleid'] = -$data->scale;
1467 if ($grades === 'reset') {
1468 $params['reset'] = true;
1469 $grades = NULL;
1472 return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, $grades, $params);
1476 * Delete grade item for given data
1478 * @category grade
1479 * @param object $data object
1480 * @return object grade_item
1482 function data_grade_item_delete($data) {
1483 global $CFG;
1484 require_once($CFG->libdir.'/gradelib.php');
1486 return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, NULL, array('deleted'=>1));
1489 // junk functions
1491 * takes a list of records, the current data, a search string,
1492 * and mode to display prints the translated template
1494 * @deprecated since Moodle 4.1 MDL-75146 - please do not use this function any more.
1495 * @todo MDL-75189 Final deprecation in Moodle 4.5.
1496 * @param string $templatename the template name
1497 * @param array $records the entries records
1498 * @param stdClass $data the database instance object
1499 * @param string $search the current search term
1500 * @param int $page page number for pagination
1501 * @param bool $return if the result should be returned (true) or printed (false)
1502 * @param moodle_url|null $jumpurl a moodle_url by which to jump back to the record list (can be null)
1503 * @return mixed string with all parsed entries or nothing if $return is false
1505 function data_print_template($templatename, $records, $data, $search='', $page=0, $return=false, moodle_url $jumpurl=null) {
1506 debugging(
1507 'data_print_template is deprecated. Use mod_data\\manager::get_template and mod_data\\template::parse_entries instead',
1508 DEBUG_DEVELOPER
1511 $options = [
1512 'search' => $search,
1513 'page' => $page,
1515 if ($jumpurl) {
1516 $options['baseurl'] = $jumpurl;
1518 $manager = manager::create_from_instance($data);
1519 $parser = $manager->get_template($templatename, $options);
1520 $content = $parser->parse_entries($records);
1521 if ($return) {
1522 return $content;
1524 echo $content;
1528 * Return rating related permissions
1530 * @param string $contextid the context id
1531 * @param string $component the component to get rating permissions for
1532 * @param string $ratingarea the rating area to get permissions for
1533 * @return array an associative array of the user's rating permissions
1535 function data_rating_permissions($contextid, $component, $ratingarea) {
1536 $context = context::instance_by_id($contextid, MUST_EXIST);
1537 if ($component != 'mod_data' || $ratingarea != 'entry') {
1538 return null;
1540 return array(
1541 'view' => has_capability('mod/data:viewrating',$context),
1542 'viewany' => has_capability('mod/data:viewanyrating',$context),
1543 'viewall' => has_capability('mod/data:viewallratings',$context),
1544 'rate' => has_capability('mod/data:rate',$context)
1549 * Validates a submitted rating
1550 * @param array $params submitted data
1551 * context => object the context in which the rated items exists [required]
1552 * itemid => int the ID of the object being rated
1553 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
1554 * rating => int the submitted rating
1555 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
1556 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
1557 * @return boolean true if the rating is valid. Will throw rating_exception if not
1559 function data_rating_validate($params) {
1560 global $DB, $USER;
1562 // Check the component is mod_data
1563 if ($params['component'] != 'mod_data') {
1564 throw new rating_exception('invalidcomponent');
1567 // Check the ratingarea is entry (the only rating area in data module)
1568 if ($params['ratingarea'] != 'entry') {
1569 throw new rating_exception('invalidratingarea');
1572 // Check the rateduserid is not the current user .. you can't rate your own entries
1573 if ($params['rateduserid'] == $USER->id) {
1574 throw new rating_exception('nopermissiontorate');
1577 $datasql = "SELECT d.id as dataid, d.scale, d.course, r.userid as userid, d.approval, r.approved, r.timecreated, d.assesstimestart, d.assesstimefinish, r.groupid
1578 FROM {data_records} r
1579 JOIN {data} d ON r.dataid = d.id
1580 WHERE r.id = :itemid";
1581 $dataparams = array('itemid'=>$params['itemid']);
1582 if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1583 //item doesn't exist
1584 throw new rating_exception('invaliditemid');
1587 if ($info->scale != $params['scaleid']) {
1588 //the scale being submitted doesnt match the one in the database
1589 throw new rating_exception('invalidscaleid');
1592 //check that the submitted rating is valid for the scale
1594 // lower limit
1595 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
1596 throw new rating_exception('invalidnum');
1599 // upper limit
1600 if ($info->scale < 0) {
1601 //its a custom scale
1602 $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
1603 if ($scalerecord) {
1604 $scalearray = explode(',', $scalerecord->scale);
1605 if ($params['rating'] > count($scalearray)) {
1606 throw new rating_exception('invalidnum');
1608 } else {
1609 throw new rating_exception('invalidscaleid');
1611 } else if ($params['rating'] > $info->scale) {
1612 //if its numeric and submitted rating is above maximum
1613 throw new rating_exception('invalidnum');
1616 if ($info->approval && !$info->approved) {
1617 //database requires approval but this item isnt approved
1618 throw new rating_exception('nopermissiontorate');
1621 // check the item we're rating was created in the assessable time window
1622 if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
1623 if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
1624 throw new rating_exception('notavailable');
1628 $course = $DB->get_record('course', array('id'=>$info->course), '*', MUST_EXIST);
1629 $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1630 $context = context_module::instance($cm->id);
1632 // if the supplied context doesnt match the item's context
1633 if ($context->id != $params['context']->id) {
1634 throw new rating_exception('invalidcontext');
1637 // Make sure groups allow this user to see the item they're rating
1638 $groupid = $info->groupid;
1639 if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
1640 if (!groups_group_exists($groupid)) { // Can't find group
1641 throw new rating_exception('cannotfindgroup');//something is wrong
1644 if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
1645 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
1646 throw new rating_exception('notmemberofgroup');
1650 return true;
1654 * Can the current user see ratings for a given itemid?
1656 * @param array $params submitted data
1657 * contextid => int contextid [required]
1658 * component => The component for this module - should always be mod_data [required]
1659 * ratingarea => object the context in which the rated items exists [required]
1660 * itemid => int the ID of the object being rated [required]
1661 * scaleid => int scale id [optional]
1662 * @return bool
1663 * @throws coding_exception
1664 * @throws rating_exception
1666 function mod_data_rating_can_see_item_ratings($params) {
1667 global $DB;
1669 // Check the component is mod_data.
1670 if (!isset($params['component']) || $params['component'] != 'mod_data') {
1671 throw new rating_exception('invalidcomponent');
1674 // Check the ratingarea is entry (the only rating area in data).
1675 if (!isset($params['ratingarea']) || $params['ratingarea'] != 'entry') {
1676 throw new rating_exception('invalidratingarea');
1679 if (!isset($params['itemid'])) {
1680 throw new rating_exception('invaliditemid');
1683 $datasql = "SELECT d.id as dataid, d.course, r.groupid
1684 FROM {data_records} r
1685 JOIN {data} d ON r.dataid = d.id
1686 WHERE r.id = :itemid";
1687 $dataparams = array('itemid' => $params['itemid']);
1688 if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1689 // Item doesn't exist.
1690 throw new rating_exception('invaliditemid');
1693 // User can see ratings of all participants.
1694 if ($info->groupid == 0) {
1695 return true;
1698 $course = $DB->get_record('course', array('id' => $info->course), '*', MUST_EXIST);
1699 $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1701 // Make sure groups allow this user to see the item they're rating.
1702 return groups_group_visible($info->groupid, $course, $cm);
1707 * function that takes in the current data, number of items per page,
1708 * a search string and prints a preference box in view.php
1710 * This preference box prints a searchable advanced search template if
1711 * a) A template is defined
1712 * b) The advanced search checkbox is checked.
1714 * @global object
1715 * @global object
1716 * @param object $data
1717 * @param int $perpage
1718 * @param string $search
1719 * @param string $sort
1720 * @param string $order
1721 * @param array $search_array
1722 * @param int $advanced
1723 * @param string $mode
1724 * @return void
1726 function data_print_preference_form($data, $perpage, $search, $sort='', $order='ASC', $search_array = '', $advanced = 0, $mode= ''){
1727 global $DB, $PAGE, $OUTPUT;
1729 $cm = get_coursemodule_from_instance('data', $data->id);
1730 $context = context_module::instance($cm->id);
1731 echo '<div class="datapreferences my-5">';
1732 echo '<form id="options" action="view.php" method="get">';
1733 echo '<div class="d-flex">';
1734 echo '<div>';
1735 echo '<input type="hidden" name="d" value="'.$data->id.'" />';
1736 if ($mode =='asearch') {
1737 $advanced = 1;
1738 echo '<input type="hidden" name="mode" value="list" />';
1740 echo '<label for="pref_perpage">'.get_string('pagesize','data').'</label> ';
1741 $pagesizes = array(2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9,10=>10,15=>15,
1742 20=>20,30=>30,40=>40,50=>50,100=>100,200=>200,300=>300,400=>400,500=>500,1000=>1000);
1743 echo html_writer::select($pagesizes, 'perpage', $perpage, false, array('id' => 'pref_perpage', 'class' => 'custom-select'));
1745 if ($advanced) {
1746 $regsearchclass = 'search_none';
1747 $advancedsearchclass = 'search_inline';
1748 } else {
1749 $regsearchclass = 'search_inline';
1750 $advancedsearchclass = 'search_none';
1752 echo '<div id="reg_search" class="' . $regsearchclass . ' form-inline" >&nbsp;&nbsp;&nbsp;';
1753 echo '<label for="pref_search">' . get_string('search') . '</label> <input type="text" ' .
1754 'class="form-control" size="16" name="search" id= "pref_search" value="' . s($search) . '" /></div>';
1755 echo '&nbsp;&nbsp;&nbsp;<label for="pref_sortby">'.get_string('sortby').'</label> ';
1756 // foreach field, print the option
1757 echo '<select name="sort" id="pref_sortby" class="custom-select mr-1">';
1758 if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'name')) {
1759 echo '<optgroup label="'.get_string('fields', 'data').'">';
1760 foreach ($fields as $field) {
1761 if ($field->id == $sort) {
1762 echo '<option value="'.$field->id.'" selected="selected">'.$field->name.'</option>';
1763 } else {
1764 echo '<option value="'.$field->id.'">'.$field->name.'</option>';
1767 echo '</optgroup>';
1769 $options = array();
1770 $options[DATA_TIMEADDED] = get_string('timeadded', 'data');
1771 $options[DATA_TIMEMODIFIED] = get_string('timemodified', 'data');
1772 $options[DATA_FIRSTNAME] = get_string('authorfirstname', 'data');
1773 $options[DATA_LASTNAME] = get_string('authorlastname', 'data');
1774 if ($data->approval and has_capability('mod/data:approve', $context)) {
1775 $options[DATA_APPROVED] = get_string('approved', 'data');
1777 echo '<optgroup label="'.get_string('other', 'data').'">';
1778 foreach ($options as $key => $name) {
1779 if ($key == $sort) {
1780 echo '<option value="'.$key.'" selected="selected">'.$name.'</option>';
1781 } else {
1782 echo '<option value="'.$key.'">'.$name.'</option>';
1785 echo '</optgroup>';
1786 echo '</select>';
1787 echo '<label for="pref_order" class="accesshide">'.get_string('order').'</label>';
1788 echo '<select id="pref_order" name="order" class="custom-select mr-1">';
1789 if ($order == 'ASC') {
1790 echo '<option value="ASC" selected="selected">'.get_string('ascending','data').'</option>';
1791 } else {
1792 echo '<option value="ASC">'.get_string('ascending','data').'</option>';
1794 if ($order == 'DESC') {
1795 echo '<option value="DESC" selected="selected">'.get_string('descending','data').'</option>';
1796 } else {
1797 echo '<option value="DESC">'.get_string('descending','data').'</option>';
1799 echo '</select>';
1801 if ($advanced) {
1802 $checked = ' checked="checked" ';
1804 else {
1805 $checked = '';
1807 $PAGE->requires->js('/mod/data/data.js');
1808 echo '&nbsp;<input type="hidden" name="advanced" value="0" />';
1809 echo '&nbsp;<input type="hidden" name="filter" value="1" />';
1810 echo '&nbsp;<input type="checkbox" id="advancedcheckbox" name="advanced" value="1" ' . $checked . ' ' .
1811 'onchange="showHideAdvSearch(this.checked);" class="mx-1" />' .
1812 '<label for="advancedcheckbox">' . get_string('advancedsearch', 'data') . '</label>';
1813 echo '</div>';
1814 echo '<div id="advsearch-save-sec" class="ml-auto '. $regsearchclass . '">';
1815 echo '&nbsp;<input type="submit" class="btn btn-secondary" value="' . get_string('savesettings', 'data') . '" />';
1816 echo '</div>';
1817 echo '</div>';
1818 echo '<div>';
1820 echo '<br />';
1821 echo '<div class="' . $advancedsearchclass . '" id="data_adv_form">';
1822 echo '<table class="boxaligncenter">';
1824 // print ASC or DESC
1825 echo '<tr><td colspan="2">&nbsp;</td></tr>';
1826 $i = 0;
1828 // Determine if we are printing all fields for advanced search, or the template for advanced search
1829 // If a template is not defined, use the deafault template and display all fields.
1830 $asearchtemplate = $data->asearchtemplate;
1831 if (empty($asearchtemplate)) {
1832 $asearchtemplate = data_generate_default_template($data, 'asearchtemplate', 0, false, false);
1835 static $fields = array();
1836 static $dataid = null;
1838 if (empty($dataid)) {
1839 $dataid = $data->id;
1840 } else if ($dataid != $data->id) {
1841 $fields = array();
1844 if (empty($fields)) {
1845 $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id));
1846 foreach ($fieldrecords as $fieldrecord) {
1847 $fields[]= data_get_field($fieldrecord, $data);
1851 // Replacing tags
1852 $patterns = array();
1853 $replacement = array();
1855 // Then we generate strings to replace for normal tags
1856 $otherfields = [];
1857 foreach ($fields as $field) {
1858 $fieldname = $field->field->name;
1859 $fieldname = preg_quote($fieldname, '/');
1860 $searchfield = data_get_field_from_id($field->field->id, $data);
1862 if ($searchfield->type === 'unknown') {
1863 continue;
1865 if (!empty($search_array[$field->field->id]->data)) {
1866 $searchinput = $searchfield->display_search_field($search_array[$field->field->id]->data);
1867 } else {
1868 $searchinput = $searchfield->display_search_field();
1870 $patterns[] = "/\[\[$fieldname\]\]/i";
1871 $replacement[] = $searchinput;
1872 // Extra field information.
1873 $patterns[] = "/\[\[$fieldname#name\]\]/i";
1874 $replacement[] = $field->field->name;
1875 $patterns[] = "/\[\[$fieldname#description\]\]/i";
1876 $replacement[] = $field->field->description;
1877 // Other fields.
1878 if (strpos($asearchtemplate, "[[" . $field->field->name . "]]") === false) {
1879 $otherfields[] = [
1880 'fieldname' => $searchfield->field->name,
1881 'fieldcontent' => $searchinput,
1885 $patterns[] = "/##otherfields##/";
1886 if (!empty($otherfields)) {
1887 $replacement[] = $OUTPUT->render_from_template(
1888 'mod_data/fields_otherfields',
1889 ['fields' => $otherfields]
1891 } else {
1892 $replacement[] = '';
1895 $fn = !empty($search_array[DATA_FIRSTNAME]->data) ? $search_array[DATA_FIRSTNAME]->data : '';
1896 $ln = !empty($search_array[DATA_LASTNAME]->data) ? $search_array[DATA_LASTNAME]->data : '';
1897 $patterns[] = '/##firstname##/';
1898 $replacement[] = '<label class="accesshide" for="u_fn">' . get_string('authorfirstname', 'data') . '</label>' .
1899 '<input type="text" class="form-control" size="16" id="u_fn" name="u_fn" value="' . s($fn) . '" />';
1900 $patterns[] = '/##lastname##/';
1901 $replacement[] = '<label class="accesshide" for="u_ln">' . get_string('authorlastname', 'data') . '</label>' .
1902 '<input type="text" class="form-control" size="16" id="u_ln" name="u_ln" value="' . s($ln) . '" />';
1904 if (core_tag_tag::is_enabled('mod_data', 'data_records')) {
1905 $patterns[] = "/##tags##/";
1906 $selectedtags = isset($search_array[DATA_TAGS]->rawtagnames) ? $search_array[DATA_TAGS]->rawtagnames : [];
1907 $replacement[] = data_generate_tag_form(false, $selectedtags);
1910 // actual replacement of the tags
1912 $options = new stdClass();
1913 $options->para=false;
1914 $options->noclean=true;
1915 echo '<tr><td>';
1916 echo preg_replace($patterns, $replacement, format_text($asearchtemplate, FORMAT_HTML, $options));
1917 echo '</td></tr>';
1919 echo '<tr><td colspan="4"><br/>' .
1920 '<input type="submit" class="btn btn-primary mr-1" value="' . get_string('savesettings', 'data') . '" />' .
1921 '<input type="submit" class="btn btn-secondary" name="resetadv" value="' . get_string('resetsettings', 'data') . '" />' .
1922 '</td></tr>';
1923 echo '</table>';
1924 echo '</div>';
1925 echo '</form>';
1926 echo '</div>';
1927 echo '<hr/>';
1931 * @global object
1932 * @global object
1933 * @param object $data
1934 * @param object $record
1935 * @param bool $print if the result must be printed or returner.
1936 * @return void Output echo'd
1938 function data_print_ratings($data, $record, bool $print = true) {
1939 global $OUTPUT;
1940 $result = '';
1941 if (!empty($record->rating)){
1942 $result = $OUTPUT->render($record->rating);
1944 if (!$print) {
1945 return $result;
1947 echo $result;
1951 * List the actions that correspond to a view of this module.
1952 * This is used by the participation report.
1954 * Note: This is not used by new logging system. Event with
1955 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1956 * be considered as view action.
1958 * @return array
1960 function data_get_view_actions() {
1961 return array('view');
1965 * List the actions that correspond to a post of this module.
1966 * This is used by the participation report.
1968 * Note: This is not used by new logging system. Event with
1969 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1970 * will be considered as post action.
1972 * @return array
1974 function data_get_post_actions() {
1975 return array('add','update','record delete');
1979 * @param string $name
1980 * @param int $dataid
1981 * @param int $fieldid
1982 * @return bool
1984 function data_fieldname_exists($name, $dataid, $fieldid = 0) {
1985 global $DB;
1987 if (!is_numeric($name)) {
1988 $like = $DB->sql_like('df.name', ':name', false);
1989 } else {
1990 $like = "df.name = :name";
1992 $params = array('name'=>$name);
1993 if ($fieldid) {
1994 $params['dataid'] = $dataid;
1995 $params['fieldid1'] = $fieldid;
1996 $params['fieldid2'] = $fieldid;
1997 return $DB->record_exists_sql("SELECT * FROM {data_fields} df
1998 WHERE $like AND df.dataid = :dataid
1999 AND ((df.id < :fieldid1) OR (df.id > :fieldid2))", $params);
2000 } else {
2001 $params['dataid'] = $dataid;
2002 return $DB->record_exists_sql("SELECT * FROM {data_fields} df
2003 WHERE $like AND df.dataid = :dataid", $params);
2008 * @param array $fieldinput
2010 function data_convert_arrays_to_strings(&$fieldinput) {
2011 foreach ($fieldinput as $key => $val) {
2012 if (is_array($val)) {
2013 $str = '';
2014 foreach ($val as $inner) {
2015 $str .= $inner . ',';
2017 $str = substr($str, 0, -1);
2019 $fieldinput->$key = $str;
2026 * Converts a database (module instance) to use the Roles System
2028 * @global object
2029 * @global object
2030 * @uses CONTEXT_MODULE
2031 * @uses CAP_PREVENT
2032 * @uses CAP_ALLOW
2033 * @param object $data a data object with the same attributes as a record
2034 * from the data database table
2035 * @param int $datamodid the id of the data module, from the modules table
2036 * @param array $teacherroles array of roles that have archetype teacher
2037 * @param array $studentroles array of roles that have archetype student
2038 * @param array $guestroles array of roles that have archetype guest
2039 * @param int $cmid the course_module id for this data instance
2040 * @return boolean data module was converted or not
2042 function data_convert_to_roles($data, $teacherroles=array(), $studentroles=array(), $cmid=NULL) {
2043 global $CFG, $DB, $OUTPUT;
2045 if (!isset($data->participants) && !isset($data->assesspublic)
2046 && !isset($data->groupmode)) {
2047 // We assume that this database has already been converted to use the
2048 // Roles System. above fields get dropped the data module has been
2049 // upgraded to use Roles.
2050 return false;
2053 if (empty($cmid)) {
2054 // We were not given the course_module id. Try to find it.
2055 if (!$cm = get_coursemodule_from_instance('data', $data->id)) {
2056 echo $OUTPUT->notification('Could not get the course module for the data');
2057 return false;
2058 } else {
2059 $cmid = $cm->id;
2062 $context = context_module::instance($cmid);
2065 // $data->participants:
2066 // 1 - Only teachers can add entries
2067 // 3 - Teachers and students can add entries
2068 switch ($data->participants) {
2069 case 1:
2070 foreach ($studentroles as $studentrole) {
2071 assign_capability('mod/data:writeentry', CAP_PREVENT, $studentrole->id, $context->id);
2073 foreach ($teacherroles as $teacherrole) {
2074 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
2076 break;
2077 case 3:
2078 foreach ($studentroles as $studentrole) {
2079 assign_capability('mod/data:writeentry', CAP_ALLOW, $studentrole->id, $context->id);
2081 foreach ($teacherroles as $teacherrole) {
2082 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
2084 break;
2087 // $data->assessed:
2088 // 2 - Only teachers can rate posts
2089 // 1 - Everyone can rate posts
2090 // 0 - No one can rate posts
2091 switch ($data->assessed) {
2092 case 0:
2093 foreach ($studentroles as $studentrole) {
2094 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2096 foreach ($teacherroles as $teacherrole) {
2097 assign_capability('mod/data:rate', CAP_PREVENT, $teacherrole->id, $context->id);
2099 break;
2100 case 1:
2101 foreach ($studentroles as $studentrole) {
2102 assign_capability('mod/data:rate', CAP_ALLOW, $studentrole->id, $context->id);
2104 foreach ($teacherroles as $teacherrole) {
2105 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2107 break;
2108 case 2:
2109 foreach ($studentroles as $studentrole) {
2110 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2112 foreach ($teacherroles as $teacherrole) {
2113 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2115 break;
2118 // $data->assesspublic:
2119 // 0 - Students can only see their own ratings
2120 // 1 - Students can see everyone's ratings
2121 switch ($data->assesspublic) {
2122 case 0:
2123 foreach ($studentroles as $studentrole) {
2124 assign_capability('mod/data:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
2126 foreach ($teacherroles as $teacherrole) {
2127 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2129 break;
2130 case 1:
2131 foreach ($studentroles as $studentrole) {
2132 assign_capability('mod/data:viewrating', CAP_ALLOW, $studentrole->id, $context->id);
2134 foreach ($teacherroles as $teacherrole) {
2135 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2137 break;
2140 if (empty($cm)) {
2141 $cm = $DB->get_record('course_modules', array('id'=>$cmid));
2144 switch ($cm->groupmode) {
2145 case NOGROUPS:
2146 break;
2147 case SEPARATEGROUPS:
2148 foreach ($studentroles as $studentrole) {
2149 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
2151 foreach ($teacherroles as $teacherrole) {
2152 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2154 break;
2155 case VISIBLEGROUPS:
2156 foreach ($studentroles as $studentrole) {
2157 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
2159 foreach ($teacherroles as $teacherrole) {
2160 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2162 break;
2164 return true;
2168 * Returns the best name to show for a preset
2170 * @param string $shortname
2171 * @param string $path
2172 * @return string
2173 * @deprecated since Moodle 4.1 MDL-75148 - please, use the preset::get_name_from_plugin() function instead.
2174 * @todo MDL-75189 This will be deleted in Moodle 4.5.
2175 * @see preset::get_name_from_plugin()
2177 function data_preset_name($shortname, $path) {
2178 debugging('data_preset_name() is deprecated. Please use preset::get_name_from_plugin() instead.', DEBUG_DEVELOPER);
2180 return preset::get_name_from_plugin($shortname);
2184 * Returns an array of all the available presets.
2186 * @return array
2187 * @deprecated since Moodle 4.1 MDL-75148 - please, use the manager::get_available_presets() function instead.
2188 * @todo MDL-75189 This will be deleted in Moodle 4.5.
2189 * @see manager::get_available_presets()
2191 function data_get_available_presets($context) {
2192 debugging('data_get_available_presets() is deprecated. Please use manager::get_available_presets() instead.', DEBUG_DEVELOPER);
2194 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2195 $manager = manager::create_from_coursemodule($cm);
2196 return $manager->get_available_presets();
2200 * Gets an array of all of the presets that users have saved to the site.
2202 * @param stdClass $context The context that we are looking from.
2203 * @param array $presets
2204 * @return array An array of presets
2205 * @deprecated since Moodle 4.1 MDL-75148 - please, use the manager::get_available_saved_presets() function instead.
2206 * @todo MDL-75189 This will be deleted in Moodle 4.5.
2207 * @see manager::get_available_saved_presets()
2209 function data_get_available_site_presets($context, array $presets=array()) {
2210 debugging(
2211 'data_get_available_site_presets() is deprecated. Please use manager::get_available_saved_presets() instead.',
2212 DEBUG_DEVELOPER
2215 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2216 $manager = manager::create_from_coursemodule($cm);
2217 $savedpresets = $manager->get_available_saved_presets();
2218 return array_merge($presets, $savedpresets);
2222 * Deletes a saved preset.
2224 * @param string $name
2225 * @return bool
2226 * @deprecated since Moodle 4.1 MDL-75187 - please, use the preset::delete() function instead.
2227 * @todo MDL-75189 This will be deleted in Moodle 4.5.
2228 * @see preset::delete()
2230 function data_delete_site_preset($name) {
2231 debugging('data_delete_site_preset() is deprecated. Please use preset::delete() instead.', DEBUG_DEVELOPER);
2233 $fs = get_file_storage();
2235 $files = $fs->get_directory_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/');
2236 if (!empty($files)) {
2237 foreach ($files as $file) {
2238 $file->delete();
2242 $dir = $fs->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/', '.');
2243 if (!empty($dir)) {
2244 $dir->delete();
2246 return true;
2250 * Prints the heads for a page
2252 * @param stdClass $course
2253 * @param stdClass $cm
2254 * @param stdClass $data
2255 * @param string $currenttab
2256 * @param string $actionbar
2258 function data_print_header($course, $cm, $data, $currenttab='', string $actionbar = '') {
2260 global $CFG, $displaynoticegood, $displaynoticebad, $OUTPUT, $PAGE, $USER;
2262 echo $OUTPUT->header();
2264 echo $actionbar;
2266 // Print any notices
2268 if (!empty($displaynoticegood)) {
2269 echo $OUTPUT->notification($displaynoticegood, 'notifysuccess'); // good (usually green)
2270 } else if (!empty($displaynoticebad)) {
2271 echo $OUTPUT->notification($displaynoticebad); // bad (usuually red)
2276 * Can user add more entries?
2278 * @param object $data
2279 * @param mixed $currentgroup
2280 * @param int $groupmode
2281 * @param stdClass $context
2282 * @return bool
2284 function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = null) {
2285 global $DB;
2287 // Don't let add entry to a database that has no fields.
2288 if (!$DB->record_exists('data_fields', ['dataid' => $data->id])) {
2289 return false;
2292 if (empty($context)) {
2293 $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
2294 $context = context_module::instance($cm->id);
2297 if (has_capability('mod/data:manageentries', $context)) {
2298 // no entry limits apply if user can manage
2300 } else if (!has_capability('mod/data:writeentry', $context)) {
2301 return false;
2303 } else if (data_atmaxentries($data)) {
2304 return false;
2305 } else if (data_in_readonly_period($data)) {
2306 // Check whether we're in a read-only period
2307 return false;
2310 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
2311 return true;
2314 if ($currentgroup) {
2315 return groups_is_member($currentgroup);
2316 } else {
2317 //else it might be group 0 in visible mode
2318 if ($groupmode == VISIBLEGROUPS){
2319 return true;
2320 } else {
2321 return false;
2327 * Check whether the current user is allowed to manage the given record considering manageentries capability,
2328 * data_in_readonly_period() result, ownership (determined by data_isowner()) and manageapproved setting.
2329 * @param mixed $record record object or id
2330 * @param object $data data object
2331 * @param object $context context object
2332 * @return bool returns true if the user is allowd to edit the entry, false otherwise
2334 function data_user_can_manage_entry($record, $data, $context) {
2335 global $DB;
2337 if (has_capability('mod/data:manageentries', $context)) {
2338 return true;
2341 // Check whether this activity is read-only at present.
2342 $readonly = data_in_readonly_period($data);
2344 if (!$readonly) {
2345 // Get record object from db if just id given like in data_isowner.
2346 // ...done before calling data_isowner() to avoid querying db twice.
2347 if (!is_object($record)) {
2348 if (!$record = $DB->get_record('data_records', array('id' => $record))) {
2349 return false;
2352 if (data_isowner($record)) {
2353 if ($data->approval && $record->approved) {
2354 return $data->manageapproved == 1;
2355 } else {
2356 return true;
2361 return false;
2365 * Check whether the specified database activity is currently in a read-only period
2367 * @param object $data
2368 * @return bool returns true if the time fields in $data indicate a read-only period; false otherwise
2370 function data_in_readonly_period($data) {
2371 $now = time();
2372 if (!$data->timeviewfrom && !$data->timeviewto) {
2373 return false;
2374 } else if (($data->timeviewfrom && $now < $data->timeviewfrom) || ($data->timeviewto && $now > $data->timeviewto)) {
2375 return false;
2377 return true;
2381 * Check if the files in a directory are the expected for a preset.
2383 * @return bool Wheter the defined $directory has or not all the expected preset files.
2385 * @deprecated since Moodle 4.1 MDL-75148 - please, use the preset::is_directory_a_preset() function instead.
2386 * @todo MDL-75189 This will be deleted in Moodle 4.5.
2387 * @see manager::is_directory_a_preset()
2389 function is_directory_a_preset($directory) {
2390 debugging('is_directory_a_preset() is deprecated. Please use preset::is_directory_a_preset() instead.', DEBUG_DEVELOPER);
2392 return preset::is_directory_a_preset($directory);
2396 * Abstract class used for data preset importers
2398 * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more.
2399 * @todo MDL-75189 Final deprecation in Moodle 4.5.
2401 abstract class data_preset_importer {
2403 protected $course;
2404 protected $cm;
2405 protected $module;
2406 protected $directory;
2409 * Constructor
2411 * @param stdClass $course
2412 * @param stdClass $cm
2413 * @param stdClass $module
2414 * @param string $directory
2416 public function __construct($course, $cm, $module, $directory) {
2417 debugging(
2418 'data_preset_importer is deprecated. Please use mod\\data\\local\\importer\\preset_importer instead',
2419 DEBUG_DEVELOPER
2422 $this->course = $course;
2423 $this->cm = $cm;
2424 $this->module = $module;
2425 $this->directory = $directory;
2429 * Returns the name of the directory the preset is located in
2430 * @return string
2432 public function get_directory() {
2433 return basename($this->directory);
2437 * Retreive the contents of a file. That file may either be in a conventional directory of the Moodle file storage
2438 * @param file_storage $filestorage. should be null if using a conventional directory
2439 * @param stored_file $fileobj the directory to look in. null if using a conventional directory
2440 * @param string $dir the directory to look in. null if using the Moodle file storage
2441 * @param string $filename the name of the file we want
2442 * @return string the contents of the file or null if the file doesn't exist.
2444 public function data_preset_get_file_contents(&$filestorage, &$fileobj, $dir, $filename) {
2445 if(empty($filestorage) || empty($fileobj)) {
2446 if (substr($dir, -1)!='/') {
2447 $dir .= '/';
2449 if (file_exists($dir.$filename)) {
2450 return file_get_contents($dir.$filename);
2451 } else {
2452 return null;
2454 } else {
2455 if ($filestorage->file_exists(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename)) {
2456 $file = $filestorage->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename);
2457 return $file->get_content();
2458 } else {
2459 return null;
2465 * Gets the preset settings
2466 * @global moodle_database $DB
2467 * @return stdClass
2469 public function get_preset_settings() {
2470 global $DB, $CFG;
2471 require_once($CFG->libdir.'/xmlize.php');
2473 $fs = $fileobj = null;
2474 if (!preset::is_directory_a_preset($this->directory)) {
2475 //maybe the user requested a preset stored in the Moodle file storage
2477 $fs = get_file_storage();
2478 $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA);
2480 //preset name to find will be the final element of the directory
2481 $explodeddirectory = explode('/', $this->directory);
2482 $presettofind = end($explodeddirectory);
2484 //now go through the available files available and see if we can find it
2485 foreach ($files as $file) {
2486 if (($file->is_directory() && $file->get_filepath()=='/') || !$file->is_directory()) {
2487 continue;
2489 $presetname = trim($file->get_filepath(), '/');
2490 if ($presetname==$presettofind) {
2491 $this->directory = $presetname;
2492 $fileobj = $file;
2496 if (empty($fileobj)) {
2497 throw new \moodle_exception('invalidpreset', 'data', '', $this->directory);
2501 $allowed_settings = array(
2502 'intro',
2503 'comments',
2504 'requiredentries',
2505 'requiredentriestoview',
2506 'maxentries',
2507 'rssarticles',
2508 'approval',
2509 'defaultsortdir',
2510 'defaultsort');
2512 $result = new stdClass;
2513 $result->settings = new stdClass;
2514 $result->importfields = array();
2515 $result->currentfields = $DB->get_records('data_fields', array('dataid'=>$this->module->id));
2516 if (!$result->currentfields) {
2517 $result->currentfields = array();
2521 /* Grab XML */
2522 $presetxml = $this->data_preset_get_file_contents($fs, $fileobj, $this->directory,'preset.xml');
2523 $parsedxml = xmlize($presetxml, 0);
2525 /* First, do settings. Put in user friendly array. */
2526 $settingsarray = $parsedxml['preset']['#']['settings'][0]['#'];
2527 $result->settings = new StdClass();
2528 foreach ($settingsarray as $setting => $value) {
2529 if (!is_array($value) || !in_array($setting, $allowed_settings)) {
2530 // unsupported setting
2531 continue;
2533 $result->settings->$setting = $value[0]['#'];
2536 /* Now work out fields to user friendly array */
2537 $fieldsarray = $parsedxml['preset']['#']['field'];
2538 foreach ($fieldsarray as $field) {
2539 if (!is_array($field)) {
2540 continue;
2542 $f = new StdClass();
2543 foreach ($field['#'] as $param => $value) {
2544 if (!is_array($value)) {
2545 continue;
2547 $f->$param = $value[0]['#'];
2549 $f->dataid = $this->module->id;
2550 $f->type = clean_param($f->type, PARAM_ALPHA);
2551 $result->importfields[] = $f;
2553 /* Now add the HTML templates to the settings array so we can update d */
2554 $result->settings->singletemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"singletemplate.html");
2555 $result->settings->listtemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplate.html");
2556 $result->settings->listtemplateheader = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplateheader.html");
2557 $result->settings->listtemplatefooter = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplatefooter.html");
2558 $result->settings->addtemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"addtemplate.html");
2559 $result->settings->rsstemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstemplate.html");
2560 $result->settings->rsstitletemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstitletemplate.html");
2561 $result->settings->csstemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"csstemplate.css");
2562 $result->settings->jstemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"jstemplate.js");
2563 $result->settings->asearchtemplate = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"asearchtemplate.html");
2565 $result->settings->instance = $this->module->id;
2566 return $result;
2570 * Import the preset into the given database module
2571 * @return bool
2573 function import($overwritesettings) {
2574 global $DB, $CFG, $OUTPUT;
2576 $params = $this->get_preset_settings();
2577 $settings = $params->settings;
2578 $newfields = $params->importfields;
2579 $currentfields = $params->currentfields;
2580 $preservedfields = array();
2582 /* Maps fields and makes new ones */
2583 if (!empty($newfields)) {
2584 /* We require an injective mapping, and need to know what to protect */
2585 foreach ($newfields as $nid => $newfield) {
2586 $cid = optional_param("field_$nid", -1, PARAM_INT);
2587 if ($cid == -1) {
2588 continue;
2590 if (array_key_exists($cid, $preservedfields)){
2591 throw new \moodle_exception('notinjectivemap', 'data');
2593 else $preservedfields[$cid] = true;
2595 $missingfieldtypes = [];
2596 foreach ($newfields as $nid => $newfield) {
2597 $cid = optional_param("field_$nid", -1, PARAM_INT);
2599 /* A mapping. Just need to change field params. Data kept. */
2600 if ($cid != -1 and isset($currentfields[$cid])) {
2601 $fieldobject = data_get_field_from_id($currentfields[$cid]->id, $this->module);
2602 foreach ($newfield as $param => $value) {
2603 if ($param != "id") {
2604 $fieldobject->field->$param = $value;
2607 unset($fieldobject->field->similarfield);
2608 $fieldobject->update_field();
2609 unset($fieldobject);
2610 } else {
2611 /* Make a new field */
2612 $filepath = "field/$newfield->type/field.class.php";
2613 if (!file_exists($filepath)) {
2614 $missingfieldtypes[] = $newfield->name;
2615 continue;
2617 include_once($filepath);
2619 if (!isset($newfield->description)) {
2620 $newfield->description = '';
2622 $classname = 'data_field_'.$newfield->type;
2623 $fieldclass = new $classname($newfield, $this->module);
2624 $fieldclass->insert_field();
2625 unset($fieldclass);
2628 if (!empty($missingfieldtypes)) {
2629 echo $OUTPUT->notification(get_string('missingfieldtypeimport', 'data') . html_writer::alist($missingfieldtypes));
2633 /* Get rid of all old unused data */
2634 foreach ($currentfields as $cid => $currentfield) {
2635 if (!array_key_exists($cid, $preservedfields)) {
2636 /* Data not used anymore so wipe! */
2637 echo "Deleting field $currentfield->name<br />";
2639 // Delete all information related to fields.
2640 $todelete = data_get_field_from_id($currentfield->id, $this->module);
2641 $todelete->delete_field();
2645 // handle special settings here
2646 if (!empty($settings->defaultsort)) {
2647 if (is_numeric($settings->defaultsort)) {
2648 // old broken value
2649 $settings->defaultsort = 0;
2650 } else {
2651 $settings->defaultsort = (int)$DB->get_field('data_fields', 'id', array('dataid'=>$this->module->id, 'name'=>$settings->defaultsort));
2653 } else {
2654 $settings->defaultsort = 0;
2657 // do we want to overwrite all current database settings?
2658 if ($overwritesettings) {
2659 // all supported settings
2660 $overwrite = array_keys((array)$settings);
2661 } else {
2662 // only templates and sorting
2663 $overwrite = array('singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter',
2664 'addtemplate', 'rsstemplate', 'rsstitletemplate', 'csstemplate', 'jstemplate',
2665 'asearchtemplate', 'defaultsortdir', 'defaultsort');
2668 // now overwrite current data settings
2669 foreach ($this->module as $prop=>$unused) {
2670 if (in_array($prop, $overwrite)) {
2671 $this->module->$prop = $settings->$prop;
2675 data_update_instance($this->module);
2677 return $this->cleanup();
2681 * Any clean up routines should go here
2682 * @return bool
2684 public function cleanup() {
2685 return true;
2690 * Data preset importer for uploaded presets
2692 * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more.
2693 * @todo MDL-75189 Final deprecation in Moodle 4.5.
2695 class data_preset_upload_importer extends data_preset_importer {
2696 public function __construct($course, $cm, $module, $filepath) {
2697 global $USER;
2699 debugging(
2700 'data_preset_upload_importer is deprecated. Please use mod\\data\\local\\importer\\preset_upload_importer instead',
2701 DEBUG_DEVELOPER
2704 if (is_file($filepath)) {
2705 $fp = get_file_packer();
2706 if ($fp->extract_to_pathname($filepath, $filepath.'_extracted')) {
2707 fulldelete($filepath);
2709 $filepath .= '_extracted';
2711 parent::__construct($course, $cm, $module, $filepath);
2714 public function cleanup() {
2715 return fulldelete($this->directory);
2720 * Data preset importer for existing presets
2722 * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more.
2723 * @todo MDL-75189 Final deprecation in Moodle 4.5.
2725 class data_preset_existing_importer extends data_preset_importer {
2726 protected $userid;
2727 public function __construct($course, $cm, $module, $fullname) {
2728 global $USER;
2730 debugging(
2731 'data_preset_existing_importer is deprecated. Please use mod\\data\\local\\importer\\preset_existing_importer instead',
2732 DEBUG_DEVELOPER
2735 list($userid, $shortname) = explode('/', $fullname, 2);
2736 $context = context_module::instance($cm->id);
2737 if ($userid && ($userid != $USER->id) && !has_capability('mod/data:manageuserpresets', $context) && !has_capability('mod/data:viewalluserpresets', $context)) {
2738 throw new coding_exception('Invalid preset provided');
2741 $this->userid = $userid;
2742 $filepath = data_preset_path($course, $userid, $shortname);
2743 parent::__construct($course, $cm, $module, $filepath);
2745 public function get_userid() {
2746 return $this->userid;
2751 * @global object
2752 * @global object
2753 * @param object $course
2754 * @param int $userid
2755 * @param string $shortname
2756 * @return string
2758 function data_preset_path($course, $userid, $shortname) {
2759 global $USER, $CFG;
2761 $context = context_course::instance($course->id);
2763 $userid = (int)$userid;
2765 $path = null;
2766 if ($userid > 0 && ($userid == $USER->id || has_capability('mod/data:viewalluserpresets', $context))) {
2767 $path = $CFG->dataroot.'/data/preset/'.$userid.'/'.$shortname;
2768 } else if ($userid == 0) {
2769 $path = $CFG->dirroot.'/mod/data/preset/'.$shortname;
2770 } else if ($userid < 0) {
2771 $path = $CFG->tempdir.'/data/'.-$userid.'/'.$shortname;
2774 return $path;
2778 * Implementation of the function for printing the form elements that control
2779 * whether the course reset functionality affects the data.
2781 * @param MoodleQuickForm $mform form passed by reference
2783 function data_reset_course_form_definition(&$mform) {
2784 $mform->addElement('header', 'dataheader', get_string('modulenameplural', 'data'));
2785 $mform->addElement('checkbox', 'reset_data', get_string('deleteallentries','data'));
2787 $mform->addElement('checkbox', 'reset_data_notenrolled', get_string('deletenotenrolled', 'data'));
2788 $mform->disabledIf('reset_data_notenrolled', 'reset_data', 'checked');
2790 $mform->addElement('checkbox', 'reset_data_ratings', get_string('deleteallratings'));
2791 $mform->disabledIf('reset_data_ratings', 'reset_data', 'checked');
2793 $mform->addElement('checkbox', 'reset_data_comments', get_string('deleteallcomments'));
2794 $mform->disabledIf('reset_data_comments', 'reset_data', 'checked');
2796 $mform->addElement('checkbox', 'reset_data_tags', get_string('removealldatatags', 'data'));
2797 $mform->disabledIf('reset_data_tags', 'reset_data', 'checked');
2801 * Course reset form defaults.
2802 * @return array
2804 function data_reset_course_form_defaults($course) {
2805 return array('reset_data'=>0, 'reset_data_ratings'=>1, 'reset_data_comments'=>1, 'reset_data_notenrolled'=>0);
2809 * Removes all grades from gradebook
2811 * @global object
2812 * @global object
2813 * @param int $courseid
2814 * @param string $type optional type
2816 function data_reset_gradebook($courseid, $type='') {
2817 global $CFG, $DB;
2819 $sql = "SELECT d.*, cm.idnumber as cmidnumber, d.course as courseid
2820 FROM {data} d, {course_modules} cm, {modules} m
2821 WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id AND d.course=?";
2823 if ($datas = $DB->get_records_sql($sql, array($courseid))) {
2824 foreach ($datas as $data) {
2825 data_grade_item_update($data, 'reset');
2831 * Actual implementation of the reset course functionality, delete all the
2832 * data responses for course $data->courseid.
2834 * @global object
2835 * @global object
2836 * @param object $data the data submitted from the reset course.
2837 * @return array status array
2839 function data_reset_userdata($data) {
2840 global $CFG, $DB;
2841 require_once($CFG->libdir.'/filelib.php');
2842 require_once($CFG->dirroot.'/rating/lib.php');
2844 $componentstr = get_string('modulenameplural', 'data');
2845 $status = array();
2847 $allrecordssql = "SELECT r.id
2848 FROM {data_records} r
2849 INNER JOIN {data} d ON r.dataid = d.id
2850 WHERE d.course = ?";
2852 $alldatassql = "SELECT d.id
2853 FROM {data} d
2854 WHERE d.course=?";
2856 $rm = new rating_manager();
2857 $ratingdeloptions = new stdClass;
2858 $ratingdeloptions->component = 'mod_data';
2859 $ratingdeloptions->ratingarea = 'entry';
2861 // Set the file storage - may need it to remove files later.
2862 $fs = get_file_storage();
2864 // delete entries if requested
2865 if (!empty($data->reset_data)) {
2866 $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2867 $DB->delete_records_select('data_content', "recordid IN ($allrecordssql)", array($data->courseid));
2868 $DB->delete_records_select('data_records', "dataid IN ($alldatassql)", array($data->courseid));
2870 if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2871 foreach ($datas as $dataid=>$unused) {
2872 if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2873 continue;
2875 $datacontext = context_module::instance($cm->id);
2877 // Delete any files that may exist.
2878 $fs->delete_area_files($datacontext->id, 'mod_data', 'content');
2880 $ratingdeloptions->contextid = $datacontext->id;
2881 $rm->delete_ratings($ratingdeloptions);
2883 core_tag_tag::delete_instances('mod_data', null, $datacontext->id);
2887 if (empty($data->reset_gradebook_grades)) {
2888 // remove all grades from gradebook
2889 data_reset_gradebook($data->courseid);
2891 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallentries', 'data'), 'error'=>false);
2894 // remove entries by users not enrolled into course
2895 if (!empty($data->reset_data_notenrolled)) {
2896 $recordssql = "SELECT r.id, r.userid, r.dataid, u.id AS userexists, u.deleted AS userdeleted
2897 FROM {data_records} r
2898 JOIN {data} d ON r.dataid = d.id
2899 LEFT JOIN {user} u ON r.userid = u.id
2900 WHERE d.course = ? AND r.userid > 0";
2902 $course_context = context_course::instance($data->courseid);
2903 $notenrolled = array();
2904 $fields = array();
2905 $rs = $DB->get_recordset_sql($recordssql, array($data->courseid));
2906 foreach ($rs as $record) {
2907 if (array_key_exists($record->userid, $notenrolled) or !$record->userexists or $record->userdeleted
2908 or !is_enrolled($course_context, $record->userid)) {
2909 //delete ratings
2910 if (!$cm = get_coursemodule_from_instance('data', $record->dataid)) {
2911 continue;
2913 $datacontext = context_module::instance($cm->id);
2914 $ratingdeloptions->contextid = $datacontext->id;
2915 $ratingdeloptions->itemid = $record->id;
2916 $rm->delete_ratings($ratingdeloptions);
2918 // Delete any files that may exist.
2919 if ($contents = $DB->get_records('data_content', array('recordid' => $record->id), '', 'id')) {
2920 foreach ($contents as $content) {
2921 $fs->delete_area_files($datacontext->id, 'mod_data', 'content', $content->id);
2924 $notenrolled[$record->userid] = true;
2926 core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $record->id);
2928 $DB->delete_records('comments', array('itemid' => $record->id, 'commentarea' => 'database_entry'));
2929 $DB->delete_records('data_content', array('recordid' => $record->id));
2930 $DB->delete_records('data_records', array('id' => $record->id));
2933 $rs->close();
2934 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'data'), 'error'=>false);
2937 // remove all ratings
2938 if (!empty($data->reset_data_ratings)) {
2939 if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2940 foreach ($datas as $dataid=>$unused) {
2941 if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2942 continue;
2944 $datacontext = context_module::instance($cm->id);
2946 $ratingdeloptions->contextid = $datacontext->id;
2947 $rm->delete_ratings($ratingdeloptions);
2951 if (empty($data->reset_gradebook_grades)) {
2952 // remove all grades from gradebook
2953 data_reset_gradebook($data->courseid);
2956 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false);
2959 // remove all comments
2960 if (!empty($data->reset_data_comments)) {
2961 $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2962 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false);
2965 // Remove all the tags.
2966 if (!empty($data->reset_data_tags)) {
2967 if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2968 foreach ($datas as $dataid => $unused) {
2969 if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2970 continue;
2973 $context = context_module::instance($cm->id);
2974 core_tag_tag::delete_instances('mod_data', null, $context->id);
2978 $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'data'), 'error' => false);
2981 // updating dates - shift may be negative too
2982 if ($data->timeshift) {
2983 // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
2984 // See MDL-9367.
2985 shift_course_mod_dates('data', array('timeavailablefrom', 'timeavailableto',
2986 'timeviewfrom', 'timeviewto', 'assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
2987 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
2990 return $status;
2994 * Returns all other caps used in module
2996 * @return array
2998 function data_get_extra_capabilities() {
2999 return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate',
3000 'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete'];
3004 * @param string $feature FEATURE_xx constant for requested feature
3005 * @return mixed True if module supports feature, false if not, null if doesn't know or string for the module purpose.
3007 function data_supports($feature) {
3008 switch($feature) {
3009 case FEATURE_GROUPS: return true;
3010 case FEATURE_GROUPINGS: return true;
3011 case FEATURE_MOD_INTRO: return true;
3012 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
3013 case FEATURE_COMPLETION_HAS_RULES: return true;
3014 case FEATURE_GRADE_HAS_GRADE: return true;
3015 case FEATURE_GRADE_OUTCOMES: return true;
3016 case FEATURE_RATE: return true;
3017 case FEATURE_BACKUP_MOODLE2: return true;
3018 case FEATURE_SHOW_DESCRIPTION: return true;
3019 case FEATURE_COMMENT: return true;
3020 case FEATURE_MOD_PURPOSE: return MOD_PURPOSE_COLLABORATION;
3022 default: return null;
3026 ////////////////////////////////////////////////////////////////////////////////
3027 // File API //
3028 ////////////////////////////////////////////////////////////////////////////////
3031 * Lists all browsable file areas
3033 * @package mod_data
3034 * @category files
3035 * @param stdClass $course course object
3036 * @param stdClass $cm course module object
3037 * @param stdClass $context context object
3038 * @return array
3040 function data_get_file_areas($course, $cm, $context) {
3041 return array('content' => get_string('areacontent', 'mod_data'));
3045 * File browsing support for data module.
3047 * @param file_browser $browser
3048 * @param array $areas
3049 * @param stdClass $course
3050 * @param cm_info $cm
3051 * @param context $context
3052 * @param string $filearea
3053 * @param int $itemid
3054 * @param string $filepath
3055 * @param string $filename
3056 * @return file_info_stored file_info_stored instance or null if not found
3058 function data_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
3059 global $CFG, $DB, $USER;
3061 if ($context->contextlevel != CONTEXT_MODULE) {
3062 return null;
3065 if (!isset($areas[$filearea])) {
3066 return null;
3069 if (is_null($itemid)) {
3070 require_once($CFG->dirroot.'/mod/data/locallib.php');
3071 return new data_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
3074 if (!$content = $DB->get_record('data_content', array('id'=>$itemid))) {
3075 return null;
3078 if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3079 return null;
3082 if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3083 return null;
3086 if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3087 return null;
3090 //check if approved
3091 if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3092 return null;
3095 // group access
3096 if ($record->groupid) {
3097 $groupmode = groups_get_activity_groupmode($cm, $course);
3098 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3099 if (!groups_is_member($record->groupid)) {
3100 return null;
3105 $fieldobj = data_get_field($field, $data, $cm);
3107 $filepath = is_null($filepath) ? '/' : $filepath;
3108 $filename = is_null($filename) ? '.' : $filename;
3109 if (!$fieldobj->file_ok($filepath.$filename)) {
3110 return null;
3113 $fs = get_file_storage();
3114 if (!($storedfile = $fs->get_file($context->id, 'mod_data', $filearea, $itemid, $filepath, $filename))) {
3115 return null;
3118 // Checks to see if the user can manage files or is the owner.
3119 // TODO MDL-33805 - Do not use userid here and move the capability check above.
3120 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
3121 return null;
3124 $urlbase = $CFG->wwwroot.'/pluginfile.php';
3126 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
3130 * Serves the data attachments. Implements needed access control ;-)
3132 * @package mod_data
3133 * @category files
3134 * @param stdClass $course course object
3135 * @param stdClass $cm course module object
3136 * @param stdClass $context context object
3137 * @param string $filearea file area
3138 * @param array $args extra arguments
3139 * @param bool $forcedownload whether or not force download
3140 * @param array $options additional options affecting the file serving
3141 * @return bool false if file not found, does not return if found - justsend the file
3143 function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
3144 global $CFG, $DB;
3146 if ($context->contextlevel != CONTEXT_MODULE) {
3147 return false;
3150 require_course_login($course, true, $cm);
3152 if ($filearea === 'content') {
3153 $contentid = (int)array_shift($args);
3155 if (!$content = $DB->get_record('data_content', array('id'=>$contentid))) {
3156 return false;
3159 if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3160 return false;
3163 if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3164 return false;
3167 if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3168 return false;
3171 if ($data->id != $cm->instance) {
3172 // hacker attempt - context does not match the contentid
3173 return false;
3176 //check if approved
3177 if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3178 return false;
3181 // group access
3182 if ($record->groupid) {
3183 $groupmode = groups_get_activity_groupmode($cm, $course);
3184 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3185 if (!groups_is_member($record->groupid)) {
3186 return false;
3191 $fieldobj = data_get_field($field, $data, $cm);
3193 $relativepath = implode('/', $args);
3194 $fullpath = "/$context->id/mod_data/content/$content->id/$relativepath";
3196 if (!$fieldobj->file_ok($relativepath)) {
3197 return false;
3200 $fs = get_file_storage();
3201 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
3202 return false;
3205 // finally send the file
3206 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
3209 return false;
3213 function data_extend_navigation($navigation, $course, $module, $cm) {
3214 global $CFG, $OUTPUT, $USER, $DB;
3215 require_once($CFG->dirroot . '/mod/data/locallib.php');
3217 $rid = optional_param('rid', 0, PARAM_INT);
3219 $data = $DB->get_record('data', array('id'=>$cm->instance));
3220 $currentgroup = groups_get_activity_group($cm);
3221 $groupmode = groups_get_activity_groupmode($cm);
3223 $numentries = data_numentries($data);
3224 $canmanageentries = has_capability('mod/data:manageentries', context_module::instance($cm->id));
3226 if ($data->entriesleft = data_get_entries_left_to_add($data, $numentries, $canmanageentries)) {
3227 $entriesnode = $navigation->add(get_string('entrieslefttoadd', 'data', $data));
3228 $entriesnode->add_class('note');
3231 $navigation->add(get_string('list', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance)));
3232 if (!empty($rid)) {
3233 $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'rid'=>$rid)));
3234 } else {
3235 $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'single')));
3237 $navigation->add(get_string('search', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'asearch')));
3241 * Adds module specific settings to the settings block
3243 * @param settings_navigation $settings The settings navigation object
3244 * @param navigation_node $datanode The node to add module settings to
3246 function data_extend_settings_navigation(settings_navigation $settings, navigation_node $datanode) {
3247 global $DB, $CFG, $USER;
3249 $data = $DB->get_record('data', array("id" => $settings->get_page()->cm->instance));
3251 $currentgroup = groups_get_activity_group($settings->get_page()->cm);
3252 $groupmode = groups_get_activity_groupmode($settings->get_page()->cm);
3254 // Took out participation list here!
3255 if (data_user_can_add_entry($data, $currentgroup, $groupmode, $settings->get_page()->cm->context)) {
3256 if (empty($editentry)) { //TODO: undefined
3257 $addstring = get_string('add', 'data');
3258 } else {
3259 $addstring = get_string('editentry', 'data');
3261 $addentrynode = $datanode->add($addstring,
3262 new moodle_url('/mod/data/edit.php', array('d' => $settings->get_page()->cm->instance)));
3263 $addentrynode->set_show_in_secondary_navigation(false);
3266 if (has_capability(DATA_CAP_EXPORT, $settings->get_page()->cm->context)) {
3267 // The capability required to Export database records is centrally defined in 'lib.php'
3268 // and should be weaker than those required to edit Templates, Fields and Presets.
3269 $exportentriesnode = $datanode->add(get_string('exportentries', 'data'),
3270 new moodle_url('/mod/data/export.php', array('d' => $data->id)));
3271 $exportentriesnode->set_show_in_secondary_navigation(false);
3273 if (has_capability('mod/data:manageentries', $settings->get_page()->cm->context)) {
3274 $importentriesnode = $datanode->add(get_string('importentries', 'data'),
3275 new moodle_url('/mod/data/import.php', array('d' => $data->id)));
3276 $importentriesnode->set_show_in_secondary_navigation(false);
3279 if (has_capability('mod/data:managetemplates', $settings->get_page()->cm->context)) {
3280 $currenttab = '';
3281 if ($currenttab == 'list') {
3282 $defaultemplate = 'listtemplate';
3283 } else if ($currenttab == 'add') {
3284 $defaultemplate = 'addtemplate';
3285 } else if ($currenttab == 'asearch') {
3286 $defaultemplate = 'asearchtemplate';
3287 } else {
3288 $defaultemplate = 'singletemplate';
3291 $datanode->add(get_string('presets', 'data'), new moodle_url('/mod/data/preset.php', array('d' => $data->id)));
3292 $datanode->add(get_string('fields', 'data'),
3293 new moodle_url('/mod/data/field.php', array('d' => $data->id)));
3294 $datanode->add(get_string('templates', 'data'),
3295 new moodle_url('/mod/data/templates.php', array('d' => $data->id)));
3298 if (!empty($CFG->enablerssfeeds) && !empty($CFG->data_enablerssfeeds) && $data->rssarticles > 0) {
3299 require_once("$CFG->libdir/rsslib.php");
3301 $string = get_string('rsstype', 'data');
3303 $url = new moodle_url(rss_get_url($settings->get_page()->cm->context->id, $USER->id, 'mod_data', $data->id));
3304 $datanode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
3309 * Save the database configuration as a preset.
3311 * @param stdClass $course The course the database module belongs to.
3312 * @param stdClass $cm The course module record
3313 * @param stdClass $data The database record
3314 * @param string $path
3315 * @return bool
3316 * @deprecated since Moodle 4.1 MDL-75142 - please, use the preset::save() function instead.
3317 * @todo MDL-75189 This will be deleted in Moodle 4.5.
3318 * @see preset::save()
3320 function data_presets_save($course, $cm, $data, $path) {
3321 debugging('data_presets_save() is deprecated. Please use preset::save() instead.', DEBUG_DEVELOPER);
3323 $manager = manager::create_from_instance($data);
3324 $preset = preset::create_from_instance($manager, $path);
3325 return $preset->save();
3329 * Generates the XML for the database module provided
3331 * @global moodle_database $DB
3332 * @param stdClass $course The course the database module belongs to.
3333 * @param stdClass $cm The course module record
3334 * @param stdClass $data The database record
3335 * @return string The XML for the preset
3336 * @deprecated since Moodle 4.1 MDL-75142 - please, use the protected preset::generate_preset_xml() function instead.
3337 * @todo MDL-75189 This will be deleted in Moodle 4.5.
3338 * @see preset::generate_preset_xml()
3340 function data_presets_generate_xml($course, $cm, $data) {
3341 debugging(
3342 'data_presets_generate_xml() is deprecated. Please use the protected preset::generate_preset_xml() instead.',
3343 DEBUG_DEVELOPER
3346 $manager = manager::create_from_instance($data);
3347 $preset = preset::create_from_instance($manager, $data->name);
3348 $reflection = new \ReflectionClass(preset::class);
3349 $method = $reflection->getMethod('generate_preset_xml');
3350 $method->setAccessible(true);
3351 return $method->invokeArgs($preset, []);
3355 * Export current fields and presets.
3357 * @param stdClass $course The course the database module belongs to.
3358 * @param stdClass $cm The course module record
3359 * @param stdClass $data The database record
3360 * @param bool $tostorage
3361 * @return string the full path to the exported preset file.
3362 * @deprecated since Moodle 4.1 MDL-75142 - please, use the preset::export() function instead.
3363 * @todo MDL-75189 This will be deleted in Moodle 4.5.
3364 * @see preset::export()
3366 function data_presets_export($course, $cm, $data, $tostorage=false) {
3367 debugging('data_presets_export() is deprecated. Please use preset::export() instead.', DEBUG_DEVELOPER);
3369 $manager = manager::create_from_instance($data);
3370 $preset = preset::create_from_instance($manager, $data->name);
3371 return $preset->export();
3375 * Running addtional permission check on plugin, for example, plugins
3376 * may have switch to turn on/off comments option, this callback will
3377 * affect UI display, not like pluginname_comment_validate only throw
3378 * exceptions.
3379 * Capability check has been done in comment->check_permissions(), we
3380 * don't need to do it again here.
3382 * @package mod_data
3383 * @category comment
3385 * @param stdClass $comment_param {
3386 * context => context the context object
3387 * courseid => int course id
3388 * cm => stdClass course module object
3389 * commentarea => string comment area
3390 * itemid => int itemid
3392 * @return array
3394 function data_comment_permissions($comment_param) {
3395 global $CFG, $DB;
3396 if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3397 throw new comment_exception('invalidcommentitemid');
3399 if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3400 throw new comment_exception('invalidid', 'data');
3402 if ($data->comments) {
3403 return array('post'=>true, 'view'=>true);
3404 } else {
3405 return array('post'=>false, 'view'=>false);
3410 * Validate comment parameter before perform other comments actions
3412 * @package mod_data
3413 * @category comment
3415 * @param stdClass $comment_param {
3416 * context => context the context object
3417 * courseid => int course id
3418 * cm => stdClass course module object
3419 * commentarea => string comment area
3420 * itemid => int itemid
3422 * @return boolean
3424 function data_comment_validate($comment_param) {
3425 global $DB;
3426 // validate comment area
3427 if ($comment_param->commentarea != 'database_entry') {
3428 throw new comment_exception('invalidcommentarea');
3430 // validate itemid
3431 if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3432 throw new comment_exception('invalidcommentitemid');
3434 if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3435 throw new comment_exception('invalidid', 'data');
3437 if (!$course = $DB->get_record('course', array('id'=>$data->course))) {
3438 throw new comment_exception('coursemisconf');
3440 if (!$cm = get_coursemodule_from_instance('data', $data->id, $course->id)) {
3441 throw new comment_exception('invalidcoursemodule');
3443 if (!$data->comments) {
3444 throw new comment_exception('commentsoff', 'data');
3446 $context = context_module::instance($cm->id);
3448 //check if approved
3449 if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3450 throw new comment_exception('notapprovederror', 'data');
3453 // group access
3454 if ($record->groupid) {
3455 $groupmode = groups_get_activity_groupmode($cm, $course);
3456 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3457 if (!groups_is_member($record->groupid)) {
3458 throw new comment_exception('notmemberofgroup');
3462 // validate context id
3463 if ($context->id != $comment_param->context->id) {
3464 throw new comment_exception('invalidcontext');
3466 // validation for comment deletion
3467 if (!empty($comment_param->commentid)) {
3468 if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
3469 if ($comment->commentarea != 'database_entry') {
3470 throw new comment_exception('invalidcommentarea');
3472 if ($comment->contextid != $comment_param->context->id) {
3473 throw new comment_exception('invalidcontext');
3475 if ($comment->itemid != $comment_param->itemid) {
3476 throw new comment_exception('invalidcommentitemid');
3478 } else {
3479 throw new comment_exception('invalidcommentid');
3482 return true;
3486 * Return a list of page types
3487 * @param string $pagetype current page type
3488 * @param stdClass $parentcontext Block's parent context
3489 * @param stdClass $currentcontext Current context of block
3491 function data_page_type_list($pagetype, $parentcontext, $currentcontext) {
3492 $module_pagetype = array('mod-data-*'=>get_string('page-mod-data-x', 'data'));
3493 return $module_pagetype;
3497 * Get all of the record ids from a database activity.
3499 * @param int $dataid The dataid of the database module.
3500 * @param object $selectdata Contains an additional sql statement for the
3501 * where clause for group and approval fields.
3502 * @param array $params Parameters that coincide with the sql statement.
3503 * @return array $idarray An array of record ids
3505 function data_get_all_recordids($dataid, $selectdata = '', $params = null) {
3506 global $DB;
3507 $initsql = 'SELECT r.id
3508 FROM {data_records} r
3509 WHERE r.dataid = :dataid';
3510 if ($selectdata != '') {
3511 $initsql .= $selectdata;
3512 $params = array_merge(array('dataid' => $dataid), $params);
3513 } else {
3514 $params = array('dataid' => $dataid);
3516 $initsql .= ' GROUP BY r.id';
3517 $initrecord = $DB->get_recordset_sql($initsql, $params);
3518 $idarray = array();
3519 foreach ($initrecord as $data) {
3520 $idarray[] = $data->id;
3522 // Close the record set and free up resources.
3523 $initrecord->close();
3524 return $idarray;
3528 * Get the ids of all the records that match that advanced search criteria
3529 * This goes and loops through each criterion one at a time until it either
3530 * runs out of records or returns a subset of records.
3532 * @param array $recordids An array of record ids.
3533 * @param array $searcharray Contains information for the advanced search criteria
3534 * @param int $dataid The data id of the database.
3535 * @return array $recordids An array of record ids.
3537 function data_get_advance_search_ids($recordids, $searcharray, $dataid) {
3538 // Check to see if we have any record IDs.
3539 if (empty($recordids)) {
3540 // Send back an empty search.
3541 return array();
3543 $searchcriteria = array_keys($searcharray);
3544 // Loop through and reduce the IDs one search criteria at a time.
3545 foreach ($searchcriteria as $key) {
3546 $recordids = data_get_recordids($key, $searcharray, $dataid, $recordids);
3547 // If we don't have anymore IDs then stop.
3548 if (!$recordids) {
3549 break;
3552 return $recordids;
3556 * Gets the record IDs given the search criteria
3558 * @param string $alias Record alias.
3559 * @param array $searcharray Criteria for the search.
3560 * @param int $dataid Data ID for the database
3561 * @param array $recordids An array of record IDs.
3562 * @return array $nestarray An arry of record IDs
3564 function data_get_recordids($alias, $searcharray, $dataid, $recordids) {
3565 global $DB;
3566 $searchcriteria = $alias; // Keep the criteria.
3567 $nestsearch = $searcharray[$alias];
3568 // searching for content outside of mdl_data_content
3569 if ($alias < 0) {
3570 $alias = '';
3572 list($insql, $params) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED);
3573 $nestselect = 'SELECT c' . $alias . '.recordid
3574 FROM {data_content} c' . $alias . '
3575 INNER JOIN {data_fields} f
3576 ON f.id = c' . $alias . '.fieldid
3577 INNER JOIN {data_records} r
3578 ON r.id = c' . $alias . '.recordid
3579 INNER JOIN {user} u
3580 ON u.id = r.userid ';
3581 $nestwhere = 'WHERE r.dataid = :dataid
3582 AND c' . $alias .'.recordid ' . $insql . '
3583 AND ';
3585 $params['dataid'] = $dataid;
3586 if (count($nestsearch->params) != 0) {
3587 $params = array_merge($params, $nestsearch->params);
3588 $nestsql = $nestselect . $nestwhere . $nestsearch->sql;
3589 } else if ($searchcriteria == DATA_TIMEMODIFIED) {
3590 $nestsql = $nestselect . $nestwhere . $nestsearch->field . ' >= :timemodified GROUP BY c' . $alias . '.recordid';
3591 $params['timemodified'] = $nestsearch->data;
3592 } else if ($searchcriteria == DATA_TAGS) {
3593 if (empty($nestsearch->rawtagnames)) {
3594 return [];
3596 $i = 0;
3597 $tagwhere = [];
3598 $tagselect = '';
3599 foreach ($nestsearch->rawtagnames as $tagrawname) {
3600 $tagselect .= " INNER JOIN {tag_instance} ti_$i
3601 ON ti_$i.component = 'mod_data'
3602 AND ti_$i.itemtype = 'data_records'
3603 AND ti_$i.itemid = r.id
3604 INNER JOIN {tag} t_$i
3605 ON ti_$i.tagid = t_$i.id ";
3606 $tagwhere[] = " t_$i.rawname = :trawname_$i ";
3607 $params["trawname_$i"] = $tagrawname;
3608 $i++;
3610 $nestsql = $nestselect . $tagselect . $nestwhere . implode(' AND ', $tagwhere);
3611 } else { // First name or last name.
3612 $thing = $DB->sql_like($nestsearch->field, ':search1', false);
3613 $nestsql = $nestselect . $nestwhere . $thing . ' GROUP BY c' . $alias . '.recordid';
3614 $params['search1'] = "%$nestsearch->data%";
3616 $nestrecords = $DB->get_recordset_sql($nestsql, $params);
3617 $nestarray = array();
3618 foreach ($nestrecords as $data) {
3619 $nestarray[] = $data->recordid;
3621 // Close the record set and free up resources.
3622 $nestrecords->close();
3623 return $nestarray;
3627 * Returns an array with an sql string for advanced searches and the parameters that go with them.
3629 * @param int $sort DATA_*
3630 * @param stdClass $data Data module object
3631 * @param array $recordids An array of record IDs.
3632 * @param string $selectdata Information for the where and select part of the sql statement.
3633 * @param string $sortorder Additional sort parameters
3634 * @return array sqlselect sqlselect['sql'] has the sql string, sqlselect['params'] contains an array of parameters.
3636 function data_get_advanced_search_sql($sort, $data, $recordids, $selectdata, $sortorder) {
3637 global $DB;
3639 $userfieldsapi = \core_user\fields::for_userpic()->excluding('id');
3640 $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
3642 if ($sort == 0) {
3643 $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . '
3644 FROM {data_content} c,
3645 {data_records} r,
3646 {user} u ';
3647 $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, u.firstname, u.lastname, ' . $namefields;
3648 } else {
3649 // Sorting through 'Other' criteria
3650 if ($sort <= 0) {
3651 switch ($sort) {
3652 case DATA_LASTNAME:
3653 $sortcontentfull = "u.lastname";
3654 break;
3655 case DATA_FIRSTNAME:
3656 $sortcontentfull = "u.firstname";
3657 break;
3658 case DATA_APPROVED:
3659 $sortcontentfull = "r.approved";
3660 break;
3661 case DATA_TIMEMODIFIED:
3662 $sortcontentfull = "r.timemodified";
3663 break;
3664 case DATA_TIMEADDED:
3665 default:
3666 $sortcontentfull = "r.timecreated";
3668 } else {
3669 $sortfield = data_get_field_from_id($sort, $data);
3670 $sortcontent = $DB->sql_compare_text('c.' . $sortfield->get_sort_field());
3671 $sortcontentfull = $sortfield->get_sort_sql($sortcontent);
3674 $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ',
3675 ' . $sortcontentfull . '
3676 AS sortorder
3677 FROM {data_content} c,
3678 {data_records} r,
3679 {user} u ';
3680 $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ', ' .$sortcontentfull;
3683 // Default to a standard Where statement if $selectdata is empty.
3684 if ($selectdata == '') {
3685 $selectdata = 'WHERE c.recordid = r.id
3686 AND r.dataid = :dataid
3687 AND r.userid = u.id ';
3690 // Find the field we are sorting on
3691 if ($sort > 0 or data_get_field_from_id($sort, $data)) {
3692 $selectdata .= ' AND c.fieldid = :sort AND s.recordid = r.id';
3693 $nestselectsql .= ',{data_content} s ';
3696 // If there are no record IDs then return an sql statment that will return no rows.
3697 if (count($recordids) != 0) {
3698 list($insql, $inparam) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED);
3699 } else {
3700 list($insql, $inparam) = $DB->get_in_or_equal(array('-1'), SQL_PARAMS_NAMED);
3702 $nestfromsql = $selectdata . ' AND c.recordid ' . $insql . $groupsql;
3703 $sqlselect['sql'] = "$nestselectsql $nestfromsql $sortorder";
3704 $sqlselect['params'] = $inparam;
3705 return $sqlselect;
3709 * Checks to see if the user has permission to delete the preset.
3710 * @param stdClass $context Context object.
3711 * @param stdClass $preset The preset object that we are checking for deletion.
3712 * @return bool Returns true if the user can delete, otherwise false.
3713 * @deprecated since Moodle 4.1 MDL-75187 - please, use the preset::can_manage() function instead.
3714 * @todo MDL-75189 This will be deleted in Moodle 4.5.
3715 * @see preset::can_manage()
3717 function data_user_can_delete_preset($context, $preset) {
3718 global $USER;
3720 debugging('data_user_can_delete_preset() is deprecated. Please use manager::can_manage() instead.', DEBUG_DEVELOPER);
3722 if ($context->contextlevel == CONTEXT_MODULE && isset($preset->name)) {
3723 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
3724 $manager = manager::create_from_coursemodule($cm);
3725 $todelete = preset::create_from_instance($manager, $preset->name);
3726 return $todelete->can_manage();
3729 if (has_capability('mod/data:manageuserpresets', $context)) {
3730 return true;
3731 } else {
3732 $candelete = false;
3733 $userid = $preset instanceof preset ? $preset->get_userid() : $preset->userid;
3734 if ($userid == $USER->id) {
3735 $candelete = true;
3737 return $candelete;
3742 * Delete a record entry.
3744 * @param int $recordid The ID for the record to be deleted.
3745 * @param object $data The data object for this activity.
3746 * @param int $courseid ID for the current course (for logging).
3747 * @param int $cmid The course module ID.
3748 * @return bool True if the record deleted, false if not.
3750 function data_delete_record($recordid, $data, $courseid, $cmid) {
3751 global $DB, $CFG;
3753 if ($deleterecord = $DB->get_record('data_records', array('id' => $recordid))) {
3754 if ($deleterecord->dataid == $data->id) {
3755 if ($contents = $DB->get_records('data_content', array('recordid' => $deleterecord->id))) {
3756 foreach ($contents as $content) {
3757 if ($field = data_get_field_from_id($content->fieldid, $data)) {
3758 $field->delete_content($content->recordid);
3761 $DB->delete_records('data_content', array('recordid'=>$deleterecord->id));
3762 $DB->delete_records('data_records', array('id'=>$deleterecord->id));
3764 // Delete cached RSS feeds.
3765 if (!empty($CFG->enablerssfeeds)) {
3766 require_once($CFG->dirroot.'/mod/data/rsslib.php');
3767 data_rss_delete_file($data);
3770 core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $recordid);
3772 // Trigger an event for deleting this record.
3773 $event = \mod_data\event\record_deleted::create(array(
3774 'objectid' => $deleterecord->id,
3775 'context' => context_module::instance($cmid),
3776 'courseid' => $courseid,
3777 'other' => array(
3778 'dataid' => $deleterecord->dataid
3781 $event->add_record_snapshot('data_records', $deleterecord);
3782 $event->trigger();
3783 $course = get_course($courseid);
3784 $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
3785 data_update_completion_state($data, $course, $cm);
3787 return true;
3792 return false;
3796 * Check for required fields, and build a list of fields to be updated in a
3797 * submission.
3799 * @param $mod stdClass The current recordid - provided as an optimisation.
3800 * @param $fields array The field data
3801 * @param $datarecord stdClass The submitted data.
3802 * @return stdClass containing:
3803 * * string[] generalnotifications Notifications for the form as a whole.
3804 * * string[] fieldnotifications Notifications for a specific field.
3805 * * bool validated Whether the field was validated successfully.
3806 * * data_field_base[] fields The field objects to be update.
3808 function data_process_submission(stdClass $mod, $fields, stdClass $datarecord) {
3809 $result = new stdClass();
3811 // Empty form checking - you can't submit an empty form.
3812 $emptyform = true;
3813 $requiredfieldsfilled = true;
3814 $fieldsvalidated = true;
3816 // Store the notifications.
3817 $result->generalnotifications = array();
3818 $result->fieldnotifications = array();
3820 // Store the instantiated classes as an optimisation when processing the result.
3821 // This prevents the fields being re-initialised when updating.
3822 $result->fields = array();
3824 $submitteddata = array();
3825 foreach ($datarecord as $fieldname => $fieldvalue) {
3826 if (strpos($fieldname, '_')) {
3827 $namearray = explode('_', $fieldname, 3);
3828 $fieldid = $namearray[1];
3829 if (!isset($submitteddata[$fieldid])) {
3830 $submitteddata[$fieldid] = array();
3832 if (count($namearray) === 2) {
3833 $subfieldid = 0;
3834 } else {
3835 $subfieldid = $namearray[2];
3838 $fielddata = new stdClass();
3839 $fielddata->fieldname = $fieldname;
3840 $fielddata->value = $fieldvalue;
3841 $submitteddata[$fieldid][$subfieldid] = $fielddata;
3845 // Check all form fields which have the required are filled.
3846 foreach ($fields as $fieldrecord) {
3847 // Check whether the field has any data.
3848 $fieldhascontent = false;
3850 $field = data_get_field($fieldrecord, $mod);
3851 if (isset($submitteddata[$fieldrecord->id])) {
3852 // Field validation check.
3853 if (method_exists($field, 'field_validation')) {
3854 $errormessage = $field->field_validation($submitteddata[$fieldrecord->id]);
3855 if ($errormessage) {
3856 $result->fieldnotifications[$field->field->name][] = $errormessage;
3857 $fieldsvalidated = false;
3860 foreach ($submitteddata[$fieldrecord->id] as $fieldname => $value) {
3861 if ($field->notemptyfield($value->value, $value->fieldname)) {
3862 // The field has content and the form is not empty.
3863 $fieldhascontent = true;
3864 $emptyform = false;
3869 // If the field is required, add a notification to that effect.
3870 if ($field->field->required && !$fieldhascontent) {
3871 if (!isset($result->fieldnotifications[$field->field->name])) {
3872 $result->fieldnotifications[$field->field->name] = array();
3874 $result->fieldnotifications[$field->field->name][] = get_string('errormustsupplyvalue', 'data');
3875 $requiredfieldsfilled = false;
3878 // Update the field.
3879 if (isset($submitteddata[$fieldrecord->id])) {
3880 foreach ($submitteddata[$fieldrecord->id] as $value) {
3881 $result->fields[$value->fieldname] = $field;
3886 if ($emptyform) {
3887 // The form is empty.
3888 $result->generalnotifications[] = get_string('emptyaddform', 'data');
3891 $result->validated = $requiredfieldsfilled && !$emptyform && $fieldsvalidated;
3893 return $result;
3897 * This standard function will check all instances of this module
3898 * and make sure there are up-to-date events created for each of them.
3899 * If courseid = 0, then every data event in the site is checked, else
3900 * only data events belonging to the course specified are checked.
3901 * This function is used, in its new format, by restore_refresh_events()
3903 * @param int $courseid
3904 * @param int|stdClass $instance Data module instance or ID.
3905 * @param int|stdClass $cm Course module object or ID (not used in this module).
3906 * @return bool
3908 function data_refresh_events($courseid = 0, $instance = null, $cm = null) {
3909 global $DB, $CFG;
3910 require_once($CFG->dirroot.'/mod/data/locallib.php');
3912 // If we have instance information then we can just update the one event instead of updating all events.
3913 if (isset($instance)) {
3914 if (!is_object($instance)) {
3915 $instance = $DB->get_record('data', array('id' => $instance), '*', MUST_EXIST);
3917 data_set_events($instance);
3918 return true;
3921 if ($courseid) {
3922 if (! $data = $DB->get_records("data", array("course" => $courseid))) {
3923 return true;
3925 } else {
3926 if (! $data = $DB->get_records("data")) {
3927 return true;
3931 foreach ($data as $datum) {
3932 data_set_events($datum);
3934 return true;
3938 * Fetch the configuration for this database activity.
3940 * @param stdClass $database The object returned from the database for this instance
3941 * @param string $key The name of the key to retrieve. If none is supplied, then all configuration is returned
3942 * @param mixed $default The default value to use if no value was found for the specified key
3943 * @return mixed The returned value
3945 function data_get_config($database, $key = null, $default = null) {
3946 if (!empty($database->config)) {
3947 $config = json_decode($database->config);
3948 } else {
3949 $config = new stdClass();
3952 if ($key === null) {
3953 return $config;
3956 if (property_exists($config, $key)) {
3957 return $config->$key;
3959 return $default;
3963 * Update the configuration for this database activity.
3965 * @param stdClass $database The object returned from the database for this instance
3966 * @param string $key The name of the key to set
3967 * @param mixed $value The value to set for the key
3969 function data_set_config(&$database, $key, $value) {
3970 // Note: We must pass $database by reference because there may be subsequent calls to update_record and these should
3971 // not overwrite the configuration just set.
3972 global $DB;
3974 $config = data_get_config($database);
3976 if (!isset($config->$key) || $config->$key !== $value) {
3977 $config->$key = $value;
3978 $database->config = json_encode($config);
3979 $DB->set_field('data', 'config', $database->config, ['id' => $database->id]);
3983 * Sets the automatic completion state for this database item based on the
3984 * count of on its entries.
3985 * @since Moodle 3.3
3986 * @param object $data The data object for this activity
3987 * @param object $course Course
3988 * @param object $cm course-module
3990 function data_update_completion_state($data, $course, $cm) {
3991 // If completion option is enabled, evaluate it and return true/false.
3992 $completion = new completion_info($course);
3993 if ($data->completionentries && $completion->is_enabled($cm)) {
3994 $numentries = data_numentries($data);
3995 // Check the number of entries required against the number of entries already made.
3996 if ($numentries >= $data->completionentries) {
3997 $completion->update_state($cm, COMPLETION_COMPLETE);
3998 } else {
3999 $completion->update_state($cm, COMPLETION_INCOMPLETE);
4005 * Mark the activity completed (if required) and trigger the course_module_viewed event.
4007 * @deprecated since Moodle 4.1 MDL-75146 - please do not use this function any more.
4008 * @todo MDL-75189 Final deprecation in Moodle 4.5.
4009 * @param stdClass $data data object
4010 * @param stdClass $course course object
4011 * @param stdClass $cm course module object
4012 * @param stdClass $context context object
4013 * @since Moodle 3.3
4015 function data_view($data, $course, $cm, $context) {
4016 global $CFG;
4017 debugging('data_view is deprecated. Use mod_data\\manager::set_module_viewed instead', DEBUG_DEVELOPER);
4018 require_once($CFG->libdir . '/completionlib.php');
4020 // Trigger course_module_viewed event.
4021 $params = array(
4022 'context' => $context,
4023 'objectid' => $data->id
4026 $event = \mod_data\event\course_module_viewed::create($params);
4027 $event->add_record_snapshot('course_modules', $cm);
4028 $event->add_record_snapshot('course', $course);
4029 $event->add_record_snapshot('data', $data);
4030 $event->trigger();
4032 // Completion.
4033 $completion = new completion_info($course);
4034 $completion->set_module_viewed($cm);
4038 * Get icon mapping for font-awesome.
4040 function mod_data_get_fontawesome_icon_map() {
4041 return [
4042 'mod_data:field/checkbox' => 'fa-check-square-o',
4043 'mod_data:field/date' => 'fa-calendar-o',
4044 'mod_data:field/file' => 'fa-file',
4045 'mod_data:field/latlong' => 'fa-globe',
4046 'mod_data:field/menu' => 'fa-bars',
4047 'mod_data:field/multimenu' => 'fa-bars',
4048 'mod_data:field/number' => 'fa-hashtag',
4049 'mod_data:field/picture' => 'fa-picture-o',
4050 'mod_data:field/radiobutton' => 'fa-circle-o',
4051 'mod_data:field/textarea' => 'fa-font',
4052 'mod_data:field/text' => 'fa-i-cursor',
4053 'mod_data:field/url' => 'fa-link',
4058 * Check if the module has any update that affects the current user since a given time.
4060 * @param cm_info $cm course module data
4061 * @param int $from the time to check updates from
4062 * @param array $filter if we need to check only specific updates
4063 * @return stdClass an object with the different type of areas indicating if they were updated or not
4064 * @since Moodle 3.2
4066 function data_check_updates_since(cm_info $cm, $from, $filter = array()) {
4067 global $DB, $CFG;
4068 require_once($CFG->dirroot . '/mod/data/locallib.php');
4070 $updates = course_check_module_updates_since($cm, $from, array(), $filter);
4072 // Check for new entries.
4073 $updates->entries = (object) array('updated' => false);
4075 $data = $DB->get_record('data', array('id' => $cm->instance), '*', MUST_EXIST);
4076 $searcharray = [];
4077 $searcharray[DATA_TIMEMODIFIED] = new stdClass();
4078 $searcharray[DATA_TIMEMODIFIED]->sql = '';
4079 $searcharray[DATA_TIMEMODIFIED]->params = array();
4080 $searcharray[DATA_TIMEMODIFIED]->field = 'r.timemodified';
4081 $searcharray[DATA_TIMEMODIFIED]->data = $from;
4083 $currentgroup = groups_get_activity_group($cm);
4084 // Teachers should retrieve all entries when not in separate groups.
4085 if (has_capability('mod/data:manageentries', $cm->context) && groups_get_activity_groupmode($cm) != SEPARATEGROUPS) {
4086 $currentgroup = 0;
4088 list($entries, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
4089 data_search_entries($data, $cm, $cm->context, 'list', $currentgroup, '', null, null, 0, 0, true, $searcharray);
4091 if (!empty($entries)) {
4092 $updates->entries->updated = true;
4093 $updates->entries->itemids = array_keys($entries);
4096 return $updates;
4100 * This function receives a calendar event and returns the action associated with it, or null if there is none.
4102 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
4103 * is not displayed on the block.
4105 * @param calendar_event $event
4106 * @param \core_calendar\action_factory $factory
4107 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
4108 * @return \core_calendar\local\event\entities\action_interface|null
4110 function mod_data_core_calendar_provide_event_action(calendar_event $event,
4111 \core_calendar\action_factory $factory,
4112 int $userid = 0) {
4113 global $USER;
4115 if (!$userid) {
4116 $userid = $USER->id;
4119 $cm = get_fast_modinfo($event->courseid, $userid)->instances['data'][$event->instance];
4121 if (!$cm->uservisible) {
4122 // The module is not visible to the user for any reason.
4123 return null;
4126 $now = time();
4128 if (!empty($cm->customdata['timeavailableto']) && $cm->customdata['timeavailableto'] < $now) {
4129 // The module has closed so the user can no longer submit anything.
4130 return null;
4133 // The module is actionable if we don't have a start time or the start time is
4134 // in the past.
4135 $actionable = (empty($cm->customdata['timeavailablefrom']) || $cm->customdata['timeavailablefrom'] <= $now);
4137 return $factory->create_instance(
4138 get_string('add', 'data'),
4139 new \moodle_url('/mod/data/view.php', array('id' => $cm->id)),
4141 $actionable
4146 * Add a get_coursemodule_info function in case any database type wants to add 'extra' information
4147 * for the course (see resource).
4149 * Given a course_module object, this function returns any "extra" information that may be needed
4150 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
4152 * @param stdClass $coursemodule The coursemodule object (record).
4153 * @return cached_cm_info An object on information that the courses
4154 * will know about (most noticeably, an icon).
4156 function data_get_coursemodule_info($coursemodule) {
4157 global $DB;
4159 $dbparams = ['id' => $coursemodule->instance];
4160 $fields = 'id, name, intro, introformat, completionentries, timeavailablefrom, timeavailableto';
4161 if (!$data = $DB->get_record('data', $dbparams, $fields)) {
4162 return false;
4165 $result = new cached_cm_info();
4166 $result->name = $data->name;
4168 if ($coursemodule->showdescription) {
4169 // Convert intro to html. Do not filter cached version, filters run at display time.
4170 $result->content = format_module_intro('data', $data, $coursemodule->id, false);
4173 // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
4174 if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
4175 $result->customdata['customcompletionrules']['completionentries'] = $data->completionentries;
4177 // Other properties that may be used in calendar or on dashboard.
4178 if ($data->timeavailablefrom) {
4179 $result->customdata['timeavailablefrom'] = $data->timeavailablefrom;
4181 if ($data->timeavailableto) {
4182 $result->customdata['timeavailableto'] = $data->timeavailableto;
4185 return $result;
4189 * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
4191 * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
4192 * @return array $descriptions the array of descriptions for the custom rules.
4194 function mod_data_get_completion_active_rule_descriptions($cm) {
4195 // Values will be present in cm_info, and we assume these are up to date.
4196 if (empty($cm->customdata['customcompletionrules'])
4197 || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
4198 return [];
4201 $descriptions = [];
4202 foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
4203 switch ($key) {
4204 case 'completionentries':
4205 if (!empty($val)) {
4206 $descriptions[] = get_string('completionentriesdesc', 'data', $val);
4208 break;
4209 default:
4210 break;
4213 return $descriptions;
4217 * This function calculates the minimum and maximum cutoff values for the timestart of
4218 * the given event.
4220 * It will return an array with two values, the first being the minimum cutoff value and
4221 * the second being the maximum cutoff value. Either or both values can be null, which
4222 * indicates there is no minimum or maximum, respectively.
4224 * If a cutoff is required then the function must return an array containing the cutoff
4225 * timestamp and error string to display to the user if the cutoff value is violated.
4227 * A minimum and maximum cutoff return value will look like:
4229 * [1505704373, 'The due date must be after the sbumission start date'],
4230 * [1506741172, 'The due date must be before the cutoff date']
4233 * @param calendar_event $event The calendar event to get the time range for
4234 * @param stdClass $instance The module instance to get the range from
4235 * @return array
4237 function mod_data_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
4238 $mindate = null;
4239 $maxdate = null;
4241 if ($event->eventtype == DATA_EVENT_TYPE_OPEN) {
4242 // The start time of the open event can't be equal to or after the
4243 // close time of the database activity.
4244 if (!empty($instance->timeavailableto)) {
4245 $maxdate = [
4246 $instance->timeavailableto,
4247 get_string('openafterclose', 'data')
4250 } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) {
4251 // The start time of the close event can't be equal to or earlier than the
4252 // open time of the database activity.
4253 if (!empty($instance->timeavailablefrom)) {
4254 $mindate = [
4255 $instance->timeavailablefrom,
4256 get_string('closebeforeopen', 'data')
4261 return [$mindate, $maxdate];
4265 * This function will update the data module according to the
4266 * event that has been modified.
4268 * It will set the timeopen or timeclose value of the data instance
4269 * according to the type of event provided.
4271 * @throws \moodle_exception
4272 * @param \calendar_event $event
4273 * @param stdClass $data The module instance to get the range from
4275 function mod_data_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $data) {
4276 global $DB;
4278 if (empty($event->instance) || $event->modulename != 'data') {
4279 return;
4282 if ($event->instance != $data->id) {
4283 return;
4286 if (!in_array($event->eventtype, [DATA_EVENT_TYPE_OPEN, DATA_EVENT_TYPE_CLOSE])) {
4287 return;
4290 $courseid = $event->courseid;
4291 $modulename = $event->modulename;
4292 $instanceid = $event->instance;
4293 $modified = false;
4295 $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
4296 $context = context_module::instance($coursemodule->id);
4298 // The user does not have the capability to modify this activity.
4299 if (!has_capability('moodle/course:manageactivities', $context)) {
4300 return;
4303 if ($event->eventtype == DATA_EVENT_TYPE_OPEN) {
4304 // If the event is for the data activity opening then we should
4305 // set the start time of the data activity to be the new start
4306 // time of the event.
4307 if ($data->timeavailablefrom != $event->timestart) {
4308 $data->timeavailablefrom = $event->timestart;
4309 $data->timemodified = time();
4310 $modified = true;
4312 } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) {
4313 // If the event is for the data activity closing then we should
4314 // set the end time of the data activity to be the new start
4315 // time of the event.
4316 if ($data->timeavailableto != $event->timestart) {
4317 $data->timeavailableto = $event->timestart;
4318 $modified = true;
4322 if ($modified) {
4323 $data->timemodified = time();
4324 $DB->update_record('data', $data);
4325 $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
4326 $event->trigger();
4331 * Callback to fetch the activity event type lang string.
4333 * @param string $eventtype The event type.
4334 * @return lang_string The event type lang string.
4336 function mod_data_core_calendar_get_event_action_string(string $eventtype): string {
4337 $modulename = get_string('modulename', 'data');
4339 switch ($eventtype) {
4340 case DATA_EVENT_TYPE_OPEN:
4341 $identifier = 'calendarstart';
4342 break;
4343 case DATA_EVENT_TYPE_CLOSE:
4344 $identifier = 'calendarend';
4345 break;
4346 default:
4347 return get_string('requiresaction', 'calendar', $modulename);
4350 return get_string($identifier, 'data', $modulename);