See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This is the external method to return the information needed to list all enrolled user attempts. 19 * 20 * @package mod_h5pactivity 21 * @since Moodle 3.11 22 * @copyright 2020 Ilya Tregubov <ilya@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace mod_h5pactivity\external; 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 global $CFG; 31 require_once($CFG->libdir . '/externallib.php'); 32 33 use mod_h5pactivity\local\manager; 34 use mod_h5pactivity\local\attempt; 35 use mod_h5pactivity\local\report; 36 use mod_h5pactivity\local\report\attempts as report_attempts; 37 use external_api; 38 use external_function_parameters; 39 use external_value; 40 use external_multiple_structure; 41 use external_single_structure; 42 use external_warnings; 43 use moodle_exception; 44 use context_module; 45 use stdClass; 46 47 /** 48 * This is the external method to return the information needed to list all enrolled user attempts. 49 * 50 * @copyright 2020 Ilya Tregubov <ilya@moodle.com> 51 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 52 */ 53 class get_user_attempts extends external_api { 54 55 /** 56 * Webservice parameters. 57 * 58 * @return external_function_parameters 59 */ 60 public static function execute_parameters(): external_function_parameters { 61 return new external_function_parameters( 62 [ 63 'h5pactivityid' => new external_value(PARAM_INT, 'h5p activity instance id'), 64 'sortorder' => new external_value(PARAM_TEXT, 65 'sort by either user id, firstname or lastname (with optional asc/desc)', VALUE_DEFAULT, 'id ASC'), 66 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1), 67 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0), 68 'firstinitial' => new external_value(PARAM_TEXT, 'Users whose first name ' . 69 'starts with $firstinitial', VALUE_DEFAULT, ''), 70 'lastinitial' => new external_value(PARAM_TEXT, 'Users whose last name ' . 71 'starts with $lastinitial', VALUE_DEFAULT, ''), 72 ] 73 ); 74 } 75 76 /** 77 * Return user attempts information in a h5p activity. 78 * 79 * @throws moodle_exception if the user cannot see the report 80 * @param int $h5pactivityid The h5p activity id 81 * @param int $sortorder The sort order 82 * @param int $page page number 83 * @param int $perpage items per page 84 * @param int $firstinitial Users whose first name starts with $firstinitial 85 * @param int $lastinitial Users whose last name starts with $lastinitial 86 * @return stdClass report data 87 */ 88 public static function execute(int $h5pactivityid, $sortorder = 'id ASC', ?int $page = 0, 89 ?int $perpage = 0, $firstinitial = '', $lastinitial = ''): stdClass { 90 [ 91 'h5pactivityid' => $h5pactivityid, 92 'sortorder' => $sortorder, 93 'page' => $page, 94 'perpage' => $perpage, 95 'firstinitial' => $firstinitial, 96 'lastinitial' => $lastinitial, 97 ] = external_api::validate_parameters(self::execute_parameters(), [ 98 'h5pactivityid' => $h5pactivityid, 99 'sortorder' => $sortorder, 100 'page' => $page, 101 'perpage' => $perpage, 102 'firstinitial' => $firstinitial, 103 'lastinitial' => $lastinitial, 104 ]); 105 106 $warnings = []; 107 108 [$course, $cm] = get_course_and_cm_from_instance($h5pactivityid, 'h5pactivity'); 109 110 $context = context_module::instance($cm->id); 111 self::validate_context($context); 112 113 $manager = manager::create_from_coursemodule($cm); 114 $instance = $manager->get_instance(); 115 if (!$manager->can_view_all_attempts()) { 116 throw new moodle_exception('nopermissiontoviewattempts', 'error', '', null, 117 'h5pactivity:reviewattempts required view attempts of all enrolled users.'); 118 } 119 120 // Ensure sortorder parameter is safe to use. Fallback to default value of the parameter itself. 121 $sortorderparts = explode(' ', $sortorder, 2); 122 $sortorder = get_safe_orderby([ 123 'id' => 'u.id', 124 'firstname' => 'u.firstname', 125 'lastname' => 'u.lastname', 126 'default' => 'u.id', 127 ], $sortorderparts[0], $sortorderparts[1] ?? ''); 128 129 $users = self::get_active_users($manager, 'u.id, u.firstname, u.lastname', 130 $sortorder, $page * $perpage, $perpage); 131 132 $usersattempts = []; 133 134 foreach ($users as $user) { 135 136 if ($firstinitial) { 137 if (strpos($user->firstname, $firstinitial) === false) { 138 continue; 139 } 140 } 141 142 if ($lastinitial) { 143 if (strpos($user->lastname, $lastinitial) === false) { 144 continue; 145 } 146 } 147 148 $report = $manager->get_report($user->id); 149 if ($report && $report instanceof report_attempts) { 150 $usersattempts[] = self::export_user_attempts($report, $user->id); 151 } else { 152 $warnings[] = [ 153 'item' => 'user', 154 'itemid' => $user->id, 155 'warningcode' => '1', 156 'message' => "Cannot access user attempts", 157 ]; 158 } 159 } 160 161 $result = (object)[ 162 'activityid' => $instance->id, 163 'usersattempts' => $usersattempts, 164 'warnings' => $warnings, 165 ]; 166 167 return $result; 168 } 169 170 /** 171 * Generate the active users list 172 * 173 * @param manager $manager the h5pactivity manager 174 * @param string $userfields the user fields to get 175 * @param string $sortorder the SQL sortorder 176 * @param int $limitfrom SQL limit from 177 * @param int $limitnum SQL limit num 178 */ 179 private static function get_active_users( 180 manager $manager, 181 string $userfields = 'u.*', 182 string $sortorder = '', 183 int $limitfrom = 0, 184 int $limitnum = 0 185 ): array { 186 187 global $DB; 188 189 $capjoin = $manager->get_active_users_join(true); 190 191 // Final SQL. 192 $sql = "SELECT DISTINCT {$userfields} 193 FROM {user} u {$capjoin->joins} 194 WHERE {$capjoin->wheres} 195 {$sortorder}"; 196 197 return $DB->get_records_sql($sql, $capjoin->params, $limitfrom, $limitnum); 198 } 199 200 /** 201 * Export attempts data for a specific user. 202 * 203 * @param report $report the report attempts object 204 * @param int $userid the user id 205 * @return stdClass 206 */ 207 private static function export_user_attempts(report $report, int $userid): stdClass { 208 $scored = $report->get_scored(); 209 $attempts = $report->get_attempts(); 210 211 $result = (object)[ 212 'userid' => $userid, 213 'attempts' => [], 214 ]; 215 216 foreach ($attempts as $attempt) { 217 $result->attempts[] = self::export_attempt($attempt); 218 } 219 220 if (!empty($scored)) { 221 $result->scored = (object)[ 222 'title' => $scored->title, 223 'grademethod' => $scored->grademethod, 224 'attempts' => [self::export_attempt($scored->attempt)], 225 ]; 226 } 227 228 return $result; 229 } 230 231 /** 232 * Return a data object from an attempt. 233 * 234 * @param attempt $attempt the attempt object 235 * @return stdClass a WS compatible version of the attempt 236 */ 237 private static function export_attempt(attempt $attempt): stdClass { 238 $result = (object)[ 239 'id' => $attempt->get_id(), 240 'h5pactivityid' => $attempt->get_h5pactivityid(), 241 'userid' => $attempt->get_userid(), 242 'timecreated' => $attempt->get_timecreated(), 243 'timemodified' => $attempt->get_timemodified(), 244 'attempt' => $attempt->get_attempt(), 245 'rawscore' => $attempt->get_rawscore(), 246 'maxscore' => $attempt->get_maxscore(), 247 'duration' => $attempt->get_duration(), 248 'scaled' => $attempt->get_scaled(), 249 ]; 250 if ($attempt->get_completion() !== null) { 251 $result->completion = $attempt->get_completion(); 252 } 253 if ($attempt->get_success() !== null) { 254 $result->success = $attempt->get_success(); 255 } 256 return $result; 257 } 258 259 /** 260 * Describes the get_h5pactivity_access_information return value. 261 * 262 * @return external_single_structure 263 */ 264 public static function execute_returns(): external_single_structure { 265 return new external_single_structure([ 266 'activityid' => new external_value(PARAM_INT, 'Activity course module ID'), 267 'usersattempts' => new external_multiple_structure( 268 self::get_user_attempts_returns(), 'The complete users attempts list' 269 ), 270 'warnings' => new external_warnings(), 271 ], 'Activity attempts data'); 272 } 273 274 /** 275 * Describes the get_h5pactivity_access_information return value. 276 * 277 * @return external_single_structure 278 */ 279 private static function get_user_attempts_returns(): external_single_structure { 280 $structure = [ 281 'userid' => new external_value(PARAM_INT, 'The user id'), 282 'attempts' => new external_multiple_structure(self::get_user_attempt_returns(), 'The complete attempts list'), 283 'scored' => new external_single_structure([ 284 'title' => new external_value(PARAM_NOTAGS, 'Scored attempts title'), 285 'grademethod' => new external_value(PARAM_NOTAGS, 'Grading method'), 286 'attempts' => new external_multiple_structure(self::get_user_attempt_returns(), 'List of the grading attempts'), 287 ], 'Attempts used to grade the activity', VALUE_OPTIONAL), 288 ]; 289 return new external_single_structure($structure); 290 } 291 292 /** 293 * Return the external structure of an attempt. 294 * 295 * @return external_single_structure 296 */ 297 private static function get_user_attempt_returns(): external_single_structure { 298 $result = new external_single_structure([ 299 'id' => new external_value(PARAM_INT, 'ID of the context'), 300 'h5pactivityid' => new external_value(PARAM_INT, 'ID of the H5P activity'), 301 'userid' => new external_value(PARAM_INT, 'ID of the user'), 302 'timecreated' => new external_value(PARAM_INT, 'Attempt creation'), 303 'timemodified' => new external_value(PARAM_INT, 'Attempt modified'), 304 'attempt' => new external_value(PARAM_INT, 'Attempt number'), 305 'rawscore' => new external_value(PARAM_INT, 'Attempt score value'), 306 'maxscore' => new external_value(PARAM_INT, 'Attempt max score'), 307 'duration' => new external_value(PARAM_INT, 'Attempt duration in seconds'), 308 'completion' => new external_value(PARAM_INT, 'Attempt completion', VALUE_OPTIONAL), 309 'success' => new external_value(PARAM_INT, 'Attempt success', VALUE_OPTIONAL), 310 'scaled' => new external_value(PARAM_FLOAT, 'Attempt scaled'), 311 ]); 312 return $result; 313 } 314 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body