Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
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/>.
16
 
17
/**
18
 * @package   mod_data
19
 * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
20
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21
 */
22
 
23
use mod_data\manager;
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
// Some constants
28
define ('DATA_MAX_ENTRIES', 50);
29
define ('DATA_PERPAGE_SINGLE', 1);
30
 
31
define ('DATA_FIRSTNAME', -1);
32
define ('DATA_LASTNAME', -2);
33
define ('DATA_APPROVED', -3);
34
define ('DATA_TIMEADDED', 0);
35
define ('DATA_TIMEMODIFIED', -4);
36
define ('DATA_TAGS', -5);
37
 
38
define ('DATA_CAP_EXPORT', 'mod/data:viewalluserpresets');
39
// Users having assigned the default role "Non-editing teacher" can export database records
40
// Using the mod/data capability "viewalluserpresets" existing in Moodle 1.9.x.
41
// In Moodle >= 2, new roles may be introduced and used instead.
42
 
43
define('DATA_PRESET_COMPONENT', 'mod_data');
44
define('DATA_PRESET_FILEAREA', 'site_presets');
45
define('DATA_PRESET_CONTEXT', SYSCONTEXTID);
46
 
47
define('DATA_EVENT_TYPE_OPEN', 'open');
48
define('DATA_EVENT_TYPE_CLOSE', 'close');
49
 
50
require_once(__DIR__ . '/deprecatedlib.php');
51
 
52
/**
53
 * @package   mod_data
54
 * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
55
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
56
 */
57
class data_field_base {     // Base class for Database Field Types (see field/*/field.class.php)
58
 
59
    /** @var string Subclasses must override the type with their name */
60
    var $type = 'unknown';
61
    /** @var object The database object that this field belongs to */
62
    var $data = NULL;
63
    /** @var object The field object itself, if we know it */
64
    var $field = NULL;
65
    /** @var int Width of the icon for this fieldtype */
66
    var $iconwidth = 16;
67
    /** @var int Width of the icon for this fieldtype */
68
    var $iconheight = 16;
69
    /** @var object course module or cmifno */
70
    var $cm;
71
    /** @var object activity context */
72
    var $context;
73
    /** @var priority for globalsearch indexing */
74
    protected static $priority = self::NO_PRIORITY;
75
    /** priority value for invalid fields regarding indexing */
76
    const NO_PRIORITY = 0;
77
    /** priority value for minimum priority */
78
    const MIN_PRIORITY = 1;
79
    /** priority value for low priority */
80
    const LOW_PRIORITY = 2;
81
    /** priority value for high priority */
82
    const HIGH_PRIORITY = 3;
83
    /** priority value for maximum priority */
84
    const MAX_PRIORITY = 4;
85
 
86
    /** @var bool whether the field is used in preview mode. */
87
    protected $preview = false;
88
 
89
    /**
90
     * Constructor function
91
     *
92
     * @global object
93
     * @uses CONTEXT_MODULE
94
     * @param int $field
95
     * @param int $data
96
     * @param int $cm
97
     */
98
    function __construct($field=0, $data=0, $cm=0) {   // Field or data or both, each can be id or object
99
        global $DB;
100
 
101
        if (empty($field) && empty($data)) {
102
            throw new \moodle_exception('missingfield', 'data');
103
        }
104
 
105
        if (!empty($field)) {
106
            if (is_object($field)) {
107
                $this->field = $field;  // Programmer knows what they are doing, we hope
108
            } else if (!$this->field = $DB->get_record('data_fields', array('id'=>$field))) {
109
                throw new \moodle_exception('invalidfieldid', 'data');
110
            }
111
            if (empty($data)) {
112
                if (!$this->data = $DB->get_record('data', array('id'=>$this->field->dataid))) {
113
                    throw new \moodle_exception('invalidid', 'data');
114
                }
115
            }
116
        }
117
 
118
        if (empty($this->data)) {         // We need to define this properly
119
            if (!empty($data)) {
120
                if (is_object($data)) {
121
                    $this->data = $data;  // Programmer knows what they are doing, we hope
122
                } else if (!$this->data = $DB->get_record('data', array('id'=>$data))) {
123
                    throw new \moodle_exception('invalidid', 'data');
124
                }
125
            } else {                      // No way to define it!
126
                throw new \moodle_exception('missingdata', 'data');
127
            }
128
        }
129
 
130
        if ($cm) {
131
            $this->cm = $cm;
132
        } else {
133
            $this->cm = get_coursemodule_from_instance('data', $this->data->id);
134
        }
135
 
136
        if (empty($this->field)) {         // We need to define some default values
137
            $this->define_default_field();
138
        }
139
 
140
        $this->context = context_module::instance($this->cm->id);
141
    }
142
 
143
    /**
144
     * Return the field type name.
145
     *
146
     * @return string the filed type.
147
     */
148
    public function get_name(): string {
149
        return $this->field->name;
150
    }
151
 
152
    /**
153
     * Return if the field type supports preview.
154
     *
155
     * Fields without a preview cannot be displayed in the preset preview.
156
     *
157
     * @return bool if the plugin supports preview.
158
     */
159
    public function supports_preview(): bool {
160
        return false;
161
    }
162
 
163
    /**
164
     * Generate a fake data_content for this field to be used in preset previews.
165
     *
166
     * Data plugins must override this method and support_preview in order to enable
167
     * preset preview for this field.
168
     *
169
     * @param int $recordid the fake record id
170
     * @return stdClass the fake record
171
     */
172
    public function get_data_content_preview(int $recordid): stdClass {
173
        $message = get_string('nopreviewavailable', 'mod_data', $this->field->name);
174
        return (object)[
175
            'id' => 0,
176
            'fieldid' => $this->field->id,
177
            'recordid' => $recordid,
178
            'content' => "<span class=\"nopreview\">$message</span>",
179
            'content1' => null,
180
            'content2' => null,
181
            'content3' => null,
182
            'content4' => null,
183
        ];
184
    }
185
 
186
    /**
187
     * Set the field to preview mode.
188
     *
189
     * @param bool $preview the new preview value
190
     */
191
    public function set_preview(bool $preview) {
192
        $this->preview = $preview;
193
    }
194
 
195
    /**
196
     * Get the field preview value.
197
     *
198
     * @return bool
199
     */
200
    public function get_preview(): bool {
201
        return $this->preview;
202
    }
203
 
204
 
205
    /**
206
     * This field just sets up a default field object
207
     *
208
     * @return bool
209
     */
210
    function define_default_field() {
211
        global $OUTPUT;
212
        if (empty($this->data->id)) {
213
            echo $OUTPUT->notification('Programmer error: dataid not defined in field class');
214
        }
215
        $this->field = new stdClass();
216
        $this->field->id = 0;
217
        $this->field->dataid = $this->data->id;
218
        $this->field->type   = $this->type;
219
        $this->field->param1 = '';
220
        $this->field->param2 = '';
221
        $this->field->param3 = '';
222
        $this->field->name = '';
223
        $this->field->description = '';
224
        $this->field->required = false;
225
 
226
        return true;
227
    }
228
 
229
    /**
230
     * Set up the field object according to data in an object.  Now is the time to clean it!
231
     *
232
     * @return bool
233
     */
234
    function define_field($data) {
235
        $this->field->type        = $this->type;
236
        $this->field->dataid      = $this->data->id;
237
 
238
        $this->field->name        = trim($data->name);
239
        $this->field->description = trim($data->description);
240
        $this->field->required    = !empty($data->required) ? 1 : 0;
241
 
242
        if (isset($data->param1)) {
243
            $this->field->param1 = trim($data->param1);
244
        }
245
        if (isset($data->param2)) {
246
            $this->field->param2 = trim($data->param2);
247
        }
248
        if (isset($data->param3)) {
249
            $this->field->param3 = trim($data->param3);
250
        }
251
        if (isset($data->param4)) {
252
            $this->field->param4 = trim($data->param4);
253
        }
254
        if (isset($data->param5)) {
255
            $this->field->param5 = trim($data->param5);
256
        }
257
 
258
        return true;
259
    }
260
 
261
    /**
262
     * Insert a new field in the database
263
     * We assume the field object is already defined as $this->field
264
     *
265
     * @global object
266
     * @return bool
267
     */
268
    function insert_field() {
269
        global $DB, $OUTPUT;
270
 
271
        if (empty($this->field)) {
272
            echo $OUTPUT->notification('Programmer error: Field has not been defined yet!  See define_field()');
273
            return false;
274
        }
275
 
276
        $this->field->id = $DB->insert_record('data_fields',$this->field);
277
 
278
        // Trigger an event for creating this field.
279
        $event = \mod_data\event\field_created::create(array(
280
            'objectid' => $this->field->id,
281
            'context' => $this->context,
282
            'other' => array(
283
                'fieldname' => $this->field->name,
284
                'dataid' => $this->data->id
285
            )
286
        ));
287
        $event->trigger();
288
 
289
        return true;
290
    }
291
 
292
 
293
    /**
294
     * Update a field in the database
295
     *
296
     * @global object
297
     * @return bool
298
     */
299
    function update_field() {
300
        global $DB;
301
 
302
        $DB->update_record('data_fields', $this->field);
303
 
304
        // Trigger an event for updating this field.
305
        $event = \mod_data\event\field_updated::create(array(
306
            'objectid' => $this->field->id,
307
            'context' => $this->context,
308
            'other' => array(
309
                'fieldname' => $this->field->name,
310
                'dataid' => $this->data->id
311
            )
312
        ));
313
        $event->trigger();
314
 
315
        return true;
316
    }
317
 
318
    /**
319
     * Delete a field completely
320
     *
321
     * @global object
322
     * @return bool
323
     */
324
    function delete_field() {
325
        global $DB;
326
 
327
        if (!empty($this->field->id)) {
328
            $manager = manager::create_from_instance($this->data);
329
 
330
            // Get the field before we delete it.
331
            $field = $DB->get_record('data_fields', array('id' => $this->field->id));
332
 
333
            $this->delete_content();
334
            $DB->delete_records('data_fields', array('id'=>$this->field->id));
335
 
336
            // Trigger an event for deleting this field.
337
            $event = \mod_data\event\field_deleted::create(array(
338
                'objectid' => $this->field->id,
339
                'context' => $this->context,
340
                'other' => array(
341
                    'fieldname' => $this->field->name,
342
                    'dataid' => $this->data->id
343
                 )
344
            ));
345
 
346
            if (!$manager->has_fields() && $manager->has_records()) {
347
                $DB->delete_records('data_records', ['dataid' => $this->data->id]);
348
            }
349
 
350
            $event->add_record_snapshot('data_fields', $field);
351
            $event->trigger();
352
        }
353
 
354
        return true;
355
    }
356
 
357
    /**
358
     * Print the relevant form element in the ADD template for this field
359
     *
360
     * @global object
361
     * @param int $recordid
362
     * @return string
363
     */
364
    function display_add_field($recordid=0, $formdata=null) {
365
        global $DB, $OUTPUT;
366
 
367
        if ($formdata) {
368
            $fieldname = 'field_' . $this->field->id;
369
            $content = $formdata->$fieldname;
370
        } else if ($recordid) {
371
            $content = $DB->get_field('data_content', 'content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid));
372
        } else {
373
            $content = '';
374
        }
375
 
376
        // beware get_field returns false for new, empty records MDL-18567
377
        if ($content===false) {
378
            $content='';
379
        }
380
 
381
        $str = '<div title="' . s($this->field->description) . '">';
382
        $str .= '<label for="field_'.$this->field->id.'"><span class="accesshide">'.s($this->field->name).'</span>';
383
        if ($this->field->required) {
384
            $image = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
385
            $str .= html_writer::div($image, 'inline-req');
386
        }
387
        $str .= '</label><input class="basefieldinput form-control d-inline mod-data-input" ' .
388
                'type="text" name="field_' . $this->field->id . '" ' .
389
                'id="field_' . $this->field->id . '" value="' . s($content) . '" />';
390
        $str .= '</div>';
391
 
392
        return $str;
393
    }
394
 
395
    /**
396
     * Print the relevant form element to define the attributes for this field
397
     * viewable by teachers only.
398
     *
399
     * @global object
400
     * @global object
401
     * @return void Output is echo'd
402
     */
403
    function display_edit_field() {
404
        global $CFG, $DB, $OUTPUT;
405
 
406
        if (empty($this->field)) {   // No field has been defined yet, try and make one
407
            $this->define_default_field();
408
        }
409
 
410
        // Throw an exception if field type doen't exist. Anyway user should never access to edit a field with an unknown fieldtype.
411
        if ($this->type === 'unknown') {
412
            throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name]));
413
        }
414
 
415
        echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide');
416
 
417
        echo '<form id="editfield" action="'.$CFG->wwwroot.'/mod/data/field.php" method="post">'."\n";
418
        echo '<input type="hidden" name="d" value="'.$this->data->id.'" />'."\n";
419
        if (empty($this->field->id)) {
420
            echo '<input type="hidden" name="mode" value="add" />'."\n";
421
        } else {
422
            echo '<input type="hidden" name="fid" value="'.$this->field->id.'" />'."\n";
423
            echo '<input type="hidden" name="mode" value="update" />'."\n";
424
        }
425
        echo '<input type="hidden" name="type" value="'.$this->type.'" />'."\n";
426
        echo '<input name="sesskey" value="'.sesskey().'" type="hidden" />'."\n";
427
 
428
        echo $OUTPUT->heading($this->name(), 3);
429
 
430
        $filepath = $CFG->dirroot . '/mod/data/field/' . $this->type . '/mod.html';
431
        $templatename = 'datafield_' . $this->type . '/' . $this->type;
432
 
433
        try {
434
            $templatefilepath = \core\output\mustache_template_finder::get_template_filepath($templatename);
435
            $templatefileexists = true;
436
        } catch (moodle_exception $e) {
437
            if (!file_exists($filepath)) {
438
                // Neither file exists.
439
                throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name]));
440
            }
441
            $templatefileexists = false;
442
        }
443
 
444
        if ($templatefileexists) {
445
            // Give out templated Bootstrap formatted form fields.
446
            $data = $this->get_field_params();
447
            echo $OUTPUT->render_from_template($templatename, $data);
448
        } else {
449
            // Fall back to display mod.html for backward compatibility.
450
            require_once($filepath);
451
        }
452
 
453
        $actionbuttons = html_writer::start_div();
454
        $actionbuttons .= html_writer::tag('input', null, [
455
            'type' => 'submit',
456
            'name' => 'cancel',
457
            'value' => get_string('cancel'),
458
            'class' => 'btn btn-secondary mx-1'
459
        ]);
460
        $actionbuttons .= html_writer::tag('input', null, [
461
            'type' => 'submit',
462
            'value' => get_string('save'),
463
            'class' => 'btn btn-primary mx-1'
464
        ]);
465
        $actionbuttons .= html_writer::end_div();
466
 
467
        $stickyfooter = new core\output\sticky_footer($actionbuttons);
468
        echo $OUTPUT->render($stickyfooter);
469
 
470
        echo '</form>';
471
 
472
        echo $OUTPUT->box_end();
473
    }
474
 
475
    /**
476
     * Validates params of fieldinput data. Overwrite to validate fieldtype specific data.
477
     *
478
     * You are expected to return an array like ['paramname' => 'Error message for paramname param'] if there is an error,
479
     * return an empty array if everything is fine.
480
     *
481
     * @param stdClass $fieldinput The field input data to check
482
     * @return array $errors if empty validation was fine, otherwise contains one or more error messages
483
     */
484
    public function validate(stdClass $fieldinput): array {
485
        return [];
486
    }
487
 
488
    /**
489
     * Return the data_content of the field, or generate it if it is in preview mode.
490
     *
491
     * @param int $recordid the record id
492
     * @return stdClass|bool the record data or false if none
493
     */
494
    protected function get_data_content(int $recordid) {
495
        global $DB;
496
        if ($this->preview) {
497
            return $this->get_data_content_preview($recordid);
498
        }
499
        return $DB->get_record(
500
            'data_content',
501
            ['fieldid' => $this->field->id, 'recordid' => $recordid]
502
        );
503
    }
504
 
505
    /**
506
     * Display the content of the field in browse mode
507
     *
508
     * @global object
509
     * @param int $recordid
510
     * @param object $template
511
     * @return bool|string
512
     */
513
    function display_browse_field($recordid, $template) {
514
        $content = $this->get_data_content($recordid);
515
        if (!$content || !isset($content->content)) {
516
            return '';
517
        }
518
        $options = new stdClass();
519
        if ($this->field->param1 == '1') {
520
            // We are autolinking this field, so disable linking within us.
521
            $options->filter = false;
522
        }
523
        $options->para = false;
1441 ariadna 524
        $format = !empty($content->content1) && !empty(trim($content->content1)) ? $content->content1 : null;
525
        $str = format_text($content->content, $format, $options);
1 efrain 526
        return $str;
527
    }
528
 
529
    /**
530
     * Update the content of one data field in the data_content table
531
     * @global object
532
     * @param int $recordid
533
     * @param mixed $value
534
     * @param string $name
535
     * @return bool
536
     */
537
    function update_content($recordid, $value, $name=''){
538
        global $DB;
539
 
540
        $content = new stdClass();
541
        $content->fieldid = $this->field->id;
542
        $content->recordid = $recordid;
543
        $content->content = clean_param($value, PARAM_NOTAGS);
544
 
545
        if ($oldcontent = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
546
            $content->id = $oldcontent->id;
547
            return $DB->update_record('data_content', $content);
548
        } else {
549
            return $DB->insert_record('data_content', $content);
550
        }
551
    }
552
 
553
    /**
554
     * Delete all content associated with the field
555
     *
556
     * @global object
557
     * @param int $recordid
558
     * @return bool
559
     */
560
    function delete_content($recordid=0) {
561
        global $DB;
562
 
563
        if ($recordid) {
564
            $conditions = array('fieldid'=>$this->field->id, 'recordid'=>$recordid);
565
        } else {
566
            $conditions = array('fieldid'=>$this->field->id);
567
        }
568
 
569
        $rs = $DB->get_recordset('data_content', $conditions);
570
        if ($rs->valid()) {
571
            $fs = get_file_storage();
572
            foreach ($rs as $content) {
573
                $fs->delete_area_files($this->context->id, 'mod_data', 'content', $content->id);
574
            }
575
        }
576
        $rs->close();
577
 
578
        return $DB->delete_records('data_content', $conditions);
579
    }
580
 
581
    /**
582
     * Check if a field from an add form is empty
583
     *
584
     * @param mixed $value
585
     * @param mixed $name
586
     * @return bool
587
     */
588
    function notemptyfield($value, $name) {
589
        return !empty($value);
590
    }
591
 
592
    /**
593
     * Just in case a field needs to print something before the whole form
594
     */
595
    function print_before_form() {
596
    }
597
 
598
    /**
599
     * Just in case a field needs to print something after the whole form
600
     */
601
    function print_after_form() {
602
    }
603
 
604
 
605
    /**
606
     * Returns the sortable field for the content. By default, it's just content
607
     * but for some plugins, it could be content 1 - content4
608
     *
609
     * @return string
610
     */
611
    function get_sort_field() {
612
        return 'content';
613
    }
614
 
615
    /**
616
     * Returns the SQL needed to refer to the column.  Some fields may need to CAST() etc.
617
     *
618
     * @param string $fieldname
619
     * @return string $fieldname
620
     */
621
    function get_sort_sql($fieldname) {
622
        return $fieldname;
623
    }
624
 
625
    /**
626
     * Returns the name/type of the field
627
     *
628
     * @return string
629
     */
630
    function name() {
631
        return get_string('fieldtypelabel', "datafield_$this->type");
632
    }
633
 
634
    /**
635
     * Prints the respective type icon
636
     *
637
     * @global object
638
     * @return string
639
     */
640
    function image() {
641
        global $OUTPUT;
642
 
1441 ariadna 643
        return $OUTPUT->image_icon('icon', $this->type, 'datafield_' . $this->type);
1 efrain 644
    }
645
 
646
    /**
647
     * Per default, it is assumed that fields support text exporting.
648
     * Override this (return false) on fields not supporting text exporting.
649
     *
650
     * @return bool true
651
     */
652
    function text_export_supported() {
653
        return true;
654
    }
655
 
656
    /**
657
     * Per default, it is assumed that fields do not support file exporting. Override this (return true)
658
     * on fields supporting file export. You will also have to implement export_file_value().
659
     *
660
     * @return bool true if field will export a file, false otherwise
661
     */
662
    public function file_export_supported(): bool {
663
        return false;
664
    }
665
 
666
    /**
667
     * Per default, does not return a file (just null).
668
     * Override this in fields class, if you want your field to export a file content.
669
     * In case you are exporting a file value, export_text_value() should return the corresponding file name.
670
     *
671
     * @param stdClass $record
672
     * @return null|string the file content as string or null, if no file content is being provided
673
     */
674
    public function export_file_value(stdClass $record): null|string {
675
        return null;
676
    }
677
 
678
    /**
679
     * Per default, a field does not support the import of files.
680
     *
681
     * A field type can overwrite this function and return true. In this case it also has to implement the function
682
     * import_file_value().
683
     *
684
     * @return false means file imports are not supported
685
     */
686
    public function file_import_supported(): bool {
687
        return false;
688
    }
689
 
690
    /**
691
     * Returns a stored_file object for exporting a file of a given record.
692
     *
693
     * @param int $contentid content id
694
     * @param string $filecontent the content of the file as string
695
     * @param string $filename the filename the file should have
696
     */
697
    public function import_file_value(int $contentid, string $filecontent, string $filename): void {
698
        return;
699
    }
700
 
701
    /**
702
     * Per default, return the record's text value only from the "content" field.
703
     * Override this in fields class if necessary.
704
     *
705
     * @param stdClass $record
706
     * @return string
707
     */
708
    public function export_text_value(stdClass $record) {
709
        if ($this->text_export_supported()) {
710
            return $record->content;
711
        }
712
        return '';
713
    }
714
 
715
    /**
716
     * @param string $relativepath
717
     * @return bool false
718
     */
719
    function file_ok($relativepath) {
720
        return false;
721
    }
722
 
723
    /**
724
     * Returns the priority for being indexed by globalsearch
725
     *
726
     * @return int
727
     */
728
    public static function get_priority() {
729
        return static::$priority;
730
    }
731
 
732
    /**
733
     * Returns the presentable string value for a field content.
734
     *
735
     * The returned string should be plain text.
736
     *
737
     * @param stdClass $content
738
     * @return string
739
     */
740
    public static function get_content_value($content) {
741
        return trim($content->content, "\r\n ");
742
    }
743
 
744
    /**
745
     * Return the plugin configs for external functions,
746
     * in some cases the configs will need formatting or be returned only if the current user has some capabilities enabled.
747
     *
748
     * @return array the list of config parameters
749
     * @since Moodle 3.3
750
     */
751
    public function get_config_for_external() {
752
        // Return all the field configs to null (maybe there is a private key for a service or something similar there).
753
        $configs = [];
754
        for ($i = 1; $i <= 10; $i++) {
755
            $configs["param$i"] = null;
756
        }
757
        return $configs;
758
    }
759
 
760
    /**
761
     * Function to let field define their parameters.
762
     *
763
     * This method that should be overridden by the datafield plugins
764
     * when they need to define their data.
765
     *
766
     * @return array
767
     */
768
    protected function get_field_params(): array {
769
        // Name and description of the field.
770
        $data = [
771
            'name' => $this->field->name,
772
            'description' => $this->field->description,
773
        ];
774
 
775
        // Whether the field is required.
776
        if (isset($this->field->required)) {
777
            $data['required'] = $this->field->required;
778
        }
779
 
780
        // Add all the field parameters.
781
        for ($i = 1; $i <= 10; $i++) {
782
            if (isset($this->field->{"param$i"})) {
783
                $data["param$i"] = $this->field->{"param$i"};
784
            }
785
        }
786
 
787
        return $data;
788
    }
789
 
790
}
791
 
792
 
793
/**
794
 * Given a template and a dataid, generate a default case template
795
 *
796
 * @param stdClass $data the mod_data record.
797
 * @param string $template the template name
798
 * @param int $recordid the entry record
799
 * @param bool $form print a form instead of data
800
 * @param bool $update if the function update the $data object or not
801
 * @return string the template content or an empty string if no content is available (for instance, when database has no fields).
802
 */
803
function data_generate_default_template(&$data, $template, $recordid = 0, $form = false, $update = true) {
804
    global $DB;
805
 
806
    if (!$data || !$template) {
807
        return '';
808
    }
809
 
810
    // These templates are empty by default (they have no content).
811
    $emptytemplates = [
812
        'csstemplate',
813
        'jstemplate',
814
        'listtemplateheader',
815
        'listtemplatefooter',
816
        'rsstitletemplate',
817
    ];
818
    if (in_array($template, $emptytemplates)) {
819
        return '';
820
    }
821
 
822
    $manager = manager::create_from_instance($data);
823
    if (empty($manager->get_fields())) {
824
        // No template will be returned if there are no fields.
825
        return '';
826
    }
827
 
828
    $templateclass = \mod_data\template::create_default_template($manager, $template, $form);
829
    $templatecontent = $templateclass->get_template_content();
830
 
831
    if ($update) {
832
        // Update the database instance.
833
        $newdata = new stdClass();
834
        $newdata->id = $data->id;
835
        $newdata->{$template} = $templatecontent;
836
        $DB->update_record('data', $newdata);
837
        $data->{$template} = $templatecontent;
838
    }
839
 
840
    return $templatecontent;
841
}
842
 
843
/**
844
 * Build the form elements to manage tags for a record.
845
 *
846
 * @param int|bool $recordid
847
 * @param string[] $selected raw tag names
848
 * @return string
849
 */
850
function data_generate_tag_form($recordid = false, $selected = []) {
851
    global $CFG, $DB, $OUTPUT, $PAGE;
852
 
853
    $tagtypestoshow = \core_tag_area::get_showstandard('mod_data', 'data_records');
854
    $showstandard = ($tagtypestoshow != core_tag_tag::HIDE_STANDARD);
855
    $typenewtags = ($tagtypestoshow != core_tag_tag::STANDARD_ONLY);
856
 
857
    $str = html_writer::start_tag('div', array('class' => 'datatagcontrol'));
858
 
859
    $namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
860
 
861
    $tagcollid = \core_tag_area::get_collection('mod_data', 'data_records');
862
    $tags = [];
863
    $selectedtags = [];
864
 
865
    if ($showstandard) {
866
        $tags += $DB->get_records_menu('tag', array('isstandard' => 1, 'tagcollid' => $tagcollid),
867
            $namefield, 'id,' . $namefield . ' as fieldname');
868
    }
869
 
870
    if ($recordid) {
871
        $selectedtags += core_tag_tag::get_item_tags_array('mod_data', 'data_records', $recordid);
872
    }
873
 
874
    if (!empty($selected)) {
875
        list($sql, $params) = $DB->get_in_or_equal($selected, SQL_PARAMS_NAMED);
876
        $params['tagcollid'] = $tagcollid;
877
        $sql = "SELECT id, $namefield FROM {tag} WHERE tagcollid = :tagcollid AND rawname $sql";
878
        $selectedtags += $DB->get_records_sql_menu($sql, $params);
879
    }
880
 
881
    $tags += $selectedtags;
882
 
1441 ariadna 883
    $str .= '<select class="form-select" name="tags[]" id="tags" multiple>';
1 efrain 884
    foreach ($tags as $tagid => $tag) {
885
        $selected = key_exists($tagid, $selectedtags) ? 'selected' : '';
886
        $str .= "<option value='$tag' $selected>$tag</option>";
887
    }
888
    $str .= '</select>';
889
 
890
    if (has_capability('moodle/tag:manage', context_system::instance()) && $showstandard) {
891
        $url = new moodle_url('/tag/manage.php', array('tc' => core_tag_area::get_collection('mod_data',
892
            'data_records')));
893
        $str .= ' ' . $OUTPUT->action_link($url, get_string('managestandardtags', 'tag'));
894
    }
895
 
896
    $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array(
897
            '#tags',
898
            $typenewtags,
899
            '',
900
            get_string('entertags', 'tag'),
901
            false,
902
            $showstandard,
903
            get_string('noselection', 'form')
904
        )
905
    );
906
 
907
    $str .= html_writer::end_tag('div');
908
 
909
    return $str;
910
}
911
 
912
 
913
/**
914
 * Search for a field name and replaces it with another one in all the
915
 * form templates. Set $newfieldname as '' if you want to delete the
916
 * field from the form.
917
 *
918
 * @global object
919
 * @param object $data
920
 * @param string $searchfieldname
921
 * @param string $newfieldname
922
 * @return bool
923
 */
924
function data_replace_field_in_templates($data, $searchfieldname, $newfieldname) {
925
    global $DB;
926
 
927
    $newdata = (object)['id' => $data->id];
928
    $update = false;
929
    $templates = ['listtemplate', 'singletemplate', 'asearchtemplate', 'addtemplate', 'rsstemplate'];
930
    foreach ($templates as $templatename) {
931
        if (empty($data->$templatename)) {
932
            continue;
933
        }
934
        $search = [
935
            '[[' . $searchfieldname . ']]',
936
            '[[' . $searchfieldname . '#id]]',
937
            '[[' . $searchfieldname . '#name]]',
938
            '[[' . $searchfieldname . '#description]]',
939
        ];
940
        if (empty($newfieldname)) {
941
            $replace = ['', '', '', ''];
942
        } else {
943
            $replace = [
944
                '[[' . $newfieldname . ']]',
945
                '[[' . $newfieldname . '#id]]',
946
                '[[' . $newfieldname . '#name]]',
947
                '[[' . $newfieldname . '#description]]',
948
            ];
949
        }
950
        $newdata->{$templatename} = str_ireplace($search, $replace, $data->{$templatename} ?? '');
951
        $update = true;
952
    }
953
    if (!$update) {
954
        return true;
955
    }
956
    return $DB->update_record('data', $newdata);
957
}
958
 
959
 
960
/**
961
 * Appends a new field at the end of the form template.
962
 *
963
 * @global object
964
 * @param object $data
965
 * @param string $newfieldname
966
 * @return bool if the field has been added or not
967
 */
968
function data_append_new_field_to_templates($data, $newfieldname): bool {
969
    global $DB, $OUTPUT;
970
 
971
    $newdata = (object)['id' => $data->id];
972
    $update = false;
973
    $templates = ['singletemplate', 'addtemplate', 'rsstemplate'];
974
    foreach ($templates as $templatename) {
975
        if (empty($data->$templatename)
976
            || strpos($data->$templatename, "[[$newfieldname]]") !== false
977
            || strpos($data->$templatename, "##otherfields##") !== false
978
        ) {
979
            continue;
980
        }
981
        $newdata->$templatename = $data->$templatename;
982
        $fields = [[
983
            'fieldname' => '[[' . $newfieldname . '#name]]',
984
            'fieldcontent' => '[[' . $newfieldname . ']]',
985
        ]];
986
        $newdata->$templatename .= $OUTPUT->render_from_template(
987
            'mod_data/fields_otherfields',
988
            ['fields' => $fields, 'classes' => 'added_field']
989
        );
990
        $update = true;
991
    }
992
    if (!$update) {
993
        return false;
994
    }
995
    return $DB->update_record('data', $newdata);
996
}
997
 
998
 
999
/**
1000
 * given a field name
1001
 * this function creates an instance of the particular subfield class
1002
 *
1003
 * @global object
1004
 * @param string $name
1005
 * @param object $data
1006
 * @return object|bool
1007
 */
1008
function data_get_field_from_name($name, $data){
1009
    global $DB;
1010
 
1011
    $field = $DB->get_record('data_fields', array('name'=>$name, 'dataid'=>$data->id));
1012
 
1013
    if ($field) {
1014
        return data_get_field($field, $data);
1015
    } else {
1016
        return false;
1017
    }
1018
}
1019
 
1020
/**
1021
 * given a field id
1022
 * this function creates an instance of the particular subfield class
1023
 *
1024
 * @global object
1025
 * @param int $fieldid
1026
 * @param object $data
1027
 * @return bool|object
1028
 */
1029
function data_get_field_from_id($fieldid, $data){
1030
    global $DB;
1031
 
1032
    $field = $DB->get_record('data_fields', array('id'=>$fieldid, 'dataid'=>$data->id));
1033
 
1034
    if ($field) {
1035
        return data_get_field($field, $data);
1036
    } else {
1037
        return false;
1038
    }
1039
}
1040
 
1041
/**
1042
 * given a field id
1043
 * this function creates an instance of the particular subfield class
1044
 *
1045
 * @global object
1046
 * @param string $type
1047
 * @param object $data
1048
 * @return object
1049
 */
1050
function data_get_field_new($type, $data) {
1051
    global $CFG;
1052
 
1053
    $type = clean_param($type, PARAM_ALPHA);
1054
    $filepath = $CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php';
1055
    // It should never access this method if the subfield class doesn't exist.
1056
    if (!file_exists($filepath)) {
1057
        throw new \moodle_exception('invalidfieldtype', 'data');
1058
    }
1059
    require_once($filepath);
1060
    $newfield = 'data_field_'.$type;
1061
    $newfield = new $newfield(0, $data);
1062
    return $newfield;
1063
}
1064
 
1065
/**
1066
 * returns a subclass field object given a record of the field, used to
1067
 * invoke plugin methods
1068
 * input: $param $field - record from db
1069
 *
1070
 * @global object
1071
 * @param stdClass $field the field record
1072
 * @param stdClass $data the data instance
1073
 * @param stdClass|null $cm optional course module data
1074
 * @return data_field_base the field object instance or data_field_base if unkown type
1075
 */
1076
function data_get_field(stdClass $field, stdClass $data, ?stdClass $cm=null): data_field_base {
1077
    global $CFG;
1078
    if (!isset($field->type)) {
1079
        return new data_field_base($field);
1080
    }
1081
    $field->type = clean_param($field->type, PARAM_ALPHA);
1082
    $filepath = $CFG->dirroot.'/mod/data/field/'.$field->type.'/field.class.php';
1083
    if (!file_exists($filepath)) {
1084
        return new data_field_base($field);
1085
    }
1086
    require_once($filepath);
1087
    $newfield = 'data_field_'.$field->type;
1088
    $newfield = new $newfield($field, $data, $cm);
1089
    return $newfield;
1090
}
1091
 
1092
 
1093
/**
1094
 * Given record object (or id), returns true if the record belongs to the current user
1095
 *
1096
 * @global object
1097
 * @global object
1098
 * @param mixed $record record object or id
1099
 * @return bool
1100
 */
1101
function data_isowner($record) {
1102
    global $USER, $DB;
1103
 
1104
    if (!isloggedin()) { // perf shortcut
1105
        return false;
1106
    }
1107
 
1108
    if (!is_object($record)) {
1109
        if (!$record = $DB->get_record('data_records', array('id'=>$record))) {
1110
            return false;
1111
        }
1112
    }
1113
 
1114
    return ($record->userid == $USER->id);
1115
}
1116
 
1117
/**
1118
 * has a user reached the max number of entries?
1119
 *
1120
 * @param object $data
1121
 * @return bool
1122
 */
1123
function data_atmaxentries($data){
1124
    if (!$data->maxentries){
1125
        return false;
1126
 
1127
    } else {
1128
        return (data_numentries($data) >= $data->maxentries);
1129
    }
1130
}
1131
 
1132
/**
1133
 * returns the number of entries already made by this user
1134
 *
1135
 * @global object
1136
 * @global object
1137
 * @param object $data
1138
 * @return int
1139
 */
1140
function data_numentries($data, $userid=null) {
1141
    global $USER, $DB;
1142
    if ($userid === null) {
1143
        $userid = $USER->id;
1144
    }
1145
    $sql = 'SELECT COUNT(*) FROM {data_records} WHERE dataid=? AND userid=?';
1146
    return $DB->count_records_sql($sql, array($data->id, $userid));
1147
}
1148
 
1149
/**
1150
 * function that takes in a dataid and adds a record
1151
 * this is used everytime an add template is submitted
1152
 *
1153
 * @global object
1154
 * @global object
1155
 * @param object $data
1156
 * @param int $groupid
1157
 * @param int $userid
1158
 * @param bool $approved If specified, and the user has the capability to approve entries, then this value
1159
 *      will be used as the approved status of the new record
1160
 * @return bool
1161
 */
1162
function data_add_record($data, $groupid = 0, $userid = null, bool $approved = true) {
1163
    global $USER, $DB;
1164
 
1165
    $cm = get_coursemodule_from_instance('data', $data->id);
1166
    $context = context_module::instance($cm->id);
1167
 
1168
    $record = new stdClass();
1169
    $record->userid = $userid ?? $USER->id;
1170
    $record->dataid = $data->id;
1171
    $record->groupid = $groupid;
1172
    $record->timecreated = $record->timemodified = time();
1173
    if (has_capability('mod/data:approve', $context)) {
1174
        $record->approved = $approved;
1175
    } else {
1176
        $record->approved = 0;
1177
    }
1178
    $record->id = $DB->insert_record('data_records', $record);
1179
 
1180
    // Trigger an event for creating this record.
1181
    $event = \mod_data\event\record_created::create(array(
1182
        'objectid' => $record->id,
1183
        'context' => $context,
1184
        'other' => array(
1185
            'dataid' => $data->id
1186
        )
1187
    ));
1188
    $event->trigger();
1189
 
1190
    $course = get_course($cm->course);
1191
    data_update_completion_state($data, $course, $cm);
1192
 
1193
    return $record->id;
1194
}
1195
 
1196
/**
1197
 * check the multple existence any tag in a template
1198
 *
1199
 * check to see if there are 2 or more of the same tag being used.
1200
 *
1201
 * @global object
1202
 * @param int $dataid,
1203
 * @param string $template
1204
 * @return bool
1205
 */
1206
function data_tags_check($dataid, $template) {
1207
    global $DB, $OUTPUT;
1208
 
1209
    // first get all the possible tags
1210
    $fields = $DB->get_records('data_fields', array('dataid'=>$dataid));
1211
    // then we generate strings to replace
1212
    $tagsok = true; // let's be optimistic
1213
    foreach ($fields as $field){
1214
        $pattern="/\[\[" . preg_quote($field->name, '/') . "\]\]/i";
1215
        if (preg_match_all($pattern, $template, $dummy)>1){
1216
            $tagsok = false;
1217
            echo $OUTPUT->notification('[['.$field->name.']] - '.get_string('multipletags','data'));
1218
        }
1219
    }
1220
    // else return true
1221
    return $tagsok;
1222
}
1223
 
1224
/**
1225
 * Adds an instance of a data
1226
 *
1227
 * @param stdClass $data
1228
 * @param mod_data_mod_form $mform
1229
 * @return int intance id
1230
 */
1231
function data_add_instance($data, $mform = null) {
1232
    global $DB, $CFG;
1233
    require_once($CFG->dirroot.'/mod/data/locallib.php');
1234
 
1235
    if (empty($data->assessed)) {
1236
        $data->assessed = 0;
1237
    }
1238
 
1239
    if (empty($data->ratingtime) || empty($data->assessed)) {
1240
        $data->assesstimestart  = 0;
1241
        $data->assesstimefinish = 0;
1242
    }
1243
 
1244
    $data->timemodified = time();
1245
 
1246
    $data->id = $DB->insert_record('data', $data);
1247
 
1248
    // Add calendar events if necessary.
1249
    data_set_events($data);
1250
    if (!empty($data->completionexpected)) {
1251
        \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $data->completionexpected);
1252
    }
1253
 
1254
    data_grade_item_update($data);
1255
 
1256
    return $data->id;
1257
}
1258
 
1259
/**
1260
 * updates an instance of a data
1261
 *
1262
 * @global object
1263
 * @param object $data
1264
 * @return bool
1265
 */
1266
function data_update_instance($data) {
1267
    global $DB, $CFG;
1268
    require_once($CFG->dirroot.'/mod/data/locallib.php');
1269
 
1270
    $data->timemodified = time();
1271
    if (!empty($data->instance)) {
1272
        $data->id = $data->instance;
1273
    }
1274
 
1275
    if (empty($data->assessed)) {
1276
        $data->assessed = 0;
1277
    }
1278
 
1279
    if (empty($data->ratingtime) or empty($data->assessed)) {
1280
        $data->assesstimestart  = 0;
1281
        $data->assesstimefinish = 0;
1282
    }
1283
 
1284
    if (empty($data->notification)) {
1285
        $data->notification = 0;
1286
    }
1287
 
1288
    $DB->update_record('data', $data);
1289
 
1290
    // Add calendar events if necessary.
1291
    data_set_events($data);
1292
    $completionexpected = (!empty($data->completionexpected)) ? $data->completionexpected : null;
1293
    \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $completionexpected);
1294
 
1295
    data_grade_item_update($data);
1296
 
1297
    return true;
1298
 
1299
}
1300
 
1301
/**
1302
 * deletes an instance of a data
1303
 *
1304
 * @global object
1305
 * @param int $id
1306
 * @return bool
1307
 */
1308
function data_delete_instance($id) {    // takes the dataid
1309
    global $DB, $CFG;
1310
 
1311
    if (!$data = $DB->get_record('data', array('id'=>$id))) {
1312
        return false;
1313
    }
1314
 
1315
    $cm = get_coursemodule_from_instance('data', $data->id);
1316
    $context = context_module::instance($cm->id);
1317
 
1318
    // Delete all information related to fields.
1319
    $fields = $DB->get_records('data_fields', ['dataid' => $id]);
1320
    foreach ($fields as $field) {
1321
        $todelete = data_get_field($field, $data, $cm);
1322
        $todelete->delete_field();
1323
    }
1324
 
1325
    // Remove old calendar events.
1326
    $events = $DB->get_records('event', array('modulename' => 'data', 'instance' => $id));
1327
    foreach ($events as $event) {
1328
        $event = calendar_event::load($event);
1329
        $event->delete();
1330
    }
1331
 
1332
    // cleanup gradebook
1333
    data_grade_item_delete($data);
1334
 
1335
    // Delete the instance itself
1336
    // We must delete the module record after we delete the grade item.
1337
    $result = $DB->delete_records('data', array('id'=>$id));
1338
 
1339
    return $result;
1340
}
1341
 
1342
/**
1343
 * returns a summary of data activity of this user
1344
 *
1345
 * @global object
1346
 * @param object $course
1347
 * @param object $user
1348
 * @param object $mod
1349
 * @param object $data
1350
 * @return object|null
1351
 */
1352
function data_user_outline($course, $user, $mod, $data) {
1353
    global $DB, $CFG;
1354
    require_once("$CFG->libdir/gradelib.php");
1355
 
1356
    $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1357
    if (empty($grades->items[0]->grades)) {
1358
        $grade = false;
1359
    } else {
1360
        $grade = reset($grades->items[0]->grades);
1361
    }
1362
 
1363
 
1364
    if ($countrecords = $DB->count_records('data_records', array('dataid'=>$data->id, 'userid'=>$user->id))) {
1365
        $result = new stdClass();
1366
        $result->info = get_string('numrecords', 'data', $countrecords);
1367
        $lastrecord   = $DB->get_record_sql('SELECT id,timemodified FROM {data_records}
1368
                                              WHERE dataid = ? AND userid = ?
1369
                                           ORDER BY timemodified DESC', array($data->id, $user->id), true);
1370
        $result->time = $lastrecord->timemodified;
1371
        if ($grade) {
1372
            if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1373
                $result->info .= ', ' . get_string('gradenoun') . ': ' . $grade->str_long_grade;
1374
            } else {
1375
                $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
1376
            }
1377
        }
1378
        return $result;
1379
    } else if ($grade) {
1380
        $result = (object) [
1381
            'time' => grade_get_date_for_user_grade($grade, $user),
1382
        ];
1383
        if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1384
            $result->info = get_string('gradenoun') . ': ' . $grade->str_long_grade;
1385
        } else {
1386
            $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
1387
        }
1388
 
1389
        return $result;
1390
    }
1391
    return NULL;
1392
}
1393
 
1394
/**
1395
 * Prints all the records uploaded by this user
1396
 *
1397
 * @global object
1398
 * @param object $course
1399
 * @param object $user
1400
 * @param object $mod
1401
 * @param object $data
1402
 */
1403
function data_user_complete($course, $user, $mod, $data) {
1404
    global $DB, $CFG, $OUTPUT;
1405
    require_once("$CFG->libdir/gradelib.php");
1406
 
1407
    $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1408
    if (!empty($grades->items[0]->grades)) {
1409
        $grade = reset($grades->items[0]->grades);
1410
        if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1411
            echo $OUTPUT->container(get_string('gradenoun') . ': ' . $grade->str_long_grade);
1412
            if ($grade->str_feedback) {
1413
                echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1414
            }
1415
        } else {
1416
            echo $OUTPUT->container(get_string('gradenoun') . ': ' . get_string('hidden', 'grades'));
1417
        }
1418
    }
1419
    $records = $DB->get_records(
1420
        'data_records',
1421
        ['dataid' => $data->id, 'userid' => $user->id],
1422
        'timemodified DESC'
1423
    );
1424
    if ($records) {
1425
        $manager = manager::create_from_instance($data);
1426
        $parser = $manager->get_template('singletemplate');
1427
        echo $parser->parse_entries($records);
1428
    }
1429
}
1430
 
1431
/**
1432
 * Return grade for given user or all users.
1433
 *
1434
 * @global object
1435
 * @param object $data
1436
 * @param int $userid optional user id, 0 means all users
1437
 * @return array array of grades, false if none
1438
 */
1439
function data_get_user_grades($data, $userid=0) {
1440
    global $CFG;
1441
 
1442
    require_once($CFG->dirroot.'/rating/lib.php');
1443
 
1444
    $ratingoptions = new stdClass;
1445
    $ratingoptions->component = 'mod_data';
1446
    $ratingoptions->ratingarea = 'entry';
1447
    $ratingoptions->modulename = 'data';
1448
    $ratingoptions->moduleid   = $data->id;
1449
 
1450
    $ratingoptions->userid = $userid;
1451
    $ratingoptions->aggregationmethod = $data->assessed;
1452
    $ratingoptions->scaleid = $data->scale;
1453
    $ratingoptions->itemtable = 'data_records';
1454
    $ratingoptions->itemtableusercolumn = 'userid';
1455
 
1456
    $rm = new rating_manager();
1457
    return $rm->get_user_grades($ratingoptions);
1458
}
1459
 
1460
/**
1461
 * Update activity grades
1462
 *
1463
 * @category grade
1464
 * @param object $data
1465
 * @param int $userid specific user only, 0 means all
1466
 * @param bool $nullifnone
1467
 */
1468
function data_update_grades($data, $userid=0, $nullifnone=true) {
1469
    global $CFG, $DB;
1470
    require_once($CFG->libdir.'/gradelib.php');
1471
 
1472
    if (!$data->assessed) {
1473
        data_grade_item_update($data);
1474
 
1475
    } else if ($grades = data_get_user_grades($data, $userid)) {
1476
        data_grade_item_update($data, $grades);
1477
 
1478
    } else if ($userid and $nullifnone) {
1479
        $grade = new stdClass();
1480
        $grade->userid   = $userid;
1481
        $grade->rawgrade = NULL;
1482
        data_grade_item_update($data, $grade);
1483
 
1484
    } else {
1485
        data_grade_item_update($data);
1486
    }
1487
}
1488
 
1489
/**
1490
 * Update/create grade item for given data
1491
 *
1492
 * @category grade
1493
 * @param stdClass $data A database instance with extra cmidnumber property
1494
 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1495
 * @return object grade_item
1496
 */
1497
function data_grade_item_update($data, $grades=NULL) {
1498
    global $CFG;
1499
    require_once($CFG->libdir.'/gradelib.php');
1500
 
1501
    $params = array('itemname'=>$data->name, 'idnumber'=>$data->cmidnumber);
1502
 
1503
    if (!$data->assessed or $data->scale == 0) {
1504
        $params['gradetype'] = GRADE_TYPE_NONE;
1505
 
1506
    } else if ($data->scale > 0) {
1507
        $params['gradetype'] = GRADE_TYPE_VALUE;
1508
        $params['grademax']  = $data->scale;
1509
        $params['grademin']  = 0;
1510
 
1511
    } else if ($data->scale < 0) {
1512
        $params['gradetype'] = GRADE_TYPE_SCALE;
1513
        $params['scaleid']   = -$data->scale;
1514
    }
1515
 
1516
    if ($grades  === 'reset') {
1517
        $params['reset'] = true;
1518
        $grades = NULL;
1519
    }
1520
 
1521
    return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, $grades, $params);
1522
}
1523
 
1524
/**
1525
 * Delete grade item for given data
1526
 *
1527
 * @category grade
1528
 * @param object $data object
1529
 * @return object grade_item
1530
 */
1531
function data_grade_item_delete($data) {
1532
    global $CFG;
1533
    require_once($CFG->libdir.'/gradelib.php');
1534
 
1535
    return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, NULL, array('deleted'=>1));
1536
}
1537
 
1538
// junk functions
1539
 
1540
/**
1541
 * Return rating related permissions
1542
 *
1543
 * @param string $contextid the context id
1544
 * @param string $component the component to get rating permissions for
1545
 * @param string $ratingarea the rating area to get permissions for
1546
 * @return array an associative array of the user's rating permissions
1547
 */
1548
function data_rating_permissions($contextid, $component, $ratingarea) {
1549
    $context = context::instance_by_id($contextid, MUST_EXIST);
1550
    if ($component != 'mod_data' || $ratingarea != 'entry') {
1551
        return null;
1552
    }
1553
    return array(
1554
        'view'    => has_capability('mod/data:viewrating',$context),
1555
        'viewany' => has_capability('mod/data:viewanyrating',$context),
1556
        'viewall' => has_capability('mod/data:viewallratings',$context),
1557
        'rate'    => has_capability('mod/data:rate',$context)
1558
    );
1559
}
1560
 
1561
/**
1562
 * Validates a submitted rating
1563
 * @param array $params submitted data
1564
 *            context => object the context in which the rated items exists [required]
1565
 *            itemid => int the ID of the object being rated
1566
 *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
1567
 *            rating => int the submitted rating
1568
 *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
1569
 *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
1570
 * @return boolean true if the rating is valid. Will throw rating_exception if not
1571
 */
1572
function data_rating_validate($params) {
1573
    global $DB, $USER;
1574
 
1575
    // Check the component is mod_data
1576
    if ($params['component'] != 'mod_data') {
1577
        throw new rating_exception('invalidcomponent');
1578
    }
1579
 
1580
    // Check the ratingarea is entry (the only rating area in data module)
1581
    if ($params['ratingarea'] != 'entry') {
1582
        throw new rating_exception('invalidratingarea');
1583
    }
1584
 
1585
    // Check the rateduserid is not the current user .. you can't rate your own entries
1586
    if ($params['rateduserid'] == $USER->id) {
1587
        throw new rating_exception('nopermissiontorate');
1588
    }
1589
 
1590
    $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
1591
                  FROM {data_records} r
1592
                  JOIN {data} d ON r.dataid = d.id
1593
                 WHERE r.id = :itemid";
1594
    $dataparams = array('itemid'=>$params['itemid']);
1595
    if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1596
        //item doesn't exist
1597
        throw new rating_exception('invaliditemid');
1598
    }
1599
 
1600
    if ($info->scale != $params['scaleid']) {
1601
        //the scale being submitted doesnt match the one in the database
1602
        throw new rating_exception('invalidscaleid');
1603
    }
1604
 
1605
    //check that the submitted rating is valid for the scale
1606
 
1607
    // lower limit
1608
    if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
1609
        throw new rating_exception('invalidnum');
1610
    }
1611
 
1612
    // upper limit
1613
    if ($info->scale < 0) {
1614
        //its a custom scale
1615
        $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
1616
        if ($scalerecord) {
1617
            $scalearray = explode(',', $scalerecord->scale);
1618
            if ($params['rating'] > count($scalearray)) {
1619
                throw new rating_exception('invalidnum');
1620
            }
1621
        } else {
1622
            throw new rating_exception('invalidscaleid');
1623
        }
1624
    } else if ($params['rating'] > $info->scale) {
1625
        //if its numeric and submitted rating is above maximum
1626
        throw new rating_exception('invalidnum');
1627
    }
1628
 
1629
    if ($info->approval && !$info->approved) {
1630
        //database requires approval but this item isnt approved
1631
        throw new rating_exception('nopermissiontorate');
1632
    }
1633
 
1634
    // check the item we're rating was created in the assessable time window
1635
    if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
1636
        if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
1637
            throw new rating_exception('notavailable');
1638
        }
1639
    }
1640
 
1641
    $course = $DB->get_record('course', array('id'=>$info->course), '*', MUST_EXIST);
1642
    $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1643
    $context = context_module::instance($cm->id);
1644
 
1645
    // if the supplied context doesnt match the item's context
1646
    if ($context->id != $params['context']->id) {
1647
        throw new rating_exception('invalidcontext');
1648
    }
1649
 
1650
    // Make sure groups allow this user to see the item they're rating
1651
    $groupid = $info->groupid;
1652
    if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
1653
        if (!groups_group_exists($groupid)) { // Can't find group
1654
            throw new rating_exception('cannotfindgroup');//something is wrong
1655
        }
1656
 
1657
        if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
1658
            // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
1659
            throw new rating_exception('notmemberofgroup');
1660
        }
1661
    }
1662
 
1663
    return true;
1664
}
1665
 
1666
/**
1667
 * Can the current user see ratings for a given itemid?
1668
 *
1669
 * @param array $params submitted data
1670
 *            contextid => int contextid [required]
1671
 *            component => The component for this module - should always be mod_data [required]
1672
 *            ratingarea => object the context in which the rated items exists [required]
1673
 *            itemid => int the ID of the object being rated [required]
1674
 *            scaleid => int scale id [optional]
1675
 * @return bool
1676
 * @throws coding_exception
1677
 * @throws rating_exception
1678
 */
1679
function mod_data_rating_can_see_item_ratings($params) {
1680
    global $DB;
1681
 
1682
    // Check the component is mod_data.
1683
    if (!isset($params['component']) || $params['component'] != 'mod_data') {
1684
        throw new rating_exception('invalidcomponent');
1685
    }
1686
 
1687
    // Check the ratingarea is entry (the only rating area in data).
1688
    if (!isset($params['ratingarea']) || $params['ratingarea'] != 'entry') {
1689
        throw new rating_exception('invalidratingarea');
1690
    }
1691
 
1692
    if (!isset($params['itemid'])) {
1693
        throw new rating_exception('invaliditemid');
1694
    }
1695
 
1696
    $datasql = "SELECT d.id as dataid, d.course, r.groupid
1697
                  FROM {data_records} r
1698
                  JOIN {data} d ON r.dataid = d.id
1699
                 WHERE r.id = :itemid";
1700
    $dataparams = array('itemid' => $params['itemid']);
1701
    if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1702
        // Item doesn't exist.
1703
        throw new rating_exception('invaliditemid');
1704
    }
1705
 
1706
    // User can see ratings of all participants.
1707
    if ($info->groupid == 0) {
1708
        return true;
1709
    }
1710
 
1711
    $course = $DB->get_record('course', array('id' => $info->course), '*', MUST_EXIST);
1712
    $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1713
 
1714
    // Make sure groups allow this user to see the item they're rating.
1715
    return groups_group_visible($info->groupid, $course, $cm);
1716
}
1717
 
1718
 
1719
/**
1720
 * function that takes in the current data, number of items per page,
1721
 * a search string and prints a preference box in view.php
1722
 *
1723
 * This preference box prints a searchable advanced search template if
1724
 *     a) A template is defined
1725
 *  b) The advanced search checkbox is checked.
1726
 *
1727
 * @global object
1728
 * @global object
1729
 * @param object $data
1730
 * @param int $perpage
1731
 * @param string $search
1732
 * @param string $sort
1733
 * @param string $order
1734
 * @param array $search_array
1735
 * @param int $advanced
1736
 * @param string $mode
1737
 * @return void
1738
 */
1739
function data_print_preference_form($data, $perpage, $search, $sort='', $order='ASC', $search_array = '', $advanced = 0, $mode= ''){
1740
    global $DB, $PAGE, $OUTPUT;
1741
 
1742
    $cm = get_coursemodule_from_instance('data', $data->id);
1743
    $context = context_module::instance($cm->id);
1744
    echo '<div class="datapreferences my-5">';
1745
    echo '<form id="options" action="view.php" method="get">';
1441 ariadna 1746
    echo '<div class="d-flex flex-wrap align-items-center gap-1">';
1 efrain 1747
    echo '<input type="hidden" name="d" value="'.$data->id.'" />';
1748
    if ($mode =='asearch') {
1749
        $advanced = 1;
1750
        echo '<input type="hidden" name="mode" value="list" />';
1751
    }
1752
    echo '<label for="pref_perpage">'.get_string('pagesize','data').'</label> ';
1753
    $pagesizes = array(2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9,10=>10,15=>15,
1754
                       20=>20,30=>30,40=>40,50=>50,100=>100,200=>200,300=>300,400=>400,500=>500,1000=>1000);
1755
    echo html_writer::select($pagesizes, 'perpage', $perpage, false, array('id' => 'pref_perpage',
1441 ariadna 1756
        'class' => 'form-select me-1'));
1 efrain 1757
 
1758
    if ($advanced) {
1759
        $regsearchclass = 'search_none';
1760
        $advancedsearchclass = 'search_inline';
1761
    } else {
1762
        $regsearchclass = 'search_inline';
1763
        $advancedsearchclass = 'search_none';
1764
    }
1441 ariadna 1765
    echo '<div id="reg_search" class="' . $regsearchclass . ' me-1" >';
1766
    echo '<label for="pref_search" class="me-1">' . get_string('search') . '</label><input type="text" ' .
1767
         'class="form-control d-inline-block align-middle w-auto me-1" size="16" name="search" id= "pref_search" value="' . s($search) . '" /></div>';
1 efrain 1768
    echo '<label for="pref_sortby">'.get_string('sortby').'</label> ';
1769
    // foreach field, print the option
1441 ariadna 1770
    echo '<select name="sort" id="pref_sortby" class="form-select me-1">';
1 efrain 1771
    if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'name')) {
1772
        echo '<optgroup label="'.get_string('fields', 'data').'">';
1773
        foreach ($fields as $field) {
1774
            if ($field->id == $sort) {
1775
                echo '<option value="'.$field->id.'" selected="selected">'.s($field->name).'</option>';
1776
            } else {
1777
                echo '<option value="'.$field->id.'">'.s($field->name).'</option>';
1778
            }
1779
        }
1780
        echo '</optgroup>';
1781
    }
1782
    $options = array();
1783
    $options[DATA_TIMEADDED]    = get_string('timeadded', 'data');
1784
    $options[DATA_TIMEMODIFIED] = get_string('timemodified', 'data');
1785
    $options[DATA_FIRSTNAME]    = get_string('authorfirstname', 'data');
1786
    $options[DATA_LASTNAME]     = get_string('authorlastname', 'data');
1787
    if ($data->approval and has_capability('mod/data:approve', $context)) {
1788
        $options[DATA_APPROVED] = get_string('approved', 'data');
1789
    }
1790
    echo '<optgroup label="'.get_string('other', 'data').'">';
1791
    foreach ($options as $key => $name) {
1792
        if ($key == $sort) {
1793
            echo '<option value="'.$key.'" selected="selected">'.$name.'</option>';
1794
        } else {
1795
            echo '<option value="'.$key.'">'.$name.'</option>';
1796
        }
1797
    }
1798
    echo '</optgroup>';
1799
    echo '</select>';
1800
    echo '<label for="pref_order" class="accesshide">'.get_string('order').'</label>';
1441 ariadna 1801
    echo '<select id="pref_order" name="order" class="form-select me-1">';
1 efrain 1802
    if ($order == 'ASC') {
1803
        echo '<option value="ASC" selected="selected">'.get_string('ascending','data').'</option>';
1804
    } else {
1805
        echo '<option value="ASC">'.get_string('ascending','data').'</option>';
1806
    }
1807
    if ($order == 'DESC') {
1808
        echo '<option value="DESC" selected="selected">'.get_string('descending','data').'</option>';
1809
    } else {
1810
        echo '<option value="DESC">'.get_string('descending','data').'</option>';
1811
    }
1812
    echo '</select>';
1813
 
1814
    if ($advanced) {
1815
        $checked = ' checked="checked" ';
1816
    }
1817
    else {
1818
        $checked = '';
1819
    }
1820
    $PAGE->requires->js('/mod/data/data.js');
1821
    echo '<input type="hidden" name="advanced" value="0" />';
1822
    echo '<input type="hidden" name="filter" value="1" />';
1823
    echo '<input type="checkbox" id="advancedcheckbox" name="advanced" value="1" ' . $checked . ' ' .
1824
         'onchange="showHideAdvSearch(this.checked);" class="mx-1" />' .
1825
         '<label for="advancedcheckbox">' . get_string('advancedsearch', 'data') . '</label>';
1441 ariadna 1826
    echo '<div id="advsearch-save-sec" class="ms-3 '. $regsearchclass . '">';
1 efrain 1827
    echo '<input type="submit" class="btn btn-secondary" value="' . get_string('savesettings', 'data') . '" />';
1828
    echo '</div>';
1829
    echo '</div>';
1830
 
1831
    echo '<br />';
1832
    echo '<div class="' . $advancedsearchclass . '" id="data_adv_form">';
1833
    echo '<table class="boxaligncenter">';
1834
 
1835
    // print ASC or DESC
1836
    echo '<tr><td colspan="2">&nbsp;</td></tr>';
1837
    $i = 0;
1838
 
1839
    // Determine if we are printing all fields for advanced search, or the template for advanced search
1840
    // If a template is not defined, use the deafault template and display all fields.
1841
    $asearchtemplate = $data->asearchtemplate;
1842
    if (empty($asearchtemplate)) {
1843
        $asearchtemplate = data_generate_default_template($data, 'asearchtemplate', 0, false, false);
1844
    }
1845
 
1846
    static $fields = array();
1847
    static $dataid = null;
1848
 
1849
    if (empty($dataid)) {
1850
        $dataid = $data->id;
1851
    } else if ($dataid != $data->id) {
1852
        $fields = array();
1853
    }
1854
 
1855
    if (empty($fields)) {
1856
        $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id));
1857
        foreach ($fieldrecords as $fieldrecord) {
1858
            $fields[]= data_get_field($fieldrecord, $data);
1859
        }
1860
    }
1861
 
1862
    // Replacing tags
1863
    $patterns = array();
1864
    $replacement = array();
1865
 
1866
    // Then we generate strings to replace for normal tags
1867
    $otherfields = [];
1868
    foreach ($fields as $field) {
1869
        $fieldname = $field->field->name;
1870
        $fieldname = preg_quote($fieldname, '/');
1871
        $searchfield = data_get_field_from_id($field->field->id, $data);
1872
 
1873
        if ($searchfield->type === 'unknown') {
1874
            continue;
1875
        }
1876
        if (!empty($search_array[$field->field->id]->data)) {
1877
            $searchinput = $searchfield->display_search_field($search_array[$field->field->id]->data);
1878
        } else {
1879
            $searchinput = $searchfield->display_search_field();
1880
        }
1881
        $patterns[] = "/\[\[$fieldname\]\]/i";
1882
        $replacement[] = $searchinput;
1883
        // Extra field information.
1884
        $patterns[] = "/\[\[$fieldname#name\]\]/i";
1885
        $replacement[] = $field->field->name;
1886
        $patterns[] = "/\[\[$fieldname#description\]\]/i";
1887
        $replacement[] = $field->field->description;
1888
        // Other fields.
1889
        if (strpos($asearchtemplate, "[[" . $field->field->name . "]]") === false) {
1890
            $otherfields[] = [
1891
                'fieldname' => $searchfield->field->name,
1892
                'fieldcontent' => $searchinput,
1893
            ];
1894
        }
1895
    }
1896
    $patterns[] = "/##otherfields##/";
1897
    if (!empty($otherfields)) {
1898
        $replacement[] = $OUTPUT->render_from_template(
1899
            'mod_data/fields_otherfields',
1900
            ['fields' => $otherfields]
1901
        );
1902
    } else {
1903
        $replacement[] = '';
1904
    }
1905
 
1906
    $fn = !empty($search_array[DATA_FIRSTNAME]->data) ? $search_array[DATA_FIRSTNAME]->data : '';
1907
    $ln = !empty($search_array[DATA_LASTNAME]->data) ? $search_array[DATA_LASTNAME]->data : '';
1908
    $patterns[]    = '/##firstname##/';
1909
    $replacement[] = '<label class="accesshide" for="u_fn">' . get_string('authorfirstname', 'data') . '</label>' .
1910
                     '<input type="text" class="form-control" size="16" id="u_fn" name="u_fn" value="' . s($fn) . '" />';
1911
    $patterns[]    = '/##lastname##/';
1912
    $replacement[] = '<label class="accesshide" for="u_ln">' . get_string('authorlastname', 'data') . '</label>' .
1913
                     '<input type="text" class="form-control" size="16" id="u_ln" name="u_ln" value="' . s($ln) . '" />';
1914
 
1915
    if (core_tag_tag::is_enabled('mod_data', 'data_records')) {
1916
        $patterns[] = "/##tags##/";
1917
        $selectedtags = isset($search_array[DATA_TAGS]->rawtagnames) ? $search_array[DATA_TAGS]->rawtagnames : [];
1918
        $replacement[] = data_generate_tag_form(false, $selectedtags);
1919
    }
1920
 
1921
    // actual replacement of the tags
1922
 
1923
    $options = new stdClass();
1924
    $options->para=false;
1925
    $options->noclean=true;
1926
    echo '<tr><td>';
1927
    echo preg_replace($patterns, $replacement, format_text($asearchtemplate, FORMAT_HTML, $options));
1928
    echo '</td></tr>';
1929
 
1930
    echo '<tr><td colspan="4"><br/>' .
1441 ariadna 1931
         '<input type="submit" class="btn btn-primary me-1" value="' . get_string('savesettings', 'data') . '" />' .
1 efrain 1932
         '<input type="submit" class="btn btn-secondary" name="resetadv" value="' . get_string('resetsettings', 'data') . '" />' .
1933
         '</td></tr>';
1934
    echo '</table>';
1935
    echo '</div>';
1936
    echo '</form>';
1937
    echo '</div>';
1938
    echo '<hr/>';
1939
}
1940
 
1941
/**
1942
 * @global object
1943
 * @global object
1944
 * @param object $data
1945
 * @param object $record
1946
 * @param bool $print if the result must be printed or returner.
1947
 * @return void Output echo'd
1948
 */
1949
function data_print_ratings($data, $record, bool $print = true) {
1950
    global $OUTPUT;
1951
    $result = '';
1952
    if (!empty($record->rating)){
1953
        $result = $OUTPUT->render($record->rating);
1954
    }
1955
    if (!$print) {
1956
        return $result;
1957
    }
1958
    echo $result;
1959
}
1960
 
1961
/**
1962
 * List the actions that correspond to a view of this module.
1963
 * This is used by the participation report.
1964
 *
1965
 * Note: This is not used by new logging system. Event with
1966
 *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1967
 *       be considered as view action.
1968
 *
1969
 * @return array
1970
 */
1971
function data_get_view_actions() {
1972
    return array('view');
1973
}
1974
 
1975
/**
1976
 * List the actions that correspond to a post of this module.
1977
 * This is used by the participation report.
1978
 *
1979
 * Note: This is not used by new logging system. Event with
1980
 *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1981
 *       will be considered as post action.
1982
 *
1983
 * @return array
1984
 */
1985
function data_get_post_actions() {
1986
    return array('add','update','record delete');
1987
}
1988
 
1989
/**
1990
 * @param string $name
1991
 * @param int $dataid
1992
 * @param int $fieldid
1993
 * @return bool
1994
 */
1995
function data_fieldname_exists($name, $dataid, $fieldid = 0) {
1996
    global $DB;
1997
 
1998
    if (!is_numeric($name)) {
1999
        $like = $DB->sql_like('df.name', ':name', false);
2000
    } else {
2001
        $like = "df.name = :name";
2002
    }
2003
    $params = array('name'=>$name);
2004
    if ($fieldid) {
2005
        $params['dataid']   = $dataid;
2006
        $params['fieldid1'] = $fieldid;
2007
        $params['fieldid2'] = $fieldid;
2008
        return $DB->record_exists_sql("SELECT * FROM {data_fields} df
2009
                                        WHERE $like AND df.dataid = :dataid
2010
                                              AND ((df.id < :fieldid1) OR (df.id > :fieldid2))", $params);
2011
    } else {
2012
        $params['dataid']   = $dataid;
2013
        return $DB->record_exists_sql("SELECT * FROM {data_fields} df
2014
                                        WHERE $like AND df.dataid = :dataid", $params);
2015
    }
2016
}
2017
 
2018
/**
2019
 * @param array $fieldinput
2020
 */
2021
function data_convert_arrays_to_strings(&$fieldinput) {
2022
    foreach ($fieldinput as $key => $val) {
2023
        if (is_array($val)) {
2024
            $str = '';
2025
            foreach ($val as $inner) {
2026
                $str .= $inner . ',';
2027
            }
2028
            $str = substr($str, 0, -1);
2029
 
2030
            $fieldinput->$key = $str;
2031
        }
2032
    }
2033
}
2034
 
2035
 
2036
/**
2037
 * Converts a database (module instance) to use the Roles System
2038
 *
2039
 * @global object
2040
 * @global object
2041
 * @uses CONTEXT_MODULE
2042
 * @uses CAP_PREVENT
2043
 * @uses CAP_ALLOW
2044
 * @param object $data a data object with the same attributes as a record
2045
 *                     from the data database table
2046
 * @param int $datamodid the id of the data module, from the modules table
2047
 * @param array $teacherroles array of roles that have archetype teacher
2048
 * @param array $studentroles array of roles that have archetype student
2049
 * @param array $guestroles array of roles that have archetype guest
2050
 * @param int $cmid the course_module id for this data instance
2051
 * @return boolean data module was converted or not
2052
 */
2053
function data_convert_to_roles($data, $teacherroles=array(), $studentroles=array(), $cmid=NULL) {
2054
    global $CFG, $DB, $OUTPUT;
2055
 
2056
    if (!isset($data->participants) && !isset($data->assesspublic)
2057
            && !isset($data->groupmode)) {
2058
        // We assume that this database has already been converted to use the
2059
        // Roles System. above fields get dropped the data module has been
2060
        // upgraded to use Roles.
2061
        return false;
2062
    }
2063
 
2064
    if (empty($cmid)) {
2065
        // We were not given the course_module id. Try to find it.
2066
        if (!$cm = get_coursemodule_from_instance('data', $data->id)) {
2067
            echo $OUTPUT->notification('Could not get the course module for the data');
2068
            return false;
2069
        } else {
2070
            $cmid = $cm->id;
2071
        }
2072
    }
2073
    $context = context_module::instance($cmid);
2074
 
2075
 
2076
    // $data->participants:
2077
    // 1 - Only teachers can add entries
2078
    // 3 - Teachers and students can add entries
2079
    switch ($data->participants) {
2080
        case 1:
2081
            foreach ($studentroles as $studentrole) {
2082
                assign_capability('mod/data:writeentry', CAP_PREVENT, $studentrole->id, $context->id);
2083
            }
2084
            foreach ($teacherroles as $teacherrole) {
2085
                assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
2086
            }
2087
            break;
2088
        case 3:
2089
            foreach ($studentroles as $studentrole) {
2090
                assign_capability('mod/data:writeentry', CAP_ALLOW, $studentrole->id, $context->id);
2091
            }
2092
            foreach ($teacherroles as $teacherrole) {
2093
                assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
2094
            }
2095
            break;
2096
    }
2097
 
2098
    // $data->assessed:
2099
    // 2 - Only teachers can rate posts
2100
    // 1 - Everyone can rate posts
2101
    // 0 - No one can rate posts
2102
    switch ($data->assessed) {
2103
        case 0:
2104
            foreach ($studentroles as $studentrole) {
2105
                assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2106
            }
2107
            foreach ($teacherroles as $teacherrole) {
2108
                assign_capability('mod/data:rate', CAP_PREVENT, $teacherrole->id, $context->id);
2109
            }
2110
            break;
2111
        case 1:
2112
            foreach ($studentroles as $studentrole) {
2113
                assign_capability('mod/data:rate', CAP_ALLOW, $studentrole->id, $context->id);
2114
            }
2115
            foreach ($teacherroles as $teacherrole) {
2116
                assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2117
            }
2118
            break;
2119
        case 2:
2120
            foreach ($studentroles as $studentrole) {
2121
                assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2122
            }
2123
            foreach ($teacherroles as $teacherrole) {
2124
                assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2125
            }
2126
            break;
2127
    }
2128
 
2129
    // $data->assesspublic:
2130
    // 0 - Students can only see their own ratings
2131
    // 1 - Students can see everyone's ratings
2132
    switch ($data->assesspublic) {
2133
        case 0:
2134
            foreach ($studentroles as $studentrole) {
2135
                assign_capability('mod/data:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
2136
            }
2137
            foreach ($teacherroles as $teacherrole) {
2138
                assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2139
            }
2140
            break;
2141
        case 1:
2142
            foreach ($studentroles as $studentrole) {
2143
                assign_capability('mod/data:viewrating', CAP_ALLOW, $studentrole->id, $context->id);
2144
            }
2145
            foreach ($teacherroles as $teacherrole) {
2146
                assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2147
            }
2148
            break;
2149
    }
2150
 
2151
    if (empty($cm)) {
2152
        $cm = $DB->get_record('course_modules', array('id'=>$cmid));
2153
    }
2154
 
2155
    switch ($cm->groupmode) {
2156
        case NOGROUPS:
2157
            break;
2158
        case SEPARATEGROUPS:
2159
            foreach ($studentroles as $studentrole) {
2160
                assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
2161
            }
2162
            foreach ($teacherroles as $teacherrole) {
2163
                assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2164
            }
2165
            break;
2166
        case VISIBLEGROUPS:
2167
            foreach ($studentroles as $studentrole) {
2168
                assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
2169
            }
2170
            foreach ($teacherroles as $teacherrole) {
2171
                assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2172
            }
2173
            break;
2174
    }
2175
    return true;
2176
}
2177
 
2178
/**
2179
 * Prints the heads for a page
2180
 *
2181
 * @param stdClass $course
2182
 * @param stdClass $cm
2183
 * @param stdClass $data
2184
 * @param string $currenttab
2185
 * @param string $actionbar
2186
 */
2187
function data_print_header($course, $cm, $data, $currenttab='', string $actionbar = '') {
2188
 
2189
    global $CFG, $displaynoticegood, $displaynoticebad, $OUTPUT, $PAGE, $USER;
2190
 
2191
    echo $OUTPUT->header();
2192
 
2193
    echo $actionbar;
2194
 
2195
    // Print any notices
2196
 
2197
    if (!empty($displaynoticegood)) {
2198
        echo $OUTPUT->notification($displaynoticegood, 'notifysuccess');    // good (usually green)
2199
    } else if (!empty($displaynoticebad)) {
2200
        echo $OUTPUT->notification($displaynoticebad);                     // bad (usuually red)
2201
    }
2202
}
2203
 
2204
/**
2205
 * Can user add more entries?
2206
 *
2207
 * @param object $data
2208
 * @param mixed $currentgroup
2209
 * @param int $groupmode
2210
 * @param stdClass $context
2211
 * @return bool
2212
 */
2213
function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = null) {
2214
    global $DB;
2215
 
2216
    // Don't let add entry to a database that has no fields.
2217
    if (!$DB->record_exists('data_fields', ['dataid' => $data->id])) {
2218
        return false;
2219
    }
2220
 
2221
    if (empty($context)) {
2222
        $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
2223
        $context = context_module::instance($cm->id);
2224
    }
2225
 
2226
    if (has_capability('mod/data:manageentries', $context)) {
2227
        // no entry limits apply if user can manage
2228
 
2229
    } else if (!has_capability('mod/data:writeentry', $context)) {
2230
        return false;
2231
 
2232
    } else if (data_atmaxentries($data)) {
2233
        return false;
2234
    } else if (data_in_readonly_period($data)) {
2235
        // Check whether we're in a read-only period
2236
        return false;
2237
    }
2238
 
2239
    if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
2240
        return true;
2241
    }
2242
 
2243
    if ($currentgroup) {
2244
        return groups_is_member($currentgroup);
2245
    } else {
2246
        //else it might be group 0 in visible mode
2247
        if ($groupmode == VISIBLEGROUPS){
2248
            return true;
2249
        } else {
2250
            return false;
2251
        }
2252
    }
2253
}
2254
 
2255
/**
2256
 * Check whether the current user is allowed to manage the given record considering manageentries capability,
2257
 * data_in_readonly_period() result, ownership (determined by data_isowner()) and manageapproved setting.
2258
 * @param mixed $record record object or id
2259
 * @param object $data data object
2260
 * @param object $context context object
2261
 * @return bool returns true if the user is allowd to edit the entry, false otherwise
2262
 */
2263
function data_user_can_manage_entry($record, $data, $context) {
2264
    global $DB;
2265
 
2266
    if (has_capability('mod/data:manageentries', $context)) {
2267
        return true;
2268
    }
2269
 
2270
    // Check whether this activity is read-only at present.
2271
    $readonly = data_in_readonly_period($data);
2272
 
2273
    if (!$readonly) {
2274
        // Get record object from db if just id given like in data_isowner.
2275
        // ...done before calling data_isowner() to avoid querying db twice.
2276
        if (!is_object($record)) {
2277
            if (!$record = $DB->get_record('data_records', array('id' => $record))) {
2278
                return false;
2279
            }
2280
        }
2281
        if (data_isowner($record)) {
2282
            if ($data->approval && $record->approved) {
2283
                return $data->manageapproved == 1;
2284
            } else {
2285
                return true;
2286
            }
2287
        }
2288
    }
2289
 
2290
    return false;
2291
}
2292
 
2293
/**
2294
 * Check whether the specified database activity is currently in a read-only period
2295
 *
2296
 * @param object $data
2297
 * @return bool returns true if the time fields in $data indicate a read-only period; false otherwise
2298
 */
2299
function data_in_readonly_period($data) {
2300
    $now = time();
2301
    if (!$data->timeviewfrom && !$data->timeviewto) {
2302
        return false;
2303
    } else if (($data->timeviewfrom && $now < $data->timeviewfrom) || ($data->timeviewto && $now > $data->timeviewto)) {
2304
        return false;
2305
    }
2306
    return true;
2307
}
2308
 
2309
/**
2310
 * @global object
2311
 * @global object
2312
 * @param object $course
2313
 * @param int $userid
2314
 * @param string $shortname
2315
 * @return string
2316
 */
2317
function data_preset_path($course, $userid, $shortname) {
2318
    global $USER, $CFG;
2319
 
2320
    $context = context_course::instance($course->id);
2321
 
2322
    $userid = (int)$userid;
2323
 
2324
    $path = null;
2325
    if ($userid > 0 && ($userid == $USER->id || has_capability('mod/data:viewalluserpresets', $context))) {
2326
        $path = $CFG->dataroot.'/data/preset/'.$userid.'/'.$shortname;
2327
    } else if ($userid == 0) {
2328
        $path = $CFG->dirroot.'/mod/data/preset/'.$shortname;
2329
    } else if ($userid < 0) {
2330
        $path = $CFG->tempdir.'/data/'.-$userid.'/'.$shortname;
2331
    }
2332
 
2333
    return $path;
2334
}
2335
 
2336
/**
2337
 * Implementation of the function for printing the form elements that control
2338
 * whether the course reset functionality affects the data.
2339
 *
2340
 * @param MoodleQuickForm $mform form passed by reference
2341
 */
2342
function data_reset_course_form_definition(&$mform) {
2343
    $mform->addElement('header', 'dataheader', get_string('modulenameplural', 'data'));
1441 ariadna 2344
    $mform->addElement('static', 'datadelete', get_string('delete'));
1 efrain 2345
    $mform->addElement('checkbox', 'reset_data', get_string('deleteallentries','data'));
2346
 
2347
    $mform->addElement('checkbox', 'reset_data_notenrolled', get_string('deletenotenrolled', 'data'));
2348
    $mform->disabledIf('reset_data_notenrolled', 'reset_data', 'checked');
2349
 
2350
    $mform->addElement('checkbox', 'reset_data_ratings', get_string('deleteallratings'));
2351
    $mform->disabledIf('reset_data_ratings', 'reset_data', 'checked');
2352
 
2353
    $mform->addElement('checkbox', 'reset_data_comments', get_string('deleteallcomments'));
2354
    $mform->disabledIf('reset_data_comments', 'reset_data', 'checked');
2355
 
2356
    $mform->addElement('checkbox', 'reset_data_tags', get_string('removealldatatags', 'data'));
2357
    $mform->disabledIf('reset_data_tags', 'reset_data', 'checked');
2358
}
2359
 
2360
/**
2361
 * Course reset form defaults.
2362
 * @return array
2363
 */
2364
function data_reset_course_form_defaults($course) {
2365
    return array('reset_data'=>0, 'reset_data_ratings'=>1, 'reset_data_comments'=>1, 'reset_data_notenrolled'=>0);
2366
}
2367
 
2368
/**
2369
 * Removes all grades from gradebook
2370
 *
2371
 * @global object
2372
 * @global object
2373
 * @param int $courseid
2374
 * @param string $type optional type
2375
 */
2376
function data_reset_gradebook($courseid, $type='') {
2377
    global $CFG, $DB;
2378
 
2379
    $sql = "SELECT d.*, cm.idnumber as cmidnumber, d.course as courseid
2380
              FROM {data} d, {course_modules} cm, {modules} m
2381
             WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id AND d.course=?";
2382
 
2383
    if ($datas = $DB->get_records_sql($sql, array($courseid))) {
2384
        foreach ($datas as $data) {
2385
            data_grade_item_update($data, 'reset');
2386
        }
2387
    }
2388
}
2389
 
2390
/**
2391
 * Actual implementation of the reset course functionality, delete all the
2392
 * data responses for course $data->courseid.
2393
 *
2394
 * @global object
2395
 * @global object
2396
 * @param object $data the data submitted from the reset course.
2397
 * @return array status array
2398
 */
2399
function data_reset_userdata($data) {
2400
    global $CFG, $DB;
2401
    require_once($CFG->libdir.'/filelib.php');
2402
    require_once($CFG->dirroot.'/rating/lib.php');
2403
 
2404
    $componentstr = get_string('modulenameplural', 'data');
1441 ariadna 2405
    $status = [];
1 efrain 2406
 
2407
    $allrecordssql = "SELECT r.id
2408
                        FROM {data_records} r
2409
                             INNER JOIN {data} d ON r.dataid = d.id
2410
                       WHERE d.course = ?";
2411
 
2412
    $alldatassql = "SELECT d.id
2413
                      FROM {data} d
2414
                     WHERE d.course=?";
2415
 
2416
    $rm = new rating_manager();
2417
    $ratingdeloptions = new stdClass;
2418
    $ratingdeloptions->component = 'mod_data';
2419
    $ratingdeloptions->ratingarea = 'entry';
2420
 
2421
    // Set the file storage - may need it to remove files later.
2422
    $fs = get_file_storage();
2423
 
1441 ariadna 2424
    // Delete entries if requested.
1 efrain 2425
    if (!empty($data->reset_data)) {
1441 ariadna 2426
        $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", [$data->courseid]);
2427
        $DB->delete_records_select('data_content', "recordid IN ($allrecordssql)", [$data->courseid]);
2428
        $DB->delete_records_select('data_records', "dataid IN ($alldatassql)", [$data->courseid]);
1 efrain 2429
 
1441 ariadna 2430
        if ($datas = $DB->get_records_sql($alldatassql, [$data->courseid])) {
1 efrain 2431
            foreach ($datas as $dataid=>$unused) {
2432
                if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2433
                    continue;
2434
                }
2435
                $datacontext = context_module::instance($cm->id);
2436
 
2437
                // Delete any files that may exist.
2438
                $fs->delete_area_files($datacontext->id, 'mod_data', 'content');
2439
 
2440
                $ratingdeloptions->contextid = $datacontext->id;
2441
                $rm->delete_ratings($ratingdeloptions);
2442
 
2443
                core_tag_tag::delete_instances('mod_data', null, $datacontext->id);
2444
            }
2445
        }
2446
 
2447
        if (empty($data->reset_gradebook_grades)) {
1441 ariadna 2448
            // Remove all grades from gradebook.
1 efrain 2449
            data_reset_gradebook($data->courseid);
2450
        }
1441 ariadna 2451
        $status[] = [
2452
            'component' => $componentstr,
2453
            'item' => get_string('deleteallentries', 'data'),
2454
            'error' => false,
2455
        ];
1 efrain 2456
    }
2457
 
1441 ariadna 2458
    // Remove entries by users not enrolled into course.
1 efrain 2459
    if (!empty($data->reset_data_notenrolled)) {
2460
        $recordssql = "SELECT r.id, r.userid, r.dataid, u.id AS userexists, u.deleted AS userdeleted
2461
                         FROM {data_records} r
2462
                              JOIN {data} d ON r.dataid = d.id
2463
                              LEFT JOIN {user} u ON r.userid = u.id
2464
                        WHERE d.course = ? AND r.userid > 0";
2465
 
2466
        $course_context = context_course::instance($data->courseid);
1441 ariadna 2467
        $notenrolled = [];
2468
        $fields = [];
2469
        $rs = $DB->get_recordset_sql($recordssql, [$data->courseid]);
1 efrain 2470
        foreach ($rs as $record) {
2471
            if (array_key_exists($record->userid, $notenrolled) or !$record->userexists or $record->userdeleted
2472
              or !is_enrolled($course_context, $record->userid)) {
1441 ariadna 2473
                // Delete ratings.
1 efrain 2474
                if (!$cm = get_coursemodule_from_instance('data', $record->dataid)) {
2475
                    continue;
2476
                }
2477
                $datacontext = context_module::instance($cm->id);
2478
                $ratingdeloptions->contextid = $datacontext->id;
2479
                $ratingdeloptions->itemid = $record->id;
2480
                $rm->delete_ratings($ratingdeloptions);
2481
 
2482
                // Delete any files that may exist.
1441 ariadna 2483
                if ($contents = $DB->get_records('data_content', ['recordid' => $record->id], '', 'id')) {
1 efrain 2484
                    foreach ($contents as $content) {
2485
                        $fs->delete_area_files($datacontext->id, 'mod_data', 'content', $content->id);
2486
                    }
2487
                }
2488
                $notenrolled[$record->userid] = true;
2489
 
2490
                core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $record->id);
2491
 
1441 ariadna 2492
                $DB->delete_records('comments', ['itemid' => $record->id, 'commentarea' => 'database_entry']);
2493
                $DB->delete_records('data_content', ['recordid' => $record->id]);
2494
                $DB->delete_records('data_records', ['id' => $record->id]);
1 efrain 2495
            }
2496
        }
2497
        $rs->close();
1441 ariadna 2498
        $status[] = [
2499
            'component' => $componentstr,
2500
            'item' => get_string('deletenotenrolled', 'data'),
2501
            'error' => false,
2502
        ];
1 efrain 2503
    }
2504
 
1441 ariadna 2505
    // Remove all ratings.
1 efrain 2506
    if (!empty($data->reset_data_ratings)) {
1441 ariadna 2507
        if ($datas = $DB->get_records_sql($alldatassql, [$data->courseid])) {
1 efrain 2508
            foreach ($datas as $dataid=>$unused) {
2509
                if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2510
                    continue;
2511
                }
2512
                $datacontext = context_module::instance($cm->id);
2513
 
2514
                $ratingdeloptions->contextid = $datacontext->id;
2515
                $rm->delete_ratings($ratingdeloptions);
2516
            }
2517
        }
2518
 
2519
        if (empty($data->reset_gradebook_grades)) {
1441 ariadna 2520
            // Remove all grades from gradebook.
1 efrain 2521
            data_reset_gradebook($data->courseid);
2522
        }
2523
 
1441 ariadna 2524
        $status[] = [
2525
            'component' => $componentstr,
2526
            'item' => get_string('deleteallratings'),
2527
            'error' => false,
2528
        ];
1 efrain 2529
    }
2530
 
1441 ariadna 2531
    // Remove all comments.
1 efrain 2532
    if (!empty($data->reset_data_comments)) {
1441 ariadna 2533
        $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", [$data->courseid]);
2534
        $status[] = [
2535
            'component' => $componentstr,
2536
            'item' => get_string('deleteallcomments'),
2537
            'error' => false,
2538
        ];
1 efrain 2539
    }
2540
 
2541
    // Remove all the tags.
2542
    if (!empty($data->reset_data_tags)) {
1441 ariadna 2543
        if ($datas = $DB->get_records_sql($alldatassql, [$data->courseid])) {
1 efrain 2544
            foreach ($datas as $dataid => $unused) {
2545
                if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2546
                    continue;
2547
                }
2548
 
2549
                $context = context_module::instance($cm->id);
2550
                core_tag_tag::delete_instances('mod_data', null, $context->id);
2551
 
2552
            }
2553
        }
1441 ariadna 2554
        $status[] = [
2555
            'component' => $componentstr,
2556
            'item' => get_string('removealldatatags', 'data'),
2557
            'error' => false,
2558
        ];
1 efrain 2559
    }
2560
 
1441 ariadna 2561
    // Updating dates - shift may be negative too.
1 efrain 2562
    if ($data->timeshift) {
2563
        // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
2564
        // See MDL-9367.
1441 ariadna 2565
        shift_course_mod_dates(
2566
            'data',
2567
            [
2568
                'timeavailablefrom',
2569
                'timeavailableto',
2570
                'timeviewfrom',
2571
                'timeviewto',
2572
                'assesstimestart',
2573
                'assesstimefinish',
2574
            ],
2575
            $data->timeshift,
2576
            $data->courseid,
2577
        );
2578
        $status[] = [
2579
            'component' => $componentstr,
2580
            'item' => get_string('date'),
2581
            'error' => false,
2582
        ];
1 efrain 2583
    }
2584
 
2585
    return $status;
2586
}
2587
 
2588
/**
2589
 * Returns all other caps used in module
2590
 *
2591
 * @return array
2592
 */
2593
function data_get_extra_capabilities() {
2594
    return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate',
2595
            'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete'];
2596
}
2597
 
2598
/**
2599
 * @param string $feature FEATURE_xx constant for requested feature
2600
 * @return mixed True if module supports feature, false if not, null if doesn't know or string for the module purpose.
2601
 */
2602
function data_supports($feature) {
2603
    switch($feature) {
2604
        case FEATURE_GROUPS:                  return true;
2605
        case FEATURE_GROUPINGS:               return true;
2606
        case FEATURE_MOD_INTRO:               return true;
2607
        case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
2608
        case FEATURE_COMPLETION_HAS_RULES:    return true;
2609
        case FEATURE_GRADE_HAS_GRADE:         return true;
2610
        case FEATURE_GRADE_OUTCOMES:          return true;
2611
        case FEATURE_RATE:                    return true;
2612
        case FEATURE_BACKUP_MOODLE2:          return true;
2613
        case FEATURE_SHOW_DESCRIPTION:        return true;
2614
        case FEATURE_COMMENT:                 return true;
2615
        case FEATURE_MOD_PURPOSE:             return MOD_PURPOSE_COLLABORATION;
2616
 
2617
        default: return null;
2618
    }
2619
}
2620
 
2621
////////////////////////////////////////////////////////////////////////////////
2622
// File API                                                                   //
2623
////////////////////////////////////////////////////////////////////////////////
2624
 
2625
/**
2626
 * Lists all browsable file areas
2627
 *
2628
 * @package  mod_data
2629
 * @category files
2630
 * @param stdClass $course course object
2631
 * @param stdClass $cm course module object
2632
 * @param stdClass $context context object
2633
 * @return array
2634
 */
2635
function data_get_file_areas($course, $cm, $context) {
2636
    return array('content' => get_string('areacontent', 'mod_data'));
2637
}
2638
 
2639
/**
2640
 * File browsing support for data module.
2641
 *
2642
 * @param file_browser $browser
2643
 * @param array $areas
2644
 * @param stdClass $course
2645
 * @param cm_info $cm
2646
 * @param context $context
2647
 * @param string $filearea
2648
 * @param int $itemid
2649
 * @param string $filepath
2650
 * @param string $filename
2651
 * @return file_info_stored file_info_stored instance or null if not found
2652
 */
2653
function data_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
2654
    global $CFG, $DB, $USER;
2655
 
2656
    if ($context->contextlevel != CONTEXT_MODULE) {
2657
        return null;
2658
    }
2659
 
2660
    if (!isset($areas[$filearea])) {
2661
        return null;
2662
    }
2663
 
2664
    if (is_null($itemid)) {
2665
        require_once($CFG->dirroot.'/mod/data/locallib.php');
2666
        return new data_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
2667
    }
2668
 
2669
    if (!$content = $DB->get_record('data_content', array('id'=>$itemid))) {
2670
        return null;
2671
    }
2672
 
2673
    if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
2674
        return null;
2675
    }
2676
 
2677
    if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
2678
        return null;
2679
    }
2680
 
2681
    if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
2682
        return null;
2683
    }
2684
 
2685
    //check if approved
2686
    if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
2687
        return null;
2688
    }
2689
 
2690
    // group access
2691
    if ($record->groupid) {
2692
        $groupmode = groups_get_activity_groupmode($cm, $course);
2693
        if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
2694
            if (!groups_is_member($record->groupid)) {
2695
                return null;
2696
            }
2697
        }
2698
    }
2699
 
2700
    $fieldobj = data_get_field($field, $data, $cm);
2701
 
2702
    $filepath = is_null($filepath) ? '/' : $filepath;
2703
    $filename = is_null($filename) ? '.' : $filename;
2704
    if (!$fieldobj->file_ok($filepath.$filename)) {
2705
        return null;
2706
    }
2707
 
2708
    $fs = get_file_storage();
2709
    if (!($storedfile = $fs->get_file($context->id, 'mod_data', $filearea, $itemid, $filepath, $filename))) {
2710
        return null;
2711
    }
2712
 
2713
    // Checks to see if the user can manage files or is the owner.
2714
    // TODO MDL-33805 - Do not use userid here and move the capability check above.
2715
    if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
2716
        return null;
2717
    }
2718
 
2719
    $urlbase = $CFG->wwwroot.'/pluginfile.php';
2720
 
2721
    return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
2722
}
2723
 
2724
/**
2725
 * Serves the data attachments. Implements needed access control ;-)
2726
 *
2727
 * @package  mod_data
2728
 * @category files
2729
 * @param stdClass $course course object
2730
 * @param stdClass $cm course module object
2731
 * @param stdClass $context context object
2732
 * @param string $filearea file area
2733
 * @param array $args extra arguments
2734
 * @param bool $forcedownload whether or not force download
2735
 * @param array $options additional options affecting the file serving
2736
 * @return bool false if file not found, does not return if found - justsend the file
2737
 */
2738
function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
2739
    global $CFG, $DB;
2740
 
2741
    if ($context->contextlevel != CONTEXT_MODULE) {
2742
        return false;
2743
    }
2744
 
2745
    require_course_login($course, true, $cm);
2746
 
2747
    if ($filearea === 'content') {
2748
        $contentid = (int)array_shift($args);
2749
 
2750
        if (!$content = $DB->get_record('data_content', array('id'=>$contentid))) {
2751
            return false;
2752
        }
2753
 
2754
        if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
2755
            return false;
2756
        }
2757
 
2758
        if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
2759
            return false;
2760
        }
2761
 
2762
        if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
2763
            return false;
2764
        }
2765
 
2766
        if ($data->id != $cm->instance) {
2767
            // hacker attempt - context does not match the contentid
2768
            return false;
2769
        }
2770
 
2771
        //check if approved
2772
        if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
2773
            return false;
2774
        }
2775
 
2776
        // group access
2777
        if ($record->groupid) {
2778
            $groupmode = groups_get_activity_groupmode($cm, $course);
2779
            if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
2780
                if (!groups_is_member($record->groupid)) {
2781
                    return false;
2782
                }
2783
            }
2784
        }
2785
 
2786
        $fieldobj = data_get_field($field, $data, $cm);
2787
 
2788
        $relativepath = implode('/', $args);
2789
        $fullpath = "/$context->id/mod_data/content/$content->id/$relativepath";
2790
 
2791
        if (!$fieldobj->file_ok($relativepath)) {
2792
            return false;
2793
        }
2794
 
2795
        $fs = get_file_storage();
2796
        if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
2797
            return false;
2798
        }
2799
 
2800
        // finally send the file
2801
        send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
2802
    }
2803
 
2804
    return false;
2805
}
2806
 
2807
 
2808
function data_extend_navigation($navigation, $course, $module, $cm) {
2809
    global $CFG, $OUTPUT, $USER, $DB;
2810
    require_once($CFG->dirroot . '/mod/data/locallib.php');
2811
 
2812
    $rid = optional_param('rid', 0, PARAM_INT);
2813
 
2814
    $data = $DB->get_record('data', array('id'=>$cm->instance));
2815
    $currentgroup = groups_get_activity_group($cm);
2816
    $groupmode = groups_get_activity_groupmode($cm);
2817
 
2818
     $numentries = data_numentries($data);
2819
    $canmanageentries = has_capability('mod/data:manageentries', context_module::instance($cm->id));
2820
 
2821
    if ($data->entriesleft = data_get_entries_left_to_add($data, $numentries, $canmanageentries)) {
2822
        $entriesnode = $navigation->add(get_string('entrieslefttoadd', 'data', $data));
2823
        $entriesnode->add_class('note');
2824
    }
2825
 
2826
    $navigation->add(get_string('list', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance)));
2827
    if (!empty($rid)) {
2828
        $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'rid'=>$rid)));
2829
    } else {
2830
        $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'single')));
2831
    }
2832
    $navigation->add(get_string('search', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'asearch')));
2833
}
2834
 
2835
/**
2836
 * Adds module specific settings to the settings block
2837
 *
2838
 * @param settings_navigation $settings The settings navigation object
2839
 * @param navigation_node $datanode The node to add module settings to
2840
 */
2841
function data_extend_settings_navigation(settings_navigation $settings, navigation_node $datanode) {
2842
    global $DB, $CFG, $USER;
2843
 
2844
    $data = $DB->get_record('data', array("id" => $settings->get_page()->cm->instance));
2845
 
2846
    $currentgroup = groups_get_activity_group($settings->get_page()->cm);
2847
    $groupmode = groups_get_activity_groupmode($settings->get_page()->cm);
2848
 
2849
    // Took out participation list here!
2850
    if (data_user_can_add_entry($data, $currentgroup, $groupmode, $settings->get_page()->cm->context)) {
2851
        if (empty($editentry)) { //TODO: undefined
2852
            $addstring = get_string('add', 'data');
2853
        } else {
2854
            $addstring = get_string('editentry', 'data');
2855
        }
2856
        $addentrynode = $datanode->add($addstring,
2857
            new moodle_url('/mod/data/edit.php', array('d' => $settings->get_page()->cm->instance)));
2858
        $addentrynode->set_show_in_secondary_navigation(false);
2859
    }
2860
 
2861
    if (has_capability(DATA_CAP_EXPORT, $settings->get_page()->cm->context)) {
2862
        // The capability required to Export database records is centrally defined in 'lib.php'
2863
        // and should be weaker than those required to edit Templates, Fields and Presets.
2864
        $exportentriesnode = $datanode->add(get_string('exportentries', 'data'),
2865
            new moodle_url('/mod/data/export.php', array('d' => $data->id)));
2866
        $exportentriesnode->set_show_in_secondary_navigation(false);
2867
    }
2868
    if (has_capability('mod/data:manageentries', $settings->get_page()->cm->context)) {
2869
        $importentriesnode = $datanode->add(get_string('importentries', 'data'),
2870
            new moodle_url('/mod/data/import.php', array('d' => $data->id)));
2871
        $importentriesnode->set_show_in_secondary_navigation(false);
2872
    }
2873
 
2874
    if (has_capability('mod/data:managetemplates', $settings->get_page()->cm->context)) {
2875
        $currenttab = '';
2876
        if ($currenttab == 'list') {
2877
            $defaultemplate = 'listtemplate';
2878
        } else if ($currenttab == 'add') {
2879
            $defaultemplate = 'addtemplate';
2880
        } else if ($currenttab == 'asearch') {
2881
            $defaultemplate = 'asearchtemplate';
2882
        } else {
2883
            $defaultemplate = 'singletemplate';
2884
        }
2885
 
2886
        $datanode->add(get_string('presets', 'data'), new moodle_url('/mod/data/preset.php', array('d' => $data->id)));
2887
        $datanode->add(get_string('fields', 'data'),
2888
            new moodle_url('/mod/data/field.php', array('d' => $data->id)));
2889
        $datanode->add(get_string('templates', 'data'),
2890
            new moodle_url('/mod/data/templates.php', array('d' => $data->id)));
2891
    }
2892
 
2893
    if (!empty($CFG->enablerssfeeds) && !empty($CFG->data_enablerssfeeds) && $data->rssarticles > 0) {
2894
        require_once("$CFG->libdir/rsslib.php");
2895
 
2896
        $string = get_string('rsstype', 'data');
2897
 
2898
        $url = new moodle_url(rss_get_url($settings->get_page()->cm->context->id, $USER->id, 'mod_data', $data->id));
2899
        $datanode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
2900
    }
2901
}
2902
 
2903
/**
2904
 * Running addtional permission check on plugin, for example, plugins
2905
 * may have switch to turn on/off comments option, this callback will
2906
 * affect UI display, not like pluginname_comment_validate only throw
2907
 * exceptions.
2908
 * Capability check has been done in comment->check_permissions(), we
2909
 * don't need to do it again here.
2910
 *
2911
 * @package  mod_data
2912
 * @category comment
2913
 *
2914
 * @param stdClass $comment_param {
2915
 *              context  => context the context object
2916
 *              courseid => int course id
2917
 *              cm       => stdClass course module object
2918
 *              commentarea => string comment area
2919
 *              itemid      => int itemid
2920
 * }
2921
 * @return array
2922
 */
2923
function data_comment_permissions($comment_param) {
2924
    global $CFG, $DB;
2925
    if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
2926
        throw new comment_exception('invalidcommentitemid');
2927
    }
2928
    if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
2929
        throw new comment_exception('invalidid', 'data');
2930
    }
2931
    if ($data->comments) {
2932
        return array('post'=>true, 'view'=>true);
2933
    } else {
2934
        return array('post'=>false, 'view'=>false);
2935
    }
2936
}
2937
 
2938
/**
2939
 * Validate comment parameter before perform other comments actions
2940
 *
2941
 * @package  mod_data
2942
 * @category comment
2943
 *
2944
 * @param stdClass $comment_param {
2945
 *              context  => context the context object
2946
 *              courseid => int course id
2947
 *              cm       => stdClass course module object
2948
 *              commentarea => string comment area
2949
 *              itemid      => int itemid
2950
 * }
2951
 * @return boolean
2952
 */
2953
function data_comment_validate($comment_param) {
2954
    global $DB;
2955
    // validate comment area
2956
    if ($comment_param->commentarea != 'database_entry') {
2957
        throw new comment_exception('invalidcommentarea');
2958
    }
2959
    // validate itemid
2960
    if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
2961
        throw new comment_exception('invalidcommentitemid');
2962
    }
2963
    if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
2964
        throw new comment_exception('invalidid', 'data');
2965
    }
2966
    if (!$course = $DB->get_record('course', array('id'=>$data->course))) {
2967
        throw new comment_exception('coursemisconf');
2968
    }
2969
    if (!$cm = get_coursemodule_from_instance('data', $data->id, $course->id)) {
2970
        throw new comment_exception('invalidcoursemodule');
2971
    }
2972
    if (!$data->comments) {
2973
        throw new comment_exception('commentsoff', 'data');
2974
    }
2975
    $context = context_module::instance($cm->id);
2976
 
2977
    //check if approved
2978
    if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
2979
        throw new comment_exception('notapprovederror', 'data');
2980
    }
2981
 
2982
    // group access
2983
    if ($record->groupid) {
2984
        $groupmode = groups_get_activity_groupmode($cm, $course);
2985
        if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
2986
            if (!groups_is_member($record->groupid)) {
2987
                throw new comment_exception('notmemberofgroup');
2988
            }
2989
        }
2990
    }
2991
    // validate context id
2992
    if ($context->id != $comment_param->context->id) {
2993
        throw new comment_exception('invalidcontext');
2994
    }
2995
    // validation for comment deletion
2996
    if (!empty($comment_param->commentid)) {
2997
        if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
2998
            if ($comment->commentarea != 'database_entry') {
2999
                throw new comment_exception('invalidcommentarea');
3000
            }
3001
            if ($comment->contextid != $comment_param->context->id) {
3002
                throw new comment_exception('invalidcontext');
3003
            }
3004
            if ($comment->itemid != $comment_param->itemid) {
3005
                throw new comment_exception('invalidcommentitemid');
3006
            }
3007
        } else {
3008
            throw new comment_exception('invalidcommentid');
3009
        }
3010
    }
3011
    return true;
3012
}
3013
 
3014
/**
3015
 * Return a list of page types
3016
 * @param string $pagetype current page type
3017
 * @param stdClass $parentcontext Block's parent context
3018
 * @param stdClass $currentcontext Current context of block
3019
 */
3020
function data_page_type_list($pagetype, $parentcontext, $currentcontext) {
3021
    $module_pagetype = array('mod-data-*'=>get_string('page-mod-data-x', 'data'));
3022
    return $module_pagetype;
3023
}
3024
 
3025
/**
3026
 * Get all of the record ids from a database activity.
3027
 *
3028
 * @param int    $dataid      The dataid of the database module.
3029
 * @param object $selectdata  Contains an additional sql statement for the
3030
 *                            where clause for group and approval fields.
3031
 * @param array  $params      Parameters that coincide with the sql statement.
3032
 * @return array $idarray     An array of record ids
3033
 */
3034
function data_get_all_recordids($dataid, $selectdata = '', $params = null) {
3035
    global $DB;
3036
    $initsql = 'SELECT r.id
3037
                  FROM {data_records} r
3038
                 WHERE r.dataid = :dataid';
3039
    if ($selectdata != '') {
3040
        $initsql .= $selectdata;
3041
        $params = array_merge(array('dataid' => $dataid), $params);
3042
    } else {
3043
        $params = array('dataid' => $dataid);
3044
    }
3045
    $initsql .= ' GROUP BY r.id';
3046
    $initrecord = $DB->get_recordset_sql($initsql, $params);
3047
    $idarray = array();
3048
    foreach ($initrecord as $data) {
3049
        $idarray[] = $data->id;
3050
    }
3051
    // Close the record set and free up resources.
3052
    $initrecord->close();
3053
    return $idarray;
3054
}
3055
 
3056
/**
3057
 * Get the ids of all the records that match that advanced search criteria
3058
 * This goes and loops through each criterion one at a time until it either
3059
 * runs out of records or returns a subset of records.
3060
 *
3061
 * @param array $recordids    An array of record ids.
3062
 * @param array $searcharray  Contains information for the advanced search criteria
3063
 * @param int $dataid         The data id of the database.
3064
 * @return array $recordids   An array of record ids.
3065
 */
3066
function data_get_advance_search_ids($recordids, $searcharray, $dataid) {
3067
    // Check to see if we have any record IDs.
3068
    if (empty($recordids)) {
3069
        // Send back an empty search.
3070
        return array();
3071
    }
3072
    $searchcriteria = array_keys($searcharray);
3073
    // Loop through and reduce the IDs one search criteria at a time.
3074
    foreach ($searchcriteria as $key) {
3075
        $recordids = data_get_recordids($key, $searcharray, $dataid, $recordids);
3076
        // If we don't have anymore IDs then stop.
3077
        if (!$recordids) {
3078
            break;
3079
        }
3080
    }
3081
    return $recordids;
3082
}
3083
 
3084
/**
3085
 * Gets the record IDs given the search criteria
3086
 *
3087
 * @param string $alias       Record alias.
3088
 * @param array $searcharray  Criteria for the search.
3089
 * @param int $dataid         Data ID for the database
3090
 * @param array $recordids    An array of record IDs.
3091
 * @return array $nestarray   An arry of record IDs
3092
 */
3093
function data_get_recordids($alias, $searcharray, $dataid, $recordids) {
3094
    global $DB;
3095
    $searchcriteria = $alias;   // Keep the criteria.
3096
    $nestsearch = $searcharray[$alias];
3097
    // searching for content outside of mdl_data_content
3098
    if ($alias < 0) {
3099
        $alias = '';
3100
    }
3101
    list($insql, $params) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED);
3102
    $nestselect = 'SELECT c' . $alias . '.recordid
3103
                     FROM {data_content} c' . $alias . '
3104
               INNER JOIN {data_fields} f
3105
                       ON f.id = c' . $alias . '.fieldid
3106
               INNER JOIN {data_records} r
3107
                       ON r.id = c' . $alias . '.recordid
3108
               INNER JOIN {user} u
3109
                       ON u.id = r.userid ';
3110
    $nestwhere = 'WHERE r.dataid = :dataid
3111
                    AND c' . $alias .'.recordid ' . $insql . '
3112
                    AND ';
3113
 
3114
    $params['dataid'] = $dataid;
3115
    if (count($nestsearch->params) != 0) {
3116
        $params = array_merge($params, $nestsearch->params);
3117
        $nestsql = $nestselect . $nestwhere . $nestsearch->sql;
3118
    } else if ($searchcriteria == DATA_TIMEMODIFIED) {
3119
        $nestsql = $nestselect . $nestwhere . $nestsearch->field . ' >= :timemodified GROUP BY c' . $alias . '.recordid';
3120
        $params['timemodified'] = $nestsearch->data;
3121
    } else if ($searchcriteria == DATA_TAGS) {
3122
        if (empty($nestsearch->rawtagnames)) {
3123
            return [];
3124
        }
3125
        $i = 0;
3126
        $tagwhere = [];
3127
        $tagselect = '';
3128
        foreach ($nestsearch->rawtagnames as $tagrawname) {
3129
            $tagselect .= " INNER JOIN {tag_instance} ti_$i
3130
                                    ON ti_$i.component = 'mod_data'
3131
                                   AND ti_$i.itemtype = 'data_records'
3132
                                   AND ti_$i.itemid = r.id
3133
                            INNER JOIN {tag} t_$i
3134
                                    ON ti_$i.tagid = t_$i.id ";
3135
            $tagwhere[] = " t_$i.rawname = :trawname_$i ";
3136
            $params["trawname_$i"] = $tagrawname;
3137
            $i++;
3138
        }
3139
        $nestsql = $nestselect . $tagselect . $nestwhere . implode(' AND ', $tagwhere);
3140
    } else {    // First name or last name.
3141
        $thing = $DB->sql_like($nestsearch->field, ':search1', false);
3142
        $nestsql = $nestselect . $nestwhere . $thing . ' GROUP BY c' . $alias . '.recordid';
3143
        $params['search1'] = "%$nestsearch->data%";
3144
    }
3145
    $nestrecords = $DB->get_recordset_sql($nestsql, $params);
3146
    $nestarray = array();
3147
    foreach ($nestrecords as $data) {
3148
        $nestarray[] = $data->recordid;
3149
    }
3150
    // Close the record set and free up resources.
3151
    $nestrecords->close();
3152
    return $nestarray;
3153
}
3154
 
3155
/**
3156
 * Returns an array with an sql string for advanced searches and the parameters that go with them.
3157
 *
3158
 * @param int $sort            DATA_*
3159
 * @param stdClass $data       Data module object
3160
 * @param array $recordids     An array of record IDs.
3161
 * @param string $selectdata   Information for the where and select part of the sql statement.
3162
 * @param string $sortorder    Additional sort parameters
3163
 * @return array sqlselect     sqlselect['sql'] has the sql string, sqlselect['params'] contains an array of parameters.
3164
 */
3165
function data_get_advanced_search_sql($sort, $data, $recordids, $selectdata, $sortorder) {
3166
    global $DB;
3167
 
3168
    $userfieldsapi = \core_user\fields::for_userpic()->excluding('id');
3169
    $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
3170
 
3171
    if ($sort == 0) {
3172
        $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . '
3173
                        FROM {data_content} c,
3174
                             {data_records} r,
3175
                             {user} u ';
3176
        $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, u.firstname, u.lastname, ' . $namefields;
3177
    } else {
3178
        // Sorting through 'Other' criteria
3179
        if ($sort <= 0) {
3180
            switch ($sort) {
3181
                case DATA_LASTNAME:
3182
                    $sortcontentfull = "u.lastname";
3183
                    break;
3184
                case DATA_FIRSTNAME:
3185
                    $sortcontentfull = "u.firstname";
3186
                    break;
3187
                case DATA_APPROVED:
3188
                    $sortcontentfull = "r.approved";
3189
                    break;
3190
                case DATA_TIMEMODIFIED:
3191
                    $sortcontentfull = "r.timemodified";
3192
                    break;
3193
                case DATA_TIMEADDED:
3194
                default:
3195
                    $sortcontentfull = "r.timecreated";
3196
            }
3197
        } else {
3198
            $sortfield = data_get_field_from_id($sort, $data);
3199
            $sortcontent = $DB->sql_compare_text('c.' . $sortfield->get_sort_field());
3200
            $sortcontentfull = $sortfield->get_sort_sql($sortcontent);
3201
        }
3202
 
3203
        $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ',
3204
                                 ' . $sortcontentfull . '
3205
                              AS sortorder
3206
                            FROM {data_content} c,
3207
                                 {data_records} r,
3208
                                 {user} u ';
3209
        $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ', ' .$sortcontentfull;
3210
    }
3211
 
3212
    // Default to a standard Where statement if $selectdata is empty.
3213
    if ($selectdata == '') {
3214
        $selectdata = 'WHERE c.recordid = r.id
3215
                         AND r.dataid = :dataid
3216
                         AND r.userid = u.id ';
3217
    }
3218
 
3219
    // Find the field we are sorting on
3220
    if ($sort > 0 or data_get_field_from_id($sort, $data)) {
3221
        $selectdata .= ' AND c.fieldid = :sort AND s.recordid = r.id';
3222
        $nestselectsql .= ',{data_content} s ';
3223
    }
3224
 
3225
    // If there are no record IDs then return an sql statment that will return no rows.
3226
    if (count($recordids) != 0) {
3227
        list($insql, $inparam) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED);
3228
    } else {
3229
        list($insql, $inparam) = $DB->get_in_or_equal(array('-1'), SQL_PARAMS_NAMED);
3230
    }
3231
    $nestfromsql = $selectdata . ' AND c.recordid ' . $insql . $groupsql;
3232
    $sqlselect['sql'] = "$nestselectsql $nestfromsql $sortorder";
3233
    $sqlselect['params'] = $inparam;
3234
    return $sqlselect;
3235
}
3236
 
3237
/**
3238
 * Delete a record entry.
3239
 *
3240
 * @param int $recordid The ID for the record to be deleted.
3241
 * @param object $data The data object for this activity.
3242
 * @param int $courseid ID for the current course (for logging).
3243
 * @param int $cmid The course module ID.
3244
 * @return bool True if the record deleted, false if not.
3245
 */
3246
function data_delete_record($recordid, $data, $courseid, $cmid) {
3247
    global $DB, $CFG;
3248
 
3249
    if ($deleterecord = $DB->get_record('data_records', array('id' => $recordid))) {
3250
        if ($deleterecord->dataid == $data->id) {
3251
            if ($contents = $DB->get_records('data_content', array('recordid' => $deleterecord->id))) {
3252
                foreach ($contents as $content) {
3253
                    if ($field = data_get_field_from_id($content->fieldid, $data)) {
3254
                        $field->delete_content($content->recordid);
3255
                    }
3256
                }
3257
                $DB->delete_records('data_content', array('recordid'=>$deleterecord->id));
3258
                $DB->delete_records('data_records', array('id'=>$deleterecord->id));
3259
 
3260
                // Delete cached RSS feeds.
3261
                if (!empty($CFG->enablerssfeeds)) {
3262
                    require_once($CFG->dirroot.'/mod/data/rsslib.php');
3263
                    data_rss_delete_file($data);
3264
                }
3265
 
3266
                core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $recordid);
3267
 
3268
                // Trigger an event for deleting this record.
3269
                $event = \mod_data\event\record_deleted::create(array(
3270
                    'objectid' => $deleterecord->id,
3271
                    'context' => context_module::instance($cmid),
3272
                    'courseid' => $courseid,
3273
                    'other' => array(
3274
                        'dataid' => $deleterecord->dataid
3275
                    )
3276
                ));
3277
                $event->add_record_snapshot('data_records', $deleterecord);
3278
                $event->trigger();
3279
                $course = get_course($courseid);
3280
                $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
3281
                data_update_completion_state($data, $course, $cm);
3282
 
3283
                return true;
3284
            }
3285
        }
3286
    }
3287
 
3288
    return false;
3289
}
3290
 
3291
/**
3292
 * Check for required fields, and build a list of fields to be updated in a
3293
 * submission.
3294
 *
3295
 * @param $mod stdClass The current recordid - provided as an optimisation.
3296
 * @param $fields array The field data
3297
 * @param $datarecord stdClass The submitted data.
3298
 * @return stdClass containing:
3299
 * * string[] generalnotifications Notifications for the form as a whole.
3300
 * * string[] fieldnotifications Notifications for a specific field.
3301
 * * bool validated Whether the field was validated successfully.
3302
 * * data_field_base[] fields The field objects to be update.
3303
 */
3304
function data_process_submission(stdClass $mod, $fields, stdClass $datarecord) {
3305
    $result = new stdClass();
3306
 
3307
    // Empty form checking - you can't submit an empty form.
3308
    $emptyform = true;
3309
    $requiredfieldsfilled = true;
3310
    $fieldsvalidated = true;
3311
 
3312
    // Store the notifications.
3313
    $result->generalnotifications = array();
3314
    $result->fieldnotifications = array();
3315
 
3316
    // Store the instantiated classes as an optimisation when processing the result.
3317
    // This prevents the fields being re-initialised when updating.
3318
    $result->fields = array();
3319
 
3320
    $submitteddata = array();
3321
    foreach ($datarecord as $fieldname => $fieldvalue) {
3322
        if (strpos($fieldname, '_')) {
3323
            $namearray = explode('_', $fieldname, 3);
3324
            $fieldid = $namearray[1];
3325
            if (!isset($submitteddata[$fieldid])) {
3326
                $submitteddata[$fieldid] = array();
3327
            }
3328
            if (count($namearray) === 2) {
3329
                $subfieldid = 0;
3330
            } else {
3331
                $subfieldid = $namearray[2];
3332
            }
3333
 
3334
            $fielddata = new stdClass();
3335
            $fielddata->fieldname = $fieldname;
3336
            $fielddata->value = $fieldvalue;
3337
            $submitteddata[$fieldid][$subfieldid] = $fielddata;
3338
        }
3339
    }
3340
 
3341
    // Check all form fields which have the required are filled.
3342
    foreach ($fields as $fieldrecord) {
3343
        // Check whether the field has any data.
3344
        $fieldhascontent = false;
3345
 
3346
        $field = data_get_field($fieldrecord, $mod);
3347
        if (isset($submitteddata[$fieldrecord->id])) {
3348
            // Field validation check.
3349
            if (method_exists($field, 'field_validation')) {
3350
                $errormessage = $field->field_validation($submitteddata[$fieldrecord->id]);
3351
                if ($errormessage) {
3352
                    $result->fieldnotifications[$field->field->name][] = $errormessage;
3353
                    $fieldsvalidated = false;
3354
                }
3355
            }
3356
            foreach ($submitteddata[$fieldrecord->id] as $fieldname => $value) {
3357
                if ($field->notemptyfield($value->value, $value->fieldname)) {
3358
                    // The field has content and the form is not empty.
3359
                    $fieldhascontent = true;
3360
                    $emptyform = false;
3361
                }
3362
            }
3363
        }
3364
 
3365
        // If the field is required, add a notification to that effect.
3366
        if ($field->field->required && !$fieldhascontent) {
3367
            if (!isset($result->fieldnotifications[$field->field->name])) {
3368
                $result->fieldnotifications[$field->field->name] = array();
3369
            }
3370
            $result->fieldnotifications[$field->field->name][] = get_string('errormustsupplyvalue', 'data');
3371
            $requiredfieldsfilled = false;
3372
        }
3373
 
3374
        // Update the field.
3375
        if (isset($submitteddata[$fieldrecord->id])) {
3376
            foreach ($submitteddata[$fieldrecord->id] as $value) {
3377
                $result->fields[$value->fieldname] = $field;
3378
            }
3379
        }
3380
    }
3381
 
3382
    if ($emptyform) {
3383
        // The form is empty.
3384
        $result->generalnotifications[] = get_string('emptyaddform', 'data');
3385
    }
3386
 
3387
    $result->validated = $requiredfieldsfilled && !$emptyform && $fieldsvalidated;
3388
 
3389
    return $result;
3390
}
3391
 
3392
/**
3393
 * This standard function will check all instances of this module
3394
 * and make sure there are up-to-date events created for each of them.
3395
 * If courseid = 0, then every data event in the site is checked, else
3396
 * only data events belonging to the course specified are checked.
3397
 * This function is used, in its new format, by restore_refresh_events()
3398
 *
3399
 * @param int $courseid
3400
 * @param int|stdClass $instance Data module instance or ID.
3401
 * @param int|stdClass $cm Course module object or ID (not used in this module).
3402
 * @return bool
3403
 */
3404
function data_refresh_events($courseid = 0, $instance = null, $cm = null) {
3405
    global $DB, $CFG;
3406
    require_once($CFG->dirroot.'/mod/data/locallib.php');
3407
 
3408
    // If we have instance information then we can just update the one event instead of updating all events.
3409
    if (isset($instance)) {
3410
        if (!is_object($instance)) {
3411
            $instance = $DB->get_record('data', array('id' => $instance), '*', MUST_EXIST);
3412
        }
3413
        data_set_events($instance);
3414
        return true;
3415
    }
3416
 
3417
    if ($courseid) {
3418
        if (! $data = $DB->get_records("data", array("course" => $courseid))) {
3419
            return true;
3420
        }
3421
    } else {
3422
        if (! $data = $DB->get_records("data")) {
3423
            return true;
3424
        }
3425
    }
3426
 
3427
    foreach ($data as $datum) {
3428
        data_set_events($datum);
3429
    }
3430
    return true;
3431
}
3432
 
3433
/**
3434
 * Fetch the configuration for this database activity.
3435
 *
3436
 * @param   stdClass    $database   The object returned from the database for this instance
3437
 * @param   string      $key        The name of the key to retrieve. If none is supplied, then all configuration is returned
3438
 * @param   mixed       $default    The default value to use if no value was found for the specified key
3439
 * @return  mixed                   The returned value
3440
 */
3441
function data_get_config($database, $key = null, $default = null) {
3442
    if (!empty($database->config)) {
3443
        $config = json_decode($database->config);
3444
    } else {
3445
        $config = new stdClass();
3446
    }
3447
 
3448
    if ($key === null) {
3449
        return $config;
3450
    }
3451
 
3452
    if (property_exists($config, $key)) {
3453
        return $config->$key;
3454
    }
3455
    return $default;
3456
}
3457
 
3458
/**
3459
 * Update the configuration for this database activity.
3460
 *
3461
 * @param   stdClass    $database   The object returned from the database for this instance
3462
 * @param   string      $key        The name of the key to set
3463
 * @param   mixed       $value      The value to set for the key
3464
 */
3465
function data_set_config(&$database, $key, $value) {
3466
    // Note: We must pass $database by reference because there may be subsequent calls to update_record and these should
3467
    // not overwrite the configuration just set.
3468
    global $DB;
3469
 
3470
    $config = data_get_config($database);
3471
 
3472
    if (!isset($config->$key) || $config->$key !== $value) {
3473
        $config->$key = $value;
3474
        $database->config = json_encode($config);
3475
        $DB->set_field('data', 'config', $database->config, ['id' => $database->id]);
3476
    }
3477
}
3478
/**
3479
 * Sets the automatic completion state for this database item based on the
3480
 * count of on its entries.
3481
 * @since Moodle 3.3
3482
 * @param object $data The data object for this activity
3483
 * @param object $course Course
3484
 * @param object $cm course-module
3485
 */
3486
function data_update_completion_state($data, $course, $cm) {
3487
    // If completion option is enabled, evaluate it and return true/false.
3488
    $completion = new completion_info($course);
3489
    if ($data->completionentries && $completion->is_enabled($cm)) {
3490
        $numentries = data_numentries($data);
3491
        // Check the number of entries required against the number of entries already made.
3492
        if ($numentries >= $data->completionentries) {
3493
            $completion->update_state($cm, COMPLETION_COMPLETE);
3494
        } else {
3495
            $completion->update_state($cm, COMPLETION_INCOMPLETE);
3496
        }
3497
    }
3498
}
3499
 
1441 ariadna 3500
 
1 efrain 3501
/**
1441 ariadna 3502
 * Get the list of deprecated icons.
1 efrain 3503
 *
1441 ariadna 3504
 * @return array with the deprecated key icons.
3505
 * @todo Final deprecation on Moodle 6.0 MDL-83465.
1 efrain 3506
 */
1441 ariadna 3507
function mod_data_get_deprecated_icons() {
1 efrain 3508
    return [
1441 ariadna 3509
        // Deprecated since Moodle 4.6.
3510
        // See MDL-82313 for refactoring.
3511
        'mod_data:field/checkbox' => 'fa-regular fa-square-check',
3512
        'mod_data:field/date' => 'fa-regular fa-calendar',
3513
        'mod_data:field/file' => 'fa-regular fa-file',
3514
        'mod_data:field/latlong' => 'fa-earth-americas',
1 efrain 3515
        'mod_data:field/menu' => 'fa-bars',
3516
        'mod_data:field/multimenu' => 'fa-bars',
3517
        'mod_data:field/number' => 'fa-hashtag',
1441 ariadna 3518
        'mod_data:field/picture' => 'fa-regular fa-image',
3519
        'mod_data:field/radiobutton' => 'fa-regular fa-circle-dot',
3520
        'mod_data:field/text' => 'fa-i-cursor',
1 efrain 3521
        'mod_data:field/textarea' => 'fa-font',
3522
        'mod_data:field/url' => 'fa-link',
3523
    ];
3524
}
3525
 
3526
/*
3527
 * Check if the module has any update that affects the current user since a given time.
3528
 *
3529
 * @param  cm_info $cm course module data
3530
 * @param  int $from the time to check updates from
3531
 * @param  array $filter  if we need to check only specific updates
3532
 * @return stdClass an object with the different type of areas indicating if they were updated or not
3533
 * @since Moodle 3.2
3534
 */
3535
function data_check_updates_since(cm_info $cm, $from, $filter = array()) {
3536
    global $DB, $CFG;
3537
    require_once($CFG->dirroot . '/mod/data/locallib.php');
3538
 
3539
    $updates = course_check_module_updates_since($cm, $from, array(), $filter);
3540
 
3541
    // Check for new entries.
3542
    $updates->entries = (object) array('updated' => false);
3543
 
3544
    $data = $DB->get_record('data', array('id' => $cm->instance), '*', MUST_EXIST);
3545
    $searcharray = [];
3546
    $searcharray[DATA_TIMEMODIFIED] = new stdClass();
3547
    $searcharray[DATA_TIMEMODIFIED]->sql     = '';
3548
    $searcharray[DATA_TIMEMODIFIED]->params  = array();
3549
    $searcharray[DATA_TIMEMODIFIED]->field   = 'r.timemodified';
3550
    $searcharray[DATA_TIMEMODIFIED]->data    = $from;
3551
 
3552
    $currentgroup = groups_get_activity_group($cm);
3553
    // Teachers should retrieve all entries when not in separate groups.
3554
    if (has_capability('mod/data:manageentries', $cm->context) && groups_get_activity_groupmode($cm) != SEPARATEGROUPS) {
3555
        $currentgroup = 0;
3556
    }
3557
    list($entries, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
3558
        data_search_entries($data, $cm, $cm->context, 'list', $currentgroup, '', null, null, 0, 0, true, $searcharray);
3559
 
3560
    if (!empty($entries)) {
3561
        $updates->entries->updated = true;
3562
        $updates->entries->itemids = array_keys($entries);
3563
    }
3564
 
3565
    return $updates;
3566
}
3567
 
3568
/**
3569
 * This function receives a calendar event and returns the action associated with it, or null if there is none.
3570
 *
3571
 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
3572
 * is not displayed on the block.
3573
 *
3574
 * @param calendar_event $event
3575
 * @param \core_calendar\action_factory $factory
3576
 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
3577
 * @return \core_calendar\local\event\entities\action_interface|null
3578
 */
3579
function mod_data_core_calendar_provide_event_action(calendar_event $event,
3580
                                                     \core_calendar\action_factory $factory,
3581
                                                     int $userid = 0) {
3582
    global $USER;
3583
 
3584
    if (!$userid) {
3585
        $userid = $USER->id;
3586
    }
3587
 
3588
    $cm = get_fast_modinfo($event->courseid, $userid)->instances['data'][$event->instance];
3589
 
3590
    if (!$cm->uservisible) {
3591
        // The module is not visible to the user for any reason.
3592
        return null;
3593
    }
3594
 
3595
    $now = time();
3596
 
3597
    if (!empty($cm->customdata['timeavailableto']) && $cm->customdata['timeavailableto'] < $now) {
3598
        // The module has closed so the user can no longer submit anything.
3599
        return null;
3600
    }
3601
 
3602
    // The module is actionable if we don't have a start time or the start time is
3603
    // in the past.
3604
    $actionable = (empty($cm->customdata['timeavailablefrom']) || $cm->customdata['timeavailablefrom'] <= $now);
3605
 
3606
    return $factory->create_instance(
3607
        get_string('add', 'data'),
3608
        new \moodle_url('/mod/data/view.php', array('id' => $cm->id)),
3609
        1,
3610
        $actionable
3611
    );
3612
}
3613
 
3614
/**
3615
 * Add a get_coursemodule_info function in case any database type wants to add 'extra' information
3616
 * for the course (see resource).
3617
 *
3618
 * Given a course_module object, this function returns any "extra" information that may be needed
3619
 * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
3620
 *
3621
 * @param stdClass $coursemodule The coursemodule object (record).
3622
 * @return cached_cm_info An object on information that the courses
3623
 *                        will know about (most noticeably, an icon).
3624
 */
3625
function data_get_coursemodule_info($coursemodule) {
3626
    global $DB;
3627
 
3628
    $dbparams = ['id' => $coursemodule->instance];
3629
    $fields = 'id, name, intro, introformat, completionentries, timeavailablefrom, timeavailableto';
3630
    if (!$data = $DB->get_record('data', $dbparams, $fields)) {
3631
        return false;
3632
    }
3633
 
3634
    $result = new cached_cm_info();
3635
    $result->name = $data->name;
3636
 
3637
    if ($coursemodule->showdescription) {
3638
        // Convert intro to html. Do not filter cached version, filters run at display time.
3639
        $result->content = format_module_intro('data', $data, $coursemodule->id, false);
3640
    }
3641
 
3642
    // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
3643
    if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
3644
        $result->customdata['customcompletionrules']['completionentries'] = $data->completionentries;
3645
    }
3646
    // Other properties that may be used in calendar or on dashboard.
3647
    if ($data->timeavailablefrom) {
3648
        $result->customdata['timeavailablefrom'] = $data->timeavailablefrom;
3649
    }
3650
    if ($data->timeavailableto) {
3651
        $result->customdata['timeavailableto'] = $data->timeavailableto;
3652
    }
3653
 
3654
    return $result;
3655
}
3656
 
3657
/**
3658
 * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
3659
 *
3660
 * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
3661
 * @return array $descriptions the array of descriptions for the custom rules.
3662
 */
3663
function mod_data_get_completion_active_rule_descriptions($cm) {
3664
    // Values will be present in cm_info, and we assume these are up to date.
3665
    if (empty($cm->customdata['customcompletionrules'])
3666
        || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
3667
        return [];
3668
    }
3669
 
3670
    $descriptions = [];
3671
    foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
3672
        switch ($key) {
3673
            case 'completionentries':
3674
                if (!empty($val)) {
3675
                    $descriptions[] = get_string('completionentriesdesc', 'data', $val);
3676
                }
3677
                break;
3678
            default:
3679
                break;
3680
        }
3681
    }
3682
    return $descriptions;
3683
}
3684
 
3685
/**
3686
 * This function calculates the minimum and maximum cutoff values for the timestart of
3687
 * the given event.
3688
 *
3689
 * It will return an array with two values, the first being the minimum cutoff value and
3690
 * the second being the maximum cutoff value. Either or both values can be null, which
3691
 * indicates there is no minimum or maximum, respectively.
3692
 *
3693
 * If a cutoff is required then the function must return an array containing the cutoff
3694
 * timestamp and error string to display to the user if the cutoff value is violated.
3695
 *
3696
 * A minimum and maximum cutoff return value will look like:
3697
 * [
3698
 *     [1505704373, 'The due date must be after the sbumission start date'],
3699
 *     [1506741172, 'The due date must be before the cutoff date']
3700
 * ]
3701
 *
3702
 * @param calendar_event $event The calendar event to get the time range for
3703
 * @param stdClass $instance The module instance to get the range from
3704
 * @return array
3705
 */
3706
function mod_data_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
3707
    $mindate = null;
3708
    $maxdate = null;
3709
 
3710
    if ($event->eventtype == DATA_EVENT_TYPE_OPEN) {
3711
        // The start time of the open event can't be equal to or after the
3712
        // close time of the database activity.
3713
        if (!empty($instance->timeavailableto)) {
3714
            $maxdate = [
3715
                $instance->timeavailableto,
3716
                get_string('openafterclose', 'data')
3717
            ];
3718
        }
3719
    } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) {
3720
        // The start time of the close event can't be equal to or earlier than the
3721
        // open time of the database activity.
3722
        if (!empty($instance->timeavailablefrom)) {
3723
            $mindate = [
3724
                $instance->timeavailablefrom,
3725
                get_string('closebeforeopen', 'data')
3726
            ];
3727
        }
3728
    }
3729
 
3730
    return [$mindate, $maxdate];
3731
}
3732
 
3733
/**
3734
 * This function will update the data module according to the
3735
 * event that has been modified.
3736
 *
3737
 * It will set the timeopen or timeclose value of the data instance
3738
 * according to the type of event provided.
3739
 *
3740
 * @throws \moodle_exception
3741
 * @param \calendar_event $event
3742
 * @param stdClass $data The module instance to get the range from
3743
 */
3744
function mod_data_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $data) {
3745
    global $DB;
3746
 
3747
    if (empty($event->instance) || $event->modulename != 'data') {
3748
        return;
3749
    }
3750
 
3751
    if ($event->instance != $data->id) {
3752
        return;
3753
    }
3754
 
3755
    if (!in_array($event->eventtype, [DATA_EVENT_TYPE_OPEN, DATA_EVENT_TYPE_CLOSE])) {
3756
        return;
3757
    }
3758
 
3759
    $courseid = $event->courseid;
3760
    $modulename = $event->modulename;
3761
    $instanceid = $event->instance;
3762
    $modified = false;
3763
 
3764
    $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
3765
    $context = context_module::instance($coursemodule->id);
3766
 
3767
    // The user does not have the capability to modify this activity.
3768
    if (!has_capability('moodle/course:manageactivities', $context)) {
3769
        return;
3770
    }
3771
 
3772
    if ($event->eventtype == DATA_EVENT_TYPE_OPEN) {
3773
        // If the event is for the data activity opening then we should
3774
        // set the start time of the data activity to be the new start
3775
        // time of the event.
3776
        if ($data->timeavailablefrom != $event->timestart) {
3777
            $data->timeavailablefrom = $event->timestart;
3778
            $data->timemodified = time();
3779
            $modified = true;
3780
        }
3781
    } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) {
3782
        // If the event is for the data activity closing then we should
3783
        // set the end time of the data activity to be the new start
3784
        // time of the event.
3785
        if ($data->timeavailableto != $event->timestart) {
3786
            $data->timeavailableto = $event->timestart;
3787
            $modified = true;
3788
        }
3789
    }
3790
 
3791
    if ($modified) {
3792
        $data->timemodified = time();
3793
        $DB->update_record('data', $data);
3794
        $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
3795
        $event->trigger();
3796
    }
3797
}
3798
 
3799
/**
3800
 * Callback to fetch the activity event type lang string.
3801
 *
3802
 * @param string $eventtype The event type.
3803
 * @return lang_string The event type lang string.
3804
 */
3805
function mod_data_core_calendar_get_event_action_string(string $eventtype): string {
3806
    $modulename = get_string('modulename', 'data');
3807
 
3808
    switch ($eventtype) {
3809
        case DATA_EVENT_TYPE_OPEN:
3810
            $identifier = 'calendarstart';
3811
            break;
3812
        case DATA_EVENT_TYPE_CLOSE:
3813
            $identifier = 'calendarend';
3814
            break;
3815
        default:
3816
            return get_string('requiresaction', 'calendar', $modulename);
3817
    }
3818
 
3819
    return get_string($identifier, 'data', $modulename);
3820
}