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