Merge branch 'MDL-55320-master-fix2' of https://github.com/lameze/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      * @param string $value
555      * @return string
556      */
557     public static function get_content_value($value) {
558         return trim($value);
559     }
563 /**
564  * Given a template and a dataid, generate a default case template
565  *
566  * @global object
567  * @param object $data
568  * @param string template [addtemplate, singletemplate, listtempalte, rsstemplate]
569  * @param int $recordid
570  * @param bool $form
571  * @param bool $update
572  * @return bool|string
573  */
574 function data_generate_default_template(&$data, $template, $recordid=0, $form=false, $update=true) {
575     global $DB;
577     if (!$data && !$template) {
578         return false;
579     }
580     if ($template == 'csstemplate' or $template == 'jstemplate' ) {
581         return '';
582     }
584     // get all the fields for that database
585     if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'id')) {
587         $table = new html_table();
588         $table->attributes['class'] = 'mod-data-default-template ##approvalstatus##';
589         $table->colclasses = array('template-field', 'template-token');
590         $table->data = array();
591         foreach ($fields as $field) {
592             if ($form) {   // Print forms instead of data
593                 $fieldobj = data_get_field($field, $data);
594                 $token = $fieldobj->display_add_field($recordid, null);
595             } else {           // Just print the tag
596                 $token = '[['.$field->name.']]';
597             }
598             $table->data[] = array(
599                 $field->name.': ',
600                 $token
601             );
602         }
603         if ($template == 'listtemplate') {
604             $cell = new html_table_cell('##edit##  ##more##  ##delete##  ##approve##  ##disapprove##  ##export##');
605             $cell->colspan = 2;
606             $cell->attributes['class'] = 'controls';
607             $table->data[] = new html_table_row(array($cell));
608         } else if ($template == 'singletemplate') {
609             $cell = new html_table_cell('##edit##  ##delete##  ##approve##  ##disapprove##  ##export##');
610             $cell->colspan = 2;
611             $cell->attributes['class'] = 'controls';
612             $table->data[] = new html_table_row(array($cell));
613         } else if ($template == 'asearchtemplate') {
614             $row = new html_table_row(array(get_string('authorfirstname', 'data').': ', '##firstname##'));
615             $row->attributes['class'] = 'searchcontrols';
616             $table->data[] = $row;
617             $row = new html_table_row(array(get_string('authorlastname', 'data').': ', '##lastname##'));
618             $row->attributes['class'] = 'searchcontrols';
619             $table->data[] = $row;
620         }
622         $str = '';
623         if ($template == 'listtemplate'){
624             $str .= '##delcheck##';
625             $str .= html_writer::empty_tag('br');
626         }
628         $str .= html_writer::start_tag('div', array('class' => 'defaulttemplate'));
629         $str .= html_writer::table($table);
630         $str .= html_writer::end_tag('div');
631         if ($template == 'listtemplate'){
632             $str .= html_writer::empty_tag('hr');
633         }
635         if ($update) {
636             $newdata = new stdClass();
637             $newdata->id = $data->id;
638             $newdata->{$template} = $str;
639             $DB->update_record('data', $newdata);
640             $data->{$template} = $str;
641         }
643         return $str;
644     }
648 /**
649  * Search for a field name and replaces it with another one in all the
650  * form templates. Set $newfieldname as '' if you want to delete the
651  * field from the form.
652  *
653  * @global object
654  * @param object $data
655  * @param string $searchfieldname
656  * @param string $newfieldname
657  * @return bool
658  */
659 function data_replace_field_in_templates($data, $searchfieldname, $newfieldname) {
660     global $DB;
662     if (!empty($newfieldname)) {
663         $prestring = '[[';
664         $poststring = ']]';
665         $idpart = '#id';
667     } else {
668         $prestring = '';
669         $poststring = '';
670         $idpart = '';
671     }
673     $newdata = new stdClass();
674     $newdata->id = $data->id;
675     $newdata->singletemplate = str_ireplace('[['.$searchfieldname.']]',
676             $prestring.$newfieldname.$poststring, $data->singletemplate);
678     $newdata->listtemplate = str_ireplace('[['.$searchfieldname.']]',
679             $prestring.$newfieldname.$poststring, $data->listtemplate);
681     $newdata->addtemplate = str_ireplace('[['.$searchfieldname.']]',
682             $prestring.$newfieldname.$poststring, $data->addtemplate);
684     $newdata->addtemplate = str_ireplace('[['.$searchfieldname.'#id]]',
685             $prestring.$newfieldname.$idpart.$poststring, $data->addtemplate);
687     $newdata->rsstemplate = str_ireplace('[['.$searchfieldname.']]',
688             $prestring.$newfieldname.$poststring, $data->rsstemplate);
690     return $DB->update_record('data', $newdata);
694 /**
695  * Appends a new field at the end of the form template.
696  *
697  * @global object
698  * @param object $data
699  * @param string $newfieldname
700  */
701 function data_append_new_field_to_templates($data, $newfieldname) {
702     global $DB;
704     $newdata = new stdClass();
705     $newdata->id = $data->id;
706     $change = false;
708     if (!empty($data->singletemplate)) {
709         $newdata->singletemplate = $data->singletemplate.' [[' . $newfieldname .']]';
710         $change = true;
711     }
712     if (!empty($data->addtemplate)) {
713         $newdata->addtemplate = $data->addtemplate.' [[' . $newfieldname . ']]';
714         $change = true;
715     }
716     if (!empty($data->rsstemplate)) {
717         $newdata->rsstemplate = $data->singletemplate.' [[' . $newfieldname . ']]';
718         $change = true;
719     }
720     if ($change) {
721         $DB->update_record('data', $newdata);
722     }
726 /**
727  * given a field name
728  * this function creates an instance of the particular subfield class
729  *
730  * @global object
731  * @param string $name
732  * @param object $data
733  * @return object|bool
734  */
735 function data_get_field_from_name($name, $data){
736     global $DB;
738     $field = $DB->get_record('data_fields', array('name'=>$name, 'dataid'=>$data->id));
740     if ($field) {
741         return data_get_field($field, $data);
742     } else {
743         return false;
744     }
747 /**
748  * given a field id
749  * this function creates an instance of the particular subfield class
750  *
751  * @global object
752  * @param int $fieldid
753  * @param object $data
754  * @return bool|object
755  */
756 function data_get_field_from_id($fieldid, $data){
757     global $DB;
759     $field = $DB->get_record('data_fields', array('id'=>$fieldid, 'dataid'=>$data->id));
761     if ($field) {
762         return data_get_field($field, $data);
763     } else {
764         return false;
765     }
768 /**
769  * given a field id
770  * this function creates an instance of the particular subfield class
771  *
772  * @global object
773  * @param string $type
774  * @param object $data
775  * @return object
776  */
777 function data_get_field_new($type, $data) {
778     global $CFG;
780     require_once($CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php');
781     $newfield = 'data_field_'.$type;
782     $newfield = new $newfield(0, $data);
783     return $newfield;
786 /**
787  * returns a subclass field object given a record of the field, used to
788  * invoke plugin methods
789  * input: $param $field - record from db
790  *
791  * @global object
792  * @param object $field
793  * @param object $data
794  * @param object $cm
795  * @return object
796  */
797 function data_get_field($field, $data, $cm=null) {
798     global $CFG;
800     if ($field) {
801         require_once('field/'.$field->type.'/field.class.php');
802         $newfield = 'data_field_'.$field->type;
803         $newfield = new $newfield($field, $data, $cm);
804         return $newfield;
805     }
809 /**
810  * Given record object (or id), returns true if the record belongs to the current user
811  *
812  * @global object
813  * @global object
814  * @param mixed $record record object or id
815  * @return bool
816  */
817 function data_isowner($record) {
818     global $USER, $DB;
820     if (!isloggedin()) { // perf shortcut
821         return false;
822     }
824     if (!is_object($record)) {
825         if (!$record = $DB->get_record('data_records', array('id'=>$record))) {
826             return false;
827         }
828     }
830     return ($record->userid == $USER->id);
833 /**
834  * has a user reached the max number of entries?
835  *
836  * @param object $data
837  * @return bool
838  */
839 function data_atmaxentries($data){
840     if (!$data->maxentries){
841         return false;
843     } else {
844         return (data_numentries($data) >= $data->maxentries);
845     }
848 /**
849  * returns the number of entries already made by this user
850  *
851  * @global object
852  * @global object
853  * @param object $data
854  * @return int
855  */
856 function data_numentries($data){
857     global $USER, $DB;
858     $sql = 'SELECT COUNT(*) FROM {data_records} WHERE dataid=? AND userid=?';
859     return $DB->count_records_sql($sql, array($data->id, $USER->id));
862 /**
863  * function that takes in a dataid and adds a record
864  * this is used everytime an add template is submitted
865  *
866  * @global object
867  * @global object
868  * @param object $data
869  * @param int $groupid
870  * @return bool
871  */
872 function data_add_record($data, $groupid=0){
873     global $USER, $DB;
875     $cm = get_coursemodule_from_instance('data', $data->id);
876     $context = context_module::instance($cm->id);
878     $record = new stdClass();
879     $record->userid = $USER->id;
880     $record->dataid = $data->id;
881     $record->groupid = $groupid;
882     $record->timecreated = $record->timemodified = time();
883     if (has_capability('mod/data:approve', $context)) {
884         $record->approved = 1;
885     } else {
886         $record->approved = 0;
887     }
888     $record->id = $DB->insert_record('data_records', $record);
890     // Trigger an event for creating this record.
891     $event = \mod_data\event\record_created::create(array(
892         'objectid' => $record->id,
893         'context' => $context,
894         'other' => array(
895             'dataid' => $data->id
896         )
897     ));
898     $event->trigger();
900     return $record->id;
903 /**
904  * check the multple existence any tag in a template
905  *
906  * check to see if there are 2 or more of the same tag being used.
907  *
908  * @global object
909  * @param int $dataid,
910  * @param string $template
911  * @return bool
912  */
913 function data_tags_check($dataid, $template) {
914     global $DB, $OUTPUT;
916     // first get all the possible tags
917     $fields = $DB->get_records('data_fields', array('dataid'=>$dataid));
918     // then we generate strings to replace
919     $tagsok = true; // let's be optimistic
920     foreach ($fields as $field){
921         $pattern="/\[\[" . preg_quote($field->name, '/') . "\]\]/i";
922         if (preg_match_all($pattern, $template, $dummy)>1){
923             $tagsok = false;
924             echo $OUTPUT->notification('[['.$field->name.']] - '.get_string('multipletags','data'));
925         }
926     }
927     // else return true
928     return $tagsok;
931 /**
932  * Adds an instance of a data
933  *
934  * @param stdClass $data
935  * @param mod_data_mod_form $mform
936  * @return int intance id
937  */
938 function data_add_instance($data, $mform = null) {
939     global $DB, $CFG;
940     require_once($CFG->dirroot.'/mod/data/locallib.php');
942     if (empty($data->assessed)) {
943         $data->assessed = 0;
944     }
946     if (empty($data->ratingtime) || empty($data->assessed)) {
947         $data->assesstimestart  = 0;
948         $data->assesstimefinish = 0;
949     }
951     $data->timemodified = time();
953     $data->id = $DB->insert_record('data', $data);
955     // Add calendar events if necessary.
956     data_set_events($data);
958     data_grade_item_update($data);
960     return $data->id;
963 /**
964  * updates an instance of a data
965  *
966  * @global object
967  * @param object $data
968  * @return bool
969  */
970 function data_update_instance($data) {
971     global $DB, $CFG;
972     require_once($CFG->dirroot.'/mod/data/locallib.php');
974     $data->timemodified = time();
975     $data->id           = $data->instance;
977     if (empty($data->assessed)) {
978         $data->assessed = 0;
979     }
981     if (empty($data->ratingtime) or empty($data->assessed)) {
982         $data->assesstimestart  = 0;
983         $data->assesstimefinish = 0;
984     }
986     if (empty($data->notification)) {
987         $data->notification = 0;
988     }
990     $DB->update_record('data', $data);
992     // Add calendar events if necessary.
993     data_set_events($data);
995     data_grade_item_update($data);
997     return true;
1001 /**
1002  * deletes an instance of a data
1003  *
1004  * @global object
1005  * @param int $id
1006  * @return bool
1007  */
1008 function data_delete_instance($id) {    // takes the dataid
1009     global $DB, $CFG;
1011     if (!$data = $DB->get_record('data', array('id'=>$id))) {
1012         return false;
1013     }
1015     $cm = get_coursemodule_from_instance('data', $data->id);
1016     $context = context_module::instance($cm->id);
1018 /// Delete all the associated information
1020     // files
1021     $fs = get_file_storage();
1022     $fs->delete_area_files($context->id, 'mod_data');
1024     // get all the records in this data
1025     $sql = "SELECT r.id
1026               FROM {data_records} r
1027              WHERE r.dataid = ?";
1029     $DB->delete_records_select('data_content', "recordid IN ($sql)", array($id));
1031     // delete all the records and fields
1032     $DB->delete_records('data_records', array('dataid'=>$id));
1033     $DB->delete_records('data_fields', array('dataid'=>$id));
1035     // Remove old calendar events.
1036     $events = $DB->get_records('event', array('modulename' => 'data', 'instance' => $id));
1037     foreach ($events as $event) {
1038         $event = calendar_event::load($event);
1039         $event->delete();
1040     }
1042     // Delete the instance itself
1043     $result = $DB->delete_records('data', array('id'=>$id));
1045     // cleanup gradebook
1046     data_grade_item_delete($data);
1048     return $result;
1051 /**
1052  * returns a summary of data activity of this user
1053  *
1054  * @global object
1055  * @param object $course
1056  * @param object $user
1057  * @param object $mod
1058  * @param object $data
1059  * @return object|null
1060  */
1061 function data_user_outline($course, $user, $mod, $data) {
1062     global $DB, $CFG;
1063     require_once("$CFG->libdir/gradelib.php");
1065     $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1066     if (empty($grades->items[0]->grades)) {
1067         $grade = false;
1068     } else {
1069         $grade = reset($grades->items[0]->grades);
1070     }
1073     if ($countrecords = $DB->count_records('data_records', array('dataid'=>$data->id, 'userid'=>$user->id))) {
1074         $result = new stdClass();
1075         $result->info = get_string('numrecords', 'data', $countrecords);
1076         $lastrecord   = $DB->get_record_sql('SELECT id,timemodified FROM {data_records}
1077                                               WHERE dataid = ? AND userid = ?
1078                                            ORDER BY timemodified DESC', array($data->id, $user->id), true);
1079         $result->time = $lastrecord->timemodified;
1080         if ($grade) {
1081             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1082         }
1083         return $result;
1084     } else if ($grade) {
1085         $result = new stdClass();
1086         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1088         //datesubmitted == time created. dategraded == time modified or time overridden
1089         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1090         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1091         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1092             $result->time = $grade->dategraded;
1093         } else {
1094             $result->time = $grade->datesubmitted;
1095         }
1097         return $result;
1098     }
1099     return NULL;
1102 /**
1103  * Prints all the records uploaded by this user
1104  *
1105  * @global object
1106  * @param object $course
1107  * @param object $user
1108  * @param object $mod
1109  * @param object $data
1110  */
1111 function data_user_complete($course, $user, $mod, $data) {
1112     global $DB, $CFG, $OUTPUT;
1113     require_once("$CFG->libdir/gradelib.php");
1115     $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1116     if (!empty($grades->items[0]->grades)) {
1117         $grade = reset($grades->items[0]->grades);
1118         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1119         if ($grade->str_feedback) {
1120             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1121         }
1122     }
1124     if ($records = $DB->get_records('data_records', array('dataid'=>$data->id,'userid'=>$user->id), 'timemodified DESC')) {
1125         data_print_template('singletemplate', $records, $data);
1126     }
1129 /**
1130  * Return grade for given user or all users.
1131  *
1132  * @global object
1133  * @param object $data
1134  * @param int $userid optional user id, 0 means all users
1135  * @return array array of grades, false if none
1136  */
1137 function data_get_user_grades($data, $userid=0) {
1138     global $CFG;
1140     require_once($CFG->dirroot.'/rating/lib.php');
1142     $ratingoptions = new stdClass;
1143     $ratingoptions->component = 'mod_data';
1144     $ratingoptions->ratingarea = 'entry';
1145     $ratingoptions->modulename = 'data';
1146     $ratingoptions->moduleid   = $data->id;
1148     $ratingoptions->userid = $userid;
1149     $ratingoptions->aggregationmethod = $data->assessed;
1150     $ratingoptions->scaleid = $data->scale;
1151     $ratingoptions->itemtable = 'data_records';
1152     $ratingoptions->itemtableusercolumn = 'userid';
1154     $rm = new rating_manager();
1155     return $rm->get_user_grades($ratingoptions);
1158 /**
1159  * Update activity grades
1160  *
1161  * @category grade
1162  * @param object $data
1163  * @param int $userid specific user only, 0 means all
1164  * @param bool $nullifnone
1165  */
1166 function data_update_grades($data, $userid=0, $nullifnone=true) {
1167     global $CFG, $DB;
1168     require_once($CFG->libdir.'/gradelib.php');
1170     if (!$data->assessed) {
1171         data_grade_item_update($data);
1173     } else if ($grades = data_get_user_grades($data, $userid)) {
1174         data_grade_item_update($data, $grades);
1176     } else if ($userid and $nullifnone) {
1177         $grade = new stdClass();
1178         $grade->userid   = $userid;
1179         $grade->rawgrade = NULL;
1180         data_grade_item_update($data, $grade);
1182     } else {
1183         data_grade_item_update($data);
1184     }
1187 /**
1188  * Update/create grade item for given data
1189  *
1190  * @category grade
1191  * @param stdClass $data A database instance with extra cmidnumber property
1192  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1193  * @return object grade_item
1194  */
1195 function data_grade_item_update($data, $grades=NULL) {
1196     global $CFG;
1197     require_once($CFG->libdir.'/gradelib.php');
1199     $params = array('itemname'=>$data->name, 'idnumber'=>$data->cmidnumber);
1201     if (!$data->assessed or $data->scale == 0) {
1202         $params['gradetype'] = GRADE_TYPE_NONE;
1204     } else if ($data->scale > 0) {
1205         $params['gradetype'] = GRADE_TYPE_VALUE;
1206         $params['grademax']  = $data->scale;
1207         $params['grademin']  = 0;
1209     } else if ($data->scale < 0) {
1210         $params['gradetype'] = GRADE_TYPE_SCALE;
1211         $params['scaleid']   = -$data->scale;
1212     }
1214     if ($grades  === 'reset') {
1215         $params['reset'] = true;
1216         $grades = NULL;
1217     }
1219     return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, $grades, $params);
1222 /**
1223  * Delete grade item for given data
1224  *
1225  * @category grade
1226  * @param object $data object
1227  * @return object grade_item
1228  */
1229 function data_grade_item_delete($data) {
1230     global $CFG;
1231     require_once($CFG->libdir.'/gradelib.php');
1233     return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, NULL, array('deleted'=>1));
1236 // junk functions
1237 /**
1238  * takes a list of records, the current data, a search string,
1239  * and mode to display prints the translated template
1240  *
1241  * @global object
1242  * @global object
1243  * @param string $template
1244  * @param array $records
1245  * @param object $data
1246  * @param string $search
1247  * @param int $page
1248  * @param bool $return
1249  * @param object $jumpurl a moodle_url by which to jump back to the record list (can be null)
1250  * @return mixed
1251  */
1252 function data_print_template($template, $records, $data, $search='', $page=0, $return=false, moodle_url $jumpurl=null) {
1253     global $CFG, $DB, $OUTPUT;
1255     $cm = get_coursemodule_from_instance('data', $data->id);
1256     $context = context_module::instance($cm->id);
1258     static $fields = array();
1259     static $dataid = null;
1261     if (empty($dataid)) {
1262         $dataid = $data->id;
1263     } else if ($dataid != $data->id) {
1264         $fields = array();
1265     }
1267     if (empty($fields)) {
1268         $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id));
1269         foreach ($fieldrecords as $fieldrecord) {
1270             $fields[]= data_get_field($fieldrecord, $data);
1271         }
1272     }
1274     if (empty($records)) {
1275         return;
1276     }
1278     if (!$jumpurl) {
1279         $jumpurl = new moodle_url('/mod/data/view.php', array('d' => $data->id));
1280     }
1281     $jumpurl = new moodle_url($jumpurl, array('page' => $page, 'sesskey' => sesskey()));
1283     foreach ($records as $record) {   // Might be just one for the single template
1285     // Replacing tags
1286         $patterns = array();
1287         $replacement = array();
1289     // Then we generate strings to replace for normal tags
1290         foreach ($fields as $field) {
1291             $patterns[]='[['.$field->field->name.']]';
1292             $replacement[] = highlight($search, $field->display_browse_field($record->id, $template));
1293         }
1295         $canmanageentries = has_capability('mod/data:manageentries', $context);
1297     // Replacing special tags (##Edit##, ##Delete##, ##More##)
1298         $patterns[]='##edit##';
1299         $patterns[]='##delete##';
1300         if (data_user_can_manage_entry($record, $data, $context)) {
1301             $replacement[] = '<a href="'.$CFG->wwwroot.'/mod/data/edit.php?d='
1302                              .$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>';
1303             $replacement[] = '<a href="'.$CFG->wwwroot.'/mod/data/view.php?d='
1304                              .$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>';
1305         } else {
1306             $replacement[] = '';
1307             $replacement[] = '';
1308         }
1310         $moreurl = $CFG->wwwroot . '/mod/data/view.php?d=' . $data->id . '&amp;rid=' . $record->id;
1311         if ($search) {
1312             $moreurl .= '&amp;filter=1';
1313         }
1314         $patterns[]='##more##';
1315         $replacement[] = '<a href="'.$moreurl.'"><img src="'.$OUTPUT->pix_url('t/preview').
1316                         '" class="iconsmall" alt="'.get_string('more', 'data').'" title="'.get_string('more', 'data').
1317                         '" /></a>';
1319         $patterns[]='##moreurl##';
1320         $replacement[] = $moreurl;
1322         $patterns[]='##delcheck##';
1323         if ($canmanageentries) {
1324             $replacement[] = html_writer::checkbox('delcheck[]', $record->id, false, '', array('class' => 'recordcheckbox'));
1325         } else {
1326             $replacement[] = '';
1327         }
1329         $patterns[]='##user##';
1330         $replacement[] = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$record->userid.
1331                                '&amp;course='.$data->course.'">'.fullname($record).'</a>';
1333         $patterns[] = '##userpicture##';
1334         $ruser = user_picture::unalias($record, null, 'userid');
1335         $replacement[] = $OUTPUT->user_picture($ruser, array('courseid' => $data->course));
1337         $patterns[]='##export##';
1339         if (!empty($CFG->enableportfolios) && ($template == 'singletemplate' || $template == 'listtemplate')
1340             && ((has_capability('mod/data:exportentry', $context)
1341                 || (data_isowner($record->id) && has_capability('mod/data:exportownentry', $context))))) {
1342             require_once($CFG->libdir . '/portfoliolib.php');
1343             $button = new portfolio_add_button();
1344             $button->set_callback_options('data_portfolio_caller', array('id' => $cm->id, 'recordid' => $record->id), 'mod_data');
1345             list($formats, $files) = data_portfolio_caller::formats($fields, $record);
1346             $button->set_formats($formats);
1347             $replacement[] = $button->to_html(PORTFOLIO_ADD_ICON_LINK);
1348         } else {
1349             $replacement[] = '';
1350         }
1352         $patterns[] = '##timeadded##';
1353         $replacement[] = userdate($record->timecreated);
1355         $patterns[] = '##timemodified##';
1356         $replacement [] = userdate($record->timemodified);
1358         $patterns[]='##approve##';
1359         if (has_capability('mod/data:approve', $context) && ($data->approval) && (!$record->approved)) {
1360             $approveurl = new moodle_url($jumpurl, array('approve' => $record->id));
1361             $approveicon = new pix_icon('t/approve', get_string('approve', 'data'), '', array('class' => 'iconsmall'));
1362             $replacement[] = html_writer::tag('span', $OUTPUT->action_icon($approveurl, $approveicon),
1363                     array('class' => 'approve'));
1364         } else {
1365             $replacement[] = '';
1366         }
1368         $patterns[]='##disapprove##';
1369         if (has_capability('mod/data:approve', $context) && ($data->approval) && ($record->approved)) {
1370             $disapproveurl = new moodle_url($jumpurl, array('disapprove' => $record->id));
1371             $disapproveicon = new pix_icon('t/block', get_string('disapprove', 'data'), '', array('class' => 'iconsmall'));
1372             $replacement[] = html_writer::tag('span', $OUTPUT->action_icon($disapproveurl, $disapproveicon),
1373                     array('class' => 'disapprove'));
1374         } else {
1375             $replacement[] = '';
1376         }
1378         $patterns[] = '##approvalstatus##';
1379         if (!$data->approval) {
1380             $replacement[] = '';
1381         } else if ($record->approved) {
1382             $replacement[] = get_string('approved', 'data');
1383         } else {
1384             $replacement[] = get_string('notapproved', 'data');
1385         }
1387         $patterns[]='##comments##';
1388         if (($template == 'listtemplate') && ($data->comments)) {
1390             if (!empty($CFG->usecomments)) {
1391                 require_once($CFG->dirroot  . '/comment/lib.php');
1392                 list($context, $course, $cm) = get_context_info_array($context->id);
1393                 $cmt = new stdClass();
1394                 $cmt->context = $context;
1395                 $cmt->course  = $course;
1396                 $cmt->cm      = $cm;
1397                 $cmt->area    = 'database_entry';
1398                 $cmt->itemid  = $record->id;
1399                 $cmt->showcount = true;
1400                 $cmt->component = 'mod_data';
1401                 $comment = new comment($cmt);
1402                 $replacement[] = $comment->output(true);
1403             }
1404         } else {
1405             $replacement[] = '';
1406         }
1408         // actual replacement of the tags
1409         $newtext = str_ireplace($patterns, $replacement, $data->{$template});
1411         // no more html formatting and filtering - see MDL-6635
1412         if ($return) {
1413             return $newtext;
1414         } else {
1415             echo $newtext;
1417             // hack alert - return is always false in singletemplate anyway ;-)
1418             /**********************************
1419              *    Printing Ratings Form       *
1420              *********************************/
1421             if ($template == 'singletemplate') {    //prints ratings options
1422                 data_print_ratings($data, $record);
1423             }
1425             /**********************************
1426              *    Printing Comments Form       *
1427              *********************************/
1428             if (($template == 'singletemplate') && ($data->comments)) {
1429                 if (!empty($CFG->usecomments)) {
1430                     require_once($CFG->dirroot . '/comment/lib.php');
1431                     list($context, $course, $cm) = get_context_info_array($context->id);
1432                     $cmt = new stdClass();
1433                     $cmt->context = $context;
1434                     $cmt->course  = $course;
1435                     $cmt->cm      = $cm;
1436                     $cmt->area    = 'database_entry';
1437                     $cmt->itemid  = $record->id;
1438                     $cmt->showcount = true;
1439                     $cmt->component = 'mod_data';
1440                     $comment = new comment($cmt);
1441                     $comment->output(false);
1442                 }
1443             }
1444         }
1445     }
1448 /**
1449  * Return rating related permissions
1450  *
1451  * @param string $contextid the context id
1452  * @param string $component the component to get rating permissions for
1453  * @param string $ratingarea the rating area to get permissions for
1454  * @return array an associative array of the user's rating permissions
1455  */
1456 function data_rating_permissions($contextid, $component, $ratingarea) {
1457     $context = context::instance_by_id($contextid, MUST_EXIST);
1458     if ($component != 'mod_data' || $ratingarea != 'entry') {
1459         return null;
1460     }
1461     return array(
1462         'view'    => has_capability('mod/data:viewrating',$context),
1463         'viewany' => has_capability('mod/data:viewanyrating',$context),
1464         'viewall' => has_capability('mod/data:viewallratings',$context),
1465         'rate'    => has_capability('mod/data:rate',$context)
1466     );
1469 /**
1470  * Validates a submitted rating
1471  * @param array $params submitted data
1472  *            context => object the context in which the rated items exists [required]
1473  *            itemid => int the ID of the object being rated
1474  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
1475  *            rating => int the submitted rating
1476  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
1477  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
1478  * @return boolean true if the rating is valid. Will throw rating_exception if not
1479  */
1480 function data_rating_validate($params) {
1481     global $DB, $USER;
1483     // Check the component is mod_data
1484     if ($params['component'] != 'mod_data') {
1485         throw new rating_exception('invalidcomponent');
1486     }
1488     // Check the ratingarea is entry (the only rating area in data module)
1489     if ($params['ratingarea'] != 'entry') {
1490         throw new rating_exception('invalidratingarea');
1491     }
1493     // Check the rateduserid is not the current user .. you can't rate your own entries
1494     if ($params['rateduserid'] == $USER->id) {
1495         throw new rating_exception('nopermissiontorate');
1496     }
1498     $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
1499                   FROM {data_records} r
1500                   JOIN {data} d ON r.dataid = d.id
1501                  WHERE r.id = :itemid";
1502     $dataparams = array('itemid'=>$params['itemid']);
1503     if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1504         //item doesn't exist
1505         throw new rating_exception('invaliditemid');
1506     }
1508     if ($info->scale != $params['scaleid']) {
1509         //the scale being submitted doesnt match the one in the database
1510         throw new rating_exception('invalidscaleid');
1511     }
1513     //check that the submitted rating is valid for the scale
1515     // lower limit
1516     if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
1517         throw new rating_exception('invalidnum');
1518     }
1520     // upper limit
1521     if ($info->scale < 0) {
1522         //its a custom scale
1523         $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
1524         if ($scalerecord) {
1525             $scalearray = explode(',', $scalerecord->scale);
1526             if ($params['rating'] > count($scalearray)) {
1527                 throw new rating_exception('invalidnum');
1528             }
1529         } else {
1530             throw new rating_exception('invalidscaleid');
1531         }
1532     } else if ($params['rating'] > $info->scale) {
1533         //if its numeric and submitted rating is above maximum
1534         throw new rating_exception('invalidnum');
1535     }
1537     if ($info->approval && !$info->approved) {
1538         //database requires approval but this item isnt approved
1539         throw new rating_exception('nopermissiontorate');
1540     }
1542     // check the item we're rating was created in the assessable time window
1543     if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
1544         if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
1545             throw new rating_exception('notavailable');
1546         }
1547     }
1549     $course = $DB->get_record('course', array('id'=>$info->course), '*', MUST_EXIST);
1550     $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1551     $context = context_module::instance($cm->id);
1553     // if the supplied context doesnt match the item's context
1554     if ($context->id != $params['context']->id) {
1555         throw new rating_exception('invalidcontext');
1556     }
1558     // Make sure groups allow this user to see the item they're rating
1559     $groupid = $info->groupid;
1560     if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
1561         if (!groups_group_exists($groupid)) { // Can't find group
1562             throw new rating_exception('cannotfindgroup');//something is wrong
1563         }
1565         if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
1566             // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
1567             throw new rating_exception('notmemberofgroup');
1568         }
1569     }
1571     return true;
1574 /**
1575  * Can the current user see ratings for a given itemid?
1576  *
1577  * @param array $params submitted data
1578  *            contextid => int contextid [required]
1579  *            component => The component for this module - should always be mod_data [required]
1580  *            ratingarea => object the context in which the rated items exists [required]
1581  *            itemid => int the ID of the object being rated [required]
1582  *            scaleid => int scale id [optional]
1583  * @return bool
1584  * @throws coding_exception
1585  * @throws rating_exception
1586  */
1587 function mod_data_rating_can_see_item_ratings($params) {
1588     global $DB;
1590     // Check the component is mod_data.
1591     if (!isset($params['component']) || $params['component'] != 'mod_data') {
1592         throw new rating_exception('invalidcomponent');
1593     }
1595     // Check the ratingarea is entry (the only rating area in data).
1596     if (!isset($params['ratingarea']) || $params['ratingarea'] != 'entry') {
1597         throw new rating_exception('invalidratingarea');
1598     }
1600     if (!isset($params['itemid'])) {
1601         throw new rating_exception('invaliditemid');
1602     }
1604     $datasql = "SELECT d.id as dataid, d.course, r.groupid
1605                   FROM {data_records} r
1606                   JOIN {data} d ON r.dataid = d.id
1607                  WHERE r.id = :itemid";
1608     $dataparams = array('itemid' => $params['itemid']);
1609     if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1610         // Item doesn't exist.
1611         throw new rating_exception('invaliditemid');
1612     }
1614     // User can see ratings of all participants.
1615     if ($info->groupid == 0) {
1616         return true;
1617     }
1619     $course = $DB->get_record('course', array('id' => $info->course), '*', MUST_EXIST);
1620     $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1622     // Make sure groups allow this user to see the item they're rating.
1623     return groups_group_visible($info->groupid, $course, $cm);
1627 /**
1628  * function that takes in the current data, number of items per page,
1629  * a search string and prints a preference box in view.php
1630  *
1631  * This preference box prints a searchable advanced search template if
1632  *     a) A template is defined
1633  *  b) The advanced search checkbox is checked.
1634  *
1635  * @global object
1636  * @global object
1637  * @param object $data
1638  * @param int $perpage
1639  * @param string $search
1640  * @param string $sort
1641  * @param string $order
1642  * @param array $search_array
1643  * @param int $advanced
1644  * @param string $mode
1645  * @return void
1646  */
1647 function data_print_preference_form($data, $perpage, $search, $sort='', $order='ASC', $search_array = '', $advanced = 0, $mode= ''){
1648     global $CFG, $DB, $PAGE, $OUTPUT;
1650     $cm = get_coursemodule_from_instance('data', $data->id);
1651     $context = context_module::instance($cm->id);
1652     echo '<br /><div class="datapreferences">';
1653     echo '<form id="options" action="view.php" method="get">';
1654     echo '<div>';
1655     echo '<input type="hidden" name="d" value="'.$data->id.'" />';
1656     if ($mode =='asearch') {
1657         $advanced = 1;
1658         echo '<input type="hidden" name="mode" value="list" />';
1659     }
1660     echo '<label for="pref_perpage">'.get_string('pagesize','data').'</label> ';
1661     $pagesizes = array(2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9,10=>10,15=>15,
1662                        20=>20,30=>30,40=>40,50=>50,100=>100,200=>200,300=>300,400=>400,500=>500,1000=>1000);
1663     echo html_writer::select($pagesizes, 'perpage', $perpage, false, array('id'=>'pref_perpage'));
1665     if ($advanced) {
1666         $regsearchclass = 'search_none';
1667         $advancedsearchclass = 'search_inline';
1668     } else {
1669         $regsearchclass = 'search_inline';
1670         $advancedsearchclass = 'search_none';
1671     }
1672     echo '<div id="reg_search" class="' . $regsearchclass . '" >&nbsp;&nbsp;&nbsp;';
1673     echo '<label for="pref_search">'.get_string('search').'</label> <input type="text" size="16" name="search" id= "pref_search" value="'.s($search).'" /></div>';
1674     echo '&nbsp;&nbsp;&nbsp;<label for="pref_sortby">'.get_string('sortby').'</label> ';
1675     // foreach field, print the option
1676     echo '<select name="sort" id="pref_sortby">';
1677     if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'name')) {
1678         echo '<optgroup label="'.get_string('fields', 'data').'">';
1679         foreach ($fields as $field) {
1680             if ($field->id == $sort) {
1681                 echo '<option value="'.$field->id.'" selected="selected">'.$field->name.'</option>';
1682             } else {
1683                 echo '<option value="'.$field->id.'">'.$field->name.'</option>';
1684             }
1685         }
1686         echo '</optgroup>';
1687     }
1688     $options = array();
1689     $options[DATA_TIMEADDED]    = get_string('timeadded', 'data');
1690     $options[DATA_TIMEMODIFIED] = get_string('timemodified', 'data');
1691     $options[DATA_FIRSTNAME]    = get_string('authorfirstname', 'data');
1692     $options[DATA_LASTNAME]     = get_string('authorlastname', 'data');
1693     if ($data->approval and has_capability('mod/data:approve', $context)) {
1694         $options[DATA_APPROVED] = get_string('approved', 'data');
1695     }
1696     echo '<optgroup label="'.get_string('other', 'data').'">';
1697     foreach ($options as $key => $name) {
1698         if ($key == $sort) {
1699             echo '<option value="'.$key.'" selected="selected">'.$name.'</option>';
1700         } else {
1701             echo '<option value="'.$key.'">'.$name.'</option>';
1702         }
1703     }
1704     echo '</optgroup>';
1705     echo '</select>';
1706     echo '<label for="pref_order" class="accesshide">'.get_string('order').'</label>';
1707     echo '<select id="pref_order" name="order">';
1708     if ($order == 'ASC') {
1709         echo '<option value="ASC" selected="selected">'.get_string('ascending','data').'</option>';
1710     } else {
1711         echo '<option value="ASC">'.get_string('ascending','data').'</option>';
1712     }
1713     if ($order == 'DESC') {
1714         echo '<option value="DESC" selected="selected">'.get_string('descending','data').'</option>';
1715     } else {
1716         echo '<option value="DESC">'.get_string('descending','data').'</option>';
1717     }
1718     echo '</select>';
1720     if ($advanced) {
1721         $checked = ' checked="checked" ';
1722     }
1723     else {
1724         $checked = '';
1725     }
1726     $PAGE->requires->js('/mod/data/data.js');
1727     echo '&nbsp;<input type="hidden" name="advanced" value="0" />';
1728     echo '&nbsp;<input type="hidden" name="filter" value="1" />';
1729     echo '&nbsp;<input type="checkbox" id="advancedcheckbox" name="advanced" value="1" '.$checked.' onchange="showHideAdvSearch(this.checked);" /><label for="advancedcheckbox">'.get_string('advancedsearch', 'data').'</label>';
1730     echo '&nbsp;<input type="submit" value="'.get_string('savesettings','data').'" />';
1732     echo '<br />';
1733     echo '<div class="' . $advancedsearchclass . '" id="data_adv_form">';
1734     echo '<table class="boxaligncenter">';
1736     // print ASC or DESC
1737     echo '<tr><td colspan="2">&nbsp;</td></tr>';
1738     $i = 0;
1740     // Determine if we are printing all fields for advanced search, or the template for advanced search
1741     // If a template is not defined, use the deafault template and display all fields.
1742     if(empty($data->asearchtemplate)) {
1743         data_generate_default_template($data, 'asearchtemplate');
1744     }
1746     static $fields = array();
1747     static $dataid = null;
1749     if (empty($dataid)) {
1750         $dataid = $data->id;
1751     } else if ($dataid != $data->id) {
1752         $fields = array();
1753     }
1755     if (empty($fields)) {
1756         $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id));
1757         foreach ($fieldrecords as $fieldrecord) {
1758             $fields[]= data_get_field($fieldrecord, $data);
1759         }
1760     }
1762     // Replacing tags
1763     $patterns = array();
1764     $replacement = array();
1766     // Then we generate strings to replace for normal tags
1767     foreach ($fields as $field) {
1768         $fieldname = $field->field->name;
1769         $fieldname = preg_quote($fieldname, '/');
1770         $patterns[] = "/\[\[$fieldname\]\]/i";
1771         $searchfield = data_get_field_from_id($field->field->id, $data);
1772         if (!empty($search_array[$field->field->id]->data)) {
1773             $replacement[] = $searchfield->display_search_field($search_array[$field->field->id]->data);
1774         } else {
1775             $replacement[] = $searchfield->display_search_field();
1776         }
1777     }
1778     $fn = !empty($search_array[DATA_FIRSTNAME]->data) ? $search_array[DATA_FIRSTNAME]->data : '';
1779     $ln = !empty($search_array[DATA_LASTNAME]->data) ? $search_array[DATA_LASTNAME]->data : '';
1780     $patterns[]    = '/##firstname##/';
1781     $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).'" />';
1782     $patterns[]    = '/##lastname##/';
1783     $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).'" />';
1785     // actual replacement of the tags
1786     $newtext = preg_replace($patterns, $replacement, $data->asearchtemplate);
1788     $options = new stdClass();
1789     $options->para=false;
1790     $options->noclean=true;
1791     echo '<tr><td>';
1792     echo format_text($newtext, FORMAT_HTML, $options);
1793     echo '</td></tr>';
1795     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>';
1796     echo '</table>';
1797     echo '</div>';
1798     echo '</div>';
1799     echo '</form>';
1800     echo '</div>';
1803 /**
1804  * @global object
1805  * @global object
1806  * @param object $data
1807  * @param object $record
1808  * @return void Output echo'd
1809  */
1810 function data_print_ratings($data, $record) {
1811     global $OUTPUT;
1812     if (!empty($record->rating)){
1813         echo $OUTPUT->render($record->rating);
1814     }
1817 /**
1818  * List the actions that correspond to a view of this module.
1819  * This is used by the participation report.
1820  *
1821  * Note: This is not used by new logging system. Event with
1822  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1823  *       be considered as view action.
1824  *
1825  * @return array
1826  */
1827 function data_get_view_actions() {
1828     return array('view');
1831 /**
1832  * List the actions that correspond to a post of this module.
1833  * This is used by the participation report.
1834  *
1835  * Note: This is not used by new logging system. Event with
1836  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1837  *       will be considered as post action.
1838  *
1839  * @return array
1840  */
1841 function data_get_post_actions() {
1842     return array('add','update','record delete');
1845 /**
1846  * @param string $name
1847  * @param int $dataid
1848  * @param int $fieldid
1849  * @return bool
1850  */
1851 function data_fieldname_exists($name, $dataid, $fieldid = 0) {
1852     global $DB;
1854     if (!is_numeric($name)) {
1855         $like = $DB->sql_like('df.name', ':name', false);
1856     } else {
1857         $like = "df.name = :name";
1858     }
1859     $params = array('name'=>$name);
1860     if ($fieldid) {
1861         $params['dataid']   = $dataid;
1862         $params['fieldid1'] = $fieldid;
1863         $params['fieldid2'] = $fieldid;
1864         return $DB->record_exists_sql("SELECT * FROM {data_fields} df
1865                                         WHERE $like AND df.dataid = :dataid
1866                                               AND ((df.id < :fieldid1) OR (df.id > :fieldid2))", $params);
1867     } else {
1868         $params['dataid']   = $dataid;
1869         return $DB->record_exists_sql("SELECT * FROM {data_fields} df
1870                                         WHERE $like AND df.dataid = :dataid", $params);
1871     }
1874 /**
1875  * @param array $fieldinput
1876  */
1877 function data_convert_arrays_to_strings(&$fieldinput) {
1878     foreach ($fieldinput as $key => $val) {
1879         if (is_array($val)) {
1880             $str = '';
1881             foreach ($val as $inner) {
1882                 $str .= $inner . ',';
1883             }
1884             $str = substr($str, 0, -1);
1886             $fieldinput->$key = $str;
1887         }
1888     }
1892 /**
1893  * Converts a database (module instance) to use the Roles System
1894  *
1895  * @global object
1896  * @global object
1897  * @uses CONTEXT_MODULE
1898  * @uses CAP_PREVENT
1899  * @uses CAP_ALLOW
1900  * @param object $data a data object with the same attributes as a record
1901  *                     from the data database table
1902  * @param int $datamodid the id of the data module, from the modules table
1903  * @param array $teacherroles array of roles that have archetype teacher
1904  * @param array $studentroles array of roles that have archetype student
1905  * @param array $guestroles array of roles that have archetype guest
1906  * @param int $cmid the course_module id for this data instance
1907  * @return boolean data module was converted or not
1908  */
1909 function data_convert_to_roles($data, $teacherroles=array(), $studentroles=array(), $cmid=NULL) {
1910     global $CFG, $DB, $OUTPUT;
1912     if (!isset($data->participants) && !isset($data->assesspublic)
1913             && !isset($data->groupmode)) {
1914         // We assume that this database has already been converted to use the
1915         // Roles System. above fields get dropped the data module has been
1916         // upgraded to use Roles.
1917         return false;
1918     }
1920     if (empty($cmid)) {
1921         // We were not given the course_module id. Try to find it.
1922         if (!$cm = get_coursemodule_from_instance('data', $data->id)) {
1923             echo $OUTPUT->notification('Could not get the course module for the data');
1924             return false;
1925         } else {
1926             $cmid = $cm->id;
1927         }
1928     }
1929     $context = context_module::instance($cmid);
1932     // $data->participants:
1933     // 1 - Only teachers can add entries
1934     // 3 - Teachers and students can add entries
1935     switch ($data->participants) {
1936         case 1:
1937             foreach ($studentroles as $studentrole) {
1938                 assign_capability('mod/data:writeentry', CAP_PREVENT, $studentrole->id, $context->id);
1939             }
1940             foreach ($teacherroles as $teacherrole) {
1941                 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
1942             }
1943             break;
1944         case 3:
1945             foreach ($studentroles as $studentrole) {
1946                 assign_capability('mod/data:writeentry', CAP_ALLOW, $studentrole->id, $context->id);
1947             }
1948             foreach ($teacherroles as $teacherrole) {
1949                 assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
1950             }
1951             break;
1952     }
1954     // $data->assessed:
1955     // 2 - Only teachers can rate posts
1956     // 1 - Everyone can rate posts
1957     // 0 - No one can rate posts
1958     switch ($data->assessed) {
1959         case 0:
1960             foreach ($studentroles as $studentrole) {
1961                 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
1962             }
1963             foreach ($teacherroles as $teacherrole) {
1964                 assign_capability('mod/data:rate', CAP_PREVENT, $teacherrole->id, $context->id);
1965             }
1966             break;
1967         case 1:
1968             foreach ($studentroles as $studentrole) {
1969                 assign_capability('mod/data:rate', CAP_ALLOW, $studentrole->id, $context->id);
1970             }
1971             foreach ($teacherroles as $teacherrole) {
1972                 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
1973             }
1974             break;
1975         case 2:
1976             foreach ($studentroles as $studentrole) {
1977                 assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
1978             }
1979             foreach ($teacherroles as $teacherrole) {
1980                 assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
1981             }
1982             break;
1983     }
1985     // $data->assesspublic:
1986     // 0 - Students can only see their own ratings
1987     // 1 - Students can see everyone's ratings
1988     switch ($data->assesspublic) {
1989         case 0:
1990             foreach ($studentroles as $studentrole) {
1991                 assign_capability('mod/data:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
1992             }
1993             foreach ($teacherroles as $teacherrole) {
1994                 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
1995             }
1996             break;
1997         case 1:
1998             foreach ($studentroles as $studentrole) {
1999                 assign_capability('mod/data:viewrating', CAP_ALLOW, $studentrole->id, $context->id);
2000             }
2001             foreach ($teacherroles as $teacherrole) {
2002                 assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2003             }
2004             break;
2005     }
2007     if (empty($cm)) {
2008         $cm = $DB->get_record('course_modules', array('id'=>$cmid));
2009     }
2011     switch ($cm->groupmode) {
2012         case NOGROUPS:
2013             break;
2014         case SEPARATEGROUPS:
2015             foreach ($studentroles as $studentrole) {
2016                 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
2017             }
2018             foreach ($teacherroles as $teacherrole) {
2019                 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2020             }
2021             break;
2022         case VISIBLEGROUPS:
2023             foreach ($studentroles as $studentrole) {
2024                 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
2025             }
2026             foreach ($teacherroles as $teacherrole) {
2027                 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2028             }
2029             break;
2030     }
2031     return true;
2034 /**
2035  * Returns the best name to show for a preset
2036  *
2037  * @param string $shortname
2038  * @param  string $path
2039  * @return string
2040  */
2041 function data_preset_name($shortname, $path) {
2043     // We are looking inside the preset itself as a first choice, but also in normal data directory
2044     $string = get_string('modulename', 'datapreset_'.$shortname);
2046     if (substr($string, 0, 1) == '[') {
2047         return $shortname;
2048     } else {
2049         return $string;
2050     }
2053 /**
2054  * Returns an array of all the available presets.
2055  *
2056  * @return array
2057  */
2058 function data_get_available_presets($context) {
2059     global $CFG, $USER;
2061     $presets = array();
2063     // First load the ratings sub plugins that exist within the modules preset dir
2064     if ($dirs = core_component::get_plugin_list('datapreset')) {
2065         foreach ($dirs as $dir=>$fulldir) {
2066             if (is_directory_a_preset($fulldir)) {
2067                 $preset = new stdClass();
2068                 $preset->path = $fulldir;
2069                 $preset->userid = 0;
2070                 $preset->shortname = $dir;
2071                 $preset->name = data_preset_name($dir, $fulldir);
2072                 if (file_exists($fulldir.'/screenshot.jpg')) {
2073                     $preset->screenshot = $CFG->wwwroot.'/mod/data/preset/'.$dir.'/screenshot.jpg';
2074                 } else if (file_exists($fulldir.'/screenshot.png')) {
2075                     $preset->screenshot = $CFG->wwwroot.'/mod/data/preset/'.$dir.'/screenshot.png';
2076                 } else if (file_exists($fulldir.'/screenshot.gif')) {
2077                     $preset->screenshot = $CFG->wwwroot.'/mod/data/preset/'.$dir.'/screenshot.gif';
2078                 }
2079                 $presets[] = $preset;
2080             }
2081         }
2082     }
2083     // Now add to that the site presets that people have saved
2084     $presets = data_get_available_site_presets($context, $presets);
2085     return $presets;
2088 /**
2089  * Gets an array of all of the presets that users have saved to the site.
2090  *
2091  * @param stdClass $context The context that we are looking from.
2092  * @param array $presets
2093  * @return array An array of presets
2094  */
2095 function data_get_available_site_presets($context, array $presets=array()) {
2096     global $USER;
2098     $fs = get_file_storage();
2099     $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA);
2100     $canviewall = has_capability('mod/data:viewalluserpresets', $context);
2101     if (empty($files)) {
2102         return $presets;
2103     }
2104     foreach ($files as $file) {
2105         if (($file->is_directory() && $file->get_filepath()=='/') || !$file->is_directory() || (!$canviewall && $file->get_userid() != $USER->id)) {
2106             continue;
2107         }
2108         $preset = new stdClass;
2109         $preset->path = $file->get_filepath();
2110         $preset->name = trim($preset->path, '/');
2111         $preset->shortname = $preset->name;
2112         $preset->userid = $file->get_userid();
2113         $preset->id = $file->get_id();
2114         $preset->storedfile = $file;
2115         $presets[] = $preset;
2116     }
2117     return $presets;
2120 /**
2121  * Deletes a saved preset.
2122  *
2123  * @param string $name
2124  * @return bool
2125  */
2126 function data_delete_site_preset($name) {
2127     $fs = get_file_storage();
2129     $files = $fs->get_directory_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/');
2130     if (!empty($files)) {
2131         foreach ($files as $file) {
2132             $file->delete();
2133         }
2134     }
2136     $dir = $fs->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/', '.');
2137     if (!empty($dir)) {
2138         $dir->delete();
2139     }
2140     return true;
2143 /**
2144  * Prints the heads for a page
2145  *
2146  * @param stdClass $course
2147  * @param stdClass $cm
2148  * @param stdClass $data
2149  * @param string $currenttab
2150  */
2151 function data_print_header($course, $cm, $data, $currenttab='') {
2153     global $CFG, $displaynoticegood, $displaynoticebad, $OUTPUT, $PAGE;
2155     $PAGE->set_title($data->name);
2156     echo $OUTPUT->header();
2157     echo $OUTPUT->heading(format_string($data->name), 2);
2158     echo $OUTPUT->box(format_module_intro('data', $data, $cm->id), 'generalbox', 'intro');
2160     // Groups needed for Add entry tab
2161     $currentgroup = groups_get_activity_group($cm);
2162     $groupmode = groups_get_activity_groupmode($cm);
2164     // Print the tabs
2166     if ($currenttab) {
2167         include('tabs.php');
2168     }
2170     // Print any notices
2172     if (!empty($displaynoticegood)) {
2173         echo $OUTPUT->notification($displaynoticegood, 'notifysuccess');    // good (usually green)
2174     } else if (!empty($displaynoticebad)) {
2175         echo $OUTPUT->notification($displaynoticebad);                     // bad (usuually red)
2176     }
2179 /**
2180  * Can user add more entries?
2181  *
2182  * @param object $data
2183  * @param mixed $currentgroup
2184  * @param int $groupmode
2185  * @param stdClass $context
2186  * @return bool
2187  */
2188 function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = null) {
2189     global $USER;
2191     if (empty($context)) {
2192         $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
2193         $context = context_module::instance($cm->id);
2194     }
2196     if (has_capability('mod/data:manageentries', $context)) {
2197         // no entry limits apply if user can manage
2199     } else if (!has_capability('mod/data:writeentry', $context)) {
2200         return false;
2202     } else if (data_atmaxentries($data)) {
2203         return false;
2204     } else if (data_in_readonly_period($data)) {
2205         // Check whether we're in a read-only period
2206         return false;
2207     }
2209     if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
2210         return true;
2211     }
2213     if ($currentgroup) {
2214         return groups_is_member($currentgroup);
2215     } else {
2216         //else it might be group 0 in visible mode
2217         if ($groupmode == VISIBLEGROUPS){
2218             return true;
2219         } else {
2220             return false;
2221         }
2222     }
2225 /**
2226  * Check whether the current user is allowed to manage the given record considering manageentries capability,
2227  * data_in_readonly_period() result, ownership (determined by data_isowner()) and manageapproved setting.
2228  * @param mixed $record record object or id
2229  * @param object $data data object
2230  * @param object $context context object
2231  * @return bool returns true if the user is allowd to edit the entry, false otherwise
2232  */
2233 function data_user_can_manage_entry($record, $data, $context) {
2234     global $DB;
2236     if (has_capability('mod/data:manageentries', $context)) {
2237         return true;
2238     }
2240     // Check whether this activity is read-only at present.
2241     $readonly = data_in_readonly_period($data);
2243     if (!$readonly) {
2244         // Get record object from db if just id given like in data_isowner.
2245         // ...done before calling data_isowner() to avoid querying db twice.
2246         if (!is_object($record)) {
2247             if (!$record = $DB->get_record('data_records', array('id' => $record))) {
2248                 return false;
2249             }
2250         }
2251         if (data_isowner($record)) {
2252             if ($data->approval && $record->approved) {
2253                 return $data->manageapproved == 1;
2254             } else {
2255                 return true;
2256             }
2257         }
2258     }
2260     return false;
2263 /**
2264  * Check whether the specified database activity is currently in a read-only period
2265  *
2266  * @param object $data
2267  * @return bool returns true if the time fields in $data indicate a read-only period; false otherwise
2268  */
2269 function data_in_readonly_period($data) {
2270     $now = time();
2271     if (!$data->timeviewfrom && !$data->timeviewto) {
2272         return false;
2273     } else if (($data->timeviewfrom && $now < $data->timeviewfrom) || ($data->timeviewto && $now > $data->timeviewto)) {
2274         return false;
2275     }
2276     return true;
2279 /**
2280  * @return bool
2281  */
2282 function is_directory_a_preset($directory) {
2283     $directory = rtrim($directory, '/\\') . '/';
2284     $status = file_exists($directory.'singletemplate.html') &&
2285               file_exists($directory.'listtemplate.html') &&
2286               file_exists($directory.'listtemplateheader.html') &&
2287               file_exists($directory.'listtemplatefooter.html') &&
2288               file_exists($directory.'addtemplate.html') &&
2289               file_exists($directory.'rsstemplate.html') &&
2290               file_exists($directory.'rsstitletemplate.html') &&
2291               file_exists($directory.'csstemplate.css') &&
2292               file_exists($directory.'jstemplate.js') &&
2293               file_exists($directory.'preset.xml');
2295     return $status;
2298 /**
2299  * Abstract class used for data preset importers
2300  */
2301 abstract class data_preset_importer {
2303     protected $course;
2304     protected $cm;
2305     protected $module;
2306     protected $directory;
2308     /**
2309      * Constructor
2310      *
2311      * @param stdClass $course
2312      * @param stdClass $cm
2313      * @param stdClass $module
2314      * @param string $directory
2315      */
2316     public function __construct($course, $cm, $module, $directory) {
2317         $this->course = $course;
2318         $this->cm = $cm;
2319         $this->module = $module;
2320         $this->directory = $directory;
2321     }
2323     /**
2324      * Returns the name of the directory the preset is located in
2325      * @return string
2326      */
2327     public function get_directory() {
2328         return basename($this->directory);
2329     }
2331     /**
2332      * Retreive the contents of a file. That file may either be in a conventional directory of the Moodle file storage
2333      * @param file_storage $filestorage. should be null if using a conventional directory
2334      * @param stored_file $fileobj the directory to look in. null if using a conventional directory
2335      * @param string $dir the directory to look in. null if using the Moodle file storage
2336      * @param string $filename the name of the file we want
2337      * @return string the contents of the file or null if the file doesn't exist.
2338      */
2339     public function data_preset_get_file_contents(&$filestorage, &$fileobj, $dir, $filename) {
2340         if(empty($filestorage) || empty($fileobj)) {
2341             if (substr($dir, -1)!='/') {
2342                 $dir .= '/';
2343             }
2344             if (file_exists($dir.$filename)) {
2345                 return file_get_contents($dir.$filename);
2346             } else {
2347                 return null;
2348             }
2349         } else {
2350             if ($filestorage->file_exists(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename)) {
2351                 $file = $filestorage->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename);
2352                 return $file->get_content();
2353             } else {
2354                 return null;
2355             }
2356         }
2358     }
2359     /**
2360      * Gets the preset settings
2361      * @global moodle_database $DB
2362      * @return stdClass
2363      */
2364     public function get_preset_settings() {
2365         global $DB;
2367         $fs = $fileobj = null;
2368         if (!is_directory_a_preset($this->directory)) {
2369             //maybe the user requested a preset stored in the Moodle file storage
2371             $fs = get_file_storage();
2372             $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA);
2374             //preset name to find will be the final element of the directory
2375             $explodeddirectory = explode('/', $this->directory);
2376             $presettofind = end($explodeddirectory);
2378             //now go through the available files available and see if we can find it
2379             foreach ($files as $file) {
2380                 if (($file->is_directory() && $file->get_filepath()=='/') || !$file->is_directory()) {
2381                     continue;
2382                 }
2383                 $presetname = trim($file->get_filepath(), '/');
2384                 if ($presetname==$presettofind) {
2385                     $this->directory = $presetname;
2386                     $fileobj = $file;
2387                 }
2388             }
2390             if (empty($fileobj)) {
2391                 print_error('invalidpreset', 'data', '', $this->directory);
2392             }
2393         }
2395         $allowed_settings = array(
2396             'intro',
2397             'comments',
2398             'requiredentries',
2399             'requiredentriestoview',
2400             'maxentries',
2401             'rssarticles',
2402             'approval',
2403             'defaultsortdir',
2404             'defaultsort');
2406         $result = new stdClass;
2407         $result->settings = new stdClass;
2408         $result->importfields = array();
2409         $result->currentfields = $DB->get_records('data_fields', array('dataid'=>$this->module->id));
2410         if (!$result->currentfields) {
2411             $result->currentfields = array();
2412         }
2415         /* Grab XML */
2416         $presetxml = $this->data_preset_get_file_contents($fs, $fileobj, $this->directory,'preset.xml');
2417         $parsedxml = xmlize($presetxml, 0);
2419         /* First, do settings. Put in user friendly array. */
2420         $settingsarray = $parsedxml['preset']['#']['settings'][0]['#'];
2421         $result->settings = new StdClass();
2422         foreach ($settingsarray as $setting => $value) {
2423             if (!is_array($value) || !in_array($setting, $allowed_settings)) {
2424                 // unsupported setting
2425                 continue;
2426             }
2427             $result->settings->$setting = $value[0]['#'];
2428         }
2430         /* Now work out fields to user friendly array */
2431         $fieldsarray = $parsedxml['preset']['#']['field'];
2432         foreach ($fieldsarray as $field) {
2433             if (!is_array($field)) {
2434                 continue;
2435             }
2436             $f = new StdClass();
2437             foreach ($field['#'] as $param => $value) {
2438                 if (!is_array($value)) {
2439                     continue;
2440                 }
2441                 $f->$param = $value[0]['#'];
2442             }
2443             $f->dataid = $this->module->id;
2444             $f->type = clean_param($f->type, PARAM_ALPHA);
2445             $result->importfields[] = $f;
2446         }
2447         /* Now add the HTML templates to the settings array so we can update d */
2448         $result->settings->singletemplate     = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"singletemplate.html");
2449         $result->settings->listtemplate       = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplate.html");
2450         $result->settings->listtemplateheader = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplateheader.html");
2451         $result->settings->listtemplatefooter = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplatefooter.html");
2452         $result->settings->addtemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"addtemplate.html");
2453         $result->settings->rsstemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstemplate.html");
2454         $result->settings->rsstitletemplate   = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstitletemplate.html");
2455         $result->settings->csstemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"csstemplate.css");
2456         $result->settings->jstemplate         = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"jstemplate.js");
2457         $result->settings->asearchtemplate    = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"asearchtemplate.html");
2459         $result->settings->instance = $this->module->id;
2460         return $result;
2461     }
2463     /**
2464      * Import the preset into the given database module
2465      * @return bool
2466      */
2467     function import($overwritesettings) {
2468         global $DB, $CFG;
2470         $params = $this->get_preset_settings();
2471         $settings = $params->settings;
2472         $newfields = $params->importfields;
2473         $currentfields = $params->currentfields;
2474         $preservedfields = array();
2476         /* Maps fields and makes new ones */
2477         if (!empty($newfields)) {
2478             /* We require an injective mapping, and need to know what to protect */
2479             foreach ($newfields as $nid => $newfield) {
2480                 $cid = optional_param("field_$nid", -1, PARAM_INT);
2481                 if ($cid == -1) {
2482                     continue;
2483                 }
2484                 if (array_key_exists($cid, $preservedfields)){
2485                     print_error('notinjectivemap', 'data');
2486                 }
2487                 else $preservedfields[$cid] = true;
2488             }
2490             foreach ($newfields as $nid => $newfield) {
2491                 $cid = optional_param("field_$nid", -1, PARAM_INT);
2493                 /* A mapping. Just need to change field params. Data kept. */
2494                 if ($cid != -1 and isset($currentfields[$cid])) {
2495                     $fieldobject = data_get_field_from_id($currentfields[$cid]->id, $this->module);
2496                     foreach ($newfield as $param => $value) {
2497                         if ($param != "id") {
2498                             $fieldobject->field->$param = $value;
2499                         }
2500                     }
2501                     unset($fieldobject->field->similarfield);
2502                     $fieldobject->update_field();
2503                     unset($fieldobject);
2504                 } else {
2505                     /* Make a new field */
2506                     include_once("field/$newfield->type/field.class.php");
2508                     if (!isset($newfield->description)) {
2509                         $newfield->description = '';
2510                     }
2511                     $classname = 'data_field_'.$newfield->type;
2512                     $fieldclass = new $classname($newfield, $this->module);
2513                     $fieldclass->insert_field();
2514                     unset($fieldclass);
2515                 }
2516             }
2517         }
2519         /* Get rid of all old unused data */
2520         if (!empty($preservedfields)) {
2521             foreach ($currentfields as $cid => $currentfield) {
2522                 if (!array_key_exists($cid, $preservedfields)) {
2523                     /* Data not used anymore so wipe! */
2524                     print "Deleting field $currentfield->name<br />";
2526                     $id = $currentfield->id;
2527                     //Why delete existing data records and related comments/ratings??
2528                     $DB->delete_records('data_content', array('fieldid'=>$id));
2529                     $DB->delete_records('data_fields', array('id'=>$id));
2530                 }
2531             }
2532         }
2534         // handle special settings here
2535         if (!empty($settings->defaultsort)) {
2536             if (is_numeric($settings->defaultsort)) {
2537                 // old broken value
2538                 $settings->defaultsort = 0;
2539             } else {
2540                 $settings->defaultsort = (int)$DB->get_field('data_fields', 'id', array('dataid'=>$this->module->id, 'name'=>$settings->defaultsort));
2541             }
2542         } else {
2543             $settings->defaultsort = 0;
2544         }
2546         // do we want to overwrite all current database settings?
2547         if ($overwritesettings) {
2548             // all supported settings
2549             $overwrite = array_keys((array)$settings);
2550         } else {
2551             // only templates and sorting
2552             $overwrite = array('singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter',
2553                                'addtemplate', 'rsstemplate', 'rsstitletemplate', 'csstemplate', 'jstemplate',
2554                                'asearchtemplate', 'defaultsortdir', 'defaultsort');
2555         }
2557         // now overwrite current data settings
2558         foreach ($this->module as $prop=>$unused) {
2559             if (in_array($prop, $overwrite)) {
2560                 $this->module->$prop = $settings->$prop;
2561             }
2562         }
2564         data_update_instance($this->module);
2566         return $this->cleanup();
2567     }
2569     /**
2570      * Any clean up routines should go here
2571      * @return bool
2572      */
2573     public function cleanup() {
2574         return true;
2575     }
2578 /**
2579  * Data preset importer for uploaded presets
2580  */
2581 class data_preset_upload_importer extends data_preset_importer {
2582     public function __construct($course, $cm, $module, $filepath) {
2583         global $USER;
2584         if (is_file($filepath)) {
2585             $fp = get_file_packer();
2586             if ($fp->extract_to_pathname($filepath, $filepath.'_extracted')) {
2587                 fulldelete($filepath);
2588             }
2589             $filepath .= '_extracted';
2590         }
2591         parent::__construct($course, $cm, $module, $filepath);
2592     }
2593     public function cleanup() {
2594         return fulldelete($this->directory);
2595     }
2598 /**
2599  * Data preset importer for existing presets
2600  */
2601 class data_preset_existing_importer extends data_preset_importer {
2602     protected $userid;
2603     public function __construct($course, $cm, $module, $fullname) {
2604         global $USER;
2605         list($userid, $shortname) = explode('/', $fullname, 2);
2606         $context = context_module::instance($cm->id);
2607         if ($userid && ($userid != $USER->id) && !has_capability('mod/data:manageuserpresets', $context) && !has_capability('mod/data:viewalluserpresets', $context)) {
2608            throw new coding_exception('Invalid preset provided');
2609         }
2611         $this->userid = $userid;
2612         $filepath = data_preset_path($course, $userid, $shortname);
2613         parent::__construct($course, $cm, $module, $filepath);
2614     }
2615     public function get_userid() {
2616         return $this->userid;
2617     }
2620 /**
2621  * @global object
2622  * @global object
2623  * @param object $course
2624  * @param int $userid
2625  * @param string $shortname
2626  * @return string
2627  */
2628 function data_preset_path($course, $userid, $shortname) {
2629     global $USER, $CFG;
2631     $context = context_course::instance($course->id);
2633     $userid = (int)$userid;
2635     $path = null;
2636     if ($userid > 0 && ($userid == $USER->id || has_capability('mod/data:viewalluserpresets', $context))) {
2637         $path = $CFG->dataroot.'/data/preset/'.$userid.'/'.$shortname;
2638     } else if ($userid == 0) {
2639         $path = $CFG->dirroot.'/mod/data/preset/'.$shortname;
2640     } else if ($userid < 0) {
2641         $path = $CFG->tempdir.'/data/'.-$userid.'/'.$shortname;
2642     }
2644     return $path;
2647 /**
2648  * Implementation of the function for printing the form elements that control
2649  * whether the course reset functionality affects the data.
2650  *
2651  * @param $mform form passed by reference
2652  */
2653 function data_reset_course_form_definition(&$mform) {
2654     $mform->addElement('header', 'dataheader', get_string('modulenameplural', 'data'));
2655     $mform->addElement('checkbox', 'reset_data', get_string('deleteallentries','data'));
2657     $mform->addElement('checkbox', 'reset_data_notenrolled', get_string('deletenotenrolled', 'data'));
2658     $mform->disabledIf('reset_data_notenrolled', 'reset_data', 'checked');
2660     $mform->addElement('checkbox', 'reset_data_ratings', get_string('deleteallratings'));
2661     $mform->disabledIf('reset_data_ratings', 'reset_data', 'checked');
2663     $mform->addElement('checkbox', 'reset_data_comments', get_string('deleteallcomments'));
2664     $mform->disabledIf('reset_data_comments', 'reset_data', 'checked');
2667 /**
2668  * Course reset form defaults.
2669  * @return array
2670  */
2671 function data_reset_course_form_defaults($course) {
2672     return array('reset_data'=>0, 'reset_data_ratings'=>1, 'reset_data_comments'=>1, 'reset_data_notenrolled'=>0);
2675 /**
2676  * Removes all grades from gradebook
2677  *
2678  * @global object
2679  * @global object
2680  * @param int $courseid
2681  * @param string $type optional type
2682  */
2683 function data_reset_gradebook($courseid, $type='') {
2684     global $CFG, $DB;
2686     $sql = "SELECT d.*, cm.idnumber as cmidnumber, d.course as courseid
2687               FROM {data} d, {course_modules} cm, {modules} m
2688              WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id AND d.course=?";
2690     if ($datas = $DB->get_records_sql($sql, array($courseid))) {
2691         foreach ($datas as $data) {
2692             data_grade_item_update($data, 'reset');
2693         }
2694     }
2697 /**
2698  * Actual implementation of the reset course functionality, delete all the
2699  * data responses for course $data->courseid.
2700  *
2701  * @global object
2702  * @global object
2703  * @param object $data the data submitted from the reset course.
2704  * @return array status array
2705  */
2706 function data_reset_userdata($data) {
2707     global $CFG, $DB;
2708     require_once($CFG->libdir.'/filelib.php');
2709     require_once($CFG->dirroot.'/rating/lib.php');
2711     $componentstr = get_string('modulenameplural', 'data');
2712     $status = array();
2714     $allrecordssql = "SELECT r.id
2715                         FROM {data_records} r
2716                              INNER JOIN {data} d ON r.dataid = d.id
2717                        WHERE d.course = ?";
2719     $alldatassql = "SELECT d.id
2720                       FROM {data} d
2721                      WHERE d.course=?";
2723     $rm = new rating_manager();
2724     $ratingdeloptions = new stdClass;
2725     $ratingdeloptions->component = 'mod_data';
2726     $ratingdeloptions->ratingarea = 'entry';
2728     // Set the file storage - may need it to remove files later.
2729     $fs = get_file_storage();
2731     // delete entries if requested
2732     if (!empty($data->reset_data)) {
2733         $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2734         $DB->delete_records_select('data_content', "recordid IN ($allrecordssql)", array($data->courseid));
2735         $DB->delete_records_select('data_records', "dataid IN ($alldatassql)", array($data->courseid));
2737         if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2738             foreach ($datas as $dataid=>$unused) {
2739                 if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2740                     continue;
2741                 }
2742                 $datacontext = context_module::instance($cm->id);
2744                 // Delete any files that may exist.
2745                 $fs->delete_area_files($datacontext->id, 'mod_data', 'content');
2747                 $ratingdeloptions->contextid = $datacontext->id;
2748                 $rm->delete_ratings($ratingdeloptions);
2749             }
2750         }
2752         if (empty($data->reset_gradebook_grades)) {
2753             // remove all grades from gradebook
2754             data_reset_gradebook($data->courseid);
2755         }
2756         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallentries', 'data'), 'error'=>false);
2757     }
2759     // remove entries by users not enrolled into course
2760     if (!empty($data->reset_data_notenrolled)) {
2761         $recordssql = "SELECT r.id, r.userid, r.dataid, u.id AS userexists, u.deleted AS userdeleted
2762                          FROM {data_records} r
2763                               JOIN {data} d ON r.dataid = d.id
2764                               LEFT JOIN {user} u ON r.userid = u.id
2765                         WHERE d.course = ? AND r.userid > 0";
2767         $course_context = context_course::instance($data->courseid);
2768         $notenrolled = array();
2769         $fields = array();
2770         $rs = $DB->get_recordset_sql($recordssql, array($data->courseid));
2771         foreach ($rs as $record) {
2772             if (array_key_exists($record->userid, $notenrolled) or !$record->userexists or $record->userdeleted
2773               or !is_enrolled($course_context, $record->userid)) {
2774                 //delete ratings
2775                 if (!$cm = get_coursemodule_from_instance('data', $record->dataid)) {
2776                     continue;
2777                 }
2778                 $datacontext = context_module::instance($cm->id);
2779                 $ratingdeloptions->contextid = $datacontext->id;
2780                 $ratingdeloptions->itemid = $record->id;
2781                 $rm->delete_ratings($ratingdeloptions);
2783                 // Delete any files that may exist.
2784                 if ($contents = $DB->get_records('data_content', array('recordid' => $record->id), '', 'id')) {
2785                     foreach ($contents as $content) {
2786                         $fs->delete_area_files($datacontext->id, 'mod_data', 'content', $content->id);
2787                     }
2788                 }
2789                 $notenrolled[$record->userid] = true;
2791                 $DB->delete_records('comments', array('itemid' => $record->id, 'commentarea' => 'database_entry'));
2792                 $DB->delete_records('data_content', array('recordid' => $record->id));
2793                 $DB->delete_records('data_records', array('id' => $record->id));
2794             }
2795         }
2796         $rs->close();
2797         $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'data'), 'error'=>false);
2798     }
2800     // remove all ratings
2801     if (!empty($data->reset_data_ratings)) {
2802         if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2803             foreach ($datas as $dataid=>$unused) {
2804                 if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2805                     continue;
2806                 }
2807                 $datacontext = context_module::instance($cm->id);
2809                 $ratingdeloptions->contextid = $datacontext->id;
2810                 $rm->delete_ratings($ratingdeloptions);
2811             }
2812         }
2814         if (empty($data->reset_gradebook_grades)) {
2815             // remove all grades from gradebook
2816             data_reset_gradebook($data->courseid);
2817         }
2819         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false);
2820     }
2822     // remove all comments
2823     if (!empty($data->reset_data_comments)) {
2824         $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2825         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false);
2826     }
2828     // updating dates - shift may be negative too
2829     if ($data->timeshift) {
2830         shift_course_mod_dates('data', array('timeavailablefrom', 'timeavailableto', 'timeviewfrom', 'timeviewto'), $data->timeshift, $data->courseid);
2831         $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
2832     }
2834     return $status;
2837 /**
2838  * Returns all other caps used in module
2839  *
2840  * @return array
2841  */
2842 function data_get_extra_capabilities() {
2843     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');
2846 /**
2847  * @param string $feature FEATURE_xx constant for requested feature
2848  * @return mixed True if module supports feature, null if doesn't know
2849  */
2850 function data_supports($feature) {
2851     switch($feature) {
2852         case FEATURE_GROUPS:                  return true;
2853         case FEATURE_GROUPINGS:               return true;
2854         case FEATURE_MOD_INTRO:               return true;
2855         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
2856         case FEATURE_GRADE_HAS_GRADE:         return true;
2857         case FEATURE_GRADE_OUTCOMES:          return true;
2858         case FEATURE_RATE:                    return true;
2859         case FEATURE_BACKUP_MOODLE2:          return true;
2860         case FEATURE_SHOW_DESCRIPTION:        return true;
2862         default: return null;
2863     }
2865 /**
2866  * @global object
2867  * @param array $export
2868  * @param string $delimiter_name
2869  * @param object $database
2870  * @param int $count
2871  * @param bool $return
2872  * @return string|void
2873  */
2874 function data_export_csv($export, $delimiter_name, $database, $count, $return=false) {
2875     global $CFG;
2876     require_once($CFG->libdir . '/csvlib.class.php');
2878     $filename = $database . '-' . $count . '-record';
2879     if ($count > 1) {
2880         $filename .= 's';
2881     }
2882     if ($return) {
2883         return csv_export_writer::print_array($export, $delimiter_name, '"', true);
2884     } else {
2885         csv_export_writer::download_array($filename, $export, $delimiter_name);
2886     }
2889 /**
2890  * @global object
2891  * @param array $export
2892  * @param string $dataname
2893  * @param int $count
2894  * @return string
2895  */
2896 function data_export_xls($export, $dataname, $count) {
2897     global $CFG;
2898     require_once("$CFG->libdir/excellib.class.php");
2899     $filename = clean_filename("{$dataname}-{$count}_record");
2900     if ($count > 1) {
2901         $filename .= 's';
2902     }
2903     $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
2904     $filename .= '.xls';
2906     $filearg = '-';
2907     $workbook = new MoodleExcelWorkbook($filearg);
2908     $workbook->send($filename);
2909     $worksheet = array();
2910     $worksheet[0] = $workbook->add_worksheet('');
2911     $rowno = 0;
2912     foreach ($export as $row) {
2913         $colno = 0;
2914         foreach($row as $col) {
2915             $worksheet[0]->write($rowno, $colno, $col);
2916             $colno++;
2917         }
2918         $rowno++;
2919     }
2920     $workbook->close();
2921     return $filename;
2924 /**
2925  * @global object
2926  * @param array $export
2927  * @param string $dataname
2928  * @param int $count
2929  * @param string
2930  */
2931 function data_export_ods($export, $dataname, $count) {
2932     global $CFG;
2933     require_once("$CFG->libdir/odslib.class.php");
2934     $filename = clean_filename("{$dataname}-{$count}_record");
2935     if ($count > 1) {
2936         $filename .= 's';
2937     }
2938     $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
2939     $filename .= '.ods';
2940     $filearg = '-';
2941     $workbook = new MoodleODSWorkbook($filearg);
2942     $workbook->send($filename);
2943     $worksheet = array();
2944     $worksheet[0] = $workbook->add_worksheet('');
2945     $rowno = 0;
2946     foreach ($export as $row) {
2947         $colno = 0;
2948         foreach($row as $col) {
2949             $worksheet[0]->write($rowno, $colno, $col);
2950             $colno++;
2951         }
2952         $rowno++;
2953     }
2954     $workbook->close();
2955     return $filename;
2958 /**
2959  * @global object
2960  * @param int $dataid
2961  * @param array $fields
2962  * @param array $selectedfields
2963  * @param int $currentgroup group ID of the current group. This is used for
2964  * exporting data while maintaining group divisions.
2965  * @param object $context the context in which the operation is performed (for capability checks)
2966  * @param bool $userdetails whether to include the details of the record author
2967  * @param bool $time whether to include time created/modified
2968  * @param bool $approval whether to include approval status
2969  * @return array
2970  */
2971 function data_get_exportdata($dataid, $fields, $selectedfields, $currentgroup=0, $context=null,
2972                              $userdetails=false, $time=false, $approval=false) {
2973     global $DB;
2975     if (is_null($context)) {
2976         $context = context_system::instance();
2977     }
2978     // exporting user data needs special permission
2979     $userdetails = $userdetails && has_capability('mod/data:exportuserinfo', $context);
2981     $exportdata = array();
2983     // populate the header in first row of export
2984     foreach($fields as $key => $field) {
2985         if (!in_array($field->field->id, $selectedfields)) {
2986             // ignore values we aren't exporting
2987             unset($fields[$key]);
2988         } else {
2989             $exportdata[0][] = $field->field->name;
2990         }
2991     }
2992     if ($userdetails) {
2993         $exportdata[0][] = get_string('user');
2994         $exportdata[0][] = get_string('username');
2995         $exportdata[0][] = get_string('email');
2996     }
2997     if ($time) {
2998         $exportdata[0][] = get_string('timeadded', 'data');
2999         $exportdata[0][] = get_string('timemodified', 'data');
3000     }
3001     if ($approval) {
3002         $exportdata[0][] = get_string('approved', 'data');
3003     }
3005     $datarecords = $DB->get_records('data_records', array('dataid'=>$dataid));
3006     ksort($datarecords);
3007     $line = 1;
3008     foreach($datarecords as $record) {
3009         // get content indexed by fieldid
3010         if ($currentgroup) {
3011             $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 = ?';
3012             $where = array($record->id, $currentgroup);
3013         } else {
3014             $select = 'SELECT fieldid, content, content1, content2, content3, content4 FROM {data_content} WHERE recordid = ?';
3015             $where = array($record->id);
3016         }
3018         if( $content = $DB->get_records_sql($select, $where) ) {
3019             foreach($fields as $field) {
3020                 $contents = '';
3021                 if(isset($content[$field->field->id])) {
3022                     $contents = $field->export_text_value($content[$field->field->id]);
3023                 }
3024                 $exportdata[$line][] = $contents;
3025             }
3026             if ($userdetails) { // Add user details to the export data
3027                 $userdata = get_complete_user_data('id', $record->userid);
3028                 $exportdata[$line][] = fullname($userdata);
3029                 $exportdata[$line][] = $userdata->username;
3030                 $exportdata[$line][] = $userdata->email;
3031             }
3032             if ($time) { // Add time added / modified
3033                 $exportdata[$line][] = userdate($record->timecreated);
3034                 $exportdata[$line][] = userdate($record->timemodified);
3035             }
3036             if ($approval) { // Add approval status
3037                 $exportdata[$line][] = (int) $record->approved;
3038             }
3039         }
3040         $line++;
3041     }
3042     $line--;
3043     return $exportdata;
3046 ////////////////////////////////////////////////////////////////////////////////
3047 // File API                                                                   //
3048 ////////////////////////////////////////////////////////////////////////////////
3050 /**
3051  * Lists all browsable file areas
3052  *
3053  * @package  mod_data
3054  * @category files
3055  * @param stdClass $course course object
3056  * @param stdClass $cm course module object
3057  * @param stdClass $context context object
3058  * @return array
3059  */
3060 function data_get_file_areas($course, $cm, $context) {
3061     return array('content' => get_string('areacontent', 'mod_data'));
3064 /**
3065  * File browsing support for data module.
3066  *
3067  * @param file_browser $browser
3068  * @param array $areas
3069  * @param stdClass $course
3070  * @param cm_info $cm
3071  * @param context $context
3072  * @param string $filearea
3073  * @param int $itemid
3074  * @param string $filepath
3075  * @param string $filename
3076  * @return file_info_stored file_info_stored instance or null if not found
3077  */
3078 function data_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
3079     global $CFG, $DB, $USER;
3081     if ($context->contextlevel != CONTEXT_MODULE) {
3082         return null;
3083     }
3085     if (!isset($areas[$filearea])) {
3086         return null;
3087     }
3089     if (is_null($itemid)) {
3090         require_once($CFG->dirroot.'/mod/data/locallib.php');
3091         return new data_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
3092     }
3094     if (!$content = $DB->get_record('data_content', array('id'=>$itemid))) {
3095         return null;
3096     }
3098     if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3099         return null;
3100     }
3102     if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3103         return null;
3104     }
3106     if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3107         return null;
3108     }
3110     //check if approved
3111     if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3112         return null;
3113     }
3115     // group access
3116     if ($record->groupid) {
3117         $groupmode = groups_get_activity_groupmode($cm, $course);
3118         if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3119             if (!groups_is_member($record->groupid)) {
3120                 return null;
3121             }
3122         }
3123     }
3125     $fieldobj = data_get_field($field, $data, $cm);
3127     $filepath = is_null($filepath) ? '/' : $filepath;
3128     $filename = is_null($filename) ? '.' : $filename;
3129     if (!$fieldobj->file_ok($filepath.$filename)) {
3130         return null;
3131     }
3133     $fs = get_file_storage();
3134     if (!($storedfile = $fs->get_file($context->id, 'mod_data', $filearea, $itemid, $filepath, $filename))) {
3135         return null;
3136     }
3138     // Checks to see if the user can manage files or is the owner.
3139     // TODO MDL-33805 - Do not use userid here and move the capability check above.
3140     if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
3141         return null;
3142     }
3144     $urlbase = $CFG->wwwroot.'/pluginfile.php';
3146     return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
3149 /**
3150  * Serves the data attachments. Implements needed access control ;-)
3151  *
3152  * @package  mod_data
3153  * @category files
3154  * @param stdClass $course course object
3155  * @param stdClass $cm course module object
3156  * @param stdClass $context context object
3157  * @param string $filearea file area
3158  * @param array $args extra arguments
3159  * @param bool $forcedownload whether or not force download
3160  * @param array $options additional options affecting the file serving
3161  * @return bool false if file not found, does not return if found - justsend the file
3162  */
3163 function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
3164     global $CFG, $DB;
3166     if ($context->contextlevel != CONTEXT_MODULE) {
3167         return false;
3168     }
3170     require_course_login($course, true, $cm);
3172     if ($filearea === 'content') {
3173         $contentid = (int)array_shift($args);
3175         if (!$content = $DB->get_record('data_content', array('id'=>$contentid))) {
3176             return false;
3177         }
3179         if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3180             return false;
3181         }
3183         if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3184             return false;
3185         }
3187         if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3188             return false;
3189         }
3191         if ($data->id != $cm->instance) {
3192             // hacker attempt - context does not match the contentid
3193             return false;
3194         }
3196         //check if approved
3197         if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3198             return false;
3199         }
3201         // group access
3202         if ($record->groupid) {
3203             $groupmode = groups_get_activity_groupmode($cm, $course);
3204             if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3205                 if (!groups_is_member($record->groupid)) {
3206                     return false;
3207                 }
3208             }
3209         }
3211         $fieldobj = data_get_field($field, $data, $cm);
3213         $relativepath = implode('/', $args);
3214         $fullpath = "/$context->id/mod_data/content/$content->id/$relativepath";
3216         if (!$fieldobj->file_ok($relativepath)) {
3217             return false;
3218         }
3220         $fs = get_file_storage();
3221         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
3222             return false;
3223         }
3225         // finally send the file
3226         send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
3227     }
3229     return false;
3233 function data_extend_navigation($navigation, $course, $module, $cm) {
3234     global $CFG, $OUTPUT, $USER, $DB;
3236     $rid = optional_param('rid', 0, PARAM_INT);
3238     $data = $DB->get_record('data', array('id'=>$cm->instance));
3239     $currentgroup = groups_get_activity_group($cm);
3240     $groupmode = groups_get_activity_groupmode($cm);
3242      $numentries = data_numentries($data);
3243     /// Check the number of entries required against the number of entries already made (doesn't apply to teachers)
3244     if ($data->requiredentries > 0 && $numentries < $data->requiredentries && !has_capability('mod/data:manageentries', context_module::instance($cm->id))) {
3245         $data->entriesleft = $data->requiredentries - $numentries;
3246         $entriesnode = $navigation->add(get_string('entrieslefttoadd', 'data', $data));
3247         $entriesnode->add_class('note');
3248     }
3250     $navigation->add(get_string('list', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance)));
3251     if (!empty($rid)) {
3252         $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'rid'=>$rid)));
3253     } else {
3254         $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'single')));
3255     }
3256     $navigation->add(get_string('search', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'asearch')));
3259 /**
3260  * Adds module specific settings to the settings block
3261  *
3262  * @param settings_navigation $settings The settings navigation object
3263  * @param navigation_node $datanode The node to add module settings to
3264  */
3265 function data_extend_settings_navigation(settings_navigation $settings, navigation_node $datanode) {
3266     global $PAGE, $DB, $CFG, $USER;
3268     $data = $DB->get_record('data', array("id" => $PAGE->cm->instance));
3270     $currentgroup = groups_get_activity_group($PAGE->cm);
3271     $groupmode = groups_get_activity_groupmode($PAGE->cm);
3273     if (data_user_can_add_entry($data, $currentgroup, $groupmode, $PAGE->cm->context)) { // took out participation list here!
3274         if (empty($editentry)) { //TODO: undefined
3275             $addstring = get_string('add', 'data');
3276         } else {
3277             $addstring = get_string('editentry', 'data');
3278         }
3279         $datanode->add($addstring, new moodle_url('/mod/data/edit.php', array('d'=>$PAGE->cm->instance)));
3280     }
3282     if (has_capability(DATA_CAP_EXPORT, $PAGE->cm->context)) {
3283         // The capability required to Export database records is centrally defined in 'lib.php'
3284         // and should be weaker than those required to edit Templates, Fields and Presets.
3285         $datanode->add(get_string('exportentries', 'data'), new moodle_url('/mod/data/export.php', array('d'=>$data->id)));
3286     }
3287     if (has_capability('mod/data:manageentries', $PAGE->cm->context)) {
3288         $datanode->add(get_string('importentries', 'data'), new moodle_url('/mod/data/import.php', array('d'=>$data->id)));
3289     }
3291     if (has_capability('mod/data:managetemplates', $PAGE->cm->context)) {
3292         $currenttab = '';
3293         if ($currenttab == 'list') {
3294             $defaultemplate = 'listtemplate';
3295         } else if ($currenttab == 'add') {
3296             $defaultemplate = 'addtemplate';
3297         } else if ($currenttab == 'asearch') {
3298             $defaultemplate = 'asearchtemplate';
3299         } else {
3300             $defaultemplate = 'singletemplate';
3301         }
3303         $templates = $datanode->add(get_string('templates', 'data'));
3305         $templatelist = array ('listtemplate', 'singletemplate', 'asearchtemplate', 'addtemplate', 'rsstemplate', 'csstemplate', 'jstemplate');
3306         foreach ($templatelist as $template) {
3307             $templates->add(get_string($template, 'data'), new moodle_url('/mod/data/templates.php', array('d'=>$data->id,'mode'=>$template)));
3308         }
3310         $datanode->add(get_string('fields', 'data'), new moodle_url('/mod/data/field.php', array('d'=>$data->id)));
3311         $datanode->add(get_string('presets', 'data'), new moodle_url('/mod/data/preset.php', array('d'=>$data->id)));
3312     }
3314     if (!empty($CFG->enablerssfeeds) && !empty($CFG->data_enablerssfeeds) && $data->rssarticles > 0) {
3315         require_once("$CFG->libdir/rsslib.php");
3317         $string = get_string('rsstype','forum');
3319         $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $USER->id, 'mod_data', $data->id));
3320         $datanode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
3321     }
3324 /**
3325  * Save the database configuration as a preset.
3326  *
3327  * @param stdClass $course The course the database module belongs to.
3328  * @param stdClass $cm The course module record
3329  * @param stdClass $data The database record
3330  * @param string $path
3331  * @return bool
3332  */
3333 function data_presets_save($course, $cm, $data, $path) {
3334     global $USER;
3335     $fs = get_file_storage();
3336     $filerecord = new stdClass;
3337     $filerecord->contextid = DATA_PRESET_CONTEXT;
3338     $filerecord->component = DATA_PRESET_COMPONENT;
3339     $filerecord->filearea = DATA_PRESET_FILEAREA;
3340     $filerecord->itemid = 0;
3341     $filerecord->filepath = '/'.$path.'/';
3342     $filerecord->userid = $USER->id;
3344     $filerecord->filename = 'preset.xml';
3345     $fs->create_file_from_string($filerecord, data_presets_generate_xml($course, $cm, $data));
3347     $filerecord->filename = 'singletemplate.html';
3348     $fs->create_file_from_string($filerecord, $data->singletemplate);
3350     $filerecord->filename = 'listtemplateheader.html';
3351     $fs->create_file_from_string($filerecord, $data->listtemplateheader);
3353     $filerecord->filename = 'listtemplate.html';
3354     $fs->create_file_from_string($filerecord, $data->listtemplate);
3356     $filerecord->filename = 'listtemplatefooter.html';
3357     $fs->create_file_from_string($filerecord, $data->listtemplatefooter);
3359     $filerecord->filename = 'addtemplate.html';
3360     $fs->create_file_from_string($filerecord, $data->addtemplate);
3362     $filerecord->filename = 'rsstemplate.html';
3363     $fs->create_file_from_string($filerecord, $data->rsstemplate);
3365     $filerecord->filename = 'rsstitletemplate.html';
3366     $fs->create_file_from_string($filerecord, $data->rsstitletemplate);
3368     $filerecord->filename = 'csstemplate.css';
3369     $fs->create_file_from_string($filerecord, $data->csstemplate);
3371     $filerecord->filename = 'jstemplate.js';
3372     $fs->create_file_from_string($filerecord, $data->jstemplate);
3374     $filerecord->filename = 'asearchtemplate.html';
3375     $fs->create_file_from_string($filerecord, $data->asearchtemplate);
3377     return true;
3380 /**
3381  * Generates the XML for the database module provided
3382  *
3383  * @global moodle_database $DB
3384  * @param stdClass $course The course the database module belongs to.
3385  * @param stdClass $cm The course module record
3386  * @param stdClass $data The database record
3387  * @return string The XML for the preset
3388  */
3389 function data_presets_generate_xml($course, $cm, $data) {
3390     global $DB;
3392     // Assemble "preset.xml":
3393     $presetxmldata = "<preset>\n\n";
3395     // Raw settings are not preprocessed during saving of presets
3396     $raw_settings = array(
3397         'intro',
3398         'comments',
3399         'requiredentries',
3400         'requiredentriestoview',
3401         'maxentries',
3402         'rssarticles',
3403         'approval',
3404         'manageapproved',
3405         'defaultsortdir'
3406     );
3408     $presetxmldata .= "<settings>\n";
3409     // First, settings that do not require any conversion
3410     foreach ($raw_settings as $setting) {
3411         $presetxmldata .= "<$setting>" . htmlspecialchars($data->$setting) . "</$setting>\n";
3412     }
3414     // Now specific settings
3415     if ($data->defaultsort > 0 && $sortfield = data_get_field_from_id($data->defaultsort, $data)) {
3416         $presetxmldata .= '<defaultsort>' . htmlspecialchars($sortfield->field->name) . "</defaultsort>\n";
3417     } else {
3418         $presetxmldata .= "<defaultsort>0</defaultsort>\n";
3419     }
3420     $presetxmldata .= "</settings>\n\n";
3421     // Now for the fields. Grab all that are non-empty
3422     $fields = $DB->get_records('data_fields', array('dataid'=>$data->id));
3423     ksort($fields);
3424     if (!empty($fields)) {
3425         foreach ($fields as $field) {
3426             $presetxmldata .= "<field>\n";
3427             foreach ($field as $key => $value) {
3428                 if ($value != '' && $key != 'id' && $key != 'dataid') {
3429                     $presetxmldata .= "<$key>" . htmlspecialchars($value) . "</$key>\n";
3430                 }
3431             }
3432             $presetxmldata .= "</field>\n\n";
3433         }
3434     }
3435     $presetxmldata .= '</preset>';
3436     return $presetxmldata;
3439 function data_presets_export($course, $cm, $data, $tostorage=false) {
3440     global $CFG, $DB;
3442     $presetname = clean_filename($data->name) . '-preset-' . gmdate("Ymd_Hi");
3443     $exportsubdir = "mod_data/presetexport/$presetname";
3444     make_temp_directory($exportsubdir);
3445     $exportdir = "$CFG->tempdir/$exportsubdir";
3447     // Assemble "preset.xml":
3448     $presetxmldata = data_presets_generate_xml($course, $cm, $data);
3450     // After opening a file in write mode, close it asap
3451     $presetxmlfile = fopen($exportdir . '/preset.xml', 'w');
3452     fwrite($presetxmlfile, $presetxmldata);
3453     fclose($presetxmlfile);
3455     // Now write the template files
3456     $singletemplate = fopen($exportdir . '/singletemplate.html', 'w');
3457     fwrite($singletemplate, $data->singletemplate);
3458     fclose($singletemplate);
3460     $listtemplateheader = fopen($exportdir . '/listtemplateheader.html', 'w');
3461     fwrite($listtemplateheader, $data->listtemplateheader);
3462     fclose($listtemplateheader);
3464     $listtemplate = fopen($exportdir . '/listtemplate.html', 'w');
3465     fwrite($listtemplate, $data->listtemplate);
3466     fclose($listtemplate);
3468     $listtemplatefooter = fopen($exportdir . '/listtemplatefooter.html', 'w');
3469     fwrite($listtemplatefooter, $data->listtemplatefooter);
3470     fclose($listtemplatefooter);
3472     $addtemplate = fopen($exportdir . '/addtemplate.html', 'w');
3473     fwrite($addtemplate, $data->addtemplate);
3474     fclose($addtemplate);
3476     $rsstemplate = fopen($exportdir . '/rsstemplate.html', 'w');
3477     fwrite($rsstemplate, $data->rsstemplate);
3478     fclose($rsstemplate);
3480     $rsstitletemplate = fopen($exportdir . '/rsstitletemplate.html', 'w');
3481     fwrite($rsstitletemplate, $data->rsstitletemplate);
3482     fclose($rsstitletemplate);
3484     $csstemplate = fopen($exportdir . '/csstemplate.css', 'w');
3485     fwrite($csstemplate, $data->csstemplate);
3486     fclose($csstemplate);
3488     $jstemplate = fopen($exportdir . '/jstemplate.js', 'w');
3489     fwrite($jstemplate, $data->jstemplate);
3490     fclose($jstemplate);
3492     $asearchtemplate = fopen($exportdir . '/asearchtemplate.html', 'w');
3493     fwrite($asearchtemplate, $data->asearchtemplate);
3494     fclose($asearchtemplate);
3496     // Check if all files have been generated
3497     if (! is_directory_a_preset($exportdir)) {
3498         print_error('generateerror', 'data');
3499     }
3501     $filenames = array(
3502         'preset.xml',
3503         'singletemplate.html',
3504         'listtemplateheader.html',
3505         'listtemplate.html',
3506         'listtemplatefooter.html',
3507         'addtemplate.html',
3508         'rsstemplate.html',
3509         'rsstitletemplate.html',
3510         'csstemplate.css',
3511         'jstemplate.js',
3512         'asearchtemplate.html'
3513     );
3515     $filelist = array();
3516     foreach ($filenames as $filename) {
3517         $filelist[$filename] = $exportdir . '/' . $filename;
3518     }
3520     $exportfile = $exportdir.'.zip';
3521     file_exists($exportfile) && unlink($exportfile);
3523     $fp = get_file_packer('application/zip');
3524     $fp->archive_to_pathname($filelist, $exportfile);
3526     foreach ($filelist as $file) {
3527         unlink($file);
3528     }
3529     rmdir($exportdir);
3531     // Return the full path to the exported preset file:
3532     return $exportfile;
3535 /**
3536  * Running addtional permission check on plugin, for example, plugins
3537  * may have switch to turn on/off comments option, this callback will
3538  * affect UI display, not like pluginname_comment_validate only throw
3539  * exceptions.
3540  * Capability check has been done in comment->check_permissions(), we
3541  * don't need to do it again here.
3542  *
3543  * @package  mod_data
3544  * @category comment
3545  *
3546  * @param stdClass $comment_param {
3547  *              context  => context the context object
3548  *              courseid => int course id
3549  *              cm       => stdClass course module object
3550  *              commentarea => string comment area
3551  *              itemid      => int itemid
3552  * }
3553  * @return array
3554  */
3555 function data_comment_permissions($comment_param) {
3556     global $CFG, $DB;
3557     if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3558         throw new comment_exception('invalidcommentitemid');
3559     }
3560     if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3561         throw new comment_exception('invalidid', 'data');
3562     }
3563     if ($data->comments) {
3564         return array('post'=>true, 'view'=>true);
3565     } else {
3566         return array('post'=>false, 'view'=>false);
3567     }
3570 /**
3571  * Validate comment parameter before perform other comments actions
3572  *
3573  * @package  mod_data
3574  * @category comment
3575  *
3576  * @param stdClass $comment_param {
3577  *              context  => context the context object
3578  *              courseid => int course id
3579  *              cm       => stdClass course module object
3580  *              commentarea => string comment area
3581  *              itemid      => int itemid
3582  * }
3583  * @return boolean
3584  */
3585 function data_comment_validate($comment_param) {
3586     global $DB;
3587     // validate comment area
3588     if ($comment_param->commentarea != 'database_entry') {
3589         throw new comment_exception('invalidcommentarea');
3590     }
3591     // validate itemid
3592     if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3593         throw new comment_exception('invalidcommentitemid');
3594     }
3595     if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3596         throw new comment_exception('invalidid', 'data');
3597     }
3598     if (!$course = $DB->get_record('course', array('id'=>$data->course))) {
3599         throw new comment_exception('coursemisconf');
3600     }
3601     if (!$cm = get_coursemodule_from_instance('data', $data->id, $course->id)) {
3602         throw new comment_exception('invalidcoursemodule');
3603     }
3604     if (!$data->comments) {
3605         throw new comment_exception('commentsoff', 'data');
3606     }
3607     $context = context_module::instance($cm->id);
3609     //check if approved
3610     if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3611         throw new comment_exception('notapproved', 'data');
3612     }
3614     // group access
3615     if ($record->groupid) {
3616         $groupmode = groups_get_activity_groupmode($cm, $course);
3617         if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3618             if (!groups_is_member($record->groupid)) {
3619                 throw new comment_exception('notmemberofgroup');
3620             }
3621         }
3622     }
3623     // validate context id
3624     if ($context->id != $comment_param->context->id) {
3625         throw new comment_exception('invalidcontext');
3626     }
3627     // validation for comment deletion
3628     if (!empty($comment_param->commentid)) {
3629         if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
3630             if ($comment->commentarea != 'database_entry') {
3631                 throw new comment_exception('invalidcommentarea');
3632             }
3633             if ($comment->contextid != $comment_param->context->id) {
3634                 throw new comment_exception('invalidcontext');
3635             }
3636             if ($comment->itemid != $comment_param->itemid) {
3637                 throw new comment_exception('invalidcommentitemid');
3638             }
3639         } else {
3640             throw new comment_exception('invalidcommentid');
3641         }
3642     }
3643     return true;
3646 /**
3647  * Return a list of page types
3648  * @param string $pagetype current page type
3649  * @param stdClass $parentcontext Block's parent context
3650  * @param stdClass $currentcontext Current context of block
3651  */
3652 function data_page_type_list($pagetype, $parentcontext, $currentcontext) {
3653     $module_pagetype = array('mod-data-*'=>get_string('page-mod-data-x', 'data'));
3654     return $module_pagetype;
3657 /**
3658  * Get all of the record ids from a database activity.
3659  *
3660  * @param int    $dataid      The dataid of the database module.
3661  * @param object $selectdata  Contains an additional sql statement for the
3662  *                            where clause for group and approval fields.
3663  * @param array  $params      Parameters that coincide with the sql statement.
3664  * @return array $idarray     An array of record ids
3665  */
3666 function data_get_all_recordids($dataid, $selectdata = '', $params = null) {
3667     global $DB;
3668     $initsql = 'SELECT r.id
3669                   FROM {data_records} r
3670                  WHERE r.dataid = :dataid';
3671     if ($selectdata != '') {
3672         $initsql .= $selectdata;
3673         $params = array_merge(array('dataid' => $dataid), $params);
3674     } else {
3675         $params = array('dataid' => $dataid);
3676     }
3677     $initsql .= ' GROUP BY r.id';
3678     $initrecord = $DB->get_recordset_sql($initsql, $params);
3679     $idarray = array();
3680     foreach ($initrecord as $data) {