2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
19 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
20 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') ||
die();
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');
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 */
64 /** @var object The field object itself, if we know it */
66 /** @var int Width of the icon for this fieldtype */
68 /** @var int Width of the icon for this fieldtype */
70 /** @var object course module or cmifno */
72 /** @var object activity 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;
91 * Constructor function
94 * @uses CONTEXT_MODULE
99 function __construct($field=0, $data=0, $cm=0) { // Field or data or both, each can be id or object
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');
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
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');
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 {
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
);
177 'fieldid' => $this->field
->id
,
178 'recordid' => $recordid,
179 'content' => "<span class=\"nopreview\">$message</span>",
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.
201 public function get_preview(): bool {
202 return $this->preview
;
207 * This field just sets up a default field object
211 function define_default_field() {
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;
231 * Set up the field object according to data in an object. Now is the time to clean it!
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
);
263 * Insert a new field in the database
264 * We assume the field object is already defined as $this->field
269 function insert_field() {
272 if (empty($this->field
)) {
273 echo $OUTPUT->notification('Programmer error: Field has not been defined yet! See define_field()');
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
,
284 'fieldname' => $this->field
->name
,
285 'dataid' => $this->data
->id
295 * Update a field in the database
300 function update_field() {
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
,
310 'fieldname' => $this->field
->name
,
311 'dataid' => $this->data
->id
320 * Delete a field completely
325 function delete_field() {
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
,
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);
359 * Print the relevant form element in the ADD template for this field
362 * @param int $recordid
365 function display_add_field($recordid=0, $formdata=null) {
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));
377 // beware get_field returns false for new, empty records MDL-18567
378 if ($content===false) {
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) . '" />';
397 * Print the relevant form element to define the attributes for this field
398 * viewable by teachers only.
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";
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
]));
436 require_once($filepath);
439 $actionbuttons = html_writer
::start_div();
440 $actionbuttons .= html_writer
::tag('input', null, [
443 'value' => get_string('cancel'),
444 'class' => 'btn btn-secondary mx-1'
446 $actionbuttons .= html_writer
::tag('input', null, [
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);
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 {
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) {
482 if ($this->preview
) {
483 return $this->get_data_content_preview($recordid);
485 return $DB->get_record(
487 ['fieldid' => $this->field
->id
, 'recordid' => $recordid]
492 * Display the content of the field in browse mode
495 * @param int $recordid
496 * @param object $template
497 * @return bool|string
499 function display_browse_field($recordid, $template) {
501 $content = $this->get_data_content($recordid);
502 if (!$content ||
!isset($content->content
)) {
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);
516 * Update the content of one data field in the data_content table
518 * @param int $recordid
519 * @param mixed $value
520 * @param string $name
523 function update_content($recordid, $value, $name=''){
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);
535 return $DB->insert_record('data_content', $content);
540 * Delete all content associated with the field
543 * @param int $recordid
546 function delete_content($recordid=0) {
550 $conditions = array('fieldid'=>$this->field
->id
, 'recordid'=>$recordid);
552 $conditions = array('fieldid'=>$this->field
->id
);
555 $rs = $DB->get_recordset('data_content', $conditions);
557 $fs = get_file_storage();
558 foreach ($rs as $content) {
559 $fs->delete_area_files($this->context
->id
, 'mod_data', 'content', $content->id
);
564 return $DB->delete_records('data_content', $conditions);
568 * Check if a field from an add form is empty
570 * @param mixed $value
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
597 function get_sort_field() {
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) {
612 * Returns the name/type of the field
617 return get_string('fieldtypelabel', "datafield_$this->type");
621 * Prints the respective type icon
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.
638 function text_export_supported() {
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 {
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 {
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 {
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
{
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
694 public function export_text_value(stdClass
$record) {
695 if ($this->text_export_supported()) {
696 return $record->content
;
702 * @param string $relativepath
705 function file_ok($relativepath) {
710 * Returns the priority for being indexed by globalsearch
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
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
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).
740 for ($i = 1; $i <= 10; $i++
) {
741 $configs["param$i"] = null;
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) {
761 if (!$data ||
!$template) {
765 // These templates are empty by default (they have no content).
769 'listtemplateheader',
770 'listtemplatefooter',
773 if (in_array($template, $emptytemplates)) {
777 $manager = manager
::create_from_instance($data);
778 if (empty($manager->get_fields())) {
779 // No template will be returned if there are no fields.
783 $templateclass = \mod_data\template
::create_default_template($manager, $template, $form);
784 $templatecontent = $templateclass->get_template_content();
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
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');
821 $tags +
= $DB->get_records_menu('tag', array('isstandard' => 1, 'tagcollid' => $tagcollid),
822 $namefield, 'id,' . $namefield . ' as fieldname');
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>";
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',
848 $str .= ' ' . $OUTPUT->action_link($url, get_string('managestandardtags', 'tag'));
851 $PAGE->requires
->js_call_amd('core/form-autocomplete', 'enhance', $params = array(
855 get_string('entertags', 'tag'),
858 get_string('noselection', 'form')
862 $str .= html_writer
::end_tag('div');
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.
874 * @param object $data
875 * @param string $searchfieldname
876 * @param string $newfieldname
879 function data_replace_field_in_templates($data, $searchfieldname, $newfieldname) {
882 $newdata = (object)['id' => $data->id
];
884 $templates = ['listtemplate', 'singletemplate', 'asearchtemplate', 'addtemplate', 'rsstemplate'];
885 foreach ($templates as $templatename) {
886 if (empty($data->$templatename)) {
890 '[[' . $searchfieldname . ']]',
891 '[[' . $searchfieldname . '#id]]',
892 '[[' . $searchfieldname . '#name]]',
893 '[[' . $searchfieldname . '#description]]',
895 if (empty($newfieldname)) {
896 $replace = ['', '', '', ''];
899 '[[' . $newfieldname . ']]',
900 '[[' . $newfieldname . '#id]]',
901 '[[' . $newfieldname . '#name]]',
902 '[[' . $newfieldname . '#description]]',
905 $newdata->{$templatename} = str_ireplace($search, $replace, $data->{$templatename} ??
'');
911 return $DB->update_record('data', $newdata);
916 * Appends a new field at the end of the form template.
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 {
926 $newdata = (object)['id' => $data->id
];
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
936 $newdata->$templatename = $data->$templatename;
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']
950 return $DB->update_record('data', $newdata);
956 * this function creates an instance of the particular subfield class
959 * @param string $name
960 * @param object $data
961 * @return object|bool
963 function data_get_field_from_name($name, $data){
966 $field = $DB->get_record('data_fields', array('name'=>$name, 'dataid'=>$data->id
));
969 return data_get_field($field, $data);
977 * this function creates an instance of the particular subfield class
980 * @param int $fieldid
981 * @param object $data
982 * @return bool|object
984 function data_get_field_from_id($fieldid, $data){
987 $field = $DB->get_record('data_fields', array('id'=>$fieldid, 'dataid'=>$data->id
));
990 return data_get_field($field, $data);
998 * this function creates an instance of the particular subfield class
1001 * @param string $type
1002 * @param object $data
1005 function data_get_field_new($type, $data) {
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);
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
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
{
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);
1047 * Given record object (or id), returns true if the record belongs to the current user
1051 * @param mixed $record record object or id
1054 function data_isowner($record) {
1057 if (!isloggedin()) { // perf shortcut
1061 if (!is_object($record)) {
1062 if (!$record = $DB->get_record('data_records', array('id'=>$record))) {
1067 return ($record->userid
== $USER->id
);
1071 * has a user reached the max number of entries?
1073 * @param object $data
1076 function data_atmaxentries($data){
1077 if (!$data->maxentries
){
1081 return (data_numentries($data) >= $data->maxentries
);
1086 * returns the number of entries already made by this user
1090 * @param object $data
1093 function data_numentries($data, $userid=null) {
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
1108 * @param object $data
1109 * @param int $groupid
1110 * @param int $userid
1113 function data_add_record($data, $groupid = 0, $userid = null) {
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;
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,
1136 'dataid' => $data->id
1141 $course = get_course($cm->course
);
1142 data_update_completion_state($data, $course, $cm);
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.
1153 * @param int $dataid,
1154 * @param string $template
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){
1168 echo $OUTPUT->notification('[['.$field->name
.']] - '.get_string('multipletags','data'));
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) {
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);
1211 * updates an instance of a data
1214 * @param object $data
1217 function data_update_instance($data) {
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);
1253 * deletes an instance of a data
1259 function data_delete_instance($id) { // takes the dataid
1262 if (!$data = $DB->get_record('data', array('id'=>$id))) {
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);
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));
1294 * returns a summary of data activity of this user
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) {
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
)) {
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
;
1323 if (!$grade->hidden ||
has_capability('moodle/grade:viewhidden', context_course
::instance($course->id
))) {
1324 $result->info
.= ', ' . get_string('gradenoun') . ': ' . $grade->str_long_grade
;
1326 $result->info
= get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
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
;
1337 $result->info
= get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
1346 * Prints all the records uploaded by this user
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
);
1367 echo $OUTPUT->container(get_string('gradenoun') . ': ' . get_string('hidden', 'grades'));
1370 $records = $DB->get_records(
1372 ['dataid' => $data->id
, 'userid' => $user->id
],
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.
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) {
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
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) {
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);
1436 data_grade_item_update($data);
1441 * Update/create grade item for given data
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) {
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;
1472 return grade_update('mod/data', $data->course
, 'mod', 'data', $data->id
, 0, $grades, $params);
1476 * Delete grade item for given data
1479 * @param object $data object
1480 * @return object grade_item
1482 function data_grade_item_delete($data) {
1484 require_once($CFG->libdir
.'/gradelib.php');
1486 return grade_update('mod/data', $data->course
, 'mod', 'data', $data->id
, 0, NULL, array('deleted'=>1));
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) {
1507 'data_print_template is deprecated. Use mod_data\\manager::get_template and mod_data\\template::parse_entries instead',
1512 'search' => $search,
1516 $options['baseurl'] = $jumpurl;
1518 $manager = manager
::create_from_instance($data);
1519 $parser = $manager->get_template($templatename, $options);
1520 $content = $parser->parse_entries($records);
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') {
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) {
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
1595 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING
) {
1596 throw new rating_exception('invalidnum');
1600 if ($info->scale
< 0) {
1601 //its a custom scale
1602 $scalerecord = $DB->get_record('scale', array('id' => -$info->scale
));
1604 $scalearray = explode(',', $scalerecord->scale
);
1605 if ($params['rating'] > count($scalearray)) {
1606 throw new rating_exception('invalidnum');
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');
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]
1663 * @throws coding_exception
1664 * @throws rating_exception
1666 function mod_data_rating_can_see_item_ratings($params) {
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) {
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.
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
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">';
1735 echo '<input type="hidden" name="d" value="'.$data->id
.'" />';
1736 if ($mode =='asearch') {
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'));
1746 $regsearchclass = 'search_none';
1747 $advancedsearchclass = 'search_inline';
1749 $regsearchclass = 'search_inline';
1750 $advancedsearchclass = 'search_none';
1752 echo '<div id="reg_search" class="' . $regsearchclass . ' form-inline" > ';
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 ' <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>';
1764 echo '<option value="'.$field->id
.'">'.$field->name
.'</option>';
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>';
1782 echo '<option value="'.$key.'">'.$name.'</option>';
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>';
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>';
1797 echo '<option value="DESC">'.get_string('descending','data').'</option>';
1802 $checked = ' checked="checked" ';
1807 $PAGE->requires
->js('/mod/data/data.js');
1808 echo ' <input type="hidden" name="advanced" value="0" />';
1809 echo ' <input type="hidden" name="filter" value="1" />';
1810 echo ' <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>';
1814 echo '<div id="advsearch-save-sec" class="ml-auto '. $regsearchclass . '">';
1815 echo ' <input type="submit" class="btn btn-secondary" value="' . get_string('savesettings', 'data') . '" />';
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"> </td></tr>';
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
) {
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);
1852 $patterns = array();
1853 $replacement = array();
1855 // Then we generate strings to replace for normal tags
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') {
1865 if (!empty($search_array[$field->field
->id
]->data
)) {
1866 $searchinput = $searchfield->display_search_field($search_array[$field->field
->id
]->data
);
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
;
1878 if (strpos($asearchtemplate, "[[" . $field->field
->name
. "]]") === false) {
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]
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;
1916 echo preg_replace($patterns, $replacement, format_text($asearchtemplate, FORMAT_HTML
, $options));
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') . '" />' .
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) {
1941 if (!empty($record->rating
)){
1942 $result = $OUTPUT->render($record->rating
);
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.
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.
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
1984 function data_fieldname_exists($name, $dataid, $fieldid = 0) {
1987 if (!is_numeric($name)) {
1988 $like = $DB->sql_like('df.name', ':name', false);
1990 $like = "df.name = :name";
1992 $params = array('name'=>$name);
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);
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)) {
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
2030 * @uses CONTEXT_MODULE
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.
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');
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
) {
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
);
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
);
2088 // 2 - Only teachers can rate posts
2089 // 1 - Everyone can rate posts
2090 // 0 - No one can rate posts
2091 switch ($data->assessed
) {
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
);
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
);
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
);
2118 // $data->assesspublic:
2119 // 0 - Students can only see their own ratings
2120 // 1 - Students can see everyone's ratings
2121 switch ($data->assesspublic
) {
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
);
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
);
2141 $cm = $DB->get_record('course_modules', array('id'=>$cmid));
2144 switch ($cm->groupmode
) {
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
);
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
);
2168 * Returns the best name to show for a preset
2170 * @param string $shortname
2171 * @param string $path
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.
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()) {
2211 'data_get_available_site_presets() is deprecated. Please use manager::get_available_saved_presets() instead.',
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
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) {
2242 $dir = $fs->get_file(DATA_PRESET_CONTEXT
, DATA_PRESET_COMPONENT
, DATA_PRESET_FILEAREA
, 0, '/'.$name.'/', '.');
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();
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
2284 function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = null) {
2287 // Don't let add entry to a database that has no fields.
2288 if (!$DB->record_exists('data_fields', ['dataid' => $data->id
])) {
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)) {
2303 } else if (data_atmaxentries($data)) {
2305 } else if (data_in_readonly_period($data)) {
2306 // Check whether we're in a read-only period
2310 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
2314 if ($currentgroup) {
2315 return groups_is_member($currentgroup);
2317 //else it might be group 0 in visible mode
2318 if ($groupmode == VISIBLEGROUPS
){
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) {
2337 if (has_capability('mod/data:manageentries', $context)) {
2341 // Check whether this activity is read-only at present.
2342 $readonly = data_in_readonly_period($data);
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))) {
2352 if (data_isowner($record)) {
2353 if ($data->approval
&& $record->approved
) {
2354 return $data->manageapproved
== 1;
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) {
2372 if (!$data->timeviewfrom
&& !$data->timeviewto
) {
2374 } else if (($data->timeviewfrom
&& $now < $data->timeviewfrom
) ||
($data->timeviewto
&& $now > $data->timeviewto
)) {
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
{
2406 protected $directory;
2411 * @param stdClass $course
2412 * @param stdClass $cm
2413 * @param stdClass $module
2414 * @param string $directory
2416 public function __construct($course, $cm, $module, $directory) {
2418 'data_preset_importer is deprecated. Please use mod\\data\\local\\importer\\preset_importer instead',
2422 $this->course
= $course;
2424 $this->module
= $module;
2425 $this->directory
= $directory;
2429 * Returns the name of the directory the preset is located in
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)!='/') {
2449 if (file_exists($dir.$filename)) {
2450 return file_get_contents($dir.$filename);
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();
2465 * Gets the preset settings
2466 * @global moodle_database $DB
2469 public function get_preset_settings() {
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()) {
2489 $presetname = trim($file->get_filepath(), '/');
2490 if ($presetname==$presettofind) {
2491 $this->directory
= $presetname;
2496 if (empty($fileobj)) {
2497 throw new \
moodle_exception('invalidpreset', 'data', '', $this->directory
);
2501 $allowed_settings = array(
2505 'requiredentriestoview',
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();
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
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)) {
2542 $f = new StdClass();
2543 foreach ($field['#'] as $param => $value) {
2544 if (!is_array($value)) {
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
;
2570 * Import the preset into the given database module
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
);
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);
2611 /* Make a new field */
2612 $filepath = "field/$newfield->type/field.class.php";
2613 if (!file_exists($filepath)) {
2614 $missingfieldtypes[] = $newfield->name
;
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();
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
)) {
2649 $settings->defaultsort
= 0;
2651 $settings->defaultsort
= (int)$DB->get_field('data_fields', 'id', array('dataid'=>$this->module
->id
, 'name'=>$settings->defaultsort
));
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);
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
2684 public function cleanup() {
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) {
2700 'data_preset_upload_importer is deprecated. Please use mod\\data\\local\\importer\\preset_upload_importer instead',
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
{
2727 public function __construct($course, $cm, $module, $fullname) {
2731 'data_preset_existing_importer is deprecated. Please use mod\\data\\local\\importer\\preset_existing_importer instead',
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
;
2753 * @param object $course
2754 * @param int $userid
2755 * @param string $shortname
2758 function data_preset_path($course, $userid, $shortname) {
2761 $context = context_course
::instance($course->id
);
2763 $userid = (int)$userid;
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;
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.
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
2813 * @param int $courseid
2814 * @param string $type optional type
2816 function data_reset_gradebook($courseid, $type='') {
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.
2836 * @param object $data the data submitted from the reset course.
2837 * @return array status array
2839 function data_reset_userdata($data) {
2841 require_once($CFG->libdir
.'/filelib.php');
2842 require_once($CFG->dirroot
.'/rating/lib.php');
2844 $componentstr = get_string('modulenameplural', 'data');
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
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)) {
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();
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
)) {
2910 if (!$cm = get_coursemodule_from_instance('data', $record->dataid
)) {
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
));
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)) {
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)) {
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.
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);
2994 * Returns all other caps used in module
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) {
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 ////////////////////////////////////////////////////////////////////////////////
3028 ////////////////////////////////////////////////////////////////////////////////
3031 * Lists all browsable file areas
3035 * @param stdClass $course course object
3036 * @param stdClass $cm course module object
3037 * @param stdClass $context context object
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
) {
3065 if (!isset($areas[$filearea])) {
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))) {
3078 if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid
))) {
3082 if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid
))) {
3086 if (!$data = $DB->get_record('data', array('id'=>$field->dataid
))) {
3091 if ($data->approval
and !$record->approved
and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
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
)) {
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)) {
3113 $fs = get_file_storage();
3114 if (!($storedfile = $fs->get_file($context->id
, 'mod_data', $filearea, $itemid, $filepath, $filename))) {
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
) {
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 ;-)
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()) {
3146 if ($context->contextlevel
!= CONTEXT_MODULE
) {
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))) {
3159 if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid
))) {
3163 if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid
))) {
3167 if (!$data = $DB->get_record('data', array('id'=>$field->dataid
))) {
3171 if ($data->id
!= $cm->instance
) {
3172 // hacker attempt - context does not match the contentid
3177 if ($data->approval
and !$record->approved
and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
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
)) {
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)) {
3200 $fs = get_file_storage();
3201 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
3205 // finally send the file
3206 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
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
)));
3233 $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance
, 'rid'=>$rid)));
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');
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
)) {
3281 if ($currenttab == 'list') {
3282 $defaultemplate = 'listtemplate';
3283 } else if ($currenttab == 'add') {
3284 $defaultemplate = 'addtemplate';
3285 } else if ($currenttab == 'asearch') {
3286 $defaultemplate = 'asearchtemplate';
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
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) {
3342 'data_presets_generate_xml() is deprecated. Please use the protected preset::generate_preset_xml() instead.',
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
3379 * Capability check has been done in comment->check_permissions(), we
3380 * don't need to do it again here.
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
3394 function data_comment_permissions($comment_param) {
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);
3405 return array('post'=>false, 'view'=>false);
3410 * Validate comment parameter before perform other comments actions
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
3424 function data_comment_validate($comment_param) {
3426 // validate comment area
3427 if ($comment_param->commentarea
!= 'database_entry') {
3428 throw new comment_exception('invalidcommentarea');
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
);
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');
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');
3479 throw new comment_exception('invalidcommentid');
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) {
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);
3514 $params = array('dataid' => $dataid);
3516 $initsql .= ' GROUP BY r.id';
3517 $initrecord = $DB->get_recordset_sql($initsql, $params);
3519 foreach ($initrecord as $data) {
3520 $idarray[] = $data->id
;
3522 // Close the record set and free up resources.
3523 $initrecord->close();
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.
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.
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) {
3566 $searchcriteria = $alias; // Keep the criteria.
3567 $nestsearch = $searcharray[$alias];
3568 // searching for content outside of mdl_data_content
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
3580 ON u.id = r.userid ';
3581 $nestwhere = 'WHERE r.dataid = :dataid
3582 AND c' . $alias .'.recordid ' . $insql . '
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
)) {
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;
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();
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) {
3639 $userfieldsapi = \core_user\fields
::for_userpic()->excluding('id');
3640 $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects
;
3643 $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . '
3644 FROM {data_content} c,
3647 $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, u.firstname, u.lastname, ' . $namefields;
3649 // Sorting through 'Other' criteria
3653 $sortcontentfull = "u.lastname";
3655 case DATA_FIRSTNAME
:
3656 $sortcontentfull = "u.firstname";
3659 $sortcontentfull = "r.approved";
3661 case DATA_TIMEMODIFIED
:
3662 $sortcontentfull = "r.timemodified";
3664 case DATA_TIMEADDED
:
3666 $sortcontentfull = "r.timecreated";
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 . '
3677 FROM {data_content} c,
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
);
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;
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) {
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)) {
3733 $userid = $preset instanceof preset ?
$preset->get_userid() : $preset->userid
;
3734 if ($userid == $USER->id
) {
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) {
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,
3778 'dataid' => $deleterecord->dataid
3781 $event->add_record_snapshot('data_records', $deleterecord);
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);
3796 * Check for required fields, and build a list of fields to be updated in a
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.
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) {
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;
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;
3887 // The form is empty.
3888 $result->generalnotifications
[] = get_string('emptyaddform', 'data');
3891 $result->validated
= $requiredfieldsfilled && !$emptyform && $fieldsvalidated;
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).
3908 function data_refresh_events($courseid = 0, $instance = null, $cm = null) {
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);
3922 if (! $data = $DB->get_records("data", array("course" => $courseid))) {
3926 if (! $data = $DB->get_records("data")) {
3931 foreach ($data as $datum) {
3932 data_set_events($datum);
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
);
3949 $config = new stdClass();
3952 if ($key === null) {
3956 if (property_exists($config, $key)) {
3957 return $config->$key;
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.
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.
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
);
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
4015 function data_view($data, $course, $cm, $context) {
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.
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);
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() {
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
4066 function data_check_updates_since(cm_info
$cm, $from, $filter = array()) {
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
);
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
) {
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);
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,
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.
4128 if (!empty($cm->customdata
['timeavailableto']) && $cm->customdata
['timeavailableto'] < $now) {
4129 // The module has closed so the user can no longer submit anything.
4133 // The module is actionable if we don't have a start time or the start time is
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
)),
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) {
4159 $dbparams = ['id' => $coursemodule->instance
];
4160 $fields = 'id, name, intro, introformat, completionentries, timeavailablefrom, timeavailableto';
4161 if (!$data = $DB->get_record('data', $dbparams, $fields)) {
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
;
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
) {
4202 foreach ($cm->customdata
['customcompletionrules'] as $key => $val) {
4204 case 'completionentries':
4206 $descriptions[] = get_string('completionentriesdesc', 'data', $val);
4213 return $descriptions;
4217 * This function calculates the minimum and maximum cutoff values for the timestart of
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
4237 function mod_data_core_calendar_get_valid_event_timestart_range(\calendar_event
$event, \stdClass
$instance) {
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
)) {
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
)) {
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) {
4278 if (empty($event->instance
) ||
$event->modulename
!= 'data') {
4282 if ($event->instance
!= $data->id
) {
4286 if (!in_array($event->eventtype
, [DATA_EVENT_TYPE_OPEN
, DATA_EVENT_TYPE_CLOSE
])) {
4290 $courseid = $event->courseid
;
4291 $modulename = $event->modulename
;
4292 $instanceid = $event->instance
;
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)) {
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();
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
;
4323 $data->timemodified
= time();
4324 $DB->update_record('data', $data);
4325 $event = \core\event\course_module_updated
::create_from_cm($coursemodule, $context);
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';
4343 case DATA_EVENT_TYPE_CLOSE
:
4344 $identifier = 'calendarend';
4347 return get_string('requiresaction', 'calendar', $modulename);
4350 return get_string($identifier, 'data', $modulename);