MDL-60433 mod_data: Fix groups in Web Services
[moodle.git] / mod / data / classes / external.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Database module external API
19  *
20  * @package    mod_data
21  * @category   external
22  * @copyright  2015 Juan Leyva <juan@moodle.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  * @since      Moodle 2.9
25  */
27 defined('MOODLE_INTERNAL') || die;
29 require_once("$CFG->libdir/externallib.php");
30 require_once($CFG->dirroot . "/mod/data/locallib.php");
32 use mod_data\external\database_summary_exporter;
33 use mod_data\external\record_exporter;
34 use mod_data\external\content_exporter;
35 use mod_data\external\field_exporter;
37 /**
38  * Database module external functions
39  *
40  * @package    mod_data
41  * @category   external
42  * @copyright  2015 Juan Leyva <juan@moodle.com>
43  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44  * @since      Moodle 2.9
45  */
46 class mod_data_external extends external_api {
48     /**
49      * Describes the parameters for get_databases_by_courses.
50      *
51      * @return external_function_parameters
52      * @since Moodle 2.9
53      */
54     public static function get_databases_by_courses_parameters() {
55         return new external_function_parameters (
56             array(
57                 'courseids' => new external_multiple_structure(
58                     new external_value(PARAM_INT, 'course id', VALUE_REQUIRED),
59                     'Array of course ids', VALUE_DEFAULT, array()
60                 ),
61             )
62         );
63     }
65     /**
66      * Returns a list of databases in a provided list of courses,
67      * if no list is provided all databases that the user can view will be returned.
68      *
69      * @param array $courseids the course ids
70      * @return array the database details
71      * @since Moodle 2.9
72      */
73     public static function get_databases_by_courses($courseids = array()) {
74         global $PAGE;
76         $params = self::validate_parameters(self::get_databases_by_courses_parameters(), array('courseids' => $courseids));
77         $warnings = array();
79         $mycourses = array();
80         if (empty($params['courseids'])) {
81             $mycourses = enrol_get_my_courses();
82             $params['courseids'] = array_keys($mycourses);
83         }
85         // Array to store the databases to return.
86         $arrdatabases = array();
88         // Ensure there are courseids to loop through.
89         if (!empty($params['courseids'])) {
91             list($dbcourses, $warnings) = external_util::validate_courses($params['courseids'], $mycourses);
93             // Get the databases in this course, this function checks users visibility permissions.
94             // We can avoid then additional validate_context calls.
95             $databases = get_all_instances_in_courses("data", $dbcourses);
97             foreach ($databases as $database) {
99                 $context = context_module::instance($database->coursemodule);
100                 // Remove fields added by get_all_instances_in_courses.
101                 unset($database->coursemodule, $database->section, $database->visible, $database->groupmode, $database->groupingid);
103                 // This information should be only available if the user can see the database entries.
104                 if (!has_capability('mod/data:viewentry', $context)) {
105                     $fields = array('comments', 'timeavailablefrom', 'timeavailableto', 'timeviewfrom',
106                                     'timeviewto', 'requiredentries', 'requiredentriestoview', 'maxentries', 'rssarticles',
107                                     'singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter', 'addtemplate',
108                                     'rsstemplate', 'rsstitletemplate', 'csstemplate', 'jstemplate', 'asearchtemplate', 'approval',
109                                     'manageapproved', 'defaultsort', 'defaultsortdir');
111                     foreach ($fields as $field) {
112                         unset($database->{$field});
113                     }
114                 }
116                 // Check additional permissions for returning optional private settings.
117                 // I avoid intentionally to use can_[add|update]_moduleinfo.
118                 if (!has_capability('moodle/course:manageactivities', $context)) {
120                     $fields = array('scale', 'assessed', 'assesstimestart', 'assesstimefinish', 'editany', 'notification',
121                                     'timemodified');
123                     foreach ($fields as $field) {
124                         unset($database->{$field});
125                     }
126                 }
127                 $exporter = new database_summary_exporter($database, array('context' => $context));
128                 $arrdatabases[] = $exporter->export($PAGE->get_renderer('core'));
129             }
130         }
132         $result = array();
133         $result['databases'] = $arrdatabases;
134         $result['warnings'] = $warnings;
135         return $result;
136     }
138     /**
139      * Describes the get_databases_by_courses return value.
140      *
141      * @return external_single_structure
142      * @since Moodle 2.9
143      */
144     public static function get_databases_by_courses_returns() {
146         return new external_single_structure(
147             array(
148                 'databases' => new external_multiple_structure(
149                     database_summary_exporter::get_read_structure()
150                 ),
151                 'warnings' => new external_warnings(),
152             )
153         );
154     }
156     /**
157      * Utility function for validating a database.
158      *
159      * @param int $databaseid database instance id
160      * @return array array containing the database object, course, context and course module objects
161      * @since  Moodle 3.3
162      */
163     protected static function validate_database($databaseid) {
164         global $DB;
166         // Request and permission validation.
167         $database = $DB->get_record('data', array('id' => $databaseid), '*', MUST_EXIST);
168         list($course, $cm) = get_course_and_cm_from_instance($database, 'data');
170         $context = context_module::instance($cm->id);
171         self::validate_context($context);
172         require_capability('mod/data:viewentry', $context);
174         return array($database, $course, $cm, $context);
175     }
177     /**
178      * Returns description of method parameters
179      *
180      * @return external_function_parameters
181      * @since Moodle 3.3
182      */
183     public static function view_database_parameters() {
184         return new external_function_parameters(
185             array(
186                 'databaseid' => new external_value(PARAM_INT, 'data instance id')
187             )
188         );
189     }
191     /**
192      * Simulate the data/view.php web interface page: trigger events, completion, etc...
193      *
194      * @param int $databaseid the data instance id
195      * @return array of warnings and status result
196      * @since Moodle 3.3
197      * @throws moodle_exception
198      */
199     public static function view_database($databaseid) {
201         $params = self::validate_parameters(self::view_database_parameters(), array('databaseid' => $databaseid));
202         $warnings = array();
204         list($database, $course, $cm, $context) = self::validate_database($params['databaseid']);
206         // Call the data/lib API.
207         data_view($database, $course, $cm, $context);
209         $result = array();
210         $result['status'] = true;
211         $result['warnings'] = $warnings;
212         return $result;
213     }
215     /**
216      * Returns description of method result value
217      *
218      * @return external_description
219      * @since Moodle 3.3
220      */
221     public static function view_database_returns() {
222         return new external_single_structure(
223             array(
224                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
225                 'warnings' => new external_warnings()
226             )
227         );
228     }
230     /**
231      * Returns description of method parameters.
232      *
233      * @return external_function_parameters
234      * @since Moodle 3.3
235      */
236     public static function get_data_access_information_parameters() {
237         return new external_function_parameters(
238             array(
239                 'databaseid' => new external_value(PARAM_INT, 'Database instance id.'),
240                 'groupid' => new external_value(PARAM_INT, 'Group id, 0 means that the function will determine the user group.',
241                                                    VALUE_DEFAULT, 0),
242             )
243         );
244     }
246     /**
247      * Return access information for a given database.
248      *
249      * @param int $databaseid the database instance id
250      * @param int $groupid (optional) group id, 0 means that the function will determine the user group
251      * @return array of warnings and access information
252      * @since Moodle 3.3
253      * @throws moodle_exception
254      */
255     public static function get_data_access_information($databaseid, $groupid = 0) {
257         $params = array('databaseid' => $databaseid, 'groupid' => $groupid);
258         $params = self::validate_parameters(self::get_data_access_information_parameters(), $params);
259         $warnings = array();
261         list($database, $course, $cm, $context) = self::validate_database($params['databaseid']);
263         $result = array(
264             'warnings' => $warnings
265         );
267         if (!empty($params['groupid'])) {
268             $groupid = $params['groupid'];
269             // Determine is the group is visible to user.
270             if (!groups_group_visible($groupid, $course, $cm)) {
271                 throw new moodle_exception('notingroup');
272             }
273         } else {
274             // Check to see if groups are being used here.
275             $groupmode = groups_get_activity_groupmode($cm);
276             if ($groupmode) {
277                 $groupid = groups_get_activity_group($cm);
278             } else {
279                 $groupid = 0;
280             }
281         }
282         // Group related information.
283         $result['groupid'] = $groupid;
284         $result['canaddentry'] = data_user_can_add_entry($database, $groupid, $groupmode, $context);
286         // Now capabilities.
287         $result['canmanageentries'] = has_capability('mod/data:manageentries', $context);
288         $result['canapprove'] = has_capability('mod/data:approve', $context);
290         // Now time access restrictions.
291         list($result['timeavailable'], $warnings) = data_get_time_availability_status($database, $result['canmanageentries']);
293         // Other information.
294         $result['numentries'] = data_numentries($database);
295         $result['entrieslefttoadd'] = data_get_entries_left_to_add($database, $result['numentries'], $result['canmanageentries']);
296         $result['entrieslefttoview'] = data_get_entries_left_to_view($database, $result['numentries'], $result['canmanageentries']);
297         $result['inreadonlyperiod'] = data_in_readonly_period($database);
299         return $result;
300     }
302     /**
303      * Returns description of method result value.
304      *
305      * @return external_description
306      * @since Moodle 3.3
307      */
308     public static function get_data_access_information_returns() {
309         return new external_single_structure(
310             array(
311                 'groupid' => new external_value(PARAM_INT, 'User current group id (calculated)'),
312                 'canaddentry' => new external_value(PARAM_BOOL, 'Whether the user can add entries or not.'),
313                 'canmanageentries' => new external_value(PARAM_BOOL, 'Whether the user can manage entries or not.'),
314                 'canapprove' => new external_value(PARAM_BOOL, 'Whether the user can approve entries or not.'),
315                 'timeavailable' => new external_value(PARAM_BOOL, 'Whether the database is available or not by time restrictions.'),
316                 'inreadonlyperiod' => new external_value(PARAM_BOOL, 'Whether the database is in read mode only.'),
317                 'numentries' => new external_value(PARAM_INT, 'The number of entries the current user added.'),
318                 'entrieslefttoadd' => new external_value(PARAM_INT, 'The number of entries left to complete the activity.'),
319                 'entrieslefttoview' => new external_value(PARAM_INT, 'The number of entries left to view other users entries.'),
320                 'warnings' => new external_warnings()
321             )
322         );
323     }
325     /**
326      * Returns description of method parameters
327      *
328      * @return external_function_parameters
329      * @since Moodle 3.3
330      */
331     public static function get_entries_parameters() {
332         return new external_function_parameters(
333             array(
334                 'databaseid' => new external_value(PARAM_INT, 'data instance id'),
335                 'groupid' => new external_value(PARAM_INT, 'Group id, 0 means that the function will determine the user group',
336                                                    VALUE_DEFAULT, 0),
337                 'returncontents' => new external_value(PARAM_BOOL, 'Whether to return contents or not. This will return each entry
338                                                         raw contents and the complete list view (using the template).',
339                                                         VALUE_DEFAULT, false),
340                 'sort' => new external_value(PARAM_INT, 'Sort the records by this field id, reserved ids are:
341                                                 0: timeadded
342                                                 -1: firstname
343                                                 -2: lastname
344                                                 -3: approved
345                                                 -4: timemodified.
346                                                 Empty for using the default database setting.', VALUE_DEFAULT, null),
347                 'order' => new external_value(PARAM_ALPHA, 'The direction of the sorting: \'ASC\' or \'DESC\'.
348                                                 Empty for using the default database setting.', VALUE_DEFAULT, null),
349                 'page' => new external_value(PARAM_INT, 'The page of records to return.', VALUE_DEFAULT, 0),
350                 'perpage' => new external_value(PARAM_INT, 'The number of records to return per page', VALUE_DEFAULT, 0),
351             )
352         );
353     }
355     /**
356      * Return access information for a given feedback
357      *
358      * @param int $databaseid       the data instance id
359      * @param int $groupid          (optional) group id, 0 means that the function will determine the user group
360      * @param bool $returncontents  Whether to return the entries contents or not
361      * @param str $sort             sort by this field
362      * @param int $order            the direction of the sorting
363      * @param int $page             page of records to return
364      * @param int $perpage          number of records to return per page
365      * @return array of warnings and the entries
366      * @since Moodle 3.3
367      * @throws moodle_exception
368      */
369     public static function get_entries($databaseid, $groupid = 0, $returncontents = false, $sort = null, $order = null,
370             $page = 0, $perpage = 0) {
371         global $PAGE, $DB;
373         $params = array('databaseid' => $databaseid, 'groupid' => $groupid, 'returncontents' => $returncontents ,
374                         'sort' => $sort, 'order' => $order, 'page' => $page, 'perpage' => $perpage);
375         $params = self::validate_parameters(self::get_entries_parameters(), $params);
376         $warnings = array();
378         if (!empty($params['order'])) {
379             $params['order'] = strtoupper($params['order']);
380             if ($params['order'] != 'ASC' && $params['order'] != 'DESC') {
381                 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $params['order'] . ')');
382             }
383         }
385         list($database, $course, $cm, $context) = self::validate_database($params['databaseid']);
386         // Check database is open in time.
387         data_require_time_available($database, null, $context);
389         if (!empty($params['groupid'])) {
390             $groupid = $params['groupid'];
391             // Determine is the group is visible to user.
392             if (!groups_group_visible($groupid, $course, $cm)) {
393                 throw new moodle_exception('notingroup');
394             }
395         } else {
396             // Check to see if groups are being used here.
397             if ($groupmode = groups_get_activity_groupmode($cm)) {
398                 // We don't need to validate a possible groupid = 0 since it would be handled by data_search_entries.
399                 $groupid = groups_get_activity_group($cm);
400             } else {
401                 $groupid = 0;
402             }
403         }
405         list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
406             data_search_entries($database, $cm, $context, 'list', $groupid, '', $params['sort'], $params['order'],
407                 $params['page'], $params['perpage']);
409         $entries = [];
410         $contentsids = [];  // Store here the content ids of the records returned.
411         foreach ($records as $record) {
412             $user = user_picture::unalias($record, null, 'userid');
413             $related = array('context' => $context, 'database' => $database, 'user' => $user);
415             $contents = $DB->get_records('data_content', array('recordid' => $record->id));
416             $contentsids = array_merge($contentsids, array_keys($contents));
417             if ($params['returncontents']) {
418                 $related['contents'] = $contents;
419             } else {
420                 $related['contents'] = null;
421             }
423             $exporter = new record_exporter($record, $related);
424             $entries[] = $exporter->export($PAGE->get_renderer('core'));
425         }
427         // Retrieve total files size for the records retrieved.
428         $totalfilesize = 0;
429         $fs = get_file_storage();
430         $files = $fs->get_area_files($context->id, 'mod_data', 'content');
431         foreach ($files as $file) {
432             if ($file->is_directory() || !in_array($file->get_itemid(), $contentsids)) {
433                 continue;
434             }
435             $totalfilesize += $file->get_filesize();
436         }
438         $result = array(
439             'entries' => $entries,
440             'totalcount' => $totalcount,
441             'totalfilesize' => $totalfilesize,
442             'warnings' => $warnings
443         );
445         // Check if we should return the list rendered.
446         if ($params['returncontents']) {
447             ob_start();
448             // The return parameter stops the execution after the first record.
449             data_print_template('listtemplate', $records, $database, '', $page, false);
450             $result['listviewcontents'] = ob_get_contents();
451             ob_end_clean();
452         }
454         return $result;
455     }
457     /**
458      * Returns description of method result value
459      *
460      * @return external_description
461      * @since Moodle 3.3
462      */
463     public static function get_entries_returns() {
464         return new external_single_structure(
465             array(
466                 'entries' => new external_multiple_structure(
467                     record_exporter::get_read_structure()
468                 ),
469                 'totalcount' => new external_value(PARAM_INT, 'Total count of records.'),
470                 'totalfilesize' => new external_value(PARAM_INT, 'Total size (bytes) of the files included in the records.'),
471                 'listviewcontents' => new external_value(PARAM_RAW, 'The list view contents as is rendered in the site.',
472                                                             VALUE_OPTIONAL),
473                 'warnings' => new external_warnings()
474             )
475         );
476     }
478     /**
479      * Returns description of method parameters
480      *
481      * @return external_function_parameters
482      * @since Moodle 3.3
483      */
484     public static function get_entry_parameters() {
485         return new external_function_parameters(
486             array(
487                 'entryid' => new external_value(PARAM_INT, 'record entry id'),
488                 'returncontents' => new external_value(PARAM_BOOL, 'Whether to return contents or not.', VALUE_DEFAULT, false),
489             )
490         );
491     }
493     /**
494      * Return one entry record from the database, including contents optionally.
495      *
496      * @param int $entryid          the record entry id id
497      * @param bool $returncontents  whether to return the entries contents or not
498      * @return array of warnings and the entries
499      * @since Moodle 3.3
500      * @throws moodle_exception
501      */
502     public static function get_entry($entryid, $returncontents = false) {
503         global $PAGE, $DB;
505         $params = array('entryid' => $entryid, 'returncontents' => $returncontents);
506         $params = self::validate_parameters(self::get_entry_parameters(), $params);
507         $warnings = array();
509         $record = $DB->get_record('data_records', array('id' => $params['entryid']), '*', MUST_EXIST);
510         list($database, $course, $cm, $context) = self::validate_database($record->dataid);
512         // Check database is open in time.
513         $canmanageentries = has_capability('mod/data:manageentries', $context);
514         data_require_time_available($database, $canmanageentries);
516         if ($record->groupid !== 0) {
517             if (!groups_group_visible($record->groupid, $course, $cm)) {
518                 throw new moodle_exception('notingroup');
519             }
520         }
522         // Check correct record entry. Group check was done before.
523         if (!data_can_view_record($database, $record, $record->groupid, $canmanageentries)) {
524             throw new moodle_exception('notapproved', 'data');
525         }
527         $related = array('context' => $context, 'database' => $database, 'user' => null);
528         if ($params['returncontents']) {
529             $related['contents'] = $DB->get_records('data_content', array('recordid' => $record->id));
530         } else {
531             $related['contents'] = null;
532         }
533         $exporter = new record_exporter($record, $related);
534         $entry = $exporter->export($PAGE->get_renderer('core'));
536         $result = array(
537             'entry' => $entry,
538             'ratinginfo' => \core_rating\external\util::get_rating_info($database, $context, 'mod_data', 'entry', array($record)),
539             'warnings' => $warnings
540         );
541         // Check if we should return the entry rendered.
542         if ($params['returncontents']) {
543             $records = [$record];
544             $result['entryviewcontents'] = data_print_template('singletemplate', $records, $database, '', 0, true);
545         }
547         return $result;
548     }
550     /**
551      * Returns description of method result value
552      *
553      * @return external_description
554      * @since Moodle 3.3
555      */
556     public static function get_entry_returns() {
557         return new external_single_structure(
558             array(
559                 'entry' => record_exporter::get_read_structure(),
560                 'entryviewcontents' => new external_value(PARAM_RAW, 'The entry as is rendered in the site.', VALUE_OPTIONAL),
561                 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
562                 'warnings' => new external_warnings()
563             )
564         );
565     }
567     /**
568      * Returns description of method parameters
569      *
570      * @return external_function_parameters
571      * @since Moodle 3.3
572      */
573     public static function get_fields_parameters() {
574         return new external_function_parameters(
575             array(
576                 'databaseid' => new external_value(PARAM_INT, 'Database instance id.'),
577             )
578         );
579     }
581     /**
582      * Return the list of configured fields for the given database.
583      *
584      * @param int $databaseid the database id
585      * @return array of warnings and the fields
586      * @since Moodle 3.3
587      * @throws moodle_exception
588      */
589     public static function get_fields($databaseid) {
590         global $PAGE;
592         $params = array('databaseid' => $databaseid);
593         $params = self::validate_parameters(self::get_fields_parameters(), $params);
594         $fields = $warnings = array();
596         list($database, $course, $cm, $context) = self::validate_database($params['databaseid']);
598         // Check database is open in time.
599         $canmanageentries = has_capability('mod/data:manageentries', $context);
600         data_require_time_available($database, $canmanageentries);
602         $fieldinstances = data_get_field_instances($database);
604         foreach ($fieldinstances as $fieldinstance) {
605             $record = $fieldinstance->field;
606             // Now get the configs the user can see with his current permissions.
607             $configs = $fieldinstance->get_config_for_external();
608             foreach ($configs as $name => $value) {
609                 // Overwrite.
610                 $record->{$name} = $value;
611             }
613             $exporter = new field_exporter($record, array('context' => $context));
614             $fields[] = $exporter->export($PAGE->get_renderer('core'));
615         }
617         $result = array(
618             'fields' => $fields,
619             'warnings' => $warnings
620         );
621         return $result;
622     }
624     /**
625      * Returns description of method result value
626      *
627      * @return external_description
628      * @since Moodle 3.3
629      */
630     public static function get_fields_returns() {
631         return new external_single_structure(
632             array(
633                 'fields' => new external_multiple_structure(
634                     field_exporter::get_read_structure()
635                 ),
636                 'warnings' => new external_warnings()
637             )
638         );
639     }
641     /**
642      * Returns description of method parameters
643      *
644      * @return external_function_parameters
645      * @since Moodle 3.3
646      */
647     public static function search_entries_parameters() {
648         return new external_function_parameters(
649             array(
650                 'databaseid' => new external_value(PARAM_INT, 'data instance id'),
651                 'groupid' => new external_value(PARAM_INT, 'Group id, 0 means that the function will determine the user group',
652                                                    VALUE_DEFAULT, 0),
653                 'returncontents' => new external_value(PARAM_BOOL, 'Whether to return contents or not.', VALUE_DEFAULT, false),
654                 'search' => new external_value(PARAM_NOTAGS, 'search string (empty when using advanced)', VALUE_DEFAULT, ''),
655                 'advsearch' => new external_multiple_structure(
656                     new external_single_structure(
657                         array(
658                             'name' => new external_value(PARAM_ALPHANUMEXT, 'Field key for search.
659                                                             Use fn or ln for first or last name'),
660                             'value' => new external_value(PARAM_RAW, 'JSON encoded value for search'),
661                         )
662                     ), 'Advanced search', VALUE_DEFAULT, array()
663                 ),
664                 'sort' => new external_value(PARAM_INT, 'Sort the records by this field id, reserved ids are:
665                                                 0: timeadded
666                                                 -1: firstname
667                                                 -2: lastname
668                                                 -3: approved
669                                                 -4: timemodified.
670                                                 Empty for using the default database setting.', VALUE_DEFAULT, null),
671                 'order' => new external_value(PARAM_ALPHA, 'The direction of the sorting: \'ASC\' or \'DESC\'.
672                                                 Empty for using the default database setting.', VALUE_DEFAULT, null),
673                 'page' => new external_value(PARAM_INT, 'The page of records to return.', VALUE_DEFAULT, 0),
674                 'perpage' => new external_value(PARAM_INT, 'The number of records to return per page', VALUE_DEFAULT, 0),
675             )
676         );
677     }
679     /**
680      * Return access information for a given feedback
681      *
682      * @param int $databaseid       the data instance id
683      * @param int $groupid          (optional) group id, 0 means that the function will determine the user group
684      * @param bool $returncontents  whether to return contents or not
685      * @param str $search           search text
686      * @param array $advsearch      advanced search data
687      * @param str $sort             sort by this field
688      * @param int $order            the direction of the sorting
689      * @param int $page             page of records to return
690      * @param int $perpage          number of records to return per page
691      * @return array of warnings and the entries
692      * @since Moodle 3.3
693      * @throws moodle_exception
694      */
695     public static function search_entries($databaseid, $groupid = 0, $returncontents = false, $search = '', $advsearch = [],
696             $sort = null, $order = null, $page = 0, $perpage = 0) {
697         global $PAGE, $DB;
699         $params = array('databaseid' => $databaseid, 'groupid' => $groupid, 'returncontents' => $returncontents, 'search' => $search,
700                         'advsearch' => $advsearch, 'sort' => $sort, 'order' => $order, 'page' => $page, 'perpage' => $perpage);
701         $params = self::validate_parameters(self::search_entries_parameters(), $params);
702         $warnings = array();
704         if (!empty($params['order'])) {
705             $params['order'] = strtoupper($params['order']);
706             if ($params['order'] != 'ASC' && $params['order'] != 'DESC') {
707                 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $params['order'] . ')');
708             }
709         }
711         list($database, $course, $cm, $context) = self::validate_database($params['databaseid']);
712         // Check database is open in time.
713         data_require_time_available($database, null, $context);
715         if (!empty($params['groupid'])) {
716             $groupid = $params['groupid'];
717             // Determine is the group is visible to user.
718             if (!groups_group_visible($groupid, $course, $cm)) {
719                 throw new moodle_exception('notingroup');
720             }
721         } else {
722             // Check to see if groups are being used here.
723             if ($groupmode = groups_get_activity_groupmode($cm)) {
724                 // We don't need to validate a possible groupid = 0 since it would be handled by data_search_entries.
725                 $groupid = groups_get_activity_group($cm);
726             } else {
727                 $groupid = 0;
728             }
729         }
731         if (!empty($params['advsearch'])) {
732             $advanced = true;
733             $defaults = [];
734             $fn = $ln = ''; // Defaults for first and last name.
735             // Force defaults for advanced search.
736             foreach ($params['advsearch'] as $adv) {
737                 if ($adv['name'] == 'fn') {
738                     $fn = json_decode($adv['value']);
739                     continue;
740                 }
741                 if ($adv['name'] == 'ln') {
742                     $ln = json_decode($adv['value']);
743                     continue;
744                 }
745                 $defaults[$adv['name']] = json_decode($adv['value']);
746             }
747             list($searcharray, $params['search']) = data_build_search_array($database, false, [], $defaults, $fn, $ln);
748         } else {
749             $advanced = null;
750             $searcharray = null;
751         }
753         list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
754             data_search_entries($database, $cm, $context, 'list', $groupid, $params['search'], $params['sort'], $params['order'],
755                 $params['page'], $params['perpage'], $advanced, $searcharray);
757         $entries = [];
758         foreach ($records as $record) {
759             $user = user_picture::unalias($record, null, 'userid');
760             $related = array('context' => $context, 'database' => $database, 'user' => $user);
761             if ($params['returncontents']) {
762                 $related['contents'] = $DB->get_records('data_content', array('recordid' => $record->id));
763             } else {
764                 $related['contents'] = null;
765             }
767             $exporter = new record_exporter($record, $related);
768             $entries[] = $exporter->export($PAGE->get_renderer('core'));
769         }
771         $result = array(
772             'entries' => $entries,
773             'totalcount' => $totalcount,
774             'maxcount' => $maxcount,
775             'warnings' => $warnings
776         );
778         // Check if we should return the list rendered.
779         if ($params['returncontents']) {
780             ob_start();
781             // The return parameter stops the execution after the first record.
782             data_print_template('listtemplate', $records, $database, '', $page, false);
783             $result['listviewcontents'] = ob_get_contents();
784             ob_end_clean();
785         }
787         return $result;
788     }
790     /**
791      * Returns description of method result value
792      *
793      * @return external_description
794      * @since Moodle 3.3
795      */
796     public static function search_entries_returns() {
797         return new external_single_structure(
798             array(
799                 'entries' => new external_multiple_structure(
800                     record_exporter::get_read_structure()
801                 ),
802                 'totalcount' => new external_value(PARAM_INT, 'Total count of records returned by the search.'),
803                 'maxcount' => new external_value(PARAM_INT, 'Total count of records that the user could see in the database
804                     (if all the search criterias were removed).', VALUE_OPTIONAL),
805                 'listviewcontents' => new external_value(PARAM_RAW, 'The list view contents as is rendered in the site.',
806                                                             VALUE_OPTIONAL),
807                 'warnings' => new external_warnings()
808             )
809         );
810     }
812     /**
813      * Returns description of method parameters
814      *
815      * @return external_function_parameters
816      * @since Moodle 3.3
817      */
818     public static function approve_entry_parameters() {
819         return new external_function_parameters(
820             array(
821                 'entryid' => new external_value(PARAM_INT, 'Record entry id.'),
822                 'approve' => new external_value(PARAM_BOOL, 'Whether to approve (true) or unapprove the entry.',
823                                                 VALUE_DEFAULT, true),
824             )
825         );
826     }
828     /**
829      * Approves or unapproves an entry.
830      *
831      * @param int $entryid          the record entry id id
832      * @param bool $approve         whether to approve (true) or unapprove the entry
833      * @return array of warnings and the entries
834      * @since Moodle 3.3
835      * @throws moodle_exception
836      */
837     public static function approve_entry($entryid, $approve = true) {
838         global $PAGE, $DB;
840         $params = array('entryid' => $entryid, 'approve' => $approve);
841         $params = self::validate_parameters(self::approve_entry_parameters(), $params);
842         $warnings = array();
844         $record = $DB->get_record('data_records', array('id' => $params['entryid']), '*', MUST_EXIST);
845         list($database, $course, $cm, $context) = self::validate_database($record->dataid);
846         // Check database is open in time.
847         data_require_time_available($database, null, $context);
848         // Check specific capabilities.
849         require_capability('mod/data:approve', $context);
851         data_approve_entry($record->id, $params['approve']);
853         $result = array(
854             'status' => true,
855             'warnings' => $warnings
856         );
857         return $result;
858     }
860     /**
861      * Returns description of method result value
862      *
863      * @return external_description
864      * @since Moodle 3.3
865      */
866     public static function approve_entry_returns() {
867         return new external_single_structure(
868             array(
869                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
870                 'warnings' => new external_warnings()
871             )
872         );
873     }
875     /**
876      * Returns description of method parameters
877      *
878      * @return external_function_parameters
879      * @since Moodle 3.3
880      */
881     public static function delete_entry_parameters() {
882         return new external_function_parameters(
883             array(
884                 'entryid' => new external_value(PARAM_INT, 'Record entry id.'),
885             )
886         );
887     }
889     /**
890      * Deletes an entry.
891      *
892      * @param int $entryid the record entry id
893      * @return array of warnings success status
894      * @since Moodle 3.3
895      * @throws moodle_exception
896      */
897     public static function delete_entry($entryid) {
898         global $PAGE, $DB;
900         $params = array('entryid' => $entryid);
901         $params = self::validate_parameters(self::delete_entry_parameters(), $params);
902         $warnings = array();
904         $record = $DB->get_record('data_records', array('id' => $params['entryid']), '*', MUST_EXIST);
905         list($database, $course, $cm, $context) = self::validate_database($record->dataid);
907         if (data_user_can_manage_entry($record, $database, $context)) {
908             data_delete_record($record->id, $database, $course->id, $cm->id);
909         } else {
910             throw new moodle_exception('noaccess', 'data');
911         }
913         $result = array(
914             'status' => true,
915             'warnings' => $warnings
916         );
917         return $result;
918     }
920     /**
921      * Returns description of method result value
922      *
923      * @return external_description
924      * @since Moodle 3.3
925      */
926     public static function delete_entry_returns() {
927         return new external_single_structure(
928             array(
929                 'status' => new external_value(PARAM_BOOL, 'Always true. If we see this field it means that the entry was deleted.'),
930                 'warnings' => new external_warnings()
931             )
932         );
933     }
935     /**
936      * Returns description of method parameters
937      *
938      * @return external_function_parameters
939      * @since Moodle 3.3
940      */
941     public static function add_entry_parameters() {
942         return new external_function_parameters(
943             array(
944                 'databaseid' => new external_value(PARAM_INT, 'data instance id'),
945                 'groupid' => new external_value(PARAM_INT, 'Group id, 0 means that the function will determine the user group',
946                                                    VALUE_DEFAULT, 0),
947                 'data' => new external_multiple_structure(
948                     new external_single_structure(
949                         array(
950                             'fieldid' => new external_value(PARAM_INT, 'The field id.'),
951                             'subfield' => new external_value(PARAM_NOTAGS, 'The subfield name (if required).', VALUE_DEFAULT, ''),
952                             'value' => new external_value(PARAM_RAW, 'The contents for the field always JSON encoded.'),
953                         )
954                     ), 'The fields data to be created'
955                 ),
956             )
957         );
958     }
960     /**
961      * Adds a new entry to a database
962      *
963      * @param int $databaseid the data instance id
964      * @param int $groupid (optional) group id, 0 means that the function will determine the user group
965      * @param array $data the fields data to be created
966      * @return array of warnings and status result
967      * @since Moodle 3.3
968      * @throws moodle_exception
969      */
970     public static function add_entry($databaseid, $groupid, $data) {
971         global $DB;
973         $params = array('databaseid' => $databaseid, 'groupid' => $groupid, 'data' => $data);
974         $params = self::validate_parameters(self::add_entry_parameters(), $params);
975         $warnings = array();
976         $fieldnotifications = array();
978         list($database, $course, $cm, $context) = self::validate_database($params['databaseid']);
979         // Check database is open in time.
980         data_require_time_available($database, null, $context);
982         // Determine default group.
983         if (empty($params['groupid'])) {
984             // Check to see if groups are being used here.
985             $groupmode = groups_get_activity_groupmode($cm);
986             if ($groupmode) {
987                 $groupid = groups_get_activity_group($cm);
988             } else {
989                 $groupid = 0;
990             }
991         }
993         // Group is validated inside the function.
994         if (!data_user_can_add_entry($database, $groupid, $groupmode, $context)) {
995             throw new moodle_exception('noaccess', 'data');
996         }
998         // Prepare the data as is expected by the API.
999         $datarecord = new stdClass;
1000         foreach ($params['data'] as $data) {
1001             $subfield = ($data['subfield'] !== '') ? '_' . $data['subfield'] : '';
1002             // We ask for JSON encoded values because of multiple choice forms or checkboxes that use array parameters.
1003             $datarecord->{'field_' . $data['fieldid'] . $subfield} = json_decode($data['value']);
1004         }
1005         // Validate to ensure that enough data was submitted.
1006         $fields = $DB->get_records('data_fields', array('dataid' => $database->id));
1007         $processeddata = data_process_submission($database, $fields, $datarecord);
1009         // Format notifications.
1010         if (!empty($processeddata->fieldnotifications)) {
1011             foreach ($processeddata->fieldnotifications as $field => $notififications) {
1012                 foreach ($notififications as $notif) {
1013                     $fieldnotifications[] = [
1014                         'fieldname' => $field,
1015                         'notification' => $notif,
1016                     ];
1017                 }
1018             }
1019         }
1021         // Create a new (empty) record.
1022         $newentryid = 0;
1023         if ($processeddata->validated && $recordid = data_add_record($database, $groupid)) {
1024             $newentryid = $recordid;
1025             // Now populate the fields contents of the new record.
1026             data_add_fields_contents_to_new_record($database, $context, $recordid, $fields, $datarecord, $processeddata);
1027         }
1029         $result = array(
1030             'newentryid' => $newentryid,
1031             'generalnotifications' => $processeddata->generalnotifications,
1032             'fieldnotifications' => $fieldnotifications,
1033         );
1034         return $result;
1035     }
1037     /**
1038      * Returns description of method result value
1039      *
1040      * @return external_description
1041      * @since Moodle 3.3
1042      */
1043     public static function add_entry_returns() {
1044         return new external_single_structure(
1045             array(
1046                 'newentryid' => new external_value(PARAM_INT, 'True new created entry id. 0 if the entry was not created.'),
1047                 'generalnotifications' => new external_multiple_structure(
1048                     new external_value(PARAM_RAW, 'General notifications')
1049                 ),
1050                 'fieldnotifications' => new external_multiple_structure(
1051                     new external_single_structure(
1052                         array(
1053                             'fieldname' => new external_value(PARAM_TEXT, 'The field name.'),
1054                             'notification' => new external_value(PARAM_RAW, 'The notification for the field.'),
1055                         )
1056                     )
1057                 ),
1058                 'warnings' => new external_warnings()
1059             )
1060         );
1061     }
1063     /**
1064      * Returns description of method parameters
1065      *
1066      * @return external_function_parameters
1067      * @since Moodle 3.3
1068      */
1069     public static function update_entry_parameters() {
1070         return new external_function_parameters(
1071             array(
1072                 'entryid' => new external_value(PARAM_INT, 'The entry record id.'),
1073                 'data' => new external_multiple_structure(
1074                     new external_single_structure(
1075                         array(
1076                             'fieldid' => new external_value(PARAM_INT, 'The field id.'),
1077                             'subfield' => new external_value(PARAM_NOTAGS, 'The subfield name (if required).', VALUE_DEFAULT, null),
1078                             'value' => new external_value(PARAM_RAW, 'The new contents for the field always JSON encoded.'),
1079                         )
1080                     ), 'The fields data to be updated'
1081                 ),
1082             )
1083         );
1084     }
1086     /**
1087      * Updates an existing entry.
1088      *
1089      * @param int $entryid the data instance id
1090      * @param array $data the fields data to be created
1091      * @return array of warnings and status result
1092      * @since Moodle 3.3
1093      * @throws moodle_exception
1094      */
1095     public static function update_entry($entryid, $data) {
1096         global $DB;
1098         $params = array('entryid' => $entryid, 'data' => $data);
1099         $params = self::validate_parameters(self::update_entry_parameters(), $params);
1100         $warnings = array();
1101         $fieldnotifications = array();
1102         $updated = false;
1104         $record = $DB->get_record('data_records', array('id' => $params['entryid']), '*', MUST_EXIST);
1105         list($database, $course, $cm, $context) = self::validate_database($record->dataid);
1106         // Check database is open in time.
1107         data_require_time_available($database, null, $context);
1109         if (!data_user_can_manage_entry($record, $database, $context)) {
1110             throw new moodle_exception('noaccess', 'data');
1111         }
1113         // Prepare the data as is expected by the API.
1114         $datarecord = new stdClass;
1115         foreach ($params['data'] as $data) {
1116             $subfield = ($data['subfield'] !== '') ? '_' . $data['subfield'] : '';
1117             // We ask for JSON encoded values because of multiple choice forms or checkboxes that use array parameters.
1118             $datarecord->{'field_' . $data['fieldid'] . $subfield} = json_decode($data['value']);
1119         }
1120         // Validate to ensure that enough data was submitted.
1121         $fields = $DB->get_records('data_fields', array('dataid' => $database->id));
1122         $processeddata = data_process_submission($database, $fields, $datarecord);
1124         // Format notifications.
1125         if (!empty($processeddata->fieldnotifications)) {
1126             foreach ($processeddata->fieldnotifications as $field => $notififications) {
1127                 foreach ($notififications as $notif) {
1128                     $fieldnotifications[] = [
1129                         'fieldname' => $field,
1130                         'notification' => $notif,
1131                     ];
1132                 }
1133             }
1134         }
1136         if ($processeddata->validated) {
1137             // Now update the fields contents.
1138             data_update_record_fields_contents($database, $record, $context, $datarecord, $processeddata);
1139             $updated = true;
1140         }
1142         $result = array(
1143             'updated' => $updated,
1144             'generalnotifications' => $processeddata->generalnotifications,
1145             'fieldnotifications' => $fieldnotifications,
1146             'warnings' => $warnings,
1147         );
1148         return $result;
1149     }
1151     /**
1152      * Returns description of method result value
1153      *
1154      * @return external_description
1155      * @since Moodle 3.3
1156      */
1157     public static function update_entry_returns() {
1158         return new external_single_structure(
1159             array(
1160                 'updated' => new external_value(PARAM_BOOL, 'True if the entry was successfully updated, false other wise.'),
1161                 'generalnotifications' => new external_multiple_structure(
1162                     new external_value(PARAM_RAW, 'General notifications')
1163                 ),
1164                 'fieldnotifications' => new external_multiple_structure(
1165                     new external_single_structure(
1166                         array(
1167                             'fieldname' => new external_value(PARAM_TEXT, 'The field name.'),
1168                             'notification' => new external_value(PARAM_RAW, 'The notification for the field.'),
1169                         )
1170                     )
1171                 ),
1172                 'warnings' => new external_warnings()
1173             )
1174         );
1175     }