MDL-59287 calendar_events: Update modules to create all events.
[moodle.git] / mod / data / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package   mod_data
20  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 defined('MOODLE_INTERNAL') || die();
26 // Some constants
27 define ('DATA_MAX_ENTRIES', 50);
28 define ('DATA_PERPAGE_SINGLE', 1);
30 define ('DATA_FIRSTNAME', -1);
31 define ('DATA_LASTNAME', -2);
32 define ('DATA_APPROVED', -3);
33 define ('DATA_TIMEADDED', 0);
34 define ('DATA_TIMEMODIFIED', -4);
36 define ('DATA_CAP_EXPORT', 'mod/data:viewalluserpresets');
38 define('DATA_PRESET_COMPONENT', 'mod_data');
39 define('DATA_PRESET_FILEAREA', 'site_presets');
40 define('DATA_PRESET_CONTEXT', SYSCONTEXTID);
42 define('DATA_EVENT_TYPE_OPEN', 'open');
43 define('DATA_EVENT_TYPE_CLOSE', 'close');
45 // Users having assigned the default role "Non-editing teacher" can export database records
46 // Using the mod/data capability "viewalluserpresets" existing in Moodle 1.9.x.
47 // In Moodle >= 2, new roles may be introduced and used instead.
49 /**
50  * @package   mod_data
51  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
52  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
53  */
54 class data_field_base {     // Base class for Database Field Types (see field/*/field.class.php)
56     /** @var string Subclasses must override the type with their name */
57     var $type = 'unknown';
58     /** @var object The database object that this field belongs to */
59     var $data = NULL;
60     /** @var object The field object itself, if we know it */
61     var $field = NULL;
62     /** @var int Width of the icon for this fieldtype */
63     var $iconwidth = 16;
64     /** @var int Width of the icon for this fieldtype */
65     var $iconheight = 16;
66     /** @var object course module or cmifno */
67     var $cm;
68     /** @var object activity context */
69     var $context;
70     /** @var priority for globalsearch indexing */
71     protected static $priority = self::NO_PRIORITY;
72     /** priority value for invalid fields regarding indexing */
73     const NO_PRIORITY = 0;
74     /** priority value for minimum priority */
75     const MIN_PRIORITY = 1;
76     /** priority value for low priority */
77     const LOW_PRIORITY = 2;
78     /** priority value for high priority */
79     const HIGH_PRIORITY = 3;
80     /** priority value for maximum priority */
81     const MAX_PRIORITY = 4;
83     /**
84      * Constructor function
85      *
86      * @global object
87      * @uses CONTEXT_MODULE
88      * @param int $field
89      * @param int $data
90      * @param int $cm
91      */
92     function __construct($field=0, $data=0, $cm=0) {   // Field or data or both, each can be id or object
93         global $DB;
95         if (empty($field) && empty($data)) {
96             print_error('missingfield', 'data');
97         }
99         if (!empty($field)) {
100             if (is_object($field)) {
101                 $this->field = $field;  // Programmer knows what they are doing, we hope
102             } else if (!$this->field = $DB->get_record('data_fields', array('id'=>$field))) {
103                 print_error('invalidfieldid', 'data');
104             }
105             if (empty($data)) {
106                 if (!$this->data = $DB->get_record('data', array('id'=>$this->field->dataid))) {
107                     print_error('invalidid', 'data');
108                 }
109             }
110         }
112         if (empty($this->data)) {         // We need to define this properly
113             if (!empty($data)) {
114                 if (is_object($data)) {
115                     $this->data = $data;  // Programmer knows what they are doing, we hope
116                 } else if (!$this->data = $DB->get_record('data', array('id'=>$data))) {
117                     print_error('invalidid', 'data');
118                 }
119             } else {                      // No way to define it!
120                 print_error('missingdata', 'data');
121             }
122         }
124         if ($cm) {
125             $this->cm = $cm;
126         } else {
127             $this->cm = get_coursemodule_from_instance('data', $this->data->id);
128         }
130         if (empty($this->field)) {         // We need to define some default values
131             $this->define_default_field();
132         }
134         $this->context = context_module::instance($this->cm->id);
135     }
138     /**
139      * This field just sets up a default field object
140      *
141      * @return bool
142      */
143     function define_default_field() {
144         global $OUTPUT;
145         if (empty($this->data->id)) {
146             echo $OUTPUT->notification('Programmer error: dataid not defined in field class');
147         }
148         $this->field = new stdClass();
149         $this->field->id = 0;
150         $this->field->dataid = $this->data->id;
151         $this->field->type   = $this->type;
152         $this->field->param1 = '';
153         $this->field->param2 = '';
154         $this->field->param3 = '';
155         $this->field->name = '';
156         $this->field->description = '';
157         $this->field->required = false;
159         return true;
160     }
162     /**
163      * Set up the field object according to data in an object.  Now is the time to clean it!
164      *
165      * @return bool
166      */
167     function define_field($data) {
168         $this->field->type        = $this->type;
169         $this->field->dataid      = $this->data->id;
171         $this->field->name        = trim($data->name);
172         $this->field->description = trim($data->description);
173         $this->field->required    = !empty($data->required) ? 1 : 0;
175         if (isset($data->param1)) {
176             $this->field->param1 = trim($data->param1);
177         }
178         if (isset($data->param2)) {
179             $this->field->param2 = trim($data->param2);
180         }
181         if (isset($data->param3)) {
182             $this->field->param3 = trim($data->param3);
183         }
184         if (isset($data->param4)) {
185             $this->field->param4 = trim($data->param4);
186         }
187         if (isset($data->param5)) {
188             $this->field->param5 = trim($data->param5);
189         }
191         return true;
192     }
194     /**
195      * Insert a new field in the database
196      * We assume the field object is already defined as $this->field
197      *
198      * @global object
199      * @return bool
200      */
201     function insert_field() {
202         global $DB, $OUTPUT;
204         if (empty($this->field)) {
205             echo $OUTPUT->notification('Programmer error: Field has not been defined yet!  See define_field()');
206             return false;
207         }
209         $this->field->id = $DB->insert_record('data_fields',$this->field);
211         // Trigger an event for creating this field.
212         $event = \mod_data\event\field_created::create(array(
213             'objectid' => $this->field->id,
214             'context' => $this->context,
215             'other' => array(
216                 'fieldname' => $this->field->name,
217                 'dataid' => $this->data->id
218             )
219         ));
220         $event->trigger();
222         return true;
223     }
226     /**
227      * Update a field in the database
228      *
229      * @global object
230      * @return bool
231      */
232     function update_field() {
233         global $DB;
235         $DB->update_record('data_fields', $this->field);
237         // Trigger an event for updating this field.
238         $event = \mod_data\event\field_updated::create(array(
239             'objectid' => $this->field->id,
240             'context' => $this->context,
241             'other' => array(
242                 'fieldname' => $this->field->name,
243                 'dataid' => $this->data->id
244             )
245         ));
246         $event->trigger();
248         return true;
249     }
251     /**
252      * Delete a field completely
253      *
254      * @global object
255      * @return bool
256      */
257     function delete_field() {
258         global $DB;
260         if (!empty($this->field->id)) {
261             // Get the field before we delete it.
262             $field = $DB->get_record('data_fields', array('id' => $this->field->id));
264             $this->delete_content();
265             $DB->delete_records('data_fields', array('id'=>$this->field->id));
267             // Trigger an event for deleting this field.
268             $event = \mod_data\event\field_deleted::create(array(
269                 'objectid' => $this->field->id,
270                 'context' => $this->context,
271                 'other' => array(
272                     'fieldname' => $this->field->name,
273                     'dataid' => $this->data->id
274                  )
275             ));
276             $event->add_record_snapshot('data_fields', $field);
277             $event->trigger();
278         }
280         return true;
281     }
283     /**
284      * Print the relevant form element in the ADD template for this field
285      *
286      * @global object
287      * @param int $recordid
288      * @return string
289      */
290     function display_add_field($recordid=0, $formdata=null) {
291         global $DB, $OUTPUT;
293         if ($formdata) {
294             $fieldname = 'field_' . $this->field->id;
295             $content = $formdata->$fieldname;
296         } else if ($recordid) {
297             $content = $DB->get_field('data_content', 'content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid));
298         } else {
299             $content = '';
300         }
302         // beware get_field returns false for new, empty records MDL-18567
303         if ($content===false) {
304             $content='';
305         }
307         $str = '<div title="' . s($this->field->description) . '">';
308         $str .= '<label for="field_'.$this->field->id.'"><span class="accesshide">'.$this->field->name.'</span>';
309         if ($this->field->required) {
310             $image = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
311             $str .= html_writer::div($image, 'inline-req');
312         }
313         $str .= '</label><input class="basefieldinput form-control d-inline mod-data-input" ' .
314                 'type="text" name="field_' . $this->field->id . '" ' .
315                 'id="field_' . $this->field->id . '" value="' . s($content) . '" />';
316         $str .= '</div>';
318         return $str;
319     }
321     /**
322      * Print the relevant form element to define the attributes for this field
323      * viewable by teachers only.
324      *
325      * @global object
326      * @global object
327      * @return void Output is echo'd
328      */
329     function display_edit_field() {
330         global $CFG, $DB, $OUTPUT;
332         if (empty($this->field)) {   // No field has been defined yet, try and make one
333             $this->define_default_field();
334         }
335         echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide');
337         echo '<form id="editfield" action="'.$CFG->wwwroot.'/mod/data/field.php" method="post">'."\n";
338         echo '<input type="hidden" name="d" value="'.$this->data->id.'" />'."\n";
339         if (empty($this->field->id)) {
340             echo '<input type="hidden" name="mode" value="add" />'."\n";
341             $savebutton = get_string('add');
342         } else {
343             echo '<input type="hidden" name="fid" value="'.$this->field->id.'" />'."\n";
344             echo '<input type="hidden" name="mode" value="update" />'."\n";
345             $savebutton = get_string('savechanges');
346         }
347         echo '<input type="hidden" name="type" value="'.$this->type.'" />'."\n";
348         echo '<input name="sesskey" value="'.sesskey().'" type="hidden" />'."\n";
350         echo $OUTPUT->heading($this->name(), 3);
352         require_once($CFG->dirroot.'/mod/data/field/'.$this->type.'/mod.html');
354         echo '<div class="mdl-align">';
355         echo '<input type="submit" class="btn btn-primary" value="'.$savebutton.'" />'."\n";
356         echo '<input type="submit" class="btn btn-secondary" name="cancel" value="'.get_string('cancel').'" />'."\n";
357         echo '</div>';
359         echo '</form>';
361         echo $OUTPUT->box_end();
362     }
364     /**
365      * Display the content of the field in browse mode
366      *
367      * @global object
368      * @param int $recordid
369      * @param object $template
370      * @return bool|string
371      */
372     function display_browse_field($recordid, $template) {
373         global $DB;
375         if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
376             if (isset($content->content)) {
377                 $options = new stdClass();
378                 if ($this->field->param1 == '1') {  // We are autolinking this field, so disable linking within us
379                     //$content->content = '<span class="nolink">'.$content->content.'</span>';
380                     //$content->content1 = FORMAT_HTML;
381                     $options->filter=false;
382                 }
383                 $options->para = false;
384                 $str = format_text($content->content, $content->content1, $options);
385             } else {
386                 $str = '';
387             }
388             return $str;
389         }
390         return false;
391     }
393     /**
394      * Update the content of one data field in the data_content table
395      * @global object
396      * @param int $recordid
397      * @param mixed $value
398      * @param string $name
399      * @return bool
400      */
401     function update_content($recordid, $value, $name=''){
402         global $DB;
404         $content = new stdClass();
405         $content->fieldid = $this->field->id;
406         $content->recordid = $recordid;
407         $content->content = clean_param($value, PARAM_NOTAGS);
409         if ($oldcontent = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
410             $content->id = $oldcontent->id;
411             return $DB->update_record('data_content', $content);
412         } else {
413             return $DB->insert_record('data_content', $content);
414         }
415     }
417     /**
418      * Delete all content associated with the field
419      *
420      * @global object
421      * @param int $recordid
422      * @return bool
423      */
424     function delete_content($recordid=0) {
425         global $DB;
427         if ($recordid) {
428             $conditions = array('fieldid'=>$this->field->id, 'recordid'=>$recordid);
429         } else {
430             $conditions = array('fieldid'=>$this->field->id);
431         }
433         $rs = $DB->get_recordset('data_content', $conditions);
434         if ($rs->valid()) {
435             $fs = get_file_storage();
436             foreach ($rs as $content) {
437                 $fs->delete_area_files($this->context->id, 'mod_data', 'content', $content->id);
438             }
439         }
440         $rs->close();
442         return $DB->delete_records('data_content', $conditions);
443     }
445     /**
446      * Check if a field from an add form is empty
447      *
448      * @param mixed $value
449      * @param mixed $name
450      * @return bool
451      */
452     function notemptyfield($value, $name) {
453         return !empty($value);
454     }
456     /**
457      * Just in case a field needs to print something before the whole form
458      */
459     function print_before_form() {
460     }
462     /**
463      * Just in case a field needs to print something after the whole form
464      */
465     function print_after_form() {
466     }
469     /**
470      * Returns the sortable field for the content. By default, it's just content
471      * but for some plugins, it could be content 1 - content4
472      *
473      * @return string
474      */
475     function get_sort_field() {
476         return 'content';
477     }
479     /**
480      * Returns the SQL needed to refer to the column.  Some fields may need to CAST() etc.
481      *
482      * @param string $fieldname
483      * @return string $fieldname
484      */
485     function get_sort_sql($fieldname) {
486         return $fieldname;
487     }
489     /**
490      * Returns the name/type of the field
491      *
492      * @return string
493      */
494     function name() {
495         return get_string('fieldtypelabel', "datafield_$this->type");
496     }
498     /**
499      * Prints the respective type icon
500      *
501      * @global object
502      * @return string
503      */
504     function image() {
505         global $OUTPUT;
507         $params = array('d'=>$this->data->id, 'fid'=>$this->field->id, 'mode'=>'display', 'sesskey'=>sesskey());
508         $link = new moodle_url('/mod/data/field.php', $params);
509         $str = '<a href="'.$link->out().'">';
510         $str .= $OUTPUT->pix_icon('field/' . $this->type, $this->type, 'data');
511         $str .= '</a>';
512         return $str;
513     }
515     /**
516      * Per default, it is assumed that fields support text exporting.
517      * Override this (return false) on fields not supporting text exporting.
518      *
519      * @return bool true
520      */
521     function text_export_supported() {
522         return true;
523     }
525     /**
526      * Per default, return the record's text value only from the "content" field.
527      * Override this in fields class if necesarry.
528      *
529      * @param string $record
530      * @return string
531      */
532     function export_text_value($record) {
533         if ($this->text_export_supported()) {
534             return $record->content;
535         }
536     }
538     /**
539      * @param string $relativepath
540      * @return bool false
541      */
542     function file_ok($relativepath) {
543         return false;
544     }
546     /**
547      * Returns the priority for being indexed by globalsearch
548      *
549      * @return int
550      */
551     public static function get_priority() {
552         return static::$priority;
553     }
555     /**
556      * Returns the presentable string value for a field content.
557      *
558      * The returned string should be plain text.
559      *
560      * @param stdClass $content
561      * @return string
562      */
563     public static function get_content_value($content) {
564         return trim($content->content, "\r\n ");
565     }
567     /**
568      * Return the plugin configs for external functions,
569      * in some cases the configs will need formatting or be returned only if the current user has some capabilities enabled.
570      *
571      * @return array the list of config parameters
572      * @since Moodle 3.3
573      */
574     public function get_config_for_external() {
575         // Return all the field configs to null (maybe there is a private key for a service or something similar there).
576         $configs = [];
577         for ($i = 1; $i <= 10; $i++) {
578             $configs["param$i"] = null;
579         }
580         return $configs;
581     }
585 /**
586  * Given a template and a dataid, generate a default case template
587  *
588  * @global object
589  * @param object $data
590  * @param string template [addtemplate, singletemplate, listtempalte, rsstemplate]
591  * @param int $recordid
592  * @param bool $form
593  * @param bool $update
594  * @return bool|string
595  */
596 function data_generate_default_template(&$data, $template, $recordid=0, $form=false, $update=true) {
597     global $DB;
599     if (!$data && !$template) {
600         return false;
601     }
602     if ($template == 'csstemplate' or $template == 'jstemplate' ) {
603         return '';
604     }
606     // get all the fields for that database
607     if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'id')) {
609         $table = new html_table();
610         $table->attributes['class'] = 'mod-data-default-template ##approvalstatus##';
611         $table->colclasses = array('template-field', 'template-token');
612         $table->data = array();
613         foreach ($fields as $field) {
614             if ($form) {   // Print forms instead of data
615                 $fieldobj = data_get_field($field, $data);
616                 $token = $fieldobj->display_add_field($recordid, null);
617             } else {           // Just print the tag
618                 $token = '[['.$field->name.']]';
619             }
620             $table->data[] = array(
621                 $field->name.': ',
622                 $token
623             );
624         }
625         if ($template == 'listtemplate') {
626             $cell = new html_table_cell('##edit##  ##more##  ##delete##  ##approve##  ##disapprove##  ##export##');
627             $cell->colspan = 2;
628             $cell->attributes['class'] = 'controls';
629             $table->data[] = new html_table_row(array($cell));
630         } else if ($template == 'singletemplate') {
631             $cell = new html_table_cell('##edit##  ##delete##  ##approve##  ##disapprove##  ##export##');
632             $cell->colspan = 2;
633             $cell->attributes['class'] = 'controls';
634             $table->data[] = new html_table_row(array($cell));
635         } else if ($template == 'asearchtemplate') {
636             $row = new html_table_row(array(get_string('authorfirstname', 'data').': ', '##firstname##'));
637             $row->attributes['class'] = 'searchcontrols';
638             $table->data[] = $row;
639             $row = new html_table_row(array(get_string('authorlastname', 'data').': ', '##lastname##'));
640             $row->attributes['class'] = 'searchcontrols';
641             $table->data[] = $row;
642         }
644         $str = '';
645         if ($template == 'listtemplate'){
646             $str .= '##delcheck##';
647             $str .= html_writer::empty_tag('br');
648         }
650         $str .= html_writer::start_tag('div', array('class' => 'defaulttemplate'));
651         $str .= html_writer::table($table);
652         $str .= html_writer::end_tag('div');
653         if ($template == 'listtemplate'){
654             $str .= html_writer::empty_tag('hr');
655         }
657         if ($update) {
658             $newdata = new stdClass();
659             $newdata->id = $data->id;
660             $newdata->{$template} = $str;
661             $DB->update_record('data', $newdata);
662             $data->{$template} = $str;
663         }
665         return $str;
666     }
670 /**
671  * Search for a field name and replaces it with another one in all the
672  * form templates. Set $newfieldname as '' if you want to delete the
673  * field from the form.
674  *
675  * @global object
676  * @param object $data
677  * @param string $searchfieldname
678  * @param string $newfieldname
679  * @return bool
680  */
681 function data_replace_field_in_templates($data, $searchfieldname, $newfieldname) {
682     global $DB;
684     if (!empty($newfieldname)) {
685         $prestring = '[[';
686         $poststring = ']]';
687         $idpart = '#id';
689     } else {
690         $prestring = '';
691         $poststring = '';
692         $idpart = '';
693     }
695     $newdata = new stdClass();
696     $newdata->id = $data->id;
697     $newdata->singletemplate = str_ireplace('[['.$searchfieldname.']]',
698             $prestring.$newfieldname.$poststring, $data->singletemplate);
700     $newdata->listtemplate = str_ireplace('[['.$searchfieldname.']]',
701             $prestring.$newfieldname.$poststring, $data->listtemplate);
703     $newdata->addtemplate = str_ireplace('[['.$searchfieldname.']]',
704             $prestring.$newfieldname.$poststring, $data->addtemplate);
706     $newdata->addtemplate = str_ireplace('[['.$searchfieldname.'#id]]',
707             $prestring.$newfieldname.$idpart.$poststring, $data->addtemplate);
709     $newdata->rsstemplate = str_ireplace('[['.$searchfieldname.']]',
710             $prestring.$newfieldname.$poststring, $data->rsstemplate);
712     return $DB->update_record('data', $newdata);
716 /**
717  * Appends a new field at the end of the form template.
718  *
719  * @global object
720  * @param object $data
721  * @param string $newfieldname
722  */
723 function data_append_new_field_to_templates($data, $newfieldname) {
724     global $DB;
726     $newdata = new stdClass();
727     $newdata->id = $data->id;
728     $change = false;
730     if (!empty($data->singletemplate)) {
731         $newdata->singletemplate = $data->singletemplate.' [[' . $newfieldname .']]';
732         $change = true;
733     }
734     if (!empty($data->addtemplate)) {
735         $newdata->addtemplate = $data->addtemplate.' [[' . $newfieldname . ']]';
736         $change = true;
737     }
738     if (!empty($data->rsstemplate)) {
739         $newdata->rsstemplate = $data->singletemplate.' [[' . $newfieldname . ']]';
740         $change = true;
741     }
742     if ($change) {
743         $DB->update_record('data', $newdata);
744     }
748 /**
749  * given a field name
750  * this function creates an instance of the particular subfield class
751  *
752  * @global object
753  * @param string $name
754  * @param object $data
755  * @return object|bool
756  */
757 function data_get_field_from_name($name, $data){
758     global $DB;
760     $field = $DB->get_record('data_fields', array('name'=>$name, 'dataid'=>$data->id));
762     if ($field) {
763         return data_get_field($field, $data);
764     } else {
765         return false;
766     }
769 /**
770  * given a field id
771  * this function creates an instance of the particular subfield class
772  *
773  * @global object
774  * @param int $fieldid
775  * @param object $data
776  * @return bool|object
777  */
778 function data_get_field_from_id($fieldid, $data){
779     global $DB;
781     $field = $DB->get_record('data_fields', array('id'=>$fieldid, 'dataid'=>$data->id));
783     if ($field) {
784         return data_get_field($field, $data);
785     } else {
786         return false;
787     }
790 /**
791  * given a field id
792  * this function creates an instance of the particular subfield class
793  *
794  * @global object
795  * @param string $type
796  * @param object $data
797  * @return object
798  */
799 function data_get_field_new($type, $data) {
800     global $CFG;
802     require_once($CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php');
803     $newfield = 'data_field_'.$type;
804     $newfield = new $newfield(0, $data);
805     return $newfield;
808 /**
809  * returns a subclass field object given a record of the field, used to
810  * invoke plugin methods
811  * input: $param $field - record from db
812  *
813  * @global object
814  * @param object $field
815  * @param object $data
816  * @param object $cm
817  * @return object
818  */
819 function data_get_field($field, $data, $cm=null) {
820     global $CFG;
822     if ($field) {
823         require_once('field/'.$field->type.'/field.class.php');
824         $newfield = 'data_field_'.$field->type;
825         $newfield = new $newfield($field, $data, $cm);
826         return $newfield;
827     }
831 /**
832  * Given record object (or id), returns true if the record belongs to the current user
833  *
834  * @global object
835  * @global object
836  * @param mixed $record record object or id
837  * @return bool
838  */
839 function data_isowner($record) {
840     global $USER, $DB;
842     if (!isloggedin()) { // perf shortcut
843         return false;
844     }
846     if (!is_object($record)) {
847         if (!$record = $DB->get_record('data_records', array('id'=>$record))) {
848             return false;
849         }
850     }
852     return ($record->userid == $USER->id);
855 /**
856  * has a user reached the max number of entries?
857  *
858  * @param object $data
859  * @return bool
860  */
861 function data_atmaxentries($data){
862     if (!$data->maxentries){
863         return false;
865     } else {
866         return (data_numentries($data) >= $data->maxentries);
867     }
870 /**
871  * returns the number of entries already made by this user
872  *
873  * @global object
874  * @global object
875  * @param object $data
876  * @return int
877  */
878 function data_numentries($data, $userid=null) {
879     global $USER, $DB;
880     if ($userid === null) {
881         $userid = $USER->id;
882     }
883     $sql = 'SELECT COUNT(*) FROM {data_records} WHERE dataid=? AND userid=?';
884     return $DB->count_records_sql($sql, array($data->id, $userid));
887 /**
888  * function that takes in a dataid and adds a record
889  * this is used everytime an add template is submitted
890  *
891  * @global object
892  * @global object
893  * @param object $data
894  * @param int $groupid
895  * @return bool
896  */
897 function data_add_record($data, $groupid=0){
898     global $USER, $DB;
900     $cm = get_coursemodule_from_instance('data', $data->id);
901     $context = context_module::instance($cm->id);
903     $record = new stdClass();
904     $record->userid = $USER->id;
905     $record->dataid = $data->id;
906     $record->groupid = $groupid;
907     $record->timecreated = $record->timemodified = time();
908     if (has_capability('mod/data:approve', $context)) {
909         $record->approved = 1;
910     } else {
911         $record->approved = 0;
912     }
913     $record->id = $DB->insert_record('data_records', $record);
915     // Trigger an event for creating this record.
916     $event = \mod_data\event\record_created::create(array(
917         'objectid' => $record->id,
918         'context' => $context,
919         'other' => array(
920             'dataid' => $data->id
921         )
922     ));
923     $event->trigger();
925     $course = get_course($cm->course);
926     data_update_completion_state($data, $course, $cm);
928     return $record->id;
931 /**
932  * check the multple existence any tag in a template
933  *
934  * check to see if there are 2 or more of the same tag being used.
935  *
936  * @global object
937  * @param int $dataid,
938  * @param string $template
939  * @return bool
940  */
941 function data_tags_check($dataid, $template) {
942     global $DB, $OUTPUT;
944     // first get all the possible tags
945     $fields = $DB->get_records('data_fields', array('dataid'=>$dataid));
946     // then we generate strings to replace
947     $tagsok = true; // let's be optimistic
948     foreach ($fields as $field){
949         $pattern="/\[\[" . preg_quote($field->name, '/') . "\]\]/i";
950         if (preg_match_all($pattern, $template, $dummy)>1){
951             $tagsok = false;
952             echo $OUTPUT->notification('[['.$field->name.']] - '.get_string('multipletags','data'));
953         }
954     }
955     // else return true
956     return $tagsok;
959 /**
960  * Adds an instance of a data
961  *
962  * @param stdClass $data
963  * @param mod_data_mod_form $mform
964  * @return int intance id
965  */
966 function data_add_instance($data, $mform = null) {
967     global $DB, $CFG;
968     require_once($CFG->dirroot.'/mod/data/locallib.php');
970     if (empty($data->assessed)) {
971         $data->assessed = 0;
972     }
974     if (empty($data->ratingtime) || empty($data->assessed)) {
975         $data->assesstimestart  = 0;
976         $data->assesstimefinish = 0;
977     }
979     $data->timemodified = time();
981     $data->id = $DB->insert_record('data', $data);
983     // Add calendar events if necessary.
984     data_set_events($data);
985     if (!empty($data->completionexpected)) {
986         \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $data->completionexpected);
987     }
989     data_grade_item_update($data);
991     return $data->id;
994 /**
995  * updates an instance of a data
996  *
997  * @global object
998  * @param object $data
999  * @return bool
1000  */
1001 function data_update_instance($data) {
1002     global $DB, $CFG;
1003     require_once($CFG->dirroot.'/mod/data/locallib.php');
1005     $data->timemodified = time();
1006     $data->id           = $data->instance;
1008     if (empty($data->assessed)) {
1009         $data->assessed = 0;
1010     }
1012     if (empty($data->ratingtime) or empty($data->assessed)) {
1013         $data->assesstimestart  = 0;
1014         $data->assesstimefinish = 0;
1015     }
1017     if (empty($data->notification)) {
1018         $data->notification = 0;
1019     }
1021     $DB->update_record('data', $data);
1023     // Add calendar events if necessary.
1024     data_set_events($data);
1025     $completionexpected = (!empty($data->completionexpected)) ? $data->completionexpected : null;
1026     \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $completionexpected);
1028     data_grade_item_update($data);
1030     return true;
1034 /**
1035  * deletes an instance of a data
1036  *
1037  * @global object
1038  * @param int $id
1039  * @return bool
1040  */
1041 function data_delete_instance($id) {    // takes the dataid
1042     global $DB, $CFG;
1044     if (!$data = $DB->get_record('data', array('id'=>$id))) {
1045         return false;
1046     }
1048     $cm = get_coursemodule_from_instance('data', $data->id);
1049     $context = context_module::instance($cm->id);
1051 /// Delete all the associated information
1053     // files
1054     $fs = get_file_storage();
1055     $fs->delete_area_files($context->id, 'mod_data');
1057     // get all the records in this data
1058     $sql = "SELECT r.id
1059               FROM {data_records} r
1060              WHERE r.dataid = ?";
1062     $DB->delete_records_select('data_content', "recordid IN ($sql)", array($id));
1064     // delete all the records and fields
1065     $DB->delete_records('data_records', array('dataid'=>$id));
1066     $DB->delete_records('data_fields', array('dataid'=>$id));
1068     // Remove old calendar events.
1069     $events = $DB->get_records('event', array('modulename' => 'data', 'instance' => $id));
1070     foreach ($events as $event) {
1071         $event = calendar_event::load($event);
1072         $event->delete();
1073     }
1075     // Delete the instance itself
1076     $result = $DB->delete_records('data', array('id'=>$id));
1078     // cleanup gradebook
1079     data_grade_item_delete($data);
1081     return $result;
1084 /**
1085  * returns a summary of data activity of this user
1086  *
1087  * @global object
1088  * @param object $course
1089  * @param object $user
1090  * @param object $mod
1091  * @param object $data
1092  * @return object|null
1093  */
1094 function data_user_outline($course, $user, $mod, $data) {
1095     global $DB, $CFG;
1096     require_once("$CFG->libdir/gradelib.php");
1098     $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1099     if (empty($grades->items[0]->grades)) {
1100         $grade = false;
1101     } else {
1102         $grade = reset($grades->items[0]->grades);
1103     }
1106     if ($countrecords = $DB->count_records('data_records', array('dataid'=>$data->id, 'userid'=>$user->id))) {
1107         $result = new stdClass();
1108         $result->info = get_string('numrecords', 'data', $countrecords);
1109         $lastrecord   = $DB->get_record_sql('SELECT id,timemodified FROM {data_records}
1110                                               WHERE dataid = ? AND userid = ?
1111                                            ORDER BY timemodified DESC', array($data->id, $user->id), true);
1112         $result->time = $lastrecord->timemodified;
1113         if ($grade) {
1114             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1115         }
1116         return $result;
1117     } else if ($grade) {
1118         $result = new stdClass();
1119         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1121         //datesubmitted == time created. dategraded == time modified or time overridden
1122         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1123         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1124         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1125             $result->time = $grade->dategraded;
1126         } else {
1127             $result->time = $grade->datesubmitted;
1128         }
1130         return $result;
1131     }
1132     return NULL;
1135 /**
1136  * Prints all the records uploaded by this user
1137  *
1138  * @global object
1139  * @param object $course
1140  * @param object $user
1141  * @param object $mod
1142  * @param object $data
1143  */
1144 function data_user_complete($course, $user, $mod, $data) {
1145     global $DB, $CFG, $OUTPUT;
1146     require_once("$CFG->libdir/gradelib.php");
1148     $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1149     if (!empty($grades->items[0]->grades)) {
1150         $grade = reset($grades->items[0]->grades);
1151         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1152         if ($grade->str_feedback) {
1153             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1154         }
1155     }
1157     if ($records = $DB->get_records('data_records', array('dataid'=>$data->id,'userid'=>$user->id), 'timemodified DESC')) {
1158         data_print_template('singletemplate', $records, $data);
1159     }
1162 /**
1163  * Return grade for given user or all users.
1164  *
1165  * @global object
1166  * @param object $data
1167  * @param int $userid optional user id, 0 means all users
1168  * @return array array of grades, false if none
1169  */
1170 function data_get_user_grades($data, $userid=0) {
1171     global $CFG;
1173     require_once($CFG->dirroot.'/rating/lib.php');
1175     $ratingoptions = new stdClass;
1176     $ratingoptions->component = 'mod_data';
1177     $ratingoptions->ratingarea = 'entry';
1178     $ratingoptions->modulename = 'data';
1179     $ratingoptions->moduleid   = $data->id;
1181     $ratingoptions->userid = $userid;
1182     $ratingoptions->aggregationmethod = $data->assessed;
1183     $ratingoptions->scaleid = $data->scale;
1184     $ratingoptions->itemtable = 'data_records';
1185     $ratingoptions->itemtableusercolumn = 'userid';
1187     $rm = new rating_manager();
1188     return $rm->get_user_grades($ratingoptions);
1191 /**
1192  * Update activity grades
1193  *
1194  * @category grade
1195  * @param object $data
1196  * @param int $userid specific user only, 0 means all
1197  * @param bool $nullifnone
1198  */
1199 function data_update_grades($data, $userid=0, $nullifnone=true) {
1200     global $CFG, $DB;
1201     require_once($CFG->libdir.'/gradelib.php');
1203     if (!$data->assessed) {
1204         data_grade_item_update($data);
1206     } else if ($grades = data_get_user_grades($data, $userid)) {
1207         data_grade_item_update($data, $grades);
1209     } else if ($userid and $nullifnone) {
1210         $grade = new stdClass();
1211         $grade->userid   = $userid;
1212         $grade->rawgrade = NULL;
1213         data_grade_item_update($data, $grade);
1215     } else {
1216         data_grade_item_update($data);
1217     }
1220 /**
1221  * Update/create grade item for given data
1222  *
1223  * @category grade
1224  * @param stdClass $data A database instance with extra cmidnumber property
1225  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1226  * @return object grade_item
1227  */
1228 function data_grade_item_update($data, $grades=NULL) {
1229     global $CFG;
1230     require_once($CFG->libdir.'/gradelib.php');
1232     $params = array('itemname'=>$data->name, 'idnumber'=>$data->cmidnumber);
1234     if (!$data->assessed or $data->scale == 0) {
1235         $params['gradetype'] = GRADE_TYPE_NONE;
1237     } else if ($data->scale > 0) {
1238         $params['gradetype'] = GRADE_TYPE_VALUE;
1239         $params['grademax']  = $data->scale;
1240         $params['grademin']  = 0;
1242     } else if ($data->scale < 0) {
1243         $params['gradetype'] = GRADE_TYPE_SCALE;
1244         $params['scaleid']   = -$data->scale;
1245     }
1247     if ($grades  === 'reset') {
1248         $params['reset'] = true;
1249         $grades = NULL;
1250     }
1252     return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, $grades, $params);
1255 /**
1256  * Delete grade item for given data
1257  *
1258  * @category grade
1259  * @param object $data object
1260  * @return object grade_item
1261  */
1262 function data_grade_item_delete($data) {
1263     global $CFG;
1264     require_once($CFG->libdir.'/gradelib.php');
1266     return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, NULL, array('deleted'=>1));
1269 // junk functions
1270 /**
1271  * takes a list of records, the current data, a search string,
1272  * and mode to display prints the translated template
1273  *
1274  * @global object
1275  * @global object
1276  * @param string $template
1277  * @param array $records
1278  * @param object $data
1279  * @param string $search
1280  * @param int $page
1281  * @param bool $return
1282  * @param object $jumpurl a moodle_url by which to jump back to the record list (can be null)
1283  * @return mixed
1284  */
1285 function data_print_template($template, $records, $data, $search='', $page=0, $return=false, moodle_url $jumpurl=null) {
1286     global $CFG, $DB, $OUTPUT;
1288     $cm = get_coursemodule_from_instance('data', $data->id);
1289     $context = context_module::instance($cm->id);
1291     static $fields = array();
1292     static $dataid = null;
1294     if (empty($dataid)) {
1295         $dataid = $data->id;
1296     } else if ($dataid != $data->id) {
1297         $fields = array();
1298     }
1300     if (empty($fields)) {
1301         $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id));
1302         foreach ($fieldrecords as $fieldrecord) {
1303             $fields[]= data_get_field($fieldrecord, $data);
1304         }
1305     }
1307     if (empty($records)) {
1308         return;
1309     }
1311     if (!$jumpurl) {
1312         $jumpurl = new moodle_url('/mod/data/view.php', array('d' => $data->id));
1313     }
1314     $jumpurl = new moodle_url($jumpurl, array('page' => $page, 'sesskey' => sesskey()));
1316     foreach ($records as $record) {   // Might be just one for the single template
1318     // Replacing tags
1319         $patterns = array();
1320         $replacement = array();
1322     // Then we generate strings to replace for normal tags
1323         foreach ($fields as $field) {
1324             $patterns[]='[['.$field->field->name.']]';
1325             $replacement[] = highlight($search, $field->display_browse_field($record->id, $template));
1326         }
1328         $canmanageentries = has_capability('mod/data:manageentries', $context);
1330     // Replacing special tags (##Edit##, ##Delete##, ##More##)
1331         $patterns[]='##edit##';
1332         $patterns[]='##delete##';
1333         if (data_user_can_manage_entry($record, $data, $context)) {
1334             $replacement[] = '<a href="'.$CFG->wwwroot.'/mod/data/edit.php?d='
1335                              .$data->id.'&amp;rid='.$record->id.'&amp;sesskey='.sesskey().'">' .
1336                              $OUTPUT->pix_icon('t/edit', get_string('edit')) . '</a>';
1337             $replacement[] = '<a href="'.$CFG->wwwroot.'/mod/data/view.php?d='
1338                              .$data->id.'&amp;delete='.$record->id.'&amp;sesskey='.sesskey().'">' .
1339                              $OUTPUT->pix_icon('t/delete', get_string('delete')) . '</a>';
1340         } else {
1341             $replacement[] = '';
1342             $replacement[] = '';
1343         }
1345         $moreurl = $CFG->wwwroot . '/mod/data/view.php?d=' . $data->id . '&amp;rid=' . $record->id;
1346         if ($search) {
1347             $moreurl .= '&amp;filter=1';
1348         }
1349         $patterns[]='##more##';
1350         $replacement[] = '<a href="'.$moreurl.'">' . $OUTPUT->pix_icon('t/preview', get_string('more', 'data')) . '</a>';
1352         $patterns[]='##moreurl##';
1353         $replacement[] = $moreurl;
1355         $patterns[]='##delcheck##';
1356         if ($canmanageentries) {
1357             $replacement[] = html_writer::checkbox('delcheck[]', $record->id, false, '', array('class' => 'recordcheckbox'));
1358         } else {
1359             $replacement[] = '';
1360         }
1362         $patterns[]='##user##';
1363         $replacement[] = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$record->userid.
1364                                '&amp;course='.$data->course.'">'.fullname($record).'</a>';
1366         $patterns[] = '##userpicture##';
1367         $ruser = user_picture::unalias($record, null, 'userid');
1368         // If the record didn't come with user data, retrieve the user from database.
1369         if (!isset($ruser->picture)) {
1370             $ruser = core_user::get_user($record->userid);
1371         }
1372         $replacement[] = $OUTPUT->user_picture($ruser, array('courseid' => $data->course));
1374         $patterns[]='##export##';
1376         if (!empty($CFG->enableportfolios) && ($template == 'singletemplate' || $template == 'listtemplate')
1377             && ((has_capability('mod/data:exportentry', $context)
1378                 || (data_isowner($record->id) && has_capability('mod/data:exportownentry', $context))))) {
1379             require_once($CFG->libdir . '/portfoliolib.php');
1380             $button = new portfolio_add_button();
1381             $button->set_callback_options('data_portfolio_caller', array('id' => $cm->id, 'recordid' => $record->id), 'mod_data');
1382             list($formats, $files) = data_portfolio_caller::formats($fields, $record);
1383             $button->set_formats($formats);
1384             $replacement[] = $button->to_html(PORTFOLIO_ADD_ICON_LINK);
1385         } else {
1386             $replacement[] = '';
1387         }
1389         $patterns[] = '##timeadded##';
1390         $replacement[] = userdate($record->timecreated);
1392         $patterns[] = '##timemodified##';
1393         $replacement [] = userdate($record->timemodified);
1395         $patterns[]='##approve##';
1396         if (has_capability('mod/data:approve', $context) && ($data->approval) && (!$record->approved)) {
1397             $approveurl = new moodle_url($jumpurl, array('approve' => $record->id));
1398             $approveicon = new pix_icon('t/approve', get_string('approve', 'data'), '', array('class' => 'iconsmall'));
1399             $replacement[] = html_writer::tag('span', $OUTPUT->action_icon($approveurl, $approveicon),
1400                     array('class' => 'approve'));
1401         } else {
1402             $replacement[] = '';
1403         }
1405         $patterns[]='##disapprove##';
1406         if (has_capability('mod/data:approve', $context) && ($data->approval) && ($record->approved)) {
1407             $disapproveurl = new moodle_url($jumpurl, array('disapprove' => $record->id));
1408             $disapproveicon = new pix_icon('t/block', get_string('disapprove', 'data'), '', array('class' => 'iconsmall'));
1409             $replacement[] = html_writer::tag('span', $OUTPUT->action_icon($disapproveurl, $disapproveicon),
1410                     array('class' => 'disapprove'));
1411         } else {
1412             $replacement[] = '';
1413         }
1415         $patterns[] = '##approvalstatus##';
1416         if (!$data->approval) {
1417             $replacement[] = '';
1418         } else if ($record->approved) {
1419             $replacement[] = get_string('approved', 'data');
1420         } else {
1421             $replacement[] = get_string('notapproved', 'data');
1422         }
1424         $patterns[]='##comments##';
1425         if (($template == 'listtemplate') && ($data->comments)) {
1427             if (!empty($CFG->usecomments)) {
1428                 require_once($CFG->dirroot  . '/comment/lib.php');
1429                 list($context, $course, $cm) = get_context_info_array($context->id);
1430                 $cmt = new stdClass();
1431                 $cmt->context = $context;
1432                 $cmt->course  = $course;
1433                 $cmt->cm      = $cm;
1434                 $cmt->area    = 'database_entry';
1435                 $cmt->itemid  = $record->id;
1436                 $cmt->showcount = true;
1437                 $cmt->component = 'mod_data';
1438                 $comment = new comment($cmt);
1439                 $replacement[] = $comment->output(true);
1440             }
1441         } else {
1442             $replacement[] = '';
1443         }
1445         // actual replacement of the tags
1446         $newtext = str_ireplace($patterns, $replacement, $data->{$template});
1448         // no more html formatting and filtering - see MDL-6635
1449         if ($return) {
1450             return $newtext;
1451         } else {
1452             echo $newtext;
1454             // hack alert - return is always false in singletemplate anyway ;-)
1455             /**********************************
1456              *    Printing Ratings Form       *
1457              *********************************/
1458             if ($template == 'singletemplate') {    //prints ratings options
1459                 data_print_ratings($data, $record);
1460             }
1462             /**********************************
1463              *    Printing Comments Form       *
1464              *********************************/
1465             if (($template == 'singletemplate') && ($data->comments)) {
1466                 if (!empty($CFG->usecomments)) {
1467                     require_once($CFG->dirroot . '/comment/lib.php');
1468                     list($context, $course, $cm) = get_context_info_array($context->id);
1469                     $cmt = new stdClass();
1470                     $cmt->context = $context;
1471                     $cmt->course  = $course;
1472                     $cmt->cm      = $cm;
1473                     $cmt->area    = 'database_entry';
1474                     $cmt->itemid  = $record->id;
1475                     $cmt->showcount = true;
1476                     $cmt->component = 'mod_data';
1477                     $comment = new comment($cmt);
1478                     $comment->output(false);
1479                 }
1480             }
1481         }
1482     }
1485 /**
1486  * Return rating related permissions
1487  *
1488  * @param string $contextid the context id
1489  * @param string $component the component to get rating permissions for
1490  * @param string $ratingarea the rating area to get permissions for
1491  * @return array an associative array of the user's rating permissions
1492  */
1493 function data_rating_permissions($contextid, $component, $ratingarea) {
1494     $context = context::instance_by_id($contextid, MUST_EXIST);
1495     if ($component != 'mod_data' || $ratingarea != 'entry') {
1496         return null;
1497     }
1498     return array(
1499         'view'    => has_capability('mod/data:viewrating',$context),
1500         'viewany' => has_capability('mod/data:viewanyrating',$context),
1501         'viewall' => has_capability('mod/data:viewallratings',$context),
1502         'rate'    => has_capability('mod/data:rate',$context)
1503     );
1506 /**
1507  * Validates a submitted rating
1508  * @param array $params submitted data
1509  *            context => object the context in which the rated items exists [required]
1510  *            itemid => int the ID of the object being rated
1511  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
1512  *            rating => int the submitted rating
1513  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
1514  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
1515  * @return boolean true if the rating is valid. Will throw rating_exception if not
1516  */
1517 function data_rating_validate($params) {
1518     global $DB, $USER;
1520     // Check the component is mod_data
1521     if ($params['component'] != 'mod_data') {
1522         throw new rating_exception('invalidcomponent');
1523     }
1525     // Check the ratingarea is entry (the only rating area in data module)
1526     if ($params['ratingarea'] != 'entry') {
1527         throw new rating_exception('invalidratingarea');
1528     }
1530     // Check the rateduserid is not the current user .. you can't rate your own entries
1531     if ($params['rateduserid'] == $USER->id) {
1532         throw new rating_exception('nopermissiontorate');
1533     }
1535     $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
1536                   FROM {data_records} r
1537                   JOIN {data} d ON r.dataid = d.id
1538                  WHERE r.id = :itemid";
1539     $dataparams = array('itemid'=>$params['itemid']);
1540     if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1541         //item doesn't exist
1542         throw new rating_exception('invaliditemid');
1543     }
1545     if ($info->scale != $params['scaleid']) {
1546         //the scale being submitted doesnt match the one in the database
1547         throw new rating_exception('invalidscaleid');
1548     }
1550     //check that the submitted rating is valid for the scale
1552     // lower limit
1553     if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
1554         throw new rating_exception('invalidnum');
1555     }
1557     // upper limit
1558     if ($info->scale < 0) {
1559         //its a custom scale
1560         $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
1561         if ($scalerecord) {
1562             $scalearray = explode(',', $scalerecord->scale);
1563             if ($params['rating'] > count($scalearray)) {
1564                 throw new rating_exception('invalidnum');
1565             }
1566         } else {
1567             throw new rating_exception('invalidscaleid');
1568         }
1569     } else if ($params['rating'] > $info->scale) {
1570         //if its numeric and submitted rating is above maximum
1571         throw new rating_exception('invalidnum');
1572     }
1574     if ($info->approval && !$info->approved) {
1575         //database requires approval but this item isnt approved
1576         throw new rating_exception('nopermissiontorate');
1577     }
1579     // check the item we're rating was created in the assessable time window
1580     if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
1581         if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
1582             throw new rating_exception('notavailable');
1583         }
1584     }
1586     $course = $DB->get_record('course', array('id'=>$info->course), '*', MUST_EXIST);
1587     $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1588     $context = context_module::instance($cm->id);
1590     // if the supplied context doesnt match the item's context
1591     if ($context->id != $params['context']->id) {
1592         throw new rating_exception('invalidcontext');
1593     }
1595     // Make sure groups allow this user to see the item they're rating
1596     $groupid = $info->groupid;
1597     if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
1598         if (!groups_group_exists($groupid)) { // Can't find group
1599             throw new rating_exception('cannotfindgroup');//something is wrong
1600         }
1602         if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
1603             // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
1604             throw new rating_exception('notmemberofgroup');
1605         }
1606     }
1608     return true;
1611 /**
1612  * Can the current user see ratings for a given itemid?
1613  *
1614  * @param array $params submitted data
1615  *            contextid => int contextid [required]
1616  *            component => The component for this module - should always be mod_data [required]
1617  *            ratingarea => object the context in which the rated items exists [required]
1618  *            itemid => int the ID of the object being rated [required]
1619  *            scaleid => int scale id [optional]
1620  * @return bool
1621  * @throws coding_exception
1622  * @throws rating_exception
1623  */
1624 function mod_data_rating_can_see_item_ratings($params) {
1625     global $DB;
1627     // Check the component is mod_data.
1628     if (!isset($params['component']) || $params['component'] != 'mod_data') {
1629         throw new rating_exception('invalidcomponent');
1630     }
1632     // Check the ratingarea is entry (the only rating area in data).
1633     if (!isset($params['ratingarea']) || $params['ratingarea'] != 'entry') {
1634         throw new rating_exception('invalidratingarea');
1635     }
1637     if (!isset($params['itemid'])) {
1638         throw new rating_exception('invaliditemid');
1639     }
1641     $datasql = "SELECT d.id as dataid, d.course, r.groupid
1642                   FROM {data_records} r
1643                   JOIN {data} d ON r.dataid = d.id
1644                  WHERE r.id = :itemid";
1645     $dataparams = array('itemid' => $params['itemid']);
1646     if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1647         // Item doesn't exist.
1648         throw new rating_exception('invaliditemid');
1649     }
1651     // User can see ratings of all participants.
1652     if ($info->groupid == 0) {
1653         return true;
1654     }
1656     $course = $DB->get_record('course', array('id' => $info->course), '*', MUST_EXIST);
1657     $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1659     // Make sure groups allow this user to see the item they're rating.
1660     return groups_group_visible($info->groupid, $course, $cm);
1664 /**
1665  * function that takes in the current data, number of items per page,
1666  * a search string and prints a preference box in view.php
1667  *
1668  * This preference box prints a searchable advanced search template if
1669  *     a) A template is defined
1670  *  b) The advanced search checkbox is checked.
1671  *
1672  * @global object
1673  * @global object
1674  * @param object $data
1675  * @param int $perpage
1676  * @param string $search
1677  * @param string $sort
1678  * @param string $order
1679  * @param array $search_array
1680  * @param int $advanced
1681  * @param string $mode
1682  * @return void
1683  */
1684 function data_print_preference_form($data, $perpage, $search, $sort='', $order='ASC', $search_array = '', $advanced = 0, $mode= ''){
1685     global $CFG, $DB, $PAGE, $OUTPUT;
1687     $cm = get_coursemodule_from_instance('data', $data->id);
1688     $context = context_module::instance($cm->id);
1689     echo '<br /><div class="datapreferences">';
1690     echo '<form id="options" action="view.php" method="get">';
1691     echo '<div>';
1692     echo '<input type="hidden" name="d" value="'.$data->id.'" />';
1693     if ($mode =='asearch') {
1694         $advanced = 1;
1695         echo '<input type="hidden" name="mode" value="list" />';
1696     }
1697     echo '<label for="pref_perpage">'.get_string('pagesize','data').'</label> ';
1698     $pagesizes = array(2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9,10=>10,15=>15,
1699                        20=>20,30=>30,40=>40,50=>50,100=>100,200=>200,300=>300,400=>400,500=>500,1000=>1000);
1700     echo html_writer::select($pagesizes, 'perpage', $perpage, false, array('id' => 'pref_perpage', 'class' => 'custom-select'));
1702     if ($advanced) {
1703         $regsearchclass = 'search_none';
1704         $advancedsearchclass = 'search_inline';
1705     } else {
1706         $regsearchclass = 'search_inline';
1707         $advancedsearchclass = 'search_none';
1708     }
1709     echo '<div id="reg_search" class="' . $regsearchclass . ' form-inline" >&nbsp;&nbsp;&nbsp;';
1710     echo '<label for="pref_search">' . get_string('search') . '</label> <input type="text" ' .
1711          'class="form-control" size="16" name="search" id= "pref_search" value="' . s($search) . '" /></div>';
1712     echo '&nbsp;&nbsp;&nbsp;<label for="pref_sortby">'.get_string('sortby').'</label> ';
1713     // foreach field, print the option
1714     echo '<select name="sort" id="pref_sortby" class="custom-select m-r-1">';
1715     if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'name')) {
1716         echo '<optgroup label="'.get_string('fields', 'data').'">';
1717         foreach ($fields as $field) {
1718             if ($field->id == $sort) {
1719                 echo '<option value="'.$field->id.'" selected="selected">'.$field->name.'</option>';
1720             } else {
1721                 echo '<option value="'.$field->id.'">'.$field->name.'</option>';
1722             }
1723         }
1724         echo '</optgroup>';
1725     }
1726     $options = array();
1727     $options[DATA_TIMEADDED]    = get_string('timeadded', 'data');
1728     $options[DATA_TIMEMODIFIED] = get_string('timemodified', 'data');
1729     $options[DATA_FIRSTNAME]    = get_string('authorfirstname', 'data');
1730     $options[DATA_LASTNAME]     = get_string('authorlastname', 'data');
1731     if ($data->approval and has_capability('mod/data:approve', $context)) {
1732         $options[DATA_APPROVED] = get_string('approved', 'data');
1733     }
1734     echo '<optgroup label="'.get_string('other', 'data').'">';
1735     foreach ($options as $key => $name) {
1736         if ($key == $sort) {
1737             echo '<option value="'.$key.'" selected="selected">'.$name.'</option>';
1738         } else {
1739             echo '<option value="'.$key.'">'.$name.'</option>';
1740         }
1741     }
1742     echo '</optgroup>';
1743     echo '</select>';
1744     echo '<label for="pref_order" class="accesshide">'.get_string('order').'</label>';
1745     echo '<select id="pref_order" name="order" class="custom-select m-r-1">';
1746     if ($order == 'ASC') {
1747         echo '<option value="ASC" selected="selected">'.get_string('ascending','data').'</option>';
1748     } else {
1749         echo '<option value="ASC">'.get_string('ascending','data').'</option>';
1750     }
1751     if ($order == 'DESC') {
1752         echo '<option value="DESC" selected="selected">'.get_string('descending','data').'</option>';
1753     } else {
1754         echo '<option value="DESC">'.get_string('descending','data').'</option>';
1755     }
1756     echo '</select>';
1758     if ($advanced) {
1759         $checked = ' checked="checked" ';
1760     }
1761     else {
1762         $checked = '';
1763     }
1764     $PAGE->requires->js('/mod/data/data.js');
1765     echo '&nbsp;<input type="hidden" name="advanced" value="0" />';
1766     echo '&nbsp;<input type="hidden" name="filter" value="1" />';
1767     echo '&nbsp;<input type="checkbox" id="advancedcheckbox" name="advanced" value="1" ' . $checked . ' ' .
1768          'onchange="showHideAdvSearch(this.checked);" class="m-x-1" />' .
1769          '<label for="advancedcheckbox">' . get_string('advancedsearch', 'data') . '</label>';
1770     echo '&nbsp;<input type="submit" class="btn btn-secondary" value="' . get_string('savesettings', 'data') . '" />';
1772     echo '<br />';
1773     echo '<div class="' . $advancedsearchclass . '" id="data_adv_form">';
1774     echo '<table class="boxaligncenter">';
1776     // print ASC or DESC
1777     echo '<tr><td colspan="2">&nbsp;</td></tr>';
1778     $i = 0;
1780     // Determine if we are printing all fields for advanced search, or the template for advanced search
1781     // If a template is not defined, use the deafault template and display all fields.
1782     if(empty($data->asearchtemplate)) {
1783         data_generate_default_template($data, 'asearchtemplate');
1784     }
1786     static $fields = array();
1787     static $dataid = null;
1789     if (empty($dataid)) {
1790         $dataid = $data->id;
1791     } else if ($dataid != $data->id) {
1792         $fields = array();
1793     }
1795     if (empty($fields)) {
1796         $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id));
1797         foreach ($fieldrecords as $fieldrecord) {
1798             $fields[]= data_get_field($fieldrecord, $data);
1799         }
1800     }
1802     // Replacing tags
1803     $patterns = array();
1804     $replacement = array();
1806     // Then we generate strings to replace for normal tags
1807     foreach ($fields as $field) {
1808         $fieldname = $field->field->name;
1809         $fieldname = preg_quote($fieldname, '/');
1810         $patterns[] = "/\[\[$fieldname\]\]/i";
1811         $searchfield = data_get_field_from_id($field->field->id, $data);
1812         if (!empty($search_array[$field->field->id]->data)) {
1813             $replacement[] = $searchfield->display_search_field($search_array[$field->field->id]->data);
1814         } else {
1815             $replacement[] = $searchfield->display_search_field();
1816         }
1817     }
1818     $fn = !empty($search_array[DATA_FIRSTNAME]->data) ? $search_array[DATA_FIRSTNAME]->data : '';
1819     $ln = !empty($search_array[DATA_LASTNAME]->data) ? $search_array[DATA_LASTNAME]->data : '';
1820     $patterns[]    = '/##firstname##/';
1821     $replacement[] = '<label class="accesshide" for="u_fn">' . get_string('authorfirstname', 'data') . '</label>' .
1822                      '<input type="text" class="form-control" size="16" id="u_fn" name="u_fn" value="' . s($fn) . '" />';
1823     $patterns[]    = '/##lastname##/';
1824     $replacement[] = '<label class="accesshide" for="u_ln">' . get_string('authorlastname', 'data') . '</label>' .
1825                      '<input type="text" class="form-control" size="16" id="u_ln" name="u_ln" value="' . s($ln) . '" />';
1827     // actual replacement of the tags
1828     $newtext = preg_replace($patterns, $replacement, $data->asearchtemplate);
1830     $options = new stdClass();
1831     $options->para=false;
1832     $options->noclean=true;
1833     echo '<tr><td>';
1834     echo format_text($newtext, FORMAT_HTML, $options);
1835     echo '</td></tr>';
1837     echo '<tr><td colspan="4"><br/>' .
1838          '<input type="submit" class="btn btn-primary m-r-1" value="' . get_string('savesettings', 'data') . '" />' .
1839          '<input type="submit" class="btn btn-secondary" name="resetadv" value="' . get_string('resetsettings', 'data') . '" />' .
1840          '</td></tr>';
1841     echo '</table>';
1842     echo '</div>';
1843     echo '</div>';
1844     echo '</form>';
1845     echo '</div>';
1848 /**
1849  * @global object
1850  * @global object
1851  * @param object $data
1852  * @param object $record
1853  * @return void Output echo'd
1854  */
1855 function data_print_ratings($data, $record) {
1856     global $OUTPUT;
1857     if (!empty($record->rating)){
1858         echo $OUTPUT->render($record->rating);
1859     }
1862 /**
1863  * List the actions that correspond to a view of this module.
1864  * This is used by the participation report.
1865  *
1866  * Note: This is not used by new logging system. Event with
1867  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1868  *       be considered as view action.
1869  *
1870  * @return array
1871  */
1872 function data_get_view_actions() {
1873     return array('view');
1876 /**
1877  * List the actions that correspond to a post of this module.
1878  * This is used by the participation report.
1879  *
1880  * Note: This is not used by new logging system. Event with
1881  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1882  *       will be considered as post action.
1883  *
1884  * @return array
1885  */
1886 function data_get_post_actions() {
1887     return array('add','update','record delete');
1890 /**
1891  * @param string $name
1892  * @param int $dataid
1893  * @param int $fieldid
1894  * @return bool
1895  */
1896 function data_fieldname_exists($name, $dataid, $fieldid = 0) {
1897     global $DB;
1899     if (!is_numeric($name)) {
1900         $like = $DB->sql_like('df.name', ':name', false);
1901     } else {
1902         $like = "df.name = :name";
1903     }
1904     $params = array('name'=>$name);
1905     if ($fieldid) {
1906         $params['dataid']   = $dataid;
1907         $params['fieldid1'] = $fieldid;
1908         $params['fieldid2'] = $fieldid;
1909         return $DB->record_exists_sql("SELECT * FROM {data_fields} df
1910                                         WHERE $like AND df.dataid = :dataid
1911                                               AND ((df.id < :fieldid1) OR (df.id > :fieldid2))", $params);
1912     } else {
1913         $params['dataid']   = $dataid;
1914         return $DB->record_exists_sql("SELECT * FROM {data_fields} df
1915                                         WHERE $like AND df.dataid = :dataid", $params);
1916     }
1919 /**
1920  * @param array $fieldinput
1921  */
1922 function data_convert_arrays_to_strings(&$fieldinput) {
1923     foreach ($fieldinput as $key => $val) {
1924         if (is_array($val)) {
1925             $str = '';
1926             foreach ($val as $inner) {
1927                 $str .= $inner . ',';
1928             }
1929             $str = substr($str, 0, -1);
1931             $fieldinput->$key = $str;
1932         }
1933     }
1937 /**
1938  * Converts a database (module instance) to use the Roles System
1939  *
1940  * @global object
1941  * @global object
1942  * @uses CONTEXT_MODULE
1943  * @uses CAP_PREVENT
1944  * @uses CAP_ALLOW
1945  * @param object $data a data object with the same attributes as a record
1946  *                     from the data database table
1947  * @param int $datamodid the id of the data module, from the modules table
1948  * @param array $teacherroles array of roles that have archetype teacher
1949  * @param array $studentroles array of roles that have archetype student
1950  * @param array $guestroles array of roles that have archetype guest
1951  * @param int $cmid the course_module id for this data instance
1952  * @return boolean data module was converted or not
1953  */
1954 function data_convert_to_roles($data, $teacherroles=array(), $studentroles=array(), $cmid=NULL) {
1955     global $CFG, $DB, $OUTPUT;
1957     if (!isset($data->participants) && !isset($data->assesspublic)
1958             && !isset($data->groupmode)) {
1959         // We assume that this database has already been converted to use the
1960         // Roles System. above fields get dropped the data module has been
1961         // upgraded to use Roles.
1962         return false;
1963     }
1965     if (empty($cmid)) {
1966         // We were not given the course_module id. Try to find it.
1967         if (!$cm = get_coursemodule_from_instance('data', $data->id)) {
1968             echo $OUTPUT->notification('Could not get the course module for the data');
1969             return false;
1970         } else {
1971             $cmid = $cm->id;
1972         }
1973     }
1974     $context = context_module::instance($cmid);
1977     // $data->participants:
1978     // 1 - Only teachers can add entries
1979     // 3 - Teachers and students can add entries
1980     switch ($data->participants) {
1981         case 1:
1982             foreach ($studentroles as $studentrole) {
1983                 assign_capability('mod/data:writeentry', CAP_PREVENT, $studentrole->id, $context->id);
1984             }
1985             foreach ($teacherroles as $teacherrole) {
1986                 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
1987             }
1988             break;
1989         case 3:
1990             foreach ($studentroles as $studentrole) {
1991                 assign_capability('mod/data:writeentry', CAP_ALLOW, $studentrole->id, $context->id);
1992             }
1993             foreach ($teacherroles as $teacherrole) {
1994                 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
1995             }
1996             break;
1997     }
1999     // $data->assessed:
2000     // 2 - Only teachers can rate posts
2001     // 1 - Everyone can rate posts
2002     // 0 - No one can rate posts
2003     switch ($data->assessed) {
2004         case 0:
2005             foreach ($studentroles as $studentrole) {
2006                 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2007             }
2008             foreach ($teacherroles as $teacherrole) {
2009                 assign_capability('mod/data:rate', CAP_PREVENT, $teacherrole->id, $context->id);
2010             }
2011             break;
2012         case 1:
2013             foreach ($studentroles as $studentrole) {
2014                 assign_capability('mod/data:rate', CAP_ALLOW, $studentrole->id, $context->id);
2015             }
2016             foreach ($teacherroles as $teacherrole) {
2017                 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2018             }
2019             break;
2020         case 2:
2021             foreach ($studentroles as $studentrole) {
2022                 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2023             }
2024             foreach ($teacherroles as $teacherrole) {
2025                 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2026             }
2027             break;
2028     }
2030     // $data->assesspublic:
2031     // 0 - Students can only see their own ratings
2032     // 1 - Students can see everyone's ratings
2033     switch ($data->assesspublic) {
2034         case 0:
2035             foreach ($studentroles as $studentrole) {
2036                 assign_capability('mod/data:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
2037             }
2038             foreach ($teacherroles as $teacherrole) {
2039                 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2040             }
2041             break;
2042         case 1:
2043             foreach ($studentroles as $studentrole) {
2044                 assign_capability('mod/data:viewrating', CAP_ALLOW, $studentrole->id, $context->id);
2045             }
2046             foreach ($teacherroles as $teacherrole) {
2047                 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2048             }
2049             break;
2050     }
2052     if (empty($cm)) {
2053         $cm = $DB->get_record('course_modules', array('id'=>$cmid));
2054     }
2056     switch ($cm->groupmode) {
2057         case NOGROUPS:
2058             break;
2059         case SEPARATEGROUPS:
2060             foreach ($studentroles as $studentrole) {
2061                 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
2062             }
2063             foreach ($teacherroles as $teacherrole) {
2064                 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2065             }
2066             break;
2067         case VISIBLEGROUPS:
2068             foreach ($studentroles as $studentrole) {
2069                 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
2070             }
2071             foreach ($teacherroles as $teacherrole) {
2072                 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2073             }
2074             break;
2075     }
2076     return true;
2079 /**
2080  * Returns the best name to show for a preset
2081  *
2082  * @param string $shortname
2083  * @param  string $path
2084  * @return string
2085  */
2086 function data_preset_name($shortname, $path) {
2088     // We are looking inside the preset itself as a first choice, but also in normal data directory
2089     $string = get_string('modulename', 'datapreset_'.$shortname);
2091     if (substr($string, 0, 1) == '[') {
2092         return $shortname;
2093     } else {
2094         return $string;
2095     }
2098 /**
2099  * Returns an array of all the available presets.
2100  *
2101  * @return array
2102  */
2103 function data_get_available_presets($context) {
2104     global $CFG, $USER;
2106     $presets = array();
2108     // First load the ratings sub plugins that exist within the modules preset dir
2109     if ($dirs = core_component::get_plugin_list('datapreset')) {
2110         foreach ($dirs as $dir=>$fulldir) {
2111             if (is_directory_a_preset($fulldir)) {
2112                 $preset = new stdClass();
2113                 $preset->path = $fulldir;
2114                 $preset->userid = 0;
2115                 $preset->shortname = $dir;
2116                 $preset->name = data_preset_name($dir, $fulldir);
2117                 if (file_exists($fulldir.'/screenshot.jpg')) {
2118                     $preset->screenshot = $CFG->wwwroot.'/mod/data/preset/'.$dir.'/screenshot.jpg';
2119                 } else if (file_exists($fulldir.'/screenshot.png')) {
2120                     $preset->screenshot = $CFG->wwwroot.'/mod/data/preset/'.$dir.'/screenshot.png';
2121                 } else if (file_exists($fulldir.'/screenshot.gif')) {
2122                     $preset->screenshot = $CFG->wwwroot.'/mod/data/preset/'.$dir.'/screenshot.gif';
2123                 }
2124                 $presets[] = $preset;
2125             }
2126         }
2127     }
2128     // Now add to that the site presets that people have saved
2129     $presets = data_get_available_site_presets($context, $presets);
2130     return $presets;
2133 /**
2134  * Gets an array of all of the presets that users have saved to the site.
2135  *
2136  * @param stdClass $context The context that we are looking from.
2137  * @param array $presets
2138  * @return array An array of presets
2139  */
2140 function data_get_available_site_presets($context, array $presets=array()) {
2141     global $USER;
2143     $fs = get_file_storage();
2144     $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA);
2145     $canviewall = has_capability('mod/data:viewalluserpresets', $context);
2146     if (empty($files)) {
2147         return $presets;
2148     }
2149     foreach ($files as $file) {
2150         if (($file->is_directory() && $file->get_filepath()=='/') || !$file->is_directory() || (!$canviewall && $file->get_userid() != $USER->id)) {
2151             continue;
2152         }
2153         $preset = new stdClass;
2154         $preset->path = $file->get_filepath();
2155         $preset->name = trim($preset->path, '/');
2156         $preset->shortname = $preset->name;
2157         $preset->userid = $file->get_userid();
2158         $preset->id = $file->get_id();
2159         $preset->storedfile = $file;
2160         $presets[] = $preset;
2161     }
2162     return $presets;
2165 /**
2166  * Deletes a saved preset.
2167  *
2168  * @param string $name
2169  * @return bool
2170  */
2171 function data_delete_site_preset($name) {
2172     $fs = get_file_storage();
2174     $files = $fs->get_directory_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/');
2175     if (!empty($files)) {
2176         foreach ($files as $file) {
2177             $file->delete();
2178         }
2179     }
2181     $dir = $fs->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/', '.');
2182     if (!empty($dir)) {
2183         $dir->delete();
2184     }
2185     return true;
2188 /**
2189  * Prints the heads for a page
2190  *
2191  * @param stdClass $course
2192  * @param stdClass $cm
2193  * @param stdClass $data
2194  * @param string $currenttab
2195  */
2196 function data_print_header($course, $cm, $data, $currenttab='') {
2198     global $CFG, $displaynoticegood, $displaynoticebad, $OUTPUT, $PAGE;
2200     $PAGE->set_title($data->name);
2201     echo $OUTPUT->header();
2202     echo $OUTPUT->heading(format_string($data->name), 2);
2203     echo $OUTPUT->box(format_module_intro('data', $data, $cm->id), 'generalbox', 'intro');
2205     // Groups needed for Add entry tab
2206     $currentgroup = groups_get_activity_group($cm);
2207     $groupmode = groups_get_activity_groupmode($cm);
2209     // Print the tabs
2211     if ($currenttab) {
2212         include('tabs.php');
2213     }
2215     // Print any notices
2217     if (!empty($displaynoticegood)) {
2218         echo $OUTPUT->notification($displaynoticegood, 'notifysuccess');    // good (usually green)
2219     } else if (!empty($displaynoticebad)) {
2220         echo $OUTPUT->notification($displaynoticebad);                     // bad (usuually red)
2221     }
2224 /**
2225  * Can user add more entries?
2226  *
2227  * @param object $data
2228  * @param mixed $currentgroup
2229  * @param int $groupmode
2230  * @param stdClass $context
2231  * @return bool
2232  */
2233 function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = null) {
2234     global $USER;
2236     if (empty($context)) {
2237         $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
2238         $context = context_module::instance($cm->id);
2239     }
2241     if (has_capability('mod/data:manageentries', $context)) {
2242         // no entry limits apply if user can manage
2244     } else if (!has_capability('mod/data:writeentry', $context)) {
2245         return false;
2247     } else if (data_atmaxentries($data)) {
2248         return false;
2249     } else if (data_in_readonly_period($data)) {
2250         // Check whether we're in a read-only period
2251         return false;
2252     }
2254     if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
2255         return true;
2256     }
2258     if ($currentgroup) {
2259         return groups_is_member($currentgroup);
2260     } else {
2261         //else it might be group 0 in visible mode
2262         if ($groupmode == VISIBLEGROUPS){
2263             return true;
2264         } else {
2265             return false;
2266         }
2267     }
2270 /**
2271  * Check whether the current user is allowed to manage the given record considering manageentries capability,
2272  * data_in_readonly_period() result, ownership (determined by data_isowner()) and manageapproved setting.
2273  * @param mixed $record record object or id
2274  * @param object $data data object
2275  * @param object $context context object
2276  * @return bool returns true if the user is allowd to edit the entry, false otherwise
2277  */
2278 function data_user_can_manage_entry($record, $data, $context) {
2279     global $DB;
2281     if (has_capability('mod/data:manageentries', $context)) {
2282         return true;
2283     }
2285     // Check whether this activity is read-only at present.
2286     $readonly = data_in_readonly_period($data);
2288     if (!$readonly) {
2289         // Get record object from db if just id given like in data_isowner.
2290         // ...done before calling data_isowner() to avoid querying db twice.
2291         if (!is_object($record)) {
2292             if (!$record = $DB->get_record('data_records', array('id' => $record))) {
2293                 return false;
2294             }
2295         }
2296         if (data_isowner($record)) {
2297             if ($data->approval && $record->approved) {
2298                 return $data->manageapproved == 1;
2299             } else {
2300                 return true;
2301             }
2302         }
2303     }
2305     return false;
2308 /**
2309  * Check whether the specified database activity is currently in a read-only period
2310  *
2311  * @param object $data
2312  * @return bool returns true if the time fields in $data indicate a read-only period; false otherwise
2313  */
2314 function data_in_readonly_period($data) {
2315     $now = time();
2316     if (!$data->timeviewfrom && !$data->timeviewto) {
2317         return false;
2318     } else if (($data->timeviewfrom && $now < $data->timeviewfrom) || ($data->timeviewto && $now > $data->timeviewto)) {
2319         return false;
2320     }
2321     return true;
2324 /**
2325  * @return bool
2326  */
2327 function is_directory_a_preset($directory) {
2328     $directory = rtrim($directory, '/\\') . '/';
2329     $status = file_exists($directory.'singletemplate.html') &&
2330               file_exists($directory.'listtemplate.html') &&
2331               file_exists($directory.'listtemplateheader.html') &&
2332               file_exists($directory.'listtemplatefooter.html') &&
2333               file_exists($directory.'addtemplate.html') &&
2334               file_exists($directory.'rsstemplate.html') &&
2335               file_exists($directory.'rsstitletemplate.html') &&
2336               file_exists($directory.'csstemplate.css') &&
2337               file_exists($directory.'jstemplate.js') &&
2338               file_exists($directory.'preset.xml');
2340     return $status;
2343 /**
2344  * Abstract class used for data preset importers
2345  */
2346 abstract class data_preset_importer {
2348     protected $course;
2349     protected $cm;
2350     protected $module;
2351     protected $directory;
2353     /**
2354      * Constructor
2355      *
2356      * @param stdClass $course
2357      * @param stdClass $cm
2358      * @param stdClass $module
2359      * @param string $directory
2360      */
2361     public function __construct($course, $cm, $module, $directory) {
2362         $this->course = $course;
2363         $this->cm = $cm;
2364         $this->module = $module;
2365         $this->directory = $directory;
2366     }
2368     /**
2369      * Returns the name of the directory the preset is located in
2370      * @return string
2371      */
2372     public function get_directory() {
2373         return basename($this->directory);
2374     }
2376     /**
2377      * Retreive the contents of a file. That file may either be in a conventional directory of the Moodle file storage
2378      * @param file_storage $filestorage. should be null if using a conventional directory
2379      * @param stored_file $fileobj the directory to look in. null if using a conventional directory
2380      * @param string $dir the directory to look in. null if using the Moodle file storage
2381      * @param string $filename the name of the file we want
2382      * @return string the contents of the file or null if the file doesn't exist.
2383      */
2384     public function data_preset_get_file_contents(&$filestorage, &$fileobj, $dir, $filename) {
2385         if(empty($filestorage) || empty($fileobj)) {
2386             if (substr($dir, -1)!='/') {
2387                 $dir .= '/';
2388             }
2389             if (file_exists($dir.$filename)) {
2390                 return file_get_contents($dir.$filename);
2391             } else {
2392                 return null;
2393             }
2394         } else {
2395             if ($filestorage->file_exists(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename)) {
2396                 $file = $filestorage->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename);
2397                 return $file->get_content();
2398             } else {
2399                 return null;
2400             }
2401         }
2403     }
2404     /**
2405      * Gets the preset settings
2406      * @global moodle_database $DB
2407      * @return stdClass
2408      */
2409     public function get_preset_settings() {
2410         global $DB;
2412         $fs = $fileobj = null;
2413         if (!is_directory_a_preset($this->directory)) {
2414             //maybe the user requested a preset stored in the Moodle file storage
2416             $fs = get_file_storage();
2417             $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA);
2419             //preset name to find will be the final element of the directory
2420             $explodeddirectory = explode('/', $this->directory);
2421             $presettofind = end($explodeddirectory);
2423             //now go through the available files available and see if we can find it
2424             foreach ($files as $file) {
2425                 if (($file->is_directory() && $file->get_filepath()=='/') || !$file->is_directory()) {
2426                     continue;
2427                 }
2428                 $presetname = trim($file->get_filepath(), '/');
2429                 if ($presetname==$presettofind) {
2430                     $this->directory = $presetname;
2431                     $fileobj = $file;
2432                 }
2433             }
2435             if (empty($fileobj)) {
2436                 print_error('invalidpreset', 'data', '', $this->directory);
2437             }
2438         }
2440         $allowed_settings = array(
2441             'intro',
2442             'comments',
2443             'requiredentries',
2444             'requiredentriestoview',
2445             'maxentries',
2446             'rssarticles',
2447             'approval',
2448             'defaultsortdir',
2449             'defaultsort');
2451         $result = new stdClass;
2452         $result->settings = new stdClass;
2453         $result->importfields = array();
2454         $result->currentfields = $DB->get_records('data_fields', array('dataid'=>$this->module->id));
2455         if (!$result->currentfields) {
2456             $result->currentfields = array();
2457         }
2460         /* Grab XML */
2461         $presetxml = $this->data_preset_get_file_contents($fs, $fileobj, $this->directory,'preset.xml');
2462         $parsedxml = xmlize($presetxml, 0);
2464         /* First, do settings. Put in user friendly array. */
2465         $settingsarray = $parsedxml['preset']['#']['settings'][0]['#'];
2466         $result->settings = new StdClass();
2467         foreach ($settingsarray as $setting => $value) {
2468             if (!is_array($value) || !in_array($setting, $allowed_settings)) {
2469                 // unsupported setting
2470                 continue;
2471             }
2472             $result->settings->$setting = $value[0]['#'];
2473         }
2475         /* Now work out fields to user friendly array */
2476         $fieldsarray = $parsedxml['preset']['#']['field'];
2477         foreach ($fieldsarray as $field) {
2478             if (!is_array($field)) {
2479                 continue;
2480             }
2481             $f = new StdClass();
2482             foreach ($field['#'] as $param => $value) {
2483                 if (!is_array($value)) {
2484                     continue;
2485                 }
2486                 $f->$param = $value[0]['#'];
2487             }
2488             $f->dataid = $this->module->id;
2489             $f->type = clean_param($f->type, PARAM_ALPHA);
2490             $result->importfields[] = $f;
2491         }
2492         /* Now add the HTML templates to the settings array so we can update d */
2493         $result->settings->singletemplate     = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"singletemplate.html");
2494         $result->settings->listtemplate       = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplate.html");
2495         $result->settings->listtemplateheader = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplateheader.html");
2496         $result->settings->listtemplatefooter = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplatefooter.html");
2497         $result->settings->addtemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"addtemplate.html");
2498         $result->settings->rsstemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstemplate.html");
2499         $result->settings->rsstitletemplate   = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstitletemplate.html");
2500         $result->settings->csstemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"csstemplate.css");
2501         $result->settings->jstemplate         = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"jstemplate.js");
2502         $result->settings->asearchtemplate    = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"asearchtemplate.html");
2504         $result->settings->instance = $this->module->id;
2505         return $result;
2506     }
2508     /**
2509      * Import the preset into the given database module
2510      * @return bool
2511      */
2512     function import($overwritesettings) {
2513         global $DB, $CFG;
2515         $params = $this->get_preset_settings();
2516         $settings = $params->settings;
2517         $newfields = $params->importfields;
2518         $currentfields = $params->currentfields;
2519         $preservedfields = array();
2521         /* Maps fields and makes new ones */
2522         if (!empty($newfields)) {
2523             /* We require an injective mapping, and need to know what to protect */
2524             foreach ($newfields as $nid => $newfield) {
2525                 $cid = optional_param("field_$nid", -1, PARAM_INT);
2526                 if ($cid == -1) {
2527                     continue;
2528                 }
2529                 if (array_key_exists($cid, $preservedfields)){
2530                     print_error('notinjectivemap', 'data');
2531                 }
2532                 else $preservedfields[$cid] = true;
2533             }
2535             foreach ($newfields as $nid => $newfield) {
2536                 $cid = optional_param("field_$nid", -1, PARAM_INT);
2538                 /* A mapping. Just need to change field params. Data kept. */
2539                 if ($cid != -1 and isset($currentfields[$cid])) {
2540                     $fieldobject = data_get_field_from_id($currentfields[$cid]->id, $this->module);
2541                     foreach ($newfield as $param => $value) {
2542                         if ($param != "id") {
2543                             $fieldobject->field->$param = $value;
2544                         }
2545                     }
2546                     unset($fieldobject->field->similarfield);
2547                     $fieldobject->update_field();
2548                     unset($fieldobject);
2549                 } else {
2550                     /* Make a new field */
2551                     include_once("field/$newfield->type/field.class.php");
2553                     if (!isset($newfield->description)) {
2554                         $newfield->description = '';
2555                     }
2556                     $classname = 'data_field_'.$newfield->type;
2557                     $fieldclass = new $classname($newfield, $this->module);
2558                     $fieldclass->insert_field();
2559                     unset($fieldclass);
2560                 }
2561             }
2562         }
2564         /* Get rid of all old unused data */
2565         if (!empty($preservedfields)) {
2566             foreach ($currentfields as $cid => $currentfield) {
2567                 if (!array_key_exists($cid, $preservedfields)) {
2568                     /* Data not used anymore so wipe! */
2569                     print "Deleting field $currentfield->name<br />";
2571                     $id = $currentfield->id;
2572                     //Why delete existing data records and related comments/ratings??
2573                     $DB->delete_records('data_content', array('fieldid'=>$id));
2574                     $DB->delete_records('data_fields', array('id'=>$id));
2575                 }
2576             }
2577         }
2579         // handle special settings here
2580         if (!empty($settings->defaultsort)) {
2581             if (is_numeric($settings->defaultsort)) {
2582                 // old broken value
2583                 $settings->defaultsort = 0;
2584             } else {
2585                 $settings->defaultsort = (int)$DB->get_field('data_fields', 'id', array('dataid'=>$this->module->id, 'name'=>$settings->defaultsort));
2586             }
2587         } else {
2588             $settings->defaultsort = 0;
2589         }
2591         // do we want to overwrite all current database settings?
2592         if ($overwritesettings) {
2593             // all supported settings
2594             $overwrite = array_keys((array)$settings);
2595         } else {
2596             // only templates and sorting
2597             $overwrite = array('singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter',
2598                                'addtemplate', 'rsstemplate', 'rsstitletemplate', 'csstemplate', 'jstemplate',
2599                                'asearchtemplate', 'defaultsortdir', 'defaultsort');
2600         }
2602         // now overwrite current data settings
2603         foreach ($this->module as $prop=>$unused) {
2604             if (in_array($prop, $overwrite)) {
2605                 $this->module->$prop = $settings->$prop;
2606             }
2607         }
2609         data_update_instance($this->module);
2611         return $this->cleanup();
2612     }
2614     /**
2615      * Any clean up routines should go here
2616      * @return bool
2617      */
2618     public function cleanup() {
2619         return true;
2620     }
2623 /**
2624  * Data preset importer for uploaded presets
2625  */
2626 class data_preset_upload_importer extends data_preset_importer {
2627     public function __construct($course, $cm, $module, $filepath) {
2628         global $USER;
2629         if (is_file($filepath)) {
2630             $fp = get_file_packer();
2631             if ($fp->extract_to_pathname($filepath, $filepath.'_extracted')) {
2632                 fulldelete($filepath);
2633             }
2634             $filepath .= '_extracted';
2635         }
2636         parent::__construct($course, $cm, $module, $filepath);
2637     }
2638     public function cleanup() {
2639         return fulldelete($this->directory);
2640     }
2643 /**
2644  * Data preset importer for existing presets
2645  */
2646 class data_preset_existing_importer extends data_preset_importer {
2647     protected $userid;
2648     public function __construct($course, $cm, $module, $fullname) {
2649         global $USER;
2650         list($userid, $shortname) = explode('/', $fullname, 2);
2651         $context = context_module::instance($cm->id);
2652         if ($userid && ($userid != $USER->id) && !has_capability('mod/data:manageuserpresets', $context) && !has_capability('mod/data:viewalluserpresets', $context)) {
2653            throw new coding_exception('Invalid preset provided');
2654         }
2656         $this->userid = $userid;
2657         $filepath = data_preset_path($course, $userid, $shortname);
2658         parent::__construct($course, $cm, $module, $filepath);
2659     }
2660     public function get_userid() {
2661         return $this->userid;
2662     }
2665 /**
2666  * @global object
2667  * @global object
2668  * @param object $course
2669  * @param int $userid
2670  * @param string $shortname
2671  * @return string
2672  */
2673 function data_preset_path($course, $userid, $shortname) {
2674     global $USER, $CFG;
2676     $context = context_course::instance($course->id);
2678     $userid = (int)$userid;
2680     $path = null;
2681     if ($userid > 0 && ($userid == $USER->id || has_capability('mod/data:viewalluserpresets', $context))) {
2682         $path = $CFG->dataroot.'/data/preset/'.$userid.'/'.$shortname;
2683     } else if ($userid == 0) {
2684         $path = $CFG->dirroot.'/mod/data/preset/'.$shortname;
2685     } else if ($userid < 0) {
2686         $path = $CFG->tempdir.'/data/'.-$userid.'/'.$shortname;
2687     }
2689     return $path;
2692 /**
2693  * Implementation of the function for printing the form elements that control
2694  * whether the course reset functionality affects the data.
2695  *
2696  * @param $mform form passed by reference
2697  */
2698 function data_reset_course_form_definition(&$mform) {
2699     $mform->addElement('header', 'dataheader', get_string('modulenameplural', 'data'));
2700     $mform->addElement('checkbox', 'reset_data', get_string('deleteallentries','data'));
2702     $mform->addElement('checkbox', 'reset_data_notenrolled', get_string('deletenotenrolled', 'data'));
2703     $mform->disabledIf('reset_data_notenrolled', 'reset_data', 'checked');
2705     $mform->addElement('checkbox', 'reset_data_ratings', get_string('deleteallratings'));
2706     $mform->disabledIf('reset_data_ratings', 'reset_data', 'checked');
2708     $mform->addElement('checkbox', 'reset_data_comments', get_string('deleteallcomments'));
2709     $mform->disabledIf('reset_data_comments', 'reset_data', 'checked');
2712 /**
2713  * Course reset form defaults.
2714  * @return array
2715  */
2716 function data_reset_course_form_defaults($course) {
2717     return array('reset_data'=>0, 'reset_data_ratings'=>1, 'reset_data_comments'=>1, 'reset_data_notenrolled'=>0);
2720 /**
2721  * Removes all grades from gradebook
2722  *
2723  * @global object
2724  * @global object
2725  * @param int $courseid
2726  * @param string $type optional type
2727  */
2728 function data_reset_gradebook($courseid, $type='') {
2729     global $CFG, $DB;
2731     $sql = "SELECT d.*, cm.idnumber as cmidnumber, d.course as courseid
2732               FROM {data} d, {course_modules} cm, {modules} m
2733              WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id AND d.course=?";
2735     if ($datas = $DB->get_records_sql($sql, array($courseid))) {
2736         foreach ($datas as $data) {
2737             data_grade_item_update($data, 'reset');
2738         }
2739     }
2742 /**
2743  * Actual implementation of the reset course functionality, delete all the
2744  * data responses for course $data->courseid.
2745  *
2746  * @global object
2747  * @global object
2748  * @param object $data the data submitted from the reset course.
2749  * @return array status array
2750  */
2751 function data_reset_userdata($data) {
2752     global $CFG, $DB;
2753     require_once($CFG->libdir.'/filelib.php');
2754     require_once($CFG->dirroot.'/rating/lib.php');
2756     $componentstr = get_string('modulenameplural', 'data');
2757     $status = array();
2759     $allrecordssql = "SELECT r.id
2760                         FROM {data_records} r
2761                              INNER JOIN {data} d ON r.dataid = d.id
2762                        WHERE d.course = ?";
2764     $alldatassql = "SELECT d.id
2765                       FROM {data} d
2766                      WHERE d.course=?";
2768     $rm = new rating_manager();
2769     $ratingdeloptions = new stdClass;
2770     $ratingdeloptions->component = 'mod_data';
2771     $ratingdeloptions->ratingarea = 'entry';
2773     // Set the file storage - may need it to remove files later.
2774     $fs = get_file_storage();
2776     // delete entries if requested
2777     if (!empty($data->reset_data)) {
2778         $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2779         $DB->delete_records_select('data_content', "recordid IN ($allrecordssql)", array($data->courseid));
2780         $DB->delete_records_select('data_records', "dataid IN ($alldatassql)", array($data->courseid));
2782         if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2783             foreach ($datas as $dataid=>$unused) {
2784                 if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2785                     continue;
2786                 }
2787                 $datacontext = context_module::instance($cm->id);
2789                 // Delete any files that may exist.
2790                 $fs->delete_area_files($datacontext->id, 'mod_data', 'content');
2792                 $ratingdeloptions->contextid = $datacontext->id;
2793                 $rm->delete_ratings($ratingdeloptions);
2794             }
2795         }
2797         if (empty($data->reset_gradebook_grades)) {
2798             // remove all grades from gradebook
2799             data_reset_gradebook($data->courseid);
2800         }
2801         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallentries', 'data'), 'error'=>false);
2802     }
2804     // remove entries by users not enrolled into course
2805     if (!empty($data->reset_data_notenrolled)) {
2806         $recordssql = "SELECT r.id, r.userid, r.dataid, u.id AS userexists, u.deleted AS userdeleted
2807                          FROM {data_records} r
2808                               JOIN {data} d ON r.dataid = d.id
2809                               LEFT JOIN {user} u ON r.userid = u.id
2810                         WHERE d.course = ? AND r.userid > 0";
2812         $course_context = context_course::instance($data->courseid);
2813         $notenrolled = array();
2814         $fields = array();
2815         $rs = $DB->get_recordset_sql($recordssql, array($data->courseid));
2816         foreach ($rs as $record) {
2817             if (array_key_exists($record->userid, $notenrolled) or !$record->userexists or $record->userdeleted
2818               or !is_enrolled($course_context, $record->userid)) {
2819                 //delete ratings
2820                 if (!$cm = get_coursemodule_from_instance('data', $record->dataid)) {
2821                     continue;
2822                 }
2823                 $datacontext = context_module::instance($cm->id);
2824                 $ratingdeloptions->contextid = $datacontext->id;
2825                 $ratingdeloptions->itemid = $record->id;
2826                 $rm->delete_ratings($ratingdeloptions);
2828                 // Delete any files that may exist.
2829                 if ($contents = $DB->get_records('data_content', array('recordid' => $record->id), '', 'id')) {
2830                     foreach ($contents as $content) {
2831                         $fs->delete_area_files($datacontext->id, 'mod_data', 'content', $content->id);
2832                     }
2833                 }
2834                 $notenrolled[$record->userid] = true;
2836                 $DB->delete_records('comments', array('itemid' => $record->id, 'commentarea' => 'database_entry'));
2837                 $DB->delete_records('data_content', array('recordid' => $record->id));
2838                 $DB->delete_records('data_records', array('id' => $record->id));
2839             }
2840         }
2841         $rs->close();
2842         $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'data'), 'error'=>false);
2843     }
2845     // remove all ratings
2846     if (!empty($data->reset_data_ratings)) {
2847         if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2848             foreach ($datas as $dataid=>$unused) {
2849                 if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2850                     continue;
2851                 }
2852                 $datacontext = context_module::instance($cm->id);
2854                 $ratingdeloptions->contextid = $datacontext->id;
2855                 $rm->delete_ratings($ratingdeloptions);
2856             }
2857         }
2859         if (empty($data->reset_gradebook_grades)) {
2860             // remove all grades from gradebook
2861             data_reset_gradebook($data->courseid);
2862         }
2864         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false);
2865     }
2867     // remove all comments
2868     if (!empty($data->reset_data_comments)) {
2869         $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2870         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false);
2871     }
2873     // updating dates - shift may be negative too
2874     if ($data->timeshift) {
2875         shift_course_mod_dates('data', array('timeavailablefrom', 'timeavailableto', 'timeviewfrom', 'timeviewto'), $data->timeshift, $data->courseid);
2876         $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
2877     }
2879     return $status;
2882 /**
2883  * Returns all other caps used in module
2884  *
2885  * @return array
2886  */
2887 function data_get_extra_capabilities() {
2888     return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate', 'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete');
2891 /**
2892  * @param string $feature FEATURE_xx constant for requested feature
2893  * @return mixed True if module supports feature, null if doesn't know
2894  */
2895 function data_supports($feature) {
2896     switch($feature) {
2897         case FEATURE_GROUPS:                  return true;
2898         case FEATURE_GROUPINGS:               return true;
2899         case FEATURE_MOD_INTRO:               return true;
2900         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
2901         case FEATURE_COMPLETION_HAS_RULES:    return true;
2902         case FEATURE_GRADE_HAS_GRADE:         return true;
2903         case FEATURE_GRADE_OUTCOMES:          return true;
2904         case FEATURE_RATE:                    return true;
2905         case FEATURE_BACKUP_MOODLE2:          return true;
2906         case FEATURE_SHOW_DESCRIPTION:        return true;
2907         case FEATURE_COMMENT:                 return true;
2909         default: return null;
2910     }
2912 /**
2913  * @global object
2914  * @param array $export
2915  * @param string $delimiter_name
2916  * @param object $database
2917  * @param int $count
2918  * @param bool $return
2919  * @return string|void
2920  */
2921 function data_export_csv($export, $delimiter_name, $database, $count, $return=false) {
2922     global $CFG;
2923     require_once($CFG->libdir . '/csvlib.class.php');
2925     $filename = $database . '-' . $count . '-record';
2926     if ($count > 1) {
2927         $filename .= 's';
2928     }
2929     if ($return) {
2930         return csv_export_writer::print_array($export, $delimiter_name, '"', true);
2931     } else {
2932         csv_export_writer::download_array($filename, $export, $delimiter_name);
2933     }
2936 /**
2937  * @global object
2938  * @param array $export
2939  * @param string $dataname
2940  * @param int $count
2941  * @return string
2942  */
2943 function data_export_xls($export, $dataname, $count) {
2944     global $CFG;
2945     require_once("$CFG->libdir/excellib.class.php");
2946     $filename = clean_filename("{$dataname}-{$count}_record");
2947     if ($count > 1) {
2948         $filename .= 's';
2949     }
2950     $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
2951     $filename .= '.xls';
2953     $filearg = '-';
2954     $workbook = new MoodleExcelWorkbook($filearg);
2955     $workbook->send($filename);
2956     $worksheet = array();
2957     $worksheet[0] = $workbook->add_worksheet('');
2958     $rowno = 0;
2959     foreach ($export as $row) {
2960         $colno = 0;
2961         foreach($row as $col) {
2962             $worksheet[0]->write($rowno, $colno, $col);
2963             $colno++;
2964         }
2965         $rowno++;
2966     }
2967     $workbook->close();
2968     return $filename;
2971 /**
2972  * @global object
2973  * @param array $export
2974  * @param string $dataname
2975  * @param int $count
2976  * @param string
2977  */
2978 function data_export_ods($export, $dataname, $count) {
2979     global $CFG;
2980     require_once("$CFG->libdir/odslib.class.php");
2981     $filename = clean_filename("{$dataname}-{$count}_record");
2982     if ($count > 1) {
2983         $filename .= 's';
2984     }
2985     $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
2986     $filename .= '.ods';
2987     $filearg = '-';
2988     $workbook = new MoodleODSWorkbook($filearg);
2989     $workbook->send($filename);
2990     $worksheet = array();
2991     $worksheet[0] = $workbook->add_worksheet('');
2992     $rowno = 0;
2993     foreach ($export as $row) {
2994         $colno = 0;
2995         foreach($row as $col) {
2996             $worksheet[0]->write($rowno, $colno, $col);
2997             $colno++;
2998         }
2999         $rowno++;
3000     }
3001     $workbook->close();
3002     return $filename;
3005 /**
3006  * @global object
3007  * @param int $dataid
3008  * @param array $fields
3009  * @param array $selectedfields
3010  * @param int $currentgroup group ID of the current group. This is used for
3011  * exporting data while maintaining group divisions.
3012  * @param object $context the context in which the operation is performed (for capability checks)
3013  * @param bool $userdetails whether to include the details of the record author
3014  * @param bool $time whether to include time created/modified
3015  * @param bool $approval whether to include approval status
3016  * @return array
3017  */
3018 function data_get_exportdata($dataid, $fields, $selectedfields, $currentgroup=0, $context=null,
3019                              $userdetails=false, $time=false, $approval=false) {
3020     global $DB;
3022     if (is_null($context)) {
3023         $context = context_system::instance();
3024     }
3025     // exporting user data needs special permission
3026     $userdetails = $userdetails && has_capability('mod/data:exportuserinfo', $context);
3028     $exportdata = array();
3030     // populate the header in first row of export
3031     foreach($fields as $key => $field) {
3032         if (!in_array($field->field->id, $selectedfields)) {
3033             // ignore values we aren't exporting
3034             unset($fields[$key]);
3035         } else {
3036             $exportdata[0][] = $field->field->name;
3037         }
3038     }
3039     if ($userdetails) {
3040         $exportdata[0][] = get_string('user');
3041         $exportdata[0][] = get_string('username');
3042         $exportdata[0][] = get_string('email');
3043     }
3044     if ($time) {
3045         $exportdata[0][] = get_string('timeadded', 'data');
3046         $exportdata[0][] = get_string('timemodified', 'data');
3047     }
3048     if ($approval) {
3049         $exportdata[0][] = get_string('approved', 'data');
3050     }
3052     $datarecords = $DB->get_records('data_records', array('dataid'=>$dataid));
3053     ksort($datarecords);
3054     $line = 1;
3055     foreach($datarecords as $record) {
3056         // get content indexed by fieldid
3057         if ($currentgroup) {
3058             $select = 'SELECT c.fieldid, c.content, c.content1, c.content2, c.content3, c.content4 FROM {data_content} c, {data_records} r WHERE c.recordid = ? AND r.id = c.recordid AND r.groupid = ?';
3059             $where = array($record->id, $currentgroup);
3060         } else {
3061             $select = 'SELECT fieldid, content, content1, content2, content3, content4 FROM {data_content} WHERE recordid = ?';
3062             $where = array($record->id);
3063         }
3065         if( $content = $DB->get_records_sql($select, $where) ) {
3066             foreach($fields as $field) {
3067                 $contents = '';
3068                 if(isset($content[$field->field->id])) {
3069                     $contents = $field->export_text_value($content[$field->field->id]);
3070                 }
3071                 $exportdata[$line][] = $contents;
3072             }
3073             if ($userdetails) { // Add user details to the export data
3074                 $userdata = get_complete_user_data('id', $record->userid);
3075                 $exportdata[$line][] = fullname($userdata);
3076                 $exportdata[$line][] = $userdata->username;
3077                 $exportdata[$line][] = $userdata->email;
3078             }
3079             if ($time) { // Add time added / modified
3080                 $exportdata[$line][] = userdate($record->timecreated);
3081                 $exportdata[$line][] = userdate($record->timemodified);
3082             }
3083             if ($approval) { // Add approval status
3084                 $exportdata[$line][] = (int) $record->approved;
3085             }
3086         }
3087         $line++;
3088     }
3089     $line--;
3090     return $exportdata;
3093 ////////////////////////////////////////////////////////////////////////////////
3094 // File API                                                                   //
3095 ////////////////////////////////////////////////////////////////////////////////
3097 /**
3098  * Lists all browsable file areas
3099  *
3100  * @package  mod_data
3101  * @category files
3102  * @param stdClass $course course object
3103  * @param stdClass $cm course module object
3104  * @param stdClass $context context object
3105  * @return array
3106  */
3107 function data_get_file_areas($course, $cm, $context) {
3108     return array('content' => get_string('areacontent', 'mod_data'));
3111 /**
3112  * File browsing support for data module.
3113  *
3114  * @param file_browser $browser
3115  * @param array $areas
3116  * @param stdClass $course
3117  * @param cm_info $cm
3118  * @param context $context
3119  * @param string $filearea
3120  * @param int $itemid
3121  * @param string $filepath
3122  * @param string $filename
3123  * @return file_info_stored file_info_stored instance or null if not found
3124  */
3125 function data_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
3126     global $CFG, $DB, $USER;
3128     if ($context->contextlevel != CONTEXT_MODULE) {
3129         return null;
3130     }
3132     if (!isset($areas[$filearea])) {
3133         return null;
3134     }
3136     if (is_null($itemid)) {
3137         require_once($CFG->dirroot.'/mod/data/locallib.php');
3138         return new data_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
3139     }
3141     if (!$content = $DB->get_record('data_content', array('id'=>$itemid))) {
3142         return null;
3143     }
3145     if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3146         return null;
3147     }
3149     if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3150         return null;
3151     }
3153     if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3154         return null;
3155     }
3157     //check if approved
3158     if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3159         return null;
3160     }
3162     // group access
3163     if ($record->groupid) {
3164         $groupmode = groups_get_activity_groupmode($cm, $course);
3165         if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3166             if (!groups_is_member($record->groupid)) {
3167                 return null;
3168             }
3169         }
3170     }
3172     $fieldobj = data_get_field($field, $data, $cm);
3174     $filepath = is_null($filepath) ? '/' : $filepath;
3175     $filename = is_null($filename) ? '.' : $filename;
3176     if (!$fieldobj->file_ok($filepath.$filename)) {
3177         return null;
3178     }
3180     $fs = get_file_storage();
3181     if (!($storedfile = $fs->get_file($context->id, 'mod_data', $filearea, $itemid, $filepath, $filename))) {
3182         return null;
3183     }
3185     // Checks to see if the user can manage files or is the owner.
3186     // TODO MDL-33805 - Do not use userid here and move the capability check above.
3187     if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
3188         return null;
3189     }
3191     $urlbase = $CFG->wwwroot.'/pluginfile.php';
3193     return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
3196 /**
3197  * Serves the data attachments. Implements needed access control ;-)
3198  *
3199  * @package  mod_data
3200  * @category files
3201  * @param stdClass $course course object
3202  * @param stdClass $cm course module object
3203  * @param stdClass $context context object
3204  * @param string $filearea file area
3205  * @param array $args extra arguments
3206  * @param bool $forcedownload whether or not force download
3207  * @param array $options additional options affecting the file serving
3208  * @return bool false if file not found, does not return if found - justsend the file
3209  */
3210 function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
3211     global $CFG, $DB;
3213     if ($context->contextlevel != CONTEXT_MODULE) {
3214         return false;
3215     }
3217     require_course_login($course, true, $cm);
3219     if ($filearea === 'content') {
3220         $contentid = (int)array_shift($args);
3222         if (!$content = $DB->get_record('data_content', array('id'=>$contentid))) {
3223             return false;
3224         }
3226         if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3227             return false;
3228         }
3230         if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3231             return false;
3232         }
3234         if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3235             return false;
3236         }
3238         if ($data->id != $cm->instance) {
3239             // hacker attempt - context does not match the contentid
3240             return false;
3241         }
3243         //check if approved
3244         if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3245             return false;
3246         }
3248         // group access
3249         if ($record->groupid) {
3250             $groupmode = groups_get_activity_groupmode($cm, $course);
3251             if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3252                 if (!groups_is_member($record->groupid)) {
3253                     return false;
3254                 }
3255             }
3256         }
3258         $fieldobj = data_get_field($field, $data, $cm);
3260         $relativepath = implode('/', $args);
3261         $fullpath = "/$context->id/mod_data/content/$content->id/$relativepath";
3263         if (!$fieldobj->file_ok($relativepath)) {
3264             return false;
3265         }
3267         $fs = get_file_storage();
3268         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
3269             return false;
3270         }
3272         // finally send the file
3273         send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
3274     }
3276     return false;
3280 function data_extend_navigation($navigation, $course, $module, $cm) {
3281     global $CFG, $OUTPUT, $USER, $DB;
3282     require_once($CFG->dirroot . '/mod/data/locallib.php');
3284     $rid = optional_param('rid', 0, PARAM_INT);
3286     $data = $DB->get_record('data', array('id'=>$cm->instance));
3287     $currentgroup = groups_get_activity_group($cm);
3288     $groupmode = groups_get_activity_groupmode($cm);
3290      $numentries = data_numentries($data);
3291     $canmanageentries = has_capability('mod/data:manageentries', context_module::instance($cm->id));
3293     if ($data->entriesleft = data_get_entries_left_to_add($data, $numentries, $canmanageentries)) {
3294         $entriesnode = $navigation->add(get_string('entrieslefttoadd', 'data', $data));
3295         $entriesnode->add_class('note');
3296     }
3298     $navigation->add(get_string('list', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance)));
3299     if (!empty($rid)) {
3300         $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'rid'=>$rid)));
3301     } else {
3302         $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'single')));
3303     }
3304     $navigation->add(get_string('search', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'asearch')));
3307 /**
3308  * Adds module specific settings to the settings block
3309  *
3310  * @param settings_navigation $settings The settings navigation object
3311  * @param navigation_node $datanode The node to add module settings to
3312  */
3313 function data_extend_settings_navigation(settings_navigation $settings, navigation_node $datanode) {
3314     global $PAGE, $DB, $CFG, $USER;
3316     $data = $DB->get_record('data', array("id" => $PAGE->cm->instance));
3318     $currentgroup = groups_get_activity_group($PAGE->cm);
3319     $groupmode = groups_get_activity_groupmode($PAGE->cm);
3321     if (data_user_can_add_entry($data, $currentgroup, $groupmode, $PAGE->cm->context)) { // took out participation list here!
3322         if (empty($editentry)) { //TODO: undefined
3323             $addstring = get_string('add', 'data');
3324         } else {
3325             $addstring = get_string('editentry', 'data');
3326         }
3327         $datanode->add($addstring, new moodle_url('/mod/data/edit.php', array('d'=>$PAGE->cm->instance)));
3328     }
3330     if (has_capability(DATA_CAP_EXPORT, $PAGE->cm->context)) {
3331         // The capability required to Export database records is centrally defined in 'lib.php'
3332         // and should be weaker than those required to edit Templates, Fields and Presets.
3333         $datanode->add(get_string('exportentries', 'data'), new moodle_url('/mod/data/export.php', array('d'=>$data->id)));
3334     }
3335     if (has_capability('mod/data:manageentries', $PAGE->cm->context)) {
3336         $datanode->add(get_string('importentries', 'data'), new moodle_url('/mod/data/import.php', array('d'=>$data->id)));
3337     }
3339     if (has_capability('mod/data:managetemplates', $PAGE->cm->context)) {
3340         $currenttab = '';
3341         if ($currenttab == 'list') {
3342             $defaultemplate = 'listtemplate';
3343         } else if ($currenttab == 'add') {
3344             $defaultemplate = 'addtemplate';
3345         } else if ($currenttab == 'asearch') {
3346             $defaultemplate = 'asearchtemplate';
3347         } else {
3348             $defaultemplate = 'singletemplate';
3349         }
3351         $templates = $datanode->add(get_string('templates', 'data'));
3353         $templatelist = array ('listtemplate', 'singletemplate', 'asearchtemplate', 'addtemplate', 'rsstemplate', 'csstemplate', 'jstemplate');
3354         foreach ($templatelist as $template) {
3355             $templates->add(get_string($template, 'data'), new moodle_url('/mod/data/templates.php', array('d'=>$data->id,'mode'=>$template)));
3356         }
3358         $datanode->add(get_string('fields', 'data'), new moodle_url('/mod/data/field.php', array('d'=>$data->id)));
3359         $datanode->add(get_string('presets', 'data'), new moodle_url('/mod/data/preset.php', array('d'=>$data->id)));
3360     }
3362     if (!empty($CFG->enablerssfeeds) && !empty($CFG->data_enablerssfeeds) && $data->rssarticles > 0) {
3363         require_once("$CFG->libdir/rsslib.php");
3365         $string = get_string('rsstype','forum');
3367         $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $USER->id, 'mod_data', $data->id));
3368         $datanode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
3369     }
3372 /**
3373  * Save the database configuration as a preset.
3374  *
3375  * @param stdClass $course The course the database module belongs to.
3376  * @param stdClass $cm The course module record
3377  * @param stdClass $data The database record
3378  * @param string $path
3379  * @return bool
3380  */
3381 function data_presets_save($course, $cm, $data, $path) {
3382     global $USER;
3383     $fs = get_file_storage();
3384     $filerecord = new stdClass;
3385     $filerecord->contextid = DATA_PRESET_CONTEXT;
3386     $filerecord->component = DATA_PRESET_COMPONENT;
3387     $filerecord->filearea = DATA_PRESET_FILEAREA;
3388     $filerecord->itemid = 0;
3389     $filerecord->filepath = '/'.$path.'/';
3390     $filerecord->userid = $USER->id;
3392     $filerecord->filename = 'preset.xml';
3393     $fs->create_file_from_string($filerecord, data_presets_generate_xml($course, $cm, $data));
3395     $filerecord->filename = 'singletemplate.html';
3396     $fs->create_file_from_string($filerecord, $data->singletemplate);
3398     $filerecord->filename = 'listtemplateheader.html';
3399     $fs->create_file_from_string($filerecord, $data->listtemplateheader);
3401     $filerecord->filename = 'listtemplate.html';
3402     $fs->create_file_from_string($filerecord, $data->listtemplate);
3404     $filerecord->filename = 'listtemplatefooter.html';
3405     $fs->create_file_from_string($filerecord, $data->listtemplatefooter);
3407     $filerecord->filename = 'addtemplate.html';
3408     $fs->create_file_from_string($filerecord, $data->addtemplate);
3410     $filerecord->filename = 'rsstemplate.html';
3411     $fs->create_file_from_string($filerecord, $data->rsstemplate);
3413     $filerecord->filename = 'rsstitletemplate.html';
3414     $fs->create_file_from_string($filerecord, $data->rsstitletemplate);
3416     $filerecord->filename = 'csstemplate.css';
3417     $fs->create_file_from_string($filerecord, $data->csstemplate);
3419     $filerecord->filename = 'jstemplate.js';
3420     $fs->create_file_from_string($filerecord, $data->jstemplate);
3422     $filerecord->filename = 'asearchtemplate.html';
3423     $fs->create_file_from_string($filerecord, $data->asearchtemplate);
3425     return true;
3428 /**
3429  * Generates the XML for the database module provided
3430  *
3431  * @global moodle_database $DB
3432  * @param stdClass $course The course the database module belongs to.
3433  * @param stdClass $cm The course module record
3434  * @param stdClass $data The database record
3435  * @return string The XML for the preset
3436  */
3437 function data_presets_generate_xml($course, $cm, $data) {
3438     global $DB;
3440     // Assemble "preset.xml":
3441     $presetxmldata = "<preset>\n\n";
3443     // Raw settings are not preprocessed during saving of presets
3444     $raw_settings = array(
3445         'intro',
3446         'comments',
3447         'requiredentries',
3448         'requiredentriestoview',
3449         'maxentries',
3450         'rssarticles',
3451         'approval',
3452         'manageapproved',
3453         'defaultsortdir'
3454     );
3456     $presetxmldata .= "<settings>\n";
3457     // First, settings that do not require any conversion
3458     foreach ($raw_settings as $setting) {
3459         $presetxmldata .= "<$setting>" . htmlspecialchars($data->$setting) . "</$setting>\n";
3460     }
3462     // Now specific settings
3463     if ($data->defaultsort > 0 && $sortfield = data_get_field_from_id($data->defaultsort, $data)) {
3464         $presetxmldata .= '<defaultsort>' . htmlspecialchars($sortfield->field->name) . "</defaultsort>\n";
3465     } else {
3466         $presetxmldata .= "<defaultsort>0</defaultsort>\n";
3467     }
3468     $presetxmldata .= "</settings>\n\n";
3469     // Now for the fields. Grab all that are non-empty
3470     $fields = $DB->get_records('data_fields', array('dataid'=>$data->id));
3471     ksort($fields);
3472     if (!empty($fields)) {
3473         foreach ($fields as $field) {
3474             $presetxmldata .= "<field>\n";
3475             foreach ($field as $key => $value) {
3476                 if ($value != '' && $key != 'id' && $key != 'dataid') {
3477                     $presetxmldata .= "<$key>" . htmlspecialchars($value) . "</$key>\n";
3478                 }
3479             }
3480             $presetxmldata .= "</field>\n\n";
3481         }
3482     }
3483     $presetxmldata .= '</preset>';
3484     return $presetxmldata;
3487 function data_presets_export($course, $cm, $data, $tostorage=false) {
3488     global $CFG, $DB;
3490     $presetname = clean_filename($data->name) . '-preset-' . gmdate("Ymd_Hi");
3491     $exportsubdir = "mod_data/presetexport/$presetname";
3492     make_temp_directory($exportsubdir);
3493     $exportdir = "$CFG->tempdir/$exportsubdir";
3495     // Assemble "preset.xml":
3496     $presetxmldata = data_presets_generate_xml($course, $cm, $data);
3498     // After opening a file in write mode, close it asap
3499     $presetxmlfile = fopen($exportdir . '/preset.xml', 'w');
3500     fwrite($presetxmlfile, $presetxmldata);
3501     fclose($presetxmlfile);
3503     // Now write the template files
3504     $singletemplate = fopen($exportdir . '/singletemplate.html', 'w');
3505     fwrite($singletemplate, $data->singletemplate);
3506     fclose($singletemplate);
3508     $listtemplateheader = fopen($exportdir . '/listtemplateheader.html', 'w');
3509     fwrite($listtemplateheader, $data->listtemplateheader);
3510     fclose($listtemplateheader);
3512     $listtemplate = fopen($exportdir . '/listtemplate.html', 'w');
3513     fwrite($listtemplate, $data->listtemplate);
3514     fclose($listtemplate);
3516     $listtemplatefooter = fopen($exportdir . '/listtemplatefooter.html', 'w');
3517     fwrite($listtemplatefooter, $data->listtemplatefooter);
3518     fclose($listtemplatefooter);
3520     $addtemplate = fopen($exportdir . '/addtemplate.html', 'w');
3521     fwrite($addtemplate, $data->addtemplate);
3522     fclose($addtemplate);
3524     $rsstemplate = fopen($exportdir . '/rsstemplate.html', 'w');
3525     fwrite($rsstemplate, $data->rsstemplate);
3526     fclose($rsstemplate);
3528     $rsstitletemplate = fopen($exportdir . '/rsstitletemplate.html', 'w');
3529     fwrite($rsstitletemplate, $data->rsstitletemplate);
3530     fclose($rsstitletemplate);
3532     $csstemplate = fopen($exportdir . '/csstemplate.css', 'w');
3533     fwrite($csstemplate, $data->csstemplate);
3534     fclose($csstemplate);
3536     $jstemplate = fopen($exportdir . '/jstemplate.js', 'w');
3537     fwrite($jstemplate, $data->jstemplate);
3538     fclose($jstemplate);
3540     $asearchtemplate = fopen($exportdir . '/asearchtemplate.html', 'w');
3541     fwrite($asearchtemplate, $data->asearchtemplate);
3542     fclose($asearchtemplate);
3544     // Check if all files have been generated
3545     if (! is_directory_a_preset($exportdir)) {
3546         print_error('generateerror', 'data');
3547     }
3549     $filenames = array(
3550         'preset.xml',
3551         'singletemplate.html',
3552         'listtemplateheader.html',
3553         'listtemplate.html',
3554         'listtemplatefooter.html',
3555         'addtemplate.html',
3556         'rsstemplate.html',
3557         'rsstitletemplate.html',
3558         'csstemplate.css',
3559         'jstemplate.js',
3560         'asearchtemplate.html'
3561     );
3563     $filelist = array();
3564     foreach ($filenames as $filename) {
3565         $filelist[$filename] = $exportdir . '/' . $filename;
3566     }
3568     $exportfile = $exportdir.'.zip';
3569     file_exists($exportfile) && unlink($exportfile);
3571     $fp = get_file_packer('application/zip');
3572     $fp->archive_to_pathname($filelist, $exportfile);
3574     foreach ($filelist as $file) {
3575         unlink($file);
3576     }
3577     rmdir($exportdir);
3579     // Return the full path to the exported preset file:
3580     return $exportfile;
3583 /**
3584  * Running addtional permission check on plugin, for example, plugins
3585  * may have switch to turn on/off comments option, this callback will
3586  * affect UI display, not like pluginname_comment_validate only throw
3587  * exceptions.
3588  * Capability check has been done in comment->check_permissions(), we
3589  * don't need to do it again here.
3590  *
3591  * @package  mod_data
3592  * @category comment
3593  *
3594  * @param stdClass $comment_param {
3595  *              context  => context the context object
3596  *              courseid => int course id
3597  *              cm       => stdClass course module object
3598  *              commentarea => string comment area
3599  *              itemid      => int itemid
3600  * }
3601  * @return array
3602  */
3603 function data_comment_permissions($comment_param) {
3604     global $CFG, $DB;
3605     if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3606         throw new comment_exception('invalidcommentitemid');
3607     }
3608     if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3609         throw new comment_exception('invalidid', 'data');
3610     }
3611     if ($data->comments) {
3612         return array('post'=>true, 'view'=>true);
3613     } else {
3614         return array('post'=>false, 'view'=>false);
3615     }
3618 /**
3619  * Validate comment parameter before perform other comments actions
3620  *
3621  * @package  mod_data
3622  * @category comment
3623  *
3624  * @param stdClass $comment_param {
3625  *              context  => context the context object
3626  *              courseid => int course id
3627  *              cm       => stdClass course module object
3628  *              commentarea => string comment area
3629  *              itemid      => int itemid
3630  * }
3631  * @return boolean
3632  */
3633 function data_comment_validate($comment_param) {
3634     global $DB;
3635     // validate comment area
3636     if ($comment_param->commentarea != 'database_entry') {
3637         throw new comment_exception('invalidcommentarea');
3638     }
3639     // validate itemid
3640     if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3641         throw new comment_exception('invalidcommentitemid');
3642     }
3643     if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3644         throw new comment_exception('invalidid', 'data');
3645     }
3646     if (!$course = $DB->get_record('course', array('id'=>$data->course))) {
3647         throw new comment_exception('coursemisconf');
3648     }
3649     if (!$cm = get_coursemodule_from_instance('data', $data->id, $course->id)) {
3650         throw new comment_exception('invalidcoursemodule');
3651     }
3652     if (!$data->comments) {
3653         throw new comment_exception('commentsoff', 'data');
3654     }
3655     $context = context_module::instance($cm->id);
3657     //check if approved
3658     if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3659         throw new comment_exception('notapproved', 'data');
3660     }
3662     // group access
3663     if ($record->groupid) {
3664         $groupmode = groups_get_activity_groupmode($cm, $course);
3665         if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3666             if (!groups_is_member($record->groupid)) {
3667                 throw new comment_exception('notmemberofgroup');
3668             }
3669         }
3670     }
3671     // validate context id
3672     if ($context->id != $comment_param->context->id) {
3673         throw new comment_exception('invalidcontext');
3674     }
3675     // validation for comment deletion
3676     if (!empty($comment_param->commentid)) {
3677         if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
3678             if ($comment->commentarea != 'database_entry') {
3679                 throw new comment_exception('invalidcommentarea');
3680             }