1 |
efrain |
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 |
* moodlelib.php - Moodle main library
|
|
|
19 |
*
|
|
|
20 |
* Main library file of miscellaneous general-purpose Moodle functions.
|
|
|
21 |
* Other main libraries:
|
|
|
22 |
* - weblib.php - functions that produce web output
|
|
|
23 |
* - datalib.php - functions that access the database
|
|
|
24 |
*
|
|
|
25 |
* @package core
|
|
|
26 |
* @subpackage lib
|
|
|
27 |
* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
|
|
|
28 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
29 |
*/
|
|
|
30 |
|
|
|
31 |
use core\di;
|
|
|
32 |
use core\hook;
|
|
|
33 |
|
|
|
34 |
defined('MOODLE_INTERNAL') || die();
|
|
|
35 |
|
|
|
36 |
// CONSTANTS (Encased in phpdoc proper comments).
|
|
|
37 |
|
|
|
38 |
// Date and time constants.
|
|
|
39 |
/**
|
|
|
40 |
* Time constant - the number of seconds in a year
|
|
|
41 |
*/
|
|
|
42 |
define('YEARSECS', 31536000);
|
|
|
43 |
|
|
|
44 |
/**
|
|
|
45 |
* Time constant - the number of seconds in a week
|
|
|
46 |
*/
|
|
|
47 |
define('WEEKSECS', 604800);
|
|
|
48 |
|
|
|
49 |
/**
|
|
|
50 |
* Time constant - the number of seconds in a day
|
|
|
51 |
*/
|
|
|
52 |
define('DAYSECS', 86400);
|
|
|
53 |
|
|
|
54 |
/**
|
|
|
55 |
* Time constant - the number of seconds in an hour
|
|
|
56 |
*/
|
|
|
57 |
define('HOURSECS', 3600);
|
|
|
58 |
|
|
|
59 |
/**
|
|
|
60 |
* Time constant - the number of seconds in a minute
|
|
|
61 |
*/
|
|
|
62 |
define('MINSECS', 60);
|
|
|
63 |
|
|
|
64 |
/**
|
|
|
65 |
* Time constant - the number of minutes in a day
|
|
|
66 |
*/
|
|
|
67 |
define('DAYMINS', 1440);
|
|
|
68 |
|
|
|
69 |
/**
|
|
|
70 |
* Time constant - the number of minutes in an hour
|
|
|
71 |
*/
|
|
|
72 |
define('HOURMINS', 60);
|
|
|
73 |
|
|
|
74 |
// Parameter constants - every call to optional_param(), required_param()
|
|
|
75 |
// or clean_param() should have a specified type of parameter.
|
|
|
76 |
|
|
|
77 |
// We currently include \core\param manually here to avoid broken upgrades.
|
|
|
78 |
// This may change after the next LTS release as LTS releases require the previous LTS release.
|
|
|
79 |
require_once(__DIR__ . '/classes/deprecation.php');
|
|
|
80 |
require_once(__DIR__ . '/classes/param.php');
|
|
|
81 |
|
|
|
82 |
/**
|
|
|
83 |
* PARAM_ALPHA - contains only English ascii letters [a-zA-Z].
|
|
|
84 |
*/
|
|
|
85 |
define('PARAM_ALPHA', \core\param::ALPHA->value);
|
|
|
86 |
|
|
|
87 |
/**
|
|
|
88 |
* PARAM_ALPHAEXT the same contents as PARAM_ALPHA (English ascii letters [a-zA-Z]) plus the chars in quotes: "_-" allowed
|
|
|
89 |
* NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
|
|
|
90 |
*/
|
|
|
91 |
define('PARAM_ALPHAEXT', \core\param::ALPHAEXT->value);
|
|
|
92 |
|
|
|
93 |
/**
|
|
|
94 |
* PARAM_ALPHANUM - expected numbers 0-9 and English ascii letters [a-zA-Z] only.
|
|
|
95 |
*/
|
|
|
96 |
define('PARAM_ALPHANUM', \core\param::ALPHANUM->value);
|
|
|
97 |
|
|
|
98 |
/**
|
|
|
99 |
* PARAM_ALPHANUMEXT - expected numbers 0-9, letters (English ascii letters [a-zA-Z]) and _- only.
|
|
|
100 |
*/
|
|
|
101 |
define('PARAM_ALPHANUMEXT', \core\param::ALPHANUMEXT->value);
|
|
|
102 |
|
|
|
103 |
/**
|
|
|
104 |
* PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
|
|
|
105 |
*/
|
|
|
106 |
define('PARAM_AUTH', \core\param::AUTH->value);
|
|
|
107 |
|
|
|
108 |
/**
|
|
|
109 |
* PARAM_BASE64 - Base 64 encoded format
|
|
|
110 |
*/
|
|
|
111 |
define('PARAM_BASE64', \core\param::BASE64->value);
|
|
|
112 |
|
|
|
113 |
/**
|
|
|
114 |
* PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
|
|
|
115 |
*/
|
|
|
116 |
define('PARAM_BOOL', \core\param::BOOL->value);
|
|
|
117 |
|
|
|
118 |
/**
|
|
|
119 |
* PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
|
|
|
120 |
* checked against the list of capabilities in the database.
|
|
|
121 |
*/
|
|
|
122 |
define('PARAM_CAPABILITY', \core\param::CAPABILITY->value);
|
|
|
123 |
|
|
|
124 |
/**
|
|
|
125 |
* PARAM_CLEANHTML - cleans submitted HTML code. Note that you almost never want
|
|
|
126 |
* to use this. The normal mode of operation is to use PARAM_RAW when receiving
|
|
|
127 |
* the input (required/optional_param or formslib) and then sanitise the HTML
|
|
|
128 |
* using format_text on output. This is for the rare cases when you want to
|
|
|
129 |
* sanitise the HTML on input. This cleaning may also fix xhtml strictness.
|
|
|
130 |
*/
|
|
|
131 |
define('PARAM_CLEANHTML', \core\param::CLEANHTML->value);
|
|
|
132 |
|
|
|
133 |
/**
|
|
|
134 |
* PARAM_EMAIL - an email address following the RFC
|
|
|
135 |
*/
|
|
|
136 |
define('PARAM_EMAIL', \core\param::EMAIL->value);
|
|
|
137 |
|
|
|
138 |
/**
|
|
|
139 |
* PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
|
|
|
140 |
*/
|
|
|
141 |
define('PARAM_FILE', \core\param::FILE->value);
|
|
|
142 |
|
|
|
143 |
/**
|
|
|
144 |
* PARAM_FLOAT - a real/floating point number.
|
|
|
145 |
*
|
|
|
146 |
* Note that you should not use PARAM_FLOAT for numbers typed in by the user.
|
|
|
147 |
* It does not work for languages that use , as a decimal separator.
|
|
|
148 |
* Use PARAM_LOCALISEDFLOAT instead.
|
|
|
149 |
*/
|
|
|
150 |
define('PARAM_FLOAT', \core\param::FLOAT->value);
|
|
|
151 |
|
|
|
152 |
/**
|
|
|
153 |
* PARAM_LOCALISEDFLOAT - a localised real/floating point number.
|
|
|
154 |
* This is preferred over PARAM_FLOAT for numbers typed in by the user.
|
|
|
155 |
* Cleans localised numbers to computer readable numbers; false for invalid numbers.
|
|
|
156 |
*/
|
|
|
157 |
define('PARAM_LOCALISEDFLOAT', \core\param::LOCALISEDFLOAT->value);
|
|
|
158 |
|
|
|
159 |
/**
|
|
|
160 |
* PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
|
|
|
161 |
*/
|
|
|
162 |
define('PARAM_HOST', \core\param::HOST->value);
|
|
|
163 |
|
|
|
164 |
/**
|
|
|
165 |
* PARAM_INT - integers only, use when expecting only numbers.
|
|
|
166 |
*/
|
|
|
167 |
define('PARAM_INT', \core\param::INT->value);
|
|
|
168 |
|
|
|
169 |
/**
|
|
|
170 |
* PARAM_LANG - checks to see if the string is a valid installed language in the current site.
|
|
|
171 |
*/
|
|
|
172 |
define('PARAM_LANG', \core\param::LANG->value);
|
|
|
173 |
|
|
|
174 |
/**
|
|
|
175 |
* PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the
|
|
|
176 |
* others! Implies PARAM_URL!)
|
|
|
177 |
*/
|
|
|
178 |
define('PARAM_LOCALURL', \core\param::LOCALURL->value);
|
|
|
179 |
|
|
|
180 |
/**
|
|
|
181 |
* PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
|
|
|
182 |
*/
|
|
|
183 |
define('PARAM_NOTAGS', \core\param::NOTAGS->value);
|
|
|
184 |
|
|
|
185 |
/**
|
|
|
186 |
* PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory
|
|
|
187 |
* traversals note: the leading slash is not removed, window drive letter is not allowed
|
|
|
188 |
*/
|
|
|
189 |
define('PARAM_PATH', \core\param::PATH->value);
|
|
|
190 |
|
|
|
191 |
/**
|
|
|
192 |
* PARAM_PEM - Privacy Enhanced Mail format
|
|
|
193 |
*/
|
|
|
194 |
define('PARAM_PEM', \core\param::PEM->value);
|
|
|
195 |
|
|
|
196 |
/**
|
|
|
197 |
* PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
|
|
|
198 |
*/
|
|
|
199 |
define('PARAM_PERMISSION', \core\param::PERMISSION->value);
|
|
|
200 |
|
|
|
201 |
/**
|
|
|
202 |
* PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
|
|
|
203 |
*/
|
|
|
204 |
define('PARAM_RAW', \core\param::RAW->value);
|
|
|
205 |
|
|
|
206 |
/**
|
|
|
207 |
* PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
|
|
|
208 |
*/
|
|
|
209 |
define('PARAM_RAW_TRIMMED', \core\param::RAW_TRIMMED->value);
|
|
|
210 |
|
|
|
211 |
/**
|
|
|
212 |
* PARAM_SAFEDIR - safe directory name, suitable for include() and require()
|
|
|
213 |
*/
|
|
|
214 |
define('PARAM_SAFEDIR', \core\param::SAFEDIR->value);
|
|
|
215 |
|
|
|
216 |
/**
|
|
|
217 |
* PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths
|
|
|
218 |
* and other references to Moodle code files.
|
|
|
219 |
*
|
|
|
220 |
* This is NOT intended to be used for absolute paths or any user uploaded files.
|
|
|
221 |
*/
|
|
|
222 |
define('PARAM_SAFEPATH', \core\param::SAFEPATH->value);
|
|
|
223 |
|
|
|
224 |
/**
|
|
|
225 |
* PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
|
|
|
226 |
*/
|
|
|
227 |
define('PARAM_SEQUENCE', \core\param::SEQUENCE->value);
|
|
|
228 |
|
|
|
229 |
/**
|
|
|
230 |
* PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
|
|
|
231 |
*/
|
|
|
232 |
define('PARAM_TAG', \core\param::TAG->value);
|
|
|
233 |
|
|
|
234 |
/**
|
|
|
235 |
* PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
|
|
|
236 |
*/
|
|
|
237 |
define('PARAM_TAGLIST', \core\param::TAGLIST->value);
|
|
|
238 |
|
|
|
239 |
/**
|
|
|
240 |
* PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
|
|
|
241 |
*/
|
|
|
242 |
define('PARAM_TEXT', \core\param::TEXT->value);
|
|
|
243 |
|
|
|
244 |
/**
|
|
|
245 |
* PARAM_THEME - Checks to see if the string is a valid theme name in the current site
|
|
|
246 |
*/
|
|
|
247 |
define('PARAM_THEME', \core\param::THEME->value);
|
|
|
248 |
|
|
|
249 |
/**
|
|
|
250 |
* PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but
|
|
|
251 |
* http://localhost.localdomain/ is ok.
|
|
|
252 |
*/
|
|
|
253 |
define('PARAM_URL', \core\param::URL->value);
|
|
|
254 |
|
|
|
255 |
/**
|
|
|
256 |
* PARAM_USERNAME - Clean username to only contains allowed characters. This is to be used ONLY when manually creating user
|
|
|
257 |
* accounts, do NOT use when syncing with external systems!!
|
|
|
258 |
*/
|
|
|
259 |
define('PARAM_USERNAME', \core\param::USERNAME->value);
|
|
|
260 |
|
|
|
261 |
/**
|
|
|
262 |
* PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
|
|
|
263 |
*/
|
|
|
264 |
define('PARAM_STRINGID', \core\param::STRINGID->value);
|
|
|
265 |
|
|
|
266 |
// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE.
|
|
|
267 |
/**
|
|
|
268 |
* PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
|
|
|
269 |
* It was one of the first types, that is why it is abused so much ;-)
|
|
|
270 |
* @deprecated since 2.0
|
|
|
271 |
*/
|
|
|
272 |
define('PARAM_CLEAN', \core\param::CLEAN->value);
|
|
|
273 |
|
|
|
274 |
/**
|
|
|
275 |
* PARAM_INTEGER - deprecated alias for PARAM_INT
|
|
|
276 |
* @deprecated since 2.0
|
|
|
277 |
*/
|
|
|
278 |
define('PARAM_INTEGER', \core\param::INT->value);
|
|
|
279 |
|
|
|
280 |
/**
|
|
|
281 |
* PARAM_NUMBER - deprecated alias of PARAM_FLOAT
|
|
|
282 |
* @deprecated since 2.0
|
|
|
283 |
*/
|
|
|
284 |
define('PARAM_NUMBER', \core\param::FLOAT->value);
|
|
|
285 |
|
|
|
286 |
/**
|
|
|
287 |
* PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
|
|
|
288 |
* NOTE: originally alias for PARAM_APLHA
|
|
|
289 |
* @deprecated since 2.0
|
|
|
290 |
*/
|
|
|
291 |
define('PARAM_ACTION', \core\param::ALPHANUMEXT->value);
|
|
|
292 |
|
|
|
293 |
/**
|
|
|
294 |
* PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
|
|
|
295 |
* NOTE: originally alias for PARAM_APLHA
|
|
|
296 |
* @deprecated since 2.0
|
|
|
297 |
*/
|
|
|
298 |
define('PARAM_FORMAT', \core\param::ALPHANUMEXT->value);
|
|
|
299 |
|
|
|
300 |
/**
|
|
|
301 |
* PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
|
|
|
302 |
* @deprecated since 2.0
|
|
|
303 |
*/
|
|
|
304 |
define('PARAM_MULTILANG', \core\param::TEXT->value);
|
|
|
305 |
|
|
|
306 |
/**
|
|
|
307 |
* PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
|
|
|
308 |
* string separated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
|
|
|
309 |
* America/Port-au-Prince)
|
|
|
310 |
*/
|
|
|
311 |
define('PARAM_TIMEZONE', \core\param::TIMEZONE->value);
|
|
|
312 |
|
|
|
313 |
/**
|
|
|
314 |
* PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
|
|
|
315 |
* @deprecated since 2.0
|
|
|
316 |
*/
|
|
|
317 |
define('PARAM_CLEANFILE', \core\param::CLEANFILE->value);
|
|
|
318 |
|
|
|
319 |
/**
|
|
|
320 |
* PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
|
|
|
321 |
* Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
|
|
|
322 |
* Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
|
|
|
323 |
* NOTE: numbers and underscores are strongly discouraged in plugin names!
|
|
|
324 |
*/
|
|
|
325 |
define('PARAM_COMPONENT', \core\param::COMPONENT->value);
|
|
|
326 |
|
|
|
327 |
/**
|
|
|
328 |
* PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
|
|
|
329 |
* It is usually used together with context id and component.
|
|
|
330 |
* Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
|
|
|
331 |
*/
|
|
|
332 |
define('PARAM_AREA', \core\param::AREA->value);
|
|
|
333 |
|
|
|
334 |
/**
|
|
|
335 |
* PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'paypal', 'completionstatus'.
|
|
|
336 |
* Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
|
|
|
337 |
* NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
|
|
|
338 |
*/
|
|
|
339 |
define('PARAM_PLUGIN', \core\param::PLUGIN->value);
|
|
|
340 |
|
|
|
341 |
|
|
|
342 |
// Web Services.
|
|
|
343 |
|
|
|
344 |
/**
|
|
|
345 |
* VALUE_REQUIRED - if the parameter is not supplied, there is an error
|
|
|
346 |
*/
|
|
|
347 |
define('VALUE_REQUIRED', 1);
|
|
|
348 |
|
|
|
349 |
/**
|
|
|
350 |
* VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
|
|
|
351 |
*/
|
|
|
352 |
define('VALUE_OPTIONAL', 2);
|
|
|
353 |
|
|
|
354 |
/**
|
|
|
355 |
* VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
|
|
|
356 |
*/
|
|
|
357 |
define('VALUE_DEFAULT', 0);
|
|
|
358 |
|
|
|
359 |
/**
|
|
|
360 |
* NULL_NOT_ALLOWED - the parameter can not be set to null in the database
|
|
|
361 |
*/
|
|
|
362 |
define('NULL_NOT_ALLOWED', false);
|
|
|
363 |
|
|
|
364 |
/**
|
|
|
365 |
* NULL_ALLOWED - the parameter can be set to null in the database
|
|
|
366 |
*/
|
|
|
367 |
define('NULL_ALLOWED', true);
|
|
|
368 |
|
|
|
369 |
// Page types.
|
|
|
370 |
|
|
|
371 |
/**
|
|
|
372 |
* PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
|
|
|
373 |
*/
|
|
|
374 |
define('PAGE_COURSE_VIEW', 'course-view');
|
|
|
375 |
|
|
|
376 |
/** Get remote addr constant */
|
|
|
377 |
define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
|
|
|
378 |
/** Get remote addr constant */
|
|
|
379 |
define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
|
|
|
380 |
/**
|
|
|
381 |
* GETREMOTEADDR_SKIP_DEFAULT defines the default behavior remote IP address validation.
|
|
|
382 |
*/
|
|
|
383 |
define('GETREMOTEADDR_SKIP_DEFAULT', GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP);
|
|
|
384 |
|
|
|
385 |
// Blog access level constant declaration.
|
|
|
386 |
define ('BLOG_USER_LEVEL', 1);
|
|
|
387 |
define ('BLOG_GROUP_LEVEL', 2);
|
|
|
388 |
define ('BLOG_COURSE_LEVEL', 3);
|
|
|
389 |
define ('BLOG_SITE_LEVEL', 4);
|
|
|
390 |
define ('BLOG_GLOBAL_LEVEL', 5);
|
|
|
391 |
|
|
|
392 |
|
|
|
393 |
// Tag constants.
|
|
|
394 |
/**
|
|
|
395 |
* To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
|
|
|
396 |
* length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
|
|
|
397 |
* TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
|
|
|
398 |
*
|
|
|
399 |
* @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
|
|
|
400 |
*/
|
|
|
401 |
define('TAG_MAX_LENGTH', 50);
|
|
|
402 |
|
|
|
403 |
// Password policy constants.
|
|
|
404 |
define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
|
|
|
405 |
define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
|
|
|
406 |
define ('PASSWORD_DIGITS', '0123456789');
|
|
|
407 |
define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
|
|
|
408 |
|
|
|
409 |
/**
|
|
|
410 |
* Required password pepper entropy.
|
|
|
411 |
*/
|
|
|
412 |
define ('PEPPER_ENTROPY', 112);
|
|
|
413 |
|
|
|
414 |
// Feature constants.
|
|
|
415 |
// Used for plugin_supports() to report features that are, or are not, supported by a module.
|
|
|
416 |
|
|
|
417 |
/** True if module can provide a grade */
|
|
|
418 |
define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
|
|
|
419 |
/** True if module supports outcomes */
|
|
|
420 |
define('FEATURE_GRADE_OUTCOMES', 'outcomes');
|
|
|
421 |
/** True if module supports advanced grading methods */
|
|
|
422 |
define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
|
|
|
423 |
/** True if module controls the grade visibility over the gradebook */
|
|
|
424 |
define('FEATURE_CONTROLS_GRADE_VISIBILITY', 'controlsgradevisbility');
|
|
|
425 |
/** True if module supports plagiarism plugins */
|
|
|
426 |
define('FEATURE_PLAGIARISM', 'plagiarism');
|
|
|
427 |
|
|
|
428 |
/** True if module has code to track whether somebody viewed it */
|
|
|
429 |
define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
|
|
|
430 |
/** True if module has custom completion rules */
|
|
|
431 |
define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
|
|
|
432 |
|
|
|
433 |
/** True if module has no 'view' page (like label) */
|
|
|
434 |
define('FEATURE_NO_VIEW_LINK', 'viewlink');
|
|
|
435 |
/** True (which is default) if the module wants support for setting the ID number for grade calculation purposes. */
|
|
|
436 |
define('FEATURE_IDNUMBER', 'idnumber');
|
|
|
437 |
/** True if module supports groups */
|
|
|
438 |
define('FEATURE_GROUPS', 'groups');
|
|
|
439 |
/** True if module supports groupings */
|
|
|
440 |
define('FEATURE_GROUPINGS', 'groupings');
|
|
|
441 |
/**
|
|
|
442 |
* True if module supports groupmembersonly (which no longer exists)
|
|
|
443 |
* @deprecated Since Moodle 2.8
|
|
|
444 |
*/
|
|
|
445 |
define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
|
|
|
446 |
|
|
|
447 |
/** Type of module */
|
|
|
448 |
define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
|
|
|
449 |
/** True if module supports intro editor */
|
|
|
450 |
define('FEATURE_MOD_INTRO', 'mod_intro');
|
|
|
451 |
/** True if module has default completion */
|
|
|
452 |
define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
|
|
|
453 |
|
|
|
454 |
define('FEATURE_COMMENT', 'comment');
|
|
|
455 |
|
|
|
456 |
define('FEATURE_RATE', 'rate');
|
|
|
457 |
/** True if module supports backup/restore of moodle2 format */
|
|
|
458 |
define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
|
|
|
459 |
|
|
|
460 |
/** True if module can show description on course main page */
|
|
|
461 |
define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
|
|
|
462 |
|
|
|
463 |
/** True if module uses the question bank */
|
|
|
464 |
define('FEATURE_USES_QUESTIONS', 'usesquestions');
|
|
|
465 |
|
|
|
466 |
/**
|
|
|
467 |
* Maximum filename char size
|
|
|
468 |
*/
|
|
|
469 |
define('MAX_FILENAME_SIZE', 100);
|
|
|
470 |
|
|
|
471 |
/** Unspecified module archetype */
|
|
|
472 |
define('MOD_ARCHETYPE_OTHER', 0);
|
|
|
473 |
/** Resource-like type module */
|
|
|
474 |
define('MOD_ARCHETYPE_RESOURCE', 1);
|
|
|
475 |
/** Assignment module archetype */
|
|
|
476 |
define('MOD_ARCHETYPE_ASSIGNMENT', 2);
|
|
|
477 |
/** System (not user-addable) module archetype */
|
|
|
478 |
define('MOD_ARCHETYPE_SYSTEM', 3);
|
|
|
479 |
|
|
|
480 |
/** Type of module */
|
|
|
481 |
define('FEATURE_MOD_PURPOSE', 'mod_purpose');
|
|
|
482 |
/** Module purpose administration */
|
|
|
483 |
define('MOD_PURPOSE_ADMINISTRATION', 'administration');
|
|
|
484 |
/** Module purpose assessment */
|
|
|
485 |
define('MOD_PURPOSE_ASSESSMENT', 'assessment');
|
|
|
486 |
/** Module purpose communication */
|
|
|
487 |
define('MOD_PURPOSE_COLLABORATION', 'collaboration');
|
|
|
488 |
/** Module purpose communication */
|
|
|
489 |
define('MOD_PURPOSE_COMMUNICATION', 'communication');
|
|
|
490 |
/** Module purpose content */
|
|
|
491 |
define('MOD_PURPOSE_CONTENT', 'content');
|
|
|
492 |
/** Module purpose interactive content */
|
|
|
493 |
define('MOD_PURPOSE_INTERACTIVECONTENT', 'interactivecontent');
|
|
|
494 |
/** Module purpose other */
|
|
|
495 |
define('MOD_PURPOSE_OTHER', 'other');
|
|
|
496 |
/**
|
|
|
497 |
* Module purpose interface
|
|
|
498 |
* @deprecated since Moodle 4.4
|
|
|
499 |
* @todo MDL-80701 Remove in Moodle 4.8
|
|
|
500 |
*/
|
|
|
501 |
define('MOD_PURPOSE_INTERFACE', 'interface');
|
|
|
502 |
|
|
|
503 |
/**
|
|
|
504 |
* Security token used for allowing access
|
|
|
505 |
* from external application such as web services.
|
|
|
506 |
* Scripts do not use any session, performance is relatively
|
|
|
507 |
* low because we need to load access info in each request.
|
|
|
508 |
* Scripts are executed in parallel.
|
|
|
509 |
*/
|
|
|
510 |
define('EXTERNAL_TOKEN_PERMANENT', 0);
|
|
|
511 |
|
|
|
512 |
/**
|
|
|
513 |
* Security token used for allowing access
|
|
|
514 |
* of embedded applications, the code is executed in the
|
|
|
515 |
* active user session. Token is invalidated after user logs out.
|
|
|
516 |
* Scripts are executed serially - normal session locking is used.
|
|
|
517 |
*/
|
|
|
518 |
define('EXTERNAL_TOKEN_EMBEDDED', 1);
|
|
|
519 |
|
|
|
520 |
/**
|
|
|
521 |
* The home page should be the site home
|
|
|
522 |
*/
|
|
|
523 |
define('HOMEPAGE_SITE', 0);
|
|
|
524 |
/**
|
|
|
525 |
* The home page should be the users my page
|
|
|
526 |
*/
|
|
|
527 |
define('HOMEPAGE_MY', 1);
|
|
|
528 |
/**
|
|
|
529 |
* The home page can be chosen by the user
|
|
|
530 |
*/
|
|
|
531 |
define('HOMEPAGE_USER', 2);
|
|
|
532 |
/**
|
|
|
533 |
* The home page should be the users my courses page
|
|
|
534 |
*/
|
|
|
535 |
define('HOMEPAGE_MYCOURSES', 3);
|
|
|
536 |
|
|
|
537 |
/**
|
|
|
538 |
* URL of the Moodle sites registration portal.
|
|
|
539 |
*/
|
|
|
540 |
defined('HUB_MOODLEORGHUBURL') || define('HUB_MOODLEORGHUBURL', 'https://stats.moodle.org');
|
|
|
541 |
|
|
|
542 |
/**
|
|
|
543 |
* URL of main Moodle site for marketing, products and services.
|
|
|
544 |
*/
|
|
|
545 |
defined('MOODLE_PRODUCTURL') || define('MOODLE_PRODUCTURL', 'https://moodle.com');
|
|
|
546 |
|
|
|
547 |
/**
|
|
|
548 |
* URL of the statistic server public key.
|
|
|
549 |
*/
|
|
|
550 |
defined('HUB_STATSPUBLICKEY') || define('HUB_STATSPUBLICKEY', 'https://moodle.org/static/statspubkey.pem');
|
|
|
551 |
|
|
|
552 |
/**
|
|
|
553 |
* Moodle mobile app service name
|
|
|
554 |
*/
|
|
|
555 |
define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
|
|
|
556 |
|
|
|
557 |
/**
|
|
|
558 |
* Indicates the user has the capabilities required to ignore activity and course file size restrictions
|
|
|
559 |
*/
|
|
|
560 |
define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1);
|
|
|
561 |
|
|
|
562 |
/**
|
|
|
563 |
* Course display settings: display all sections on one page.
|
|
|
564 |
*/
|
|
|
565 |
define('COURSE_DISPLAY_SINGLEPAGE', 0);
|
|
|
566 |
/**
|
|
|
567 |
* Course display settings: split pages into a page per section.
|
|
|
568 |
*/
|
|
|
569 |
define('COURSE_DISPLAY_MULTIPAGE', 1);
|
|
|
570 |
|
|
|
571 |
/**
|
|
|
572 |
* Authentication constant: String used in password field when password is not stored.
|
|
|
573 |
*/
|
|
|
574 |
define('AUTH_PASSWORD_NOT_CACHED', 'not cached');
|
|
|
575 |
|
|
|
576 |
/**
|
|
|
577 |
* Email from header to never include via information.
|
|
|
578 |
*/
|
|
|
579 |
define('EMAIL_VIA_NEVER', 0);
|
|
|
580 |
|
|
|
581 |
/**
|
|
|
582 |
* Email from header to always include via information.
|
|
|
583 |
*/
|
|
|
584 |
define('EMAIL_VIA_ALWAYS', 1);
|
|
|
585 |
|
|
|
586 |
/**
|
|
|
587 |
* Email from header to only include via information if the address is no-reply.
|
|
|
588 |
*/
|
|
|
589 |
define('EMAIL_VIA_NO_REPLY_ONLY', 2);
|
|
|
590 |
|
|
|
591 |
/**
|
|
|
592 |
* Contact site support form/link disabled.
|
|
|
593 |
*/
|
|
|
594 |
define('CONTACT_SUPPORT_DISABLED', 0);
|
|
|
595 |
|
|
|
596 |
/**
|
|
|
597 |
* Contact site support form/link only available to authenticated users.
|
|
|
598 |
*/
|
|
|
599 |
define('CONTACT_SUPPORT_AUTHENTICATED', 1);
|
|
|
600 |
|
|
|
601 |
/**
|
|
|
602 |
* Contact site support form/link available to anyone visiting the site.
|
|
|
603 |
*/
|
|
|
604 |
define('CONTACT_SUPPORT_ANYONE', 2);
|
|
|
605 |
|
|
|
606 |
/**
|
|
|
607 |
* Maximum number of characters for password.
|
|
|
608 |
*/
|
|
|
609 |
define('MAX_PASSWORD_CHARACTERS', 128);
|
|
|
610 |
|
|
|
611 |
/**
|
|
|
612 |
* Toggle sensitive feature is disabled. Used for sensitive inputs (passwords, tokens, keys).
|
|
|
613 |
*/
|
|
|
614 |
define('TOGGLE_SENSITIVE_DISABLED', 0);
|
|
|
615 |
|
|
|
616 |
/**
|
|
|
617 |
* Toggle sensitive feature is enabled. Used for sensitive inputs (passwords, tokens, keys).
|
|
|
618 |
*/
|
|
|
619 |
define('TOGGLE_SENSITIVE_ENABLED', 1);
|
|
|
620 |
|
|
|
621 |
/**
|
|
|
622 |
* Toggle sensitive feature is enabled for small screens only. Used for sensitive inputs (passwords, tokens, keys).
|
|
|
623 |
*/
|
|
|
624 |
define('TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY', 2);
|
|
|
625 |
|
|
|
626 |
// PARAMETER HANDLING.
|
|
|
627 |
|
|
|
628 |
/**
|
|
|
629 |
* Returns a particular value for the named variable, taken from
|
|
|
630 |
* POST or GET. If the parameter doesn't exist then an error is
|
|
|
631 |
* thrown because we require this variable.
|
|
|
632 |
*
|
|
|
633 |
* This function should be used to initialise all required values
|
|
|
634 |
* in a script that are based on parameters. Usually it will be
|
|
|
635 |
* used like this:
|
|
|
636 |
* $id = required_param('id', PARAM_INT);
|
|
|
637 |
*
|
|
|
638 |
* Please note the $type parameter is now required and the value can not be array.
|
|
|
639 |
*
|
|
|
640 |
* @param string $parname the name of the page parameter we want
|
|
|
641 |
* @param string $type expected type of parameter
|
|
|
642 |
* @return mixed
|
|
|
643 |
* @throws coding_exception
|
|
|
644 |
*/
|
|
|
645 |
function required_param($parname, $type) {
|
|
|
646 |
return \core\param::from_type($type)->required_param($parname);
|
|
|
647 |
}
|
|
|
648 |
|
|
|
649 |
/**
|
|
|
650 |
* Returns a particular array value for the named variable, taken from
|
|
|
651 |
* POST or GET. If the parameter doesn't exist then an error is
|
|
|
652 |
* thrown because we require this variable.
|
|
|
653 |
*
|
|
|
654 |
* This function should be used to initialise all required values
|
|
|
655 |
* in a script that are based on parameters. Usually it will be
|
|
|
656 |
* used like this:
|
|
|
657 |
* $ids = required_param_array('ids', PARAM_INT);
|
|
|
658 |
*
|
|
|
659 |
* Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
|
|
|
660 |
*
|
|
|
661 |
* @param string $parname the name of the page parameter we want
|
|
|
662 |
* @param string $type expected type of parameter
|
|
|
663 |
* @return array
|
|
|
664 |
* @throws coding_exception
|
|
|
665 |
*/
|
|
|
666 |
function required_param_array($parname, $type) {
|
|
|
667 |
return \core\param::from_type($type)->required_param_array($parname);
|
|
|
668 |
}
|
|
|
669 |
|
|
|
670 |
/**
|
|
|
671 |
* Returns a particular value for the named variable, taken from
|
|
|
672 |
* POST or GET, otherwise returning a given default.
|
|
|
673 |
*
|
|
|
674 |
* This function should be used to initialise all optional values
|
|
|
675 |
* in a script that are based on parameters. Usually it will be
|
|
|
676 |
* used like this:
|
|
|
677 |
* $name = optional_param('name', 'Fred', PARAM_TEXT);
|
|
|
678 |
*
|
|
|
679 |
* Please note the $type parameter is now required and the value can not be array.
|
|
|
680 |
*
|
|
|
681 |
* @param string $parname the name of the page parameter we want
|
|
|
682 |
* @param mixed $default the default value to return if nothing is found
|
|
|
683 |
* @param string $type expected type of parameter
|
|
|
684 |
* @return mixed
|
|
|
685 |
* @throws coding_exception
|
|
|
686 |
*/
|
|
|
687 |
function optional_param($parname, $default, $type) {
|
|
|
688 |
return \core\param::from_type($type)->optional_param(
|
|
|
689 |
paramname: $parname,
|
|
|
690 |
default: $default,
|
|
|
691 |
);
|
|
|
692 |
}
|
|
|
693 |
|
|
|
694 |
/**
|
|
|
695 |
* Returns a particular array value for the named variable, taken from
|
|
|
696 |
* POST or GET, otherwise returning a given default.
|
|
|
697 |
*
|
|
|
698 |
* This function should be used to initialise all optional values
|
|
|
699 |
* in a script that are based on parameters. Usually it will be
|
|
|
700 |
* used like this:
|
|
|
701 |
* $ids = optional_param('id', array(), PARAM_INT);
|
|
|
702 |
*
|
|
|
703 |
* Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
|
|
|
704 |
*
|
|
|
705 |
* @param string $parname the name of the page parameter we want
|
|
|
706 |
* @param mixed $default the default value to return if nothing is found
|
|
|
707 |
* @param string $type expected type of parameter
|
|
|
708 |
* @return array
|
|
|
709 |
* @throws coding_exception
|
|
|
710 |
*/
|
|
|
711 |
function optional_param_array($parname, $default, $type) {
|
|
|
712 |
return \core\param::from_type($type)->optional_param_array(
|
|
|
713 |
paramname: $parname,
|
|
|
714 |
default: $default,
|
|
|
715 |
);
|
|
|
716 |
}
|
|
|
717 |
|
|
|
718 |
/**
|
|
|
719 |
* Strict validation of parameter values, the values are only converted
|
|
|
720 |
* to requested PHP type. Internally it is using clean_param, the values
|
|
|
721 |
* before and after cleaning must be equal - otherwise
|
|
|
722 |
* an invalid_parameter_exception is thrown.
|
|
|
723 |
* Objects and classes are not accepted.
|
|
|
724 |
*
|
|
|
725 |
* @param mixed $param
|
|
|
726 |
* @param string $type PARAM_ constant
|
|
|
727 |
* @param bool $allownull are nulls valid value?
|
|
|
728 |
* @param string $debuginfo optional debug information
|
|
|
729 |
* @return mixed the $param value converted to PHP type
|
|
|
730 |
* @throws invalid_parameter_exception if $param is not of given type
|
|
|
731 |
*/
|
|
|
732 |
function validate_param($param, $type, $allownull = NULL_NOT_ALLOWED, $debuginfo = '') {
|
|
|
733 |
return \core\param::from_type($type)->validate_param(
|
|
|
734 |
param: $param,
|
|
|
735 |
allownull: $allownull,
|
|
|
736 |
debuginfo: $debuginfo,
|
|
|
737 |
);
|
|
|
738 |
}
|
|
|
739 |
|
|
|
740 |
/**
|
|
|
741 |
* Makes sure array contains only the allowed types, this function does not validate array key names!
|
|
|
742 |
*
|
|
|
743 |
* <code>
|
|
|
744 |
* $options = clean_param($options, PARAM_INT);
|
|
|
745 |
* </code>
|
|
|
746 |
*
|
|
|
747 |
* @param array|null $param the variable array we are cleaning
|
|
|
748 |
* @param string $type expected format of param after cleaning.
|
|
|
749 |
* @param bool $recursive clean recursive arrays
|
|
|
750 |
* @return array
|
|
|
751 |
* @throws coding_exception
|
|
|
752 |
*/
|
|
|
753 |
function clean_param_array(?array $param, $type, $recursive = false) {
|
|
|
754 |
return \core\param::from_type($type)->clean_param_array(
|
|
|
755 |
param: $param,
|
|
|
756 |
recursive: $recursive,
|
|
|
757 |
);
|
|
|
758 |
}
|
|
|
759 |
|
|
|
760 |
/**
|
|
|
761 |
* Used by {@link optional_param()} and {@link required_param()} to
|
|
|
762 |
* clean the variables and/or cast to specific types, based on
|
|
|
763 |
* an options field.
|
|
|
764 |
* <code>
|
|
|
765 |
* $course->format = clean_param($course->format, PARAM_ALPHA);
|
|
|
766 |
* $selectedgradeitem = clean_param($selectedgradeitem, PARAM_INT);
|
|
|
767 |
* </code>
|
|
|
768 |
*
|
|
|
769 |
* @param mixed $param the variable we are cleaning
|
|
|
770 |
* @param string $type expected format of param after cleaning.
|
|
|
771 |
* @return mixed
|
|
|
772 |
* @throws coding_exception
|
|
|
773 |
*/
|
|
|
774 |
function clean_param($param, $type) {
|
|
|
775 |
return \core\param::from_type($type)->clean($param);
|
|
|
776 |
}
|
|
|
777 |
|
|
|
778 |
/**
|
|
|
779 |
* Whether the PARAM_* type is compatible in RTL.
|
|
|
780 |
*
|
|
|
781 |
* Being compatible with RTL means that the data they contain can flow
|
|
|
782 |
* from right-to-left or left-to-right without compromising the user experience.
|
|
|
783 |
*
|
|
|
784 |
* Take URLs for example, they are not RTL compatible as they should always
|
|
|
785 |
* flow from the left to the right. This also applies to numbers, email addresses,
|
|
|
786 |
* configuration snippets, base64 strings, etc...
|
|
|
787 |
*
|
|
|
788 |
* This function tries to best guess which parameters can contain localised strings.
|
|
|
789 |
*
|
|
|
790 |
* @param string $paramtype Constant PARAM_*.
|
|
|
791 |
* @return bool
|
|
|
792 |
*/
|
|
|
793 |
function is_rtl_compatible($paramtype) {
|
|
|
794 |
return $paramtype == PARAM_TEXT || $paramtype == PARAM_NOTAGS;
|
|
|
795 |
}
|
|
|
796 |
|
|
|
797 |
/**
|
|
|
798 |
* Makes sure the data is using valid utf8, invalid characters are discarded.
|
|
|
799 |
*
|
|
|
800 |
* Note: this function is not intended for full objects with methods and private properties.
|
|
|
801 |
*
|
|
|
802 |
* @param mixed $value
|
|
|
803 |
* @return mixed with proper utf-8 encoding
|
|
|
804 |
*/
|
|
|
805 |
function fix_utf8($value) {
|
|
|
806 |
if (is_null($value) or $value === '') {
|
|
|
807 |
return $value;
|
|
|
808 |
|
|
|
809 |
} else if (is_string($value)) {
|
|
|
810 |
if ((string)(int)$value === $value) {
|
|
|
811 |
// Shortcut.
|
|
|
812 |
return $value;
|
|
|
813 |
}
|
|
|
814 |
|
|
|
815 |
// Remove null bytes or invalid Unicode sequences from value.
|
|
|
816 |
$value = str_replace(["\0", "\xef\xbf\xbe", "\xef\xbf\xbf"], '', $value);
|
|
|
817 |
|
|
|
818 |
// Note: this duplicates min_fix_utf8() intentionally.
|
|
|
819 |
static $buggyiconv = null;
|
|
|
820 |
if ($buggyiconv === null) {
|
|
|
821 |
$buggyiconv = (!function_exists('iconv') or @iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
|
|
|
822 |
}
|
|
|
823 |
|
|
|
824 |
if ($buggyiconv) {
|
|
|
825 |
if (function_exists('mb_convert_encoding')) {
|
|
|
826 |
$subst = mb_substitute_character();
|
|
|
827 |
mb_substitute_character('none');
|
|
|
828 |
$result = mb_convert_encoding($value, 'utf-8', 'utf-8');
|
|
|
829 |
mb_substitute_character($subst);
|
|
|
830 |
|
|
|
831 |
} else {
|
|
|
832 |
// Warn admins on admin/index.php page.
|
|
|
833 |
$result = $value;
|
|
|
834 |
}
|
|
|
835 |
|
|
|
836 |
} else {
|
|
|
837 |
$result = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
|
|
|
838 |
}
|
|
|
839 |
|
|
|
840 |
return $result;
|
|
|
841 |
|
|
|
842 |
} else if (is_array($value)) {
|
|
|
843 |
foreach ($value as $k => $v) {
|
|
|
844 |
$value[$k] = fix_utf8($v);
|
|
|
845 |
}
|
|
|
846 |
return $value;
|
|
|
847 |
|
|
|
848 |
} else if (is_object($value)) {
|
|
|
849 |
// Do not modify original.
|
|
|
850 |
$value = clone($value);
|
|
|
851 |
foreach ($value as $k => $v) {
|
|
|
852 |
$value->$k = fix_utf8($v);
|
|
|
853 |
}
|
|
|
854 |
return $value;
|
|
|
855 |
|
|
|
856 |
} else {
|
|
|
857 |
// This is some other type, no utf-8 here.
|
|
|
858 |
return $value;
|
|
|
859 |
}
|
|
|
860 |
}
|
|
|
861 |
|
|
|
862 |
/**
|
|
|
863 |
* Return true if given value is integer or string with integer value
|
|
|
864 |
*
|
|
|
865 |
* @param mixed $value String or Int
|
|
|
866 |
* @return bool true if number, false if not
|
|
|
867 |
*/
|
|
|
868 |
function is_number($value) {
|
|
|
869 |
if (is_int($value)) {
|
|
|
870 |
return true;
|
|
|
871 |
} else if (is_string($value)) {
|
|
|
872 |
return ((string)(int)$value) === $value;
|
|
|
873 |
} else {
|
|
|
874 |
return false;
|
|
|
875 |
}
|
|
|
876 |
}
|
|
|
877 |
|
|
|
878 |
/**
|
|
|
879 |
* Returns host part from url.
|
|
|
880 |
*
|
|
|
881 |
* @param string $url full url
|
|
|
882 |
* @return string host, null if not found
|
|
|
883 |
*/
|
|
|
884 |
function get_host_from_url($url) {
|
|
|
885 |
preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
|
|
|
886 |
if ($matches) {
|
|
|
887 |
return $matches[1];
|
|
|
888 |
}
|
|
|
889 |
return null;
|
|
|
890 |
}
|
|
|
891 |
|
|
|
892 |
/**
|
|
|
893 |
* Tests whether anything was returned by text editor
|
|
|
894 |
*
|
|
|
895 |
* This function is useful for testing whether something you got back from
|
|
|
896 |
* the HTML editor actually contains anything. Sometimes the HTML editor
|
|
|
897 |
* appear to be empty, but actually you get back a <br> tag or something.
|
|
|
898 |
*
|
|
|
899 |
* @param string $string a string containing HTML.
|
|
|
900 |
* @return boolean does the string contain any actual content - that is text,
|
|
|
901 |
* images, objects, etc.
|
|
|
902 |
*/
|
|
|
903 |
function html_is_blank($string) {
|
|
|
904 |
return trim(strip_tags((string)$string, '<img><object><applet><input><select><textarea><hr>')) == '';
|
|
|
905 |
}
|
|
|
906 |
|
|
|
907 |
/**
|
|
|
908 |
* Set a key in global configuration
|
|
|
909 |
*
|
|
|
910 |
* Set a key/value pair in both this session's {@link $CFG} global variable
|
|
|
911 |
* and in the 'config' database table for future sessions.
|
|
|
912 |
*
|
|
|
913 |
* Can also be used to update keys for plugin-scoped configs in config_plugin table.
|
|
|
914 |
* In that case it doesn't affect $CFG.
|
|
|
915 |
*
|
|
|
916 |
* A NULL value will delete the entry.
|
|
|
917 |
*
|
|
|
918 |
* NOTE: this function is called from lib/db/upgrade.php
|
|
|
919 |
*
|
|
|
920 |
* @param string $name the key to set
|
|
|
921 |
* @param string|int|bool|null $value the value to set (without magic quotes),
|
|
|
922 |
* null to unset the value
|
|
|
923 |
* @param string $plugin (optional) the plugin scope, default null
|
|
|
924 |
* @return bool true or exception
|
|
|
925 |
*/
|
|
|
926 |
function set_config($name, $value, $plugin = null) {
|
|
|
927 |
global $CFG, $DB;
|
|
|
928 |
|
|
|
929 |
// Redirect to appropriate handler when value is null.
|
|
|
930 |
if ($value === null) {
|
|
|
931 |
return unset_config($name, $plugin);
|
|
|
932 |
}
|
|
|
933 |
|
|
|
934 |
// Set variables determining conditions and where to store the new config.
|
|
|
935 |
// Plugin config goes to {config_plugins}, core config goes to {config}.
|
|
|
936 |
$iscore = empty($plugin);
|
|
|
937 |
if ($iscore) {
|
|
|
938 |
// If it's for core config.
|
|
|
939 |
$table = 'config';
|
|
|
940 |
$conditions = ['name' => $name];
|
|
|
941 |
$invalidatecachekey = 'core';
|
|
|
942 |
} else {
|
|
|
943 |
// If it's a plugin.
|
|
|
944 |
$table = 'config_plugins';
|
|
|
945 |
$conditions = ['name' => $name, 'plugin' => $plugin];
|
|
|
946 |
$invalidatecachekey = $plugin;
|
|
|
947 |
}
|
|
|
948 |
|
|
|
949 |
// DB handling - checks for existing config, updating or inserting only if necessary.
|
|
|
950 |
$invalidatecache = true;
|
|
|
951 |
$inserted = false;
|
|
|
952 |
$record = $DB->get_record($table, $conditions, 'id, value');
|
|
|
953 |
if ($record === false) {
|
|
|
954 |
// Inserts a new config record.
|
|
|
955 |
$config = new stdClass();
|
|
|
956 |
$config->name = $name;
|
|
|
957 |
$config->value = $value;
|
|
|
958 |
if (!$iscore) {
|
|
|
959 |
$config->plugin = $plugin;
|
|
|
960 |
}
|
|
|
961 |
$inserted = $DB->insert_record($table, $config, false);
|
|
|
962 |
} else if ($invalidatecache = ($record->value !== $value)) {
|
|
|
963 |
// Record exists - Check and only set new value if it has changed.
|
|
|
964 |
$DB->set_field($table, 'value', $value, ['id' => $record->id]);
|
|
|
965 |
}
|
|
|
966 |
|
|
|
967 |
if ($iscore && !isset($CFG->config_php_settings[$name])) {
|
|
|
968 |
// So it's defined for this invocation at least.
|
|
|
969 |
// Settings from db are always strings.
|
|
|
970 |
$CFG->$name = (string) $value;
|
|
|
971 |
}
|
|
|
972 |
|
|
|
973 |
// When setting config during a Behat test (in the CLI script, not in the web browser
|
|
|
974 |
// requests), remember which ones are set so that we can clear them later.
|
|
|
975 |
if ($iscore && $inserted && defined('BEHAT_TEST')) {
|
|
|
976 |
$CFG->behat_cli_added_config[$name] = true;
|
|
|
977 |
}
|
|
|
978 |
|
|
|
979 |
// Update siteidentifier cache, if required.
|
|
|
980 |
if ($iscore && $name === 'siteidentifier') {
|
|
|
981 |
cache_helper::update_site_identifier($value);
|
|
|
982 |
}
|
|
|
983 |
|
|
|
984 |
// Invalidate cache, if required.
|
|
|
985 |
if ($invalidatecache) {
|
|
|
986 |
cache_helper::invalidate_by_definition('core', 'config', [], $invalidatecachekey);
|
|
|
987 |
}
|
|
|
988 |
|
|
|
989 |
return true;
|
|
|
990 |
}
|
|
|
991 |
|
|
|
992 |
/**
|
|
|
993 |
* Get configuration values from the global config table
|
|
|
994 |
* or the config_plugins table.
|
|
|
995 |
*
|
|
|
996 |
* If called with one parameter, it will load all the config
|
|
|
997 |
* variables for one plugin, and return them as an object.
|
|
|
998 |
*
|
|
|
999 |
* If called with 2 parameters it will return a string single
|
|
|
1000 |
* value or false if the value is not found.
|
|
|
1001 |
*
|
|
|
1002 |
* NOTE: this function is called from lib/db/upgrade.php
|
|
|
1003 |
*
|
|
|
1004 |
* @param string $plugin full component name
|
|
|
1005 |
* @param string $name default null
|
|
|
1006 |
* @return mixed hash-like object or single value, return false no config found
|
|
|
1007 |
* @throws dml_exception
|
|
|
1008 |
*/
|
|
|
1009 |
function get_config($plugin, $name = null) {
|
|
|
1010 |
global $CFG, $DB;
|
|
|
1011 |
|
|
|
1012 |
if ($plugin === 'moodle' || $plugin === 'core' || empty($plugin)) {
|
|
|
1013 |
$forced =& $CFG->config_php_settings;
|
|
|
1014 |
$iscore = true;
|
|
|
1015 |
$plugin = 'core';
|
|
|
1016 |
} else {
|
|
|
1017 |
if (array_key_exists($plugin, $CFG->forced_plugin_settings)) {
|
|
|
1018 |
$forced =& $CFG->forced_plugin_settings[$plugin];
|
|
|
1019 |
} else {
|
|
|
1020 |
$forced = array();
|
|
|
1021 |
}
|
|
|
1022 |
$iscore = false;
|
|
|
1023 |
}
|
|
|
1024 |
|
|
|
1025 |
if (!isset($CFG->siteidentifier)) {
|
|
|
1026 |
try {
|
|
|
1027 |
// This may throw an exception during installation, which is how we detect the
|
|
|
1028 |
// need to install the database. For more details see {@see initialise_cfg()}.
|
|
|
1029 |
$CFG->siteidentifier = $DB->get_field('config', 'value', array('name' => 'siteidentifier'));
|
|
|
1030 |
} catch (dml_exception $ex) {
|
|
|
1031 |
// Set siteidentifier to false. We don't want to trip this continually.
|
|
|
1032 |
$siteidentifier = false;
|
|
|
1033 |
throw $ex;
|
|
|
1034 |
}
|
|
|
1035 |
}
|
|
|
1036 |
|
|
|
1037 |
if (!empty($name)) {
|
|
|
1038 |
if (array_key_exists($name, $forced)) {
|
|
|
1039 |
return (string)$forced[$name];
|
|
|
1040 |
} else if ($name === 'siteidentifier' && $plugin == 'core') {
|
|
|
1041 |
return $CFG->siteidentifier;
|
|
|
1042 |
}
|
|
|
1043 |
}
|
|
|
1044 |
|
|
|
1045 |
$cache = cache::make('core', 'config');
|
|
|
1046 |
$result = $cache->get($plugin);
|
|
|
1047 |
if ($result === false) {
|
|
|
1048 |
// The user is after a recordset.
|
|
|
1049 |
if (!$iscore) {
|
|
|
1050 |
$result = $DB->get_records_menu('config_plugins', array('plugin' => $plugin), '', 'name,value');
|
|
|
1051 |
} else {
|
|
|
1052 |
// This part is not really used any more, but anyway...
|
|
|
1053 |
$result = $DB->get_records_menu('config', array(), '', 'name,value');;
|
|
|
1054 |
}
|
|
|
1055 |
$cache->set($plugin, $result);
|
|
|
1056 |
}
|
|
|
1057 |
|
|
|
1058 |
if (!empty($name)) {
|
|
|
1059 |
if (array_key_exists($name, $result)) {
|
|
|
1060 |
return $result[$name];
|
|
|
1061 |
}
|
|
|
1062 |
return false;
|
|
|
1063 |
}
|
|
|
1064 |
|
|
|
1065 |
if ($plugin === 'core') {
|
|
|
1066 |
$result['siteidentifier'] = $CFG->siteidentifier;
|
|
|
1067 |
}
|
|
|
1068 |
|
|
|
1069 |
foreach ($forced as $key => $value) {
|
|
|
1070 |
if (is_null($value) or is_array($value) or is_object($value)) {
|
|
|
1071 |
// We do not want any extra mess here, just real settings that could be saved in db.
|
|
|
1072 |
unset($result[$key]);
|
|
|
1073 |
} else {
|
|
|
1074 |
// Convert to string as if it went through the DB.
|
|
|
1075 |
$result[$key] = (string)$value;
|
|
|
1076 |
}
|
|
|
1077 |
}
|
|
|
1078 |
|
|
|
1079 |
return (object)$result;
|
|
|
1080 |
}
|
|
|
1081 |
|
|
|
1082 |
/**
|
|
|
1083 |
* Removes a key from global configuration.
|
|
|
1084 |
*
|
|
|
1085 |
* NOTE: this function is called from lib/db/upgrade.php
|
|
|
1086 |
*
|
|
|
1087 |
* @param string $name the key to set
|
|
|
1088 |
* @param string $plugin (optional) the plugin scope
|
|
|
1089 |
* @return boolean whether the operation succeeded.
|
|
|
1090 |
*/
|
|
|
1091 |
function unset_config($name, $plugin=null) {
|
|
|
1092 |
global $CFG, $DB;
|
|
|
1093 |
|
|
|
1094 |
if (empty($plugin)) {
|
|
|
1095 |
unset($CFG->$name);
|
|
|
1096 |
$DB->delete_records('config', array('name' => $name));
|
|
|
1097 |
cache_helper::invalidate_by_definition('core', 'config', array(), 'core');
|
|
|
1098 |
} else {
|
|
|
1099 |
$DB->delete_records('config_plugins', array('name' => $name, 'plugin' => $plugin));
|
|
|
1100 |
cache_helper::invalidate_by_definition('core', 'config', array(), $plugin);
|
|
|
1101 |
}
|
|
|
1102 |
|
|
|
1103 |
return true;
|
|
|
1104 |
}
|
|
|
1105 |
|
|
|
1106 |
/**
|
|
|
1107 |
* Remove all the config variables for a given plugin.
|
|
|
1108 |
*
|
|
|
1109 |
* NOTE: this function is called from lib/db/upgrade.php
|
|
|
1110 |
*
|
|
|
1111 |
* @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
|
|
|
1112 |
* @return boolean whether the operation succeeded.
|
|
|
1113 |
*/
|
|
|
1114 |
function unset_all_config_for_plugin($plugin) {
|
|
|
1115 |
global $DB;
|
|
|
1116 |
// Delete from the obvious config_plugins first.
|
|
|
1117 |
$DB->delete_records('config_plugins', array('plugin' => $plugin));
|
|
|
1118 |
// Next delete any suspect settings from config.
|
|
|
1119 |
$like = $DB->sql_like('name', '?', true, true, false, '|');
|
|
|
1120 |
$params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
|
|
|
1121 |
$DB->delete_records_select('config', $like, $params);
|
|
|
1122 |
// Finally clear both the plugin cache and the core cache (suspect settings now removed from core).
|
|
|
1123 |
cache_helper::invalidate_by_definition('core', 'config', array(), array('core', $plugin));
|
|
|
1124 |
|
|
|
1125 |
return true;
|
|
|
1126 |
}
|
|
|
1127 |
|
|
|
1128 |
/**
|
|
|
1129 |
* Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
|
|
|
1130 |
*
|
|
|
1131 |
* All users are verified if they still have the necessary capability.
|
|
|
1132 |
*
|
|
|
1133 |
* @param string $value the value of the config setting.
|
|
|
1134 |
* @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
|
|
|
1135 |
* @param bool $includeadmins include administrators.
|
|
|
1136 |
* @return array of user objects.
|
|
|
1137 |
*/
|
|
|
1138 |
function get_users_from_config($value, $capability, $includeadmins = true) {
|
|
|
1139 |
if (empty($value) or $value === '$@NONE@$') {
|
|
|
1140 |
return array();
|
|
|
1141 |
}
|
|
|
1142 |
|
|
|
1143 |
// We have to make sure that users still have the necessary capability,
|
|
|
1144 |
// it should be faster to fetch them all first and then test if they are present
|
|
|
1145 |
// instead of validating them one-by-one.
|
|
|
1146 |
$users = get_users_by_capability(context_system::instance(), $capability);
|
|
|
1147 |
if ($includeadmins) {
|
|
|
1148 |
$admins = get_admins();
|
|
|
1149 |
foreach ($admins as $admin) {
|
|
|
1150 |
$users[$admin->id] = $admin;
|
|
|
1151 |
}
|
|
|
1152 |
}
|
|
|
1153 |
|
|
|
1154 |
if ($value === '$@ALL@$') {
|
|
|
1155 |
return $users;
|
|
|
1156 |
}
|
|
|
1157 |
|
|
|
1158 |
$result = array(); // Result in correct order.
|
|
|
1159 |
$allowed = explode(',', $value);
|
|
|
1160 |
foreach ($allowed as $uid) {
|
|
|
1161 |
if (isset($users[$uid])) {
|
|
|
1162 |
$user = $users[$uid];
|
|
|
1163 |
$result[$user->id] = $user;
|
|
|
1164 |
}
|
|
|
1165 |
}
|
|
|
1166 |
|
|
|
1167 |
return $result;
|
|
|
1168 |
}
|
|
|
1169 |
|
|
|
1170 |
|
|
|
1171 |
/**
|
|
|
1172 |
* Invalidates browser caches and cached data in temp.
|
|
|
1173 |
*
|
|
|
1174 |
* @return void
|
|
|
1175 |
*/
|
|
|
1176 |
function purge_all_caches() {
|
|
|
1177 |
purge_caches();
|
|
|
1178 |
}
|
|
|
1179 |
|
|
|
1180 |
/**
|
|
|
1181 |
* Selectively invalidate different types of cache.
|
|
|
1182 |
*
|
|
|
1183 |
* Purges the cache areas specified. By default, this will purge all caches but can selectively purge specific
|
|
|
1184 |
* areas alone or in combination.
|
|
|
1185 |
*
|
|
|
1186 |
* @param bool[] $options Specific parts of the cache to purge. Valid options are:
|
|
|
1187 |
* 'muc' Purge MUC caches?
|
11 |
efrain |
1188 |
* 'courses' Purge all course caches, or specific course caches (CLI only)
|
1 |
efrain |
1189 |
* 'theme' Purge theme cache?
|
|
|
1190 |
* 'lang' Purge language string cache?
|
|
|
1191 |
* 'js' Purge javascript cache?
|
|
|
1192 |
* 'filter' Purge text filter cache?
|
|
|
1193 |
* 'other' Purge all other caches?
|
|
|
1194 |
*/
|
|
|
1195 |
function purge_caches($options = []) {
|
|
|
1196 |
$defaults = array_fill_keys(['muc', 'courses', 'theme', 'lang', 'js', 'template', 'filter', 'other'], false);
|
|
|
1197 |
if (empty(array_filter($options))) {
|
|
|
1198 |
$options = array_fill_keys(array_keys($defaults), true); // Set all options to true.
|
|
|
1199 |
} else {
|
|
|
1200 |
$options = array_merge($defaults, array_intersect_key($options, $defaults)); // Override defaults with specified options.
|
|
|
1201 |
}
|
|
|
1202 |
if ($options['muc']) {
|
|
|
1203 |
cache_helper::purge_all();
|
11 |
efrain |
1204 |
} else if ($options['courses']) {
|
1 |
efrain |
1205 |
if ($options['courses'] === true) {
|
|
|
1206 |
$courseids = [];
|
|
|
1207 |
} else {
|
|
|
1208 |
$courseids = preg_split('/\s*,\s*/', $options['courses'], -1, PREG_SPLIT_NO_EMPTY);
|
|
|
1209 |
}
|
|
|
1210 |
course_modinfo::purge_course_caches($courseids);
|
|
|
1211 |
}
|
|
|
1212 |
if ($options['theme']) {
|
|
|
1213 |
theme_reset_all_caches();
|
|
|
1214 |
}
|
|
|
1215 |
if ($options['lang']) {
|
|
|
1216 |
get_string_manager()->reset_caches();
|
|
|
1217 |
}
|
|
|
1218 |
if ($options['js']) {
|
|
|
1219 |
js_reset_all_caches();
|
|
|
1220 |
}
|
|
|
1221 |
if ($options['template']) {
|
|
|
1222 |
template_reset_all_caches();
|
|
|
1223 |
}
|
|
|
1224 |
if ($options['filter']) {
|
|
|
1225 |
reset_text_filters_cache();
|
|
|
1226 |
}
|
|
|
1227 |
if ($options['other']) {
|
|
|
1228 |
purge_other_caches();
|
|
|
1229 |
}
|
|
|
1230 |
}
|
|
|
1231 |
|
|
|
1232 |
/**
|
|
|
1233 |
* Purge all non-MUC caches not otherwise purged in purge_caches.
|
|
|
1234 |
*
|
|
|
1235 |
* IMPORTANT - If you are adding anything here to do with the cache directory you should also have a look at
|
|
|
1236 |
* {@link phpunit_util::reset_dataroot()}
|
|
|
1237 |
*/
|
|
|
1238 |
function purge_other_caches() {
|
|
|
1239 |
global $DB, $CFG;
|
|
|
1240 |
if (class_exists('core_plugin_manager')) {
|
|
|
1241 |
core_plugin_manager::reset_caches();
|
|
|
1242 |
}
|
|
|
1243 |
|
|
|
1244 |
// Bump up cacherev field for all courses.
|
|
|
1245 |
try {
|
|
|
1246 |
increment_revision_number('course', 'cacherev', '');
|
|
|
1247 |
} catch (moodle_exception $e) {
|
|
|
1248 |
// Ignore exception since this function is also called before upgrade script when field course.cacherev does not exist yet.
|
|
|
1249 |
}
|
|
|
1250 |
|
|
|
1251 |
$DB->reset_caches();
|
|
|
1252 |
|
|
|
1253 |
// Purge all other caches: rss, simplepie, etc.
|
|
|
1254 |
clearstatcache();
|
|
|
1255 |
remove_dir($CFG->cachedir.'', true);
|
|
|
1256 |
|
|
|
1257 |
// Make sure cache dir is writable, throws exception if not.
|
|
|
1258 |
make_cache_directory('');
|
|
|
1259 |
|
|
|
1260 |
// This is the only place where we purge local caches, we are only adding files there.
|
|
|
1261 |
// The $CFG->localcachedirpurged flag forces local directories to be purged on cluster nodes.
|
|
|
1262 |
remove_dir($CFG->localcachedir, true);
|
|
|
1263 |
set_config('localcachedirpurged', time());
|
|
|
1264 |
make_localcache_directory('', true);
|
11 |
efrain |
1265 |
|
|
|
1266 |
// Rewarm the bootstrap.php files so the siteid is always present after a purge.
|
|
|
1267 |
initialise_local_config_cache();
|
1 |
efrain |
1268 |
\core\task\manager::clear_static_caches();
|
|
|
1269 |
}
|
|
|
1270 |
|
|
|
1271 |
/**
|
|
|
1272 |
* Get volatile flags
|
|
|
1273 |
*
|
|
|
1274 |
* @param string $type
|
|
|
1275 |
* @param int $changedsince default null
|
|
|
1276 |
* @return array records array
|
|
|
1277 |
*/
|
|
|
1278 |
function get_cache_flags($type, $changedsince = null) {
|
|
|
1279 |
global $DB;
|
|
|
1280 |
|
|
|
1281 |
$params = array('type' => $type, 'expiry' => time());
|
|
|
1282 |
$sqlwhere = "flagtype = :type AND expiry >= :expiry";
|
|
|
1283 |
if ($changedsince !== null) {
|
|
|
1284 |
$params['changedsince'] = $changedsince;
|
|
|
1285 |
$sqlwhere .= " AND timemodified > :changedsince";
|
|
|
1286 |
}
|
|
|
1287 |
$cf = array();
|
|
|
1288 |
if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
|
|
|
1289 |
foreach ($flags as $flag) {
|
|
|
1290 |
$cf[$flag->name] = $flag->value;
|
|
|
1291 |
}
|
|
|
1292 |
}
|
|
|
1293 |
return $cf;
|
|
|
1294 |
}
|
|
|
1295 |
|
|
|
1296 |
/**
|
|
|
1297 |
* Get volatile flags
|
|
|
1298 |
*
|
|
|
1299 |
* @param string $type
|
|
|
1300 |
* @param string $name
|
|
|
1301 |
* @param int $changedsince default null
|
|
|
1302 |
* @return string|false The cache flag value or false
|
|
|
1303 |
*/
|
|
|
1304 |
function get_cache_flag($type, $name, $changedsince=null) {
|
|
|
1305 |
global $DB;
|
|
|
1306 |
|
|
|
1307 |
$params = array('type' => $type, 'name' => $name, 'expiry' => time());
|
|
|
1308 |
|
|
|
1309 |
$sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
|
|
|
1310 |
if ($changedsince !== null) {
|
|
|
1311 |
$params['changedsince'] = $changedsince;
|
|
|
1312 |
$sqlwhere .= " AND timemodified > :changedsince";
|
|
|
1313 |
}
|
|
|
1314 |
|
|
|
1315 |
return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
|
|
|
1316 |
}
|
|
|
1317 |
|
|
|
1318 |
/**
|
|
|
1319 |
* Set a volatile flag
|
|
|
1320 |
*
|
|
|
1321 |
* @param string $type the "type" namespace for the key
|
|
|
1322 |
* @param string $name the key to set
|
|
|
1323 |
* @param string $value the value to set (without magic quotes) - null will remove the flag
|
|
|
1324 |
* @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
|
|
|
1325 |
* @return bool Always returns true
|
|
|
1326 |
*/
|
|
|
1327 |
function set_cache_flag($type, $name, $value, $expiry = null) {
|
|
|
1328 |
global $DB;
|
|
|
1329 |
|
|
|
1330 |
$timemodified = time();
|
|
|
1331 |
if ($expiry === null || $expiry < $timemodified) {
|
|
|
1332 |
$expiry = $timemodified + 24 * 60 * 60;
|
|
|
1333 |
} else {
|
|
|
1334 |
$expiry = (int)$expiry;
|
|
|
1335 |
}
|
|
|
1336 |
|
|
|
1337 |
if ($value === null) {
|
|
|
1338 |
unset_cache_flag($type, $name);
|
|
|
1339 |
return true;
|
|
|
1340 |
}
|
|
|
1341 |
|
|
|
1342 |
if ($f = $DB->get_record('cache_flags', array('name' => $name, 'flagtype' => $type), '*', IGNORE_MULTIPLE)) {
|
|
|
1343 |
// This is a potential problem in DEBUG_DEVELOPER.
|
|
|
1344 |
if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
|
|
|
1345 |
return true; // No need to update.
|
|
|
1346 |
}
|
|
|
1347 |
$f->value = $value;
|
|
|
1348 |
$f->expiry = $expiry;
|
|
|
1349 |
$f->timemodified = $timemodified;
|
|
|
1350 |
$DB->update_record('cache_flags', $f);
|
|
|
1351 |
} else {
|
|
|
1352 |
$f = new stdClass();
|
|
|
1353 |
$f->flagtype = $type;
|
|
|
1354 |
$f->name = $name;
|
|
|
1355 |
$f->value = $value;
|
|
|
1356 |
$f->expiry = $expiry;
|
|
|
1357 |
$f->timemodified = $timemodified;
|
|
|
1358 |
$DB->insert_record('cache_flags', $f);
|
|
|
1359 |
}
|
|
|
1360 |
return true;
|
|
|
1361 |
}
|
|
|
1362 |
|
|
|
1363 |
/**
|
|
|
1364 |
* Removes a single volatile flag
|
|
|
1365 |
*
|
|
|
1366 |
* @param string $type the "type" namespace for the key
|
|
|
1367 |
* @param string $name the key to set
|
|
|
1368 |
* @return bool
|
|
|
1369 |
*/
|
|
|
1370 |
function unset_cache_flag($type, $name) {
|
|
|
1371 |
global $DB;
|
|
|
1372 |
$DB->delete_records('cache_flags', array('name' => $name, 'flagtype' => $type));
|
|
|
1373 |
return true;
|
|
|
1374 |
}
|
|
|
1375 |
|
|
|
1376 |
/**
|
|
|
1377 |
* Garbage-collect volatile flags
|
|
|
1378 |
*
|
|
|
1379 |
* @return bool Always returns true
|
|
|
1380 |
*/
|
|
|
1381 |
function gc_cache_flags() {
|
|
|
1382 |
global $DB;
|
|
|
1383 |
$DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
|
|
|
1384 |
return true;
|
|
|
1385 |
}
|
|
|
1386 |
|
|
|
1387 |
// USER PREFERENCE API.
|
|
|
1388 |
|
|
|
1389 |
/**
|
|
|
1390 |
* Refresh user preference cache. This is used most often for $USER
|
|
|
1391 |
* object that is stored in session, but it also helps with performance in cron script.
|
|
|
1392 |
*
|
|
|
1393 |
* Preferences for each user are loaded on first use on every page, then again after the timeout expires.
|
|
|
1394 |
*
|
|
|
1395 |
* @package core
|
|
|
1396 |
* @category preference
|
|
|
1397 |
* @access public
|
|
|
1398 |
* @param stdClass $user User object. Preferences are preloaded into 'preference' property
|
|
|
1399 |
* @param int $cachelifetime Cache life time on the current page (in seconds)
|
|
|
1400 |
* @throws coding_exception
|
|
|
1401 |
* @return null
|
|
|
1402 |
*/
|
|
|
1403 |
function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
|
|
|
1404 |
global $DB;
|
|
|
1405 |
// Static cache, we need to check on each page load, not only every 2 minutes.
|
|
|
1406 |
static $loadedusers = array();
|
|
|
1407 |
|
|
|
1408 |
if (!isset($user->id)) {
|
|
|
1409 |
throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
|
|
|
1410 |
}
|
|
|
1411 |
|
|
|
1412 |
if (empty($user->id) or isguestuser($user->id)) {
|
|
|
1413 |
// No permanent storage for not-logged-in users and guest.
|
|
|
1414 |
if (!isset($user->preference)) {
|
|
|
1415 |
$user->preference = array();
|
|
|
1416 |
}
|
|
|
1417 |
return;
|
|
|
1418 |
}
|
|
|
1419 |
|
|
|
1420 |
$timenow = time();
|
|
|
1421 |
|
|
|
1422 |
if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
|
|
|
1423 |
// Already loaded at least once on this page. Are we up to date?
|
|
|
1424 |
if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
|
|
|
1425 |
// No need to reload - we are on the same page and we loaded prefs just a moment ago.
|
|
|
1426 |
return;
|
|
|
1427 |
|
|
|
1428 |
} else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
|
|
|
1429 |
// No change since the lastcheck on this page.
|
|
|
1430 |
$user->preference['_lastloaded'] = $timenow;
|
|
|
1431 |
return;
|
|
|
1432 |
}
|
|
|
1433 |
}
|
|
|
1434 |
|
|
|
1435 |
// OK, so we have to reload all preferences.
|
|
|
1436 |
$loadedusers[$user->id] = true;
|
|
|
1437 |
$user->preference = $DB->get_records_menu('user_preferences', array('userid' => $user->id), '', 'name,value'); // All values.
|
|
|
1438 |
$user->preference['_lastloaded'] = $timenow;
|
|
|
1439 |
}
|
|
|
1440 |
|
|
|
1441 |
/**
|
|
|
1442 |
* Called from set/unset_user_preferences, so that the prefs can be correctly reloaded in different sessions.
|
|
|
1443 |
*
|
|
|
1444 |
* NOTE: internal function, do not call from other code.
|
|
|
1445 |
*
|
|
|
1446 |
* @package core
|
|
|
1447 |
* @access private
|
|
|
1448 |
* @param integer $userid the user whose prefs were changed.
|
|
|
1449 |
*/
|
|
|
1450 |
function mark_user_preferences_changed($userid) {
|
|
|
1451 |
global $CFG;
|
|
|
1452 |
|
|
|
1453 |
if (empty($userid) or isguestuser($userid)) {
|
|
|
1454 |
// No cache flags for guest and not-logged-in users.
|
|
|
1455 |
return;
|
|
|
1456 |
}
|
|
|
1457 |
|
|
|
1458 |
set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
|
|
|
1459 |
}
|
|
|
1460 |
|
|
|
1461 |
/**
|
|
|
1462 |
* Sets a preference for the specified user.
|
|
|
1463 |
*
|
|
|
1464 |
* If a $user object is submitted it's 'preference' property is used for the preferences cache.
|
|
|
1465 |
*
|
|
|
1466 |
* When additional validation/permission check is needed it is better to use {@see useredit_update_user_preference()}
|
|
|
1467 |
*
|
|
|
1468 |
* @package core
|
|
|
1469 |
* @category preference
|
|
|
1470 |
* @access public
|
|
|
1471 |
* @param string $name The key to set as preference for the specified user
|
|
|
1472 |
* @param string|int|bool|null $value The value to set for the $name key in the specified user's
|
|
|
1473 |
* record, null means delete current value.
|
|
|
1474 |
* @param stdClass|int|null $user A moodle user object or id, null means current user
|
|
|
1475 |
* @throws coding_exception
|
|
|
1476 |
* @return bool Always true or exception
|
|
|
1477 |
*/
|
|
|
1478 |
function set_user_preference($name, $value, $user = null) {
|
|
|
1479 |
global $USER, $DB;
|
|
|
1480 |
|
|
|
1481 |
if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
|
|
|
1482 |
throw new coding_exception('Invalid preference name in set_user_preference() call');
|
|
|
1483 |
}
|
|
|
1484 |
|
|
|
1485 |
if (is_null($value)) {
|
|
|
1486 |
// Null means delete current.
|
|
|
1487 |
return unset_user_preference($name, $user);
|
|
|
1488 |
} else if (is_object($value)) {
|
|
|
1489 |
throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
|
|
|
1490 |
} else if (is_array($value)) {
|
|
|
1491 |
throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
|
|
|
1492 |
}
|
|
|
1493 |
// Value column maximum length is 1333 characters.
|
|
|
1494 |
$value = (string)$value;
|
|
|
1495 |
if (core_text::strlen($value) > 1333) {
|
|
|
1496 |
throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
|
|
|
1497 |
}
|
|
|
1498 |
|
|
|
1499 |
if (is_null($user)) {
|
|
|
1500 |
$user = $USER;
|
|
|
1501 |
} else if (isset($user->id)) {
|
|
|
1502 |
// It is a valid object.
|
|
|
1503 |
} else if (is_numeric($user)) {
|
|
|
1504 |
$user = (object)array('id' => (int)$user);
|
|
|
1505 |
} else {
|
|
|
1506 |
throw new coding_exception('Invalid $user parameter in set_user_preference() call');
|
|
|
1507 |
}
|
|
|
1508 |
|
|
|
1509 |
check_user_preferences_loaded($user);
|
|
|
1510 |
|
|
|
1511 |
if (empty($user->id) or isguestuser($user->id)) {
|
|
|
1512 |
// No permanent storage for not-logged-in users and guest.
|
|
|
1513 |
$user->preference[$name] = $value;
|
|
|
1514 |
return true;
|
|
|
1515 |
}
|
|
|
1516 |
|
|
|
1517 |
if ($preference = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => $name))) {
|
|
|
1518 |
if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
|
|
|
1519 |
// Preference already set to this value.
|
|
|
1520 |
return true;
|
|
|
1521 |
}
|
|
|
1522 |
$DB->set_field('user_preferences', 'value', $value, array('id' => $preference->id));
|
|
|
1523 |
|
|
|
1524 |
} else {
|
|
|
1525 |
$preference = new stdClass();
|
|
|
1526 |
$preference->userid = $user->id;
|
|
|
1527 |
$preference->name = $name;
|
|
|
1528 |
$preference->value = $value;
|
|
|
1529 |
$DB->insert_record('user_preferences', $preference);
|
|
|
1530 |
}
|
|
|
1531 |
|
|
|
1532 |
// Update value in cache.
|
|
|
1533 |
$user->preference[$name] = $value;
|
|
|
1534 |
// Update the $USER in case where we've not a direct reference to $USER.
|
|
|
1535 |
if ($user !== $USER && $user->id == $USER->id) {
|
|
|
1536 |
$USER->preference[$name] = $value;
|
|
|
1537 |
}
|
|
|
1538 |
|
|
|
1539 |
// Set reload flag for other sessions.
|
|
|
1540 |
mark_user_preferences_changed($user->id);
|
|
|
1541 |
|
|
|
1542 |
return true;
|
|
|
1543 |
}
|
|
|
1544 |
|
|
|
1545 |
/**
|
|
|
1546 |
* Sets a whole array of preferences for the current user
|
|
|
1547 |
*
|
|
|
1548 |
* If a $user object is submitted it's 'preference' property is used for the preferences cache.
|
|
|
1549 |
*
|
|
|
1550 |
* @package core
|
|
|
1551 |
* @category preference
|
|
|
1552 |
* @access public
|
|
|
1553 |
* @param array $prefarray An array of key/value pairs to be set
|
|
|
1554 |
* @param stdClass|int|null $user A moodle user object or id, null means current user
|
|
|
1555 |
* @return bool Always true or exception
|
|
|
1556 |
*/
|
|
|
1557 |
function set_user_preferences(array $prefarray, $user = null) {
|
|
|
1558 |
foreach ($prefarray as $name => $value) {
|
|
|
1559 |
set_user_preference($name, $value, $user);
|
|
|
1560 |
}
|
|
|
1561 |
return true;
|
|
|
1562 |
}
|
|
|
1563 |
|
|
|
1564 |
/**
|
|
|
1565 |
* Unsets a preference completely by deleting it from the database
|
|
|
1566 |
*
|
|
|
1567 |
* If a $user object is submitted it's 'preference' property is used for the preferences cache.
|
|
|
1568 |
*
|
|
|
1569 |
* @package core
|
|
|
1570 |
* @category preference
|
|
|
1571 |
* @access public
|
|
|
1572 |
* @param string $name The key to unset as preference for the specified user
|
|
|
1573 |
* @param stdClass|int|null $user A moodle user object or id, null means current user
|
|
|
1574 |
* @throws coding_exception
|
|
|
1575 |
* @return bool Always true or exception
|
|
|
1576 |
*/
|
|
|
1577 |
function unset_user_preference($name, $user = null) {
|
|
|
1578 |
global $USER, $DB;
|
|
|
1579 |
|
|
|
1580 |
if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
|
|
|
1581 |
throw new coding_exception('Invalid preference name in unset_user_preference() call');
|
|
|
1582 |
}
|
|
|
1583 |
|
|
|
1584 |
if (is_null($user)) {
|
|
|
1585 |
$user = $USER;
|
|
|
1586 |
} else if (isset($user->id)) {
|
|
|
1587 |
// It is a valid object.
|
|
|
1588 |
} else if (is_numeric($user)) {
|
|
|
1589 |
$user = (object)array('id' => (int)$user);
|
|
|
1590 |
} else {
|
|
|
1591 |
throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
|
|
|
1592 |
}
|
|
|
1593 |
|
|
|
1594 |
check_user_preferences_loaded($user);
|
|
|
1595 |
|
|
|
1596 |
if (empty($user->id) or isguestuser($user->id)) {
|
|
|
1597 |
// No permanent storage for not-logged-in user and guest.
|
|
|
1598 |
unset($user->preference[$name]);
|
|
|
1599 |
return true;
|
|
|
1600 |
}
|
|
|
1601 |
|
|
|
1602 |
// Delete from DB.
|
|
|
1603 |
$DB->delete_records('user_preferences', array('userid' => $user->id, 'name' => $name));
|
|
|
1604 |
|
|
|
1605 |
// Delete the preference from cache.
|
|
|
1606 |
unset($user->preference[$name]);
|
|
|
1607 |
// Update the $USER in case where we've not a direct reference to $USER.
|
|
|
1608 |
if ($user !== $USER && $user->id == $USER->id) {
|
|
|
1609 |
unset($USER->preference[$name]);
|
|
|
1610 |
}
|
|
|
1611 |
|
|
|
1612 |
// Set reload flag for other sessions.
|
|
|
1613 |
mark_user_preferences_changed($user->id);
|
|
|
1614 |
|
|
|
1615 |
return true;
|
|
|
1616 |
}
|
|
|
1617 |
|
|
|
1618 |
/**
|
|
|
1619 |
* Used to fetch user preference(s)
|
|
|
1620 |
*
|
|
|
1621 |
* If no arguments are supplied this function will return
|
|
|
1622 |
* all of the current user preferences as an array.
|
|
|
1623 |
*
|
|
|
1624 |
* If a name is specified then this function
|
|
|
1625 |
* attempts to return that particular preference value. If
|
|
|
1626 |
* none is found, then the optional value $default is returned,
|
|
|
1627 |
* otherwise null.
|
|
|
1628 |
*
|
|
|
1629 |
* If a $user object is submitted it's 'preference' property is used for the preferences cache.
|
|
|
1630 |
*
|
|
|
1631 |
* @package core
|
|
|
1632 |
* @category preference
|
|
|
1633 |
* @access public
|
|
|
1634 |
* @param string $name Name of the key to use in finding a preference value
|
|
|
1635 |
* @param mixed|null $default Value to be returned if the $name key is not set in the user preferences
|
|
|
1636 |
* @param stdClass|int|null $user A moodle user object or id, null means current user
|
|
|
1637 |
* @throws coding_exception
|
|
|
1638 |
* @return string|mixed|null A string containing the value of a single preference. An
|
|
|
1639 |
* array with all of the preferences or null
|
|
|
1640 |
*/
|
|
|
1641 |
function get_user_preferences($name = null, $default = null, $user = null) {
|
|
|
1642 |
global $USER;
|
|
|
1643 |
|
|
|
1644 |
if (is_null($name)) {
|
|
|
1645 |
// All prefs.
|
|
|
1646 |
} else if (is_numeric($name) or $name === '_lastloaded') {
|
|
|
1647 |
throw new coding_exception('Invalid preference name in get_user_preferences() call');
|
|
|
1648 |
}
|
|
|
1649 |
|
|
|
1650 |
if (is_null($user)) {
|
|
|
1651 |
$user = $USER;
|
|
|
1652 |
} else if (isset($user->id)) {
|
|
|
1653 |
// Is a valid object.
|
|
|
1654 |
} else if (is_numeric($user)) {
|
|
|
1655 |
if ($USER->id == $user) {
|
|
|
1656 |
$user = $USER;
|
|
|
1657 |
} else {
|
|
|
1658 |
$user = (object)array('id' => (int)$user);
|
|
|
1659 |
}
|
|
|
1660 |
} else {
|
|
|
1661 |
throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
|
|
|
1662 |
}
|
|
|
1663 |
|
|
|
1664 |
check_user_preferences_loaded($user);
|
|
|
1665 |
|
|
|
1666 |
if (empty($name)) {
|
|
|
1667 |
// All values.
|
|
|
1668 |
return $user->preference;
|
|
|
1669 |
} else if (isset($user->preference[$name])) {
|
|
|
1670 |
// The single string value.
|
|
|
1671 |
return $user->preference[$name];
|
|
|
1672 |
} else {
|
|
|
1673 |
// Default value (null if not specified).
|
|
|
1674 |
return $default;
|
|
|
1675 |
}
|
|
|
1676 |
}
|
|
|
1677 |
|
|
|
1678 |
// FUNCTIONS FOR HANDLING TIME.
|
|
|
1679 |
|
|
|
1680 |
/**
|
|
|
1681 |
* Given Gregorian date parts in user time produce a GMT timestamp.
|
|
|
1682 |
*
|
|
|
1683 |
* @package core
|
|
|
1684 |
* @category time
|
|
|
1685 |
* @param int $year The year part to create timestamp of
|
|
|
1686 |
* @param int $month The month part to create timestamp of
|
|
|
1687 |
* @param int $day The day part to create timestamp of
|
|
|
1688 |
* @param int $hour The hour part to create timestamp of
|
|
|
1689 |
* @param int $minute The minute part to create timestamp of
|
|
|
1690 |
* @param int $second The second part to create timestamp of
|
|
|
1691 |
* @param int|float|string $timezone Timezone modifier, used to calculate GMT time offset.
|
|
|
1692 |
* if 99 then default user's timezone is used {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
|
|
1693 |
* @param bool $applydst Toggle Daylight Saving Time, default true, will be
|
|
|
1694 |
* applied only if timezone is 99 or string.
|
|
|
1695 |
* @return int GMT timestamp
|
|
|
1696 |
*/
|
|
|
1697 |
function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
|
|
|
1698 |
$date = new DateTime('now', core_date::get_user_timezone_object($timezone));
|
|
|
1699 |
$date->setDate((int)$year, (int)$month, (int)$day);
|
|
|
1700 |
$date->setTime((int)$hour, (int)$minute, (int)$second);
|
|
|
1701 |
|
|
|
1702 |
$time = $date->getTimestamp();
|
|
|
1703 |
|
|
|
1704 |
if ($time === false) {
|
|
|
1705 |
throw new coding_exception('getTimestamp() returned false, please ensure you have passed correct values.'.
|
|
|
1706 |
' This can fail if year is more than 2038 and OS is 32 bit windows');
|
|
|
1707 |
}
|
|
|
1708 |
|
|
|
1709 |
// Moodle BC DST stuff.
|
|
|
1710 |
if (!$applydst) {
|
|
|
1711 |
$time += dst_offset_on($time, $timezone);
|
|
|
1712 |
}
|
|
|
1713 |
|
|
|
1714 |
return $time;
|
|
|
1715 |
|
|
|
1716 |
}
|
|
|
1717 |
|
|
|
1718 |
/**
|
|
|
1719 |
* Format a date/time (seconds) as weeks, days, hours etc as needed
|
|
|
1720 |
*
|
|
|
1721 |
* Given an amount of time in seconds, returns string
|
|
|
1722 |
* formatted nicely as years, days, hours etc as needed
|
|
|
1723 |
*
|
|
|
1724 |
* @package core
|
|
|
1725 |
* @category time
|
|
|
1726 |
* @uses MINSECS
|
|
|
1727 |
* @uses HOURSECS
|
|
|
1728 |
* @uses DAYSECS
|
|
|
1729 |
* @uses YEARSECS
|
|
|
1730 |
* @param int $totalsecs Time in seconds
|
|
|
1731 |
* @param stdClass $str Should be a time object
|
|
|
1732 |
* @return string A nicely formatted date/time string
|
|
|
1733 |
*/
|
|
|
1734 |
function format_time($totalsecs, $str = null) {
|
|
|
1735 |
|
|
|
1736 |
$totalsecs = abs($totalsecs);
|
|
|
1737 |
|
|
|
1738 |
if (!$str) {
|
|
|
1739 |
// Create the str structure the slow way.
|
|
|
1740 |
$str = new stdClass();
|
|
|
1741 |
$str->day = get_string('day');
|
|
|
1742 |
$str->days = get_string('days');
|
|
|
1743 |
$str->hour = get_string('hour');
|
|
|
1744 |
$str->hours = get_string('hours');
|
|
|
1745 |
$str->min = get_string('min');
|
|
|
1746 |
$str->mins = get_string('mins');
|
|
|
1747 |
$str->sec = get_string('sec');
|
|
|
1748 |
$str->secs = get_string('secs');
|
|
|
1749 |
$str->year = get_string('year');
|
|
|
1750 |
$str->years = get_string('years');
|
|
|
1751 |
}
|
|
|
1752 |
|
|
|
1753 |
$years = floor($totalsecs/YEARSECS);
|
|
|
1754 |
$remainder = $totalsecs - ($years*YEARSECS);
|
|
|
1755 |
$days = floor($remainder/DAYSECS);
|
|
|
1756 |
$remainder = $totalsecs - ($days*DAYSECS);
|
|
|
1757 |
$hours = floor($remainder/HOURSECS);
|
|
|
1758 |
$remainder = $remainder - ($hours*HOURSECS);
|
|
|
1759 |
$mins = floor($remainder/MINSECS);
|
|
|
1760 |
$secs = $remainder - ($mins*MINSECS);
|
|
|
1761 |
|
|
|
1762 |
$ss = ($secs == 1) ? $str->sec : $str->secs;
|
|
|
1763 |
$sm = ($mins == 1) ? $str->min : $str->mins;
|
|
|
1764 |
$sh = ($hours == 1) ? $str->hour : $str->hours;
|
|
|
1765 |
$sd = ($days == 1) ? $str->day : $str->days;
|
|
|
1766 |
$sy = ($years == 1) ? $str->year : $str->years;
|
|
|
1767 |
|
|
|
1768 |
$oyears = '';
|
|
|
1769 |
$odays = '';
|
|
|
1770 |
$ohours = '';
|
|
|
1771 |
$omins = '';
|
|
|
1772 |
$osecs = '';
|
|
|
1773 |
|
|
|
1774 |
if ($years) {
|
|
|
1775 |
$oyears = $years .' '. $sy;
|
|
|
1776 |
}
|
|
|
1777 |
if ($days) {
|
|
|
1778 |
$odays = $days .' '. $sd;
|
|
|
1779 |
}
|
|
|
1780 |
if ($hours) {
|
|
|
1781 |
$ohours = $hours .' '. $sh;
|
|
|
1782 |
}
|
|
|
1783 |
if ($mins) {
|
|
|
1784 |
$omins = $mins .' '. $sm;
|
|
|
1785 |
}
|
|
|
1786 |
if ($secs) {
|
|
|
1787 |
$osecs = $secs .' '. $ss;
|
|
|
1788 |
}
|
|
|
1789 |
|
|
|
1790 |
if ($years) {
|
|
|
1791 |
return trim($oyears .' '. $odays);
|
|
|
1792 |
}
|
|
|
1793 |
if ($days) {
|
|
|
1794 |
return trim($odays .' '. $ohours);
|
|
|
1795 |
}
|
|
|
1796 |
if ($hours) {
|
|
|
1797 |
return trim($ohours .' '. $omins);
|
|
|
1798 |
}
|
|
|
1799 |
if ($mins) {
|
|
|
1800 |
return trim($omins .' '. $osecs);
|
|
|
1801 |
}
|
|
|
1802 |
if ($secs) {
|
|
|
1803 |
return $osecs;
|
|
|
1804 |
}
|
|
|
1805 |
return get_string('now');
|
|
|
1806 |
}
|
|
|
1807 |
|
|
|
1808 |
/**
|
|
|
1809 |
* Returns a formatted string that represents a date in user time.
|
|
|
1810 |
*
|
|
|
1811 |
* @package core
|
|
|
1812 |
* @category time
|
|
|
1813 |
* @param int $date the timestamp in UTC, as obtained from the database.
|
|
|
1814 |
* @param string $format strftime format. You should probably get this using
|
|
|
1815 |
* get_string('strftime...', 'langconfig');
|
|
|
1816 |
* @param int|float|string $timezone by default, uses the user's time zone. if numeric and
|
|
|
1817 |
* not 99 then daylight saving will not be added.
|
|
|
1818 |
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
|
|
1819 |
* @param bool $fixday If true (default) then the leading zero from %d is removed.
|
|
|
1820 |
* If false then the leading zero is maintained.
|
|
|
1821 |
* @param bool $fixhour If true (default) then the leading zero from %I is removed.
|
|
|
1822 |
* @return string the formatted date/time.
|
|
|
1823 |
*/
|
|
|
1824 |
function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true) {
|
|
|
1825 |
$calendartype = \core_calendar\type_factory::get_calendar_instance();
|
|
|
1826 |
return $calendartype->timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour);
|
|
|
1827 |
}
|
|
|
1828 |
|
|
|
1829 |
/**
|
|
|
1830 |
* Returns a html "time" tag with both the exact user date with timezone information
|
|
|
1831 |
* as a datetime attribute in the W3C format, and the user readable date and time as text.
|
|
|
1832 |
*
|
|
|
1833 |
* @package core
|
|
|
1834 |
* @category time
|
|
|
1835 |
* @param int $date the timestamp in UTC, as obtained from the database.
|
|
|
1836 |
* @param string $format strftime format. You should probably get this using
|
|
|
1837 |
* get_string('strftime...', 'langconfig');
|
|
|
1838 |
* @param int|float|string $timezone by default, uses the user's time zone. if numeric and
|
|
|
1839 |
* not 99 then daylight saving will not be added.
|
|
|
1840 |
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
|
|
1841 |
* @param bool $fixday If true (default) then the leading zero from %d is removed.
|
|
|
1842 |
* If false then the leading zero is maintained.
|
|
|
1843 |
* @param bool $fixhour If true (default) then the leading zero from %I is removed.
|
|
|
1844 |
* @return string the formatted date/time.
|
|
|
1845 |
*/
|
|
|
1846 |
function userdate_htmltime($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true) {
|
|
|
1847 |
$userdatestr = userdate($date, $format, $timezone, $fixday, $fixhour);
|
|
|
1848 |
if (CLI_SCRIPT && !PHPUNIT_TEST) {
|
|
|
1849 |
return $userdatestr;
|
|
|
1850 |
}
|
|
|
1851 |
$machinedate = new DateTime();
|
|
|
1852 |
$machinedate->setTimestamp(intval($date));
|
|
|
1853 |
$machinedate->setTimezone(core_date::get_user_timezone_object());
|
|
|
1854 |
|
|
|
1855 |
return html_writer::tag('time', $userdatestr, ['datetime' => $machinedate->format(DateTime::W3C)]);
|
|
|
1856 |
}
|
|
|
1857 |
|
|
|
1858 |
/**
|
|
|
1859 |
* Returns a formatted date ensuring it is UTF-8.
|
|
|
1860 |
*
|
|
|
1861 |
* If we are running under Windows convert to Windows encoding and then back to UTF-8
|
|
|
1862 |
* (because it's impossible to specify UTF-8 to fetch locale info in Win32).
|
|
|
1863 |
*
|
|
|
1864 |
* @param int $date the timestamp - since Moodle 2.9 this is a real UTC timestamp
|
|
|
1865 |
* @param string $format strftime format.
|
|
|
1866 |
* @param int|float|string $tz the user timezone
|
|
|
1867 |
* @return string the formatted date/time.
|
|
|
1868 |
* @since Moodle 2.3.3
|
|
|
1869 |
*/
|
|
|
1870 |
function date_format_string($date, $format, $tz = 99) {
|
|
|
1871 |
|
|
|
1872 |
date_default_timezone_set(core_date::get_user_timezone($tz));
|
|
|
1873 |
|
|
|
1874 |
if (date('A', 0) === date('A', HOURSECS * 18)) {
|
|
|
1875 |
$datearray = getdate($date);
|
|
|
1876 |
$format = str_replace([
|
|
|
1877 |
'%P',
|
|
|
1878 |
'%p',
|
|
|
1879 |
], [
|
|
|
1880 |
$datearray['hours'] < 12 ? get_string('am', 'langconfig') : get_string('pm', 'langconfig'),
|
|
|
1881 |
$datearray['hours'] < 12 ? get_string('amcaps', 'langconfig') : get_string('pmcaps', 'langconfig'),
|
|
|
1882 |
], $format);
|
|
|
1883 |
}
|
|
|
1884 |
|
|
|
1885 |
$datestring = core_date::strftime($format, $date);
|
|
|
1886 |
core_date::set_default_server_timezone();
|
|
|
1887 |
|
|
|
1888 |
return $datestring;
|
|
|
1889 |
}
|
|
|
1890 |
|
|
|
1891 |
/**
|
|
|
1892 |
* Given a $time timestamp in GMT (seconds since epoch),
|
|
|
1893 |
* returns an array that represents the Gregorian date in user time
|
|
|
1894 |
*
|
|
|
1895 |
* @package core
|
|
|
1896 |
* @category time
|
|
|
1897 |
* @param int $time Timestamp in GMT
|
|
|
1898 |
* @param float|int|string $timezone user timezone
|
|
|
1899 |
* @return array An array that represents the date in user time
|
|
|
1900 |
*/
|
|
|
1901 |
function usergetdate($time, $timezone=99) {
|
|
|
1902 |
if ($time === null) {
|
|
|
1903 |
// PHP8 and PHP7 return different results when getdate(null) is called.
|
|
|
1904 |
// Display warning and cast to 0 to make sure the usergetdate() behaves consistently on all versions of PHP.
|
|
|
1905 |
// In the future versions of Moodle we may consider adding a strict typehint.
|
|
|
1906 |
debugging('usergetdate() expects parameter $time to be int, null given', DEBUG_DEVELOPER);
|
|
|
1907 |
$time = 0;
|
|
|
1908 |
}
|
|
|
1909 |
|
|
|
1910 |
date_default_timezone_set(core_date::get_user_timezone($timezone));
|
|
|
1911 |
$result = getdate($time);
|
|
|
1912 |
core_date::set_default_server_timezone();
|
|
|
1913 |
|
|
|
1914 |
return $result;
|
|
|
1915 |
}
|
|
|
1916 |
|
|
|
1917 |
/**
|
|
|
1918 |
* Given a GMT timestamp (seconds since epoch), offsets it by
|
|
|
1919 |
* the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
|
|
|
1920 |
*
|
|
|
1921 |
* NOTE: this function does not include DST properly,
|
|
|
1922 |
* you should use the PHP date stuff instead!
|
|
|
1923 |
*
|
|
|
1924 |
* @package core
|
|
|
1925 |
* @category time
|
|
|
1926 |
* @param int $date Timestamp in GMT
|
|
|
1927 |
* @param float|int|string $timezone user timezone
|
|
|
1928 |
* @return int
|
|
|
1929 |
*/
|
|
|
1930 |
function usertime($date, $timezone=99) {
|
|
|
1931 |
$userdate = new DateTime('@' . $date);
|
|
|
1932 |
$userdate->setTimezone(core_date::get_user_timezone_object($timezone));
|
|
|
1933 |
$dst = dst_offset_on($date, $timezone);
|
|
|
1934 |
|
|
|
1935 |
return $date - $userdate->getOffset() + $dst;
|
|
|
1936 |
}
|
|
|
1937 |
|
|
|
1938 |
/**
|
|
|
1939 |
* Get a formatted string representation of an interval between two unix timestamps.
|
|
|
1940 |
*
|
|
|
1941 |
* E.g.
|
|
|
1942 |
* $intervalstring = get_time_interval_string(12345600, 12345660);
|
|
|
1943 |
* Will produce the string:
|
|
|
1944 |
* '0d 0h 1m'
|
|
|
1945 |
*
|
|
|
1946 |
* @param int $time1 unix timestamp
|
|
|
1947 |
* @param int $time2 unix timestamp
|
|
|
1948 |
* @param string $format string (can be lang string) containing format chars: https://www.php.net/manual/en/dateinterval.format.php.
|
|
|
1949 |
* @param bool $dropzeroes If format is not provided and this is set to true, do not include zero time units.
|
|
|
1950 |
* e.g. a duration of 3 days and 2 hours will be displayed as '3d 2h' instead of '3d 2h 0s'
|
|
|
1951 |
* @param bool $fullformat If format is not provided and this is set to true, display time units in full format.
|
|
|
1952 |
* e.g. instead of showing "3d", "3 days" will be returned.
|
|
|
1953 |
* @return string the formatted string describing the time difference, e.g. '10d 11h 45m'.
|
|
|
1954 |
*/
|
|
|
1955 |
function get_time_interval_string(int $time1, int $time2, string $format = '',
|
|
|
1956 |
bool $dropzeroes = false, bool $fullformat = false): string {
|
|
|
1957 |
$dtdate = new DateTime();
|
|
|
1958 |
$dtdate->setTimeStamp($time1);
|
|
|
1959 |
$dtdate2 = new DateTime();
|
|
|
1960 |
$dtdate2->setTimeStamp($time2);
|
|
|
1961 |
$interval = $dtdate2->diff($dtdate);
|
|
|
1962 |
|
|
|
1963 |
if (empty(trim($format))) {
|
|
|
1964 |
// Default to this key.
|
|
|
1965 |
$formatkey = 'dateintervaldayhrmin';
|
|
|
1966 |
|
|
|
1967 |
if ($dropzeroes) {
|
|
|
1968 |
$units = [
|
|
|
1969 |
'y' => 'yr',
|
|
|
1970 |
'm' => 'mo',
|
|
|
1971 |
'd' => 'day',
|
|
|
1972 |
'h' => 'hr',
|
|
|
1973 |
'i' => 'min',
|
|
|
1974 |
's' => 'sec',
|
|
|
1975 |
];
|
|
|
1976 |
$formatunits = [];
|
|
|
1977 |
foreach ($units as $key => $unit) {
|
|
|
1978 |
if (empty($interval->$key)) {
|
|
|
1979 |
continue;
|
|
|
1980 |
}
|
|
|
1981 |
$formatunits[] = $unit;
|
|
|
1982 |
}
|
|
|
1983 |
if (!empty($formatunits)) {
|
|
|
1984 |
$formatkey = 'dateinterval' . implode("", $formatunits);
|
|
|
1985 |
}
|
|
|
1986 |
}
|
|
|
1987 |
|
|
|
1988 |
if ($fullformat) {
|
|
|
1989 |
$formatkey .= 'full';
|
|
|
1990 |
}
|
|
|
1991 |
$format = get_string($formatkey, 'langconfig');
|
|
|
1992 |
}
|
|
|
1993 |
return $interval->format($format);
|
|
|
1994 |
}
|
|
|
1995 |
|
|
|
1996 |
/**
|
|
|
1997 |
* Given a time, return the GMT timestamp of the most recent midnight
|
|
|
1998 |
* for the current user.
|
|
|
1999 |
*
|
|
|
2000 |
* @package core
|
|
|
2001 |
* @category time
|
|
|
2002 |
* @param int $date Timestamp in GMT
|
|
|
2003 |
* @param float|int|string $timezone user timezone
|
|
|
2004 |
* @return int Returns a GMT timestamp
|
|
|
2005 |
*/
|
|
|
2006 |
function usergetmidnight($date, $timezone=99) {
|
|
|
2007 |
|
|
|
2008 |
$userdate = usergetdate($date, $timezone);
|
|
|
2009 |
|
|
|
2010 |
// Time of midnight of this user's day, in GMT.
|
|
|
2011 |
return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
|
|
|
2012 |
|
|
|
2013 |
}
|
|
|
2014 |
|
|
|
2015 |
/**
|
|
|
2016 |
* Returns a string that prints the user's timezone
|
|
|
2017 |
*
|
|
|
2018 |
* @package core
|
|
|
2019 |
* @category time
|
|
|
2020 |
* @param float|int|string $timezone user timezone
|
|
|
2021 |
* @return string
|
|
|
2022 |
*/
|
|
|
2023 |
function usertimezone($timezone=99) {
|
|
|
2024 |
$tz = core_date::get_user_timezone($timezone);
|
|
|
2025 |
return core_date::get_localised_timezone($tz);
|
|
|
2026 |
}
|
|
|
2027 |
|
|
|
2028 |
/**
|
|
|
2029 |
* Returns a float or a string which denotes the user's timezone
|
|
|
2030 |
* A float value means that a simple offset from GMT is used, while a string (it will be the name of a timezone in the database)
|
|
|
2031 |
* means that for this timezone there are also DST rules to be taken into account
|
|
|
2032 |
* Checks various settings and picks the most dominant of those which have a value
|
|
|
2033 |
*
|
|
|
2034 |
* @package core
|
|
|
2035 |
* @category time
|
|
|
2036 |
* @param float|int|string $tz timezone to calculate GMT time offset before
|
|
|
2037 |
* calculating user timezone, 99 is default user timezone
|
|
|
2038 |
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
|
|
2039 |
* @return float|string
|
|
|
2040 |
*/
|
|
|
2041 |
function get_user_timezone($tz = 99) {
|
|
|
2042 |
global $USER, $CFG;
|
|
|
2043 |
|
|
|
2044 |
$timezones = array(
|
|
|
2045 |
$tz,
|
|
|
2046 |
isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
|
|
|
2047 |
isset($USER->timezone) ? $USER->timezone : 99,
|
|
|
2048 |
isset($CFG->timezone) ? $CFG->timezone : 99,
|
|
|
2049 |
);
|
|
|
2050 |
|
|
|
2051 |
$tz = 99;
|
|
|
2052 |
|
|
|
2053 |
// Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array.
|
|
|
2054 |
foreach ($timezones as $nextvalue) {
|
|
|
2055 |
if ((empty($tz) && !is_numeric($tz)) || $tz == 99) {
|
|
|
2056 |
$tz = $nextvalue;
|
|
|
2057 |
}
|
|
|
2058 |
}
|
|
|
2059 |
return is_numeric($tz) ? (float) $tz : $tz;
|
|
|
2060 |
}
|
|
|
2061 |
|
|
|
2062 |
/**
|
|
|
2063 |
* Calculates the Daylight Saving Offset for a given date/time (timestamp)
|
|
|
2064 |
* - Note: Daylight saving only works for string timezones and not for float.
|
|
|
2065 |
*
|
|
|
2066 |
* @package core
|
|
|
2067 |
* @category time
|
|
|
2068 |
* @param int $time must NOT be compensated at all, it has to be a pure timestamp
|
|
|
2069 |
* @param int|float|string $strtimezone user timezone
|
|
|
2070 |
* @return int
|
|
|
2071 |
*/
|
|
|
2072 |
function dst_offset_on($time, $strtimezone = null) {
|
|
|
2073 |
$tz = core_date::get_user_timezone($strtimezone);
|
|
|
2074 |
$date = new DateTime('@' . $time);
|
|
|
2075 |
$date->setTimezone(new DateTimeZone($tz));
|
|
|
2076 |
if ($date->format('I') == '1') {
|
|
|
2077 |
if ($tz === 'Australia/Lord_Howe') {
|
|
|
2078 |
return 1800;
|
|
|
2079 |
}
|
|
|
2080 |
return 3600;
|
|
|
2081 |
}
|
|
|
2082 |
return 0;
|
|
|
2083 |
}
|
|
|
2084 |
|
|
|
2085 |
/**
|
|
|
2086 |
* Calculates when the day appears in specific month
|
|
|
2087 |
*
|
|
|
2088 |
* @package core
|
|
|
2089 |
* @category time
|
|
|
2090 |
* @param int $startday starting day of the month
|
|
|
2091 |
* @param int $weekday The day when week starts (normally taken from user preferences)
|
|
|
2092 |
* @param int $month The month whose day is sought
|
|
|
2093 |
* @param int $year The year of the month whose day is sought
|
|
|
2094 |
* @return int
|
|
|
2095 |
*/
|
|
|
2096 |
function find_day_in_month($startday, $weekday, $month, $year) {
|
|
|
2097 |
$calendartype = \core_calendar\type_factory::get_calendar_instance();
|
|
|
2098 |
|
|
|
2099 |
$daysinmonth = days_in_month($month, $year);
|
|
|
2100 |
$daysinweek = count($calendartype->get_weekdays());
|
|
|
2101 |
|
|
|
2102 |
if ($weekday == -1) {
|
|
|
2103 |
// Don't care about weekday, so return:
|
|
|
2104 |
// abs($startday) if $startday != -1
|
|
|
2105 |
// $daysinmonth otherwise.
|
|
|
2106 |
return ($startday == -1) ? $daysinmonth : abs($startday);
|
|
|
2107 |
}
|
|
|
2108 |
|
|
|
2109 |
// From now on we 're looking for a specific weekday.
|
|
|
2110 |
// Give "end of month" its actual value, since we know it.
|
|
|
2111 |
if ($startday == -1) {
|
|
|
2112 |
$startday = -1 * $daysinmonth;
|
|
|
2113 |
}
|
|
|
2114 |
|
|
|
2115 |
// Starting from day $startday, the sign is the direction.
|
|
|
2116 |
if ($startday < 1) {
|
|
|
2117 |
$startday = abs($startday);
|
|
|
2118 |
$lastmonthweekday = dayofweek($daysinmonth, $month, $year);
|
|
|
2119 |
|
|
|
2120 |
// This is the last such weekday of the month.
|
|
|
2121 |
$lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
|
|
|
2122 |
if ($lastinmonth > $daysinmonth) {
|
|
|
2123 |
$lastinmonth -= $daysinweek;
|
|
|
2124 |
}
|
|
|
2125 |
|
|
|
2126 |
// Find the first such weekday <= $startday.
|
|
|
2127 |
while ($lastinmonth > $startday) {
|
|
|
2128 |
$lastinmonth -= $daysinweek;
|
|
|
2129 |
}
|
|
|
2130 |
|
|
|
2131 |
return $lastinmonth;
|
|
|
2132 |
} else {
|
|
|
2133 |
$indexweekday = dayofweek($startday, $month, $year);
|
|
|
2134 |
|
|
|
2135 |
$diff = $weekday - $indexweekday;
|
|
|
2136 |
if ($diff < 0) {
|
|
|
2137 |
$diff += $daysinweek;
|
|
|
2138 |
}
|
|
|
2139 |
|
|
|
2140 |
// This is the first such weekday of the month equal to or after $startday.
|
|
|
2141 |
$firstfromindex = $startday + $diff;
|
|
|
2142 |
|
|
|
2143 |
return $firstfromindex;
|
|
|
2144 |
}
|
|
|
2145 |
}
|
|
|
2146 |
|
|
|
2147 |
/**
|
|
|
2148 |
* Calculate the number of days in a given month
|
|
|
2149 |
*
|
|
|
2150 |
* @package core
|
|
|
2151 |
* @category time
|
|
|
2152 |
* @param int $month The month whose day count is sought
|
|
|
2153 |
* @param int $year The year of the month whose day count is sought
|
|
|
2154 |
* @return int
|
|
|
2155 |
*/
|
|
|
2156 |
function days_in_month($month, $year) {
|
|
|
2157 |
$calendartype = \core_calendar\type_factory::get_calendar_instance();
|
|
|
2158 |
return $calendartype->get_num_days_in_month($year, $month);
|
|
|
2159 |
}
|
|
|
2160 |
|
|
|
2161 |
/**
|
|
|
2162 |
* Calculate the position in the week of a specific calendar day
|
|
|
2163 |
*
|
|
|
2164 |
* @package core
|
|
|
2165 |
* @category time
|
|
|
2166 |
* @param int $day The day of the date whose position in the week is sought
|
|
|
2167 |
* @param int $month The month of the date whose position in the week is sought
|
|
|
2168 |
* @param int $year The year of the date whose position in the week is sought
|
|
|
2169 |
* @return int
|
|
|
2170 |
*/
|
|
|
2171 |
function dayofweek($day, $month, $year) {
|
|
|
2172 |
$calendartype = \core_calendar\type_factory::get_calendar_instance();
|
|
|
2173 |
return $calendartype->get_weekday($year, $month, $day);
|
|
|
2174 |
}
|
|
|
2175 |
|
|
|
2176 |
// USER AUTHENTICATION AND LOGIN.
|
|
|
2177 |
|
|
|
2178 |
/**
|
|
|
2179 |
* Returns full login url.
|
|
|
2180 |
*
|
|
|
2181 |
* Any form submissions for authentication to this URL must include username,
|
|
|
2182 |
* password as well as a logintoken generated by \core\session\manager::get_login_token().
|
|
|
2183 |
*
|
|
|
2184 |
* @return string login url
|
|
|
2185 |
*/
|
|
|
2186 |
function get_login_url() {
|
|
|
2187 |
global $CFG;
|
|
|
2188 |
|
|
|
2189 |
return "$CFG->wwwroot/login/index.php";
|
|
|
2190 |
}
|
|
|
2191 |
|
|
|
2192 |
/**
|
|
|
2193 |
* This function checks that the current user is logged in and has the
|
|
|
2194 |
* required privileges
|
|
|
2195 |
*
|
|
|
2196 |
* This function checks that the current user is logged in, and optionally
|
|
|
2197 |
* whether they are allowed to be in a particular course and view a particular
|
|
|
2198 |
* course module.
|
|
|
2199 |
* If they are not logged in, then it redirects them to the site login unless
|
|
|
2200 |
* $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
|
|
|
2201 |
* case they are automatically logged in as guests.
|
|
|
2202 |
* If $courseid is given and the user is not enrolled in that course then the
|
|
|
2203 |
* user is redirected to the course enrolment page.
|
|
|
2204 |
* If $cm is given and the course module is hidden and the user is not a teacher
|
|
|
2205 |
* in the course then the user is redirected to the course home page.
|
|
|
2206 |
*
|
|
|
2207 |
* When $cm parameter specified, this function sets page layout to 'module'.
|
|
|
2208 |
* You need to change it manually later if some other layout needed.
|
|
|
2209 |
*
|
|
|
2210 |
* @package core_access
|
|
|
2211 |
* @category access
|
|
|
2212 |
*
|
|
|
2213 |
* @param mixed $courseorid id of the course or course object
|
|
|
2214 |
* @param bool $autologinguest default true
|
|
|
2215 |
* @param object $cm course module object
|
|
|
2216 |
* @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
|
|
|
2217 |
* true. Used to avoid (=false) some scripts (file.php...) to set that variable,
|
|
|
2218 |
* in order to keep redirects working properly. MDL-14495
|
|
|
2219 |
* @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
|
|
|
2220 |
* @return mixed Void, exit, and die depending on path
|
|
|
2221 |
* @throws coding_exception
|
|
|
2222 |
* @throws require_login_exception
|
|
|
2223 |
* @throws moodle_exception
|
|
|
2224 |
*/
|
|
|
2225 |
function require_login($courseorid = null, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false) {
|
|
|
2226 |
global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
|
|
|
2227 |
|
|
|
2228 |
// Must not redirect when byteserving already started.
|
|
|
2229 |
if (!empty($_SERVER['HTTP_RANGE'])) {
|
|
|
2230 |
$preventredirect = true;
|
|
|
2231 |
}
|
|
|
2232 |
|
|
|
2233 |
if (AJAX_SCRIPT) {
|
|
|
2234 |
// We cannot redirect for AJAX scripts either.
|
|
|
2235 |
$preventredirect = true;
|
|
|
2236 |
}
|
|
|
2237 |
|
|
|
2238 |
// Setup global $COURSE, themes, language and locale.
|
|
|
2239 |
if (!empty($courseorid)) {
|
|
|
2240 |
if (is_object($courseorid)) {
|
|
|
2241 |
$course = $courseorid;
|
|
|
2242 |
} else if ($courseorid == SITEID) {
|
|
|
2243 |
$course = clone($SITE);
|
|
|
2244 |
} else {
|
|
|
2245 |
$course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
|
|
|
2246 |
}
|
|
|
2247 |
if ($cm) {
|
|
|
2248 |
if ($cm->course != $course->id) {
|
|
|
2249 |
throw new coding_exception('course and cm parameters in require_login() call do not match!!');
|
|
|
2250 |
}
|
|
|
2251 |
// Make sure we have a $cm from get_fast_modinfo as this contains activity access details.
|
|
|
2252 |
if (!($cm instanceof cm_info)) {
|
|
|
2253 |
// Note: nearly all pages call get_fast_modinfo anyway and it does not make any
|
|
|
2254 |
// db queries so this is not really a performance concern, however it is obviously
|
|
|
2255 |
// better if you use get_fast_modinfo to get the cm before calling this.
|
|
|
2256 |
$modinfo = get_fast_modinfo($course);
|
|
|
2257 |
$cm = $modinfo->get_cm($cm->id);
|
|
|
2258 |
}
|
|
|
2259 |
}
|
|
|
2260 |
} else {
|
|
|
2261 |
// Do not touch global $COURSE via $PAGE->set_course(),
|
|
|
2262 |
// the reasons is we need to be able to call require_login() at any time!!
|
|
|
2263 |
$course = $SITE;
|
|
|
2264 |
if ($cm) {
|
|
|
2265 |
throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
|
|
|
2266 |
}
|
|
|
2267 |
}
|
|
|
2268 |
|
|
|
2269 |
// If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
|
|
|
2270 |
// Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
|
|
|
2271 |
// risk leading the user back to the AJAX request URL.
|
|
|
2272 |
if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
|
|
|
2273 |
$setwantsurltome = false;
|
|
|
2274 |
}
|
|
|
2275 |
|
|
|
2276 |
// Redirect to the login page if session has expired, only with dbsessions enabled (MDL-35029) to maintain current behaviour.
|
|
|
2277 |
if ((!isloggedin() or isguestuser()) && !empty($SESSION->has_timed_out) && !empty($CFG->dbsessions)) {
|
|
|
2278 |
if ($preventredirect) {
|
|
|
2279 |
throw new require_login_session_timeout_exception();
|
|
|
2280 |
} else {
|
|
|
2281 |
if ($setwantsurltome) {
|
|
|
2282 |
$SESSION->wantsurl = qualified_me();
|
|
|
2283 |
}
|
|
|
2284 |
redirect(get_login_url());
|
|
|
2285 |
}
|
|
|
2286 |
}
|
|
|
2287 |
|
|
|
2288 |
// If the user is not even logged in yet then make sure they are.
|
|
|
2289 |
if (!isloggedin()) {
|
|
|
2290 |
if ($autologinguest && !empty($CFG->autologinguests)) {
|
|
|
2291 |
if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
|
|
|
2292 |
// Misconfigured site guest, just redirect to login page.
|
|
|
2293 |
redirect(get_login_url());
|
|
|
2294 |
exit; // Never reached.
|
|
|
2295 |
}
|
|
|
2296 |
$lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
|
|
|
2297 |
complete_user_login($guest);
|
|
|
2298 |
$USER->autologinguest = true;
|
|
|
2299 |
$SESSION->lang = $lang;
|
|
|
2300 |
} else {
|
|
|
2301 |
// NOTE: $USER->site check was obsoleted by session test cookie, $USER->confirmed test is in login/index.php.
|
|
|
2302 |
if ($preventredirect) {
|
|
|
2303 |
throw new require_login_exception('You are not logged in');
|
|
|
2304 |
}
|
|
|
2305 |
|
|
|
2306 |
if ($setwantsurltome) {
|
|
|
2307 |
$SESSION->wantsurl = qualified_me();
|
|
|
2308 |
}
|
|
|
2309 |
|
|
|
2310 |
// Give auth plugins an opportunity to authenticate or redirect to an external login page
|
|
|
2311 |
$authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
|
|
|
2312 |
foreach($authsequence as $authname) {
|
|
|
2313 |
$authplugin = get_auth_plugin($authname);
|
|
|
2314 |
$authplugin->pre_loginpage_hook();
|
|
|
2315 |
if (isloggedin()) {
|
|
|
2316 |
if ($cm) {
|
|
|
2317 |
$modinfo = get_fast_modinfo($course);
|
|
|
2318 |
$cm = $modinfo->get_cm($cm->id);
|
|
|
2319 |
}
|
|
|
2320 |
set_access_log_user();
|
|
|
2321 |
break;
|
|
|
2322 |
}
|
|
|
2323 |
}
|
|
|
2324 |
|
|
|
2325 |
// If we're still not logged in then go to the login page
|
|
|
2326 |
if (!isloggedin()) {
|
|
|
2327 |
redirect(get_login_url());
|
|
|
2328 |
exit; // Never reached.
|
|
|
2329 |
}
|
|
|
2330 |
}
|
|
|
2331 |
}
|
|
|
2332 |
|
|
|
2333 |
// Loginas as redirection if needed.
|
|
|
2334 |
if ($course->id != SITEID and \core\session\manager::is_loggedinas()) {
|
|
|
2335 |
if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
|
|
|
2336 |
if ($USER->loginascontext->instanceid != $course->id) {
|
|
|
2337 |
throw new \moodle_exception('loginasonecourse', '',
|
|
|
2338 |
$CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
|
|
|
2339 |
}
|
|
|
2340 |
}
|
|
|
2341 |
}
|
|
|
2342 |
|
|
|
2343 |
// Check whether the user should be changing password (but only if it is REALLY them).
|
|
|
2344 |
if (get_user_preferences('auth_forcepasswordchange') && !\core\session\manager::is_loggedinas()) {
|
|
|
2345 |
$userauth = get_auth_plugin($USER->auth);
|
|
|
2346 |
if ($userauth->can_change_password() and !$preventredirect) {
|
|
|
2347 |
if ($setwantsurltome) {
|
|
|
2348 |
$SESSION->wantsurl = qualified_me();
|
|
|
2349 |
}
|
|
|
2350 |
if ($changeurl = $userauth->change_password_url()) {
|
|
|
2351 |
// Use plugin custom url.
|
|
|
2352 |
redirect($changeurl);
|
|
|
2353 |
} else {
|
|
|
2354 |
// Use moodle internal method.
|
|
|
2355 |
redirect($CFG->wwwroot .'/login/change_password.php');
|
|
|
2356 |
}
|
|
|
2357 |
} else if ($userauth->can_change_password()) {
|
|
|
2358 |
throw new moodle_exception('forcepasswordchangenotice');
|
|
|
2359 |
} else {
|
|
|
2360 |
throw new moodle_exception('nopasswordchangeforced', 'auth');
|
|
|
2361 |
}
|
|
|
2362 |
}
|
|
|
2363 |
|
|
|
2364 |
// Check that the user account is properly set up. If we can't redirect to
|
|
|
2365 |
// edit their profile and this is not a WS request, perform just the lax check.
|
|
|
2366 |
// It will allow them to use filepicker on the profile edit page.
|
|
|
2367 |
|
|
|
2368 |
if ($preventredirect && !WS_SERVER) {
|
|
|
2369 |
$usernotfullysetup = user_not_fully_set_up($USER, false);
|
|
|
2370 |
} else {
|
|
|
2371 |
$usernotfullysetup = user_not_fully_set_up($USER, true);
|
|
|
2372 |
}
|
|
|
2373 |
|
|
|
2374 |
if ($usernotfullysetup) {
|
|
|
2375 |
if ($preventredirect) {
|
|
|
2376 |
throw new moodle_exception('usernotfullysetup');
|
|
|
2377 |
}
|
|
|
2378 |
if ($setwantsurltome) {
|
|
|
2379 |
$SESSION->wantsurl = qualified_me();
|
|
|
2380 |
}
|
|
|
2381 |
redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&course='. SITEID);
|
|
|
2382 |
}
|
|
|
2383 |
|
|
|
2384 |
// Make sure the USER has a sesskey set up. Used for CSRF protection.
|
|
|
2385 |
sesskey();
|
|
|
2386 |
|
|
|
2387 |
if (\core\session\manager::is_loggedinas()) {
|
|
|
2388 |
// During a "logged in as" session we should force all content to be cleaned because the
|
|
|
2389 |
// logged in user will be viewing potentially malicious user generated content.
|
|
|
2390 |
// See MDL-63786 for more details.
|
|
|
2391 |
$CFG->forceclean = true;
|
|
|
2392 |
}
|
|
|
2393 |
|
|
|
2394 |
$afterlogins = get_plugins_with_function('after_require_login', 'lib.php');
|
|
|
2395 |
|
|
|
2396 |
// Do not bother admins with any formalities, except for activities pending deletion.
|
|
|
2397 |
if (is_siteadmin() && !($cm && $cm->deletioninprogress)) {
|
|
|
2398 |
// Set the global $COURSE.
|
|
|
2399 |
if ($cm) {
|
|
|
2400 |
$PAGE->set_cm($cm, $course);
|
|
|
2401 |
$PAGE->set_pagelayout('incourse');
|
|
|
2402 |
} else if (!empty($courseorid)) {
|
|
|
2403 |
$PAGE->set_course($course);
|
|
|
2404 |
}
|
|
|
2405 |
// Set accesstime or the user will appear offline which messes up messaging.
|
|
|
2406 |
// Do not update access time for webservice or ajax requests.
|
|
|
2407 |
if (!WS_SERVER && !AJAX_SCRIPT) {
|
|
|
2408 |
user_accesstime_log($course->id);
|
|
|
2409 |
}
|
|
|
2410 |
|
|
|
2411 |
foreach ($afterlogins as $plugintype => $plugins) {
|
|
|
2412 |
foreach ($plugins as $pluginfunction) {
|
|
|
2413 |
$pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
|
|
|
2414 |
}
|
|
|
2415 |
}
|
|
|
2416 |
return;
|
|
|
2417 |
}
|
|
|
2418 |
|
|
|
2419 |
// Scripts have a chance to declare that $USER->policyagreed should not be checked.
|
|
|
2420 |
// This is mostly for places where users are actually accepting the policies, to avoid the redirect loop.
|
|
|
2421 |
if (!defined('NO_SITEPOLICY_CHECK')) {
|
|
|
2422 |
define('NO_SITEPOLICY_CHECK', false);
|
|
|
2423 |
}
|
|
|
2424 |
|
|
|
2425 |
// Check that the user has agreed to a site policy if there is one - do not test in case of admins.
|
|
|
2426 |
// Do not test if the script explicitly asked for skipping the site policies check.
|
|
|
2427 |
// Or if the user auth type is webservice.
|
|
|
2428 |
if (!$USER->policyagreed && !is_siteadmin() && !NO_SITEPOLICY_CHECK && $USER->auth !== 'webservice') {
|
|
|
2429 |
$manager = new \core_privacy\local\sitepolicy\manager();
|
|
|
2430 |
if ($policyurl = $manager->get_redirect_url(isguestuser())) {
|
|
|
2431 |
if ($preventredirect) {
|
|
|
2432 |
throw new moodle_exception('sitepolicynotagreed', 'error', '', $policyurl->out());
|
|
|
2433 |
}
|
|
|
2434 |
if ($setwantsurltome) {
|
|
|
2435 |
$SESSION->wantsurl = qualified_me();
|
|
|
2436 |
}
|
|
|
2437 |
redirect($policyurl);
|
|
|
2438 |
}
|
|
|
2439 |
}
|
|
|
2440 |
|
|
|
2441 |
// Fetch the system context, the course context, and prefetch its child contexts.
|
|
|
2442 |
$sysctx = context_system::instance();
|
|
|
2443 |
$coursecontext = context_course::instance($course->id, MUST_EXIST);
|
|
|
2444 |
if ($cm) {
|
|
|
2445 |
$cmcontext = context_module::instance($cm->id, MUST_EXIST);
|
|
|
2446 |
} else {
|
|
|
2447 |
$cmcontext = null;
|
|
|
2448 |
}
|
|
|
2449 |
|
|
|
2450 |
// If the site is currently under maintenance, then print a message.
|
|
|
2451 |
if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:maintenanceaccess', $sysctx)) {
|
|
|
2452 |
if ($preventredirect) {
|
|
|
2453 |
throw new require_login_exception('Maintenance in progress');
|
|
|
2454 |
}
|
|
|
2455 |
$PAGE->set_context(null);
|
|
|
2456 |
print_maintenance_message();
|
|
|
2457 |
}
|
|
|
2458 |
|
|
|
2459 |
// Make sure the course itself is not hidden.
|
|
|
2460 |
if ($course->id == SITEID) {
|
|
|
2461 |
// Frontpage can not be hidden.
|
|
|
2462 |
} else {
|
|
|
2463 |
if (is_role_switched($course->id)) {
|
|
|
2464 |
// When switching roles ignore the hidden flag - user had to be in course to do the switch.
|
|
|
2465 |
} else {
|
|
|
2466 |
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
|
|
2467 |
// Originally there was also test of parent category visibility, BUT is was very slow in complex queries
|
|
|
2468 |
// involving "my courses" now it is also possible to simply hide all courses user is not enrolled in :-).
|
|
|
2469 |
if ($preventredirect) {
|
|
|
2470 |
throw new require_login_exception('Course is hidden');
|
|
|
2471 |
}
|
|
|
2472 |
$PAGE->set_context(null);
|
|
|
2473 |
// We need to override the navigation URL as the course won't have been added to the navigation and thus
|
|
|
2474 |
// the navigation will mess up when trying to find it.
|
|
|
2475 |
navigation_node::override_active_url(new moodle_url('/'));
|
|
|
2476 |
notice(get_string('coursehidden'), $CFG->wwwroot .'/');
|
|
|
2477 |
}
|
|
|
2478 |
}
|
|
|
2479 |
}
|
|
|
2480 |
|
|
|
2481 |
// Is the user enrolled?
|
|
|
2482 |
if ($course->id == SITEID) {
|
|
|
2483 |
// Everybody is enrolled on the frontpage.
|
|
|
2484 |
} else {
|
|
|
2485 |
if (\core\session\manager::is_loggedinas()) {
|
|
|
2486 |
// Make sure the REAL person can access this course first.
|
|
|
2487 |
$realuser = \core\session\manager::get_realuser();
|
|
|
2488 |
if (!is_enrolled($coursecontext, $realuser->id, '', true) and
|
|
|
2489 |
!is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
|
|
|
2490 |
if ($preventredirect) {
|
|
|
2491 |
throw new require_login_exception('Invalid course login-as access');
|
|
|
2492 |
}
|
|
|
2493 |
$PAGE->set_context(null);
|
|
|
2494 |
echo $OUTPUT->header();
|
|
|
2495 |
notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
|
|
|
2496 |
}
|
|
|
2497 |
}
|
|
|
2498 |
|
|
|
2499 |
$access = false;
|
|
|
2500 |
|
|
|
2501 |
if (is_role_switched($course->id)) {
|
|
|
2502 |
// Ok, user had to be inside this course before the switch.
|
|
|
2503 |
$access = true;
|
|
|
2504 |
|
|
|
2505 |
} else if (is_viewing($coursecontext, $USER)) {
|
|
|
2506 |
// Ok, no need to mess with enrol.
|
|
|
2507 |
$access = true;
|
|
|
2508 |
|
|
|
2509 |
} else {
|
|
|
2510 |
if (isset($USER->enrol['enrolled'][$course->id])) {
|
|
|
2511 |
if ($USER->enrol['enrolled'][$course->id] > time()) {
|
|
|
2512 |
$access = true;
|
|
|
2513 |
if (isset($USER->enrol['tempguest'][$course->id])) {
|
|
|
2514 |
unset($USER->enrol['tempguest'][$course->id]);
|
|
|
2515 |
remove_temp_course_roles($coursecontext);
|
|
|
2516 |
}
|
|
|
2517 |
} else {
|
|
|
2518 |
// Expired.
|
|
|
2519 |
unset($USER->enrol['enrolled'][$course->id]);
|
|
|
2520 |
}
|
|
|
2521 |
}
|
|
|
2522 |
if (isset($USER->enrol['tempguest'][$course->id])) {
|
|
|
2523 |
if ($USER->enrol['tempguest'][$course->id] == 0) {
|
|
|
2524 |
$access = true;
|
|
|
2525 |
} else if ($USER->enrol['tempguest'][$course->id] > time()) {
|
|
|
2526 |
$access = true;
|
|
|
2527 |
} else {
|
|
|
2528 |
// Expired.
|
|
|
2529 |
unset($USER->enrol['tempguest'][$course->id]);
|
|
|
2530 |
remove_temp_course_roles($coursecontext);
|
|
|
2531 |
}
|
|
|
2532 |
}
|
|
|
2533 |
|
|
|
2534 |
if (!$access) {
|
|
|
2535 |
// Cache not ok.
|
|
|
2536 |
$until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
|
|
|
2537 |
if ($until !== false) {
|
|
|
2538 |
// Active participants may always access, a timestamp in the future, 0 (always) or false.
|
|
|
2539 |
if ($until == 0) {
|
|
|
2540 |
$until = ENROL_MAX_TIMESTAMP;
|
|
|
2541 |
}
|
|
|
2542 |
$USER->enrol['enrolled'][$course->id] = $until;
|
|
|
2543 |
$access = true;
|
|
|
2544 |
|
|
|
2545 |
} else if (core_course_category::can_view_course_info($course)) {
|
|
|
2546 |
$params = array('courseid' => $course->id, 'status' => ENROL_INSTANCE_ENABLED);
|
|
|
2547 |
$instances = $DB->get_records('enrol', $params, 'sortorder, id ASC');
|
|
|
2548 |
$enrols = enrol_get_plugins(true);
|
|
|
2549 |
// First ask all enabled enrol instances in course if they want to auto enrol user.
|
|
|
2550 |
foreach ($instances as $instance) {
|
|
|
2551 |
if (!isset($enrols[$instance->enrol])) {
|
|
|
2552 |
continue;
|
|
|
2553 |
}
|
|
|
2554 |
// Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
|
|
|
2555 |
$until = $enrols[$instance->enrol]->try_autoenrol($instance);
|
|
|
2556 |
if ($until !== false) {
|
|
|
2557 |
if ($until == 0) {
|
|
|
2558 |
$until = ENROL_MAX_TIMESTAMP;
|
|
|
2559 |
}
|
|
|
2560 |
$USER->enrol['enrolled'][$course->id] = $until;
|
|
|
2561 |
$access = true;
|
|
|
2562 |
break;
|
|
|
2563 |
}
|
|
|
2564 |
}
|
|
|
2565 |
// If not enrolled yet try to gain temporary guest access.
|
|
|
2566 |
if (!$access) {
|
|
|
2567 |
foreach ($instances as $instance) {
|
|
|
2568 |
if (!isset($enrols[$instance->enrol])) {
|
|
|
2569 |
continue;
|
|
|
2570 |
}
|
|
|
2571 |
// Get a duration for the guest access, a timestamp in the future or false.
|
|
|
2572 |
$until = $enrols[$instance->enrol]->try_guestaccess($instance);
|
|
|
2573 |
if ($until !== false and $until > time()) {
|
|
|
2574 |
$USER->enrol['tempguest'][$course->id] = $until;
|
|
|
2575 |
$access = true;
|
|
|
2576 |
break;
|
|
|
2577 |
}
|
|
|
2578 |
}
|
|
|
2579 |
}
|
|
|
2580 |
} else {
|
|
|
2581 |
// User is not enrolled and is not allowed to browse courses here.
|
|
|
2582 |
if ($preventredirect) {
|
|
|
2583 |
throw new require_login_exception('Course is not available');
|
|
|
2584 |
}
|
|
|
2585 |
$PAGE->set_context(null);
|
|
|
2586 |
// We need to override the navigation URL as the course won't have been added to the navigation and thus
|
|
|
2587 |
// the navigation will mess up when trying to find it.
|
|
|
2588 |
navigation_node::override_active_url(new moodle_url('/'));
|
|
|
2589 |
notice(get_string('coursehidden'), $CFG->wwwroot .'/');
|
|
|
2590 |
}
|
|
|
2591 |
}
|
|
|
2592 |
}
|
|
|
2593 |
|
|
|
2594 |
if (!$access) {
|
|
|
2595 |
if ($preventredirect) {
|
|
|
2596 |
throw new require_login_exception('Not enrolled');
|
|
|
2597 |
}
|
|
|
2598 |
if ($setwantsurltome) {
|
|
|
2599 |
$SESSION->wantsurl = qualified_me();
|
|
|
2600 |
}
|
|
|
2601 |
redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
|
|
|
2602 |
}
|
|
|
2603 |
}
|
|
|
2604 |
|
|
|
2605 |
// Check whether the activity has been scheduled for deletion. If so, then deny access, even for admins.
|
|
|
2606 |
if ($cm && $cm->deletioninprogress) {
|
|
|
2607 |
if ($preventredirect) {
|
|
|
2608 |
throw new moodle_exception('activityisscheduledfordeletion');
|
|
|
2609 |
}
|
|
|
2610 |
require_once($CFG->dirroot . '/course/lib.php');
|
|
|
2611 |
redirect(course_get_url($course), get_string('activityisscheduledfordeletion', 'error'));
|
|
|
2612 |
}
|
|
|
2613 |
|
|
|
2614 |
// Check visibility of activity to current user; includes visible flag, conditional availability, etc.
|
|
|
2615 |
if ($cm && !$cm->uservisible) {
|
|
|
2616 |
if ($preventredirect) {
|
|
|
2617 |
throw new require_login_exception('Activity is hidden');
|
|
|
2618 |
}
|
|
|
2619 |
// Get the error message that activity is not available and why (if explanation can be shown to the user).
|
|
|
2620 |
$PAGE->set_course($course);
|
|
|
2621 |
$renderer = $PAGE->get_renderer('course');
|
|
|
2622 |
$message = $renderer->course_section_cm_unavailable_error_message($cm);
|
|
|
2623 |
redirect(course_get_url($course), $message, null, \core\output\notification::NOTIFY_ERROR);
|
|
|
2624 |
}
|
|
|
2625 |
|
|
|
2626 |
// Set the global $COURSE.
|
|
|
2627 |
if ($cm) {
|
|
|
2628 |
$PAGE->set_cm($cm, $course);
|
|
|
2629 |
$PAGE->set_pagelayout('incourse');
|
|
|
2630 |
} else if (!empty($courseorid)) {
|
|
|
2631 |
$PAGE->set_course($course);
|
|
|
2632 |
}
|
|
|
2633 |
|
|
|
2634 |
foreach ($afterlogins as $plugintype => $plugins) {
|
|
|
2635 |
foreach ($plugins as $pluginfunction) {
|
|
|
2636 |
$pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
|
|
|
2637 |
}
|
|
|
2638 |
}
|
|
|
2639 |
|
|
|
2640 |
// Finally access granted, update lastaccess times.
|
|
|
2641 |
// Do not update access time for webservice or ajax requests.
|
|
|
2642 |
if (!WS_SERVER && !AJAX_SCRIPT) {
|
|
|
2643 |
user_accesstime_log($course->id);
|
|
|
2644 |
}
|
|
|
2645 |
}
|
|
|
2646 |
|
|
|
2647 |
/**
|
|
|
2648 |
* A convenience function for where we must be logged in as admin
|
|
|
2649 |
* @return void
|
|
|
2650 |
*/
|
|
|
2651 |
function require_admin() {
|
|
|
2652 |
require_login(null, false);
|
|
|
2653 |
require_capability('moodle/site:config', context_system::instance());
|
|
|
2654 |
}
|
|
|
2655 |
|
|
|
2656 |
/**
|
|
|
2657 |
* This function just makes sure a user is logged out.
|
|
|
2658 |
*
|
|
|
2659 |
* @package core_access
|
|
|
2660 |
* @category access
|
|
|
2661 |
*/
|
|
|
2662 |
function require_logout() {
|
|
|
2663 |
global $USER, $DB;
|
|
|
2664 |
|
|
|
2665 |
if (!isloggedin()) {
|
|
|
2666 |
// This should not happen often, no need for hooks or events here.
|
|
|
2667 |
\core\session\manager::terminate_current();
|
|
|
2668 |
return;
|
|
|
2669 |
}
|
|
|
2670 |
|
|
|
2671 |
// Execute hooks before action.
|
|
|
2672 |
$authplugins = array();
|
|
|
2673 |
$authsequence = get_enabled_auth_plugins();
|
|
|
2674 |
foreach ($authsequence as $authname) {
|
|
|
2675 |
$authplugins[$authname] = get_auth_plugin($authname);
|
|
|
2676 |
$authplugins[$authname]->prelogout_hook();
|
|
|
2677 |
}
|
|
|
2678 |
|
|
|
2679 |
// Store info that gets removed during logout.
|
|
|
2680 |
$sid = session_id();
|
|
|
2681 |
$event = \core\event\user_loggedout::create(
|
|
|
2682 |
array(
|
|
|
2683 |
'userid' => $USER->id,
|
|
|
2684 |
'objectid' => $USER->id,
|
|
|
2685 |
'other' => array('sessionid' => $sid),
|
|
|
2686 |
)
|
|
|
2687 |
);
|
|
|
2688 |
if ($session = $DB->get_record('sessions', array('sid'=>$sid))) {
|
|
|
2689 |
$event->add_record_snapshot('sessions', $session);
|
|
|
2690 |
}
|
|
|
2691 |
|
|
|
2692 |
// Clone of $USER object to be used by auth plugins.
|
|
|
2693 |
$user = fullclone($USER);
|
|
|
2694 |
|
|
|
2695 |
// Delete session record and drop $_SESSION content.
|
|
|
2696 |
\core\session\manager::terminate_current();
|
|
|
2697 |
|
|
|
2698 |
// Trigger event AFTER action.
|
|
|
2699 |
$event->trigger();
|
|
|
2700 |
|
|
|
2701 |
// Hook to execute auth plugins redirection after event trigger.
|
|
|
2702 |
foreach ($authplugins as $authplugin) {
|
|
|
2703 |
$authplugin->postlogout_hook($user);
|
|
|
2704 |
}
|
|
|
2705 |
}
|
|
|
2706 |
|
|
|
2707 |
/**
|
|
|
2708 |
* Weaker version of require_login()
|
|
|
2709 |
*
|
|
|
2710 |
* This is a weaker version of {@link require_login()} which only requires login
|
|
|
2711 |
* when called from within a course rather than the site page, unless
|
|
|
2712 |
* the forcelogin option is turned on.
|
|
|
2713 |
* @see require_login()
|
|
|
2714 |
*
|
|
|
2715 |
* @package core_access
|
|
|
2716 |
* @category access
|
|
|
2717 |
*
|
|
|
2718 |
* @param mixed $courseorid The course object or id in question
|
|
|
2719 |
* @param bool $autologinguest Allow autologin guests if that is wanted
|
|
|
2720 |
* @param object $cm Course activity module if known
|
|
|
2721 |
* @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
|
|
|
2722 |
* true. Used to avoid (=false) some scripts (file.php...) to set that variable,
|
|
|
2723 |
* in order to keep redirects working properly. MDL-14495
|
|
|
2724 |
* @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
|
|
|
2725 |
* @return void
|
|
|
2726 |
* @throws coding_exception
|
|
|
2727 |
*/
|
|
|
2728 |
function require_course_login($courseorid, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false) {
|
|
|
2729 |
global $CFG, $PAGE, $SITE;
|
|
|
2730 |
$issite = ((is_object($courseorid) and $courseorid->id == SITEID)
|
|
|
2731 |
or (!is_object($courseorid) and $courseorid == SITEID));
|
|
|
2732 |
if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
|
|
|
2733 |
// Note: nearly all pages call get_fast_modinfo anyway and it does not make any
|
|
|
2734 |
// db queries so this is not really a performance concern, however it is obviously
|
|
|
2735 |
// better if you use get_fast_modinfo to get the cm before calling this.
|
|
|
2736 |
if (is_object($courseorid)) {
|
|
|
2737 |
$course = $courseorid;
|
|
|
2738 |
} else {
|
|
|
2739 |
$course = clone($SITE);
|
|
|
2740 |
}
|
|
|
2741 |
$modinfo = get_fast_modinfo($course);
|
|
|
2742 |
$cm = $modinfo->get_cm($cm->id);
|
|
|
2743 |
}
|
|
|
2744 |
if (!empty($CFG->forcelogin)) {
|
|
|
2745 |
// Login required for both SITE and courses.
|
|
|
2746 |
require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
|
|
|
2747 |
|
|
|
2748 |
} else if ($issite && !empty($cm) and !$cm->uservisible) {
|
|
|
2749 |
// Always login for hidden activities.
|
|
|
2750 |
require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
|
|
|
2751 |
|
|
|
2752 |
} else if (isloggedin() && !isguestuser()) {
|
|
|
2753 |
// User is already logged in. Make sure the login is complete (user is fully setup, policies agreed).
|
|
|
2754 |
require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
|
|
|
2755 |
|
|
|
2756 |
} else if ($issite) {
|
|
|
2757 |
// Login for SITE not required.
|
|
|
2758 |
// We still need to instatiate PAGE vars properly so that things that rely on it like navigation function correctly.
|
|
|
2759 |
if (!empty($courseorid)) {
|
|
|
2760 |
if (is_object($courseorid)) {
|
|
|
2761 |
$course = $courseorid;
|
|
|
2762 |
} else {
|
|
|
2763 |
$course = clone $SITE;
|
|
|
2764 |
}
|
|
|
2765 |
if ($cm) {
|
|
|
2766 |
if ($cm->course != $course->id) {
|
|
|
2767 |
throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
|
|
|
2768 |
}
|
|
|
2769 |
$PAGE->set_cm($cm, $course);
|
|
|
2770 |
$PAGE->set_pagelayout('incourse');
|
|
|
2771 |
} else {
|
|
|
2772 |
$PAGE->set_course($course);
|
|
|
2773 |
}
|
|
|
2774 |
} else {
|
|
|
2775 |
// If $PAGE->course, and hence $PAGE->context, have not already been set up properly, set them up now.
|
|
|
2776 |
$PAGE->set_course($PAGE->course);
|
|
|
2777 |
}
|
|
|
2778 |
// Do not update access time for webservice or ajax requests.
|
|
|
2779 |
if (!WS_SERVER && !AJAX_SCRIPT) {
|
|
|
2780 |
user_accesstime_log(SITEID);
|
|
|
2781 |
}
|
|
|
2782 |
return;
|
|
|
2783 |
|
|
|
2784 |
} else {
|
|
|
2785 |
// Course login always required.
|
|
|
2786 |
require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
|
|
|
2787 |
}
|
|
|
2788 |
}
|
|
|
2789 |
|
|
|
2790 |
/**
|
|
|
2791 |
* Validates a user key, checking if the key exists, is not expired and the remote ip is correct.
|
|
|
2792 |
*
|
|
|
2793 |
* @param string $keyvalue the key value
|
|
|
2794 |
* @param string $script unique script identifier
|
|
|
2795 |
* @param int $instance instance id
|
|
|
2796 |
* @return stdClass the key entry in the user_private_key table
|
|
|
2797 |
* @since Moodle 3.2
|
|
|
2798 |
* @throws moodle_exception
|
|
|
2799 |
*/
|
|
|
2800 |
function validate_user_key($keyvalue, $script, $instance) {
|
|
|
2801 |
global $DB;
|
|
|
2802 |
|
|
|
2803 |
if (!$key = $DB->get_record('user_private_key', array('script' => $script, 'value' => $keyvalue, 'instance' => $instance))) {
|
|
|
2804 |
throw new \moodle_exception('invalidkey');
|
|
|
2805 |
}
|
|
|
2806 |
|
|
|
2807 |
if (!empty($key->validuntil) and $key->validuntil < time()) {
|
|
|
2808 |
throw new \moodle_exception('expiredkey');
|
|
|
2809 |
}
|
|
|
2810 |
|
|
|
2811 |
if ($key->iprestriction) {
|
|
|
2812 |
$remoteaddr = getremoteaddr(null);
|
|
|
2813 |
if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
|
|
|
2814 |
throw new \moodle_exception('ipmismatch');
|
|
|
2815 |
}
|
|
|
2816 |
}
|
|
|
2817 |
return $key;
|
|
|
2818 |
}
|
|
|
2819 |
|
|
|
2820 |
/**
|
|
|
2821 |
* Require key login. Function terminates with error if key not found or incorrect.
|
|
|
2822 |
*
|
|
|
2823 |
* @uses NO_MOODLE_COOKIES
|
|
|
2824 |
* @uses PARAM_ALPHANUM
|
|
|
2825 |
* @param string $script unique script identifier
|
|
|
2826 |
* @param int $instance optional instance id
|
|
|
2827 |
* @param string $keyvalue The key. If not supplied, this will be fetched from the current session.
|
|
|
2828 |
* @return int Instance ID
|
|
|
2829 |
*/
|
|
|
2830 |
function require_user_key_login($script, $instance = null, $keyvalue = null) {
|
|
|
2831 |
global $DB;
|
|
|
2832 |
|
|
|
2833 |
if (!NO_MOODLE_COOKIES) {
|
|
|
2834 |
throw new \moodle_exception('sessioncookiesdisable');
|
|
|
2835 |
}
|
|
|
2836 |
|
|
|
2837 |
// Extra safety.
|
|
|
2838 |
\core\session\manager::write_close();
|
|
|
2839 |
|
|
|
2840 |
if (null === $keyvalue) {
|
|
|
2841 |
$keyvalue = required_param('key', PARAM_ALPHANUM);
|
|
|
2842 |
}
|
|
|
2843 |
|
|
|
2844 |
$key = validate_user_key($keyvalue, $script, $instance);
|
|
|
2845 |
|
|
|
2846 |
if (!$user = $DB->get_record('user', array('id' => $key->userid))) {
|
|
|
2847 |
throw new \moodle_exception('invaliduserid');
|
|
|
2848 |
}
|
|
|
2849 |
|
|
|
2850 |
core_user::require_active_user($user, true, true);
|
|
|
2851 |
|
|
|
2852 |
// Emulate normal session.
|
|
|
2853 |
enrol_check_plugins($user, false);
|
|
|
2854 |
\core\session\manager::set_user($user);
|
|
|
2855 |
|
|
|
2856 |
// Note we are not using normal login.
|
|
|
2857 |
if (!defined('USER_KEY_LOGIN')) {
|
|
|
2858 |
define('USER_KEY_LOGIN', true);
|
|
|
2859 |
}
|
|
|
2860 |
|
|
|
2861 |
// Return instance id - it might be empty.
|
|
|
2862 |
return $key->instance;
|
|
|
2863 |
}
|
|
|
2864 |
|
|
|
2865 |
/**
|
|
|
2866 |
* Creates a new private user access key.
|
|
|
2867 |
*
|
|
|
2868 |
* @param string $script unique target identifier
|
|
|
2869 |
* @param int $userid
|
|
|
2870 |
* @param int $instance optional instance id
|
|
|
2871 |
* @param string $iprestriction optional ip restricted access
|
|
|
2872 |
* @param int $validuntil key valid only until given data
|
|
|
2873 |
* @return string access key value
|
|
|
2874 |
*/
|
|
|
2875 |
function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
|
|
|
2876 |
global $DB;
|
|
|
2877 |
|
|
|
2878 |
$key = new stdClass();
|
|
|
2879 |
$key->script = $script;
|
|
|
2880 |
$key->userid = $userid;
|
|
|
2881 |
$key->instance = $instance;
|
|
|
2882 |
$key->iprestriction = $iprestriction;
|
|
|
2883 |
$key->validuntil = $validuntil;
|
|
|
2884 |
$key->timecreated = time();
|
|
|
2885 |
|
|
|
2886 |
// Something long and unique.
|
|
|
2887 |
$key->value = md5($userid.'_'.time().random_string(40));
|
|
|
2888 |
while ($DB->record_exists('user_private_key', array('value' => $key->value))) {
|
|
|
2889 |
// Must be unique.
|
|
|
2890 |
$key->value = md5($userid.'_'.time().random_string(40));
|
|
|
2891 |
}
|
|
|
2892 |
$DB->insert_record('user_private_key', $key);
|
|
|
2893 |
return $key->value;
|
|
|
2894 |
}
|
|
|
2895 |
|
|
|
2896 |
/**
|
|
|
2897 |
* Delete the user's new private user access keys for a particular script.
|
|
|
2898 |
*
|
|
|
2899 |
* @param string $script unique target identifier
|
|
|
2900 |
* @param int $userid
|
|
|
2901 |
* @return void
|
|
|
2902 |
*/
|
|
|
2903 |
function delete_user_key($script, $userid) {
|
|
|
2904 |
global $DB;
|
|
|
2905 |
$DB->delete_records('user_private_key', array('script' => $script, 'userid' => $userid));
|
|
|
2906 |
}
|
|
|
2907 |
|
|
|
2908 |
/**
|
|
|
2909 |
* Gets a private user access key (and creates one if one doesn't exist).
|
|
|
2910 |
*
|
|
|
2911 |
* @param string $script unique target identifier
|
|
|
2912 |
* @param int $userid
|
|
|
2913 |
* @param int $instance optional instance id
|
|
|
2914 |
* @param string $iprestriction optional ip restricted access
|
|
|
2915 |
* @param int $validuntil key valid only until given date
|
|
|
2916 |
* @return string access key value
|
|
|
2917 |
*/
|
|
|
2918 |
function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
|
|
|
2919 |
global $DB;
|
|
|
2920 |
|
|
|
2921 |
if ($key = $DB->get_record('user_private_key', array('script' => $script, 'userid' => $userid,
|
|
|
2922 |
'instance' => $instance, 'iprestriction' => $iprestriction,
|
|
|
2923 |
'validuntil' => $validuntil))) {
|
|
|
2924 |
return $key->value;
|
|
|
2925 |
} else {
|
|
|
2926 |
return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
|
|
|
2927 |
}
|
|
|
2928 |
}
|
|
|
2929 |
|
|
|
2930 |
|
|
|
2931 |
/**
|
|
|
2932 |
* Modify the user table by setting the currently logged in user's last login to now.
|
|
|
2933 |
*
|
|
|
2934 |
* @return bool Always returns true
|
|
|
2935 |
*/
|
|
|
2936 |
function update_user_login_times() {
|
|
|
2937 |
global $USER, $DB, $SESSION;
|
|
|
2938 |
|
|
|
2939 |
if (isguestuser()) {
|
|
|
2940 |
// Do not update guest access times/ips for performance.
|
|
|
2941 |
return true;
|
|
|
2942 |
}
|
|
|
2943 |
|
|
|
2944 |
if (defined('USER_KEY_LOGIN') && USER_KEY_LOGIN === true) {
|
|
|
2945 |
// Do not update user login time when using user key login.
|
|
|
2946 |
return true;
|
|
|
2947 |
}
|
|
|
2948 |
|
|
|
2949 |
$now = time();
|
|
|
2950 |
|
|
|
2951 |
$user = new stdClass();
|
|
|
2952 |
$user->id = $USER->id;
|
|
|
2953 |
|
|
|
2954 |
// Make sure all users that logged in have some firstaccess.
|
|
|
2955 |
if ($USER->firstaccess == 0) {
|
|
|
2956 |
$USER->firstaccess = $user->firstaccess = $now;
|
|
|
2957 |
}
|
|
|
2958 |
|
|
|
2959 |
// Store the previous current as lastlogin.
|
|
|
2960 |
$USER->lastlogin = $user->lastlogin = $USER->currentlogin;
|
|
|
2961 |
|
|
|
2962 |
$USER->currentlogin = $user->currentlogin = $now;
|
|
|
2963 |
|
|
|
2964 |
// Function user_accesstime_log() may not update immediately, better do it here.
|
|
|
2965 |
$USER->lastaccess = $user->lastaccess = $now;
|
|
|
2966 |
$SESSION->userpreviousip = $USER->lastip;
|
|
|
2967 |
$USER->lastip = $user->lastip = getremoteaddr();
|
|
|
2968 |
|
|
|
2969 |
// Note: do not call user_update_user() here because this is part of the login process,
|
|
|
2970 |
// the login event means that these fields were updated.
|
|
|
2971 |
$DB->update_record('user', $user);
|
|
|
2972 |
return true;
|
|
|
2973 |
}
|
|
|
2974 |
|
|
|
2975 |
/**
|
|
|
2976 |
* Determines if a user has completed setting up their account.
|
|
|
2977 |
*
|
|
|
2978 |
* The lax mode (with $strict = false) has been introduced for special cases
|
|
|
2979 |
* only where we want to skip certain checks intentionally. This is valid in
|
|
|
2980 |
* certain mnet or ajax scenarios when the user cannot / should not be
|
|
|
2981 |
* redirected to edit their profile. In most cases, you should perform the
|
|
|
2982 |
* strict check.
|
|
|
2983 |
*
|
|
|
2984 |
* @param stdClass $user A {@link $USER} object to test for the existence of a valid name and email
|
|
|
2985 |
* @param bool $strict Be more strict and assert id and custom profile fields set, too
|
|
|
2986 |
* @return bool
|
|
|
2987 |
*/
|
|
|
2988 |
function user_not_fully_set_up($user, $strict = true) {
|
|
|
2989 |
global $CFG, $SESSION, $USER;
|
|
|
2990 |
require_once($CFG->dirroot.'/user/profile/lib.php');
|
|
|
2991 |
|
|
|
2992 |
// If the user is setup then store this in the session to avoid re-checking.
|
|
|
2993 |
// Some edge cases are when the users email starts to bounce or the
|
|
|
2994 |
// configuration for custom fields has changed while they are logged in so
|
|
|
2995 |
// we re-check this fully every hour for the rare cases it has changed.
|
|
|
2996 |
if (isset($USER->id) && isset($user->id) && $USER->id === $user->id &&
|
|
|
2997 |
isset($SESSION->fullysetupstrict) && (time() - $SESSION->fullysetupstrict) < HOURSECS) {
|
|
|
2998 |
return false;
|
|
|
2999 |
}
|
|
|
3000 |
|
|
|
3001 |
if (isguestuser($user)) {
|
|
|
3002 |
return false;
|
|
|
3003 |
}
|
|
|
3004 |
|
|
|
3005 |
if (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)) {
|
|
|
3006 |
return true;
|
|
|
3007 |
}
|
|
|
3008 |
|
|
|
3009 |
if ($strict) {
|
|
|
3010 |
if (empty($user->id)) {
|
|
|
3011 |
// Strict mode can be used with existing accounts only.
|
|
|
3012 |
return true;
|
|
|
3013 |
}
|
|
|
3014 |
if (!profile_has_required_custom_fields_set($user->id)) {
|
|
|
3015 |
return true;
|
|
|
3016 |
}
|
|
|
3017 |
if (isset($USER->id) && isset($user->id) && $USER->id === $user->id) {
|
|
|
3018 |
$SESSION->fullysetupstrict = time();
|
|
|
3019 |
}
|
|
|
3020 |
}
|
|
|
3021 |
|
|
|
3022 |
return false;
|
|
|
3023 |
}
|
|
|
3024 |
|
|
|
3025 |
/**
|
|
|
3026 |
* Check whether the user has exceeded the bounce threshold
|
|
|
3027 |
*
|
|
|
3028 |
* @param stdClass $user A {@link $USER} object
|
|
|
3029 |
* @return bool true => User has exceeded bounce threshold
|
|
|
3030 |
*/
|
|
|
3031 |
function over_bounce_threshold($user) {
|
|
|
3032 |
global $CFG, $DB;
|
|
|
3033 |
|
|
|
3034 |
if (empty($CFG->handlebounces)) {
|
|
|
3035 |
return false;
|
|
|
3036 |
}
|
|
|
3037 |
|
|
|
3038 |
if (empty($user->id)) {
|
|
|
3039 |
// No real (DB) user, nothing to do here.
|
|
|
3040 |
return false;
|
|
|
3041 |
}
|
|
|
3042 |
|
|
|
3043 |
// Set sensible defaults.
|
|
|
3044 |
if (empty($CFG->minbounces)) {
|
|
|
3045 |
$CFG->minbounces = 10;
|
|
|
3046 |
}
|
|
|
3047 |
if (empty($CFG->bounceratio)) {
|
|
|
3048 |
$CFG->bounceratio = .20;
|
|
|
3049 |
}
|
|
|
3050 |
$bouncecount = 0;
|
|
|
3051 |
$sendcount = 0;
|
|
|
3052 |
if ($bounce = $DB->get_record('user_preferences', array ('userid' => $user->id, 'name' => 'email_bounce_count'))) {
|
|
|
3053 |
$bouncecount = $bounce->value;
|
|
|
3054 |
}
|
|
|
3055 |
if ($send = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_send_count'))) {
|
|
|
3056 |
$sendcount = $send->value;
|
|
|
3057 |
}
|
|
|
3058 |
return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
|
|
|
3059 |
}
|
|
|
3060 |
|
|
|
3061 |
/**
|
|
|
3062 |
* Used to increment or reset email sent count
|
|
|
3063 |
*
|
|
|
3064 |
* @param stdClass $user object containing an id
|
|
|
3065 |
* @param bool $reset will reset the count to 0
|
|
|
3066 |
* @return void
|
|
|
3067 |
*/
|
|
|
3068 |
function set_send_count($user, $reset=false) {
|
|
|
3069 |
global $DB;
|
|
|
3070 |
|
|
|
3071 |
if (empty($user->id)) {
|
|
|
3072 |
// No real (DB) user, nothing to do here.
|
|
|
3073 |
return;
|
|
|
3074 |
}
|
|
|
3075 |
|
|
|
3076 |
if ($pref = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_send_count'))) {
|
|
|
3077 |
$pref->value = (!empty($reset)) ? 0 : $pref->value+1;
|
|
|
3078 |
$DB->update_record('user_preferences', $pref);
|
|
|
3079 |
} else if (!empty($reset)) {
|
|
|
3080 |
// If it's not there and we're resetting, don't bother. Make a new one.
|
|
|
3081 |
$pref = new stdClass();
|
|
|
3082 |
$pref->name = 'email_send_count';
|
|
|
3083 |
$pref->value = 1;
|
|
|
3084 |
$pref->userid = $user->id;
|
|
|
3085 |
$DB->insert_record('user_preferences', $pref, false);
|
|
|
3086 |
}
|
|
|
3087 |
}
|
|
|
3088 |
|
|
|
3089 |
/**
|
|
|
3090 |
* Increment or reset user's email bounce count
|
|
|
3091 |
*
|
|
|
3092 |
* @param stdClass $user object containing an id
|
|
|
3093 |
* @param bool $reset will reset the count to 0
|
|
|
3094 |
*/
|
|
|
3095 |
function set_bounce_count($user, $reset=false) {
|
|
|
3096 |
global $DB;
|
|
|
3097 |
|
|
|
3098 |
if ($pref = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_bounce_count'))) {
|
|
|
3099 |
$pref->value = (!empty($reset)) ? 0 : $pref->value+1;
|
|
|
3100 |
$DB->update_record('user_preferences', $pref);
|
|
|
3101 |
} else if (!empty($reset)) {
|
|
|
3102 |
// If it's not there and we're resetting, don't bother. Make a new one.
|
|
|
3103 |
$pref = new stdClass();
|
|
|
3104 |
$pref->name = 'email_bounce_count';
|
|
|
3105 |
$pref->value = 1;
|
|
|
3106 |
$pref->userid = $user->id;
|
|
|
3107 |
$DB->insert_record('user_preferences', $pref, false);
|
|
|
3108 |
}
|
|
|
3109 |
}
|
|
|
3110 |
|
|
|
3111 |
/**
|
|
|
3112 |
* Determines if the logged in user is currently moving an activity
|
|
|
3113 |
*
|
|
|
3114 |
* @param int $courseid The id of the course being tested
|
|
|
3115 |
* @return bool
|
|
|
3116 |
*/
|
|
|
3117 |
function ismoving($courseid) {
|
|
|
3118 |
global $USER;
|
|
|
3119 |
|
|
|
3120 |
if (!empty($USER->activitycopy)) {
|
|
|
3121 |
return ($USER->activitycopycourse == $courseid);
|
|
|
3122 |
}
|
|
|
3123 |
return false;
|
|
|
3124 |
}
|
|
|
3125 |
|
|
|
3126 |
/**
|
|
|
3127 |
* Returns a persons full name
|
|
|
3128 |
*
|
|
|
3129 |
* Given an object containing all of the users name values, this function returns a string with the full name of the person.
|
|
|
3130 |
* The result may depend on system settings or language. 'override' will force the alternativefullnameformat to be used. In
|
|
|
3131 |
* English, fullname as well as alternativefullnameformat is set to 'firstname lastname' by default. But you could have
|
|
|
3132 |
* fullname set to 'firstname lastname' and alternativefullnameformat set to 'firstname middlename alternatename lastname'.
|
|
|
3133 |
*
|
|
|
3134 |
* @param stdClass $user A {@link $USER} object to get full name of.
|
|
|
3135 |
* @param bool $override If true then the alternativefullnameformat format rather than fullnamedisplay format will be used.
|
|
|
3136 |
* @return string
|
|
|
3137 |
*/
|
|
|
3138 |
function fullname($user, $override=false) {
|
|
|
3139 |
// Note: We do not intend to deprecate this function any time soon as it is too widely used at this time.
|
|
|
3140 |
// Uses of it should be updated to use the new API and pass updated arguments.
|
|
|
3141 |
|
|
|
3142 |
// Return an empty string if there is no user.
|
|
|
3143 |
if (empty($user)) {
|
|
|
3144 |
return '';
|
|
|
3145 |
}
|
|
|
3146 |
|
|
|
3147 |
$options = ['override' => $override];
|
|
|
3148 |
return core_user::get_fullname($user, null, $options);
|
|
|
3149 |
}
|
|
|
3150 |
|
|
|
3151 |
/**
|
|
|
3152 |
* Reduces lines of duplicated code for getting user name fields.
|
|
|
3153 |
*
|
|
|
3154 |
* See also {@link user_picture::unalias()}
|
|
|
3155 |
*
|
|
|
3156 |
* @param object $addtoobject Object to add user name fields to.
|
|
|
3157 |
* @param object $secondobject Object that contains user name field information.
|
|
|
3158 |
* @param string $prefix prefix to be added to all fields (including $additionalfields) e.g. authorfirstname.
|
|
|
3159 |
* @param array $additionalfields Additional fields to be matched with data in the second object.
|
|
|
3160 |
* The key can be set to the user table field name.
|
|
|
3161 |
* @return object User name fields.
|
|
|
3162 |
*/
|
|
|
3163 |
function username_load_fields_from_object($addtoobject, $secondobject, $prefix = null, $additionalfields = null) {
|
|
|
3164 |
$fields = [];
|
|
|
3165 |
foreach (\core_user\fields::get_name_fields() as $field) {
|
|
|
3166 |
$fields[$field] = $prefix . $field;
|
|
|
3167 |
}
|
|
|
3168 |
if ($additionalfields) {
|
|
|
3169 |
// Additional fields can specify their own 'alias' such as 'id' => 'userid'. This checks to see if
|
|
|
3170 |
// the key is a number and then sets the key to the array value.
|
|
|
3171 |
foreach ($additionalfields as $key => $value) {
|
|
|
3172 |
if (is_numeric($key)) {
|
|
|
3173 |
$additionalfields[$value] = $prefix . $value;
|
|
|
3174 |
unset($additionalfields[$key]);
|
|
|
3175 |
} else {
|
|
|
3176 |
$additionalfields[$key] = $prefix . $value;
|
|
|
3177 |
}
|
|
|
3178 |
}
|
|
|
3179 |
$fields = array_merge($fields, $additionalfields);
|
|
|
3180 |
}
|
|
|
3181 |
foreach ($fields as $key => $field) {
|
|
|
3182 |
// Important that we have all of the user name fields present in the object that we are sending back.
|
|
|
3183 |
$addtoobject->$key = '';
|
|
|
3184 |
if (isset($secondobject->$field)) {
|
|
|
3185 |
$addtoobject->$key = $secondobject->$field;
|
|
|
3186 |
}
|
|
|
3187 |
}
|
|
|
3188 |
return $addtoobject;
|
|
|
3189 |
}
|
|
|
3190 |
|
|
|
3191 |
/**
|
|
|
3192 |
* Returns an array of values in order of occurance in a provided string.
|
|
|
3193 |
* The key in the result is the character postion in the string.
|
|
|
3194 |
*
|
|
|
3195 |
* @param array $values Values to be found in the string format
|
|
|
3196 |
* @param string $stringformat The string which may contain values being searched for.
|
|
|
3197 |
* @return array An array of values in order according to placement in the string format.
|
|
|
3198 |
*/
|
|
|
3199 |
function order_in_string($values, $stringformat) {
|
|
|
3200 |
$valuearray = array();
|
|
|
3201 |
foreach ($values as $value) {
|
|
|
3202 |
$pattern = "/$value\b/";
|
|
|
3203 |
// Using preg_match as strpos() may match values that are similar e.g. firstname and firstnamephonetic.
|
|
|
3204 |
if (preg_match($pattern, $stringformat)) {
|
|
|
3205 |
$replacement = "thing";
|
|
|
3206 |
// Replace the value with something more unique to ensure we get the right position when using strpos().
|
|
|
3207 |
$newformat = preg_replace($pattern, $replacement, $stringformat);
|
|
|
3208 |
$position = strpos($newformat, $replacement);
|
|
|
3209 |
$valuearray[$position] = $value;
|
|
|
3210 |
}
|
|
|
3211 |
}
|
|
|
3212 |
ksort($valuearray);
|
|
|
3213 |
return $valuearray;
|
|
|
3214 |
}
|
|
|
3215 |
|
|
|
3216 |
/**
|
|
|
3217 |
* Returns whether a given authentication plugin exists.
|
|
|
3218 |
*
|
|
|
3219 |
* @param string $auth Form of authentication to check for. Defaults to the global setting in {@link $CFG}.
|
|
|
3220 |
* @return boolean Whether the plugin is available.
|
|
|
3221 |
*/
|
|
|
3222 |
function exists_auth_plugin($auth) {
|
|
|
3223 |
global $CFG;
|
|
|
3224 |
|
|
|
3225 |
if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
|
|
|
3226 |
return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
|
|
|
3227 |
}
|
|
|
3228 |
return false;
|
|
|
3229 |
}
|
|
|
3230 |
|
|
|
3231 |
/**
|
|
|
3232 |
* Checks if a given plugin is in the list of enabled authentication plugins.
|
|
|
3233 |
*
|
|
|
3234 |
* @param string $auth Authentication plugin.
|
|
|
3235 |
* @return boolean Whether the plugin is enabled.
|
|
|
3236 |
*/
|
|
|
3237 |
function is_enabled_auth($auth) {
|
|
|
3238 |
if (empty($auth)) {
|
|
|
3239 |
return false;
|
|
|
3240 |
}
|
|
|
3241 |
|
|
|
3242 |
$enabled = get_enabled_auth_plugins();
|
|
|
3243 |
|
|
|
3244 |
return in_array($auth, $enabled);
|
|
|
3245 |
}
|
|
|
3246 |
|
|
|
3247 |
/**
|
|
|
3248 |
* Returns an authentication plugin instance.
|
|
|
3249 |
*
|
|
|
3250 |
* @param string $auth name of authentication plugin
|
|
|
3251 |
* @return auth_plugin_base An instance of the required authentication plugin.
|
|
|
3252 |
*/
|
|
|
3253 |
function get_auth_plugin($auth) {
|
|
|
3254 |
global $CFG;
|
|
|
3255 |
|
|
|
3256 |
// Check the plugin exists first.
|
|
|
3257 |
if (! exists_auth_plugin($auth)) {
|
|
|
3258 |
throw new \moodle_exception('authpluginnotfound', 'debug', '', $auth);
|
|
|
3259 |
}
|
|
|
3260 |
|
|
|
3261 |
// Return auth plugin instance.
|
|
|
3262 |
require_once("{$CFG->dirroot}/auth/$auth/auth.php");
|
|
|
3263 |
$class = "auth_plugin_$auth";
|
|
|
3264 |
return new $class;
|
|
|
3265 |
}
|
|
|
3266 |
|
|
|
3267 |
/**
|
|
|
3268 |
* Returns array of active auth plugins.
|
|
|
3269 |
*
|
|
|
3270 |
* @param bool $fix fix $CFG->auth if needed. Only set if logged in as admin.
|
|
|
3271 |
* @return array
|
|
|
3272 |
*/
|
|
|
3273 |
function get_enabled_auth_plugins($fix=false) {
|
|
|
3274 |
global $CFG;
|
|
|
3275 |
|
|
|
3276 |
$default = array('manual', 'nologin');
|
|
|
3277 |
|
|
|
3278 |
if (empty($CFG->auth)) {
|
|
|
3279 |
$auths = array();
|
|
|
3280 |
} else {
|
|
|
3281 |
$auths = explode(',', $CFG->auth);
|
|
|
3282 |
}
|
|
|
3283 |
|
|
|
3284 |
$auths = array_unique($auths);
|
|
|
3285 |
$oldauthconfig = implode(',', $auths);
|
|
|
3286 |
foreach ($auths as $k => $authname) {
|
|
|
3287 |
if (in_array($authname, $default)) {
|
|
|
3288 |
// The manual and nologin plugin never need to be stored.
|
|
|
3289 |
unset($auths[$k]);
|
|
|
3290 |
} else if (!exists_auth_plugin($authname)) {
|
|
|
3291 |
debugging(get_string('authpluginnotfound', 'debug', $authname));
|
|
|
3292 |
unset($auths[$k]);
|
|
|
3293 |
}
|
|
|
3294 |
}
|
|
|
3295 |
|
|
|
3296 |
// Ideally only explicit interaction from a human admin should trigger a
|
|
|
3297 |
// change in auth config, see MDL-70424 for details.
|
|
|
3298 |
if ($fix) {
|
|
|
3299 |
$newconfig = implode(',', $auths);
|
|
|
3300 |
if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
|
|
|
3301 |
add_to_config_log('auth', $oldauthconfig, $newconfig, 'core');
|
|
|
3302 |
set_config('auth', $newconfig);
|
|
|
3303 |
}
|
|
|
3304 |
}
|
|
|
3305 |
|
|
|
3306 |
return (array_merge($default, $auths));
|
|
|
3307 |
}
|
|
|
3308 |
|
|
|
3309 |
/**
|
|
|
3310 |
* Returns true if an internal authentication method is being used.
|
|
|
3311 |
* if method not specified then, global default is assumed
|
|
|
3312 |
*
|
|
|
3313 |
* @param string $auth Form of authentication required
|
|
|
3314 |
* @return bool
|
|
|
3315 |
*/
|
|
|
3316 |
function is_internal_auth($auth) {
|
|
|
3317 |
// Throws error if bad $auth.
|
|
|
3318 |
$authplugin = get_auth_plugin($auth);
|
|
|
3319 |
return $authplugin->is_internal();
|
|
|
3320 |
}
|
|
|
3321 |
|
|
|
3322 |
/**
|
|
|
3323 |
* Returns true if the user is a 'restored' one.
|
|
|
3324 |
*
|
|
|
3325 |
* Used in the login process to inform the user and allow him/her to reset the password
|
|
|
3326 |
*
|
|
|
3327 |
* @param string $username username to be checked
|
|
|
3328 |
* @return bool
|
|
|
3329 |
*/
|
|
|
3330 |
function is_restored_user($username) {
|
|
|
3331 |
global $CFG, $DB;
|
|
|
3332 |
|
|
|
3333 |
return $DB->record_exists('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id, 'password' => 'restored'));
|
|
|
3334 |
}
|
|
|
3335 |
|
|
|
3336 |
/**
|
|
|
3337 |
* Returns an array of user fields
|
|
|
3338 |
*
|
|
|
3339 |
* @return array User field/column names
|
|
|
3340 |
*/
|
|
|
3341 |
function get_user_fieldnames() {
|
|
|
3342 |
global $DB;
|
|
|
3343 |
|
|
|
3344 |
$fieldarray = $DB->get_columns('user');
|
|
|
3345 |
unset($fieldarray['id']);
|
|
|
3346 |
$fieldarray = array_keys($fieldarray);
|
|
|
3347 |
|
|
|
3348 |
return $fieldarray;
|
|
|
3349 |
}
|
|
|
3350 |
|
|
|
3351 |
/**
|
|
|
3352 |
* Returns the string of the language for the new user.
|
|
|
3353 |
*
|
|
|
3354 |
* @return string language for the new user
|
|
|
3355 |
*/
|
|
|
3356 |
function get_newuser_language() {
|
|
|
3357 |
global $CFG, $SESSION;
|
|
|
3358 |
return (!empty($CFG->autolangusercreation) && !empty($SESSION->lang)) ? $SESSION->lang : $CFG->lang;
|
|
|
3359 |
}
|
|
|
3360 |
|
|
|
3361 |
/**
|
|
|
3362 |
* Creates a bare-bones user record
|
|
|
3363 |
*
|
|
|
3364 |
* @todo Outline auth types and provide code example
|
|
|
3365 |
*
|
|
|
3366 |
* @param string $username New user's username to add to record
|
|
|
3367 |
* @param string $password New user's password to add to record
|
|
|
3368 |
* @param string $auth Form of authentication required
|
|
|
3369 |
* @return stdClass A complete user object
|
|
|
3370 |
*/
|
|
|
3371 |
function create_user_record($username, $password, $auth = 'manual') {
|
|
|
3372 |
global $CFG, $DB, $SESSION;
|
|
|
3373 |
require_once($CFG->dirroot.'/user/profile/lib.php');
|
|
|
3374 |
require_once($CFG->dirroot.'/user/lib.php');
|
|
|
3375 |
|
|
|
3376 |
// Just in case check text case.
|
|
|
3377 |
$username = trim(core_text::strtolower($username));
|
|
|
3378 |
|
|
|
3379 |
$authplugin = get_auth_plugin($auth);
|
|
|
3380 |
$customfields = $authplugin->get_custom_user_profile_fields();
|
|
|
3381 |
$newuser = new stdClass();
|
|
|
3382 |
if ($newinfo = $authplugin->get_userinfo($username)) {
|
|
|
3383 |
$newinfo = truncate_userinfo($newinfo);
|
|
|
3384 |
foreach ($newinfo as $key => $value) {
|
|
|
3385 |
if (in_array($key, $authplugin->userfields) || (in_array($key, $customfields))) {
|
|
|
3386 |
$newuser->$key = $value;
|
|
|
3387 |
}
|
|
|
3388 |
}
|
|
|
3389 |
}
|
|
|
3390 |
|
|
|
3391 |
if (!empty($newuser->email)) {
|
|
|
3392 |
if (email_is_not_allowed($newuser->email)) {
|
|
|
3393 |
unset($newuser->email);
|
|
|
3394 |
}
|
|
|
3395 |
}
|
|
|
3396 |
|
|
|
3397 |
$newuser->auth = $auth;
|
|
|
3398 |
$newuser->username = $username;
|
|
|
3399 |
|
|
|
3400 |
// Fix for MDL-8480
|
|
|
3401 |
// user CFG lang for user if $newuser->lang is empty
|
|
|
3402 |
// or $user->lang is not an installed language.
|
|
|
3403 |
if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
|
|
|
3404 |
$newuser->lang = get_newuser_language();
|
|
|
3405 |
}
|
|
|
3406 |
$newuser->confirmed = 1;
|
|
|
3407 |
$newuser->lastip = getremoteaddr();
|
|
|
3408 |
$newuser->timecreated = time();
|
|
|
3409 |
$newuser->timemodified = $newuser->timecreated;
|
|
|
3410 |
$newuser->mnethostid = $CFG->mnet_localhost_id;
|
|
|
3411 |
|
|
|
3412 |
$newuser->id = user_create_user($newuser, false, false);
|
|
|
3413 |
|
|
|
3414 |
// Save user profile data.
|
|
|
3415 |
profile_save_data($newuser);
|
|
|
3416 |
|
|
|
3417 |
$user = get_complete_user_data('id', $newuser->id);
|
|
|
3418 |
if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})) {
|
|
|
3419 |
set_user_preference('auth_forcepasswordchange', 1, $user);
|
|
|
3420 |
}
|
|
|
3421 |
// Set the password.
|
|
|
3422 |
update_internal_user_password($user, $password);
|
|
|
3423 |
|
|
|
3424 |
// Trigger event.
|
|
|
3425 |
\core\event\user_created::create_from_userid($newuser->id)->trigger();
|
|
|
3426 |
|
|
|
3427 |
return $user;
|
|
|
3428 |
}
|
|
|
3429 |
|
|
|
3430 |
/**
|
|
|
3431 |
* Will update a local user record from an external source (MNET users can not be updated using this method!).
|
|
|
3432 |
*
|
|
|
3433 |
* @param string $username user's username to update the record
|
|
|
3434 |
* @return stdClass A complete user object
|
|
|
3435 |
*/
|
|
|
3436 |
function update_user_record($username) {
|
|
|
3437 |
global $DB, $CFG;
|
|
|
3438 |
// Just in case check text case.
|
|
|
3439 |
$username = trim(core_text::strtolower($username));
|
|
|
3440 |
|
|
|
3441 |
$oldinfo = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id), '*', MUST_EXIST);
|
|
|
3442 |
return update_user_record_by_id($oldinfo->id);
|
|
|
3443 |
}
|
|
|
3444 |
|
|
|
3445 |
/**
|
|
|
3446 |
* Will update a local user record from an external source (MNET users can not be updated using this method!).
|
|
|
3447 |
*
|
|
|
3448 |
* @param int $id user id
|
|
|
3449 |
* @return stdClass A complete user object
|
|
|
3450 |
*/
|
|
|
3451 |
function update_user_record_by_id($id) {
|
|
|
3452 |
global $DB, $CFG;
|
|
|
3453 |
require_once($CFG->dirroot."/user/profile/lib.php");
|
|
|
3454 |
require_once($CFG->dirroot.'/user/lib.php');
|
|
|
3455 |
|
|
|
3456 |
$params = array('mnethostid' => $CFG->mnet_localhost_id, 'id' => $id, 'deleted' => 0);
|
|
|
3457 |
$oldinfo = $DB->get_record('user', $params, '*', MUST_EXIST);
|
|
|
3458 |
|
|
|
3459 |
$newuser = array();
|
|
|
3460 |
$userauth = get_auth_plugin($oldinfo->auth);
|
|
|
3461 |
|
|
|
3462 |
if ($newinfo = $userauth->get_userinfo($oldinfo->username)) {
|
|
|
3463 |
$newinfo = truncate_userinfo($newinfo);
|
|
|
3464 |
$customfields = $userauth->get_custom_user_profile_fields();
|
|
|
3465 |
|
|
|
3466 |
foreach ($newinfo as $key => $value) {
|
|
|
3467 |
$iscustom = in_array($key, $customfields);
|
|
|
3468 |
if (!$iscustom) {
|
|
|
3469 |
$key = strtolower($key);
|
|
|
3470 |
}
|
|
|
3471 |
if ((!property_exists($oldinfo, $key) && !$iscustom) or $key === 'username' or $key === 'id'
|
|
|
3472 |
or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
|
|
|
3473 |
// Unknown or must not be changed.
|
|
|
3474 |
continue;
|
|
|
3475 |
}
|
|
|
3476 |
if (empty($userauth->config->{'field_updatelocal_' . $key}) || empty($userauth->config->{'field_lock_' . $key})) {
|
|
|
3477 |
continue;
|
|
|
3478 |
}
|
|
|
3479 |
$confval = $userauth->config->{'field_updatelocal_' . $key};
|
|
|
3480 |
$lockval = $userauth->config->{'field_lock_' . $key};
|
|
|
3481 |
if ($confval === 'onlogin') {
|
|
|
3482 |
// MDL-4207 Don't overwrite modified user profile values with
|
|
|
3483 |
// empty LDAP values when 'unlocked if empty' is set. The purpose
|
|
|
3484 |
// of the setting 'unlocked if empty' is to allow the user to fill
|
|
|
3485 |
// in a value for the selected field _if LDAP is giving
|
|
|
3486 |
// nothing_ for this field. Thus it makes sense to let this value
|
|
|
3487 |
// stand in until LDAP is giving a value for this field.
|
|
|
3488 |
if (!(empty($value) && $lockval === 'unlockedifempty')) {
|
|
|
3489 |
if ($iscustom || (in_array($key, $userauth->userfields) &&
|
|
|
3490 |
((string)$oldinfo->$key !== (string)$value))) {
|
|
|
3491 |
$newuser[$key] = (string)$value;
|
|
|
3492 |
}
|
|
|
3493 |
}
|
|
|
3494 |
}
|
|
|
3495 |
}
|
|
|
3496 |
if ($newuser) {
|
|
|
3497 |
$newuser['id'] = $oldinfo->id;
|
|
|
3498 |
$newuser['timemodified'] = time();
|
|
|
3499 |
user_update_user((object) $newuser, false, false);
|
|
|
3500 |
|
|
|
3501 |
// Save user profile data.
|
|
|
3502 |
profile_save_data((object) $newuser);
|
|
|
3503 |
|
|
|
3504 |
// Trigger event.
|
|
|
3505 |
\core\event\user_updated::create_from_userid($newuser['id'])->trigger();
|
|
|
3506 |
}
|
|
|
3507 |
}
|
|
|
3508 |
|
|
|
3509 |
return get_complete_user_data('id', $oldinfo->id);
|
|
|
3510 |
}
|
|
|
3511 |
|
|
|
3512 |
/**
|
|
|
3513 |
* Will truncate userinfo as it comes from auth_get_userinfo (from external auth) which may have large fields.
|
|
|
3514 |
*
|
|
|
3515 |
* @param array $info Array of user properties to truncate if needed
|
|
|
3516 |
* @return array The now truncated information that was passed in
|
|
|
3517 |
*/
|
|
|
3518 |
function truncate_userinfo(array $info) {
|
|
|
3519 |
// Define the limits.
|
|
|
3520 |
$limit = array(
|
|
|
3521 |
'username' => 100,
|
|
|
3522 |
'idnumber' => 255,
|
|
|
3523 |
'firstname' => 100,
|
|
|
3524 |
'lastname' => 100,
|
|
|
3525 |
'email' => 100,
|
|
|
3526 |
'phone1' => 20,
|
|
|
3527 |
'phone2' => 20,
|
|
|
3528 |
'institution' => 255,
|
|
|
3529 |
'department' => 255,
|
|
|
3530 |
'address' => 255,
|
|
|
3531 |
'city' => 120,
|
|
|
3532 |
'country' => 2,
|
|
|
3533 |
);
|
|
|
3534 |
|
|
|
3535 |
// Apply where needed.
|
|
|
3536 |
foreach (array_keys($info) as $key) {
|
|
|
3537 |
if (!empty($limit[$key])) {
|
|
|
3538 |
$info[$key] = trim(core_text::substr($info[$key], 0, $limit[$key]));
|
|
|
3539 |
}
|
|
|
3540 |
}
|
|
|
3541 |
|
|
|
3542 |
return $info;
|
|
|
3543 |
}
|
|
|
3544 |
|
|
|
3545 |
/**
|
|
|
3546 |
* Marks user deleted in internal user database and notifies the auth plugin.
|
|
|
3547 |
* Also unenrols user from all roles and does other cleanup.
|
|
|
3548 |
*
|
|
|
3549 |
* Any plugin that needs to purge user data should register the 'user_deleted' event.
|
|
|
3550 |
*
|
|
|
3551 |
* @param stdClass $user full user object before delete
|
|
|
3552 |
* @return boolean success
|
|
|
3553 |
* @throws coding_exception if invalid $user parameter detected
|
|
|
3554 |
*/
|
|
|
3555 |
function delete_user(stdClass $user) {
|
|
|
3556 |
global $CFG, $DB, $SESSION;
|
|
|
3557 |
require_once($CFG->libdir.'/grouplib.php');
|
|
|
3558 |
require_once($CFG->libdir.'/gradelib.php');
|
|
|
3559 |
require_once($CFG->dirroot.'/message/lib.php');
|
|
|
3560 |
require_once($CFG->dirroot.'/user/lib.php');
|
|
|
3561 |
|
|
|
3562 |
// Make sure nobody sends bogus record type as parameter.
|
|
|
3563 |
if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
|
|
|
3564 |
throw new coding_exception('Invalid $user parameter in delete_user() detected');
|
|
|
3565 |
}
|
|
|
3566 |
|
|
|
3567 |
// Better not trust the parameter and fetch the latest info this will be very expensive anyway.
|
|
|
3568 |
if (!$user = $DB->get_record('user', array('id' => $user->id))) {
|
|
|
3569 |
debugging('Attempt to delete unknown user account.');
|
|
|
3570 |
return false;
|
|
|
3571 |
}
|
|
|
3572 |
|
|
|
3573 |
// There must be always exactly one guest record, originally the guest account was identified by username only,
|
|
|
3574 |
// now we use $CFG->siteguest for performance reasons.
|
|
|
3575 |
if ($user->username === 'guest' or isguestuser($user)) {
|
|
|
3576 |
debugging('Guest user account can not be deleted.');
|
|
|
3577 |
return false;
|
|
|
3578 |
}
|
|
|
3579 |
|
|
|
3580 |
// Admin can be theoretically from different auth plugin, but we want to prevent deletion of internal accoutns only,
|
|
|
3581 |
// if anything goes wrong ppl may force somebody to be admin via config.php setting $CFG->siteadmins.
|
|
|
3582 |
if ($user->auth === 'manual' and is_siteadmin($user)) {
|
|
|
3583 |
debugging('Local administrator accounts can not be deleted.');
|
|
|
3584 |
return false;
|
|
|
3585 |
}
|
|
|
3586 |
// Allow plugins to use this user object before we completely delete it.
|
|
|
3587 |
if ($pluginsfunction = get_plugins_with_function('pre_user_delete')) {
|
|
|
3588 |
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
|
3589 |
foreach ($plugins as $pluginfunction) {
|
|
|
3590 |
$pluginfunction($user);
|
|
|
3591 |
}
|
|
|
3592 |
}
|
|
|
3593 |
}
|
|
|
3594 |
|
|
|
3595 |
// Dispatch the hook for pre user update actions.
|
|
|
3596 |
$hook = new \core_user\hook\before_user_deleted(
|
|
|
3597 |
user: $user,
|
|
|
3598 |
);
|
|
|
3599 |
di::get(hook\manager::class)->dispatch($hook);
|
|
|
3600 |
|
|
|
3601 |
// Keep user record before updating it, as we have to pass this to user_deleted event.
|
|
|
3602 |
$olduser = clone $user;
|
|
|
3603 |
|
|
|
3604 |
// Keep a copy of user context, we need it for event.
|
|
|
3605 |
$usercontext = context_user::instance($user->id);
|
|
|
3606 |
|
|
|
3607 |
// Delete all grades - backup is kept in grade_grades_history table.
|
|
|
3608 |
grade_user_delete($user->id);
|
|
|
3609 |
|
|
|
3610 |
// TODO: remove from cohorts using standard API here.
|
|
|
3611 |
|
|
|
3612 |
// Remove user tags.
|
|
|
3613 |
core_tag_tag::remove_all_item_tags('core', 'user', $user->id);
|
|
|
3614 |
|
|
|
3615 |
// Unconditionally unenrol from all courses.
|
|
|
3616 |
enrol_user_delete($user);
|
|
|
3617 |
|
|
|
3618 |
// Unenrol from all roles in all contexts.
|
|
|
3619 |
// This might be slow but it is really needed - modules might do some extra cleanup!
|
|
|
3620 |
role_unassign_all(array('userid' => $user->id));
|
|
|
3621 |
|
|
|
3622 |
// Notify the competency subsystem.
|
|
|
3623 |
\core_competency\api::hook_user_deleted($user->id);
|
|
|
3624 |
|
|
|
3625 |
// Now do a brute force cleanup.
|
|
|
3626 |
|
|
|
3627 |
// Delete all user events and subscription events.
|
|
|
3628 |
$DB->delete_records_select('event', 'userid = :userid AND subscriptionid IS NOT NULL', ['userid' => $user->id]);
|
|
|
3629 |
|
|
|
3630 |
// Now, delete all calendar subscription from the user.
|
|
|
3631 |
$DB->delete_records('event_subscriptions', ['userid' => $user->id]);
|
|
|
3632 |
|
|
|
3633 |
// Remove from all cohorts.
|
|
|
3634 |
$DB->delete_records('cohort_members', array('userid' => $user->id));
|
|
|
3635 |
|
|
|
3636 |
// Remove from all groups.
|
|
|
3637 |
$DB->delete_records('groups_members', array('userid' => $user->id));
|
|
|
3638 |
|
|
|
3639 |
// Brute force unenrol from all courses.
|
|
|
3640 |
$DB->delete_records('user_enrolments', array('userid' => $user->id));
|
|
|
3641 |
|
|
|
3642 |
// Purge user preferences.
|
|
|
3643 |
$DB->delete_records('user_preferences', array('userid' => $user->id));
|
|
|
3644 |
|
|
|
3645 |
// Purge user extra profile info.
|
|
|
3646 |
$DB->delete_records('user_info_data', array('userid' => $user->id));
|
|
|
3647 |
|
|
|
3648 |
// Purge log of previous password hashes.
|
|
|
3649 |
$DB->delete_records('user_password_history', array('userid' => $user->id));
|
|
|
3650 |
|
|
|
3651 |
// Last course access not necessary either.
|
|
|
3652 |
$DB->delete_records('user_lastaccess', array('userid' => $user->id));
|
|
|
3653 |
// Remove all user tokens.
|
|
|
3654 |
$DB->delete_records('external_tokens', array('userid' => $user->id));
|
|
|
3655 |
|
|
|
3656 |
// Unauthorise the user for all services.
|
|
|
3657 |
$DB->delete_records('external_services_users', array('userid' => $user->id));
|
|
|
3658 |
|
|
|
3659 |
// Remove users private keys.
|
|
|
3660 |
$DB->delete_records('user_private_key', array('userid' => $user->id));
|
|
|
3661 |
|
|
|
3662 |
// Remove users customised pages.
|
|
|
3663 |
$DB->delete_records('my_pages', array('userid' => $user->id, 'private' => 1));
|
|
|
3664 |
|
|
|
3665 |
// Remove user's oauth2 refresh tokens, if present.
|
|
|
3666 |
$DB->delete_records('oauth2_refresh_token', array('userid' => $user->id));
|
|
|
3667 |
|
|
|
3668 |
// Delete user from $SESSION->bulk_users.
|
|
|
3669 |
if (isset($SESSION->bulk_users[$user->id])) {
|
|
|
3670 |
unset($SESSION->bulk_users[$user->id]);
|
|
|
3671 |
}
|
|
|
3672 |
|
|
|
3673 |
// Force logout - may fail if file based sessions used, sorry.
|
|
|
3674 |
\core\session\manager::kill_user_sessions($user->id);
|
|
|
3675 |
|
|
|
3676 |
// Generate username from email address, or a fake email.
|
|
|
3677 |
$delemail = !empty($user->email) ? $user->email : $user->username . '.' . $user->id . '@unknownemail.invalid';
|
|
|
3678 |
|
|
|
3679 |
$deltime = time();
|
|
|
3680 |
|
|
|
3681 |
// Max username length is 100 chars. Select up to limit - (length of current time + 1 [period character]) from users email.
|
|
|
3682 |
$delnameprefix = clean_param($delemail, PARAM_USERNAME);
|
|
|
3683 |
$delnamesuffix = $deltime;
|
|
|
3684 |
$delnamesuffixlength = 10;
|
|
|
3685 |
do {
|
|
|
3686 |
// Workaround for bulk deletes of users with the same email address.
|
|
|
3687 |
$delname = sprintf(
|
|
|
3688 |
"%s.%10d",
|
|
|
3689 |
core_text::substr(
|
|
|
3690 |
$delnameprefix,
|
|
|
3691 |
0,
|
|
|
3692 |
// 100 Character maximum, with a '.' character, and a 10-digit timestamp.
|
|
|
3693 |
100 - 1 - $delnamesuffixlength,
|
|
|
3694 |
),
|
|
|
3695 |
$delnamesuffix,
|
|
|
3696 |
);
|
|
|
3697 |
$delnamesuffix++;
|
|
|
3698 |
|
|
|
3699 |
// No need to use mnethostid here.
|
|
|
3700 |
} while ($DB->record_exists('user', ['username' => $delname]));
|
|
|
3701 |
|
|
|
3702 |
// Mark internal user record as "deleted".
|
|
|
3703 |
$updateuser = new stdClass();
|
|
|
3704 |
$updateuser->id = $user->id;
|
|
|
3705 |
$updateuser->deleted = 1;
|
|
|
3706 |
$updateuser->username = $delname; // Remember it just in case.
|
|
|
3707 |
$updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users.
|
|
|
3708 |
$updateuser->idnumber = ''; // Clear this field to free it up.
|
|
|
3709 |
$updateuser->picture = 0;
|
|
|
3710 |
$updateuser->timemodified = $deltime;
|
|
|
3711 |
|
|
|
3712 |
// Don't trigger update event, as user is being deleted.
|
|
|
3713 |
user_update_user($updateuser, false, false);
|
|
|
3714 |
|
|
|
3715 |
// Delete all content associated with the user context, but not the context itself.
|
|
|
3716 |
$usercontext->delete_content();
|
|
|
3717 |
|
|
|
3718 |
// Delete any search data.
|
|
|
3719 |
\core_search\manager::context_deleted($usercontext);
|
|
|
3720 |
|
|
|
3721 |
// Any plugin that needs to cleanup should register this event.
|
|
|
3722 |
// Trigger event.
|
|
|
3723 |
$event = \core\event\user_deleted::create(
|
|
|
3724 |
array(
|
|
|
3725 |
'objectid' => $user->id,
|
|
|
3726 |
'relateduserid' => $user->id,
|
|
|
3727 |
'context' => $usercontext,
|
|
|
3728 |
'other' => array(
|
|
|
3729 |
'username' => $user->username,
|
|
|
3730 |
'email' => $user->email,
|
|
|
3731 |
'idnumber' => $user->idnumber,
|
|
|
3732 |
'picture' => $user->picture,
|
|
|
3733 |
'mnethostid' => $user->mnethostid
|
|
|
3734 |
)
|
|
|
3735 |
)
|
|
|
3736 |
);
|
|
|
3737 |
$event->add_record_snapshot('user', $olduser);
|
|
|
3738 |
$event->trigger();
|
|
|
3739 |
|
|
|
3740 |
// We will update the user's timemodified, as it will be passed to the user_deleted event, which
|
|
|
3741 |
// should know about this updated property persisted to the user's table.
|
|
|
3742 |
$user->timemodified = $updateuser->timemodified;
|
|
|
3743 |
|
|
|
3744 |
// Notify auth plugin - do not block the delete even when plugin fails.
|
|
|
3745 |
$authplugin = get_auth_plugin($user->auth);
|
|
|
3746 |
$authplugin->user_delete($user);
|
|
|
3747 |
|
|
|
3748 |
return true;
|
|
|
3749 |
}
|
|
|
3750 |
|
|
|
3751 |
/**
|
|
|
3752 |
* Retrieve the guest user object.
|
|
|
3753 |
*
|
|
|
3754 |
* @return stdClass A {@link $USER} object
|
|
|
3755 |
*/
|
|
|
3756 |
function guest_user() {
|
|
|
3757 |
global $CFG, $DB;
|
|
|
3758 |
|
|
|
3759 |
if ($newuser = $DB->get_record('user', array('id' => $CFG->siteguest))) {
|
|
|
3760 |
$newuser->confirmed = 1;
|
|
|
3761 |
$newuser->lang = get_newuser_language();
|
|
|
3762 |
$newuser->lastip = getremoteaddr();
|
|
|
3763 |
}
|
|
|
3764 |
|
|
|
3765 |
return $newuser;
|
|
|
3766 |
}
|
|
|
3767 |
|
|
|
3768 |
/**
|
|
|
3769 |
* Authenticates a user against the chosen authentication mechanism
|
|
|
3770 |
*
|
|
|
3771 |
* Given a username and password, this function looks them
|
|
|
3772 |
* up using the currently selected authentication mechanism,
|
|
|
3773 |
* and if the authentication is successful, it returns a
|
|
|
3774 |
* valid $user object from the 'user' table.
|
|
|
3775 |
*
|
|
|
3776 |
* Uses auth_ functions from the currently active auth module
|
|
|
3777 |
*
|
|
|
3778 |
* After authenticate_user_login() returns success, you will need to
|
|
|
3779 |
* log that the user has logged in, and call complete_user_login() to set
|
|
|
3780 |
* the session up.
|
|
|
3781 |
*
|
|
|
3782 |
* Note: this function works only with non-mnet accounts!
|
|
|
3783 |
*
|
|
|
3784 |
* @param string $username User's username (or also email if $CFG->authloginviaemail enabled)
|
|
|
3785 |
* @param string $password User's password
|
|
|
3786 |
* @param bool $ignorelockout useful when guessing is prevented by other mechanism such as captcha or SSO
|
|
|
3787 |
* @param int $failurereason login failure reason, can be used in renderers (it may disclose if account exists)
|
|
|
3788 |
* @param string|bool $logintoken If this is set to a string it is validated against the login token for the session.
|
|
|
3789 |
* @param string|bool $loginrecaptcha If this is set to a string it is validated against Google reCaptcha.
|
|
|
3790 |
* @return stdClass|false A {@link $USER} object or false if error
|
|
|
3791 |
*/
|
|
|
3792 |
function authenticate_user_login(
|
|
|
3793 |
$username,
|
|
|
3794 |
$password,
|
|
|
3795 |
$ignorelockout = false,
|
|
|
3796 |
&$failurereason = null,
|
|
|
3797 |
$logintoken = false,
|
|
|
3798 |
string|bool $loginrecaptcha = false,
|
|
|
3799 |
) {
|
|
|
3800 |
global $CFG, $DB, $PAGE, $SESSION;
|
|
|
3801 |
require_once("$CFG->libdir/authlib.php");
|
|
|
3802 |
|
|
|
3803 |
if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
|
|
|
3804 |
// we have found the user
|
|
|
3805 |
|
|
|
3806 |
} else if (!empty($CFG->authloginviaemail)) {
|
|
|
3807 |
if ($email = clean_param($username, PARAM_EMAIL)) {
|
|
|
3808 |
$select = "mnethostid = :mnethostid AND LOWER(email) = LOWER(:email) AND deleted = 0";
|
|
|
3809 |
$params = array('mnethostid' => $CFG->mnet_localhost_id, 'email' => $email);
|
|
|
3810 |
$users = $DB->get_records_select('user', $select, $params, 'id', 'id', 0, 2);
|
|
|
3811 |
if (count($users) === 1) {
|
|
|
3812 |
// Use email for login only if unique.
|
|
|
3813 |
$user = reset($users);
|
|
|
3814 |
$user = get_complete_user_data('id', $user->id);
|
|
|
3815 |
$username = $user->username;
|
|
|
3816 |
}
|
|
|
3817 |
unset($users);
|
|
|
3818 |
}
|
|
|
3819 |
}
|
|
|
3820 |
|
|
|
3821 |
// Make sure this request came from the login form.
|
|
|
3822 |
if (!\core\session\manager::validate_login_token($logintoken)) {
|
|
|
3823 |
$failurereason = AUTH_LOGIN_FAILED;
|
|
|
3824 |
|
|
|
3825 |
// Trigger login failed event (specifying the ID of the found user, if available).
|
|
|
3826 |
\core\event\user_login_failed::create([
|
|
|
3827 |
'userid' => ($user->id ?? 0),
|
|
|
3828 |
'other' => [
|
|
|
3829 |
'username' => $username,
|
|
|
3830 |
'reason' => $failurereason,
|
|
|
3831 |
],
|
|
|
3832 |
])->trigger();
|
|
|
3833 |
|
|
|
3834 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Invalid Login Token: $username ".$_SERVER['HTTP_USER_AGENT']);
|
|
|
3835 |
return false;
|
|
|
3836 |
}
|
|
|
3837 |
|
|
|
3838 |
// Login reCaptcha.
|
|
|
3839 |
if (login_captcha_enabled() && !validate_login_captcha($loginrecaptcha)) {
|
|
|
3840 |
$failurereason = AUTH_LOGIN_FAILED_RECAPTCHA;
|
|
|
3841 |
// Trigger login failed event (specifying the ID of the found user, if available).
|
|
|
3842 |
\core\event\user_login_failed::create([
|
|
|
3843 |
'userid' => ($user->id ?? 0),
|
|
|
3844 |
'other' => [
|
|
|
3845 |
'username' => $username,
|
|
|
3846 |
'reason' => $failurereason,
|
|
|
3847 |
],
|
|
|
3848 |
])->trigger();
|
|
|
3849 |
return false;
|
|
|
3850 |
}
|
|
|
3851 |
|
|
|
3852 |
$authsenabled = get_enabled_auth_plugins();
|
|
|
3853 |
|
|
|
3854 |
if ($user) {
|
|
|
3855 |
// Use manual if auth not set.
|
|
|
3856 |
$auth = empty($user->auth) ? 'manual' : $user->auth;
|
|
|
3857 |
|
|
|
3858 |
if (in_array($user->auth, $authsenabled)) {
|
|
|
3859 |
$authplugin = get_auth_plugin($user->auth);
|
|
|
3860 |
$authplugin->pre_user_login_hook($user);
|
|
|
3861 |
}
|
|
|
3862 |
|
|
|
3863 |
if (!empty($user->suspended)) {
|
|
|
3864 |
$failurereason = AUTH_LOGIN_SUSPENDED;
|
|
|
3865 |
|
|
|
3866 |
// Trigger login failed event.
|
|
|
3867 |
$event = \core\event\user_login_failed::create(array('userid' => $user->id,
|
|
|
3868 |
'other' => array('username' => $username, 'reason' => $failurereason)));
|
|
|
3869 |
$event->trigger();
|
|
|
3870 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
|
|
3871 |
return false;
|
|
|
3872 |
}
|
|
|
3873 |
if ($auth=='nologin' or !is_enabled_auth($auth)) {
|
|
|
3874 |
// Legacy way to suspend user.
|
|
|
3875 |
$failurereason = AUTH_LOGIN_SUSPENDED;
|
|
|
3876 |
|
|
|
3877 |
// Trigger login failed event.
|
|
|
3878 |
$event = \core\event\user_login_failed::create(array('userid' => $user->id,
|
|
|
3879 |
'other' => array('username' => $username, 'reason' => $failurereason)));
|
|
|
3880 |
$event->trigger();
|
|
|
3881 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
|
|
3882 |
return false;
|
|
|
3883 |
}
|
|
|
3884 |
$auths = array($auth);
|
|
|
3885 |
|
|
|
3886 |
} else {
|
|
|
3887 |
// Check if there's a deleted record (cheaply), this should not happen because we mangle usernames in delete_user().
|
|
|
3888 |
if ($DB->get_field('user', 'id', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id, 'deleted' => 1))) {
|
|
|
3889 |
$failurereason = AUTH_LOGIN_NOUSER;
|
|
|
3890 |
|
|
|
3891 |
// Trigger login failed event.
|
|
|
3892 |
$event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
|
|
|
3893 |
'reason' => $failurereason)));
|
|
|
3894 |
$event->trigger();
|
|
|
3895 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
|
|
3896 |
return false;
|
|
|
3897 |
}
|
|
|
3898 |
|
|
|
3899 |
// User does not exist.
|
|
|
3900 |
$auths = $authsenabled;
|
|
|
3901 |
$user = new stdClass();
|
|
|
3902 |
$user->id = 0;
|
|
|
3903 |
}
|
|
|
3904 |
|
|
|
3905 |
if ($ignorelockout) {
|
|
|
3906 |
// Some other mechanism protects against brute force password guessing, for example login form might include reCAPTCHA
|
|
|
3907 |
// or this function is called from a SSO script.
|
|
|
3908 |
} else if ($user->id) {
|
|
|
3909 |
// Verify login lockout after other ways that may prevent user login.
|
|
|
3910 |
if (login_is_lockedout($user)) {
|
|
|
3911 |
$failurereason = AUTH_LOGIN_LOCKOUT;
|
|
|
3912 |
|
|
|
3913 |
// Trigger login failed event.
|
|
|
3914 |
$event = \core\event\user_login_failed::create(array('userid' => $user->id,
|
|
|
3915 |
'other' => array('username' => $username, 'reason' => $failurereason)));
|
|
|
3916 |
$event->trigger();
|
|
|
3917 |
|
|
|
3918 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Login lockout: $username ".$_SERVER['HTTP_USER_AGENT']);
|
|
|
3919 |
$SESSION->loginerrormsg = get_string('accountlocked', 'admin');
|
|
|
3920 |
|
|
|
3921 |
return false;
|
|
|
3922 |
}
|
|
|
3923 |
} else {
|
|
|
3924 |
// We can not lockout non-existing accounts.
|
|
|
3925 |
}
|
|
|
3926 |
|
|
|
3927 |
foreach ($auths as $auth) {
|
|
|
3928 |
$authplugin = get_auth_plugin($auth);
|
|
|
3929 |
|
|
|
3930 |
// On auth fail fall through to the next plugin.
|
|
|
3931 |
if (!$authplugin->user_login($username, $password)) {
|
|
|
3932 |
continue;
|
|
|
3933 |
}
|
|
|
3934 |
|
|
|
3935 |
// Before performing login actions, check if user still passes password policy, if admin setting is enabled.
|
|
|
3936 |
if (!empty($CFG->passwordpolicycheckonlogin)) {
|
|
|
3937 |
$errmsg = '';
|
|
|
3938 |
$passed = check_password_policy($password, $errmsg, $user);
|
|
|
3939 |
if (!$passed) {
|
|
|
3940 |
// First trigger event for failure.
|
|
|
3941 |
$failedevent = \core\event\user_password_policy_failed::create_from_user($user);
|
|
|
3942 |
$failedevent->trigger();
|
|
|
3943 |
|
|
|
3944 |
// If able to change password, set flag and move on.
|
|
|
3945 |
if ($authplugin->can_change_password()) {
|
|
|
3946 |
// Check if we are on internal change password page, or service is external, don't show notification.
|
|
|
3947 |
$internalchangeurl = new moodle_url('/login/change_password.php');
|
|
|
3948 |
if (!($PAGE->has_set_url() && $internalchangeurl->compare($PAGE->url)) && $authplugin->is_internal()) {
|
|
|
3949 |
\core\notification::error(get_string('passwordpolicynomatch', '', $errmsg));
|
|
|
3950 |
}
|
|
|
3951 |
set_user_preference('auth_forcepasswordchange', 1, $user);
|
|
|
3952 |
} else if ($authplugin->can_reset_password()) {
|
|
|
3953 |
// Else force a reset if possible.
|
|
|
3954 |
\core\notification::error(get_string('forcepasswordresetnotice', '', $errmsg));
|
|
|
3955 |
redirect(new moodle_url('/login/forgot_password.php'));
|
|
|
3956 |
} else {
|
|
|
3957 |
$notifymsg = get_string('forcepasswordresetfailurenotice', '', $errmsg);
|
|
|
3958 |
// If support page is set, add link for help.
|
|
|
3959 |
if (!empty($CFG->supportpage)) {
|
|
|
3960 |
$link = \html_writer::link($CFG->supportpage, $CFG->supportpage);
|
|
|
3961 |
$link = \html_writer::tag('p', $link);
|
|
|
3962 |
$notifymsg .= $link;
|
|
|
3963 |
}
|
|
|
3964 |
|
|
|
3965 |
// If no change or reset is possible, add a notification for user.
|
|
|
3966 |
\core\notification::error($notifymsg);
|
|
|
3967 |
}
|
|
|
3968 |
}
|
|
|
3969 |
}
|
|
|
3970 |
|
|
|
3971 |
// Successful authentication.
|
|
|
3972 |
if ($user->id) {
|
|
|
3973 |
// User already exists in database.
|
|
|
3974 |
if (empty($user->auth)) {
|
|
|
3975 |
// For some reason auth isn't set yet.
|
|
|
3976 |
$DB->set_field('user', 'auth', $auth, array('id' => $user->id));
|
|
|
3977 |
$user->auth = $auth;
|
|
|
3978 |
}
|
|
|
3979 |
|
|
|
3980 |
// If the existing hash is using an out-of-date algorithm (or the legacy md5 algorithm), then we should update to
|
|
|
3981 |
// the current hash algorithm while we have access to the user's password.
|
|
|
3982 |
update_internal_user_password($user, $password);
|
|
|
3983 |
|
|
|
3984 |
if ($authplugin->is_synchronised_with_external()) {
|
|
|
3985 |
// Update user record from external DB.
|
|
|
3986 |
$user = update_user_record_by_id($user->id);
|
|
|
3987 |
}
|
|
|
3988 |
} else {
|
|
|
3989 |
// The user is authenticated but user creation may be disabled.
|
|
|
3990 |
if (!empty($CFG->authpreventaccountcreation)) {
|
|
|
3991 |
$failurereason = AUTH_LOGIN_UNAUTHORISED;
|
|
|
3992 |
|
|
|
3993 |
// Trigger login failed event.
|
|
|
3994 |
$event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
|
|
|
3995 |
'reason' => $failurereason)));
|
|
|
3996 |
$event->trigger();
|
|
|
3997 |
|
|
|
3998 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Unknown user, can not create new accounts: $username ".
|
|
|
3999 |
$_SERVER['HTTP_USER_AGENT']);
|
|
|
4000 |
return false;
|
|
|
4001 |
} else {
|
|
|
4002 |
$user = create_user_record($username, $password, $auth);
|
|
|
4003 |
}
|
|
|
4004 |
}
|
|
|
4005 |
|
|
|
4006 |
$authplugin->sync_roles($user);
|
|
|
4007 |
|
|
|
4008 |
foreach ($authsenabled as $hau) {
|
|
|
4009 |
$hauth = get_auth_plugin($hau);
|
|
|
4010 |
$hauth->user_authenticated_hook($user, $username, $password);
|
|
|
4011 |
}
|
|
|
4012 |
|
|
|
4013 |
if (empty($user->id)) {
|
|
|
4014 |
$failurereason = AUTH_LOGIN_NOUSER;
|
|
|
4015 |
// Trigger login failed event.
|
|
|
4016 |
$event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
|
|
|
4017 |
'reason' => $failurereason)));
|
|
|
4018 |
$event->trigger();
|
|
|
4019 |
return false;
|
|
|
4020 |
}
|
|
|
4021 |
|
|
|
4022 |
if (!empty($user->suspended)) {
|
|
|
4023 |
// Just in case some auth plugin suspended account.
|
|
|
4024 |
$failurereason = AUTH_LOGIN_SUSPENDED;
|
|
|
4025 |
// Trigger login failed event.
|
|
|
4026 |
$event = \core\event\user_login_failed::create(array('userid' => $user->id,
|
|
|
4027 |
'other' => array('username' => $username, 'reason' => $failurereason)));
|
|
|
4028 |
$event->trigger();
|
|
|
4029 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
|
|
4030 |
return false;
|
|
|
4031 |
}
|
|
|
4032 |
|
|
|
4033 |
login_attempt_valid($user);
|
|
|
4034 |
$failurereason = AUTH_LOGIN_OK;
|
|
|
4035 |
return $user;
|
|
|
4036 |
}
|
|
|
4037 |
|
|
|
4038 |
// Failed if all the plugins have failed.
|
|
|
4039 |
if (debugging('', DEBUG_ALL)) {
|
|
|
4040 |
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
|
|
4041 |
}
|
|
|
4042 |
|
|
|
4043 |
if ($user->id) {
|
|
|
4044 |
login_attempt_failed($user);
|
|
|
4045 |
$failurereason = AUTH_LOGIN_FAILED;
|
|
|
4046 |
// Trigger login failed event.
|
|
|
4047 |
$event = \core\event\user_login_failed::create(array('userid' => $user->id,
|
|
|
4048 |
'other' => array('username' => $username, 'reason' => $failurereason)));
|
|
|
4049 |
$event->trigger();
|
|
|
4050 |
} else {
|
|
|
4051 |
$failurereason = AUTH_LOGIN_NOUSER;
|
|
|
4052 |
// Trigger login failed event.
|
|
|
4053 |
$event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
|
|
|
4054 |
'reason' => $failurereason)));
|
|
|
4055 |
$event->trigger();
|
|
|
4056 |
}
|
|
|
4057 |
|
|
|
4058 |
return false;
|
|
|
4059 |
}
|
|
|
4060 |
|
|
|
4061 |
/**
|
|
|
4062 |
* Call to complete the user login process after authenticate_user_login()
|
|
|
4063 |
* has succeeded. It will setup the $USER variable and other required bits
|
|
|
4064 |
* and pieces.
|
|
|
4065 |
*
|
|
|
4066 |
* NOTE:
|
|
|
4067 |
* - It will NOT log anything -- up to the caller to decide what to log.
|
|
|
4068 |
* - this function does not set any cookies any more!
|
|
|
4069 |
*
|
|
|
4070 |
* @param stdClass $user
|
|
|
4071 |
* @param array $extrauserinfo
|
|
|
4072 |
* @return stdClass A {@link $USER} object - BC only, do not use
|
|
|
4073 |
*/
|
|
|
4074 |
function complete_user_login($user, array $extrauserinfo = []) {
|
|
|
4075 |
global $CFG, $DB, $USER, $SESSION;
|
|
|
4076 |
|
|
|
4077 |
\core\session\manager::login_user($user);
|
|
|
4078 |
|
|
|
4079 |
// Reload preferences from DB.
|
|
|
4080 |
unset($USER->preference);
|
|
|
4081 |
check_user_preferences_loaded($USER);
|
|
|
4082 |
|
|
|
4083 |
// Update login times.
|
|
|
4084 |
update_user_login_times();
|
|
|
4085 |
|
|
|
4086 |
// Extra session prefs init.
|
|
|
4087 |
set_login_session_preferences();
|
|
|
4088 |
|
|
|
4089 |
// Trigger login event.
|
|
|
4090 |
$event = \core\event\user_loggedin::create(
|
|
|
4091 |
array(
|
|
|
4092 |
'userid' => $USER->id,
|
|
|
4093 |
'objectid' => $USER->id,
|
|
|
4094 |
'other' => [
|
|
|
4095 |
'username' => $USER->username,
|
|
|
4096 |
'extrauserinfo' => $extrauserinfo
|
|
|
4097 |
]
|
|
|
4098 |
)
|
|
|
4099 |
);
|
|
|
4100 |
$event->trigger();
|
|
|
4101 |
|
|
|
4102 |
// Allow plugins to callback as soon possible after user has completed login.
|
|
|
4103 |
di::get(\core\hook\manager::class)->dispatch(new \core_user\hook\after_login_completed());
|
|
|
4104 |
|
|
|
4105 |
// Check if the user is using a new browser or session (a new MoodleSession cookie is set in that case).
|
|
|
4106 |
// If the user is accessing from the same IP, ignore everything (most of the time will be a new session in the same browser).
|
|
|
4107 |
// Skip Web Service requests, CLI scripts, AJAX scripts, and request from the mobile app itself.
|
|
|
4108 |
$loginip = getremoteaddr();
|
|
|
4109 |
$isnewip = isset($SESSION->userpreviousip) && $SESSION->userpreviousip != $loginip;
|
|
|
4110 |
$isvalidenv = (!WS_SERVER && !CLI_SCRIPT && !NO_MOODLE_COOKIES) || PHPUNIT_TEST;
|
|
|
4111 |
|
|
|
4112 |
if (!empty($SESSION->isnewsessioncookie) && $isnewip && $isvalidenv && !\core_useragent::is_moodle_app()) {
|
|
|
4113 |
|
|
|
4114 |
$logintime = time();
|
|
|
4115 |
$ismoodleapp = false;
|
|
|
4116 |
$useragent = \core_useragent::get_user_agent_string();
|
|
|
4117 |
|
|
|
4118 |
$sitepreferences = get_message_output_default_preferences();
|
|
|
4119 |
// Check if new login notification is disabled at system level.
|
|
|
4120 |
$newlogindisabled = $sitepreferences->moodle_newlogin_disable ?? 0;
|
|
|
4121 |
// Check if message providers (web, email, mobile) are enabled at system level.
|
|
|
4122 |
$msgproviderenabled = isset($sitepreferences->message_provider_moodle_newlogin_enabled);
|
|
|
4123 |
// Get message providers enabled for a user.
|
|
|
4124 |
$userpreferences = get_user_preferences('message_provider_moodle_newlogin_enabled');
|
|
|
4125 |
// Check if notification processor plugins (web, email, mobile) are enabled at system level.
|
|
|
4126 |
$msgprocessorsready = !empty(get_message_processors(true));
|
|
|
4127 |
// If new login notification is enabled at system level then go for other conditions check.
|
|
|
4128 |
$newloginenabled = $newlogindisabled ? 0 : ($userpreferences != 'none' && $msgproviderenabled);
|
|
|
4129 |
|
|
|
4130 |
if ($newloginenabled && $msgprocessorsready) {
|
|
|
4131 |
// Schedule adhoc task to send a login notification to the user.
|
|
|
4132 |
$task = new \core\task\send_login_notifications();
|
|
|
4133 |
$task->set_userid($USER->id);
|
|
|
4134 |
$task->set_custom_data(compact('ismoodleapp', 'useragent', 'loginip', 'logintime'));
|
|
|
4135 |
$task->set_component('core');
|
|
|
4136 |
\core\task\manager::queue_adhoc_task($task);
|
|
|
4137 |
}
|
|
|
4138 |
}
|
|
|
4139 |
|
|
|
4140 |
// Queue migrating the messaging data, if we need to.
|
|
|
4141 |
if (!get_user_preferences('core_message_migrate_data', false, $USER->id)) {
|
|
|
4142 |
// Check if there are any legacy messages to migrate.
|
|
|
4143 |
if (\core_message\helper::legacy_messages_exist($USER->id)) {
|
|
|
4144 |
\core_message\task\migrate_message_data::queue_task($USER->id);
|
|
|
4145 |
} else {
|
|
|
4146 |
set_user_preference('core_message_migrate_data', true, $USER->id);
|
|
|
4147 |
}
|
|
|
4148 |
}
|
|
|
4149 |
|
|
|
4150 |
if (isguestuser()) {
|
|
|
4151 |
// No need to continue when user is THE guest.
|
|
|
4152 |
return $USER;
|
|
|
4153 |
}
|
|
|
4154 |
|
|
|
4155 |
if (CLI_SCRIPT) {
|
|
|
4156 |
// We can redirect to password change URL only in browser.
|
|
|
4157 |
return $USER;
|
|
|
4158 |
}
|
|
|
4159 |
|
|
|
4160 |
// Select password change url.
|
|
|
4161 |
$userauth = get_auth_plugin($USER->auth);
|
|
|
4162 |
|
|
|
4163 |
// Check whether the user should be changing password.
|
|
|
4164 |
if (get_user_preferences('auth_forcepasswordchange', false)) {
|
|
|
4165 |
if ($userauth->can_change_password()) {
|
|
|
4166 |
if ($changeurl = $userauth->change_password_url()) {
|
|
|
4167 |
redirect($changeurl);
|
|
|
4168 |
} else {
|
|
|
4169 |
require_once($CFG->dirroot . '/login/lib.php');
|
|
|
4170 |
$SESSION->wantsurl = core_login_get_return_url();
|
|
|
4171 |
redirect($CFG->wwwroot.'/login/change_password.php');
|
|
|
4172 |
}
|
|
|
4173 |
} else {
|
|
|
4174 |
throw new \moodle_exception('nopasswordchangeforced', 'auth');
|
|
|
4175 |
}
|
|
|
4176 |
}
|
|
|
4177 |
return $USER;
|
|
|
4178 |
}
|
|
|
4179 |
|
|
|
4180 |
/**
|
|
|
4181 |
* Check a password hash to see if it was hashed using the legacy hash algorithm (bcrypt).
|
|
|
4182 |
*
|
|
|
4183 |
* @param string $password String to check.
|
|
|
4184 |
* @return bool True if the $password matches the format of a bcrypt hash.
|
|
|
4185 |
*/
|
|
|
4186 |
function password_is_legacy_hash(#[\SensitiveParameter] string $password): bool {
|
|
|
4187 |
return (bool) preg_match('/^\$2y\$[\d]{2}\$[A-Za-z0-9\.\/]{53}$/', $password);
|
|
|
4188 |
}
|
|
|
4189 |
|
|
|
4190 |
/**
|
|
|
4191 |
* Calculate the Shannon entropy of a string.
|
|
|
4192 |
*
|
|
|
4193 |
* @param string $pepper The pepper to calculate the entropy of.
|
|
|
4194 |
* @return float The Shannon entropy of the string.
|
|
|
4195 |
*/
|
|
|
4196 |
function calculate_entropy(#[\SensitiveParameter] string $pepper): float {
|
|
|
4197 |
// Initialize entropy.
|
|
|
4198 |
$h = 0;
|
|
|
4199 |
|
|
|
4200 |
// Calculate the length of the string.
|
|
|
4201 |
$size = strlen($pepper);
|
|
|
4202 |
|
|
|
4203 |
// For each unique character in the string.
|
|
|
4204 |
foreach (count_chars($pepper, 1) as $v) {
|
|
|
4205 |
// Calculate the probability of the character.
|
|
|
4206 |
$p = $v / $size;
|
|
|
4207 |
|
|
|
4208 |
// Add the character's contribution to the total entropy.
|
|
|
4209 |
// This uses the formula for the entropy of a discrete random variable.
|
|
|
4210 |
$h -= $p * log($p) / log(2);
|
|
|
4211 |
}
|
|
|
4212 |
|
|
|
4213 |
// Instead of returning the average entropy per symbol (Shannon entropy),
|
|
|
4214 |
// we multiply by the length of the string to get total entropy.
|
|
|
4215 |
return $h * $size;
|
|
|
4216 |
}
|
|
|
4217 |
|
|
|
4218 |
/**
|
|
|
4219 |
* Get the available password peppers.
|
|
|
4220 |
* The latest pepper is checked for minimum entropy as part of this function.
|
|
|
4221 |
* We only calculate the entropy of the most recent pepper,
|
|
|
4222 |
* because passwords are always updated to the latest pepper,
|
|
|
4223 |
* and in the past we may have enforced a lower minimum entropy.
|
|
|
4224 |
* Also, we allow the latest pepper to be empty, to allow admins to migrate off peppers.
|
|
|
4225 |
*
|
|
|
4226 |
* @return array The password peppers.
|
|
|
4227 |
* @throws coding_exception If the entropy of the password pepper is less than the recommended minimum.
|
|
|
4228 |
*/
|
|
|
4229 |
function get_password_peppers(): array {
|
|
|
4230 |
global $CFG;
|
|
|
4231 |
|
|
|
4232 |
// Get all available peppers.
|
|
|
4233 |
if (isset($CFG->passwordpeppers) && is_array($CFG->passwordpeppers)) {
|
|
|
4234 |
// Sort the array in descending order of keys (numerical).
|
|
|
4235 |
$peppers = $CFG->passwordpeppers;
|
|
|
4236 |
krsort($peppers, SORT_NUMERIC);
|
|
|
4237 |
} else {
|
|
|
4238 |
$peppers = []; // Set an empty array if no peppers are found.
|
|
|
4239 |
}
|
|
|
4240 |
|
|
|
4241 |
// Check if the entropy of the most recent pepper is less than the minimum.
|
|
|
4242 |
// Also, we allow the most recent pepper to be empty, to allow admins to migrate off peppers.
|
|
|
4243 |
$lastpepper = reset($peppers);
|
|
|
4244 |
if (!empty($peppers) && $lastpepper !== '' && calculate_entropy($lastpepper) < PEPPER_ENTROPY) {
|
|
|
4245 |
throw new coding_exception(
|
|
|
4246 |
'password pepper below minimum',
|
|
|
4247 |
'The entropy of the password pepper is less than the recommended minimum.');
|
|
|
4248 |
}
|
|
|
4249 |
return $peppers;
|
|
|
4250 |
}
|
|
|
4251 |
|
|
|
4252 |
/**
|
|
|
4253 |
* Compare password against hash stored in user object to determine if it is valid.
|
|
|
4254 |
*
|
|
|
4255 |
* If necessary it also updates the stored hash to the current format.
|
|
|
4256 |
*
|
|
|
4257 |
* @param stdClass $user (Password property may be updated).
|
|
|
4258 |
* @param string $password Plain text password.
|
|
|
4259 |
* @return bool True if password is valid.
|
|
|
4260 |
*/
|
|
|
4261 |
function validate_internal_user_password(stdClass $user, #[\SensitiveParameter] string $password): bool {
|
|
|
4262 |
|
|
|
4263 |
if (exceeds_password_length($password)) {
|
|
|
4264 |
// Password cannot be more than MAX_PASSWORD_CHARACTERS characters.
|
|
|
4265 |
return false;
|
|
|
4266 |
}
|
|
|
4267 |
|
|
|
4268 |
if ($user->password === AUTH_PASSWORD_NOT_CACHED) {
|
|
|
4269 |
// Internal password is not used at all, it can not validate.
|
|
|
4270 |
return false;
|
|
|
4271 |
}
|
|
|
4272 |
|
|
|
4273 |
$peppers = get_password_peppers(); // Get the array of available peppers.
|
|
|
4274 |
$islegacy = password_is_legacy_hash($user->password); // Check if the password is a legacy bcrypt hash.
|
|
|
4275 |
|
|
|
4276 |
// If the password is a legacy hash, no peppers were used, so verify and update directly.
|
|
|
4277 |
if ($islegacy && password_verify($password, $user->password)) {
|
|
|
4278 |
update_internal_user_password($user, $password);
|
|
|
4279 |
return true;
|
|
|
4280 |
}
|
|
|
4281 |
|
|
|
4282 |
// If the password is not a legacy hash, iterate through the peppers.
|
|
|
4283 |
$latestpepper = reset($peppers);
|
|
|
4284 |
// Add an empty pepper to the beginning of the array. To make it easier to check if the password matches without any pepper.
|
|
|
4285 |
$peppers = [-1 => ''] + $peppers;
|
|
|
4286 |
foreach ($peppers as $pepper) {
|
|
|
4287 |
$pepperedpassword = $password . $pepper;
|
|
|
4288 |
|
|
|
4289 |
// If the peppered password is correct, update (if necessary) and return true.
|
|
|
4290 |
if (password_verify($pepperedpassword, $user->password)) {
|
|
|
4291 |
// If the pepper used is not the latest one, update the password.
|
|
|
4292 |
if ($pepper !== $latestpepper) {
|
|
|
4293 |
update_internal_user_password($user, $password);
|
|
|
4294 |
}
|
|
|
4295 |
return true;
|
|
|
4296 |
}
|
|
|
4297 |
}
|
|
|
4298 |
|
|
|
4299 |
// If no peppered password was correct, the password is wrong.
|
|
|
4300 |
return false;
|
|
|
4301 |
}
|
|
|
4302 |
|
|
|
4303 |
/**
|
|
|
4304 |
* Calculate hash for a plain text password.
|
|
|
4305 |
*
|
|
|
4306 |
* @param string $password Plain text password to be hashed.
|
|
|
4307 |
* @param bool $fasthash If true, use a low number of rounds when generating the hash
|
|
|
4308 |
* This is faster to generate but makes the hash less secure.
|
|
|
4309 |
* It is used when lots of hashes need to be generated quickly.
|
|
|
4310 |
* @param int $pepperlength Lenght of the peppers
|
|
|
4311 |
* @return string The hashed password.
|
|
|
4312 |
*
|
|
|
4313 |
* @throws moodle_exception If a problem occurs while generating the hash.
|
|
|
4314 |
*/
|
|
|
4315 |
function hash_internal_user_password(#[\SensitiveParameter] string $password, $fasthash = false, $pepperlength = 0): string {
|
|
|
4316 |
if (exceeds_password_length($password, $pepperlength)) {
|
|
|
4317 |
// Password cannot be more than MAX_PASSWORD_CHARACTERS.
|
|
|
4318 |
throw new \moodle_exception(get_string("passwordexceeded", 'error', MAX_PASSWORD_CHARACTERS));
|
|
|
4319 |
}
|
|
|
4320 |
|
|
|
4321 |
// Set the cost factor to 5000 for fast hashing, otherwise use default cost.
|
|
|
4322 |
$rounds = $fasthash ? 5000 : 10000;
|
|
|
4323 |
|
|
|
4324 |
// First generate a cryptographically suitable salt.
|
|
|
4325 |
$randombytes = random_bytes(16);
|
|
|
4326 |
$salt = substr(strtr(base64_encode($randombytes), '+', '.'), 0, 16);
|
|
|
4327 |
|
|
|
4328 |
// Now construct the password string with the salt and number of rounds.
|
|
|
4329 |
// The password string is in the format $algorithm$rounds$salt$hash. ($6 is the SHA512 algorithm).
|
|
|
4330 |
$generatedhash = crypt($password, implode('$', [
|
|
|
4331 |
'',
|
|
|
4332 |
// The SHA512 Algorithm
|
|
|
4333 |
'6',
|
|
|
4334 |
"rounds={$rounds}",
|
|
|
4335 |
$salt,
|
|
|
4336 |
'',
|
|
|
4337 |
]));
|
|
|
4338 |
|
|
|
4339 |
if ($generatedhash === false || $generatedhash === null) {
|
|
|
4340 |
throw new moodle_exception('Failed to generate password hash.');
|
|
|
4341 |
}
|
|
|
4342 |
|
|
|
4343 |
return $generatedhash;
|
|
|
4344 |
}
|
|
|
4345 |
|
|
|
4346 |
/**
|
|
|
4347 |
* Update password hash in user object (if necessary).
|
|
|
4348 |
*
|
|
|
4349 |
* The password is updated if:
|
|
|
4350 |
* 1. The password has changed (the hash of $user->password is different
|
|
|
4351 |
* to the hash of $password).
|
|
|
4352 |
* 2. The existing hash is using an out-of-date algorithm (or the legacy
|
|
|
4353 |
* md5 algorithm).
|
|
|
4354 |
*
|
|
|
4355 |
* The password is peppered with the latest pepper before hashing,
|
|
|
4356 |
* if peppers are available.
|
|
|
4357 |
* Updating the password will modify the $user object and the database
|
|
|
4358 |
* record to use the current hashing algorithm.
|
|
|
4359 |
* It will remove Web Services user tokens too.
|
|
|
4360 |
*
|
|
|
4361 |
* @param stdClass $user User object (password property may be updated).
|
11 |
efrain |
4362 |
* @param string|null $password Plain text password.
|
1 |
efrain |
4363 |
* @param bool $fasthash If true, use a low cost factor when generating the hash
|
|
|
4364 |
* This is much faster to generate but makes the hash
|
|
|
4365 |
* less secure. It is used when lots of hashes need to
|
|
|
4366 |
* be generated quickly.
|
|
|
4367 |
* @return bool Always returns true.
|
|
|
4368 |
*/
|
|
|
4369 |
function update_internal_user_password(
|
|
|
4370 |
stdClass $user,
|
11 |
efrain |
4371 |
#[\SensitiveParameter] ?string $password,
|
1 |
efrain |
4372 |
bool $fasthash = false
|
|
|
4373 |
): bool {
|
|
|
4374 |
global $CFG, $DB;
|
|
|
4375 |
|
|
|
4376 |
// Add the latest password pepper to the password before further processing.
|
|
|
4377 |
$peppers = get_password_peppers();
|
|
|
4378 |
if (!empty($peppers)) {
|
|
|
4379 |
$password = $password . reset($peppers);
|
|
|
4380 |
}
|
|
|
4381 |
|
|
|
4382 |
// Figure out what the hashed password should be.
|
|
|
4383 |
if (!isset($user->auth)) {
|
|
|
4384 |
debugging('User record in update_internal_user_password() must include field auth',
|
|
|
4385 |
DEBUG_DEVELOPER);
|
|
|
4386 |
$user->auth = $DB->get_field('user', 'auth', array('id' => $user->id));
|
|
|
4387 |
}
|
|
|
4388 |
$authplugin = get_auth_plugin($user->auth);
|
|
|
4389 |
if ($authplugin->prevent_local_passwords()) {
|
|
|
4390 |
$hashedpassword = AUTH_PASSWORD_NOT_CACHED;
|
|
|
4391 |
} else {
|
|
|
4392 |
$hashedpassword = hash_internal_user_password($password, $fasthash);
|
|
|
4393 |
}
|
|
|
4394 |
|
|
|
4395 |
$algorithmchanged = false;
|
|
|
4396 |
|
|
|
4397 |
if ($hashedpassword === AUTH_PASSWORD_NOT_CACHED) {
|
|
|
4398 |
// Password is not cached, update it if not set to AUTH_PASSWORD_NOT_CACHED.
|
|
|
4399 |
$passwordchanged = ($user->password !== $hashedpassword);
|
|
|
4400 |
|
|
|
4401 |
} else if (isset($user->password)) {
|
|
|
4402 |
// If verification fails then it means the password has changed.
|
|
|
4403 |
$passwordchanged = !password_verify($password, $user->password);
|
|
|
4404 |
$algorithmchanged = password_is_legacy_hash($user->password);
|
|
|
4405 |
} else {
|
|
|
4406 |
// While creating new user, password in unset in $user object, to avoid
|
|
|
4407 |
// saving it with user_create()
|
|
|
4408 |
$passwordchanged = true;
|
|
|
4409 |
}
|
|
|
4410 |
|
|
|
4411 |
if ($passwordchanged || $algorithmchanged) {
|
|
|
4412 |
$DB->set_field('user', 'password', $hashedpassword, array('id' => $user->id));
|
|
|
4413 |
$user->password = $hashedpassword;
|
|
|
4414 |
|
|
|
4415 |
// Trigger event.
|
|
|
4416 |
$user = $DB->get_record('user', array('id' => $user->id));
|
|
|
4417 |
\core\event\user_password_updated::create_from_user($user)->trigger();
|
|
|
4418 |
|
|
|
4419 |
// Remove WS user tokens.
|
|
|
4420 |
if (!empty($CFG->passwordchangetokendeletion)) {
|
|
|
4421 |
require_once($CFG->dirroot.'/webservice/lib.php');
|
|
|
4422 |
webservice::delete_user_ws_tokens($user->id);
|
|
|
4423 |
}
|
|
|
4424 |
}
|
|
|
4425 |
|
|
|
4426 |
return true;
|
|
|
4427 |
}
|
|
|
4428 |
|
|
|
4429 |
/**
|
|
|
4430 |
* Get a complete user record, which includes all the info in the user record.
|
|
|
4431 |
*
|
|
|
4432 |
* Intended for setting as $USER session variable
|
|
|
4433 |
*
|
|
|
4434 |
* @param string $field The user field to be checked for a given value.
|
|
|
4435 |
* @param string $value The value to match for $field.
|
|
|
4436 |
* @param int $mnethostid
|
|
|
4437 |
* @param bool $throwexception If true, it will throw an exception when there's no record found or when there are multiple records
|
|
|
4438 |
* found. Otherwise, it will just return false.
|
|
|
4439 |
* @return mixed False, or A {@link $USER} object.
|
|
|
4440 |
*/
|
|
|
4441 |
function get_complete_user_data($field, $value, $mnethostid = null, $throwexception = false) {
|
|
|
4442 |
global $CFG, $DB;
|
|
|
4443 |
|
|
|
4444 |
if (!$field || !$value) {
|
|
|
4445 |
return false;
|
|
|
4446 |
}
|
|
|
4447 |
|
|
|
4448 |
// Change the field to lowercase.
|
|
|
4449 |
$field = core_text::strtolower($field);
|
|
|
4450 |
|
|
|
4451 |
// List of case insensitive fields.
|
|
|
4452 |
$caseinsensitivefields = ['email'];
|
|
|
4453 |
|
|
|
4454 |
// Username input is forced to lowercase and should be case sensitive.
|
|
|
4455 |
if ($field == 'username') {
|
|
|
4456 |
$value = core_text::strtolower($value);
|
|
|
4457 |
}
|
|
|
4458 |
|
|
|
4459 |
// Build the WHERE clause for an SQL query.
|
|
|
4460 |
$params = array('fieldval' => $value);
|
|
|
4461 |
|
|
|
4462 |
// Do a case-insensitive query, if necessary. These are generally very expensive. The performance can be improved on some DBs
|
|
|
4463 |
// such as MySQL by pre-filtering users with accent-insensitive subselect.
|
|
|
4464 |
if (in_array($field, $caseinsensitivefields)) {
|
|
|
4465 |
$fieldselect = $DB->sql_equal($field, ':fieldval', false);
|
|
|
4466 |
$idsubselect = $DB->sql_equal($field, ':fieldval2', false, false);
|
|
|
4467 |
$params['fieldval2'] = $value;
|
|
|
4468 |
} else {
|
|
|
4469 |
$fieldselect = "$field = :fieldval";
|
|
|
4470 |
$idsubselect = '';
|
|
|
4471 |
}
|
|
|
4472 |
$constraints = "$fieldselect AND deleted <> 1";
|
|
|
4473 |
|
|
|
4474 |
// If we are loading user data based on anything other than id,
|
|
|
4475 |
// we must also restrict our search based on mnet host.
|
|
|
4476 |
if ($field != 'id') {
|
|
|
4477 |
if (empty($mnethostid)) {
|
|
|
4478 |
// If empty, we restrict to local users.
|
|
|
4479 |
$mnethostid = $CFG->mnet_localhost_id;
|
|
|
4480 |
}
|
|
|
4481 |
}
|
|
|
4482 |
if (!empty($mnethostid)) {
|
|
|
4483 |
$params['mnethostid'] = $mnethostid;
|
|
|
4484 |
$constraints .= " AND mnethostid = :mnethostid";
|
|
|
4485 |
}
|
|
|
4486 |
|
|
|
4487 |
if ($idsubselect) {
|
|
|
4488 |
$constraints .= " AND id IN (SELECT id FROM {user} WHERE {$idsubselect})";
|
|
|
4489 |
}
|
|
|
4490 |
|
|
|
4491 |
// Get all the basic user data.
|
|
|
4492 |
try {
|
|
|
4493 |
// Make sure that there's only a single record that matches our query.
|
|
|
4494 |
// For example, when fetching by email, multiple records might match the query as there's no guarantee that email addresses
|
|
|
4495 |
// are unique. Therefore we can't reliably tell whether the user profile data that we're fetching is the correct one.
|
|
|
4496 |
$user = $DB->get_record_select('user', $constraints, $params, '*', MUST_EXIST);
|
|
|
4497 |
} catch (dml_exception $exception) {
|
|
|
4498 |
if ($throwexception) {
|
|
|
4499 |
throw $exception;
|
|
|
4500 |
} else {
|
|
|
4501 |
// Return false when no records or multiple records were found.
|
|
|
4502 |
return false;
|
|
|
4503 |
}
|
|
|
4504 |
}
|
|
|
4505 |
|
|
|
4506 |
// Get various settings and preferences.
|
|
|
4507 |
|
|
|
4508 |
// Preload preference cache.
|
|
|
4509 |
check_user_preferences_loaded($user);
|
|
|
4510 |
|
|
|
4511 |
// Load course enrolment related stuff.
|
|
|
4512 |
$user->lastcourseaccess = array(); // During last session.
|
|
|
4513 |
$user->currentcourseaccess = array(); // During current session.
|
|
|
4514 |
if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid' => $user->id))) {
|
|
|
4515 |
foreach ($lastaccesses as $lastaccess) {
|
|
|
4516 |
$user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
|
|
|
4517 |
}
|
|
|
4518 |
}
|
|
|
4519 |
|
|
|
4520 |
// Add cohort theme.
|
|
|
4521 |
if (!empty($CFG->allowcohortthemes)) {
|
|
|
4522 |
require_once($CFG->dirroot . '/cohort/lib.php');
|
|
|
4523 |
if ($cohorttheme = cohort_get_user_cohort_theme($user->id)) {
|
|
|
4524 |
$user->cohorttheme = $cohorttheme;
|
|
|
4525 |
}
|
|
|
4526 |
}
|
|
|
4527 |
|
|
|
4528 |
// Add the custom profile fields to the user record.
|
|
|
4529 |
$user->profile = array();
|
|
|
4530 |
if (!isguestuser($user)) {
|
|
|
4531 |
require_once($CFG->dirroot.'/user/profile/lib.php');
|
|
|
4532 |
profile_load_custom_fields($user);
|
|
|
4533 |
}
|
|
|
4534 |
|
|
|
4535 |
// Rewrite some variables if necessary.
|
|
|
4536 |
if (!empty($user->description)) {
|
|
|
4537 |
// No need to cart all of it around.
|
|
|
4538 |
$user->description = true;
|
|
|
4539 |
}
|
|
|
4540 |
if (isguestuser($user)) {
|
|
|
4541 |
// Guest language always same as site.
|
|
|
4542 |
$user->lang = get_newuser_language();
|
|
|
4543 |
// Name always in current language.
|
|
|
4544 |
$user->firstname = get_string('guestuser');
|
|
|
4545 |
$user->lastname = ' ';
|
|
|
4546 |
}
|
|
|
4547 |
|
|
|
4548 |
return $user;
|
|
|
4549 |
}
|
|
|
4550 |
|
|
|
4551 |
/**
|
|
|
4552 |
* Validate a password against the configured password policy
|
|
|
4553 |
*
|
|
|
4554 |
* @param string $password the password to be checked against the password policy
|
|
|
4555 |
* @param string|null $errmsg the error message to display when the password doesn't comply with the policy.
|
|
|
4556 |
* @param stdClass|null $user the user object to perform password validation against. Defaults to null if not provided.
|
|
|
4557 |
*
|
|
|
4558 |
* @return bool true if the password is valid according to the policy. false otherwise.
|
|
|
4559 |
*/
|
|
|
4560 |
function check_password_policy(string $password, ?string &$errmsg, ?stdClass $user = null) {
|
|
|
4561 |
global $CFG;
|
|
|
4562 |
if (!empty($CFG->passwordpolicy) && !isguestuser($user)) {
|
|
|
4563 |
$errors = get_password_policy_errors($password, $user);
|
|
|
4564 |
|
|
|
4565 |
foreach ($errors as $error) {
|
|
|
4566 |
$errmsg .= '<div>' . $error . '</div>';
|
|
|
4567 |
}
|
|
|
4568 |
}
|
|
|
4569 |
|
|
|
4570 |
return $errmsg == '';
|
|
|
4571 |
}
|
|
|
4572 |
|
|
|
4573 |
/**
|
|
|
4574 |
* Validate a password against the configured password policy.
|
|
|
4575 |
* Note: This function is unaffected by whether the password policy is enabled or not.
|
|
|
4576 |
*
|
|
|
4577 |
* @param string $password the password to be checked against the password policy
|
|
|
4578 |
* @param stdClass|null $user the user object to perform password validation against. Defaults to null if not provided.
|
|
|
4579 |
*
|
|
|
4580 |
* @return string[] Array of error messages.
|
|
|
4581 |
*/
|
|
|
4582 |
function get_password_policy_errors(string $password, ?stdClass $user = null) : array {
|
|
|
4583 |
global $CFG;
|
|
|
4584 |
|
|
|
4585 |
$errors = [];
|
|
|
4586 |
|
|
|
4587 |
if (core_text::strlen($password) < $CFG->minpasswordlength) {
|
|
|
4588 |
$errors[] = get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength);
|
|
|
4589 |
}
|
|
|
4590 |
if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
|
|
|
4591 |
$errors[] = get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits);
|
|
|
4592 |
}
|
|
|
4593 |
if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
|
|
|
4594 |
$errors[] = get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower);
|
|
|
4595 |
}
|
|
|
4596 |
if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
|
|
|
4597 |
$errors[] = get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper);
|
|
|
4598 |
}
|
|
|
4599 |
if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
|
|
|
4600 |
$errors[] = get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum);
|
|
|
4601 |
}
|
|
|
4602 |
if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
|
|
|
4603 |
$errors[] = get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars);
|
|
|
4604 |
}
|
|
|
4605 |
|
|
|
4606 |
// Fire any additional password policy functions from plugins.
|
|
|
4607 |
// Plugin functions should output an error message string or empty string for success.
|
|
|
4608 |
$pluginsfunction = get_plugins_with_function('check_password_policy');
|
|
|
4609 |
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
|
4610 |
foreach ($plugins as $pluginfunction) {
|
|
|
4611 |
$pluginerr = $pluginfunction($password, $user);
|
|
|
4612 |
if ($pluginerr) {
|
|
|
4613 |
$errors[] = $pluginerr;
|
|
|
4614 |
}
|
|
|
4615 |
}
|
|
|
4616 |
}
|
|
|
4617 |
|
|
|
4618 |
return $errors;
|
|
|
4619 |
}
|
|
|
4620 |
|
|
|
4621 |
/**
|
|
|
4622 |
* When logging in, this function is run to set certain preferences for the current SESSION.
|
|
|
4623 |
*/
|
|
|
4624 |
function set_login_session_preferences() {
|
|
|
4625 |
global $SESSION;
|
|
|
4626 |
|
|
|
4627 |
$SESSION->justloggedin = true;
|
|
|
4628 |
|
|
|
4629 |
unset($SESSION->lang);
|
|
|
4630 |
unset($SESSION->forcelang);
|
|
|
4631 |
unset($SESSION->load_navigation_admin);
|
|
|
4632 |
}
|
|
|
4633 |
|
|
|
4634 |
|
|
|
4635 |
/**
|
|
|
4636 |
* Delete a course, including all related data from the database, and any associated files.
|
|
|
4637 |
*
|
|
|
4638 |
* @param mixed $courseorid The id of the course or course object to delete.
|
|
|
4639 |
* @param bool $showfeedback Whether to display notifications of each action the function performs.
|
|
|
4640 |
* @return bool true if all the removals succeeded. false if there were any failures. If this
|
|
|
4641 |
* method returns false, some of the removals will probably have succeeded, and others
|
|
|
4642 |
* failed, but you have no way of knowing which.
|
|
|
4643 |
*/
|
|
|
4644 |
function delete_course($courseorid, $showfeedback = true) {
|
|
|
4645 |
global $DB, $CFG;
|
|
|
4646 |
|
|
|
4647 |
if (is_object($courseorid)) {
|
|
|
4648 |
$courseid = $courseorid->id;
|
|
|
4649 |
$course = $courseorid;
|
|
|
4650 |
} else {
|
|
|
4651 |
$courseid = $courseorid;
|
|
|
4652 |
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
|
|
|
4653 |
return false;
|
|
|
4654 |
}
|
|
|
4655 |
}
|
|
|
4656 |
$context = context_course::instance($courseid);
|
|
|
4657 |
|
|
|
4658 |
// Frontpage course can not be deleted!!
|
|
|
4659 |
if ($courseid == SITEID) {
|
|
|
4660 |
return false;
|
|
|
4661 |
}
|
|
|
4662 |
|
|
|
4663 |
// Allow plugins to use this course before we completely delete it.
|
|
|
4664 |
if ($pluginsfunction = get_plugins_with_function('pre_course_delete')) {
|
|
|
4665 |
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
|
4666 |
foreach ($plugins as $pluginfunction) {
|
|
|
4667 |
$pluginfunction($course);
|
|
|
4668 |
}
|
|
|
4669 |
}
|
|
|
4670 |
}
|
|
|
4671 |
|
|
|
4672 |
// Dispatch the hook for pre course delete actions.
|
|
|
4673 |
$hook = new \core_course\hook\before_course_deleted(
|
|
|
4674 |
course: $course,
|
|
|
4675 |
);
|
|
|
4676 |
\core\di::get(\core\hook\manager::class)->dispatch($hook);
|
|
|
4677 |
|
|
|
4678 |
// Tell the search manager we are about to delete a course. This prevents us sending updates
|
|
|
4679 |
// for each individual context being deleted.
|
|
|
4680 |
\core_search\manager::course_deleting_start($courseid);
|
|
|
4681 |
|
|
|
4682 |
$handler = core_course\customfield\course_handler::create();
|
|
|
4683 |
$handler->delete_instance($courseid);
|
|
|
4684 |
|
|
|
4685 |
// Make the course completely empty.
|
|
|
4686 |
remove_course_contents($courseid, $showfeedback);
|
|
|
4687 |
|
|
|
4688 |
// Delete the course and related context instance.
|
|
|
4689 |
context_helper::delete_instance(CONTEXT_COURSE, $courseid);
|
|
|
4690 |
|
|
|
4691 |
$DB->delete_records("course", array("id" => $courseid));
|
|
|
4692 |
$DB->delete_records("course_format_options", array("courseid" => $courseid));
|
|
|
4693 |
|
|
|
4694 |
// Reset all course related caches here.
|
|
|
4695 |
core_courseformat\base::reset_course_cache($courseid);
|
|
|
4696 |
|
|
|
4697 |
// Tell search that we have deleted the course so it can delete course data from the index.
|
|
|
4698 |
\core_search\manager::course_deleting_finish($courseid);
|
|
|
4699 |
|
|
|
4700 |
// Trigger a course deleted event.
|
|
|
4701 |
$event = \core\event\course_deleted::create(array(
|
|
|
4702 |
'objectid' => $course->id,
|
|
|
4703 |
'context' => $context,
|
|
|
4704 |
'other' => array(
|
|
|
4705 |
'shortname' => $course->shortname,
|
|
|
4706 |
'fullname' => $course->fullname,
|
|
|
4707 |
'idnumber' => $course->idnumber
|
|
|
4708 |
)
|
|
|
4709 |
));
|
|
|
4710 |
$event->add_record_snapshot('course', $course);
|
|
|
4711 |
$event->trigger();
|
|
|
4712 |
|
|
|
4713 |
return true;
|
|
|
4714 |
}
|
|
|
4715 |
|
|
|
4716 |
/**
|
|
|
4717 |
* Clear a course out completely, deleting all content but don't delete the course itself.
|
|
|
4718 |
*
|
|
|
4719 |
* This function does not verify any permissions.
|
|
|
4720 |
*
|
|
|
4721 |
* Please note this function also deletes all user enrolments,
|
|
|
4722 |
* enrolment instances and role assignments by default.
|
|
|
4723 |
*
|
|
|
4724 |
* $options:
|
|
|
4725 |
* - 'keep_roles_and_enrolments' - false by default
|
|
|
4726 |
* - 'keep_groups_and_groupings' - false by default
|
|
|
4727 |
*
|
|
|
4728 |
* @param int $courseid The id of the course that is being deleted
|
|
|
4729 |
* @param bool $showfeedback Whether to display notifications of each action the function performs.
|
|
|
4730 |
* @param array $options extra options
|
|
|
4731 |
* @return bool true if all the removals succeeded. false if there were any failures. If this
|
|
|
4732 |
* method returns false, some of the removals will probably have succeeded, and others
|
|
|
4733 |
* failed, but you have no way of knowing which.
|
|
|
4734 |
*/
|
|
|
4735 |
function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
|
|
|
4736 |
global $CFG, $DB, $OUTPUT;
|
|
|
4737 |
|
|
|
4738 |
require_once($CFG->libdir.'/badgeslib.php');
|
|
|
4739 |
require_once($CFG->libdir.'/completionlib.php');
|
|
|
4740 |
require_once($CFG->libdir.'/questionlib.php');
|
|
|
4741 |
require_once($CFG->libdir.'/gradelib.php');
|
|
|
4742 |
require_once($CFG->dirroot.'/group/lib.php');
|
|
|
4743 |
require_once($CFG->dirroot.'/comment/lib.php');
|
|
|
4744 |
require_once($CFG->dirroot.'/rating/lib.php');
|
|
|
4745 |
require_once($CFG->dirroot.'/notes/lib.php');
|
|
|
4746 |
|
|
|
4747 |
// Handle course badges.
|
|
|
4748 |
badges_handle_course_deletion($courseid);
|
|
|
4749 |
|
|
|
4750 |
// NOTE: these concatenated strings are suboptimal, but it is just extra info...
|
|
|
4751 |
$strdeleted = get_string('deleted').' - ';
|
|
|
4752 |
|
|
|
4753 |
// Some crazy wishlist of stuff we should skip during purging of course content.
|
|
|
4754 |
$options = (array)$options;
|
|
|
4755 |
|
|
|
4756 |
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
|
|
4757 |
$coursecontext = context_course::instance($courseid);
|
|
|
4758 |
$fs = get_file_storage();
|
|
|
4759 |
|
|
|
4760 |
// Delete course completion information, this has to be done before grades and enrols.
|
|
|
4761 |
$cc = new completion_info($course);
|
|
|
4762 |
$cc->clear_criteria();
|
|
|
4763 |
if ($showfeedback) {
|
|
|
4764 |
echo $OUTPUT->notification($strdeleted.get_string('completion', 'completion'), 'notifysuccess');
|
|
|
4765 |
}
|
|
|
4766 |
|
|
|
4767 |
// Remove all data from gradebook - this needs to be done before course modules
|
|
|
4768 |
// because while deleting this information, the system may need to reference
|
|
|
4769 |
// the course modules that own the grades.
|
|
|
4770 |
remove_course_grades($courseid, $showfeedback);
|
|
|
4771 |
remove_grade_letters($coursecontext, $showfeedback);
|
|
|
4772 |
|
|
|
4773 |
// Delete course blocks in any all child contexts,
|
|
|
4774 |
// they may depend on modules so delete them first.
|
|
|
4775 |
$childcontexts = $coursecontext->get_child_contexts(); // Returns all subcontexts since 2.2.
|
|
|
4776 |
foreach ($childcontexts as $childcontext) {
|
|
|
4777 |
blocks_delete_all_for_context($childcontext->id);
|
|
|
4778 |
}
|
|
|
4779 |
unset($childcontexts);
|
|
|
4780 |
blocks_delete_all_for_context($coursecontext->id);
|
|
|
4781 |
if ($showfeedback) {
|
|
|
4782 |
echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
|
|
|
4783 |
}
|
|
|
4784 |
|
|
|
4785 |
$DB->set_field('course_modules', 'deletioninprogress', '1', ['course' => $courseid]);
|
|
|
4786 |
rebuild_course_cache($courseid, true);
|
|
|
4787 |
|
|
|
4788 |
// Get the list of all modules that are properly installed.
|
|
|
4789 |
$allmodules = $DB->get_records_menu('modules', array(), '', 'name, id');
|
|
|
4790 |
|
|
|
4791 |
// Delete every instance of every module,
|
|
|
4792 |
// this has to be done before deleting of course level stuff.
|
|
|
4793 |
$locations = core_component::get_plugin_list('mod');
|
|
|
4794 |
foreach ($locations as $modname => $moddir) {
|
|
|
4795 |
if ($modname === 'NEWMODULE') {
|
|
|
4796 |
continue;
|
|
|
4797 |
}
|
|
|
4798 |
if (array_key_exists($modname, $allmodules)) {
|
|
|
4799 |
$sql = "SELECT cm.*, m.id AS modinstance, m.name, '$modname' AS modname
|
|
|
4800 |
FROM {".$modname."} m
|
|
|
4801 |
LEFT JOIN {course_modules} cm ON cm.instance = m.id AND cm.module = :moduleid
|
|
|
4802 |
WHERE m.course = :courseid";
|
|
|
4803 |
$instances = $DB->get_records_sql($sql, array('courseid' => $course->id,
|
|
|
4804 |
'modulename' => $modname, 'moduleid' => $allmodules[$modname]));
|
|
|
4805 |
|
|
|
4806 |
include_once("$moddir/lib.php"); // Shows php warning only if plugin defective.
|
|
|
4807 |
$moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance.
|
|
|
4808 |
|
|
|
4809 |
if ($instances) {
|
|
|
4810 |
foreach ($instances as $cm) {
|
|
|
4811 |
if ($cm->id) {
|
|
|
4812 |
// Delete activity context questions and question categories.
|
|
|
4813 |
question_delete_activity($cm);
|
|
|
4814 |
// Notify the competency subsystem.
|
|
|
4815 |
\core_competency\api::hook_course_module_deleted($cm);
|
|
|
4816 |
|
|
|
4817 |
// Delete all tag instances associated with the instance of this module.
|
|
|
4818 |
core_tag_tag::delete_instances("mod_{$modname}", null, context_module::instance($cm->id)->id);
|
|
|
4819 |
core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id);
|
|
|
4820 |
}
|
|
|
4821 |
if (function_exists($moddelete)) {
|
|
|
4822 |
// This purges all module data in related tables, extra user prefs, settings, etc.
|
|
|
4823 |
$moddelete($cm->modinstance);
|
|
|
4824 |
} else {
|
|
|
4825 |
// NOTE: we should not allow installation of modules with missing delete support!
|
|
|
4826 |
debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
|
|
|
4827 |
$DB->delete_records($modname, array('id' => $cm->modinstance));
|
|
|
4828 |
}
|
|
|
4829 |
|
|
|
4830 |
if ($cm->id) {
|
|
|
4831 |
// Delete cm and its context - orphaned contexts are purged in cron in case of any race condition.
|
|
|
4832 |
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
|
|
|
4833 |
$DB->delete_records('course_modules_completion', ['coursemoduleid' => $cm->id]);
|
|
|
4834 |
$DB->delete_records('course_modules_viewed', ['coursemoduleid' => $cm->id]);
|
|
|
4835 |
$DB->delete_records('course_modules', array('id' => $cm->id));
|
|
|
4836 |
rebuild_course_cache($cm->course, true);
|
|
|
4837 |
}
|
|
|
4838 |
}
|
|
|
4839 |
}
|
|
|
4840 |
if ($instances and $showfeedback) {
|
|
|
4841 |
echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
|
|
|
4842 |
}
|
|
|
4843 |
} else {
|
|
|
4844 |
// Ooops, this module is not properly installed, force-delete it in the next block.
|
|
|
4845 |
}
|
|
|
4846 |
}
|
|
|
4847 |
|
|
|
4848 |
// We have tried to delete everything the nice way - now let's force-delete any remaining module data.
|
|
|
4849 |
|
|
|
4850 |
// Delete completion defaults.
|
|
|
4851 |
$DB->delete_records("course_completion_defaults", array("course" => $courseid));
|
|
|
4852 |
|
|
|
4853 |
// Remove all data from availability and completion tables that is associated
|
|
|
4854 |
// with course-modules belonging to this course. Note this is done even if the
|
|
|
4855 |
// features are not enabled now, in case they were enabled previously.
|
|
|
4856 |
$DB->delete_records_subquery('course_modules_completion', 'coursemoduleid', 'id',
|
|
|
4857 |
'SELECT id from {course_modules} WHERE course = ?', [$courseid]);
|
|
|
4858 |
$DB->delete_records_subquery('course_modules_viewed', 'coursemoduleid', 'id',
|
|
|
4859 |
'SELECT id from {course_modules} WHERE course = ?', [$courseid]);
|
|
|
4860 |
|
|
|
4861 |
// Remove course-module data that has not been removed in modules' _delete_instance callbacks.
|
|
|
4862 |
$cms = $DB->get_records('course_modules', array('course' => $course->id));
|
|
|
4863 |
$allmodulesbyid = array_flip($allmodules);
|
|
|
4864 |
foreach ($cms as $cm) {
|
|
|
4865 |
if (array_key_exists($cm->module, $allmodulesbyid)) {
|
|
|
4866 |
try {
|
|
|
4867 |
$DB->delete_records($allmodulesbyid[$cm->module], array('id' => $cm->instance));
|
|
|
4868 |
} catch (Exception $e) {
|
|
|
4869 |
// Ignore weird or missing table problems.
|
|
|
4870 |
}
|
|
|
4871 |
}
|
|
|
4872 |
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
|
|
|
4873 |
$DB->delete_records('course_modules', array('id' => $cm->id));
|
|
|
4874 |
rebuild_course_cache($cm->course, true);
|
|
|
4875 |
}
|
|
|
4876 |
|
|
|
4877 |
if ($showfeedback) {
|
|
|
4878 |
echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
|
|
|
4879 |
}
|
|
|
4880 |
|
|
|
4881 |
// Delete questions and question categories.
|
|
|
4882 |
question_delete_course($course);
|
|
|
4883 |
if ($showfeedback) {
|
|
|
4884 |
echo $OUTPUT->notification($strdeleted.get_string('questions', 'question'), 'notifysuccess');
|
|
|
4885 |
}
|
|
|
4886 |
|
|
|
4887 |
// Delete content bank contents.
|
|
|
4888 |
$cb = new \core_contentbank\contentbank();
|
|
|
4889 |
$cbdeleted = $cb->delete_contents($coursecontext);
|
|
|
4890 |
if ($showfeedback && $cbdeleted) {
|
|
|
4891 |
echo $OUTPUT->notification($strdeleted.get_string('contentbank', 'contentbank'), 'notifysuccess');
|
|
|
4892 |
}
|
|
|
4893 |
|
|
|
4894 |
// Make sure there are no subcontexts left - all valid blocks and modules should be already gone.
|
|
|
4895 |
$childcontexts = $coursecontext->get_child_contexts(); // Returns all subcontexts since 2.2.
|
|
|
4896 |
foreach ($childcontexts as $childcontext) {
|
|
|
4897 |
$childcontext->delete();
|
|
|
4898 |
}
|
|
|
4899 |
unset($childcontexts);
|
|
|
4900 |
|
|
|
4901 |
// Remove roles and enrolments by default.
|
|
|
4902 |
if (empty($options['keep_roles_and_enrolments'])) {
|
|
|
4903 |
// This hack is used in restore when deleting contents of existing course.
|
|
|
4904 |
// During restore, we should remove only enrolment related data that the user performing the restore has a
|
|
|
4905 |
// permission to remove.
|
|
|
4906 |
$userid = $options['userid'] ?? null;
|
|
|
4907 |
enrol_course_delete($course, $userid);
|
|
|
4908 |
role_unassign_all(array('contextid' => $coursecontext->id, 'component' => ''), true);
|
|
|
4909 |
if ($showfeedback) {
|
|
|
4910 |
echo $OUTPUT->notification($strdeleted.get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
|
|
|
4911 |
}
|
|
|
4912 |
}
|
|
|
4913 |
|
|
|
4914 |
// Delete any groups, removing members and grouping/course links first.
|
|
|
4915 |
if (empty($options['keep_groups_and_groupings'])) {
|
|
|
4916 |
groups_delete_groupings($course->id, $showfeedback);
|
|
|
4917 |
groups_delete_groups($course->id, $showfeedback);
|
|
|
4918 |
}
|
|
|
4919 |
|
|
|
4920 |
// Filters be gone!
|
|
|
4921 |
filter_delete_all_for_context($coursecontext->id);
|
|
|
4922 |
|
|
|
4923 |
// Notes, you shall not pass!
|
|
|
4924 |
note_delete_all($course->id);
|
|
|
4925 |
|
|
|
4926 |
// Die comments!
|
|
|
4927 |
comment::delete_comments($coursecontext->id);
|
|
|
4928 |
|
|
|
4929 |
// Ratings are history too.
|
|
|
4930 |
$delopt = new stdclass();
|
|
|
4931 |
$delopt->contextid = $coursecontext->id;
|
|
|
4932 |
$rm = new rating_manager();
|
|
|
4933 |
$rm->delete_ratings($delopt);
|
|
|
4934 |
|
|
|
4935 |
// Delete course tags.
|
|
|
4936 |
core_tag_tag::remove_all_item_tags('core', 'course', $course->id);
|
|
|
4937 |
|
|
|
4938 |
// Give the course format the opportunity to remove its obscure data.
|
|
|
4939 |
$format = course_get_format($course);
|
|
|
4940 |
$format->delete_format_data();
|
|
|
4941 |
|
|
|
4942 |
// Notify the competency subsystem.
|
|
|
4943 |
\core_competency\api::hook_course_deleted($course);
|
|
|
4944 |
|
|
|
4945 |
// Delete calendar events.
|
|
|
4946 |
$DB->delete_records('event', array('courseid' => $course->id));
|
|
|
4947 |
$fs->delete_area_files($coursecontext->id, 'calendar');
|
|
|
4948 |
|
|
|
4949 |
// Delete all related records in other core tables that may have a courseid
|
|
|
4950 |
// This array stores the tables that need to be cleared, as
|
|
|
4951 |
// table_name => column_name that contains the course id.
|
|
|
4952 |
$tablestoclear = array(
|
|
|
4953 |
'backup_courses' => 'courseid', // Scheduled backup stuff.
|
|
|
4954 |
'user_lastaccess' => 'courseid', // User access info.
|
|
|
4955 |
);
|
|
|
4956 |
foreach ($tablestoclear as $table => $col) {
|
|
|
4957 |
$DB->delete_records($table, array($col => $course->id));
|
|
|
4958 |
}
|
|
|
4959 |
|
|
|
4960 |
// Delete all course backup files.
|
|
|
4961 |
$fs->delete_area_files($coursecontext->id, 'backup');
|
|
|
4962 |
|
|
|
4963 |
// Cleanup course record - remove links to deleted stuff.
|
|
|
4964 |
// Do not wipe cacherev, as this course might be reused and we need to ensure that it keeps
|
|
|
4965 |
// increasing.
|
|
|
4966 |
$oldcourse = new stdClass();
|
|
|
4967 |
$oldcourse->id = $course->id;
|
|
|
4968 |
$oldcourse->summary = '';
|
|
|
4969 |
$oldcourse->legacyfiles = 0;
|
|
|
4970 |
if (!empty($options['keep_groups_and_groupings'])) {
|
|
|
4971 |
$oldcourse->defaultgroupingid = 0;
|
|
|
4972 |
}
|
|
|
4973 |
$DB->update_record('course', $oldcourse);
|
|
|
4974 |
|
|
|
4975 |
// Delete course sections.
|
|
|
4976 |
$DB->delete_records('course_sections', array('course' => $course->id));
|
|
|
4977 |
|
|
|
4978 |
// Delete legacy, section and any other course files.
|
|
|
4979 |
$fs->delete_area_files($coursecontext->id, 'course'); // Files from summary and section.
|
|
|
4980 |
|
|
|
4981 |
// Delete all remaining stuff linked to context such as files, comments, ratings, etc.
|
|
|
4982 |
if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
|
|
|
4983 |
// Easy, do not delete the context itself...
|
|
|
4984 |
$coursecontext->delete_content();
|
|
|
4985 |
} else {
|
|
|
4986 |
// Hack alert!!!!
|
|
|
4987 |
// We can not drop all context stuff because it would bork enrolments and roles,
|
|
|
4988 |
// there might be also files used by enrol plugins...
|
|
|
4989 |
}
|
|
|
4990 |
|
|
|
4991 |
// Delete legacy files - just in case some files are still left there after conversion to new file api,
|
|
|
4992 |
// also some non-standard unsupported plugins may try to store something there.
|
|
|
4993 |
fulldelete($CFG->dataroot.'/'.$course->id);
|
|
|
4994 |
|
|
|
4995 |
// Delete from cache to reduce the cache size especially makes sense in case of bulk course deletion.
|
|
|
4996 |
course_modinfo::purge_course_cache($courseid);
|
|
|
4997 |
|
|
|
4998 |
// Trigger a course content deleted event.
|
|
|
4999 |
$event = \core\event\course_content_deleted::create(array(
|
|
|
5000 |
'objectid' => $course->id,
|
|
|
5001 |
'context' => $coursecontext,
|
|
|
5002 |
'other' => array('shortname' => $course->shortname,
|
|
|
5003 |
'fullname' => $course->fullname,
|
|
|
5004 |
'options' => $options) // Passing this for legacy reasons.
|
|
|
5005 |
));
|
|
|
5006 |
$event->add_record_snapshot('course', $course);
|
|
|
5007 |
$event->trigger();
|
|
|
5008 |
|
|
|
5009 |
return true;
|
|
|
5010 |
}
|
|
|
5011 |
|
|
|
5012 |
/**
|
|
|
5013 |
* Change dates in module - used from course reset.
|
|
|
5014 |
*
|
|
|
5015 |
* @param string $modname forum, assignment, etc
|
|
|
5016 |
* @param array $fields array of date fields from mod table
|
|
|
5017 |
* @param int $timeshift time difference
|
|
|
5018 |
* @param int $courseid
|
|
|
5019 |
* @param int $modid (Optional) passed if specific mod instance in course needs to be updated.
|
|
|
5020 |
* @return bool success
|
|
|
5021 |
*/
|
|
|
5022 |
function shift_course_mod_dates($modname, $fields, $timeshift, $courseid, $modid = 0) {
|
|
|
5023 |
global $CFG, $DB;
|
|
|
5024 |
include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
|
|
|
5025 |
|
|
|
5026 |
$return = true;
|
|
|
5027 |
$params = array($timeshift, $courseid);
|
|
|
5028 |
foreach ($fields as $field) {
|
|
|
5029 |
$updatesql = "UPDATE {".$modname."}
|
|
|
5030 |
SET $field = $field + ?
|
|
|
5031 |
WHERE course=? AND $field<>0";
|
|
|
5032 |
if ($modid) {
|
|
|
5033 |
$updatesql .= ' AND id=?';
|
|
|
5034 |
$params[] = $modid;
|
|
|
5035 |
}
|
|
|
5036 |
$return = $DB->execute($updatesql, $params) && $return;
|
|
|
5037 |
}
|
|
|
5038 |
|
|
|
5039 |
return $return;
|
|
|
5040 |
}
|
|
|
5041 |
|
|
|
5042 |
/**
|
|
|
5043 |
* This function will empty a course of user data.
|
|
|
5044 |
* It will retain the activities and the structure of the course.
|
|
|
5045 |
*
|
|
|
5046 |
* @param object $data an object containing all the settings including courseid (without magic quotes)
|
|
|
5047 |
* @return array status array of array component, item, error
|
|
|
5048 |
*/
|
|
|
5049 |
function reset_course_userdata($data) {
|
|
|
5050 |
global $CFG, $DB;
|
|
|
5051 |
require_once($CFG->libdir.'/gradelib.php');
|
|
|
5052 |
require_once($CFG->libdir.'/completionlib.php');
|
|
|
5053 |
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
|
|
|
5054 |
require_once($CFG->dirroot.'/group/lib.php');
|
|
|
5055 |
|
|
|
5056 |
$data->courseid = $data->id;
|
|
|
5057 |
$context = context_course::instance($data->courseid);
|
|
|
5058 |
|
|
|
5059 |
$eventparams = array(
|
|
|
5060 |
'context' => $context,
|
|
|
5061 |
'courseid' => $data->id,
|
|
|
5062 |
'other' => array(
|
|
|
5063 |
'reset_options' => (array) $data
|
|
|
5064 |
)
|
|
|
5065 |
);
|
|
|
5066 |
$event = \core\event\course_reset_started::create($eventparams);
|
|
|
5067 |
$event->trigger();
|
|
|
5068 |
|
|
|
5069 |
// Calculate the time shift of dates.
|
|
|
5070 |
if (!empty($data->reset_start_date)) {
|
|
|
5071 |
// Time part of course startdate should be zero.
|
|
|
5072 |
$data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
|
|
|
5073 |
} else {
|
|
|
5074 |
$data->timeshift = 0;
|
|
|
5075 |
}
|
|
|
5076 |
|
|
|
5077 |
// Result array: component, item, error.
|
|
|
5078 |
$status = array();
|
|
|
5079 |
|
|
|
5080 |
// Start the resetting.
|
|
|
5081 |
$componentstr = get_string('general');
|
|
|
5082 |
|
|
|
5083 |
// Move the course start time.
|
|
|
5084 |
if (!empty($data->reset_start_date) and $data->timeshift) {
|
|
|
5085 |
// Change course start data.
|
|
|
5086 |
$DB->set_field('course', 'startdate', $data->reset_start_date, array('id' => $data->courseid));
|
|
|
5087 |
// Update all course and group events - do not move activity events.
|
|
|
5088 |
$updatesql = "UPDATE {event}
|
|
|
5089 |
SET timestart = timestart + ?
|
|
|
5090 |
WHERE courseid=? AND instance=0";
|
|
|
5091 |
$DB->execute($updatesql, array($data->timeshift, $data->courseid));
|
|
|
5092 |
|
|
|
5093 |
// Update any date activity restrictions.
|
|
|
5094 |
if ($CFG->enableavailability) {
|
|
|
5095 |
\availability_date\condition::update_all_dates($data->courseid, $data->timeshift);
|
|
|
5096 |
}
|
|
|
5097 |
|
|
|
5098 |
// Update completion expected dates.
|
|
|
5099 |
if ($CFG->enablecompletion) {
|
|
|
5100 |
$modinfo = get_fast_modinfo($data->courseid);
|
|
|
5101 |
$changed = false;
|
|
|
5102 |
foreach ($modinfo->get_cms() as $cm) {
|
|
|
5103 |
if ($cm->completion && !empty($cm->completionexpected)) {
|
|
|
5104 |
$DB->set_field('course_modules', 'completionexpected', $cm->completionexpected + $data->timeshift,
|
|
|
5105 |
array('id' => $cm->id));
|
|
|
5106 |
$changed = true;
|
|
|
5107 |
}
|
|
|
5108 |
}
|
|
|
5109 |
|
|
|
5110 |
// Clear course cache if changes made.
|
|
|
5111 |
if ($changed) {
|
|
|
5112 |
rebuild_course_cache($data->courseid, true);
|
|
|
5113 |
}
|
|
|
5114 |
|
|
|
5115 |
// Update course date completion criteria.
|
|
|
5116 |
\completion_criteria_date::update_date($data->courseid, $data->timeshift);
|
|
|
5117 |
}
|
|
|
5118 |
|
|
|
5119 |
$status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false);
|
|
|
5120 |
}
|
|
|
5121 |
|
|
|
5122 |
if (!empty($data->reset_end_date)) {
|
|
|
5123 |
// If the user set a end date value respect it.
|
|
|
5124 |
$DB->set_field('course', 'enddate', $data->reset_end_date, array('id' => $data->courseid));
|
|
|
5125 |
} else if ($data->timeshift > 0 && $data->reset_end_date_old) {
|
|
|
5126 |
// If there is a time shift apply it to the end date as well.
|
|
|
5127 |
$enddate = $data->reset_end_date_old + $data->timeshift;
|
|
|
5128 |
$DB->set_field('course', 'enddate', $enddate, array('id' => $data->courseid));
|
|
|
5129 |
}
|
|
|
5130 |
|
|
|
5131 |
if (!empty($data->reset_events)) {
|
|
|
5132 |
$DB->delete_records('event', array('courseid' => $data->courseid));
|
|
|
5133 |
$status[] = array('component' => $componentstr, 'item' => get_string('deleteevents', 'calendar'), 'error' => false);
|
|
|
5134 |
}
|
|
|
5135 |
|
|
|
5136 |
if (!empty($data->reset_notes)) {
|
|
|
5137 |
require_once($CFG->dirroot.'/notes/lib.php');
|
|
|
5138 |
note_delete_all($data->courseid);
|
|
|
5139 |
$status[] = array('component' => $componentstr, 'item' => get_string('deletenotes', 'notes'), 'error' => false);
|
|
|
5140 |
}
|
|
|
5141 |
|
|
|
5142 |
if (!empty($data->delete_blog_associations)) {
|
|
|
5143 |
require_once($CFG->dirroot.'/blog/lib.php');
|
|
|
5144 |
blog_remove_associations_for_course($data->courseid);
|
|
|
5145 |
$status[] = array('component' => $componentstr, 'item' => get_string('deleteblogassociations', 'blog'), 'error' => false);
|
|
|
5146 |
}
|
|
|
5147 |
|
|
|
5148 |
if (!empty($data->reset_completion)) {
|
|
|
5149 |
// Delete course and activity completion information.
|
|
|
5150 |
$course = $DB->get_record('course', array('id' => $data->courseid));
|
|
|
5151 |
$cc = new completion_info($course);
|
|
|
5152 |
$cc->delete_all_completion_data();
|
|
|
5153 |
$status[] = array('component' => $componentstr,
|
|
|
5154 |
'item' => get_string('deletecompletiondata', 'completion'), 'error' => false);
|
|
|
5155 |
}
|
|
|
5156 |
|
|
|
5157 |
if (!empty($data->reset_competency_ratings)) {
|
|
|
5158 |
\core_competency\api::hook_course_reset_competency_ratings($data->courseid);
|
|
|
5159 |
$status[] = array('component' => $componentstr,
|
|
|
5160 |
'item' => get_string('deletecompetencyratings', 'core_competency'), 'error' => false);
|
|
|
5161 |
}
|
|
|
5162 |
|
|
|
5163 |
$componentstr = get_string('roles');
|
|
|
5164 |
|
|
|
5165 |
if (!empty($data->reset_roles_overrides)) {
|
|
|
5166 |
$children = $context->get_child_contexts();
|
|
|
5167 |
foreach ($children as $child) {
|
|
|
5168 |
$child->delete_capabilities();
|
|
|
5169 |
}
|
|
|
5170 |
$context->delete_capabilities();
|
|
|
5171 |
$status[] = array('component' => $componentstr, 'item' => get_string('deletecourseoverrides', 'role'), 'error' => false);
|
|
|
5172 |
}
|
|
|
5173 |
|
|
|
5174 |
if (!empty($data->reset_roles_local)) {
|
|
|
5175 |
$children = $context->get_child_contexts();
|
|
|
5176 |
foreach ($children as $child) {
|
|
|
5177 |
role_unassign_all(array('contextid' => $child->id));
|
|
|
5178 |
}
|
|
|
5179 |
$status[] = array('component' => $componentstr, 'item' => get_string('deletelocalroles', 'role'), 'error' => false);
|
|
|
5180 |
}
|
|
|
5181 |
|
|
|
5182 |
// First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
|
|
|
5183 |
$data->unenrolled = array();
|
|
|
5184 |
if (!empty($data->unenrol_users)) {
|
|
|
5185 |
$plugins = enrol_get_plugins(true);
|
|
|
5186 |
$instances = enrol_get_instances($data->courseid, true);
|
|
|
5187 |
foreach ($instances as $key => $instance) {
|
|
|
5188 |
if (!isset($plugins[$instance->enrol])) {
|
|
|
5189 |
unset($instances[$key]);
|
|
|
5190 |
continue;
|
|
|
5191 |
}
|
|
|
5192 |
}
|
|
|
5193 |
|
|
|
5194 |
$usersroles = enrol_get_course_users_roles($data->courseid);
|
|
|
5195 |
foreach ($data->unenrol_users as $withroleid) {
|
|
|
5196 |
if ($withroleid) {
|
|
|
5197 |
$sql = "SELECT ue.*
|
|
|
5198 |
FROM {user_enrolments} ue
|
|
|
5199 |
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
|
|
|
5200 |
JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
|
|
|
5201 |
JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
|
|
|
5202 |
$params = array('courseid' => $data->courseid, 'roleid' => $withroleid, 'courselevel' => CONTEXT_COURSE);
|
|
|
5203 |
|
|
|
5204 |
} else {
|
|
|
5205 |
// Without any role assigned at course context.
|
|
|
5206 |
$sql = "SELECT ue.*
|
|
|
5207 |
FROM {user_enrolments} ue
|
|
|
5208 |
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
|
|
|
5209 |
JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
|
|
|
5210 |
LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)
|
|
|
5211 |
WHERE ra.id IS null";
|
|
|
5212 |
$params = array('courseid' => $data->courseid, 'courselevel' => CONTEXT_COURSE);
|
|
|
5213 |
}
|
|
|
5214 |
|
|
|
5215 |
$rs = $DB->get_recordset_sql($sql, $params);
|
|
|
5216 |
foreach ($rs as $ue) {
|
|
|
5217 |
if (!isset($instances[$ue->enrolid])) {
|
|
|
5218 |
continue;
|
|
|
5219 |
}
|
|
|
5220 |
$instance = $instances[$ue->enrolid];
|
|
|
5221 |
$plugin = $plugins[$instance->enrol];
|
|
|
5222 |
if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
|
|
|
5223 |
continue;
|
|
|
5224 |
}
|
|
|
5225 |
|
|
|
5226 |
if ($withroleid && count($usersroles[$ue->userid]) > 1) {
|
|
|
5227 |
// If we don't remove all roles and user has more than one role, just remove this role.
|
|
|
5228 |
role_unassign($withroleid, $ue->userid, $context->id);
|
|
|
5229 |
|
|
|
5230 |
unset($usersroles[$ue->userid][$withroleid]);
|
|
|
5231 |
} else {
|
|
|
5232 |
// If we remove all roles or user has only one role, unenrol user from course.
|
|
|
5233 |
$plugin->unenrol_user($instance, $ue->userid);
|
|
|
5234 |
}
|
|
|
5235 |
$data->unenrolled[$ue->userid] = $ue->userid;
|
|
|
5236 |
}
|
|
|
5237 |
$rs->close();
|
|
|
5238 |
}
|
|
|
5239 |
}
|
|
|
5240 |
if (!empty($data->unenrolled)) {
|
|
|
5241 |
$status[] = array(
|
|
|
5242 |
'component' => $componentstr,
|
|
|
5243 |
'item' => get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')',
|
|
|
5244 |
'error' => false
|
|
|
5245 |
);
|
|
|
5246 |
}
|
|
|
5247 |
|
|
|
5248 |
$componentstr = get_string('groups');
|
|
|
5249 |
|
|
|
5250 |
// Remove all group members.
|
|
|
5251 |
if (!empty($data->reset_groups_members)) {
|
|
|
5252 |
groups_delete_group_members($data->courseid);
|
|
|
5253 |
$status[] = array('component' => $componentstr, 'item' => get_string('removegroupsmembers', 'group'), 'error' => false);
|
|
|
5254 |
}
|
|
|
5255 |
|
|
|
5256 |
// Remove all groups.
|
|
|
5257 |
if (!empty($data->reset_groups_remove)) {
|
|
|
5258 |
groups_delete_groups($data->courseid, false);
|
|
|
5259 |
$status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroups', 'group'), 'error' => false);
|
|
|
5260 |
}
|
|
|
5261 |
|
|
|
5262 |
// Remove all grouping members.
|
|
|
5263 |
if (!empty($data->reset_groupings_members)) {
|
|
|
5264 |
groups_delete_groupings_groups($data->courseid, false);
|
|
|
5265 |
$status[] = array('component' => $componentstr, 'item' => get_string('removegroupingsmembers', 'group'), 'error' => false);
|
|
|
5266 |
}
|
|
|
5267 |
|
|
|
5268 |
// Remove all groupings.
|
|
|
5269 |
if (!empty($data->reset_groupings_remove)) {
|
|
|
5270 |
groups_delete_groupings($data->courseid, false);
|
|
|
5271 |
$status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroupings', 'group'), 'error' => false);
|
|
|
5272 |
}
|
|
|
5273 |
|
|
|
5274 |
// Look in every instance of every module for data to delete.
|
|
|
5275 |
$unsupportedmods = array();
|
|
|
5276 |
if ($allmods = $DB->get_records('modules') ) {
|
|
|
5277 |
foreach ($allmods as $mod) {
|
|
|
5278 |
$modname = $mod->name;
|
|
|
5279 |
$modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
|
|
|
5280 |
$moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data.
|
|
|
5281 |
if (file_exists($modfile)) {
|
|
|
5282 |
if (!$DB->count_records($modname, array('course' => $data->courseid))) {
|
|
|
5283 |
continue; // Skip mods with no instances.
|
|
|
5284 |
}
|
|
|
5285 |
include_once($modfile);
|
|
|
5286 |
if (function_exists($moddeleteuserdata)) {
|
|
|
5287 |
$modstatus = $moddeleteuserdata($data);
|
|
|
5288 |
if (is_array($modstatus)) {
|
|
|
5289 |
$status = array_merge($status, $modstatus);
|
|
|
5290 |
} else {
|
|
|
5291 |
debugging('Module '.$modname.' returned incorrect staus - must be an array!');
|
|
|
5292 |
}
|
|
|
5293 |
} else {
|
|
|
5294 |
$unsupportedmods[] = $mod;
|
|
|
5295 |
}
|
|
|
5296 |
} else {
|
|
|
5297 |
debugging('Missing lib.php in '.$modname.' module!');
|
|
|
5298 |
}
|
|
|
5299 |
// Update calendar events for all modules.
|
|
|
5300 |
course_module_bulk_update_calendar_events($modname, $data->courseid);
|
|
|
5301 |
}
|
|
|
5302 |
// Purge the course cache after resetting course start date. MDL-76936
|
|
|
5303 |
if ($data->timeshift) {
|
|
|
5304 |
course_modinfo::purge_course_cache($data->courseid);
|
|
|
5305 |
}
|
|
|
5306 |
}
|
|
|
5307 |
|
|
|
5308 |
// Mention unsupported mods.
|
|
|
5309 |
if (!empty($unsupportedmods)) {
|
|
|
5310 |
foreach ($unsupportedmods as $mod) {
|
|
|
5311 |
$status[] = array(
|
|
|
5312 |
'component' => get_string('modulenameplural', $mod->name),
|
|
|
5313 |
'item' => '',
|
|
|
5314 |
'error' => get_string('resetnotimplemented')
|
|
|
5315 |
);
|
|
|
5316 |
}
|
|
|
5317 |
}
|
|
|
5318 |
|
|
|
5319 |
$componentstr = get_string('gradebook', 'grades');
|
|
|
5320 |
// Reset gradebook,.
|
|
|
5321 |
if (!empty($data->reset_gradebook_items)) {
|
|
|
5322 |
remove_course_grades($data->courseid, false);
|
|
|
5323 |
grade_grab_course_grades($data->courseid);
|
|
|
5324 |
grade_regrade_final_grades($data->courseid);
|
|
|
5325 |
$status[] = array('component' => $componentstr, 'item' => get_string('removeallcourseitems', 'grades'), 'error' => false);
|
|
|
5326 |
|
|
|
5327 |
} else if (!empty($data->reset_gradebook_grades)) {
|
|
|
5328 |
grade_course_reset($data->courseid);
|
|
|
5329 |
$status[] = array('component' => $componentstr, 'item' => get_string('removeallcoursegrades', 'grades'), 'error' => false);
|
|
|
5330 |
}
|
|
|
5331 |
// Reset comments.
|
|
|
5332 |
if (!empty($data->reset_comments)) {
|
|
|
5333 |
require_once($CFG->dirroot.'/comment/lib.php');
|
|
|
5334 |
comment::reset_course_page_comments($context);
|
|
|
5335 |
}
|
|
|
5336 |
|
|
|
5337 |
$event = \core\event\course_reset_ended::create($eventparams);
|
|
|
5338 |
$event->trigger();
|
|
|
5339 |
|
|
|
5340 |
return $status;
|
|
|
5341 |
}
|
|
|
5342 |
|
|
|
5343 |
/**
|
|
|
5344 |
* Generate an email processing address.
|
|
|
5345 |
*
|
|
|
5346 |
* @param int $modid
|
|
|
5347 |
* @param string $modargs
|
|
|
5348 |
* @return string Returns email processing address
|
|
|
5349 |
*/
|
|
|
5350 |
function generate_email_processing_address($modid, $modargs) {
|
|
|
5351 |
global $CFG;
|
|
|
5352 |
|
|
|
5353 |
$header = $CFG->mailprefix . substr(base64_encode(pack('C', $modid)), 0, 2).$modargs;
|
|
|
5354 |
return $header . substr(md5($header.get_site_identifier()), 0, 16).'@'.$CFG->maildomain;
|
|
|
5355 |
}
|
|
|
5356 |
|
|
|
5357 |
/**
|
|
|
5358 |
* ?
|
|
|
5359 |
*
|
|
|
5360 |
* @todo Finish documenting this function
|
|
|
5361 |
*
|
|
|
5362 |
* @param string $modargs
|
|
|
5363 |
* @param string $body Currently unused
|
|
|
5364 |
*/
|
|
|
5365 |
function moodle_process_email($modargs, $body) {
|
|
|
5366 |
global $DB;
|
|
|
5367 |
|
|
|
5368 |
// The first char should be an unencoded letter. We'll take this as an action.
|
|
|
5369 |
switch ($modargs[0]) {
|
|
|
5370 |
case 'B': { // Bounce.
|
|
|
5371 |
list(, $userid) = unpack('V', base64_decode(substr($modargs, 1, 8)));
|
|
|
5372 |
if ($user = $DB->get_record("user", array('id' => $userid), "id,email")) {
|
|
|
5373 |
// Check the half md5 of their email.
|
|
|
5374 |
$md5check = substr(md5($user->email), 0, 16);
|
|
|
5375 |
if ($md5check == substr($modargs, -16)) {
|
|
|
5376 |
set_bounce_count($user);
|
|
|
5377 |
}
|
|
|
5378 |
// Else maybe they've already changed it?
|
|
|
5379 |
}
|
|
|
5380 |
}
|
|
|
5381 |
break;
|
|
|
5382 |
// Maybe more later?
|
|
|
5383 |
}
|
|
|
5384 |
}
|
|
|
5385 |
|
|
|
5386 |
// CORRESPONDENCE.
|
|
|
5387 |
|
|
|
5388 |
/**
|
|
|
5389 |
* Get mailer instance, enable buffering, flush buffer or disable buffering.
|
|
|
5390 |
*
|
|
|
5391 |
* @param string $action 'get', 'buffer', 'close' or 'flush'
|
|
|
5392 |
* @return moodle_phpmailer|null mailer instance if 'get' used or nothing
|
|
|
5393 |
*/
|
|
|
5394 |
function get_mailer($action='get') {
|
|
|
5395 |
global $CFG;
|
|
|
5396 |
|
|
|
5397 |
/** @var moodle_phpmailer $mailer */
|
|
|
5398 |
static $mailer = null;
|
|
|
5399 |
static $counter = 0;
|
|
|
5400 |
|
|
|
5401 |
if (!isset($CFG->smtpmaxbulk)) {
|
|
|
5402 |
$CFG->smtpmaxbulk = 1;
|
|
|
5403 |
}
|
|
|
5404 |
|
|
|
5405 |
if ($action == 'get') {
|
|
|
5406 |
$prevkeepalive = false;
|
|
|
5407 |
|
|
|
5408 |
if (isset($mailer) and $mailer->Mailer == 'smtp') {
|
|
|
5409 |
if ($counter < $CFG->smtpmaxbulk and !$mailer->isError()) {
|
|
|
5410 |
$counter++;
|
|
|
5411 |
// Reset the mailer.
|
|
|
5412 |
$mailer->Priority = 3;
|
|
|
5413 |
$mailer->CharSet = 'UTF-8'; // Our default.
|
|
|
5414 |
$mailer->ContentType = "text/plain";
|
|
|
5415 |
$mailer->Encoding = "8bit";
|
|
|
5416 |
$mailer->From = "root@localhost";
|
|
|
5417 |
$mailer->FromName = "Root User";
|
|
|
5418 |
$mailer->Sender = "";
|
|
|
5419 |
$mailer->Subject = "";
|
|
|
5420 |
$mailer->Body = "";
|
|
|
5421 |
$mailer->AltBody = "";
|
|
|
5422 |
$mailer->ConfirmReadingTo = "";
|
|
|
5423 |
|
|
|
5424 |
$mailer->clearAllRecipients();
|
|
|
5425 |
$mailer->clearReplyTos();
|
|
|
5426 |
$mailer->clearAttachments();
|
|
|
5427 |
$mailer->clearCustomHeaders();
|
|
|
5428 |
return $mailer;
|
|
|
5429 |
}
|
|
|
5430 |
|
|
|
5431 |
$prevkeepalive = $mailer->SMTPKeepAlive;
|
|
|
5432 |
get_mailer('flush');
|
|
|
5433 |
}
|
|
|
5434 |
|
|
|
5435 |
require_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
|
|
|
5436 |
$mailer = new moodle_phpmailer();
|
|
|
5437 |
|
|
|
5438 |
$counter = 1;
|
|
|
5439 |
|
|
|
5440 |
if ($CFG->smtphosts == 'qmail') {
|
|
|
5441 |
// Use Qmail system.
|
|
|
5442 |
$mailer->isQmail();
|
|
|
5443 |
|
|
|
5444 |
} else if (empty($CFG->smtphosts)) {
|
|
|
5445 |
// Use PHP mail() = sendmail.
|
|
|
5446 |
$mailer->isMail();
|
|
|
5447 |
|
|
|
5448 |
} else {
|
|
|
5449 |
// Use SMTP directly.
|
|
|
5450 |
$mailer->isSMTP();
|
|
|
5451 |
if (!empty($CFG->debugsmtp) && (!empty($CFG->debugdeveloper))) {
|
|
|
5452 |
$mailer->SMTPDebug = 3;
|
|
|
5453 |
}
|
|
|
5454 |
// Specify main and backup servers.
|
|
|
5455 |
$mailer->Host = $CFG->smtphosts;
|
|
|
5456 |
// Specify secure connection protocol.
|
|
|
5457 |
$mailer->SMTPSecure = $CFG->smtpsecure;
|
|
|
5458 |
// Use previous keepalive.
|
|
|
5459 |
$mailer->SMTPKeepAlive = $prevkeepalive;
|
|
|
5460 |
|
|
|
5461 |
if ($CFG->smtpuser) {
|
|
|
5462 |
// Use SMTP authentication.
|
|
|
5463 |
$mailer->SMTPAuth = true;
|
|
|
5464 |
$mailer->Username = $CFG->smtpuser;
|
|
|
5465 |
$mailer->Password = $CFG->smtppass;
|
|
|
5466 |
}
|
|
|
5467 |
}
|
|
|
5468 |
|
|
|
5469 |
return $mailer;
|
|
|
5470 |
}
|
|
|
5471 |
|
|
|
5472 |
$nothing = null;
|
|
|
5473 |
|
|
|
5474 |
// Keep smtp session open after sending.
|
|
|
5475 |
if ($action == 'buffer') {
|
|
|
5476 |
if (!empty($CFG->smtpmaxbulk)) {
|
|
|
5477 |
get_mailer('flush');
|
|
|
5478 |
$m = get_mailer();
|
|
|
5479 |
if ($m->Mailer == 'smtp') {
|
|
|
5480 |
$m->SMTPKeepAlive = true;
|
|
|
5481 |
}
|
|
|
5482 |
}
|
|
|
5483 |
return $nothing;
|
|
|
5484 |
}
|
|
|
5485 |
|
|
|
5486 |
// Close smtp session, but continue buffering.
|
|
|
5487 |
if ($action == 'flush') {
|
|
|
5488 |
if (isset($mailer) and $mailer->Mailer == 'smtp') {
|
|
|
5489 |
if (!empty($mailer->SMTPDebug)) {
|
|
|
5490 |
echo '<pre>'."\n";
|
|
|
5491 |
}
|
|
|
5492 |
$mailer->SmtpClose();
|
|
|
5493 |
if (!empty($mailer->SMTPDebug)) {
|
|
|
5494 |
echo '</pre>';
|
|
|
5495 |
}
|
|
|
5496 |
}
|
|
|
5497 |
return $nothing;
|
|
|
5498 |
}
|
|
|
5499 |
|
|
|
5500 |
// Close smtp session, do not buffer anymore.
|
|
|
5501 |
if ($action == 'close') {
|
|
|
5502 |
if (isset($mailer) and $mailer->Mailer == 'smtp') {
|
|
|
5503 |
get_mailer('flush');
|
|
|
5504 |
$mailer->SMTPKeepAlive = false;
|
|
|
5505 |
}
|
|
|
5506 |
$mailer = null; // Better force new instance.
|
|
|
5507 |
return $nothing;
|
|
|
5508 |
}
|
|
|
5509 |
}
|
|
|
5510 |
|
|
|
5511 |
/**
|
|
|
5512 |
* A helper function to test for email diversion
|
|
|
5513 |
*
|
|
|
5514 |
* @param string $email
|
|
|
5515 |
* @return bool Returns true if the email should be diverted
|
|
|
5516 |
*/
|
|
|
5517 |
function email_should_be_diverted($email) {
|
|
|
5518 |
global $CFG;
|
|
|
5519 |
|
|
|
5520 |
if (empty($CFG->divertallemailsto)) {
|
|
|
5521 |
return false;
|
|
|
5522 |
}
|
|
|
5523 |
|
|
|
5524 |
if (empty($CFG->divertallemailsexcept)) {
|
|
|
5525 |
return true;
|
|
|
5526 |
}
|
|
|
5527 |
|
|
|
5528 |
$patterns = array_map('trim', preg_split("/[\s,]+/", $CFG->divertallemailsexcept, -1, PREG_SPLIT_NO_EMPTY));
|
|
|
5529 |
foreach ($patterns as $pattern) {
|
|
|
5530 |
if (preg_match("/{$pattern}/i", $email)) {
|
|
|
5531 |
return false;
|
|
|
5532 |
}
|
|
|
5533 |
}
|
|
|
5534 |
|
|
|
5535 |
return true;
|
|
|
5536 |
}
|
|
|
5537 |
|
|
|
5538 |
/**
|
|
|
5539 |
* Generate a unique email Message-ID using the moodle domain and install path
|
|
|
5540 |
*
|
|
|
5541 |
* @param string $localpart An optional unique message id prefix.
|
|
|
5542 |
* @return string The formatted ID ready for appending to the email headers.
|
|
|
5543 |
*/
|
|
|
5544 |
function generate_email_messageid($localpart = null) {
|
|
|
5545 |
global $CFG;
|
|
|
5546 |
|
|
|
5547 |
$urlinfo = parse_url($CFG->wwwroot);
|
|
|
5548 |
$base = '@' . $urlinfo['host'];
|
|
|
5549 |
|
|
|
5550 |
// If multiple moodles are on the same domain we want to tell them
|
|
|
5551 |
// apart so we add the install path to the local part. This means
|
|
|
5552 |
// that the id local part should never contain a / character so
|
|
|
5553 |
// we can correctly parse the id to reassemble the wwwroot.
|
|
|
5554 |
if (isset($urlinfo['path'])) {
|
|
|
5555 |
$base = $urlinfo['path'] . $base;
|
|
|
5556 |
}
|
|
|
5557 |
|
|
|
5558 |
if (empty($localpart)) {
|
|
|
5559 |
$localpart = uniqid('', true);
|
|
|
5560 |
}
|
|
|
5561 |
|
|
|
5562 |
// Because we may have an option /installpath suffix to the local part
|
|
|
5563 |
// of the id we need to escape any / chars which are in the $localpart.
|
|
|
5564 |
$localpart = str_replace('/', '%2F', $localpart);
|
|
|
5565 |
|
|
|
5566 |
return '<' . $localpart . $base . '>';
|
|
|
5567 |
}
|
|
|
5568 |
|
|
|
5569 |
/**
|
|
|
5570 |
* Send an email to a specified user
|
|
|
5571 |
*
|
|
|
5572 |
* @param stdClass $user A {@link $USER} object
|
|
|
5573 |
* @param stdClass $from A {@link $USER} object
|
|
|
5574 |
* @param string $subject plain text subject line of the email
|
|
|
5575 |
* @param string $messagetext plain text version of the message
|
|
|
5576 |
* @param string $messagehtml complete html version of the message (optional)
|
|
|
5577 |
* @param string $attachment a file on the filesystem, either relative to $CFG->dataroot or a full path to a file in one of
|
|
|
5578 |
* the following directories: $CFG->cachedir, $CFG->dataroot, $CFG->dirroot, $CFG->localcachedir, $CFG->tempdir
|
|
|
5579 |
* @param string $attachname the name of the file (extension indicates MIME)
|
|
|
5580 |
* @param bool $usetrueaddress determines whether $from email address should
|
|
|
5581 |
* be sent out. Will be overruled by user profile setting for maildisplay
|
|
|
5582 |
* @param string $replyto Email address to reply to
|
|
|
5583 |
* @param string $replytoname Name of reply to recipient
|
|
|
5584 |
* @param int $wordwrapwidth custom word wrap width, default 79
|
|
|
5585 |
* @return bool Returns true if mail was sent OK and false if there was an error.
|
|
|
5586 |
*/
|
|
|
5587 |
function email_to_user($user, $from, $subject, $messagetext, $messagehtml = '', $attachment = '', $attachname = '',
|
|
|
5588 |
$usetrueaddress = true, $replyto = '', $replytoname = '', $wordwrapwidth = 79) {
|
|
|
5589 |
|
|
|
5590 |
global $CFG, $PAGE, $SITE;
|
|
|
5591 |
|
|
|
5592 |
if (empty($user) or empty($user->id)) {
|
|
|
5593 |
debugging('Can not send email to null user', DEBUG_DEVELOPER);
|
|
|
5594 |
return false;
|
|
|
5595 |
}
|
|
|
5596 |
|
|
|
5597 |
if (empty($user->email)) {
|
|
|
5598 |
debugging('Can not send email to user without email: '.$user->id, DEBUG_DEVELOPER);
|
|
|
5599 |
return false;
|
|
|
5600 |
}
|
|
|
5601 |
|
|
|
5602 |
if (!empty($user->deleted)) {
|
|
|
5603 |
debugging('Can not send email to deleted user: '.$user->id, DEBUG_DEVELOPER);
|
|
|
5604 |
return false;
|
|
|
5605 |
}
|
|
|
5606 |
|
|
|
5607 |
if (defined('BEHAT_SITE_RUNNING')) {
|
|
|
5608 |
// Fake email sending in behat.
|
|
|
5609 |
return true;
|
|
|
5610 |
}
|
|
|
5611 |
|
|
|
5612 |
if (!empty($CFG->noemailever)) {
|
|
|
5613 |
// Hidden setting for development sites, set in config.php if needed.
|
|
|
5614 |
debugging('Not sending email due to $CFG->noemailever config setting', DEBUG_NORMAL);
|
|
|
5615 |
return true;
|
|
|
5616 |
}
|
|
|
5617 |
|
|
|
5618 |
if (email_should_be_diverted($user->email)) {
|
|
|
5619 |
$subject = "[DIVERTED {$user->email}] $subject";
|
|
|
5620 |
$user = clone($user);
|
|
|
5621 |
$user->email = $CFG->divertallemailsto;
|
|
|
5622 |
}
|
|
|
5623 |
|
|
|
5624 |
// Skip mail to suspended users.
|
|
|
5625 |
if ((isset($user->auth) && $user->auth=='nologin') or (isset($user->suspended) && $user->suspended)) {
|
|
|
5626 |
return true;
|
|
|
5627 |
}
|
|
|
5628 |
|
|
|
5629 |
if (!validate_email($user->email)) {
|
|
|
5630 |
// We can not send emails to invalid addresses - it might create security issue or confuse the mailer.
|
|
|
5631 |
debugging("email_to_user: User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.");
|
|
|
5632 |
return false;
|
|
|
5633 |
}
|
|
|
5634 |
|
|
|
5635 |
if (over_bounce_threshold($user)) {
|
|
|
5636 |
debugging("email_to_user: User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
|
|
|
5637 |
return false;
|
|
|
5638 |
}
|
|
|
5639 |
|
|
|
5640 |
// TLD .invalid is specifically reserved for invalid domain names.
|
|
|
5641 |
// For More information, see {@link http://tools.ietf.org/html/rfc2606#section-2}.
|
|
|
5642 |
if (substr($user->email, -8) == '.invalid') {
|
|
|
5643 |
debugging("email_to_user: User $user->id (".fullname($user).") email domain ($user->email) is invalid! Not sending.");
|
|
|
5644 |
return true; // This is not an error.
|
|
|
5645 |
}
|
|
|
5646 |
|
|
|
5647 |
// If the user is a remote mnet user, parse the email text for URL to the
|
|
|
5648 |
// wwwroot and modify the url to direct the user's browser to login at their
|
|
|
5649 |
// home site (identity provider - idp) before hitting the link itself.
|
|
|
5650 |
if (is_mnet_remote_user($user)) {
|
|
|
5651 |
require_once($CFG->dirroot.'/mnet/lib.php');
|
|
|
5652 |
|
|
|
5653 |
$jumpurl = mnet_get_idp_jump_url($user);
|
|
|
5654 |
$callback = partial('mnet_sso_apply_indirection', $jumpurl);
|
|
|
5655 |
|
|
|
5656 |
$messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
|
|
|
5657 |
$callback,
|
|
|
5658 |
$messagetext);
|
|
|
5659 |
$messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
|
|
|
5660 |
$callback,
|
|
|
5661 |
$messagehtml);
|
|
|
5662 |
}
|
|
|
5663 |
$mail = get_mailer();
|
|
|
5664 |
|
|
|
5665 |
if (!empty($mail->SMTPDebug)) {
|
|
|
5666 |
echo '<pre>' . "\n";
|
|
|
5667 |
}
|
|
|
5668 |
|
|
|
5669 |
$temprecipients = array();
|
|
|
5670 |
$tempreplyto = array();
|
|
|
5671 |
|
|
|
5672 |
// Make sure that we fall back onto some reasonable no-reply address.
|
|
|
5673 |
$noreplyaddressdefault = 'noreply@' . get_host_from_url($CFG->wwwroot);
|
|
|
5674 |
$noreplyaddress = empty($CFG->noreplyaddress) ? $noreplyaddressdefault : $CFG->noreplyaddress;
|
|
|
5675 |
|
|
|
5676 |
if (!validate_email($noreplyaddress)) {
|
|
|
5677 |
debugging('email_to_user: Invalid noreply-email '.s($noreplyaddress));
|
|
|
5678 |
$noreplyaddress = $noreplyaddressdefault;
|
|
|
5679 |
}
|
|
|
5680 |
|
|
|
5681 |
// Make up an email address for handling bounces.
|
|
|
5682 |
if (!empty($CFG->handlebounces)) {
|
|
|
5683 |
$modargs = 'B'.base64_encode(pack('V', $user->id)).substr(md5($user->email), 0, 16);
|
|
|
5684 |
$mail->Sender = generate_email_processing_address(0, $modargs);
|
|
|
5685 |
} else {
|
|
|
5686 |
$mail->Sender = $noreplyaddress;
|
|
|
5687 |
}
|
|
|
5688 |
|
|
|
5689 |
// Make sure that the explicit replyto is valid, fall back to the implicit one.
|
|
|
5690 |
if (!empty($replyto) && !validate_email($replyto)) {
|
|
|
5691 |
debugging('email_to_user: Invalid replyto-email '.s($replyto));
|
|
|
5692 |
$replyto = $noreplyaddress;
|
|
|
5693 |
}
|
|
|
5694 |
|
|
|
5695 |
if (is_string($from)) { // So we can pass whatever we want if there is need.
|
|
|
5696 |
$mail->From = $noreplyaddress;
|
|
|
5697 |
$mail->FromName = $from;
|
|
|
5698 |
// Check if using the true address is true, and the email is in the list of allowed domains for sending email,
|
|
|
5699 |
// and that the senders email setting is either displayed to everyone, or display to only other users that are enrolled
|
|
|
5700 |
// in a course with the sender.
|
|
|
5701 |
} else if ($usetrueaddress && can_send_from_real_email_address($from, $user)) {
|
|
|
5702 |
if (!validate_email($from->email)) {
|
|
|
5703 |
debugging('email_to_user: Invalid from-email '.s($from->email).' - not sending');
|
|
|
5704 |
// Better not to use $noreplyaddress in this case.
|
|
|
5705 |
return false;
|
|
|
5706 |
}
|
|
|
5707 |
$mail->From = $from->email;
|
|
|
5708 |
$fromdetails = new stdClass();
|
|
|
5709 |
$fromdetails->name = fullname($from);
|
|
|
5710 |
$fromdetails->url = preg_replace('#^https?://#', '', $CFG->wwwroot);
|
|
|
5711 |
$fromdetails->siteshortname = format_string($SITE->shortname);
|
|
|
5712 |
$fromstring = $fromdetails->name;
|
|
|
5713 |
if ($CFG->emailfromvia == EMAIL_VIA_ALWAYS) {
|
|
|
5714 |
$fromstring = get_string('emailvia', 'core', $fromdetails);
|
|
|
5715 |
}
|
|
|
5716 |
$mail->FromName = $fromstring;
|
|
|
5717 |
if (empty($replyto)) {
|
|
|
5718 |
$tempreplyto[] = array($from->email, fullname($from));
|
|
|
5719 |
}
|
|
|
5720 |
} else {
|
|
|
5721 |
$mail->From = $noreplyaddress;
|
|
|
5722 |
$fromdetails = new stdClass();
|
|
|
5723 |
$fromdetails->name = fullname($from);
|
|
|
5724 |
$fromdetails->url = preg_replace('#^https?://#', '', $CFG->wwwroot);
|
|
|
5725 |
$fromdetails->siteshortname = format_string($SITE->shortname);
|
|
|
5726 |
$fromstring = $fromdetails->name;
|
|
|
5727 |
if ($CFG->emailfromvia != EMAIL_VIA_NEVER) {
|
|
|
5728 |
$fromstring = get_string('emailvia', 'core', $fromdetails);
|
|
|
5729 |
}
|
|
|
5730 |
$mail->FromName = $fromstring;
|
|
|
5731 |
if (empty($replyto)) {
|
|
|
5732 |
$tempreplyto[] = array($noreplyaddress, get_string('noreplyname'));
|
|
|
5733 |
}
|
|
|
5734 |
}
|
|
|
5735 |
|
|
|
5736 |
if (!empty($replyto)) {
|
|
|
5737 |
$tempreplyto[] = array($replyto, $replytoname);
|
|
|
5738 |
}
|
|
|
5739 |
|
|
|
5740 |
$temprecipients[] = array($user->email, fullname($user));
|
|
|
5741 |
|
|
|
5742 |
// Set word wrap.
|
|
|
5743 |
$mail->WordWrap = $wordwrapwidth;
|
|
|
5744 |
|
|
|
5745 |
if (!empty($from->customheaders)) {
|
|
|
5746 |
// Add custom headers.
|
|
|
5747 |
if (is_array($from->customheaders)) {
|
|
|
5748 |
foreach ($from->customheaders as $customheader) {
|
|
|
5749 |
$mail->addCustomHeader($customheader);
|
|
|
5750 |
}
|
|
|
5751 |
} else {
|
|
|
5752 |
$mail->addCustomHeader($from->customheaders);
|
|
|
5753 |
}
|
|
|
5754 |
}
|
|
|
5755 |
|
|
|
5756 |
// If the X-PHP-Originating-Script email header is on then also add an additional
|
|
|
5757 |
// header with details of where exactly in moodle the email was triggered from,
|
|
|
5758 |
// either a call to message_send() or to email_to_user().
|
|
|
5759 |
if (ini_get('mail.add_x_header')) {
|
|
|
5760 |
|
|
|
5761 |
$stack = debug_backtrace(false);
|
|
|
5762 |
$origin = $stack[0];
|
|
|
5763 |
|
|
|
5764 |
foreach ($stack as $depth => $call) {
|
|
|
5765 |
if ($call['function'] == 'message_send') {
|
|
|
5766 |
$origin = $call;
|
|
|
5767 |
}
|
|
|
5768 |
}
|
|
|
5769 |
|
|
|
5770 |
$originheader = $CFG->wwwroot . ' => ' . gethostname() . ':'
|
|
|
5771 |
. str_replace($CFG->dirroot . '/', '', $origin['file']) . ':' . $origin['line'];
|
|
|
5772 |
$mail->addCustomHeader('X-Moodle-Originating-Script: ' . $originheader);
|
|
|
5773 |
}
|
|
|
5774 |
|
|
|
5775 |
if (!empty($CFG->emailheaders)) {
|
|
|
5776 |
$headers = array_map('trim', explode("\n", $CFG->emailheaders));
|
|
|
5777 |
foreach ($headers as $header) {
|
|
|
5778 |
if (!empty($header)) {
|
|
|
5779 |
$mail->addCustomHeader($header);
|
|
|
5780 |
}
|
|
|
5781 |
}
|
|
|
5782 |
}
|
|
|
5783 |
|
|
|
5784 |
if (!empty($from->priority)) {
|
|
|
5785 |
$mail->Priority = $from->priority;
|
|
|
5786 |
}
|
|
|
5787 |
|
|
|
5788 |
$renderer = $PAGE->get_renderer('core');
|
|
|
5789 |
$context = array(
|
|
|
5790 |
'sitefullname' => $SITE->fullname,
|
|
|
5791 |
'siteshortname' => $SITE->shortname,
|
|
|
5792 |
'sitewwwroot' => $CFG->wwwroot,
|
|
|
5793 |
'subject' => $subject,
|
|
|
5794 |
'prefix' => $CFG->emailsubjectprefix,
|
|
|
5795 |
'to' => $user->email,
|
|
|
5796 |
'toname' => fullname($user),
|
|
|
5797 |
'from' => $mail->From,
|
|
|
5798 |
'fromname' => $mail->FromName,
|
|
|
5799 |
);
|
|
|
5800 |
if (!empty($tempreplyto[0])) {
|
|
|
5801 |
$context['replyto'] = $tempreplyto[0][0];
|
|
|
5802 |
$context['replytoname'] = $tempreplyto[0][1];
|
|
|
5803 |
}
|
|
|
5804 |
if ($user->id > 0) {
|
|
|
5805 |
$context['touserid'] = $user->id;
|
|
|
5806 |
$context['tousername'] = $user->username;
|
|
|
5807 |
}
|
|
|
5808 |
|
|
|
5809 |
if (!empty($user->mailformat) && $user->mailformat == 1) {
|
|
|
5810 |
// Only process html templates if the user preferences allow html email.
|
|
|
5811 |
|
|
|
5812 |
if (!$messagehtml) {
|
|
|
5813 |
// If no html has been given, BUT there is an html wrapping template then
|
|
|
5814 |
// auto convert the text to html and then wrap it.
|
|
|
5815 |
$messagehtml = trim(text_to_html($messagetext));
|
|
|
5816 |
}
|
|
|
5817 |
$context['body'] = $messagehtml;
|
|
|
5818 |
$messagehtml = $renderer->render_from_template('core/email_html', $context);
|
|
|
5819 |
}
|
|
|
5820 |
|
|
|
5821 |
$context['body'] = html_to_text(nl2br($messagetext));
|
|
|
5822 |
$mail->Subject = $renderer->render_from_template('core/email_subject', $context);
|
|
|
5823 |
$mail->FromName = $renderer->render_from_template('core/email_fromname', $context);
|
|
|
5824 |
$messagetext = $renderer->render_from_template('core/email_text', $context);
|
|
|
5825 |
|
|
|
5826 |
// Autogenerate a MessageID if it's missing.
|
|
|
5827 |
if (empty($mail->MessageID)) {
|
|
|
5828 |
$mail->MessageID = generate_email_messageid();
|
|
|
5829 |
}
|
|
|
5830 |
|
|
|
5831 |
if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) {
|
|
|
5832 |
// Don't ever send HTML to users who don't want it.
|
|
|
5833 |
$mail->isHTML(true);
|
|
|
5834 |
$mail->Encoding = 'quoted-printable';
|
|
|
5835 |
$mail->Body = $messagehtml;
|
|
|
5836 |
$mail->AltBody = "\n$messagetext\n";
|
|
|
5837 |
} else {
|
|
|
5838 |
$mail->IsHTML(false);
|
|
|
5839 |
$mail->Body = "\n$messagetext\n";
|
|
|
5840 |
}
|
|
|
5841 |
|
|
|
5842 |
if ($attachment && $attachname) {
|
|
|
5843 |
if (preg_match( "~\\.\\.~" , $attachment )) {
|
|
|
5844 |
// Security check for ".." in dir path.
|
|
|
5845 |
$supportuser = core_user::get_support_user();
|
|
|
5846 |
$temprecipients[] = array($supportuser->email, fullname($supportuser, true));
|
|
|
5847 |
$mail->addStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
|
|
|
5848 |
} else {
|
|
|
5849 |
require_once($CFG->libdir.'/filelib.php');
|
|
|
5850 |
$mimetype = mimeinfo('type', $attachname);
|
|
|
5851 |
|
|
|
5852 |
// Before doing the comparison, make sure that the paths are correct (Windows uses slashes in the other direction).
|
|
|
5853 |
// The absolute (real) path is also fetched to ensure that comparisons to allowed paths are compared equally.
|
|
|
5854 |
$attachpath = str_replace('\\', '/', realpath($attachment));
|
|
|
5855 |
|
|
|
5856 |
// Build an array of all filepaths from which attachments can be added (normalised slashes, absolute/real path).
|
|
|
5857 |
$allowedpaths = array_map(function(string $path): string {
|
|
|
5858 |
return str_replace('\\', '/', realpath($path));
|
|
|
5859 |
}, [
|
|
|
5860 |
$CFG->cachedir,
|
|
|
5861 |
$CFG->dataroot,
|
|
|
5862 |
$CFG->dirroot,
|
|
|
5863 |
$CFG->localcachedir,
|
|
|
5864 |
$CFG->tempdir,
|
|
|
5865 |
$CFG->localrequestdir,
|
|
|
5866 |
]);
|
|
|
5867 |
|
|
|
5868 |
// Set addpath to true.
|
|
|
5869 |
$addpath = true;
|
|
|
5870 |
|
|
|
5871 |
// Check if attachment includes one of the allowed paths.
|
|
|
5872 |
foreach (array_filter($allowedpaths) as $allowedpath) {
|
|
|
5873 |
// Set addpath to false if the attachment includes one of the allowed paths.
|
|
|
5874 |
if (strpos($attachpath, $allowedpath) === 0) {
|
|
|
5875 |
$addpath = false;
|
|
|
5876 |
break;
|
|
|
5877 |
}
|
|
|
5878 |
}
|
|
|
5879 |
|
|
|
5880 |
// If the attachment is a full path to a file in the multiple allowed paths, use it as is,
|
|
|
5881 |
// otherwise assume it is a relative path from the dataroot (for backwards compatibility reasons).
|
|
|
5882 |
if ($addpath == true) {
|
|
|
5883 |
$attachment = $CFG->dataroot . '/' . $attachment;
|
|
|
5884 |
}
|
|
|
5885 |
|
|
|
5886 |
$mail->addAttachment($attachment, $attachname, 'base64', $mimetype);
|
|
|
5887 |
}
|
|
|
5888 |
}
|
|
|
5889 |
|
|
|
5890 |
// Check if the email should be sent in an other charset then the default UTF-8.
|
|
|
5891 |
if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
|
|
|
5892 |
|
|
|
5893 |
// Use the defined site mail charset or eventually the one preferred by the recipient.
|
|
|
5894 |
$charset = $CFG->sitemailcharset;
|
|
|
5895 |
if (!empty($CFG->allowusermailcharset)) {
|
|
|
5896 |
if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
|
|
|
5897 |
$charset = $useremailcharset;
|
|
|
5898 |
}
|
|
|
5899 |
}
|
|
|
5900 |
|
|
|
5901 |
// Convert all the necessary strings if the charset is supported.
|
|
|
5902 |
$charsets = get_list_of_charsets();
|
|
|
5903 |
unset($charsets['UTF-8']);
|
|
|
5904 |
if (in_array($charset, $charsets)) {
|
|
|
5905 |
$mail->CharSet = $charset;
|
|
|
5906 |
$mail->FromName = core_text::convert($mail->FromName, 'utf-8', strtolower($charset));
|
|
|
5907 |
$mail->Subject = core_text::convert($mail->Subject, 'utf-8', strtolower($charset));
|
|
|
5908 |
$mail->Body = core_text::convert($mail->Body, 'utf-8', strtolower($charset));
|
|
|
5909 |
$mail->AltBody = core_text::convert($mail->AltBody, 'utf-8', strtolower($charset));
|
|
|
5910 |
|
|
|
5911 |
foreach ($temprecipients as $key => $values) {
|
|
|
5912 |
$temprecipients[$key][1] = core_text::convert($values[1], 'utf-8', strtolower($charset));
|
|
|
5913 |
}
|
|
|
5914 |
foreach ($tempreplyto as $key => $values) {
|
|
|
5915 |
$tempreplyto[$key][1] = core_text::convert($values[1], 'utf-8', strtolower($charset));
|
|
|
5916 |
}
|
|
|
5917 |
}
|
|
|
5918 |
}
|
|
|
5919 |
|
|
|
5920 |
foreach ($temprecipients as $values) {
|
|
|
5921 |
$mail->addAddress($values[0], $values[1]);
|
|
|
5922 |
}
|
|
|
5923 |
foreach ($tempreplyto as $values) {
|
|
|
5924 |
$mail->addReplyTo($values[0], $values[1]);
|
|
|
5925 |
}
|
|
|
5926 |
|
|
|
5927 |
if (!empty($CFG->emaildkimselector)) {
|
|
|
5928 |
$domain = substr(strrchr($mail->From, "@"), 1);
|
|
|
5929 |
$pempath = "{$CFG->dataroot}/dkim/{$domain}/{$CFG->emaildkimselector}.private";
|
|
|
5930 |
if (file_exists($pempath)) {
|
|
|
5931 |
$mail->DKIM_domain = $domain;
|
|
|
5932 |
$mail->DKIM_private = $pempath;
|
|
|
5933 |
$mail->DKIM_selector = $CFG->emaildkimselector;
|
|
|
5934 |
$mail->DKIM_identity = $mail->From;
|
|
|
5935 |
} else {
|
|
|
5936 |
debugging("Email DKIM selector chosen due to {$mail->From} but no certificate found at $pempath", DEBUG_DEVELOPER);
|
|
|
5937 |
}
|
|
|
5938 |
}
|
|
|
5939 |
|
|
|
5940 |
if ($mail->send()) {
|
|
|
5941 |
set_send_count($user);
|
|
|
5942 |
if (!empty($mail->SMTPDebug)) {
|
|
|
5943 |
echo '</pre>';
|
|
|
5944 |
}
|
|
|
5945 |
return true;
|
|
|
5946 |
} else {
|
|
|
5947 |
// Trigger event for failing to send email.
|
|
|
5948 |
$event = \core\event\email_failed::create(array(
|
|
|
5949 |
'context' => context_system::instance(),
|
|
|
5950 |
'userid' => $from->id,
|
|
|
5951 |
'relateduserid' => $user->id,
|
|
|
5952 |
'other' => array(
|
|
|
5953 |
'subject' => $subject,
|
|
|
5954 |
'message' => $messagetext,
|
|
|
5955 |
'errorinfo' => $mail->ErrorInfo
|
|
|
5956 |
)
|
|
|
5957 |
));
|
|
|
5958 |
$event->trigger();
|
|
|
5959 |
if (CLI_SCRIPT) {
|
|
|
5960 |
mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
|
|
|
5961 |
}
|
|
|
5962 |
if (!empty($mail->SMTPDebug)) {
|
|
|
5963 |
echo '</pre>';
|
|
|
5964 |
}
|
|
|
5965 |
return false;
|
|
|
5966 |
}
|
|
|
5967 |
}
|
|
|
5968 |
|
|
|
5969 |
/**
|
|
|
5970 |
* Check to see if a user's real email address should be used for the "From" field.
|
|
|
5971 |
*
|
|
|
5972 |
* @param object $from The user object for the user we are sending the email from.
|
|
|
5973 |
* @param object $user The user object that we are sending the email to.
|
|
|
5974 |
* @param array $unused No longer used.
|
|
|
5975 |
* @return bool Returns true if we can use the from user's email adress in the "From" field.
|
|
|
5976 |
*/
|
|
|
5977 |
function can_send_from_real_email_address($from, $user, $unused = null) {
|
|
|
5978 |
global $CFG;
|
|
|
5979 |
if (!isset($CFG->allowedemaildomains) || empty(trim($CFG->allowedemaildomains))) {
|
|
|
5980 |
return false;
|
|
|
5981 |
}
|
|
|
5982 |
$alloweddomains = array_map('trim', explode("\n", $CFG->allowedemaildomains));
|
|
|
5983 |
// Email is in the list of allowed domains for sending email,
|
|
|
5984 |
// and the senders email setting is either displayed to everyone, or display to only other users that are enrolled
|
|
|
5985 |
// in a course with the sender.
|
|
|
5986 |
if (\core\ip_utils::is_domain_in_allowed_list(substr($from->email, strpos($from->email, '@') + 1), $alloweddomains)
|
|
|
5987 |
&& ($from->maildisplay == core_user::MAILDISPLAY_EVERYONE
|
|
|
5988 |
|| ($from->maildisplay == core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY
|
|
|
5989 |
&& enrol_get_shared_courses($user, $from, false, true)))) {
|
|
|
5990 |
return true;
|
|
|
5991 |
}
|
|
|
5992 |
return false;
|
|
|
5993 |
}
|
|
|
5994 |
|
|
|
5995 |
/**
|
|
|
5996 |
* Generate a signoff for emails based on support settings
|
|
|
5997 |
*
|
|
|
5998 |
* @return string
|
|
|
5999 |
*/
|
|
|
6000 |
function generate_email_signoff() {
|
|
|
6001 |
global $CFG, $OUTPUT;
|
|
|
6002 |
|
|
|
6003 |
$signoff = "\n";
|
|
|
6004 |
if (!empty($CFG->supportname)) {
|
|
|
6005 |
$signoff .= $CFG->supportname."\n";
|
|
|
6006 |
}
|
|
|
6007 |
|
|
|
6008 |
$supportemail = $OUTPUT->supportemail(['class' => 'font-weight-bold']);
|
|
|
6009 |
|
|
|
6010 |
if ($supportemail) {
|
|
|
6011 |
$signoff .= "\n" . $supportemail . "\n";
|
|
|
6012 |
}
|
|
|
6013 |
|
|
|
6014 |
return $signoff;
|
|
|
6015 |
}
|
|
|
6016 |
|
|
|
6017 |
/**
|
|
|
6018 |
* Sets specified user's password and send the new password to the user via email.
|
|
|
6019 |
*
|
|
|
6020 |
* @param stdClass $user A {@link $USER} object
|
|
|
6021 |
* @param bool $fasthash If true, use a low cost factor when generating the hash for speed.
|
|
|
6022 |
* @return bool|string Returns "true" if mail was sent OK and "false" if there was an error
|
|
|
6023 |
*/
|
|
|
6024 |
function setnew_password_and_mail($user, $fasthash = false) {
|
|
|
6025 |
global $CFG, $DB;
|
|
|
6026 |
|
|
|
6027 |
// We try to send the mail in language the user understands,
|
|
|
6028 |
// unfortunately the filter_string() does not support alternative langs yet
|
|
|
6029 |
// so multilang will not work properly for site->fullname.
|
|
|
6030 |
$lang = empty($user->lang) ? get_newuser_language() : $user->lang;
|
|
|
6031 |
|
|
|
6032 |
$site = get_site();
|
|
|
6033 |
|
|
|
6034 |
$supportuser = core_user::get_support_user();
|
|
|
6035 |
|
|
|
6036 |
$newpassword = generate_password();
|
|
|
6037 |
|
|
|
6038 |
update_internal_user_password($user, $newpassword, $fasthash);
|
|
|
6039 |
|
|
|
6040 |
$a = new stdClass();
|
|
|
6041 |
$a->firstname = fullname($user, true);
|
|
|
6042 |
$a->sitename = format_string($site->fullname);
|
|
|
6043 |
$a->username = $user->username;
|
|
|
6044 |
$a->newpassword = $newpassword;
|
|
|
6045 |
$a->link = $CFG->wwwroot .'/login/?lang='.$lang;
|
|
|
6046 |
$a->signoff = generate_email_signoff();
|
|
|
6047 |
|
|
|
6048 |
$message = (string)new lang_string('newusernewpasswordtext', '', $a, $lang);
|
|
|
6049 |
|
|
|
6050 |
$subject = format_string($site->fullname) .': '. (string)new lang_string('newusernewpasswordsubj', '', $a, $lang);
|
|
|
6051 |
|
|
|
6052 |
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
|
|
6053 |
return email_to_user($user, $supportuser, $subject, $message);
|
|
|
6054 |
|
|
|
6055 |
}
|
|
|
6056 |
|
|
|
6057 |
/**
|
|
|
6058 |
* Resets specified user's password and send the new password to the user via email.
|
|
|
6059 |
*
|
|
|
6060 |
* @param stdClass $user A {@link $USER} object
|
|
|
6061 |
* @return bool Returns true if mail was sent OK and false if there was an error.
|
|
|
6062 |
*/
|
|
|
6063 |
function reset_password_and_mail($user) {
|
|
|
6064 |
global $CFG;
|
|
|
6065 |
|
|
|
6066 |
$site = get_site();
|
|
|
6067 |
$supportuser = core_user::get_support_user();
|
|
|
6068 |
|
|
|
6069 |
$userauth = get_auth_plugin($user->auth);
|
|
|
6070 |
if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
|
|
|
6071 |
trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
|
|
|
6072 |
return false;
|
|
|
6073 |
}
|
|
|
6074 |
|
|
|
6075 |
$newpassword = generate_password();
|
|
|
6076 |
|
|
|
6077 |
if (!$userauth->user_update_password($user, $newpassword)) {
|
|
|
6078 |
throw new \moodle_exception("cannotsetpassword");
|
|
|
6079 |
}
|
|
|
6080 |
|
|
|
6081 |
$a = new stdClass();
|
|
|
6082 |
$a->firstname = $user->firstname;
|
|
|
6083 |
$a->lastname = $user->lastname;
|
|
|
6084 |
$a->sitename = format_string($site->fullname);
|
|
|
6085 |
$a->username = $user->username;
|
|
|
6086 |
$a->newpassword = $newpassword;
|
|
|
6087 |
$a->link = $CFG->wwwroot .'/login/change_password.php';
|
|
|
6088 |
$a->signoff = generate_email_signoff();
|
|
|
6089 |
|
|
|
6090 |
$message = get_string('newpasswordtext', '', $a);
|
|
|
6091 |
|
|
|
6092 |
$subject = format_string($site->fullname) .': '. get_string('changedpassword');
|
|
|
6093 |
|
|
|
6094 |
unset_user_preference('create_password', $user); // Prevent cron from generating the password.
|
|
|
6095 |
|
|
|
6096 |
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
|
|
6097 |
return email_to_user($user, $supportuser, $subject, $message);
|
|
|
6098 |
}
|
|
|
6099 |
|
|
|
6100 |
/**
|
|
|
6101 |
* Send email to specified user with confirmation text and activation link.
|
|
|
6102 |
*
|
|
|
6103 |
* @param stdClass $user A {@link $USER} object
|
|
|
6104 |
* @param string $confirmationurl user confirmation URL
|
|
|
6105 |
* @return bool Returns true if mail was sent OK and false if there was an error.
|
|
|
6106 |
*/
|
|
|
6107 |
function send_confirmation_email($user, $confirmationurl = null) {
|
|
|
6108 |
global $CFG;
|
|
|
6109 |
|
|
|
6110 |
$site = get_site();
|
|
|
6111 |
$supportuser = core_user::get_support_user();
|
|
|
6112 |
|
|
|
6113 |
$data = new stdClass();
|
|
|
6114 |
$data->sitename = format_string($site->fullname);
|
|
|
6115 |
$data->admin = generate_email_signoff();
|
|
|
6116 |
|
|
|
6117 |
$subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
|
|
|
6118 |
|
|
|
6119 |
if (empty($confirmationurl)) {
|
|
|
6120 |
$confirmationurl = '/login/confirm.php';
|
|
|
6121 |
}
|
|
|
6122 |
|
|
|
6123 |
$confirmationurl = new moodle_url($confirmationurl);
|
|
|
6124 |
// Remove data parameter just in case it was included in the confirmation so we can add it manually later.
|
|
|
6125 |
$confirmationurl->remove_params('data');
|
|
|
6126 |
$confirmationpath = $confirmationurl->out(false);
|
|
|
6127 |
|
|
|
6128 |
// We need to custom encode the username to include trailing dots in the link.
|
|
|
6129 |
// Because of this custom encoding we can't use moodle_url directly.
|
|
|
6130 |
// Determine if a query string is present in the confirmation url.
|
|
|
6131 |
$hasquerystring = strpos($confirmationpath, '?') !== false;
|
|
|
6132 |
// Perform normal url encoding of the username first.
|
|
|
6133 |
$username = urlencode($user->username);
|
|
|
6134 |
// Prevent problems with trailing dots not being included as part of link in some mail clients.
|
|
|
6135 |
$username = str_replace('.', '%2E', $username);
|
|
|
6136 |
|
|
|
6137 |
$data->link = $confirmationpath . ( $hasquerystring ? '&' : '?') . 'data='. $user->secret .'/'. $username;
|
|
|
6138 |
|
|
|
6139 |
$message = get_string('emailconfirmation', '', $data);
|
|
|
6140 |
$messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
|
|
|
6141 |
|
|
|
6142 |
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
|
|
6143 |
return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
|
|
|
6144 |
}
|
|
|
6145 |
|
|
|
6146 |
/**
|
|
|
6147 |
* Sends a password change confirmation email.
|
|
|
6148 |
*
|
|
|
6149 |
* @param stdClass $user A {@link $USER} object
|
|
|
6150 |
* @param stdClass $resetrecord An object tracking metadata regarding password reset request
|
|
|
6151 |
* @return bool Returns true if mail was sent OK and false if there was an error.
|
|
|
6152 |
*/
|
|
|
6153 |
function send_password_change_confirmation_email($user, $resetrecord) {
|
|
|
6154 |
global $CFG;
|
|
|
6155 |
|
|
|
6156 |
$site = get_site();
|
|
|
6157 |
$supportuser = core_user::get_support_user();
|
|
|
6158 |
$pwresetmins = isset($CFG->pwresettime) ? floor($CFG->pwresettime / MINSECS) : 30;
|
|
|
6159 |
|
|
|
6160 |
$data = new stdClass();
|
|
|
6161 |
$data->firstname = $user->firstname;
|
|
|
6162 |
$data->lastname = $user->lastname;
|
|
|
6163 |
$data->username = $user->username;
|
|
|
6164 |
$data->sitename = format_string($site->fullname);
|
|
|
6165 |
$data->link = $CFG->wwwroot .'/login/forgot_password.php?token='. $resetrecord->token;
|
|
|
6166 |
$data->admin = generate_email_signoff();
|
|
|
6167 |
$data->resetminutes = $pwresetmins;
|
|
|
6168 |
|
|
|
6169 |
$message = get_string('emailresetconfirmation', '', $data);
|
|
|
6170 |
$subject = get_string('emailresetconfirmationsubject', '', format_string($site->fullname));
|
|
|
6171 |
|
|
|
6172 |
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
|
|
6173 |
return email_to_user($user, $supportuser, $subject, $message);
|
|
|
6174 |
|
|
|
6175 |
}
|
|
|
6176 |
|
|
|
6177 |
/**
|
|
|
6178 |
* Sends an email containing information on how to change your password.
|
|
|
6179 |
*
|
|
|
6180 |
* @param stdClass $user A {@link $USER} object
|
|
|
6181 |
* @return bool Returns true if mail was sent OK and false if there was an error.
|
|
|
6182 |
*/
|
|
|
6183 |
function send_password_change_info($user) {
|
|
|
6184 |
$site = get_site();
|
|
|
6185 |
$supportuser = core_user::get_support_user();
|
|
|
6186 |
|
|
|
6187 |
$data = new stdClass();
|
|
|
6188 |
$data->firstname = $user->firstname;
|
|
|
6189 |
$data->lastname = $user->lastname;
|
|
|
6190 |
$data->username = $user->username;
|
|
|
6191 |
$data->sitename = format_string($site->fullname);
|
|
|
6192 |
$data->admin = generate_email_signoff();
|
|
|
6193 |
|
|
|
6194 |
if (!is_enabled_auth($user->auth)) {
|
|
|
6195 |
$message = get_string('emailpasswordchangeinfodisabled', '', $data);
|
|
|
6196 |
$subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
|
|
|
6197 |
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
|
|
6198 |
return email_to_user($user, $supportuser, $subject, $message);
|
|
|
6199 |
}
|
|
|
6200 |
|
|
|
6201 |
$userauth = get_auth_plugin($user->auth);
|
|
|
6202 |
['subject' => $subject, 'message' => $message] = $userauth->get_password_change_info($user);
|
|
|
6203 |
|
|
|
6204 |
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
|
|
6205 |
return email_to_user($user, $supportuser, $subject, $message);
|
|
|
6206 |
}
|
|
|
6207 |
|
|
|
6208 |
/**
|
|
|
6209 |
* Check that an email is allowed. It returns an error message if there was a problem.
|
|
|
6210 |
*
|
|
|
6211 |
* @param string $email Content of email
|
|
|
6212 |
* @return string|false
|
|
|
6213 |
*/
|
|
|
6214 |
function email_is_not_allowed($email) {
|
|
|
6215 |
global $CFG;
|
|
|
6216 |
|
|
|
6217 |
// Comparing lowercase domains.
|
|
|
6218 |
$email = strtolower($email);
|
|
|
6219 |
if (!empty($CFG->allowemailaddresses)) {
|
|
|
6220 |
$allowed = explode(' ', strtolower($CFG->allowemailaddresses));
|
|
|
6221 |
foreach ($allowed as $allowedpattern) {
|
|
|
6222 |
$allowedpattern = trim($allowedpattern);
|
|
|
6223 |
if (!$allowedpattern) {
|
|
|
6224 |
continue;
|
|
|
6225 |
}
|
|
|
6226 |
if (strpos($allowedpattern, '.') === 0) {
|
|
|
6227 |
if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
|
|
|
6228 |
// Subdomains are in a form ".example.com" - matches "xxx@anything.example.com".
|
|
|
6229 |
return false;
|
|
|
6230 |
}
|
|
|
6231 |
|
|
|
6232 |
} else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) {
|
|
|
6233 |
return false;
|
|
|
6234 |
}
|
|
|
6235 |
}
|
|
|
6236 |
return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
|
|
|
6237 |
|
|
|
6238 |
} else if (!empty($CFG->denyemailaddresses)) {
|
|
|
6239 |
$denied = explode(' ', strtolower($CFG->denyemailaddresses));
|
|
|
6240 |
foreach ($denied as $deniedpattern) {
|
|
|
6241 |
$deniedpattern = trim($deniedpattern);
|
|
|
6242 |
if (!$deniedpattern) {
|
|
|
6243 |
continue;
|
|
|
6244 |
}
|
|
|
6245 |
if (strpos($deniedpattern, '.') === 0) {
|
|
|
6246 |
if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
|
|
|
6247 |
// Subdomains are in a form ".example.com" - matches "xxx@anything.example.com".
|
|
|
6248 |
return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
|
|
|
6249 |
}
|
|
|
6250 |
|
|
|
6251 |
} else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) {
|
|
|
6252 |
return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
|
|
|
6253 |
}
|
|
|
6254 |
}
|
|
|
6255 |
}
|
|
|
6256 |
|
|
|
6257 |
return false;
|
|
|
6258 |
}
|
|
|
6259 |
|
|
|
6260 |
// FILE HANDLING.
|
|
|
6261 |
|
|
|
6262 |
/**
|
|
|
6263 |
* Returns local file storage instance
|
|
|
6264 |
*
|
|
|
6265 |
* @return ?file_storage
|
|
|
6266 |
*/
|
|
|
6267 |
function get_file_storage($reset = false) {
|
|
|
6268 |
global $CFG;
|
|
|
6269 |
|
|
|
6270 |
static $fs = null;
|
|
|
6271 |
|
|
|
6272 |
if ($reset) {
|
|
|
6273 |
$fs = null;
|
|
|
6274 |
return;
|
|
|
6275 |
}
|
|
|
6276 |
|
|
|
6277 |
if ($fs) {
|
|
|
6278 |
return $fs;
|
|
|
6279 |
}
|
|
|
6280 |
|
|
|
6281 |
require_once("$CFG->libdir/filelib.php");
|
|
|
6282 |
|
|
|
6283 |
$fs = new file_storage();
|
|
|
6284 |
|
|
|
6285 |
return $fs;
|
|
|
6286 |
}
|
|
|
6287 |
|
|
|
6288 |
/**
|
|
|
6289 |
* Returns local file storage instance
|
|
|
6290 |
*
|
|
|
6291 |
* @return file_browser
|
|
|
6292 |
*/
|
|
|
6293 |
function get_file_browser() {
|
|
|
6294 |
global $CFG;
|
|
|
6295 |
|
|
|
6296 |
static $fb = null;
|
|
|
6297 |
|
|
|
6298 |
if ($fb) {
|
|
|
6299 |
return $fb;
|
|
|
6300 |
}
|
|
|
6301 |
|
|
|
6302 |
require_once("$CFG->libdir/filelib.php");
|
|
|
6303 |
|
|
|
6304 |
$fb = new file_browser();
|
|
|
6305 |
|
|
|
6306 |
return $fb;
|
|
|
6307 |
}
|
|
|
6308 |
|
|
|
6309 |
/**
|
|
|
6310 |
* Returns file packer
|
|
|
6311 |
*
|
|
|
6312 |
* @param string $mimetype default application/zip
|
|
|
6313 |
* @return file_packer|false
|
|
|
6314 |
*/
|
|
|
6315 |
function get_file_packer($mimetype='application/zip') {
|
|
|
6316 |
global $CFG;
|
|
|
6317 |
|
|
|
6318 |
static $fp = array();
|
|
|
6319 |
|
|
|
6320 |
if (isset($fp[$mimetype])) {
|
|
|
6321 |
return $fp[$mimetype];
|
|
|
6322 |
}
|
|
|
6323 |
|
|
|
6324 |
switch ($mimetype) {
|
|
|
6325 |
case 'application/zip':
|
|
|
6326 |
case 'application/vnd.moodle.profiling':
|
|
|
6327 |
$classname = 'zip_packer';
|
|
|
6328 |
break;
|
|
|
6329 |
|
|
|
6330 |
case 'application/x-gzip' :
|
|
|
6331 |
$classname = 'tgz_packer';
|
|
|
6332 |
break;
|
|
|
6333 |
|
|
|
6334 |
case 'application/vnd.moodle.backup':
|
|
|
6335 |
$classname = 'mbz_packer';
|
|
|
6336 |
break;
|
|
|
6337 |
|
|
|
6338 |
default:
|
|
|
6339 |
return false;
|
|
|
6340 |
}
|
|
|
6341 |
|
|
|
6342 |
require_once("$CFG->libdir/filestorage/$classname.php");
|
|
|
6343 |
$fp[$mimetype] = new $classname();
|
|
|
6344 |
|
|
|
6345 |
return $fp[$mimetype];
|
|
|
6346 |
}
|
|
|
6347 |
|
|
|
6348 |
/**
|
|
|
6349 |
* Returns current name of file on disk if it exists.
|
|
|
6350 |
*
|
|
|
6351 |
* @param string $newfile File to be verified
|
|
|
6352 |
* @return string Current name of file on disk if true
|
|
|
6353 |
*/
|
|
|
6354 |
function valid_uploaded_file($newfile) {
|
|
|
6355 |
if (empty($newfile)) {
|
|
|
6356 |
return '';
|
|
|
6357 |
}
|
|
|
6358 |
if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
|
|
|
6359 |
return $newfile['tmp_name'];
|
|
|
6360 |
} else {
|
|
|
6361 |
return '';
|
|
|
6362 |
}
|
|
|
6363 |
}
|
|
|
6364 |
|
|
|
6365 |
/**
|
|
|
6366 |
* Returns the maximum size for uploading files.
|
|
|
6367 |
*
|
|
|
6368 |
* There are seven possible upload limits:
|
|
|
6369 |
* 1. in Apache using LimitRequestBody (no way of checking or changing this)
|
|
|
6370 |
* 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
|
|
|
6371 |
* 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
|
|
|
6372 |
* 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
|
|
|
6373 |
* 5. by the Moodle admin in $CFG->maxbytes
|
|
|
6374 |
* 6. by the teacher in the current course $course->maxbytes
|
|
|
6375 |
* 7. by the teacher for the current module, eg $assignment->maxbytes
|
|
|
6376 |
*
|
|
|
6377 |
* These last two are passed to this function as arguments (in bytes).
|
|
|
6378 |
* Anything defined as 0 is ignored.
|
|
|
6379 |
* The smallest of all the non-zero numbers is returned.
|
|
|
6380 |
*
|
|
|
6381 |
* @todo Finish documenting this function
|
|
|
6382 |
*
|
|
|
6383 |
* @param int $sitebytes Set maximum size
|
|
|
6384 |
* @param int $coursebytes Current course $course->maxbytes (in bytes)
|
|
|
6385 |
* @param int $modulebytes Current module ->maxbytes (in bytes)
|
|
|
6386 |
* @param bool $unused This parameter has been deprecated and is not used any more.
|
|
|
6387 |
* @return int The maximum size for uploading files.
|
|
|
6388 |
*/
|
|
|
6389 |
function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0, $unused = false) {
|
|
|
6390 |
|
|
|
6391 |
if (! $filesize = ini_get('upload_max_filesize')) {
|
|
|
6392 |
$filesize = '5M';
|
|
|
6393 |
}
|
|
|
6394 |
$minimumsize = get_real_size($filesize);
|
|
|
6395 |
|
|
|
6396 |
if ($postsize = ini_get('post_max_size')) {
|
|
|
6397 |
$postsize = get_real_size($postsize);
|
|
|
6398 |
if ($postsize < $minimumsize) {
|
|
|
6399 |
$minimumsize = $postsize;
|
|
|
6400 |
}
|
|
|
6401 |
}
|
|
|
6402 |
|
|
|
6403 |
if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
|
|
|
6404 |
$minimumsize = $sitebytes;
|
|
|
6405 |
}
|
|
|
6406 |
|
|
|
6407 |
if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
|
|
|
6408 |
$minimumsize = $coursebytes;
|
|
|
6409 |
}
|
|
|
6410 |
|
|
|
6411 |
if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
|
|
|
6412 |
$minimumsize = $modulebytes;
|
|
|
6413 |
}
|
|
|
6414 |
|
|
|
6415 |
return $minimumsize;
|
|
|
6416 |
}
|
|
|
6417 |
|
|
|
6418 |
/**
|
|
|
6419 |
* Returns the maximum size for uploading files for the current user
|
|
|
6420 |
*
|
|
|
6421 |
* This function takes in account {@link get_max_upload_file_size()} the user's capabilities
|
|
|
6422 |
*
|
|
|
6423 |
* @param context $context The context in which to check user capabilities
|
|
|
6424 |
* @param int $sitebytes Set maximum size
|
|
|
6425 |
* @param int $coursebytes Current course $course->maxbytes (in bytes)
|
|
|
6426 |
* @param int $modulebytes Current module ->maxbytes (in bytes)
|
|
|
6427 |
* @param stdClass|int|null $user The user
|
|
|
6428 |
* @param bool $unused This parameter has been deprecated and is not used any more.
|
|
|
6429 |
* @return int The maximum size for uploading files.
|
|
|
6430 |
*/
|
|
|
6431 |
function get_user_max_upload_file_size($context, $sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $user = null,
|
|
|
6432 |
$unused = false) {
|
|
|
6433 |
global $USER;
|
|
|
6434 |
|
|
|
6435 |
if (empty($user)) {
|
|
|
6436 |
$user = $USER;
|
|
|
6437 |
}
|
|
|
6438 |
|
|
|
6439 |
if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
|
|
|
6440 |
return USER_CAN_IGNORE_FILE_SIZE_LIMITS;
|
|
|
6441 |
}
|
|
|
6442 |
|
|
|
6443 |
return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes);
|
|
|
6444 |
}
|
|
|
6445 |
|
|
|
6446 |
/**
|
|
|
6447 |
* Returns an array of possible sizes in local language
|
|
|
6448 |
*
|
|
|
6449 |
* Related to {@link get_max_upload_file_size()} - this function returns an
|
|
|
6450 |
* array of possible sizes in an array, translated to the
|
|
|
6451 |
* local language.
|
|
|
6452 |
*
|
|
|
6453 |
* The list of options will go up to the minimum of $sitebytes, $coursebytes or $modulebytes.
|
|
|
6454 |
*
|
|
|
6455 |
* If $coursebytes or $sitebytes is not 0, an option will be included for "Course/Site upload limit (X)"
|
|
|
6456 |
* with the value set to 0. This option will be the first in the list.
|
|
|
6457 |
*
|
|
|
6458 |
* @uses SORT_NUMERIC
|
|
|
6459 |
* @param int $sitebytes Set maximum size
|
|
|
6460 |
* @param int $coursebytes Current course $course->maxbytes (in bytes)
|
|
|
6461 |
* @param int $modulebytes Current module ->maxbytes (in bytes)
|
|
|
6462 |
* @param int|array $custombytes custom upload size/s which will be added to list,
|
|
|
6463 |
* Only value/s smaller then maxsize will be added to list.
|
|
|
6464 |
* @return array
|
|
|
6465 |
*/
|
|
|
6466 |
function get_max_upload_sizes($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $custombytes = null) {
|
|
|
6467 |
global $CFG;
|
|
|
6468 |
|
|
|
6469 |
if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
|
|
|
6470 |
return array();
|
|
|
6471 |
}
|
|
|
6472 |
|
|
|
6473 |
if ($sitebytes == 0) {
|
|
|
6474 |
// Will get the minimum of upload_max_filesize or post_max_size.
|
|
|
6475 |
$sitebytes = get_max_upload_file_size();
|
|
|
6476 |
}
|
|
|
6477 |
|
|
|
6478 |
$filesize = array();
|
|
|
6479 |
$sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
|
|
|
6480 |
5242880, 10485760, 20971520, 52428800, 104857600,
|
|
|
6481 |
262144000, 524288000, 786432000, 1073741824,
|
|
|
6482 |
2147483648, 4294967296, 8589934592);
|
|
|
6483 |
|
|
|
6484 |
// If custombytes is given and is valid then add it to the list.
|
|
|
6485 |
if (is_number($custombytes) and $custombytes > 0) {
|
|
|
6486 |
$custombytes = (int)$custombytes;
|
|
|
6487 |
if (!in_array($custombytes, $sizelist)) {
|
|
|
6488 |
$sizelist[] = $custombytes;
|
|
|
6489 |
}
|
|
|
6490 |
} else if (is_array($custombytes)) {
|
|
|
6491 |
$sizelist = array_unique(array_merge($sizelist, $custombytes));
|
|
|
6492 |
}
|
|
|
6493 |
|
|
|
6494 |
// Allow maxbytes to be selected if it falls outside the above boundaries.
|
|
|
6495 |
if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
|
|
|
6496 |
// Note: get_real_size() is used in order to prevent problems with invalid values.
|
|
|
6497 |
$sizelist[] = get_real_size($CFG->maxbytes);
|
|
|
6498 |
}
|
|
|
6499 |
|
|
|
6500 |
foreach ($sizelist as $sizebytes) {
|
|
|
6501 |
if ($sizebytes < $maxsize && $sizebytes > 0) {
|
|
|
6502 |
$filesize[(string)intval($sizebytes)] = display_size($sizebytes, 0);
|
|
|
6503 |
}
|
|
|
6504 |
}
|
|
|
6505 |
|
|
|
6506 |
$limitlevel = '';
|
|
|
6507 |
$displaysize = '';
|
|
|
6508 |
if ($modulebytes &&
|
|
|
6509 |
(($modulebytes < $coursebytes || $coursebytes == 0) &&
|
|
|
6510 |
($modulebytes < $sitebytes || $sitebytes == 0))) {
|
|
|
6511 |
$limitlevel = get_string('activity', 'core');
|
|
|
6512 |
$displaysize = display_size($modulebytes, 0);
|
|
|
6513 |
$filesize[$modulebytes] = $displaysize; // Make sure the limit is also included in the list.
|
|
|
6514 |
|
|
|
6515 |
} else if ($coursebytes && ($coursebytes < $sitebytes || $sitebytes == 0)) {
|
|
|
6516 |
$limitlevel = get_string('course', 'core');
|
|
|
6517 |
$displaysize = display_size($coursebytes, 0);
|
|
|
6518 |
$filesize[$coursebytes] = $displaysize; // Make sure the limit is also included in the list.
|
|
|
6519 |
|
|
|
6520 |
} else if ($sitebytes) {
|
|
|
6521 |
$limitlevel = get_string('site', 'core');
|
|
|
6522 |
$displaysize = display_size($sitebytes, 0);
|
|
|
6523 |
$filesize[$sitebytes] = $displaysize; // Make sure the limit is also included in the list.
|
|
|
6524 |
}
|
|
|
6525 |
|
|
|
6526 |
krsort($filesize, SORT_NUMERIC);
|
|
|
6527 |
if ($limitlevel) {
|
|
|
6528 |
$params = (object) array('contextname' => $limitlevel, 'displaysize' => $displaysize);
|
|
|
6529 |
$filesize = array('0' => get_string('uploadlimitwithsize', 'core', $params)) + $filesize;
|
|
|
6530 |
}
|
|
|
6531 |
|
|
|
6532 |
return $filesize;
|
|
|
6533 |
}
|
|
|
6534 |
|
|
|
6535 |
/**
|
|
|
6536 |
* Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
|
|
|
6537 |
*
|
|
|
6538 |
* If excludefiles is defined, then that file/directory is ignored
|
|
|
6539 |
* If getdirs is true, then (sub)directories are included in the output
|
|
|
6540 |
* If getfiles is true, then files are included in the output
|
|
|
6541 |
* (at least one of these must be true!)
|
|
|
6542 |
*
|
|
|
6543 |
* @todo Finish documenting this function. Add examples of $excludefile usage.
|
|
|
6544 |
*
|
|
|
6545 |
* @param string $rootdir A given root directory to start from
|
|
|
6546 |
* @param string|array $excludefiles If defined then the specified file/directory is ignored
|
|
|
6547 |
* @param bool $descend If true then subdirectories are recursed as well
|
|
|
6548 |
* @param bool $getdirs If true then (sub)directories are included in the output
|
|
|
6549 |
* @param bool $getfiles If true then files are included in the output
|
|
|
6550 |
* @return array An array with all the filenames in all subdirectories, relative to the given rootdir
|
|
|
6551 |
*/
|
|
|
6552 |
function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
|
|
|
6553 |
|
|
|
6554 |
$dirs = array();
|
|
|
6555 |
|
|
|
6556 |
if (!$getdirs and !$getfiles) { // Nothing to show.
|
|
|
6557 |
return $dirs;
|
|
|
6558 |
}
|
|
|
6559 |
|
|
|
6560 |
if (!is_dir($rootdir)) { // Must be a directory.
|
|
|
6561 |
return $dirs;
|
|
|
6562 |
}
|
|
|
6563 |
|
|
|
6564 |
if (!$dir = opendir($rootdir)) { // Can't open it for some reason.
|
|
|
6565 |
return $dirs;
|
|
|
6566 |
}
|
|
|
6567 |
|
|
|
6568 |
if (!is_array($excludefiles)) {
|
|
|
6569 |
$excludefiles = array($excludefiles);
|
|
|
6570 |
}
|
|
|
6571 |
|
|
|
6572 |
while (false !== ($file = readdir($dir))) {
|
|
|
6573 |
$firstchar = substr($file, 0, 1);
|
|
|
6574 |
if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
|
|
|
6575 |
continue;
|
|
|
6576 |
}
|
|
|
6577 |
$fullfile = $rootdir .'/'. $file;
|
|
|
6578 |
if (filetype($fullfile) == 'dir') {
|
|
|
6579 |
if ($getdirs) {
|
|
|
6580 |
$dirs[] = $file;
|
|
|
6581 |
}
|
|
|
6582 |
if ($descend) {
|
|
|
6583 |
$subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
|
|
|
6584 |
foreach ($subdirs as $subdir) {
|
|
|
6585 |
$dirs[] = $file .'/'. $subdir;
|
|
|
6586 |
}
|
|
|
6587 |
}
|
|
|
6588 |
} else if ($getfiles) {
|
|
|
6589 |
$dirs[] = $file;
|
|
|
6590 |
}
|
|
|
6591 |
}
|
|
|
6592 |
closedir($dir);
|
|
|
6593 |
|
|
|
6594 |
asort($dirs);
|
|
|
6595 |
|
|
|
6596 |
return $dirs;
|
|
|
6597 |
}
|
|
|
6598 |
|
|
|
6599 |
|
|
|
6600 |
/**
|
|
|
6601 |
* Adds up all the files in a directory and works out the size.
|
|
|
6602 |
*
|
|
|
6603 |
* @param string $rootdir The directory to start from
|
|
|
6604 |
* @param string $excludefile A file to exclude when summing directory size
|
|
|
6605 |
* @return int The summed size of all files and subfiles within the root directory
|
|
|
6606 |
*/
|
|
|
6607 |
function get_directory_size($rootdir, $excludefile='') {
|
|
|
6608 |
global $CFG;
|
|
|
6609 |
|
|
|
6610 |
// Do it this way if we can, it's much faster.
|
|
|
6611 |
if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
|
|
|
6612 |
$command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
|
|
|
6613 |
$output = null;
|
|
|
6614 |
$return = null;
|
|
|
6615 |
exec($command, $output, $return);
|
|
|
6616 |
if (is_array($output)) {
|
|
|
6617 |
// We told it to return k.
|
|
|
6618 |
return get_real_size(intval($output[0]).'k');
|
|
|
6619 |
}
|
|
|
6620 |
}
|
|
|
6621 |
|
|
|
6622 |
if (!is_dir($rootdir)) {
|
|
|
6623 |
// Must be a directory.
|
|
|
6624 |
return 0;
|
|
|
6625 |
}
|
|
|
6626 |
|
|
|
6627 |
if (!$dir = @opendir($rootdir)) {
|
|
|
6628 |
// Can't open it for some reason.
|
|
|
6629 |
return 0;
|
|
|
6630 |
}
|
|
|
6631 |
|
|
|
6632 |
$size = 0;
|
|
|
6633 |
|
|
|
6634 |
while (false !== ($file = readdir($dir))) {
|
|
|
6635 |
$firstchar = substr($file, 0, 1);
|
|
|
6636 |
if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
|
|
|
6637 |
continue;
|
|
|
6638 |
}
|
|
|
6639 |
$fullfile = $rootdir .'/'. $file;
|
|
|
6640 |
if (filetype($fullfile) == 'dir') {
|
|
|
6641 |
$size += get_directory_size($fullfile, $excludefile);
|
|
|
6642 |
} else {
|
|
|
6643 |
$size += filesize($fullfile);
|
|
|
6644 |
}
|
|
|
6645 |
}
|
|
|
6646 |
closedir($dir);
|
|
|
6647 |
|
|
|
6648 |
return $size;
|
|
|
6649 |
}
|
|
|
6650 |
|
|
|
6651 |
/**
|
|
|
6652 |
* Converts bytes into display form
|
|
|
6653 |
*
|
|
|
6654 |
* @param int $size The size to convert to human readable form
|
|
|
6655 |
* @param int $decimalplaces If specified, uses fixed number of decimal places
|
|
|
6656 |
* @param string $fixedunits If specified, uses fixed units (e.g. 'KB')
|
|
|
6657 |
* @return string Display version of size
|
|
|
6658 |
*/
|
|
|
6659 |
function display_size($size, int $decimalplaces = 1, string $fixedunits = ''): string {
|
|
|
6660 |
|
|
|
6661 |
static $units;
|
|
|
6662 |
|
|
|
6663 |
if ($size === USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
|
|
|
6664 |
return get_string('unlimited');
|
|
|
6665 |
}
|
|
|
6666 |
|
|
|
6667 |
if (empty($units)) {
|
|
|
6668 |
$units[] = get_string('sizeb');
|
|
|
6669 |
$units[] = get_string('sizekb');
|
|
|
6670 |
$units[] = get_string('sizemb');
|
|
|
6671 |
$units[] = get_string('sizegb');
|
|
|
6672 |
$units[] = get_string('sizetb');
|
|
|
6673 |
$units[] = get_string('sizepb');
|
|
|
6674 |
}
|
|
|
6675 |
|
|
|
6676 |
switch ($fixedunits) {
|
|
|
6677 |
case 'PB' :
|
|
|
6678 |
$magnitude = 5;
|
|
|
6679 |
break;
|
|
|
6680 |
case 'TB' :
|
|
|
6681 |
$magnitude = 4;
|
|
|
6682 |
break;
|
|
|
6683 |
case 'GB' :
|
|
|
6684 |
$magnitude = 3;
|
|
|
6685 |
break;
|
|
|
6686 |
case 'MB' :
|
|
|
6687 |
$magnitude = 2;
|
|
|
6688 |
break;
|
|
|
6689 |
case 'KB' :
|
|
|
6690 |
$magnitude = 1;
|
|
|
6691 |
break;
|
|
|
6692 |
case 'B' :
|
|
|
6693 |
$magnitude = 0;
|
|
|
6694 |
break;
|
|
|
6695 |
case '':
|
|
|
6696 |
$magnitude = floor(log($size, 1024));
|
|
|
6697 |
$magnitude = max(0, min(5, $magnitude));
|
|
|
6698 |
break;
|
|
|
6699 |
default:
|
|
|
6700 |
throw new coding_exception('Unknown fixed units value: ' . $fixedunits);
|
|
|
6701 |
}
|
|
|
6702 |
|
|
|
6703 |
// Special case for magnitude 0 (bytes) - never use decimal places.
|
|
|
6704 |
$nbsp = "\xc2\xa0";
|
|
|
6705 |
if ($magnitude === 0) {
|
|
|
6706 |
return round($size) . $nbsp . $units[$magnitude];
|
|
|
6707 |
}
|
|
|
6708 |
|
|
|
6709 |
// Convert to specified units.
|
|
|
6710 |
$sizeinunit = $size / 1024 ** $magnitude;
|
|
|
6711 |
|
|
|
6712 |
// Fixed decimal places.
|
|
|
6713 |
return sprintf('%.' . $decimalplaces . 'f', $sizeinunit) . $nbsp . $units[$magnitude];
|
|
|
6714 |
}
|
|
|
6715 |
|
|
|
6716 |
/**
|
|
|
6717 |
* Cleans a given filename by removing suspicious or troublesome characters
|
|
|
6718 |
*
|
|
|
6719 |
* @see clean_param()
|
|
|
6720 |
* @param string $string file name
|
|
|
6721 |
* @return string cleaned file name
|
|
|
6722 |
*/
|
|
|
6723 |
function clean_filename($string) {
|
|
|
6724 |
return clean_param($string, PARAM_FILE);
|
|
|
6725 |
}
|
|
|
6726 |
|
|
|
6727 |
// STRING TRANSLATION.
|
|
|
6728 |
|
|
|
6729 |
/**
|
|
|
6730 |
* Returns the code for the current language
|
|
|
6731 |
*
|
|
|
6732 |
* @category string
|
|
|
6733 |
* @return string
|
|
|
6734 |
*/
|
|
|
6735 |
function current_language() {
|
|
|
6736 |
global $CFG, $PAGE, $SESSION, $USER;
|
|
|
6737 |
|
|
|
6738 |
if (!empty($SESSION->forcelang)) {
|
|
|
6739 |
// Allows overriding course-forced language (useful for admins to check
|
|
|
6740 |
// issues in courses whose language they don't understand).
|
|
|
6741 |
// Also used by some code to temporarily get language-related information in a
|
|
|
6742 |
// specific language (see force_current_language()).
|
|
|
6743 |
$return = $SESSION->forcelang;
|
|
|
6744 |
|
|
|
6745 |
} else if (!empty($PAGE->cm->lang)) {
|
|
|
6746 |
// Activity language, if set.
|
|
|
6747 |
$return = $PAGE->cm->lang;
|
|
|
6748 |
|
|
|
6749 |
} else if (!empty($PAGE->course->id) && $PAGE->course->id != SITEID && !empty($PAGE->course->lang)) {
|
|
|
6750 |
// Course language can override all other settings for this page.
|
|
|
6751 |
$return = $PAGE->course->lang;
|
|
|
6752 |
|
|
|
6753 |
} else if (!empty($SESSION->lang)) {
|
|
|
6754 |
// Session language can override other settings.
|
|
|
6755 |
$return = $SESSION->lang;
|
|
|
6756 |
|
|
|
6757 |
} else if (!empty($USER->lang)) {
|
|
|
6758 |
$return = $USER->lang;
|
|
|
6759 |
|
|
|
6760 |
} else if (isset($CFG->lang)) {
|
|
|
6761 |
$return = $CFG->lang;
|
|
|
6762 |
|
|
|
6763 |
} else {
|
|
|
6764 |
$return = 'en';
|
|
|
6765 |
}
|
|
|
6766 |
|
|
|
6767 |
// Just in case this slipped in from somewhere by accident.
|
|
|
6768 |
$return = str_replace('_utf8', '', $return);
|
|
|
6769 |
|
|
|
6770 |
return $return;
|
|
|
6771 |
}
|
|
|
6772 |
|
|
|
6773 |
/**
|
|
|
6774 |
* Fix the current language to the given language code.
|
|
|
6775 |
*
|
|
|
6776 |
* @param string $lang The language code to use.
|
|
|
6777 |
* @return void
|
|
|
6778 |
*/
|
|
|
6779 |
function fix_current_language(string $lang): void {
|
|
|
6780 |
global $CFG, $COURSE, $SESSION, $USER;
|
|
|
6781 |
|
|
|
6782 |
if (!get_string_manager()->translation_exists($lang)) {
|
|
|
6783 |
throw new coding_exception("The language pack for $lang is not available");
|
|
|
6784 |
}
|
|
|
6785 |
|
|
|
6786 |
$fixglobal = '';
|
|
|
6787 |
$fixlang = 'lang';
|
|
|
6788 |
if (!empty($SESSION->forcelang)) {
|
|
|
6789 |
$fixglobal = $SESSION;
|
|
|
6790 |
$fixlang = 'forcelang';
|
|
|
6791 |
} else if (!empty($COURSE->id) && $COURSE->id != SITEID && !empty($COURSE->lang)) {
|
|
|
6792 |
$fixglobal = $COURSE;
|
|
|
6793 |
} else if (!empty($SESSION->lang)) {
|
|
|
6794 |
$fixglobal = $SESSION;
|
|
|
6795 |
} else if (!empty($USER->lang)) {
|
|
|
6796 |
$fixglobal = $USER;
|
|
|
6797 |
} else if (isset($CFG->lang)) {
|
|
|
6798 |
set_config('lang', $lang);
|
|
|
6799 |
}
|
|
|
6800 |
|
|
|
6801 |
if ($fixglobal) {
|
|
|
6802 |
$fixglobal->$fixlang = $lang;
|
|
|
6803 |
}
|
|
|
6804 |
}
|
|
|
6805 |
|
|
|
6806 |
/**
|
|
|
6807 |
* Returns parent language of current active language if defined
|
|
|
6808 |
*
|
|
|
6809 |
* @category string
|
|
|
6810 |
* @param string $lang null means current language
|
|
|
6811 |
* @return string
|
|
|
6812 |
*/
|
|
|
6813 |
function get_parent_language($lang=null) {
|
|
|
6814 |
|
|
|
6815 |
$parentlang = get_string_manager()->get_string('parentlanguage', 'langconfig', null, $lang);
|
|
|
6816 |
|
|
|
6817 |
if ($parentlang === 'en') {
|
|
|
6818 |
$parentlang = '';
|
|
|
6819 |
}
|
|
|
6820 |
|
|
|
6821 |
return $parentlang;
|
|
|
6822 |
}
|
|
|
6823 |
|
|
|
6824 |
/**
|
|
|
6825 |
* Force the current language to get strings and dates localised in the given language.
|
|
|
6826 |
*
|
|
|
6827 |
* After calling this function, all strings will be provided in the given language
|
|
|
6828 |
* until this function is called again, or equivalent code is run.
|
|
|
6829 |
*
|
|
|
6830 |
* @param string $language
|
|
|
6831 |
* @return string previous $SESSION->forcelang value
|
|
|
6832 |
*/
|
|
|
6833 |
function force_current_language($language) {
|
|
|
6834 |
global $SESSION;
|
|
|
6835 |
$sessionforcelang = isset($SESSION->forcelang) ? $SESSION->forcelang : '';
|
|
|
6836 |
if ($language !== $sessionforcelang) {
|
|
|
6837 |
// Setting forcelang to null or an empty string disables its effect.
|
|
|
6838 |
if (empty($language) || get_string_manager()->translation_exists($language, false)) {
|
|
|
6839 |
$SESSION->forcelang = $language;
|
|
|
6840 |
moodle_setlocale();
|
|
|
6841 |
}
|
|
|
6842 |
}
|
|
|
6843 |
return $sessionforcelang;
|
|
|
6844 |
}
|
|
|
6845 |
|
|
|
6846 |
/**
|
|
|
6847 |
* Returns current string_manager instance.
|
|
|
6848 |
*
|
|
|
6849 |
* The param $forcereload is needed for CLI installer only where the string_manager instance
|
|
|
6850 |
* must be replaced during the install.php script life time.
|
|
|
6851 |
*
|
|
|
6852 |
* @category string
|
|
|
6853 |
* @param bool $forcereload shall the singleton be released and new instance created instead?
|
|
|
6854 |
* @return core_string_manager
|
|
|
6855 |
*/
|
|
|
6856 |
function get_string_manager($forcereload=false) {
|
|
|
6857 |
global $CFG;
|
|
|
6858 |
|
|
|
6859 |
static $singleton = null;
|
|
|
6860 |
|
|
|
6861 |
if ($forcereload) {
|
|
|
6862 |
$singleton = null;
|
|
|
6863 |
}
|
|
|
6864 |
if ($singleton === null) {
|
|
|
6865 |
if (empty($CFG->early_install_lang)) {
|
|
|
6866 |
|
|
|
6867 |
$transaliases = array();
|
|
|
6868 |
if (empty($CFG->langlist)) {
|
|
|
6869 |
$translist = array();
|
|
|
6870 |
} else {
|
|
|
6871 |
$translist = explode(',', $CFG->langlist);
|
|
|
6872 |
$translist = array_map('trim', $translist);
|
|
|
6873 |
// Each language in the $CFG->langlist can has an "alias" that would substitute the default language name.
|
|
|
6874 |
foreach ($translist as $i => $value) {
|
|
|
6875 |
$parts = preg_split('/\s*\|\s*/', $value, 2);
|
|
|
6876 |
if (count($parts) == 2) {
|
|
|
6877 |
$transaliases[$parts[0]] = $parts[1];
|
|
|
6878 |
$translist[$i] = $parts[0];
|
|
|
6879 |
}
|
|
|
6880 |
}
|
|
|
6881 |
}
|
|
|
6882 |
|
|
|
6883 |
if (!empty($CFG->config_php_settings['customstringmanager'])) {
|
|
|
6884 |
$classname = $CFG->config_php_settings['customstringmanager'];
|
|
|
6885 |
|
|
|
6886 |
if (class_exists($classname)) {
|
|
|
6887 |
$implements = class_implements($classname);
|
|
|
6888 |
|
|
|
6889 |
if (isset($implements['core_string_manager'])) {
|
|
|
6890 |
$singleton = new $classname($CFG->langotherroot, $CFG->langlocalroot, $translist, $transaliases);
|
|
|
6891 |
return $singleton;
|
|
|
6892 |
|
|
|
6893 |
} else {
|
|
|
6894 |
debugging('Unable to instantiate custom string manager: class '.$classname.
|
|
|
6895 |
' does not implement the core_string_manager interface.');
|
|
|
6896 |
}
|
|
|
6897 |
|
|
|
6898 |
} else {
|
|
|
6899 |
debugging('Unable to instantiate custom string manager: class '.$classname.' can not be found.');
|
|
|
6900 |
}
|
|
|
6901 |
}
|
|
|
6902 |
|
|
|
6903 |
$singleton = new core_string_manager_standard($CFG->langotherroot, $CFG->langlocalroot, $translist, $transaliases);
|
|
|
6904 |
|
|
|
6905 |
} else {
|
|
|
6906 |
$singleton = new core_string_manager_install();
|
|
|
6907 |
}
|
|
|
6908 |
}
|
|
|
6909 |
|
|
|
6910 |
return $singleton;
|
|
|
6911 |
}
|
|
|
6912 |
|
|
|
6913 |
/**
|
|
|
6914 |
* Returns a localized string.
|
|
|
6915 |
*
|
|
|
6916 |
* Returns the translated string specified by $identifier as
|
|
|
6917 |
* for $module. Uses the same format files as STphp.
|
|
|
6918 |
* $a is an object, string or number that can be used
|
|
|
6919 |
* within translation strings
|
|
|
6920 |
*
|
|
|
6921 |
* eg 'hello {$a->firstname} {$a->lastname}'
|
|
|
6922 |
* or 'hello {$a}'
|
|
|
6923 |
*
|
|
|
6924 |
* If you would like to directly echo the localized string use
|
|
|
6925 |
* the function {@link print_string()}
|
|
|
6926 |
*
|
|
|
6927 |
* Example usage of this function involves finding the string you would
|
|
|
6928 |
* like a local equivalent of and using its identifier and module information
|
|
|
6929 |
* to retrieve it.<br/>
|
|
|
6930 |
* If you open moodle/lang/en/moodle.php and look near line 278
|
|
|
6931 |
* you will find a string to prompt a user for their word for 'course'
|
|
|
6932 |
* <code>
|
|
|
6933 |
* $string['course'] = 'Course';
|
|
|
6934 |
* </code>
|
|
|
6935 |
* So if you want to display the string 'Course'
|
|
|
6936 |
* in any language that supports it on your site
|
|
|
6937 |
* you just need to use the identifier 'course'
|
|
|
6938 |
* <code>
|
|
|
6939 |
* $mystring = '<strong>'. get_string('course') .'</strong>';
|
|
|
6940 |
* or
|
|
|
6941 |
* </code>
|
|
|
6942 |
* If the string you want is in another file you'd take a slightly
|
|
|
6943 |
* different approach. Looking in moodle/lang/en/calendar.php you find
|
|
|
6944 |
* around line 75:
|
|
|
6945 |
* <code>
|
|
|
6946 |
* $string['typecourse'] = 'Course event';
|
|
|
6947 |
* </code>
|
|
|
6948 |
* If you want to display the string "Course event" in any language
|
|
|
6949 |
* supported you would use the identifier 'typecourse' and the module 'calendar'
|
|
|
6950 |
* (because it is in the file calendar.php):
|
|
|
6951 |
* <code>
|
|
|
6952 |
* $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
|
|
|
6953 |
* </code>
|
|
|
6954 |
*
|
|
|
6955 |
* As a last resort, should the identifier fail to map to a string
|
|
|
6956 |
* the returned string will be [[ $identifier ]]
|
|
|
6957 |
*
|
|
|
6958 |
* In Moodle 2.3 there is a new argument to this function $lazyload.
|
|
|
6959 |
* Setting $lazyload to true causes get_string to return a lang_string object
|
|
|
6960 |
* rather than the string itself. The fetching of the string is then put off until
|
|
|
6961 |
* the string object is first used. The object can be used by calling it's out
|
|
|
6962 |
* method or by casting the object to a string, either directly e.g.
|
|
|
6963 |
* (string)$stringobject
|
|
|
6964 |
* or indirectly by using the string within another string or echoing it out e.g.
|
|
|
6965 |
* echo $stringobject
|
|
|
6966 |
* return "<p>{$stringobject}</p>";
|
|
|
6967 |
* It is worth noting that using $lazyload and attempting to use the string as an
|
|
|
6968 |
* array key will cause a fatal error as objects cannot be used as array keys.
|
|
|
6969 |
* But you should never do that anyway!
|
|
|
6970 |
* For more information {@link lang_string}
|
|
|
6971 |
*
|
|
|
6972 |
* @category string
|
|
|
6973 |
* @param string $identifier The key identifier for the localized string
|
|
|
6974 |
* @param string $component The module where the key identifier is stored,
|
|
|
6975 |
* usually expressed as the filename in the language pack without the
|
|
|
6976 |
* .php on the end but can also be written as mod/forum or grade/export/xls.
|
|
|
6977 |
* If none is specified then moodle.php is used.
|
|
|
6978 |
* @param string|object|array|int $a An object, string or number that can be used
|
|
|
6979 |
* within translation strings
|
|
|
6980 |
* @param bool $lazyload If set to true a string object is returned instead of
|
|
|
6981 |
* the string itself. The string then isn't calculated until it is first used.
|
|
|
6982 |
* @return string The localized string.
|
|
|
6983 |
* @throws coding_exception
|
|
|
6984 |
*/
|
|
|
6985 |
function get_string($identifier, $component = '', $a = null, $lazyload = false) {
|
|
|
6986 |
global $CFG;
|
|
|
6987 |
|
|
|
6988 |
// If the lazy load argument has been supplied return a lang_string object
|
|
|
6989 |
// instead.
|
|
|
6990 |
// We need to make sure it is true (and a bool) as you will see below there
|
|
|
6991 |
// used to be a forth argument at one point.
|
|
|
6992 |
if ($lazyload === true) {
|
|
|
6993 |
return new lang_string($identifier, $component, $a);
|
|
|
6994 |
}
|
|
|
6995 |
|
|
|
6996 |
if ($CFG->debugdeveloper && clean_param($identifier, PARAM_STRINGID) === '') {
|
|
|
6997 |
throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.', DEBUG_DEVELOPER);
|
|
|
6998 |
}
|
|
|
6999 |
|
|
|
7000 |
// There is now a forth argument again, this time it is a boolean however so
|
|
|
7001 |
// we can still check for the old extralocations parameter.
|
|
|
7002 |
if (!is_bool($lazyload) && !empty($lazyload)) {
|
|
|
7003 |
debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
|
|
|
7004 |
}
|
|
|
7005 |
|
|
|
7006 |
if (strpos((string)$component, '/') !== false) {
|
|
|
7007 |
debugging('The module name you passed to get_string is the deprecated format ' .
|
|
|
7008 |
'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
|
|
|
7009 |
$componentpath = explode('/', $component);
|
|
|
7010 |
|
|
|
7011 |
switch ($componentpath[0]) {
|
|
|
7012 |
case 'mod':
|
|
|
7013 |
$component = $componentpath[1];
|
|
|
7014 |
break;
|
|
|
7015 |
case 'blocks':
|
|
|
7016 |
case 'block':
|
|
|
7017 |
$component = 'block_'.$componentpath[1];
|
|
|
7018 |
break;
|
|
|
7019 |
case 'enrol':
|
|
|
7020 |
$component = 'enrol_'.$componentpath[1];
|
|
|
7021 |
break;
|
|
|
7022 |
case 'format':
|
|
|
7023 |
$component = 'format_'.$componentpath[1];
|
|
|
7024 |
break;
|
|
|
7025 |
case 'grade':
|
|
|
7026 |
$component = 'grade'.$componentpath[1].'_'.$componentpath[2];
|
|
|
7027 |
break;
|
|
|
7028 |
}
|
|
|
7029 |
}
|
|
|
7030 |
|
|
|
7031 |
$result = get_string_manager()->get_string($identifier, $component, $a);
|
|
|
7032 |
|
|
|
7033 |
// Debugging feature lets you display string identifier and component.
|
|
|
7034 |
if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
|
|
|
7035 |
$result .= ' {' . $identifier . '/' . $component . '}';
|
|
|
7036 |
}
|
|
|
7037 |
return $result;
|
|
|
7038 |
}
|
|
|
7039 |
|
|
|
7040 |
/**
|
|
|
7041 |
* Converts an array of strings to their localized value.
|
|
|
7042 |
*
|
|
|
7043 |
* @param array $array An array of strings
|
|
|
7044 |
* @param string $component The language module that these strings can be found in.
|
|
|
7045 |
* @return stdClass translated strings.
|
|
|
7046 |
*/
|
|
|
7047 |
function get_strings($array, $component = '') {
|
|
|
7048 |
$string = new stdClass;
|
|
|
7049 |
foreach ($array as $item) {
|
|
|
7050 |
$string->$item = get_string($item, $component);
|
|
|
7051 |
}
|
|
|
7052 |
return $string;
|
|
|
7053 |
}
|
|
|
7054 |
|
|
|
7055 |
/**
|
|
|
7056 |
* Prints out a translated string.
|
|
|
7057 |
*
|
|
|
7058 |
* Prints out a translated string using the return value from the {@link get_string()} function.
|
|
|
7059 |
*
|
|
|
7060 |
* Example usage of this function when the string is in the moodle.php file:<br/>
|
|
|
7061 |
* <code>
|
|
|
7062 |
* echo '<strong>';
|
|
|
7063 |
* print_string('course');
|
|
|
7064 |
* echo '</strong>';
|
|
|
7065 |
* </code>
|
|
|
7066 |
*
|
|
|
7067 |
* Example usage of this function when the string is not in the moodle.php file:<br/>
|
|
|
7068 |
* <code>
|
|
|
7069 |
* echo '<h1>';
|
|
|
7070 |
* print_string('typecourse', 'calendar');
|
|
|
7071 |
* echo '</h1>';
|
|
|
7072 |
* </code>
|
|
|
7073 |
*
|
|
|
7074 |
* @category string
|
|
|
7075 |
* @param string $identifier The key identifier for the localized string
|
|
|
7076 |
* @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
|
|
|
7077 |
* @param string|object|array $a An object, string or number that can be used within translation strings
|
|
|
7078 |
*/
|
|
|
7079 |
function print_string($identifier, $component = '', $a = null) {
|
|
|
7080 |
echo get_string($identifier, $component, $a);
|
|
|
7081 |
}
|
|
|
7082 |
|
|
|
7083 |
/**
|
|
|
7084 |
* Returns a list of charset codes
|
|
|
7085 |
*
|
|
|
7086 |
* Returns a list of charset codes. It's hardcoded, so they should be added manually
|
|
|
7087 |
* (checking that such charset is supported by the texlib library!)
|
|
|
7088 |
*
|
|
|
7089 |
* @return array And associative array with contents in the form of charset => charset
|
|
|
7090 |
*/
|
|
|
7091 |
function get_list_of_charsets() {
|
|
|
7092 |
|
|
|
7093 |
$charsets = array(
|
|
|
7094 |
'EUC-JP' => 'EUC-JP',
|
|
|
7095 |
'ISO-2022-JP'=> 'ISO-2022-JP',
|
|
|
7096 |
'ISO-8859-1' => 'ISO-8859-1',
|
|
|
7097 |
'SHIFT-JIS' => 'SHIFT-JIS',
|
|
|
7098 |
'GB2312' => 'GB2312',
|
|
|
7099 |
'GB18030' => 'GB18030', // GB18030 not supported by typo and mbstring.
|
|
|
7100 |
'UTF-8' => 'UTF-8');
|
|
|
7101 |
|
|
|
7102 |
asort($charsets);
|
|
|
7103 |
|
|
|
7104 |
return $charsets;
|
|
|
7105 |
}
|
|
|
7106 |
|
|
|
7107 |
/**
|
|
|
7108 |
* Returns a list of valid and compatible themes
|
|
|
7109 |
*
|
|
|
7110 |
* @return array
|
|
|
7111 |
*/
|
|
|
7112 |
function get_list_of_themes() {
|
|
|
7113 |
global $CFG;
|
|
|
7114 |
|
|
|
7115 |
$themes = array();
|
|
|
7116 |
|
|
|
7117 |
if (!empty($CFG->themelist)) { // Use admin's list of themes.
|
|
|
7118 |
$themelist = explode(',', $CFG->themelist);
|
|
|
7119 |
} else {
|
|
|
7120 |
$themelist = array_keys(core_component::get_plugin_list("theme"));
|
|
|
7121 |
}
|
|
|
7122 |
|
|
|
7123 |
foreach ($themelist as $key => $themename) {
|
|
|
7124 |
$theme = theme_config::load($themename);
|
|
|
7125 |
$themes[$themename] = $theme;
|
|
|
7126 |
}
|
|
|
7127 |
|
|
|
7128 |
core_collator::asort_objects_by_method($themes, 'get_theme_name');
|
|
|
7129 |
|
|
|
7130 |
return $themes;
|
|
|
7131 |
}
|
|
|
7132 |
|
|
|
7133 |
/**
|
|
|
7134 |
* Factory function for emoticon_manager
|
|
|
7135 |
*
|
|
|
7136 |
* @return emoticon_manager singleton
|
|
|
7137 |
*/
|
|
|
7138 |
function get_emoticon_manager() {
|
|
|
7139 |
static $singleton = null;
|
|
|
7140 |
|
|
|
7141 |
if (is_null($singleton)) {
|
|
|
7142 |
$singleton = new emoticon_manager();
|
|
|
7143 |
}
|
|
|
7144 |
|
|
|
7145 |
return $singleton;
|
|
|
7146 |
}
|
|
|
7147 |
|
|
|
7148 |
/**
|
|
|
7149 |
* Provides core support for plugins that have to deal with emoticons (like HTML editor or emoticon filter).
|
|
|
7150 |
*
|
|
|
7151 |
* Whenever this manager mentiones 'emoticon object', the following data
|
|
|
7152 |
* structure is expected: stdClass with properties text, imagename, imagecomponent,
|
|
|
7153 |
* altidentifier and altcomponent
|
|
|
7154 |
*
|
|
|
7155 |
* @see admin_setting_emoticons
|
|
|
7156 |
*
|
|
|
7157 |
* @copyright 2010 David Mudrak
|
|
|
7158 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
7159 |
*/
|
|
|
7160 |
class emoticon_manager {
|
|
|
7161 |
|
|
|
7162 |
/**
|
|
|
7163 |
* Returns the currently enabled emoticons
|
|
|
7164 |
*
|
|
|
7165 |
* @param boolean $selectable - If true, only return emoticons that should be selectable from a list.
|
|
|
7166 |
* @return array of emoticon objects
|
|
|
7167 |
*/
|
|
|
7168 |
public function get_emoticons($selectable = false) {
|
|
|
7169 |
global $CFG;
|
|
|
7170 |
$notselectable = ['martin', 'egg'];
|
|
|
7171 |
|
|
|
7172 |
if (empty($CFG->emoticons)) {
|
|
|
7173 |
return array();
|
|
|
7174 |
}
|
|
|
7175 |
|
|
|
7176 |
$emoticons = $this->decode_stored_config($CFG->emoticons);
|
|
|
7177 |
|
|
|
7178 |
if (!is_array($emoticons)) {
|
|
|
7179 |
// Something is wrong with the format of stored setting.
|
|
|
7180 |
debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
|
|
|
7181 |
return array();
|
|
|
7182 |
}
|
|
|
7183 |
if ($selectable) {
|
|
|
7184 |
foreach ($emoticons as $index => $emote) {
|
|
|
7185 |
if (in_array($emote->altidentifier, $notselectable)) {
|
|
|
7186 |
// Skip this one.
|
|
|
7187 |
unset($emoticons[$index]);
|
|
|
7188 |
}
|
|
|
7189 |
}
|
|
|
7190 |
}
|
|
|
7191 |
|
|
|
7192 |
return $emoticons;
|
|
|
7193 |
}
|
|
|
7194 |
|
|
|
7195 |
/**
|
|
|
7196 |
* Converts emoticon object into renderable pix_emoticon object
|
|
|
7197 |
*
|
|
|
7198 |
* @param stdClass $emoticon emoticon object
|
|
|
7199 |
* @param array $attributes explicit HTML attributes to set
|
|
|
7200 |
* @return pix_emoticon
|
|
|
7201 |
*/
|
|
|
7202 |
public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
|
|
|
7203 |
$stringmanager = get_string_manager();
|
|
|
7204 |
if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
|
|
|
7205 |
$alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
|
|
|
7206 |
} else {
|
|
|
7207 |
$alt = s($emoticon->text);
|
|
|
7208 |
}
|
|
|
7209 |
return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
|
|
|
7210 |
}
|
|
|
7211 |
|
|
|
7212 |
/**
|
|
|
7213 |
* Encodes the array of emoticon objects into a string storable in config table
|
|
|
7214 |
*
|
|
|
7215 |
* @see self::decode_stored_config()
|
|
|
7216 |
* @param array $emoticons array of emtocion objects
|
|
|
7217 |
* @return string
|
|
|
7218 |
*/
|
|
|
7219 |
public function encode_stored_config(array $emoticons) {
|
|
|
7220 |
return json_encode($emoticons);
|
|
|
7221 |
}
|
|
|
7222 |
|
|
|
7223 |
/**
|
|
|
7224 |
* Decodes the string into an array of emoticon objects
|
|
|
7225 |
*
|
|
|
7226 |
* @see self::encode_stored_config()
|
|
|
7227 |
* @param string $encoded
|
|
|
7228 |
* @return array|null
|
|
|
7229 |
*/
|
|
|
7230 |
public function decode_stored_config($encoded) {
|
|
|
7231 |
$decoded = json_decode($encoded);
|
|
|
7232 |
if (!is_array($decoded)) {
|
|
|
7233 |
return null;
|
|
|
7234 |
}
|
|
|
7235 |
return $decoded;
|
|
|
7236 |
}
|
|
|
7237 |
|
|
|
7238 |
/**
|
|
|
7239 |
* Returns default set of emoticons supported by Moodle
|
|
|
7240 |
*
|
|
|
7241 |
* @return array of sdtClasses
|
|
|
7242 |
*/
|
|
|
7243 |
public function default_emoticons() {
|
|
|
7244 |
return array(
|
|
|
7245 |
$this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
|
|
|
7246 |
$this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
|
|
|
7247 |
$this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
|
|
|
7248 |
$this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
|
|
|
7249 |
$this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
|
|
|
7250 |
$this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
|
|
|
7251 |
$this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
|
|
|
7252 |
$this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
|
|
|
7253 |
$this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
|
|
|
7254 |
$this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
|
|
|
7255 |
$this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
|
|
|
7256 |
$this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
|
|
|
7257 |
$this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
|
|
|
7258 |
$this->prepare_emoticon_object(":(", 's/sad', 'sad'),
|
|
|
7259 |
$this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
|
|
|
7260 |
$this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
|
|
|
7261 |
$this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
|
|
|
7262 |
$this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
|
|
|
7263 |
$this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
|
|
|
7264 |
$this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
|
|
|
7265 |
$this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
|
|
|
7266 |
$this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
|
|
|
7267 |
$this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
|
|
|
7268 |
$this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
|
|
|
7269 |
$this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
|
|
|
7270 |
$this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
|
|
|
7271 |
$this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
|
|
|
7272 |
$this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
|
|
|
7273 |
$this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
|
|
|
7274 |
$this->prepare_emoticon_object("( )", 's/egg', 'egg'),
|
|
|
7275 |
);
|
|
|
7276 |
}
|
|
|
7277 |
|
|
|
7278 |
/**
|
|
|
7279 |
* Helper method preparing the stdClass with the emoticon properties
|
|
|
7280 |
*
|
|
|
7281 |
* @param string|array $text or array of strings
|
|
|
7282 |
* @param string $imagename to be used by {@link pix_emoticon}
|
|
|
7283 |
* @param string $altidentifier alternative string identifier, null for no alt
|
|
|
7284 |
* @param string $altcomponent where the alternative string is defined
|
|
|
7285 |
* @param string $imagecomponent to be used by {@link pix_emoticon}
|
|
|
7286 |
* @return stdClass
|
|
|
7287 |
*/
|
|
|
7288 |
protected function prepare_emoticon_object($text, $imagename, $altidentifier = null,
|
|
|
7289 |
$altcomponent = 'core_pix', $imagecomponent = 'core') {
|
|
|
7290 |
return (object)array(
|
|
|
7291 |
'text' => $text,
|
|
|
7292 |
'imagename' => $imagename,
|
|
|
7293 |
'imagecomponent' => $imagecomponent,
|
|
|
7294 |
'altidentifier' => $altidentifier,
|
|
|
7295 |
'altcomponent' => $altcomponent,
|
|
|
7296 |
);
|
|
|
7297 |
}
|
|
|
7298 |
}
|
|
|
7299 |
|
|
|
7300 |
// ENCRYPTION.
|
|
|
7301 |
|
|
|
7302 |
/**
|
|
|
7303 |
* rc4encrypt
|
|
|
7304 |
*
|
|
|
7305 |
* @param string $data Data to encrypt.
|
|
|
7306 |
* @return string The now encrypted data.
|
|
|
7307 |
*/
|
|
|
7308 |
function rc4encrypt($data) {
|
|
|
7309 |
return endecrypt(get_site_identifier(), $data, '');
|
|
|
7310 |
}
|
|
|
7311 |
|
|
|
7312 |
/**
|
|
|
7313 |
* rc4decrypt
|
|
|
7314 |
*
|
|
|
7315 |
* @param string $data Data to decrypt.
|
|
|
7316 |
* @return string The now decrypted data.
|
|
|
7317 |
*/
|
|
|
7318 |
function rc4decrypt($data) {
|
|
|
7319 |
return endecrypt(get_site_identifier(), $data, 'de');
|
|
|
7320 |
}
|
|
|
7321 |
|
|
|
7322 |
/**
|
|
|
7323 |
* Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
|
|
|
7324 |
*
|
|
|
7325 |
* @todo Finish documenting this function
|
|
|
7326 |
*
|
|
|
7327 |
* @param string $pwd The password to use when encrypting or decrypting
|
|
|
7328 |
* @param string $data The data to be decrypted/encrypted
|
|
|
7329 |
* @param string $case Either 'de' for decrypt or '' for encrypt
|
|
|
7330 |
* @return string
|
|
|
7331 |
*/
|
|
|
7332 |
function endecrypt ($pwd, $data, $case) {
|
|
|
7333 |
|
|
|
7334 |
if ($case == 'de') {
|
|
|
7335 |
$data = urldecode($data);
|
|
|
7336 |
}
|
|
|
7337 |
|
|
|
7338 |
$key[] = '';
|
|
|
7339 |
$box[] = '';
|
|
|
7340 |
$pwdlength = strlen($pwd);
|
|
|
7341 |
|
|
|
7342 |
for ($i = 0; $i <= 255; $i++) {
|
|
|
7343 |
$key[$i] = ord(substr($pwd, ($i % $pwdlength), 1));
|
|
|
7344 |
$box[$i] = $i;
|
|
|
7345 |
}
|
|
|
7346 |
|
|
|
7347 |
$x = 0;
|
|
|
7348 |
|
|
|
7349 |
for ($i = 0; $i <= 255; $i++) {
|
|
|
7350 |
$x = ($x + $box[$i] + $key[$i]) % 256;
|
|
|
7351 |
$tempswap = $box[$i];
|
|
|
7352 |
$box[$i] = $box[$x];
|
|
|
7353 |
$box[$x] = $tempswap;
|
|
|
7354 |
}
|
|
|
7355 |
|
|
|
7356 |
$cipher = '';
|
|
|
7357 |
|
|
|
7358 |
$a = 0;
|
|
|
7359 |
$j = 0;
|
|
|
7360 |
|
|
|
7361 |
for ($i = 0; $i < strlen($data); $i++) {
|
|
|
7362 |
$a = ($a + 1) % 256;
|
|
|
7363 |
$j = ($j + $box[$a]) % 256;
|
|
|
7364 |
$temp = $box[$a];
|
|
|
7365 |
$box[$a] = $box[$j];
|
|
|
7366 |
$box[$j] = $temp;
|
|
|
7367 |
$k = $box[(($box[$a] + $box[$j]) % 256)];
|
|
|
7368 |
$cipherby = ord(substr($data, $i, 1)) ^ $k;
|
|
|
7369 |
$cipher .= chr($cipherby);
|
|
|
7370 |
}
|
|
|
7371 |
|
|
|
7372 |
if ($case == 'de') {
|
|
|
7373 |
$cipher = urldecode(urlencode($cipher));
|
|
|
7374 |
} else {
|
|
|
7375 |
$cipher = urlencode($cipher);
|
|
|
7376 |
}
|
|
|
7377 |
|
|
|
7378 |
return $cipher;
|
|
|
7379 |
}
|
|
|
7380 |
|
|
|
7381 |
// ENVIRONMENT CHECKING.
|
|
|
7382 |
|
|
|
7383 |
/**
|
|
|
7384 |
* This method validates a plug name. It is much faster than calling clean_param.
|
|
|
7385 |
*
|
|
|
7386 |
* @param string $name a string that might be a plugin name.
|
|
|
7387 |
* @return bool if this string is a valid plugin name.
|
|
|
7388 |
*/
|
|
|
7389 |
function is_valid_plugin_name($name) {
|
|
|
7390 |
// This does not work for 'mod', bad luck, use any other type.
|
|
|
7391 |
return core_component::is_valid_plugin_name('tool', $name);
|
|
|
7392 |
}
|
|
|
7393 |
|
|
|
7394 |
/**
|
|
|
7395 |
* Get a list of all the plugins of a given type that define a certain API function
|
|
|
7396 |
* in a certain file. The plugin component names and function names are returned.
|
|
|
7397 |
*
|
|
|
7398 |
* @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
|
|
|
7399 |
* @param string $function the part of the name of the function after the
|
|
|
7400 |
* frankenstyle prefix. e.g 'hook' if you are looking for functions with
|
|
|
7401 |
* names like report_courselist_hook.
|
|
|
7402 |
* @param string $file the name of file within the plugin that defines the
|
|
|
7403 |
* function. Defaults to lib.php.
|
|
|
7404 |
* @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
|
|
|
7405 |
* and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
|
|
|
7406 |
*/
|
|
|
7407 |
function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
|
|
|
7408 |
global $CFG;
|
|
|
7409 |
|
|
|
7410 |
// We don't include here as all plugin types files would be included.
|
|
|
7411 |
$plugins = get_plugins_with_function($function, $file, false);
|
|
|
7412 |
|
|
|
7413 |
if (empty($plugins[$plugintype])) {
|
|
|
7414 |
return array();
|
|
|
7415 |
}
|
|
|
7416 |
|
|
|
7417 |
$allplugins = core_component::get_plugin_list($plugintype);
|
|
|
7418 |
|
|
|
7419 |
// Reformat the array and include the files.
|
|
|
7420 |
$pluginfunctions = array();
|
|
|
7421 |
foreach ($plugins[$plugintype] as $pluginname => $functionname) {
|
|
|
7422 |
|
|
|
7423 |
// Check that it has not been removed and the file is still available.
|
|
|
7424 |
if (!empty($allplugins[$pluginname])) {
|
|
|
7425 |
|
|
|
7426 |
$filepath = $allplugins[$pluginname] . DIRECTORY_SEPARATOR . $file;
|
|
|
7427 |
if (file_exists($filepath)) {
|
|
|
7428 |
include_once($filepath);
|
|
|
7429 |
|
|
|
7430 |
// Now that the file is loaded, we must verify the function still exists.
|
|
|
7431 |
if (function_exists($functionname)) {
|
|
|
7432 |
$pluginfunctions[$plugintype . '_' . $pluginname] = $functionname;
|
|
|
7433 |
} else {
|
|
|
7434 |
// Invalidate the cache for next run.
|
|
|
7435 |
\cache_helper::invalidate_by_definition('core', 'plugin_functions');
|
|
|
7436 |
}
|
|
|
7437 |
}
|
|
|
7438 |
}
|
|
|
7439 |
}
|
|
|
7440 |
|
|
|
7441 |
return $pluginfunctions;
|
|
|
7442 |
}
|
|
|
7443 |
|
|
|
7444 |
/**
|
|
|
7445 |
* Get a list of all the plugins that define a certain API function in a certain file.
|
|
|
7446 |
*
|
|
|
7447 |
* @param string $function the part of the name of the function after the
|
|
|
7448 |
* frankenstyle prefix. e.g 'hook' if you are looking for functions with
|
|
|
7449 |
* names like report_courselist_hook.
|
|
|
7450 |
* @param string $file the name of file within the plugin that defines the
|
|
|
7451 |
* function. Defaults to lib.php.
|
|
|
7452 |
* @param bool $include Whether to include the files that contain the functions or not.
|
|
|
7453 |
* @param bool $migratedtohook if true this is a deprecated lib.php callback, if hook callback is present then do nothing
|
|
|
7454 |
* @return array with [plugintype][plugin] = functionname
|
|
|
7455 |
*/
|
|
|
7456 |
function get_plugins_with_function($function, $file = 'lib.php', $include = true, bool $migratedtohook = false) {
|
|
|
7457 |
global $CFG;
|
|
|
7458 |
|
|
|
7459 |
if (during_initial_install() || isset($CFG->upgraderunning)) {
|
|
|
7460 |
// API functions _must not_ be called during an installation or upgrade.
|
|
|
7461 |
return [];
|
|
|
7462 |
}
|
|
|
7463 |
|
|
|
7464 |
$plugincallback = $function;
|
|
|
7465 |
$filtermigrated = function($plugincallback, $pluginfunctions): array {
|
|
|
7466 |
foreach ($pluginfunctions as $plugintype => $plugins) {
|
|
|
7467 |
foreach ($plugins as $plugin => $unusedfunction) {
|
|
|
7468 |
$component = $plugintype . '_' . $plugin;
|
|
|
7469 |
if ($hooks = di::get(hook\manager::class)->get_hooks_deprecating_plugin_callback($plugincallback)) {
|
|
|
7470 |
if (di::get(hook\manager::class)->is_deprecating_hook_present($component, $plugincallback)) {
|
|
|
7471 |
// Ignore the old callback, it is there only for older Moodle versions.
|
|
|
7472 |
unset($pluginfunctions[$plugintype][$plugin]);
|
|
|
7473 |
} else {
|
|
|
7474 |
$hookmessage = count($hooks) == 1 ? reset($hooks) : 'one of ' . implode(', ', $hooks);
|
|
|
7475 |
debugging(
|
|
|
7476 |
"Callback $plugincallback in $component component should be migrated to new " .
|
|
|
7477 |
"hook callback for $hookmessage",
|
|
|
7478 |
DEBUG_DEVELOPER
|
|
|
7479 |
);
|
|
|
7480 |
}
|
|
|
7481 |
}
|
|
|
7482 |
}
|
|
|
7483 |
}
|
|
|
7484 |
return $pluginfunctions;
|
|
|
7485 |
};
|
|
|
7486 |
|
|
|
7487 |
$cache = \cache::make('core', 'plugin_functions');
|
|
|
7488 |
|
|
|
7489 |
// Including both although I doubt that we will find two functions definitions with the same name.
|
|
|
7490 |
// Clean the filename as cache_helper::hash_key only allows a-zA-Z0-9_.
|
|
|
7491 |
$pluginfunctions = false;
|
|
|
7492 |
if (!empty($CFG->allversionshash)) {
|
|
|
7493 |
$key = $CFG->allversionshash . '_' . $function . '_' . clean_param($file, PARAM_ALPHA);
|
|
|
7494 |
$pluginfunctions = $cache->get($key);
|
|
|
7495 |
}
|
|
|
7496 |
$dirty = false;
|
|
|
7497 |
|
|
|
7498 |
// Use the plugin manager to check that plugins are currently installed.
|
|
|
7499 |
$pluginmanager = \core_plugin_manager::instance();
|
|
|
7500 |
|
|
|
7501 |
if ($pluginfunctions !== false) {
|
|
|
7502 |
|
|
|
7503 |
// Checking that the files are still available.
|
|
|
7504 |
foreach ($pluginfunctions as $plugintype => $plugins) {
|
|
|
7505 |
|
|
|
7506 |
$allplugins = \core_component::get_plugin_list($plugintype);
|
|
|
7507 |
$installedplugins = $pluginmanager->get_installed_plugins($plugintype);
|
|
|
7508 |
foreach ($plugins as $plugin => $function) {
|
|
|
7509 |
if (!isset($installedplugins[$plugin])) {
|
|
|
7510 |
// Plugin code is still present on disk but it is not installed.
|
|
|
7511 |
$dirty = true;
|
|
|
7512 |
break 2;
|
|
|
7513 |
}
|
|
|
7514 |
|
|
|
7515 |
// Cache might be out of sync with the codebase, skip the plugin if it is not available.
|
|
|
7516 |
if (empty($allplugins[$plugin])) {
|
|
|
7517 |
$dirty = true;
|
|
|
7518 |
break 2;
|
|
|
7519 |
}
|
|
|
7520 |
|
|
|
7521 |
$fileexists = file_exists($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
|
|
|
7522 |
if ($include && $fileexists) {
|
|
|
7523 |
// Include the files if it was requested.
|
|
|
7524 |
include_once($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
|
|
|
7525 |
} else if (!$fileexists) {
|
|
|
7526 |
// If the file is not available any more it should not be returned.
|
|
|
7527 |
$dirty = true;
|
|
|
7528 |
break 2;
|
|
|
7529 |
}
|
|
|
7530 |
|
|
|
7531 |
// Check if the function still exists in the file.
|
|
|
7532 |
if ($include && !function_exists($function)) {
|
|
|
7533 |
$dirty = true;
|
|
|
7534 |
break 2;
|
|
|
7535 |
}
|
|
|
7536 |
}
|
|
|
7537 |
}
|
|
|
7538 |
|
|
|
7539 |
// If the cache is dirty, we should fall through and let it rebuild.
|
|
|
7540 |
if (!$dirty) {
|
|
|
7541 |
if ($migratedtohook && $file === 'lib.php') {
|
|
|
7542 |
$pluginfunctions = $filtermigrated($plugincallback, $pluginfunctions);
|
|
|
7543 |
}
|
|
|
7544 |
return $pluginfunctions;
|
|
|
7545 |
}
|
|
|
7546 |
}
|
|
|
7547 |
|
|
|
7548 |
$pluginfunctions = array();
|
|
|
7549 |
|
|
|
7550 |
// To fill the cached. Also, everything should continue working with cache disabled.
|
|
|
7551 |
$plugintypes = \core_component::get_plugin_types();
|
|
|
7552 |
foreach ($plugintypes as $plugintype => $unused) {
|
|
|
7553 |
|
|
|
7554 |
// We need to include files here.
|
|
|
7555 |
$pluginswithfile = \core_component::get_plugin_list_with_file($plugintype, $file, true);
|
|
|
7556 |
$installedplugins = $pluginmanager->get_installed_plugins($plugintype);
|
|
|
7557 |
foreach ($pluginswithfile as $plugin => $notused) {
|
|
|
7558 |
|
|
|
7559 |
if (!isset($installedplugins[$plugin])) {
|
|
|
7560 |
continue;
|
|
|
7561 |
}
|
|
|
7562 |
|
|
|
7563 |
$fullfunction = $plugintype . '_' . $plugin . '_' . $function;
|
|
|
7564 |
|
|
|
7565 |
$pluginfunction = false;
|
|
|
7566 |
if (function_exists($fullfunction)) {
|
|
|
7567 |
// Function exists with standard name. Store, indexed by frankenstyle name of plugin.
|
|
|
7568 |
$pluginfunction = $fullfunction;
|
|
|
7569 |
|
|
|
7570 |
} else if ($plugintype === 'mod') {
|
|
|
7571 |
// For modules, we also allow plugin without full frankenstyle but just starting with the module name.
|
|
|
7572 |
$shortfunction = $plugin . '_' . $function;
|
|
|
7573 |
if (function_exists($shortfunction)) {
|
|
|
7574 |
$pluginfunction = $shortfunction;
|
|
|
7575 |
}
|
|
|
7576 |
}
|
|
|
7577 |
|
|
|
7578 |
if ($pluginfunction) {
|
|
|
7579 |
if (empty($pluginfunctions[$plugintype])) {
|
|
|
7580 |
$pluginfunctions[$plugintype] = array();
|
|
|
7581 |
}
|
|
|
7582 |
$pluginfunctions[$plugintype][$plugin] = $pluginfunction;
|
|
|
7583 |
}
|
|
|
7584 |
|
|
|
7585 |
}
|
|
|
7586 |
}
|
|
|
7587 |
if (!empty($CFG->allversionshash)) {
|
|
|
7588 |
$cache->set($key, $pluginfunctions);
|
|
|
7589 |
}
|
|
|
7590 |
|
|
|
7591 |
if ($migratedtohook && $file === 'lib.php') {
|
|
|
7592 |
$pluginfunctions = $filtermigrated($plugincallback, $pluginfunctions);
|
|
|
7593 |
}
|
|
|
7594 |
|
|
|
7595 |
return $pluginfunctions;
|
|
|
7596 |
|
|
|
7597 |
}
|
|
|
7598 |
|
|
|
7599 |
/**
|
|
|
7600 |
* Lists plugin-like directories within specified directory
|
|
|
7601 |
*
|
|
|
7602 |
* This function was originally used for standard Moodle plugins, please use
|
|
|
7603 |
* new core_component::get_plugin_list() now.
|
|
|
7604 |
*
|
|
|
7605 |
* This function is used for general directory listing and backwards compatility.
|
|
|
7606 |
*
|
|
|
7607 |
* @param string $directory relative directory from root
|
|
|
7608 |
* @param string $exclude dir name to exclude from the list (defaults to none)
|
|
|
7609 |
* @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
|
|
|
7610 |
* @return array Sorted array of directory names found under the requested parameters
|
|
|
7611 |
*/
|
|
|
7612 |
function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
|
|
|
7613 |
global $CFG;
|
|
|
7614 |
|
|
|
7615 |
$plugins = array();
|
|
|
7616 |
|
|
|
7617 |
if (empty($basedir)) {
|
|
|
7618 |
$basedir = $CFG->dirroot .'/'. $directory;
|
|
|
7619 |
|
|
|
7620 |
} else {
|
|
|
7621 |
$basedir = $basedir .'/'. $directory;
|
|
|
7622 |
}
|
|
|
7623 |
|
|
|
7624 |
if ($CFG->debugdeveloper and empty($exclude)) {
|
|
|
7625 |
// Make sure devs do not use this to list normal plugins,
|
|
|
7626 |
// this is intended for general directories that are not plugins!
|
|
|
7627 |
|
|
|
7628 |
$subtypes = core_component::get_plugin_types();
|
|
|
7629 |
if (in_array($basedir, $subtypes)) {
|
|
|
7630 |
debugging('get_list_of_plugins() should not be used to list real plugins, use core_component::get_plugin_list() instead!', DEBUG_DEVELOPER);
|
|
|
7631 |
}
|
|
|
7632 |
unset($subtypes);
|
|
|
7633 |
}
|
|
|
7634 |
|
|
|
7635 |
$ignorelist = array_flip(array_filter([
|
|
|
7636 |
'CVS',
|
|
|
7637 |
'_vti_cnf',
|
|
|
7638 |
'amd',
|
|
|
7639 |
'classes',
|
|
|
7640 |
'simpletest',
|
|
|
7641 |
'tests',
|
|
|
7642 |
'templates',
|
|
|
7643 |
'yui',
|
|
|
7644 |
$exclude,
|
|
|
7645 |
]));
|
|
|
7646 |
|
|
|
7647 |
if (file_exists($basedir) && filetype($basedir) == 'dir') {
|
|
|
7648 |
if (!$dirhandle = opendir($basedir)) {
|
|
|
7649 |
debugging("Directory permission error for plugin ({$directory}). Directory exists but cannot be read.", DEBUG_DEVELOPER);
|
|
|
7650 |
return array();
|
|
|
7651 |
}
|
|
|
7652 |
while (false !== ($dir = readdir($dirhandle))) {
|
|
|
7653 |
if (strpos($dir, '.') === 0) {
|
|
|
7654 |
// Ignore directories starting with .
|
|
|
7655 |
// These are treated as hidden directories.
|
|
|
7656 |
continue;
|
|
|
7657 |
}
|
|
|
7658 |
if (array_key_exists($dir, $ignorelist)) {
|
|
|
7659 |
// This directory features on the ignore list.
|
|
|
7660 |
continue;
|
|
|
7661 |
}
|
|
|
7662 |
if (filetype($basedir .'/'. $dir) != 'dir') {
|
|
|
7663 |
continue;
|
|
|
7664 |
}
|
|
|
7665 |
$plugins[] = $dir;
|
|
|
7666 |
}
|
|
|
7667 |
closedir($dirhandle);
|
|
|
7668 |
}
|
|
|
7669 |
if ($plugins) {
|
|
|
7670 |
asort($plugins);
|
|
|
7671 |
}
|
|
|
7672 |
return $plugins;
|
|
|
7673 |
}
|
|
|
7674 |
|
|
|
7675 |
/**
|
|
|
7676 |
* Invoke plugin's callback functions
|
|
|
7677 |
*
|
|
|
7678 |
* @param string $type plugin type e.g. 'mod'
|
|
|
7679 |
* @param string $name plugin name
|
|
|
7680 |
* @param string $feature feature name
|
|
|
7681 |
* @param string $action feature's action
|
|
|
7682 |
* @param array $params parameters of callback function, should be an array
|
|
|
7683 |
* @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
|
|
|
7684 |
* @param bool $migratedtohook if true this is a deprecated callback, if hook callback is present then do nothing
|
|
|
7685 |
* @return mixed
|
|
|
7686 |
*
|
|
|
7687 |
* @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
|
|
|
7688 |
*/
|
|
|
7689 |
function plugin_callback($type, $name, $feature, $action, $params = null, $default = null, bool $migratedtohook = false) {
|
|
|
7690 |
return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default, $migratedtohook);
|
|
|
7691 |
}
|
|
|
7692 |
|
|
|
7693 |
/**
|
|
|
7694 |
* Invoke component's callback functions
|
|
|
7695 |
*
|
|
|
7696 |
* @param string $component frankenstyle component name, e.g. 'mod_quiz'
|
|
|
7697 |
* @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
|
|
|
7698 |
* @param array $params parameters of callback function
|
|
|
7699 |
* @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
|
|
|
7700 |
* @param bool $migratedtohook if true this is a deprecated callback, if hook callback is present then do nothing
|
|
|
7701 |
* @return mixed
|
|
|
7702 |
*/
|
|
|
7703 |
function component_callback($component, $function, array $params = array(), $default = null, bool $migratedtohook = false) {
|
|
|
7704 |
$functionname = component_callback_exists($component, $function);
|
|
|
7705 |
|
|
|
7706 |
if ($functionname) {
|
|
|
7707 |
if ($migratedtohook) {
|
|
|
7708 |
$hookmanager = di::get(hook\manager::class);
|
|
|
7709 |
if ($hooks = $hookmanager->get_hooks_deprecating_plugin_callback($function)) {
|
|
|
7710 |
if ($hookmanager->is_deprecating_hook_present($component, $function)) {
|
|
|
7711 |
// Do not call the old lib.php callback,
|
|
|
7712 |
// it is there for compatibility with older Moodle versions only.
|
|
|
7713 |
return null;
|
|
|
7714 |
} else {
|
|
|
7715 |
$hookmessage = count($hooks) == 1 ? reset($hooks) : 'one of ' . implode(', ', $hooks);
|
|
|
7716 |
debugging(
|
|
|
7717 |
"Callback $function in $component component should be migrated to new hook callback for $hookmessage",
|
|
|
7718 |
DEBUG_DEVELOPER);
|
|
|
7719 |
}
|
|
|
7720 |
}
|
|
|
7721 |
}
|
|
|
7722 |
|
|
|
7723 |
// Function exists, so just return function result.
|
|
|
7724 |
$ret = call_user_func_array($functionname, $params);
|
|
|
7725 |
if (is_null($ret)) {
|
|
|
7726 |
return $default;
|
|
|
7727 |
} else {
|
|
|
7728 |
return $ret;
|
|
|
7729 |
}
|
|
|
7730 |
}
|
|
|
7731 |
return $default;
|
|
|
7732 |
}
|
|
|
7733 |
|
|
|
7734 |
/**
|
|
|
7735 |
* Determine if a component callback exists and return the function name to call. Note that this
|
|
|
7736 |
* function will include the required library files so that the functioname returned can be
|
|
|
7737 |
* called directly.
|
|
|
7738 |
*
|
|
|
7739 |
* @param string $component frankenstyle component name, e.g. 'mod_quiz'
|
|
|
7740 |
* @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
|
|
|
7741 |
* @return mixed Complete function name to call if the callback exists or false if it doesn't.
|
|
|
7742 |
* @throws coding_exception if invalid component specfied
|
|
|
7743 |
*/
|
|
|
7744 |
function component_callback_exists($component, $function) {
|
|
|
7745 |
global $CFG; // This is needed for the inclusions.
|
|
|
7746 |
|
|
|
7747 |
$cleancomponent = clean_param($component, PARAM_COMPONENT);
|
|
|
7748 |
if (empty($cleancomponent)) {
|
|
|
7749 |
throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
|
|
|
7750 |
}
|
|
|
7751 |
$component = $cleancomponent;
|
|
|
7752 |
|
|
|
7753 |
list($type, $name) = core_component::normalize_component($component);
|
|
|
7754 |
$component = $type . '_' . $name;
|
|
|
7755 |
|
|
|
7756 |
$oldfunction = $name.'_'.$function;
|
|
|
7757 |
$function = $component.'_'.$function;
|
|
|
7758 |
|
|
|
7759 |
$dir = core_component::get_component_directory($component);
|
|
|
7760 |
if (empty($dir)) {
|
|
|
7761 |
throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
|
|
|
7762 |
}
|
|
|
7763 |
|
|
|
7764 |
// Load library and look for function.
|
|
|
7765 |
if (file_exists($dir.'/lib.php')) {
|
|
|
7766 |
require_once($dir.'/lib.php');
|
|
|
7767 |
}
|
|
|
7768 |
|
|
|
7769 |
if (!function_exists($function) and function_exists($oldfunction)) {
|
|
|
7770 |
if ($type !== 'mod' and $type !== 'core') {
|
|
|
7771 |
debugging("Please use new function name $function instead of legacy $oldfunction", DEBUG_DEVELOPER);
|
|
|
7772 |
}
|
|
|
7773 |
$function = $oldfunction;
|
|
|
7774 |
}
|
|
|
7775 |
|
|
|
7776 |
if (function_exists($function)) {
|
|
|
7777 |
return $function;
|
|
|
7778 |
}
|
|
|
7779 |
return false;
|
|
|
7780 |
}
|
|
|
7781 |
|
|
|
7782 |
/**
|
|
|
7783 |
* Call the specified callback method on the provided class.
|
|
|
7784 |
*
|
|
|
7785 |
* If the callback returns null, then the default value is returned instead.
|
|
|
7786 |
* If the class does not exist, then the default value is returned.
|
|
|
7787 |
*
|
|
|
7788 |
* @param string $classname The name of the class to call upon.
|
|
|
7789 |
* @param string $methodname The name of the staticically defined method on the class.
|
|
|
7790 |
* @param array $params The arguments to pass into the method.
|
|
|
7791 |
* @param mixed $default The default value.
|
|
|
7792 |
* @param bool $migratedtohook True if the callback has been migrated to a hook.
|
|
|
7793 |
* @return mixed The return value.
|
|
|
7794 |
*/
|
|
|
7795 |
function component_class_callback($classname, $methodname, array $params, $default = null, bool $migratedtohook = false) {
|
|
|
7796 |
if (!class_exists($classname)) {
|
|
|
7797 |
return $default;
|
|
|
7798 |
}
|
|
|
7799 |
|
|
|
7800 |
if (!method_exists($classname, $methodname)) {
|
|
|
7801 |
return $default;
|
|
|
7802 |
}
|
|
|
7803 |
|
|
|
7804 |
$fullfunction = $classname . '::' . $methodname;
|
|
|
7805 |
|
|
|
7806 |
if ($migratedtohook) {
|
|
|
7807 |
$functionparts = explode('\\', trim($fullfunction, '\\'));
|
|
|
7808 |
$component = $functionparts[0];
|
|
|
7809 |
$callback = end($functionparts);
|
|
|
7810 |
$hookmanager = di::get(hook\manager::class);
|
|
|
7811 |
if ($hooks = $hookmanager->get_hooks_deprecating_plugin_callback($callback)) {
|
|
|
7812 |
if ($hookmanager->is_deprecating_hook_present($component, $callback)) {
|
|
|
7813 |
// Do not call the old class callback,
|
|
|
7814 |
// it is there for compatibility with older Moodle versions only.
|
|
|
7815 |
return null;
|
|
|
7816 |
} else {
|
|
|
7817 |
$hookmessage = count($hooks) == 1 ? reset($hooks) : 'one of ' . implode(', ', $hooks);
|
|
|
7818 |
debugging("Callback $callback in $component component should be migrated to new hook callback for $hookmessage",
|
|
|
7819 |
DEBUG_DEVELOPER);
|
|
|
7820 |
}
|
|
|
7821 |
}
|
|
|
7822 |
}
|
|
|
7823 |
|
|
|
7824 |
$result = call_user_func_array($fullfunction, $params);
|
|
|
7825 |
|
|
|
7826 |
if (null === $result) {
|
|
|
7827 |
return $default;
|
|
|
7828 |
} else {
|
|
|
7829 |
return $result;
|
|
|
7830 |
}
|
|
|
7831 |
}
|
|
|
7832 |
|
|
|
7833 |
/**
|
|
|
7834 |
* Checks whether a plugin supports a specified feature.
|
|
|
7835 |
*
|
|
|
7836 |
* @param string $type Plugin type e.g. 'mod'
|
|
|
7837 |
* @param string $name Plugin name e.g. 'forum'
|
|
|
7838 |
* @param string $feature Feature code (FEATURE_xx constant)
|
|
|
7839 |
* @param mixed $default default value if feature support unknown
|
|
|
7840 |
* @return mixed Feature result (false if not supported, null if feature is unknown,
|
|
|
7841 |
* otherwise usually true but may have other feature-specific value such as array)
|
|
|
7842 |
* @throws coding_exception
|
|
|
7843 |
*/
|
|
|
7844 |
function plugin_supports($type, $name, $feature, $default = null) {
|
|
|
7845 |
global $CFG;
|
|
|
7846 |
|
|
|
7847 |
if ($type === 'mod' and $name === 'NEWMODULE') {
|
|
|
7848 |
// Somebody forgot to rename the module template.
|
|
|
7849 |
return false;
|
|
|
7850 |
}
|
|
|
7851 |
|
|
|
7852 |
$component = clean_param($type . '_' . $name, PARAM_COMPONENT);
|
|
|
7853 |
if (empty($component)) {
|
|
|
7854 |
throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
|
|
|
7855 |
}
|
|
|
7856 |
|
|
|
7857 |
$function = null;
|
|
|
7858 |
|
|
|
7859 |
if ($type === 'mod') {
|
|
|
7860 |
// We need this special case because we support subplugins in modules,
|
|
|
7861 |
// otherwise it would end up in infinite loop.
|
|
|
7862 |
if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
|
|
|
7863 |
include_once("$CFG->dirroot/mod/$name/lib.php");
|
|
|
7864 |
$function = $component.'_supports';
|
|
|
7865 |
if (!function_exists($function)) {
|
|
|
7866 |
// Legacy non-frankenstyle function name.
|
|
|
7867 |
$function = $name.'_supports';
|
|
|
7868 |
}
|
|
|
7869 |
}
|
|
|
7870 |
|
|
|
7871 |
} else {
|
|
|
7872 |
if (!$path = core_component::get_plugin_directory($type, $name)) {
|
|
|
7873 |
// Non existent plugin type.
|
|
|
7874 |
return false;
|
|
|
7875 |
}
|
|
|
7876 |
if (file_exists("$path/lib.php")) {
|
|
|
7877 |
include_once("$path/lib.php");
|
|
|
7878 |
$function = $component.'_supports';
|
|
|
7879 |
}
|
|
|
7880 |
}
|
|
|
7881 |
|
|
|
7882 |
if ($function and function_exists($function)) {
|
|
|
7883 |
$supports = $function($feature);
|
|
|
7884 |
if (is_null($supports)) {
|
|
|
7885 |
// Plugin does not know - use default.
|
|
|
7886 |
return $default;
|
|
|
7887 |
} else {
|
|
|
7888 |
return $supports;
|
|
|
7889 |
}
|
|
|
7890 |
}
|
|
|
7891 |
|
|
|
7892 |
// Plugin does not care, so use default.
|
|
|
7893 |
return $default;
|
|
|
7894 |
}
|
|
|
7895 |
|
|
|
7896 |
/**
|
|
|
7897 |
* Returns true if the current version of PHP is greater that the specified one.
|
|
|
7898 |
*
|
|
|
7899 |
* @todo Check PHP version being required here is it too low?
|
|
|
7900 |
*
|
|
|
7901 |
* @param string $version The version of php being tested.
|
|
|
7902 |
* @return bool
|
|
|
7903 |
*/
|
|
|
7904 |
function check_php_version($version='5.2.4') {
|
|
|
7905 |
return (version_compare(phpversion(), $version) >= 0);
|
|
|
7906 |
}
|
|
|
7907 |
|
|
|
7908 |
/**
|
|
|
7909 |
* Determine if moodle installation requires update.
|
|
|
7910 |
*
|
|
|
7911 |
* Checks version numbers of main code and all plugins to see
|
|
|
7912 |
* if there are any mismatches.
|
|
|
7913 |
*
|
|
|
7914 |
* @param bool $checkupgradeflag check the outagelessupgrade flag to see if an upgrade is running.
|
|
|
7915 |
* @return bool
|
|
|
7916 |
*/
|
|
|
7917 |
function moodle_needs_upgrading($checkupgradeflag = true) {
|
|
|
7918 |
global $CFG, $DB;
|
|
|
7919 |
|
|
|
7920 |
// Say no if there is already an upgrade running.
|
|
|
7921 |
if ($checkupgradeflag) {
|
|
|
7922 |
$lock = $DB->get_field('config', 'value', ['name' => 'outagelessupgrade']);
|
|
|
7923 |
$currentprocessrunningupgrade = (defined('CLI_UPGRADE_RUNNING') && CLI_UPGRADE_RUNNING);
|
|
|
7924 |
// If we ARE locked, but this PHP process is NOT the process running the upgrade,
|
|
|
7925 |
// We should always return false.
|
|
|
7926 |
// This means the upgrade is running from CLI somewhere, or about to.
|
|
|
7927 |
if (!empty($lock) && !$currentprocessrunningupgrade) {
|
|
|
7928 |
return false;
|
|
|
7929 |
}
|
|
|
7930 |
}
|
|
|
7931 |
|
|
|
7932 |
if (empty($CFG->version)) {
|
|
|
7933 |
return true;
|
|
|
7934 |
}
|
|
|
7935 |
|
|
|
7936 |
// There is no need to purge plugininfo caches here because
|
|
|
7937 |
// these caches are not used during upgrade and they are purged after
|
|
|
7938 |
// every upgrade.
|
|
|
7939 |
|
|
|
7940 |
if (empty($CFG->allversionshash)) {
|
|
|
7941 |
return true;
|
|
|
7942 |
}
|
|
|
7943 |
|
|
|
7944 |
$hash = core_component::get_all_versions_hash();
|
|
|
7945 |
|
|
|
7946 |
return ($hash !== $CFG->allversionshash);
|
|
|
7947 |
}
|
|
|
7948 |
|
|
|
7949 |
/**
|
|
|
7950 |
* Returns the major version of this site
|
|
|
7951 |
*
|
|
|
7952 |
* Moodle version numbers consist of three numbers separated by a dot, for
|
|
|
7953 |
* example 1.9.11 or 2.0.2. The first two numbers, like 1.9 or 2.0, represent so
|
|
|
7954 |
* called major version. This function extracts the major version from either
|
|
|
7955 |
* $CFG->release (default) or eventually from the $release variable defined in
|
|
|
7956 |
* the main version.php.
|
|
|
7957 |
*
|
|
|
7958 |
* @param bool $fromdisk should the version if source code files be used
|
|
|
7959 |
* @return string|false the major version like '2.3', false if could not be determined
|
|
|
7960 |
*/
|
|
|
7961 |
function moodle_major_version($fromdisk = false) {
|
|
|
7962 |
global $CFG;
|
|
|
7963 |
|
|
|
7964 |
if ($fromdisk) {
|
|
|
7965 |
$release = null;
|
|
|
7966 |
require($CFG->dirroot.'/version.php');
|
|
|
7967 |
if (empty($release)) {
|
|
|
7968 |
return false;
|
|
|
7969 |
}
|
|
|
7970 |
|
|
|
7971 |
} else {
|
|
|
7972 |
if (empty($CFG->release)) {
|
|
|
7973 |
return false;
|
|
|
7974 |
}
|
|
|
7975 |
$release = $CFG->release;
|
|
|
7976 |
}
|
|
|
7977 |
|
|
|
7978 |
if (preg_match('/^[0-9]+\.[0-9]+/', $release, $matches)) {
|
|
|
7979 |
return $matches[0];
|
|
|
7980 |
} else {
|
|
|
7981 |
return false;
|
|
|
7982 |
}
|
|
|
7983 |
}
|
|
|
7984 |
|
|
|
7985 |
// MISCELLANEOUS.
|
|
|
7986 |
|
|
|
7987 |
/**
|
|
|
7988 |
* Gets the system locale
|
|
|
7989 |
*
|
|
|
7990 |
* @return string Retuns the current locale.
|
|
|
7991 |
*/
|
|
|
7992 |
function moodle_getlocale() {
|
|
|
7993 |
global $CFG;
|
|
|
7994 |
|
|
|
7995 |
// Fetch the correct locale based on ostype.
|
|
|
7996 |
if ($CFG->ostype == 'WINDOWS') {
|
|
|
7997 |
$stringtofetch = 'localewin';
|
|
|
7998 |
} else {
|
|
|
7999 |
$stringtofetch = 'locale';
|
|
|
8000 |
}
|
|
|
8001 |
|
|
|
8002 |
if (!empty($CFG->locale)) { // Override locale for all language packs.
|
|
|
8003 |
return $CFG->locale;
|
|
|
8004 |
}
|
|
|
8005 |
|
|
|
8006 |
return get_string($stringtofetch, 'langconfig');
|
|
|
8007 |
}
|
|
|
8008 |
|
|
|
8009 |
/**
|
|
|
8010 |
* Sets the system locale
|
|
|
8011 |
*
|
|
|
8012 |
* @category string
|
|
|
8013 |
* @param string $locale Can be used to force a locale
|
|
|
8014 |
*/
|
|
|
8015 |
function moodle_setlocale($locale='') {
|
|
|
8016 |
global $CFG;
|
|
|
8017 |
|
|
|
8018 |
static $currentlocale = ''; // Last locale caching.
|
|
|
8019 |
|
|
|
8020 |
$oldlocale = $currentlocale;
|
|
|
8021 |
|
|
|
8022 |
// The priority is the same as in get_string() - parameter, config, course, session, user, global language.
|
|
|
8023 |
if (!empty($locale)) {
|
|
|
8024 |
$currentlocale = $locale;
|
|
|
8025 |
} else {
|
|
|
8026 |
$currentlocale = moodle_getlocale();
|
|
|
8027 |
}
|
|
|
8028 |
|
|
|
8029 |
// Do nothing if locale already set up.
|
|
|
8030 |
if ($oldlocale == $currentlocale) {
|
|
|
8031 |
return;
|
|
|
8032 |
}
|
|
|
8033 |
|
|
|
8034 |
// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
|
|
|
8035 |
// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
|
|
|
8036 |
// Some day, numeric, monetary and other categories should be set too, I think. :-/.
|
|
|
8037 |
|
|
|
8038 |
// Get current values.
|
|
|
8039 |
$monetary= setlocale (LC_MONETARY, 0);
|
|
|
8040 |
$numeric = setlocale (LC_NUMERIC, 0);
|
|
|
8041 |
$ctype = setlocale (LC_CTYPE, 0);
|
|
|
8042 |
if ($CFG->ostype != 'WINDOWS') {
|
|
|
8043 |
$messages= setlocale (LC_MESSAGES, 0);
|
|
|
8044 |
}
|
|
|
8045 |
// Set locale to all.
|
|
|
8046 |
$result = setlocale (LC_ALL, $currentlocale);
|
|
|
8047 |
// If setting of locale fails try the other utf8 or utf-8 variant,
|
|
|
8048 |
// some operating systems support both (Debian), others just one (OSX).
|
|
|
8049 |
if ($result === false) {
|
|
|
8050 |
if (stripos($currentlocale, '.UTF-8') !== false) {
|
|
|
8051 |
$newlocale = str_ireplace('.UTF-8', '.UTF8', $currentlocale);
|
|
|
8052 |
setlocale (LC_ALL, $newlocale);
|
|
|
8053 |
} else if (stripos($currentlocale, '.UTF8') !== false) {
|
|
|
8054 |
$newlocale = str_ireplace('.UTF8', '.UTF-8', $currentlocale);
|
|
|
8055 |
setlocale (LC_ALL, $newlocale);
|
|
|
8056 |
}
|
|
|
8057 |
}
|
|
|
8058 |
// Set old values.
|
|
|
8059 |
setlocale (LC_MONETARY, $monetary);
|
|
|
8060 |
setlocale (LC_NUMERIC, $numeric);
|
|
|
8061 |
if ($CFG->ostype != 'WINDOWS') {
|
|
|
8062 |
setlocale (LC_MESSAGES, $messages);
|
|
|
8063 |
}
|
|
|
8064 |
if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') {
|
|
|
8065 |
// To workaround a well-known PHP problem with Turkish letter Ii.
|
|
|
8066 |
setlocale (LC_CTYPE, $ctype);
|
|
|
8067 |
}
|
|
|
8068 |
}
|
|
|
8069 |
|
|
|
8070 |
/**
|
|
|
8071 |
* Count words in a string.
|
|
|
8072 |
*
|
|
|
8073 |
* Words are defined as things between whitespace.
|
|
|
8074 |
*
|
|
|
8075 |
* @category string
|
|
|
8076 |
* @param string $string The text to be searched for words. May be HTML.
|
|
|
8077 |
* @param int|null $format
|
|
|
8078 |
* @return int The count of words in the specified string
|
|
|
8079 |
*/
|
|
|
8080 |
function count_words($string, $format = null) {
|
|
|
8081 |
// Before stripping tags, add a space after the close tag of anything that is not obviously inline.
|
|
|
8082 |
// Also, br is a special case because it definitely delimits a word, but has no close tag.
|
|
|
8083 |
$string = preg_replace('~
|
|
|
8084 |
( # Capture the tag we match.
|
|
|
8085 |
</ # Start of close tag.
|
|
|
8086 |
(?! # Do not match any of these specific close tag names.
|
|
|
8087 |
a> | b> | del> | em> | i> |
|
|
|
8088 |
ins> | s> | small> | span> |
|
|
|
8089 |
strong> | sub> | sup> | u>
|
|
|
8090 |
)
|
|
|
8091 |
\w+ # But, apart from those execptions, match any tag name.
|
|
|
8092 |
> # End of close tag.
|
|
|
8093 |
|
|
|
|
8094 |
<br> | <br\s*/> # Special cases that are not close tags.
|
|
|
8095 |
)
|
|
|
8096 |
~x', '$1 ', $string); // Add a space after the close tag.
|
|
|
8097 |
if ($format !== null && $format != FORMAT_PLAIN) {
|
|
|
8098 |
// Match the usual text cleaning before display.
|
|
|
8099 |
// Ideally we should apply multilang filter only here, other filters might add extra text.
|
|
|
8100 |
$string = format_text($string, $format, ['filter' => false, 'noclean' => false, 'para' => false]);
|
|
|
8101 |
}
|
|
|
8102 |
// Now remove HTML tags.
|
|
|
8103 |
$string = strip_tags($string);
|
|
|
8104 |
// Decode HTML entities.
|
|
|
8105 |
$string = html_entity_decode($string, ENT_COMPAT);
|
|
|
8106 |
|
|
|
8107 |
// Now, the word count is the number of blocks of characters separated
|
|
|
8108 |
// by any sort of space. That seems to be the definition used by all other systems.
|
|
|
8109 |
// To be precise about what is considered to separate words:
|
|
|
8110 |
// * Anything that Unicode considers a 'Separator'
|
|
|
8111 |
// * Anything that Unicode considers a 'Control character'
|
|
|
8112 |
// * An em- or en- dash.
|
|
|
8113 |
return count(preg_split('~[\p{Z}\p{Cc}—–]+~u', $string, -1, PREG_SPLIT_NO_EMPTY));
|
|
|
8114 |
}
|
|
|
8115 |
|
|
|
8116 |
/**
|
|
|
8117 |
* Count letters in a string.
|
|
|
8118 |
*
|
|
|
8119 |
* Letters are defined as chars not in tags and different from whitespace.
|
|
|
8120 |
*
|
|
|
8121 |
* @category string
|
|
|
8122 |
* @param string $string The text to be searched for letters. May be HTML.
|
|
|
8123 |
* @param int|null $format
|
|
|
8124 |
* @return int The count of letters in the specified text.
|
|
|
8125 |
*/
|
|
|
8126 |
function count_letters($string, $format = null) {
|
|
|
8127 |
if ($format !== null && $format != FORMAT_PLAIN) {
|
|
|
8128 |
// Match the usual text cleaning before display.
|
|
|
8129 |
// Ideally we should apply multilang filter only here, other filters might add extra text.
|
|
|
8130 |
$string = format_text($string, $format, ['filter' => false, 'noclean' => false, 'para' => false]);
|
|
|
8131 |
}
|
|
|
8132 |
$string = strip_tags($string); // Tags are out now.
|
|
|
8133 |
$string = html_entity_decode($string, ENT_COMPAT);
|
|
|
8134 |
$string = preg_replace('/[[:space:]]*/', '', $string); // Whitespace are out now.
|
|
|
8135 |
|
|
|
8136 |
return core_text::strlen($string);
|
|
|
8137 |
}
|
|
|
8138 |
|
|
|
8139 |
/**
|
|
|
8140 |
* Generate and return a random string of the specified length.
|
|
|
8141 |
*
|
|
|
8142 |
* @param int $length The length of the string to be created.
|
|
|
8143 |
* @return string
|
|
|
8144 |
*/
|
|
|
8145 |
function random_string($length=15) {
|
|
|
8146 |
$randombytes = random_bytes($length);
|
|
|
8147 |
$pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
|
8148 |
$pool .= 'abcdefghijklmnopqrstuvwxyz';
|
|
|
8149 |
$pool .= '0123456789';
|
|
|
8150 |
$poollen = strlen($pool);
|
|
|
8151 |
$string = '';
|
|
|
8152 |
for ($i = 0; $i < $length; $i++) {
|
|
|
8153 |
$rand = ord($randombytes[$i]);
|
|
|
8154 |
$string .= substr($pool, ($rand%($poollen)), 1);
|
|
|
8155 |
}
|
|
|
8156 |
return $string;
|
|
|
8157 |
}
|
|
|
8158 |
|
|
|
8159 |
/**
|
|
|
8160 |
* Generate a complex random string (useful for md5 salts)
|
|
|
8161 |
*
|
|
|
8162 |
* This function is based on the above {@link random_string()} however it uses a
|
|
|
8163 |
* larger pool of characters and generates a string between 24 and 32 characters
|
|
|
8164 |
*
|
|
|
8165 |
* @param int $length Optional if set generates a string to exactly this length
|
|
|
8166 |
* @return string
|
|
|
8167 |
*/
|
|
|
8168 |
function complex_random_string($length=null) {
|
|
|
8169 |
$pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
8170 |
$pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
|
|
|
8171 |
$poollen = strlen($pool);
|
|
|
8172 |
if ($length===null) {
|
|
|
8173 |
$length = floor(rand(24, 32));
|
|
|
8174 |
}
|
|
|
8175 |
$randombytes = random_bytes($length);
|
|
|
8176 |
$string = '';
|
|
|
8177 |
for ($i = 0; $i < $length; $i++) {
|
|
|
8178 |
$rand = ord($randombytes[$i]);
|
|
|
8179 |
$string .= $pool[($rand%$poollen)];
|
|
|
8180 |
}
|
|
|
8181 |
return $string;
|
|
|
8182 |
}
|
|
|
8183 |
|
|
|
8184 |
/**
|
|
|
8185 |
* Given some text (which may contain HTML) and an ideal length,
|
|
|
8186 |
* this function truncates the text neatly on a word boundary if possible
|
|
|
8187 |
*
|
|
|
8188 |
* @category string
|
|
|
8189 |
* @param string $text text to be shortened
|
|
|
8190 |
* @param int $ideal ideal string length
|
|
|
8191 |
* @param boolean $exact if false, $text will not be cut mid-word
|
|
|
8192 |
* @param string $ending The string to append if the passed string is truncated
|
|
|
8193 |
* @return string $truncate shortened string
|
|
|
8194 |
*/
|
|
|
8195 |
function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
|
|
|
8196 |
// If the plain text is shorter than the maximum length, return the whole text.
|
|
|
8197 |
if (core_text::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
|
|
|
8198 |
return $text;
|
|
|
8199 |
}
|
|
|
8200 |
|
|
|
8201 |
// Splits on HTML tags. Each open/close/empty tag will be the first thing
|
|
|
8202 |
// and only tag in its 'line'.
|
|
|
8203 |
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
|
|
|
8204 |
|
|
|
8205 |
$totallength = core_text::strlen($ending);
|
|
|
8206 |
$truncate = '';
|
|
|
8207 |
|
|
|
8208 |
// This array stores information about open and close tags and their position
|
|
|
8209 |
// in the truncated string. Each item in the array is an object with fields
|
|
|
8210 |
// ->open (true if open), ->tag (tag name in lower case), and ->pos
|
|
|
8211 |
// (byte position in truncated text).
|
|
|
8212 |
$tagdetails = array();
|
|
|
8213 |
|
|
|
8214 |
foreach ($lines as $linematchings) {
|
|
|
8215 |
// If there is any html-tag in this line, handle it and add it (uncounted) to the output.
|
|
|
8216 |
if (!empty($linematchings[1])) {
|
|
|
8217 |
// If it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>).
|
|
|
8218 |
if (!preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $linematchings[1])) {
|
|
|
8219 |
if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $linematchings[1], $tagmatchings)) {
|
|
|
8220 |
// Record closing tag.
|
|
|
8221 |
$tagdetails[] = (object) array(
|
|
|
8222 |
'open' => false,
|
|
|
8223 |
'tag' => core_text::strtolower($tagmatchings[1]),
|
|
|
8224 |
'pos' => core_text::strlen($truncate),
|
|
|
8225 |
);
|
|
|
8226 |
|
|
|
8227 |
} else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $linematchings[1], $tagmatchings)) {
|
|
|
8228 |
// Record opening tag.
|
|
|
8229 |
$tagdetails[] = (object) array(
|
|
|
8230 |
'open' => true,
|
|
|
8231 |
'tag' => core_text::strtolower($tagmatchings[1]),
|
|
|
8232 |
'pos' => core_text::strlen($truncate),
|
|
|
8233 |
);
|
|
|
8234 |
} else if (preg_match('/^<!--\[if\s.*?\]>$/s', $linematchings[1], $tagmatchings)) {
|
|
|
8235 |
$tagdetails[] = (object) array(
|
|
|
8236 |
'open' => true,
|
|
|
8237 |
'tag' => core_text::strtolower('if'),
|
|
|
8238 |
'pos' => core_text::strlen($truncate),
|
|
|
8239 |
);
|
|
|
8240 |
} else if (preg_match('/^<!--<!\[endif\]-->$/s', $linematchings[1], $tagmatchings)) {
|
|
|
8241 |
$tagdetails[] = (object) array(
|
|
|
8242 |
'open' => false,
|
|
|
8243 |
'tag' => core_text::strtolower('if'),
|
|
|
8244 |
'pos' => core_text::strlen($truncate),
|
|
|
8245 |
);
|
|
|
8246 |
}
|
|
|
8247 |
}
|
|
|
8248 |
// Add html-tag to $truncate'd text.
|
|
|
8249 |
$truncate .= $linematchings[1];
|
|
|
8250 |
}
|
|
|
8251 |
|
|
|
8252 |
// Calculate the length of the plain text part of the line; handle entities as one character.
|
|
|
8253 |
$contentlength = core_text::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $linematchings[2]));
|
|
|
8254 |
if ($totallength + $contentlength > $ideal) {
|
|
|
8255 |
// The number of characters which are left.
|
|
|
8256 |
$left = $ideal - $totallength;
|
|
|
8257 |
$entitieslength = 0;
|
|
|
8258 |
// Search for html entities.
|
|
|
8259 |
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $linematchings[2], $entities, PREG_OFFSET_CAPTURE)) {
|
|
|
8260 |
// Calculate the real length of all entities in the legal range.
|
|
|
8261 |
foreach ($entities[0] as $entity) {
|
|
|
8262 |
if ($entity[1]+1-$entitieslength <= $left) {
|
|
|
8263 |
$left--;
|
|
|
8264 |
$entitieslength += core_text::strlen($entity[0]);
|
|
|
8265 |
} else {
|
|
|
8266 |
// No more characters left.
|
|
|
8267 |
break;
|
|
|
8268 |
}
|
|
|
8269 |
}
|
|
|
8270 |
}
|
|
|
8271 |
$breakpos = $left + $entitieslength;
|
|
|
8272 |
|
|
|
8273 |
// If the words shouldn't be cut in the middle...
|
|
|
8274 |
if (!$exact) {
|
|
|
8275 |
// Search the last occurence of a space.
|
|
|
8276 |
for (; $breakpos > 0; $breakpos--) {
|
|
|
8277 |
if ($char = core_text::substr($linematchings[2], $breakpos, 1)) {
|
|
|
8278 |
if ($char === '.' or $char === ' ') {
|
|
|
8279 |
$breakpos += 1;
|
|
|
8280 |
break;
|
|
|
8281 |
} else if (strlen($char) > 2) {
|
|
|
8282 |
// Chinese/Japanese/Korean text can be truncated at any UTF-8 character boundary.
|
|
|
8283 |
$breakpos += 1;
|
|
|
8284 |
break;
|
|
|
8285 |
}
|
|
|
8286 |
}
|
|
|
8287 |
}
|
|
|
8288 |
}
|
|
|
8289 |
if ($breakpos == 0) {
|
|
|
8290 |
// This deals with the test_shorten_text_no_spaces case.
|
|
|
8291 |
$breakpos = $left + $entitieslength;
|
|
|
8292 |
} else if ($breakpos > $left + $entitieslength) {
|
|
|
8293 |
// This deals with the previous for loop breaking on the first char.
|
|
|
8294 |
$breakpos = $left + $entitieslength;
|
|
|
8295 |
}
|
|
|
8296 |
|
|
|
8297 |
$truncate .= core_text::substr($linematchings[2], 0, $breakpos);
|
|
|
8298 |
// Maximum length is reached, so get off the loop.
|
|
|
8299 |
break;
|
|
|
8300 |
} else {
|
|
|
8301 |
$truncate .= $linematchings[2];
|
|
|
8302 |
$totallength += $contentlength;
|
|
|
8303 |
}
|
|
|
8304 |
|
|
|
8305 |
// If the maximum length is reached, get off the loop.
|
|
|
8306 |
if ($totallength >= $ideal) {
|
|
|
8307 |
break;
|
|
|
8308 |
}
|
|
|
8309 |
}
|
|
|
8310 |
|
|
|
8311 |
// Add the defined ending to the text.
|
|
|
8312 |
$truncate .= $ending;
|
|
|
8313 |
|
|
|
8314 |
// Now calculate the list of open html tags based on the truncate position.
|
|
|
8315 |
$opentags = array();
|
|
|
8316 |
foreach ($tagdetails as $taginfo) {
|
|
|
8317 |
if ($taginfo->open) {
|
|
|
8318 |
// Add tag to the beginning of $opentags list.
|
|
|
8319 |
array_unshift($opentags, $taginfo->tag);
|
|
|
8320 |
} else {
|
|
|
8321 |
// Can have multiple exact same open tags, close the last one.
|
|
|
8322 |
$pos = array_search($taginfo->tag, array_reverse($opentags, true));
|
|
|
8323 |
if ($pos !== false) {
|
|
|
8324 |
unset($opentags[$pos]);
|
|
|
8325 |
}
|
|
|
8326 |
}
|
|
|
8327 |
}
|
|
|
8328 |
|
|
|
8329 |
// Close all unclosed html-tags.
|
|
|
8330 |
foreach ($opentags as $tag) {
|
|
|
8331 |
if ($tag === 'if') {
|
|
|
8332 |
$truncate .= '<!--<![endif]-->';
|
|
|
8333 |
} else {
|
|
|
8334 |
$truncate .= '</' . $tag . '>';
|
|
|
8335 |
}
|
|
|
8336 |
}
|
|
|
8337 |
|
|
|
8338 |
return $truncate;
|
|
|
8339 |
}
|
|
|
8340 |
|
|
|
8341 |
/**
|
|
|
8342 |
* Shortens a given filename by removing characters positioned after the ideal string length.
|
|
|
8343 |
* When the filename is too long, the file cannot be created on the filesystem due to exceeding max byte size.
|
|
|
8344 |
* Limiting the filename to a certain size (considering multibyte characters) will prevent this.
|
|
|
8345 |
*
|
|
|
8346 |
* @param string $filename file name
|
|
|
8347 |
* @param int $length ideal string length
|
|
|
8348 |
* @param bool $includehash Whether to include a file hash in the shortened version. This ensures uniqueness.
|
|
|
8349 |
* @return string $shortened shortened file name
|
|
|
8350 |
*/
|
|
|
8351 |
function shorten_filename($filename, $length = MAX_FILENAME_SIZE, $includehash = false) {
|
|
|
8352 |
$shortened = $filename;
|
|
|
8353 |
// Extract a part of the filename if it's char size exceeds the ideal string length.
|
|
|
8354 |
if (core_text::strlen($filename) > $length) {
|
|
|
8355 |
// Exclude extension if present in filename.
|
|
|
8356 |
$mimetypes = get_mimetypes_array();
|
|
|
8357 |
$extension = pathinfo($filename, PATHINFO_EXTENSION);
|
|
|
8358 |
if ($extension && !empty($mimetypes[$extension])) {
|
|
|
8359 |
$basename = pathinfo($filename, PATHINFO_FILENAME);
|
|
|
8360 |
$hash = empty($includehash) ? '' : ' - ' . substr(sha1($basename), 0, 10);
|
|
|
8361 |
$shortened = core_text::substr($basename, 0, $length - strlen($hash)) . $hash;
|
|
|
8362 |
$shortened .= '.' . $extension;
|
|
|
8363 |
} else {
|
|
|
8364 |
$hash = empty($includehash) ? '' : ' - ' . substr(sha1($filename), 0, 10);
|
|
|
8365 |
$shortened = core_text::substr($filename, 0, $length - strlen($hash)) . $hash;
|
|
|
8366 |
}
|
|
|
8367 |
}
|
|
|
8368 |
return $shortened;
|
|
|
8369 |
}
|
|
|
8370 |
|
|
|
8371 |
/**
|
|
|
8372 |
* Shortens a given array of filenames by removing characters positioned after the ideal string length.
|
|
|
8373 |
*
|
|
|
8374 |
* @param array $path The paths to reduce the length.
|
|
|
8375 |
* @param int $length Ideal string length
|
|
|
8376 |
* @param bool $includehash Whether to include a file hash in the shortened version. This ensures uniqueness.
|
|
|
8377 |
* @return array $result Shortened paths in array.
|
|
|
8378 |
*/
|
|
|
8379 |
function shorten_filenames(array $path, $length = MAX_FILENAME_SIZE, $includehash = false) {
|
|
|
8380 |
$result = null;
|
|
|
8381 |
|
|
|
8382 |
$result = array_reduce($path, function($carry, $singlepath) use ($length, $includehash) {
|
|
|
8383 |
$carry[] = shorten_filename($singlepath, $length, $includehash);
|
|
|
8384 |
return $carry;
|
|
|
8385 |
}, []);
|
|
|
8386 |
|
|
|
8387 |
return $result;
|
|
|
8388 |
}
|
|
|
8389 |
|
|
|
8390 |
/**
|
|
|
8391 |
* Given dates in seconds, how many weeks is the date from startdate
|
|
|
8392 |
* The first week is 1, the second 2 etc ...
|
|
|
8393 |
*
|
|
|
8394 |
* @param int $startdate Timestamp for the start date
|
|
|
8395 |
* @param int $thedate Timestamp for the end date
|
|
|
8396 |
* @return string
|
|
|
8397 |
*/
|
|
|
8398 |
function getweek ($startdate, $thedate) {
|
|
|
8399 |
if ($thedate < $startdate) {
|
|
|
8400 |
return 0;
|
|
|
8401 |
}
|
|
|
8402 |
|
|
|
8403 |
return floor(($thedate - $startdate) / WEEKSECS) + 1;
|
|
|
8404 |
}
|
|
|
8405 |
|
|
|
8406 |
/**
|
|
|
8407 |
* Returns a randomly generated password of length $maxlen. inspired by
|
|
|
8408 |
*
|
|
|
8409 |
* {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
|
|
|
8410 |
* {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
|
|
|
8411 |
*
|
|
|
8412 |
* @param int $maxlen The maximum size of the password being generated.
|
|
|
8413 |
* @return string
|
|
|
8414 |
*/
|
|
|
8415 |
function generate_password($maxlen=10) {
|
|
|
8416 |
global $CFG;
|
|
|
8417 |
|
|
|
8418 |
if (empty($CFG->passwordpolicy)) {
|
|
|
8419 |
$fillers = PASSWORD_DIGITS;
|
|
|
8420 |
$wordlist = file($CFG->wordlist);
|
|
|
8421 |
$word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
|
|
|
8422 |
$word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
|
|
|
8423 |
$filler1 = $fillers[rand(0, strlen($fillers) - 1)];
|
|
|
8424 |
$password = $word1 . $filler1 . $word2;
|
|
|
8425 |
} else {
|
|
|
8426 |
$minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
|
|
|
8427 |
$digits = $CFG->minpassworddigits;
|
|
|
8428 |
$lower = $CFG->minpasswordlower;
|
|
|
8429 |
$upper = $CFG->minpasswordupper;
|
|
|
8430 |
$nonalphanum = $CFG->minpasswordnonalphanum;
|
|
|
8431 |
$total = $lower + $upper + $digits + $nonalphanum;
|
|
|
8432 |
// Var minlength should be the greater one of the two ( $minlen and $total ).
|
|
|
8433 |
$minlen = $minlen < $total ? $total : $minlen;
|
|
|
8434 |
// Var maxlen can never be smaller than minlen.
|
|
|
8435 |
$maxlen = $minlen > $maxlen ? $minlen : $maxlen;
|
|
|
8436 |
$additional = $maxlen - $total;
|
|
|
8437 |
|
|
|
8438 |
// Make sure we have enough characters to fulfill
|
|
|
8439 |
// complexity requirements.
|
|
|
8440 |
$passworddigits = PASSWORD_DIGITS;
|
|
|
8441 |
while ($digits > strlen($passworddigits)) {
|
|
|
8442 |
$passworddigits .= PASSWORD_DIGITS;
|
|
|
8443 |
}
|
|
|
8444 |
$passwordlower = PASSWORD_LOWER;
|
|
|
8445 |
while ($lower > strlen($passwordlower)) {
|
|
|
8446 |
$passwordlower .= PASSWORD_LOWER;
|
|
|
8447 |
}
|
|
|
8448 |
$passwordupper = PASSWORD_UPPER;
|
|
|
8449 |
while ($upper > strlen($passwordupper)) {
|
|
|
8450 |
$passwordupper .= PASSWORD_UPPER;
|
|
|
8451 |
}
|
|
|
8452 |
$passwordnonalphanum = PASSWORD_NONALPHANUM;
|
|
|
8453 |
while ($nonalphanum > strlen($passwordnonalphanum)) {
|
|
|
8454 |
$passwordnonalphanum .= PASSWORD_NONALPHANUM;
|
|
|
8455 |
}
|
|
|
8456 |
|
|
|
8457 |
// Now mix and shuffle it all.
|
|
|
8458 |
$password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
|
|
|
8459 |
substr(str_shuffle ($passwordupper), 0, $upper) .
|
|
|
8460 |
substr(str_shuffle ($passworddigits), 0, $digits) .
|
|
|
8461 |
substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
|
|
|
8462 |
substr(str_shuffle ($passwordlower .
|
|
|
8463 |
$passwordupper .
|
|
|
8464 |
$passworddigits .
|
|
|
8465 |
$passwordnonalphanum), 0 , $additional));
|
|
|
8466 |
}
|
|
|
8467 |
|
|
|
8468 |
return substr ($password, 0, $maxlen);
|
|
|
8469 |
}
|
|
|
8470 |
|
|
|
8471 |
/**
|
|
|
8472 |
* Given a float, prints it nicely.
|
|
|
8473 |
* Localized floats must not be used in calculations!
|
|
|
8474 |
*
|
|
|
8475 |
* The stripzeros feature is intended for making numbers look nicer in small
|
|
|
8476 |
* areas where it is not necessary to indicate the degree of accuracy by showing
|
|
|
8477 |
* ending zeros. If you turn it on with $decimalpoints set to 3, for example,
|
|
|
8478 |
* then it will display '5.4' instead of '5.400' or '5' instead of '5.000'.
|
|
|
8479 |
*
|
|
|
8480 |
* @param float $float The float to print
|
|
|
8481 |
* @param int $decimalpoints The number of decimal places to print. -1 is a special value for auto detect (full precision).
|
|
|
8482 |
* @param bool $localized use localized decimal separator
|
|
|
8483 |
* @param bool $stripzeros If true, removes final zeros after decimal point. It will be ignored and the trailing zeros after
|
|
|
8484 |
* the decimal point are always striped if $decimalpoints is -1.
|
|
|
8485 |
* @return string locale float
|
|
|
8486 |
*/
|
|
|
8487 |
function format_float($float, $decimalpoints=1, $localized=true, $stripzeros=false) {
|
|
|
8488 |
if (is_null($float)) {
|
|
|
8489 |
return '';
|
|
|
8490 |
}
|
|
|
8491 |
if ($localized) {
|
|
|
8492 |
$separator = get_string('decsep', 'langconfig');
|
|
|
8493 |
} else {
|
|
|
8494 |
$separator = '.';
|
|
|
8495 |
}
|
|
|
8496 |
if ($decimalpoints == -1) {
|
|
|
8497 |
// The following counts the number of decimals.
|
|
|
8498 |
// It is safe as both floatval() and round() functions have same behaviour when non-numeric values are provided.
|
|
|
8499 |
$floatval = floatval($float);
|
|
|
8500 |
for ($decimalpoints = 0; $floatval != round($float, $decimalpoints); $decimalpoints++);
|
|
|
8501 |
}
|
|
|
8502 |
|
|
|
8503 |
$result = number_format($float, $decimalpoints, $separator, '');
|
|
|
8504 |
if ($stripzeros && $decimalpoints > 0) {
|
|
|
8505 |
// Remove zeros and final dot if not needed.
|
|
|
8506 |
// However, only do this if there is a decimal point!
|
|
|
8507 |
$result = preg_replace('~(' . preg_quote($separator, '~') . ')?0+$~', '', $result);
|
|
|
8508 |
}
|
|
|
8509 |
return $result;
|
|
|
8510 |
}
|
|
|
8511 |
|
|
|
8512 |
/**
|
|
|
8513 |
* Converts locale specific floating point/comma number back to standard PHP float value
|
|
|
8514 |
* Do NOT try to do any math operations before this conversion on any user submitted floats!
|
|
|
8515 |
*
|
|
|
8516 |
* @param string $localefloat locale aware float representation
|
|
|
8517 |
* @param bool $strict If true, then check the input and return false if it is not a valid number.
|
|
|
8518 |
* @return mixed float|bool - false or the parsed float.
|
|
|
8519 |
*/
|
|
|
8520 |
function unformat_float($localefloat, $strict = false) {
|
|
|
8521 |
$localefloat = trim((string)$localefloat);
|
|
|
8522 |
|
|
|
8523 |
if ($localefloat == '') {
|
|
|
8524 |
return null;
|
|
|
8525 |
}
|
|
|
8526 |
|
|
|
8527 |
$localefloat = str_replace(' ', '', $localefloat); // No spaces - those might be used as thousand separators.
|
|
|
8528 |
$localefloat = str_replace(get_string('decsep', 'langconfig'), '.', $localefloat);
|
|
|
8529 |
|
|
|
8530 |
if ($strict && !is_numeric($localefloat)) {
|
|
|
8531 |
return false;
|
|
|
8532 |
}
|
|
|
8533 |
|
|
|
8534 |
return (float)$localefloat;
|
|
|
8535 |
}
|
|
|
8536 |
|
|
|
8537 |
/**
|
|
|
8538 |
* Given a simple array, this shuffles it up just like shuffle()
|
|
|
8539 |
* Unlike PHP's shuffle() this function works on any machine.
|
|
|
8540 |
*
|
|
|
8541 |
* @param array $array The array to be rearranged
|
|
|
8542 |
* @return array
|
|
|
8543 |
*/
|
|
|
8544 |
function swapshuffle($array) {
|
|
|
8545 |
|
|
|
8546 |
$last = count($array) - 1;
|
|
|
8547 |
for ($i = 0; $i <= $last; $i++) {
|
|
|
8548 |
$from = rand(0, $last);
|
|
|
8549 |
$curr = $array[$i];
|
|
|
8550 |
$array[$i] = $array[$from];
|
|
|
8551 |
$array[$from] = $curr;
|
|
|
8552 |
}
|
|
|
8553 |
return $array;
|
|
|
8554 |
}
|
|
|
8555 |
|
|
|
8556 |
/**
|
|
|
8557 |
* Like {@link swapshuffle()}, but works on associative arrays
|
|
|
8558 |
*
|
|
|
8559 |
* @param array $array The associative array to be rearranged
|
|
|
8560 |
* @return array
|
|
|
8561 |
*/
|
|
|
8562 |
function swapshuffle_assoc($array) {
|
|
|
8563 |
|
|
|
8564 |
$newarray = array();
|
|
|
8565 |
$newkeys = swapshuffle(array_keys($array));
|
|
|
8566 |
|
|
|
8567 |
foreach ($newkeys as $newkey) {
|
|
|
8568 |
$newarray[$newkey] = $array[$newkey];
|
|
|
8569 |
}
|
|
|
8570 |
return $newarray;
|
|
|
8571 |
}
|
|
|
8572 |
|
|
|
8573 |
/**
|
|
|
8574 |
* Given an arbitrary array, and a number of draws,
|
|
|
8575 |
* this function returns an array with that amount
|
|
|
8576 |
* of items. The indexes are retained.
|
|
|
8577 |
*
|
|
|
8578 |
* @todo Finish documenting this function
|
|
|
8579 |
*
|
|
|
8580 |
* @param array $array
|
|
|
8581 |
* @param int $draws
|
|
|
8582 |
* @return array
|
|
|
8583 |
*/
|
|
|
8584 |
function draw_rand_array($array, $draws) {
|
|
|
8585 |
|
|
|
8586 |
$return = array();
|
|
|
8587 |
|
|
|
8588 |
$last = count($array);
|
|
|
8589 |
|
|
|
8590 |
if ($draws > $last) {
|
|
|
8591 |
$draws = $last;
|
|
|
8592 |
}
|
|
|
8593 |
|
|
|
8594 |
while ($draws > 0) {
|
|
|
8595 |
$last--;
|
|
|
8596 |
|
|
|
8597 |
$keys = array_keys($array);
|
|
|
8598 |
$rand = rand(0, $last);
|
|
|
8599 |
|
|
|
8600 |
$return[$keys[$rand]] = $array[$keys[$rand]];
|
|
|
8601 |
unset($array[$keys[$rand]]);
|
|
|
8602 |
|
|
|
8603 |
$draws--;
|
|
|
8604 |
}
|
|
|
8605 |
|
|
|
8606 |
return $return;
|
|
|
8607 |
}
|
|
|
8608 |
|
|
|
8609 |
/**
|
|
|
8610 |
* Calculate the difference between two microtimes
|
|
|
8611 |
*
|
|
|
8612 |
* @param string $a The first Microtime
|
|
|
8613 |
* @param string $b The second Microtime
|
|
|
8614 |
* @return string
|
|
|
8615 |
*/
|
|
|
8616 |
function microtime_diff($a, $b) {
|
|
|
8617 |
list($adec, $asec) = explode(' ', $a);
|
|
|
8618 |
list($bdec, $bsec) = explode(' ', $b);
|
|
|
8619 |
return $bsec - $asec + $bdec - $adec;
|
|
|
8620 |
}
|
|
|
8621 |
|
|
|
8622 |
/**
|
|
|
8623 |
* Given a list (eg a,b,c,d,e) this function returns
|
|
|
8624 |
* an array of 1->a, 2->b, 3->c etc
|
|
|
8625 |
*
|
|
|
8626 |
* @param string $list The string to explode into array bits
|
|
|
8627 |
* @param string $separator The separator used within the list string
|
|
|
8628 |
* @return array The now assembled array
|
|
|
8629 |
*/
|
|
|
8630 |
function make_menu_from_list($list, $separator=',') {
|
|
|
8631 |
|
|
|
8632 |
$array = array_reverse(explode($separator, $list), true);
|
|
|
8633 |
foreach ($array as $key => $item) {
|
|
|
8634 |
$outarray[$key+1] = trim($item);
|
|
|
8635 |
}
|
|
|
8636 |
return $outarray;
|
|
|
8637 |
}
|
|
|
8638 |
|
|
|
8639 |
/**
|
|
|
8640 |
* Creates an array that represents all the current grades that
|
|
|
8641 |
* can be chosen using the given grading type.
|
|
|
8642 |
*
|
|
|
8643 |
* Negative numbers
|
|
|
8644 |
* are scales, zero is no grade, and positive numbers are maximum
|
|
|
8645 |
* grades.
|
|
|
8646 |
*
|
|
|
8647 |
* @todo Finish documenting this function or better deprecated this completely!
|
|
|
8648 |
*
|
|
|
8649 |
* @param int $gradingtype
|
|
|
8650 |
* @return array
|
|
|
8651 |
*/
|
|
|
8652 |
function make_grades_menu($gradingtype) {
|
|
|
8653 |
global $DB;
|
|
|
8654 |
|
|
|
8655 |
$grades = array();
|
|
|
8656 |
if ($gradingtype < 0) {
|
|
|
8657 |
if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
|
|
|
8658 |
return make_menu_from_list($scale->scale);
|
|
|
8659 |
}
|
|
|
8660 |
} else if ($gradingtype > 0) {
|
|
|
8661 |
for ($i=$gradingtype; $i>=0; $i--) {
|
|
|
8662 |
$grades[$i] = $i .' / '. $gradingtype;
|
|
|
8663 |
}
|
|
|
8664 |
return $grades;
|
|
|
8665 |
}
|
|
|
8666 |
return $grades;
|
|
|
8667 |
}
|
|
|
8668 |
|
|
|
8669 |
/**
|
|
|
8670 |
* make_unique_id_code
|
|
|
8671 |
*
|
|
|
8672 |
* @todo Finish documenting this function
|
|
|
8673 |
*
|
|
|
8674 |
* @uses $_SERVER
|
|
|
8675 |
* @param string $extra Extra string to append to the end of the code
|
|
|
8676 |
* @return string
|
|
|
8677 |
*/
|
|
|
8678 |
function make_unique_id_code($extra = '') {
|
|
|
8679 |
|
|
|
8680 |
$hostname = 'unknownhost';
|
|
|
8681 |
if (!empty($_SERVER['HTTP_HOST'])) {
|
|
|
8682 |
$hostname = $_SERVER['HTTP_HOST'];
|
|
|
8683 |
} else if (!empty($_ENV['HTTP_HOST'])) {
|
|
|
8684 |
$hostname = $_ENV['HTTP_HOST'];
|
|
|
8685 |
} else if (!empty($_SERVER['SERVER_NAME'])) {
|
|
|
8686 |
$hostname = $_SERVER['SERVER_NAME'];
|
|
|
8687 |
} else if (!empty($_ENV['SERVER_NAME'])) {
|
|
|
8688 |
$hostname = $_ENV['SERVER_NAME'];
|
|
|
8689 |
}
|
|
|
8690 |
|
|
|
8691 |
$date = gmdate("ymdHis");
|
|
|
8692 |
|
|
|
8693 |
$random = random_string(6);
|
|
|
8694 |
|
|
|
8695 |
if ($extra) {
|
|
|
8696 |
return $hostname .'+'. $date .'+'. $random .'+'. $extra;
|
|
|
8697 |
} else {
|
|
|
8698 |
return $hostname .'+'. $date .'+'. $random;
|
|
|
8699 |
}
|
|
|
8700 |
}
|
|
|
8701 |
|
|
|
8702 |
|
|
|
8703 |
/**
|
|
|
8704 |
* Function to check the passed address is within the passed subnet
|
|
|
8705 |
*
|
|
|
8706 |
* The parameter is a comma separated string of subnet definitions.
|
|
|
8707 |
* Subnet strings can be in one of three formats:
|
|
|
8708 |
* 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
|
|
|
8709 |
* 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group)
|
|
|
8710 |
* 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
|
|
|
8711 |
* Code for type 1 modified from user posted comments by mediator at
|
|
|
8712 |
* {@link http://au.php.net/manual/en/function.ip2long.php}
|
|
|
8713 |
*
|
|
|
8714 |
* @param string $addr The address you are checking
|
|
|
8715 |
* @param string $subnetstr The string of subnet addresses
|
|
|
8716 |
* @param bool $checkallzeros The state to whether check for 0.0.0.0
|
|
|
8717 |
* @return bool
|
|
|
8718 |
*/
|
|
|
8719 |
function address_in_subnet($addr, $subnetstr, $checkallzeros = false) {
|
|
|
8720 |
|
|
|
8721 |
if ($addr == '0.0.0.0' && !$checkallzeros) {
|
|
|
8722 |
return false;
|
|
|
8723 |
}
|
|
|
8724 |
$subnets = explode(',', $subnetstr);
|
|
|
8725 |
$found = false;
|
|
|
8726 |
$addr = trim($addr);
|
|
|
8727 |
$addr = cleanremoteaddr($addr, false); // Normalise.
|
|
|
8728 |
if ($addr === null) {
|
|
|
8729 |
return false;
|
|
|
8730 |
}
|
|
|
8731 |
$addrparts = explode(':', $addr);
|
|
|
8732 |
|
|
|
8733 |
$ipv6 = strpos($addr, ':');
|
|
|
8734 |
|
|
|
8735 |
foreach ($subnets as $subnet) {
|
|
|
8736 |
$subnet = trim($subnet);
|
|
|
8737 |
if ($subnet === '') {
|
|
|
8738 |
continue;
|
|
|
8739 |
}
|
|
|
8740 |
|
|
|
8741 |
if (strpos($subnet, '/') !== false) {
|
|
|
8742 |
// 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn.
|
|
|
8743 |
list($ip, $mask) = explode('/', $subnet);
|
|
|
8744 |
$mask = trim($mask);
|
|
|
8745 |
if (!is_number($mask)) {
|
|
|
8746 |
continue; // Incorect mask number, eh?
|
|
|
8747 |
}
|
|
|
8748 |
$ip = cleanremoteaddr($ip, false); // Normalise.
|
|
|
8749 |
if ($ip === null) {
|
|
|
8750 |
continue;
|
|
|
8751 |
}
|
|
|
8752 |
if (strpos($ip, ':') !== false) {
|
|
|
8753 |
// IPv6.
|
|
|
8754 |
if (!$ipv6) {
|
|
|
8755 |
continue;
|
|
|
8756 |
}
|
|
|
8757 |
if ($mask > 128 or $mask < 0) {
|
|
|
8758 |
continue; // Nonsense.
|
|
|
8759 |
}
|
|
|
8760 |
if ($mask == 0) {
|
|
|
8761 |
return true; // Any address.
|
|
|
8762 |
}
|
|
|
8763 |
if ($mask == 128) {
|
|
|
8764 |
if ($ip === $addr) {
|
|
|
8765 |
return true;
|
|
|
8766 |
}
|
|
|
8767 |
continue;
|
|
|
8768 |
}
|
|
|
8769 |
$ipparts = explode(':', $ip);
|
|
|
8770 |
$modulo = $mask % 16;
|
|
|
8771 |
$ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
|
|
|
8772 |
$addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
|
|
|
8773 |
if (implode(':', $ipnet) === implode(':', $addrnet)) {
|
|
|
8774 |
if ($modulo == 0) {
|
|
|
8775 |
return true;
|
|
|
8776 |
}
|
|
|
8777 |
$pos = ($mask-$modulo)/16;
|
|
|
8778 |
$ipnet = hexdec($ipparts[$pos]);
|
|
|
8779 |
$addrnet = hexdec($addrparts[$pos]);
|
|
|
8780 |
$mask = 0xffff << (16 - $modulo);
|
|
|
8781 |
if (($addrnet & $mask) == ($ipnet & $mask)) {
|
|
|
8782 |
return true;
|
|
|
8783 |
}
|
|
|
8784 |
}
|
|
|
8785 |
|
|
|
8786 |
} else {
|
|
|
8787 |
// IPv4.
|
|
|
8788 |
if ($ipv6) {
|
|
|
8789 |
continue;
|
|
|
8790 |
}
|
|
|
8791 |
if ($mask > 32 or $mask < 0) {
|
|
|
8792 |
continue; // Nonsense.
|
|
|
8793 |
}
|
|
|
8794 |
if ($mask == 0) {
|
|
|
8795 |
return true;
|
|
|
8796 |
}
|
|
|
8797 |
if ($mask == 32) {
|
|
|
8798 |
if ($ip === $addr) {
|
|
|
8799 |
return true;
|
|
|
8800 |
}
|
|
|
8801 |
continue;
|
|
|
8802 |
}
|
|
|
8803 |
$mask = 0xffffffff << (32 - $mask);
|
|
|
8804 |
if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
|
|
|
8805 |
return true;
|
|
|
8806 |
}
|
|
|
8807 |
}
|
|
|
8808 |
|
|
|
8809 |
} else if (strpos($subnet, '-') !== false) {
|
|
|
8810 |
// 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy. A range of IP addresses in the last group.
|
|
|
8811 |
$parts = explode('-', $subnet);
|
|
|
8812 |
if (count($parts) != 2) {
|
|
|
8813 |
continue;
|
|
|
8814 |
}
|
|
|
8815 |
|
|
|
8816 |
if (strpos($subnet, ':') !== false) {
|
|
|
8817 |
// IPv6.
|
|
|
8818 |
if (!$ipv6) {
|
|
|
8819 |
continue;
|
|
|
8820 |
}
|
|
|
8821 |
$ipstart = cleanremoteaddr(trim($parts[0]), false); // Normalise.
|
|
|
8822 |
if ($ipstart === null) {
|
|
|
8823 |
continue;
|
|
|
8824 |
}
|
|
|
8825 |
$ipparts = explode(':', $ipstart);
|
|
|
8826 |
$start = hexdec(array_pop($ipparts));
|
|
|
8827 |
$ipparts[] = trim($parts[1]);
|
|
|
8828 |
$ipend = cleanremoteaddr(implode(':', $ipparts), false); // Normalise.
|
|
|
8829 |
if ($ipend === null) {
|
|
|
8830 |
continue;
|
|
|
8831 |
}
|
|
|
8832 |
$ipparts[7] = '';
|
|
|
8833 |
$ipnet = implode(':', $ipparts);
|
|
|
8834 |
if (strpos($addr, $ipnet) !== 0) {
|
|
|
8835 |
continue;
|
|
|
8836 |
}
|
|
|
8837 |
$ipparts = explode(':', $ipend);
|
|
|
8838 |
$end = hexdec($ipparts[7]);
|
|
|
8839 |
|
|
|
8840 |
$addrend = hexdec($addrparts[7]);
|
|
|
8841 |
|
|
|
8842 |
if (($addrend >= $start) and ($addrend <= $end)) {
|
|
|
8843 |
return true;
|
|
|
8844 |
}
|
|
|
8845 |
|
|
|
8846 |
} else {
|
|
|
8847 |
// IPv4.
|
|
|
8848 |
if ($ipv6) {
|
|
|
8849 |
continue;
|
|
|
8850 |
}
|
|
|
8851 |
$ipstart = cleanremoteaddr(trim($parts[0]), false); // Normalise.
|
|
|
8852 |
if ($ipstart === null) {
|
|
|
8853 |
continue;
|
|
|
8854 |
}
|
|
|
8855 |
$ipparts = explode('.', $ipstart);
|
|
|
8856 |
$ipparts[3] = trim($parts[1]);
|
|
|
8857 |
$ipend = cleanremoteaddr(implode('.', $ipparts), false); // Normalise.
|
|
|
8858 |
if ($ipend === null) {
|
|
|
8859 |
continue;
|
|
|
8860 |
}
|
|
|
8861 |
|
|
|
8862 |
if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
|
|
|
8863 |
return true;
|
|
|
8864 |
}
|
|
|
8865 |
}
|
|
|
8866 |
|
|
|
8867 |
} else {
|
|
|
8868 |
// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
|
|
|
8869 |
if (strpos($subnet, ':') !== false) {
|
|
|
8870 |
// IPv6.
|
|
|
8871 |
if (!$ipv6) {
|
|
|
8872 |
continue;
|
|
|
8873 |
}
|
|
|
8874 |
$parts = explode(':', $subnet);
|
|
|
8875 |
$count = count($parts);
|
|
|
8876 |
if ($parts[$count-1] === '') {
|
|
|
8877 |
unset($parts[$count-1]); // Trim trailing :'s.
|
|
|
8878 |
$count--;
|
|
|
8879 |
$subnet = implode('.', $parts);
|
|
|
8880 |
}
|
|
|
8881 |
$isip = cleanremoteaddr($subnet, false); // Normalise.
|
|
|
8882 |
if ($isip !== null) {
|
|
|
8883 |
if ($isip === $addr) {
|
|
|
8884 |
return true;
|
|
|
8885 |
}
|
|
|
8886 |
continue;
|
|
|
8887 |
} else if ($count > 8) {
|
|
|
8888 |
continue;
|
|
|
8889 |
}
|
|
|
8890 |
$zeros = array_fill(0, 8-$count, '0');
|
|
|
8891 |
$subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
|
|
|
8892 |
if (address_in_subnet($addr, $subnet)) {
|
|
|
8893 |
return true;
|
|
|
8894 |
}
|
|
|
8895 |
|
|
|
8896 |
} else {
|
|
|
8897 |
// IPv4.
|
|
|
8898 |
if ($ipv6) {
|
|
|
8899 |
continue;
|
|
|
8900 |
}
|
|
|
8901 |
$parts = explode('.', $subnet);
|
|
|
8902 |
$count = count($parts);
|
|
|
8903 |
if ($parts[$count-1] === '') {
|
|
|
8904 |
unset($parts[$count-1]); // Trim trailing .
|
|
|
8905 |
$count--;
|
|
|
8906 |
$subnet = implode('.', $parts);
|
|
|
8907 |
}
|
|
|
8908 |
if ($count == 4) {
|
|
|
8909 |
$subnet = cleanremoteaddr($subnet, false); // Normalise.
|
|
|
8910 |
if ($subnet === $addr) {
|
|
|
8911 |
return true;
|
|
|
8912 |
}
|
|
|
8913 |
continue;
|
|
|
8914 |
} else if ($count > 4) {
|
|
|
8915 |
continue;
|
|
|
8916 |
}
|
|
|
8917 |
$zeros = array_fill(0, 4-$count, '0');
|
|
|
8918 |
$subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
|
|
|
8919 |
if (address_in_subnet($addr, $subnet)) {
|
|
|
8920 |
return true;
|
|
|
8921 |
}
|
|
|
8922 |
}
|
|
|
8923 |
}
|
|
|
8924 |
}
|
|
|
8925 |
|
|
|
8926 |
return false;
|
|
|
8927 |
}
|
|
|
8928 |
|
|
|
8929 |
/**
|
|
|
8930 |
* For outputting debugging info
|
|
|
8931 |
*
|
|
|
8932 |
* @param string $string The string to write
|
|
|
8933 |
* @param string $eol The end of line char(s) to use
|
|
|
8934 |
* @param string $sleep Period to make the application sleep
|
|
|
8935 |
* This ensures any messages have time to display before redirect
|
|
|
8936 |
*/
|
|
|
8937 |
function mtrace($string, $eol="\n", $sleep=0) {
|
|
|
8938 |
global $CFG;
|
|
|
8939 |
|
|
|
8940 |
if (isset($CFG->mtrace_wrapper) && function_exists($CFG->mtrace_wrapper)) {
|
|
|
8941 |
$fn = $CFG->mtrace_wrapper;
|
|
|
8942 |
$fn($string, $eol);
|
|
|
8943 |
return;
|
|
|
8944 |
} else if (defined('STDOUT') && !PHPUNIT_TEST && !defined('BEHAT_TEST')) {
|
|
|
8945 |
// We must explicitly call the add_line function here.
|
|
|
8946 |
// Uses of fwrite to STDOUT are not picked up by ob_start.
|
|
|
8947 |
if ($output = \core\task\logmanager::add_line("{$string}{$eol}")) {
|
|
|
8948 |
fwrite(STDOUT, $output);
|
|
|
8949 |
}
|
|
|
8950 |
} else {
|
|
|
8951 |
echo $string . $eol;
|
|
|
8952 |
}
|
|
|
8953 |
|
|
|
8954 |
// Flush again.
|
|
|
8955 |
flush();
|
|
|
8956 |
|
|
|
8957 |
// Delay to keep message on user's screen in case of subsequent redirect.
|
|
|
8958 |
if ($sleep) {
|
|
|
8959 |
sleep($sleep);
|
|
|
8960 |
}
|
|
|
8961 |
}
|
|
|
8962 |
|
|
|
8963 |
/**
|
|
|
8964 |
* Helper to {@see mtrace()} an exception or throwable, including all relevant information.
|
|
|
8965 |
*
|
|
|
8966 |
* @param Throwable $e the error to ouptput.
|
|
|
8967 |
*/
|
|
|
8968 |
function mtrace_exception(Throwable $e): void {
|
|
|
8969 |
$info = get_exception_info($e);
|
|
|
8970 |
|
|
|
8971 |
$message = $info->message;
|
|
|
8972 |
if ($info->debuginfo) {
|
|
|
8973 |
$message .= "\n\n" . $info->debuginfo;
|
|
|
8974 |
}
|
|
|
8975 |
if ($info->backtrace) {
|
|
|
8976 |
$message .= "\n\n" . format_backtrace($info->backtrace, true);
|
|
|
8977 |
}
|
|
|
8978 |
|
|
|
8979 |
mtrace($message);
|
|
|
8980 |
}
|
|
|
8981 |
|
|
|
8982 |
/**
|
|
|
8983 |
* Replace 1 or more slashes or backslashes to 1 slash
|
|
|
8984 |
*
|
|
|
8985 |
* @param string $path The path to strip
|
|
|
8986 |
* @return string the path with double slashes removed
|
|
|
8987 |
*/
|
|
|
8988 |
function cleardoubleslashes ($path) {
|
|
|
8989 |
return preg_replace('/(\/|\\\){1,}/', '/', $path);
|
|
|
8990 |
}
|
|
|
8991 |
|
|
|
8992 |
/**
|
|
|
8993 |
* Is the current ip in a given list?
|
|
|
8994 |
*
|
|
|
8995 |
* @param string $list
|
|
|
8996 |
* @return bool
|
|
|
8997 |
*/
|
|
|
8998 |
function remoteip_in_list($list) {
|
|
|
8999 |
$clientip = getremoteaddr(null);
|
|
|
9000 |
|
|
|
9001 |
if (!$clientip) {
|
|
|
9002 |
// Ensure access on cli.
|
|
|
9003 |
return true;
|
|
|
9004 |
}
|
|
|
9005 |
return \core\ip_utils::is_ip_in_subnet_list($clientip, $list);
|
|
|
9006 |
}
|
|
|
9007 |
|
|
|
9008 |
/**
|
|
|
9009 |
* Returns most reliable client address
|
|
|
9010 |
*
|
|
|
9011 |
* @param string $default If an address can't be determined, then return this
|
|
|
9012 |
* @return string The remote IP address
|
|
|
9013 |
*/
|
|
|
9014 |
function getremoteaddr($default='0.0.0.0') {
|
|
|
9015 |
global $CFG;
|
|
|
9016 |
|
|
|
9017 |
if (!isset($CFG->getremoteaddrconf)) {
|
|
|
9018 |
// This will happen, for example, before just after the upgrade, as the
|
|
|
9019 |
// user is redirected to the admin screen.
|
|
|
9020 |
$variablestoskip = GETREMOTEADDR_SKIP_DEFAULT;
|
|
|
9021 |
} else {
|
|
|
9022 |
$variablestoskip = $CFG->getremoteaddrconf;
|
|
|
9023 |
}
|
|
|
9024 |
if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
|
|
|
9025 |
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
|
|
9026 |
$address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
|
|
|
9027 |
return $address ? $address : $default;
|
|
|
9028 |
}
|
|
|
9029 |
}
|
|
|
9030 |
if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
|
|
|
9031 |
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
|
9032 |
$forwardedaddresses = explode(",", $_SERVER['HTTP_X_FORWARDED_FOR']);
|
|
|
9033 |
|
|
|
9034 |
$forwardedaddresses = array_filter($forwardedaddresses, function($ip) {
|
|
|
9035 |
global $CFG;
|
|
|
9036 |
return !\core\ip_utils::is_ip_in_subnet_list($ip, $CFG->reverseproxyignore ?? '', ',');
|
|
|
9037 |
});
|
|
|
9038 |
|
|
|
9039 |
// Multiple proxies can append values to this header including an
|
|
|
9040 |
// untrusted original request header so we must only trust the last ip.
|
|
|
9041 |
$address = end($forwardedaddresses);
|
|
|
9042 |
|
|
|
9043 |
if (substr_count($address, ":") > 1) {
|
|
|
9044 |
// Remove port and brackets from IPv6.
|
|
|
9045 |
if (preg_match("/\[(.*)\]:/", $address, $matches)) {
|
|
|
9046 |
$address = $matches[1];
|
|
|
9047 |
}
|
|
|
9048 |
} else {
|
|
|
9049 |
// Remove port from IPv4.
|
|
|
9050 |
if (substr_count($address, ":") == 1) {
|
|
|
9051 |
$parts = explode(":", $address);
|
|
|
9052 |
$address = $parts[0];
|
|
|
9053 |
}
|
|
|
9054 |
}
|
|
|
9055 |
|
|
|
9056 |
$address = cleanremoteaddr($address);
|
|
|
9057 |
return $address ? $address : $default;
|
|
|
9058 |
}
|
|
|
9059 |
}
|
|
|
9060 |
if (!empty($_SERVER['REMOTE_ADDR'])) {
|
|
|
9061 |
$address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
|
|
|
9062 |
return $address ? $address : $default;
|
|
|
9063 |
} else {
|
|
|
9064 |
return $default;
|
|
|
9065 |
}
|
|
|
9066 |
}
|
|
|
9067 |
|
|
|
9068 |
/**
|
|
|
9069 |
* Cleans an ip address. Internal addresses are now allowed.
|
|
|
9070 |
* (Originally local addresses were not allowed.)
|
|
|
9071 |
*
|
|
|
9072 |
* @param string $addr IPv4 or IPv6 address
|
|
|
9073 |
* @param bool $compress use IPv6 address compression
|
|
|
9074 |
* @return string normalised ip address string, null if error
|
|
|
9075 |
*/
|
|
|
9076 |
function cleanremoteaddr($addr, $compress=false) {
|
|
|
9077 |
$addr = trim($addr);
|
|
|
9078 |
|
|
|
9079 |
if (strpos($addr, ':') !== false) {
|
|
|
9080 |
// Can be only IPv6.
|
|
|
9081 |
$parts = explode(':', $addr);
|
|
|
9082 |
$count = count($parts);
|
|
|
9083 |
|
|
|
9084 |
if (strpos($parts[$count-1], '.') !== false) {
|
|
|
9085 |
// Legacy ipv4 notation.
|
|
|
9086 |
$last = array_pop($parts);
|
|
|
9087 |
$ipv4 = cleanremoteaddr($last, true);
|
|
|
9088 |
if ($ipv4 === null) {
|
|
|
9089 |
return null;
|
|
|
9090 |
}
|
|
|
9091 |
$bits = explode('.', $ipv4);
|
|
|
9092 |
$parts[] = dechex($bits[0]).dechex($bits[1]);
|
|
|
9093 |
$parts[] = dechex($bits[2]).dechex($bits[3]);
|
|
|
9094 |
$count = count($parts);
|
|
|
9095 |
$addr = implode(':', $parts);
|
|
|
9096 |
}
|
|
|
9097 |
|
|
|
9098 |
if ($count < 3 or $count > 8) {
|
|
|
9099 |
return null; // Severly malformed.
|
|
|
9100 |
}
|
|
|
9101 |
|
|
|
9102 |
if ($count != 8) {
|
|
|
9103 |
if (strpos($addr, '::') === false) {
|
|
|
9104 |
return null; // Malformed.
|
|
|
9105 |
}
|
|
|
9106 |
// Uncompress.
|
|
|
9107 |
$insertat = array_search('', $parts, true);
|
|
|
9108 |
$missing = array_fill(0, 1 + 8 - $count, '0');
|
|
|
9109 |
array_splice($parts, $insertat, 1, $missing);
|
|
|
9110 |
foreach ($parts as $key => $part) {
|
|
|
9111 |
if ($part === '') {
|
|
|
9112 |
$parts[$key] = '0';
|
|
|
9113 |
}
|
|
|
9114 |
}
|
|
|
9115 |
}
|
|
|
9116 |
|
|
|
9117 |
$adr = implode(':', $parts);
|
|
|
9118 |
if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
|
|
|
9119 |
return null; // Incorrect format - sorry.
|
|
|
9120 |
}
|
|
|
9121 |
|
|
|
9122 |
// Normalise 0s and case.
|
|
|
9123 |
$parts = array_map('hexdec', $parts);
|
|
|
9124 |
$parts = array_map('dechex', $parts);
|
|
|
9125 |
|
|
|
9126 |
$result = implode(':', $parts);
|
|
|
9127 |
|
|
|
9128 |
if (!$compress) {
|
|
|
9129 |
return $result;
|
|
|
9130 |
}
|
|
|
9131 |
|
|
|
9132 |
if ($result === '0:0:0:0:0:0:0:0') {
|
|
|
9133 |
return '::'; // All addresses.
|
|
|
9134 |
}
|
|
|
9135 |
|
|
|
9136 |
$compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
|
|
|
9137 |
if ($compressed !== $result) {
|
|
|
9138 |
return $compressed;
|
|
|
9139 |
}
|
|
|
9140 |
|
|
|
9141 |
$compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
|
|
|
9142 |
if ($compressed !== $result) {
|
|
|
9143 |
return $compressed;
|
|
|
9144 |
}
|
|
|
9145 |
|
|
|
9146 |
$compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
|
|
|
9147 |
if ($compressed !== $result) {
|
|
|
9148 |
return $compressed;
|
|
|
9149 |
}
|
|
|
9150 |
|
|
|
9151 |
return $result;
|
|
|
9152 |
}
|
|
|
9153 |
|
|
|
9154 |
// First get all things that look like IPv4 addresses.
|
|
|
9155 |
$parts = array();
|
|
|
9156 |
if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
|
|
|
9157 |
return null;
|
|
|
9158 |
}
|
|
|
9159 |
unset($parts[0]);
|
|
|
9160 |
|
|
|
9161 |
foreach ($parts as $key => $match) {
|
|
|
9162 |
if ($match > 255) {
|
|
|
9163 |
return null;
|
|
|
9164 |
}
|
|
|
9165 |
$parts[$key] = (int)$match; // Normalise 0s.
|
|
|
9166 |
}
|
|
|
9167 |
|
|
|
9168 |
return implode('.', $parts);
|
|
|
9169 |
}
|
|
|
9170 |
|
|
|
9171 |
|
|
|
9172 |
/**
|
|
|
9173 |
* Is IP address a public address?
|
|
|
9174 |
*
|
|
|
9175 |
* @param string $ip The ip to check
|
|
|
9176 |
* @return bool true if the ip is public
|
|
|
9177 |
*/
|
|
|
9178 |
function ip_is_public($ip) {
|
|
|
9179 |
return (bool) filter_var($ip, FILTER_VALIDATE_IP, (FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE));
|
|
|
9180 |
}
|
|
|
9181 |
|
|
|
9182 |
/**
|
|
|
9183 |
* This function will make a complete copy of anything it's given,
|
|
|
9184 |
* regardless of whether it's an object or not.
|
|
|
9185 |
*
|
|
|
9186 |
* @param mixed $thing Something you want cloned
|
|
|
9187 |
* @return mixed What ever it is you passed it
|
|
|
9188 |
*/
|
|
|
9189 |
function fullclone($thing) {
|
|
|
9190 |
return unserialize(serialize($thing));
|
|
|
9191 |
}
|
|
|
9192 |
|
|
|
9193 |
/**
|
|
|
9194 |
* Used to make sure that $min <= $value <= $max
|
|
|
9195 |
*
|
|
|
9196 |
* Make sure that value is between min, and max
|
|
|
9197 |
*
|
|
|
9198 |
* @param int $min The minimum value
|
|
|
9199 |
* @param int $value The value to check
|
|
|
9200 |
* @param int $max The maximum value
|
|
|
9201 |
* @return int
|
|
|
9202 |
*/
|
|
|
9203 |
function bounded_number($min, $value, $max) {
|
|
|
9204 |
if ($value < $min) {
|
|
|
9205 |
return $min;
|
|
|
9206 |
}
|
|
|
9207 |
if ($value > $max) {
|
|
|
9208 |
return $max;
|
|
|
9209 |
}
|
|
|
9210 |
return $value;
|
|
|
9211 |
}
|
|
|
9212 |
|
|
|
9213 |
/**
|
|
|
9214 |
* Check if there is a nested array within the passed array
|
|
|
9215 |
*
|
|
|
9216 |
* @param array $array
|
|
|
9217 |
* @return bool true if there is a nested array false otherwise
|
|
|
9218 |
*/
|
|
|
9219 |
function array_is_nested($array) {
|
|
|
9220 |
foreach ($array as $value) {
|
|
|
9221 |
if (is_array($value)) {
|
|
|
9222 |
return true;
|
|
|
9223 |
}
|
|
|
9224 |
}
|
|
|
9225 |
return false;
|
|
|
9226 |
}
|
|
|
9227 |
|
|
|
9228 |
/**
|
|
|
9229 |
* get_performance_info() pairs up with init_performance_info()
|
|
|
9230 |
* loaded in setup.php. Returns an array with 'html' and 'txt'
|
|
|
9231 |
* values ready for use, and each of the individual stats provided
|
|
|
9232 |
* separately as well.
|
|
|
9233 |
*
|
|
|
9234 |
* @return array
|
|
|
9235 |
*/
|
|
|
9236 |
function get_performance_info() {
|
|
|
9237 |
global $CFG, $PERF, $DB, $PAGE;
|
|
|
9238 |
|
|
|
9239 |
$info = array();
|
|
|
9240 |
$info['txt'] = me() . ' '; // Holds log-friendly representation.
|
|
|
9241 |
|
|
|
9242 |
$info['html'] = '';
|
|
|
9243 |
if (!empty($CFG->themedesignermode)) {
|
|
|
9244 |
// Attempt to avoid devs debugging peformance issues, when its caused by css building and so on.
|
|
|
9245 |
$info['html'] .= '<p><strong>Warning: Theme designer mode is enabled.</strong></p>';
|
|
|
9246 |
}
|
|
|
9247 |
$info['html'] .= '<ul class="list-unstyled row mx-md-0">'; // Holds userfriendly HTML representation.
|
|
|
9248 |
|
|
|
9249 |
$info['realtime'] = microtime_diff($PERF->starttime, microtime());
|
|
|
9250 |
|
|
|
9251 |
$info['html'] .= '<li class="timeused col-sm-4">'.$info['realtime'].' secs</li> ';
|
|
|
9252 |
$info['txt'] .= 'time: '.$info['realtime'].'s ';
|
|
|
9253 |
|
|
|
9254 |
// GET/POST (or NULL if $_SERVER['REQUEST_METHOD'] is undefined) is useful for txt logged information.
|
|
|
9255 |
$info['txt'] .= 'method: ' . ($_SERVER['REQUEST_METHOD'] ?? "NULL") . ' ';
|
|
|
9256 |
|
|
|
9257 |
if (function_exists('memory_get_usage')) {
|
|
|
9258 |
$info['memory_total'] = memory_get_usage();
|
|
|
9259 |
$info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
|
|
|
9260 |
$info['html'] .= '<li class="memoryused col-sm-4">RAM: '.display_size($info['memory_total']).'</li> ';
|
|
|
9261 |
$info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.
|
|
|
9262 |
$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
|
|
|
9263 |
}
|
|
|
9264 |
|
|
|
9265 |
if (function_exists('memory_get_peak_usage')) {
|
|
|
9266 |
$info['memory_peak'] = memory_get_peak_usage();
|
|
|
9267 |
$info['html'] .= '<li class="memoryused col-sm-4">RAM peak: '.display_size($info['memory_peak']).'</li> ';
|
|
|
9268 |
$info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
|
|
|
9269 |
}
|
|
|
9270 |
|
|
|
9271 |
$info['html'] .= '</ul><ul class="list-unstyled row mx-md-0">';
|
|
|
9272 |
$inc = get_included_files();
|
|
|
9273 |
$info['includecount'] = count($inc);
|
|
|
9274 |
$info['html'] .= '<li class="included col-sm-4">Included '.$info['includecount'].' files</li> ';
|
|
|
9275 |
$info['txt'] .= 'includecount: '.$info['includecount'].' ';
|
|
|
9276 |
|
|
|
9277 |
if (!empty($CFG->early_install_lang) or empty($PAGE)) {
|
|
|
9278 |
// We can not track more performance before installation or before PAGE init, sorry.
|
|
|
9279 |
return $info;
|
|
|
9280 |
}
|
|
|
9281 |
|
|
|
9282 |
$filtermanager = filter_manager::instance();
|
|
|
9283 |
if (method_exists($filtermanager, 'get_performance_summary')) {
|
|
|
9284 |
list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
|
|
|
9285 |
$info = array_merge($filterinfo, $info);
|
|
|
9286 |
foreach ($filterinfo as $key => $value) {
|
|
|
9287 |
$info['html'] .= "<li class='$key col-sm-4'>$nicenames[$key]: $value </li> ";
|
|
|
9288 |
$info['txt'] .= "$key: $value ";
|
|
|
9289 |
}
|
|
|
9290 |
}
|
|
|
9291 |
|
|
|
9292 |
$stringmanager = get_string_manager();
|
|
|
9293 |
if (method_exists($stringmanager, 'get_performance_summary')) {
|
|
|
9294 |
list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
|
|
|
9295 |
$info = array_merge($filterinfo, $info);
|
|
|
9296 |
foreach ($filterinfo as $key => $value) {
|
|
|
9297 |
$info['html'] .= "<li class='$key col-sm-4'>$nicenames[$key]: $value </li> ";
|
|
|
9298 |
$info['txt'] .= "$key: $value ";
|
|
|
9299 |
}
|
|
|
9300 |
}
|
|
|
9301 |
|
|
|
9302 |
$info['dbqueries'] = $DB->perf_get_reads().'/'.$DB->perf_get_writes();
|
|
|
9303 |
$info['html'] .= '<li class="dbqueries col-sm-4">DB reads/writes: '.$info['dbqueries'].'</li> ';
|
|
|
9304 |
$info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
|
|
|
9305 |
|
|
|
9306 |
if ($DB->want_read_slave()) {
|
|
|
9307 |
$info['dbreads_slave'] = $DB->perf_get_reads_slave();
|
|
|
9308 |
$info['html'] .= '<li class="dbqueries col-sm-4">DB reads from slave: '.$info['dbreads_slave'].'</li> ';
|
|
|
9309 |
$info['txt'] .= 'db reads from slave: '.$info['dbreads_slave'].' ';
|
|
|
9310 |
}
|
|
|
9311 |
|
|
|
9312 |
$info['dbtime'] = round($DB->perf_get_queries_time(), 5);
|
|
|
9313 |
$info['html'] .= '<li class="dbtime col-sm-4">DB queries time: '.$info['dbtime'].' secs</li> ';
|
|
|
9314 |
$info['txt'] .= 'db queries time: ' . $info['dbtime'] . 's ';
|
|
|
9315 |
|
|
|
9316 |
if (function_exists('posix_times')) {
|
|
|
9317 |
$ptimes = posix_times();
|
|
|
9318 |
if (is_array($ptimes)) {
|
|
|
9319 |
foreach ($ptimes as $key => $val) {
|
|
|
9320 |
$info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
|
|
|
9321 |
}
|
|
|
9322 |
$info['html'] .= "<li class=\"posixtimes col-sm-4\">ticks: $info[ticks] user: $info[utime]";
|
|
|
9323 |
$info['html'] .= "sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</li> ";
|
|
|
9324 |
$info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
|
|
|
9325 |
}
|
|
|
9326 |
}
|
|
|
9327 |
|
|
|
9328 |
// Grab the load average for the last minute.
|
|
|
9329 |
// /proc will only work under some linux configurations
|
|
|
9330 |
// while uptime is there under MacOSX/Darwin and other unices.
|
|
|
9331 |
if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
|
|
|
9332 |
list($serverload) = explode(' ', $loadavg[0]);
|
|
|
9333 |
unset($loadavg);
|
|
|
9334 |
} else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
|
|
|
9335 |
if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
|
|
|
9336 |
$serverload = $matches[1];
|
|
|
9337 |
} else {
|
|
|
9338 |
trigger_error('Could not parse uptime output!');
|
|
|
9339 |
}
|
|
|
9340 |
}
|
|
|
9341 |
if (!empty($serverload)) {
|
|
|
9342 |
$info['serverload'] = $serverload;
|
|
|
9343 |
$info['html'] .= '<li class="serverload col-sm-4">Load average: '.$info['serverload'].'</li> ';
|
|
|
9344 |
$info['txt'] .= "serverload: {$info['serverload']} ";
|
|
|
9345 |
}
|
|
|
9346 |
|
|
|
9347 |
// Display size of session if session started.
|
|
|
9348 |
if ($si = \core\session\manager::get_performance_info()) {
|
|
|
9349 |
$info['sessionsize'] = $si['size'];
|
|
|
9350 |
$info['html'] .= "<li class=\"serverload col-sm-4\">" . $si['html'] . "</li>";
|
|
|
9351 |
$info['txt'] .= $si['txt'];
|
|
|
9352 |
}
|
|
|
9353 |
|
|
|
9354 |
// Display time waiting for session if applicable.
|
|
|
9355 |
if (!empty($PERF->sessionlock['wait'])) {
|
|
|
9356 |
$sessionwait = number_format($PERF->sessionlock['wait'], 3) . ' secs';
|
|
|
9357 |
$info['html'] .= html_writer::tag('li', 'Session wait: ' . $sessionwait, [
|
|
|
9358 |
'class' => 'sessionwait col-sm-4'
|
|
|
9359 |
]);
|
|
|
9360 |
$info['txt'] .= 'sessionwait: ' . $sessionwait . ' ';
|
|
|
9361 |
}
|
|
|
9362 |
|
|
|
9363 |
$info['html'] .= '</ul>';
|
|
|
9364 |
$html = '';
|
|
|
9365 |
if ($stats = cache_helper::get_stats()) {
|
|
|
9366 |
|
|
|
9367 |
$table = new html_table();
|
|
|
9368 |
$table->attributes['class'] = 'cachesused table table-dark table-sm w-auto table-bordered';
|
|
|
9369 |
$table->head = ['Mode', 'Cache item', 'Static', 'H', 'M', get_string('mappingprimary', 'cache'), 'H', 'M', 'S', 'I/O'];
|
|
|
9370 |
$table->data = [];
|
|
|
9371 |
$table->align = ['left', 'left', 'left', 'right', 'right', 'left', 'right', 'right', 'right', 'right'];
|
|
|
9372 |
|
|
|
9373 |
$text = 'Caches used (hits/misses/sets): ';
|
|
|
9374 |
$hits = 0;
|
|
|
9375 |
$misses = 0;
|
|
|
9376 |
$sets = 0;
|
|
|
9377 |
$maxstores = 0;
|
|
|
9378 |
|
|
|
9379 |
// We want to align static caches into their own column.
|
|
|
9380 |
$hasstatic = false;
|
|
|
9381 |
foreach ($stats as $definition => $details) {
|
|
|
9382 |
$numstores = count($details['stores']);
|
|
|
9383 |
$first = key($details['stores']);
|
|
|
9384 |
if ($first !== cache_store::STATIC_ACCEL) {
|
|
|
9385 |
$numstores++; // Add a blank space for the missing static store.
|
|
|
9386 |
}
|
|
|
9387 |
$maxstores = max($maxstores, $numstores);
|
|
|
9388 |
}
|
|
|
9389 |
|
|
|
9390 |
$storec = 0;
|
|
|
9391 |
|
|
|
9392 |
while ($storec++ < ($maxstores - 2)) {
|
|
|
9393 |
if ($storec == ($maxstores - 2)) {
|
|
|
9394 |
$table->head[] = get_string('mappingfinal', 'cache');
|
|
|
9395 |
} else {
|
|
|
9396 |
$table->head[] = "Store $storec";
|
|
|
9397 |
}
|
|
|
9398 |
$table->align[] = 'left';
|
|
|
9399 |
$table->align[] = 'right';
|
|
|
9400 |
$table->align[] = 'right';
|
|
|
9401 |
$table->align[] = 'right';
|
|
|
9402 |
$table->align[] = 'right';
|
|
|
9403 |
$table->head[] = 'H';
|
|
|
9404 |
$table->head[] = 'M';
|
|
|
9405 |
$table->head[] = 'S';
|
|
|
9406 |
$table->head[] = 'I/O';
|
|
|
9407 |
}
|
|
|
9408 |
|
|
|
9409 |
ksort($stats);
|
|
|
9410 |
|
|
|
9411 |
foreach ($stats as $definition => $details) {
|
|
|
9412 |
switch ($details['mode']) {
|
|
|
9413 |
case cache_store::MODE_APPLICATION:
|
|
|
9414 |
$modeclass = 'application';
|
|
|
9415 |
$mode = ' <span title="application cache">App</span>';
|
|
|
9416 |
break;
|
|
|
9417 |
case cache_store::MODE_SESSION:
|
|
|
9418 |
$modeclass = 'session';
|
|
|
9419 |
$mode = ' <span title="session cache">Ses</span>';
|
|
|
9420 |
break;
|
|
|
9421 |
case cache_store::MODE_REQUEST:
|
|
|
9422 |
$modeclass = 'request';
|
|
|
9423 |
$mode = ' <span title="request cache">Req</span>';
|
|
|
9424 |
break;
|
|
|
9425 |
}
|
|
|
9426 |
$row = [$mode, $definition];
|
|
|
9427 |
|
|
|
9428 |
$text .= "$definition {";
|
|
|
9429 |
|
|
|
9430 |
$storec = 0;
|
|
|
9431 |
foreach ($details['stores'] as $store => $data) {
|
|
|
9432 |
|
|
|
9433 |
if ($storec == 0 && $store !== cache_store::STATIC_ACCEL) {
|
|
|
9434 |
$row[] = '';
|
|
|
9435 |
$row[] = '';
|
|
|
9436 |
$row[] = '';
|
|
|
9437 |
$storec++;
|
|
|
9438 |
}
|
|
|
9439 |
|
|
|
9440 |
$hits += $data['hits'];
|
|
|
9441 |
$misses += $data['misses'];
|
|
|
9442 |
$sets += $data['sets'];
|
|
|
9443 |
if ($data['hits'] == 0 and $data['misses'] > 0) {
|
|
|
9444 |
$cachestoreclass = 'nohits bg-danger';
|
|
|
9445 |
} else if ($data['hits'] < $data['misses']) {
|
|
|
9446 |
$cachestoreclass = 'lowhits bg-warning text-dark';
|
|
|
9447 |
} else {
|
|
|
9448 |
$cachestoreclass = 'hihits';
|
|
|
9449 |
}
|
|
|
9450 |
$text .= "$store($data[hits]/$data[misses]/$data[sets]) ";
|
|
|
9451 |
$cell = new html_table_cell($store);
|
|
|
9452 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9453 |
$row[] = $cell;
|
|
|
9454 |
$cell = new html_table_cell($data['hits']);
|
|
|
9455 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9456 |
$row[] = $cell;
|
|
|
9457 |
$cell = new html_table_cell($data['misses']);
|
|
|
9458 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9459 |
$row[] = $cell;
|
|
|
9460 |
|
|
|
9461 |
if ($store !== cache_store::STATIC_ACCEL) {
|
|
|
9462 |
// The static cache is never set.
|
|
|
9463 |
$cell = new html_table_cell($data['sets']);
|
|
|
9464 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9465 |
$row[] = $cell;
|
|
|
9466 |
|
|
|
9467 |
if ($data['hits'] || $data['sets']) {
|
|
|
9468 |
if ($data['iobytes'] === cache_store::IO_BYTES_NOT_SUPPORTED) {
|
|
|
9469 |
$size = '-';
|
|
|
9470 |
} else {
|
|
|
9471 |
$size = display_size($data['iobytes'], 1, 'KB');
|
|
|
9472 |
if ($data['iobytes'] >= 10 * 1024) {
|
|
|
9473 |
$cachestoreclass = ' bg-warning text-dark';
|
|
|
9474 |
}
|
|
|
9475 |
}
|
|
|
9476 |
} else {
|
|
|
9477 |
$size = '';
|
|
|
9478 |
}
|
|
|
9479 |
$cell = new html_table_cell($size);
|
|
|
9480 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9481 |
$row[] = $cell;
|
|
|
9482 |
}
|
|
|
9483 |
$storec++;
|
|
|
9484 |
}
|
|
|
9485 |
while ($storec++ < $maxstores) {
|
|
|
9486 |
$row[] = '';
|
|
|
9487 |
$row[] = '';
|
|
|
9488 |
$row[] = '';
|
|
|
9489 |
$row[] = '';
|
|
|
9490 |
$row[] = '';
|
|
|
9491 |
}
|
|
|
9492 |
$text .= '} ';
|
|
|
9493 |
|
|
|
9494 |
$table->data[] = $row;
|
|
|
9495 |
}
|
|
|
9496 |
|
|
|
9497 |
$html .= html_writer::table($table);
|
|
|
9498 |
|
|
|
9499 |
// Now lets also show sub totals for each cache store.
|
|
|
9500 |
$storetotals = [];
|
|
|
9501 |
$storetotal = ['hits' => 0, 'misses' => 0, 'sets' => 0, 'iobytes' => 0];
|
|
|
9502 |
foreach ($stats as $definition => $details) {
|
|
|
9503 |
foreach ($details['stores'] as $store => $data) {
|
|
|
9504 |
if (!array_key_exists($store, $storetotals)) {
|
|
|
9505 |
$storetotals[$store] = ['hits' => 0, 'misses' => 0, 'sets' => 0, 'iobytes' => 0];
|
|
|
9506 |
}
|
|
|
9507 |
$storetotals[$store]['class'] = $data['class'];
|
|
|
9508 |
$storetotals[$store]['hits'] += $data['hits'];
|
|
|
9509 |
$storetotals[$store]['misses'] += $data['misses'];
|
|
|
9510 |
$storetotals[$store]['sets'] += $data['sets'];
|
|
|
9511 |
$storetotal['hits'] += $data['hits'];
|
|
|
9512 |
$storetotal['misses'] += $data['misses'];
|
|
|
9513 |
$storetotal['sets'] += $data['sets'];
|
|
|
9514 |
if ($data['iobytes'] !== cache_store::IO_BYTES_NOT_SUPPORTED) {
|
|
|
9515 |
$storetotals[$store]['iobytes'] += $data['iobytes'];
|
|
|
9516 |
$storetotal['iobytes'] += $data['iobytes'];
|
|
|
9517 |
}
|
|
|
9518 |
}
|
|
|
9519 |
}
|
|
|
9520 |
|
|
|
9521 |
$table = new html_table();
|
|
|
9522 |
$table->attributes['class'] = 'cachesused table table-dark table-sm w-auto table-bordered';
|
|
|
9523 |
$table->head = [get_string('storename', 'cache'), get_string('type_cachestore', 'plugin'), 'H', 'M', 'S', 'I/O'];
|
|
|
9524 |
$table->data = [];
|
|
|
9525 |
$table->align = ['left', 'left', 'right', 'right', 'right', 'right'];
|
|
|
9526 |
|
|
|
9527 |
ksort($storetotals);
|
|
|
9528 |
|
|
|
9529 |
foreach ($storetotals as $store => $data) {
|
|
|
9530 |
$row = [];
|
|
|
9531 |
if ($data['hits'] == 0 and $data['misses'] > 0) {
|
|
|
9532 |
$cachestoreclass = 'nohits bg-danger';
|
|
|
9533 |
} else if ($data['hits'] < $data['misses']) {
|
|
|
9534 |
$cachestoreclass = 'lowhits bg-warning text-dark';
|
|
|
9535 |
} else {
|
|
|
9536 |
$cachestoreclass = 'hihits';
|
|
|
9537 |
}
|
|
|
9538 |
$cell = new html_table_cell($store);
|
|
|
9539 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9540 |
$row[] = $cell;
|
|
|
9541 |
$cell = new html_table_cell($data['class']);
|
|
|
9542 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9543 |
$row[] = $cell;
|
|
|
9544 |
$cell = new html_table_cell($data['hits']);
|
|
|
9545 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9546 |
$row[] = $cell;
|
|
|
9547 |
$cell = new html_table_cell($data['misses']);
|
|
|
9548 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9549 |
$row[] = $cell;
|
|
|
9550 |
$cell = new html_table_cell($data['sets']);
|
|
|
9551 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9552 |
$row[] = $cell;
|
|
|
9553 |
if ($data['hits'] || $data['sets']) {
|
|
|
9554 |
if ($data['iobytes']) {
|
|
|
9555 |
$size = display_size($data['iobytes'], 1, 'KB');
|
|
|
9556 |
} else {
|
|
|
9557 |
$size = '-';
|
|
|
9558 |
}
|
|
|
9559 |
} else {
|
|
|
9560 |
$size = '';
|
|
|
9561 |
}
|
|
|
9562 |
$cell = new html_table_cell($size);
|
|
|
9563 |
$cell->attributes = ['class' => $cachestoreclass];
|
|
|
9564 |
$row[] = $cell;
|
|
|
9565 |
$table->data[] = $row;
|
|
|
9566 |
}
|
|
|
9567 |
if (!empty($storetotal['iobytes'])) {
|
|
|
9568 |
$size = display_size($storetotal['iobytes'], 1, 'KB');
|
|
|
9569 |
} else if (!empty($storetotal['hits']) || !empty($storetotal['sets'])) {
|
|
|
9570 |
$size = '-';
|
|
|
9571 |
} else {
|
|
|
9572 |
$size = '';
|
|
|
9573 |
}
|
|
|
9574 |
$row = [
|
|
|
9575 |
get_string('total'),
|
|
|
9576 |
'',
|
|
|
9577 |
$storetotal['hits'],
|
|
|
9578 |
$storetotal['misses'],
|
|
|
9579 |
$storetotal['sets'],
|
|
|
9580 |
$size,
|
|
|
9581 |
];
|
|
|
9582 |
$table->data[] = $row;
|
|
|
9583 |
|
|
|
9584 |
$html .= html_writer::table($table);
|
|
|
9585 |
|
|
|
9586 |
$info['cachesused'] = "$hits / $misses / $sets";
|
|
|
9587 |
$info['html'] .= $html;
|
|
|
9588 |
$info['txt'] .= $text.'. ';
|
|
|
9589 |
} else {
|
|
|
9590 |
$info['cachesused'] = '0 / 0 / 0';
|
|
|
9591 |
$info['html'] .= '<div class="cachesused">Caches used (hits/misses/sets): 0/0/0</div>';
|
|
|
9592 |
$info['txt'] .= 'Caches used (hits/misses/sets): 0/0/0 ';
|
|
|
9593 |
}
|
|
|
9594 |
|
|
|
9595 |
// Display lock information if any.
|
|
|
9596 |
if (!empty($PERF->locks)) {
|
|
|
9597 |
$table = new html_table();
|
|
|
9598 |
$table->attributes['class'] = 'locktimings table table-dark table-sm w-auto table-bordered';
|
|
|
9599 |
$table->head = ['Lock', 'Waited (s)', 'Obtained', 'Held for (s)'];
|
|
|
9600 |
$table->align = ['left', 'right', 'center', 'right'];
|
|
|
9601 |
$table->data = [];
|
|
|
9602 |
$text = 'Locks (waited/obtained/held):';
|
|
|
9603 |
foreach ($PERF->locks as $locktiming) {
|
|
|
9604 |
$row = [];
|
|
|
9605 |
$row[] = s($locktiming->type . '/' . $locktiming->resource);
|
|
|
9606 |
$text .= ' ' . $locktiming->type . '/' . $locktiming->resource . ' (';
|
|
|
9607 |
|
|
|
9608 |
// The time we had to wait to get the lock.
|
|
|
9609 |
$roundedtime = number_format($locktiming->wait, 1);
|
|
|
9610 |
$cell = new html_table_cell($roundedtime);
|
|
|
9611 |
if ($locktiming->wait > 0.5) {
|
|
|
9612 |
$cell->attributes = ['class' => 'bg-warning text-dark'];
|
|
|
9613 |
}
|
|
|
9614 |
$row[] = $cell;
|
|
|
9615 |
$text .= $roundedtime . '/';
|
|
|
9616 |
|
|
|
9617 |
// Show a tick or cross for success.
|
|
|
9618 |
$row[] = $locktiming->success ? '✓' : '❌';
|
|
|
9619 |
$text .= ($locktiming->success ? 'y' : 'n') . '/';
|
|
|
9620 |
|
|
|
9621 |
// If applicable, show how long we held the lock before releasing it.
|
|
|
9622 |
if (property_exists($locktiming, 'held')) {
|
|
|
9623 |
$roundedtime = number_format($locktiming->held, 1);
|
|
|
9624 |
$cell = new html_table_cell($roundedtime);
|
|
|
9625 |
if ($locktiming->held > 0.5) {
|
|
|
9626 |
$cell->attributes = ['class' => 'bg-warning text-dark'];
|
|
|
9627 |
}
|
|
|
9628 |
$row[] = $cell;
|
|
|
9629 |
$text .= $roundedtime;
|
|
|
9630 |
} else {
|
|
|
9631 |
$row[] = '-';
|
|
|
9632 |
$text .= '-';
|
|
|
9633 |
}
|
|
|
9634 |
$text .= ')';
|
|
|
9635 |
|
|
|
9636 |
$table->data[] = $row;
|
|
|
9637 |
}
|
|
|
9638 |
$info['html'] .= html_writer::table($table);
|
|
|
9639 |
$info['txt'] .= $text . '. ';
|
|
|
9640 |
}
|
|
|
9641 |
|
|
|
9642 |
$info['html'] = '<div class="performanceinfo siteinfo container-fluid px-md-0 overflow-auto pt-3">'.$info['html'].'</div>';
|
|
|
9643 |
return $info;
|
|
|
9644 |
}
|
|
|
9645 |
|
|
|
9646 |
/**
|
|
|
9647 |
* Renames a file or directory to a unique name within the same directory.
|
|
|
9648 |
*
|
|
|
9649 |
* This function is designed to avoid any potential race conditions, and select an unused name.
|
|
|
9650 |
*
|
|
|
9651 |
* @param string $filepath Original filepath
|
|
|
9652 |
* @param string $prefix Prefix to use for the temporary name
|
|
|
9653 |
* @return string|bool New file path or false if failed
|
|
|
9654 |
* @since Moodle 3.10
|
|
|
9655 |
*/
|
|
|
9656 |
function rename_to_unused_name(string $filepath, string $prefix = '_temp_') {
|
|
|
9657 |
$dir = dirname($filepath);
|
|
|
9658 |
$basename = $dir . '/' . $prefix;
|
|
|
9659 |
$limit = 0;
|
|
|
9660 |
while ($limit < 100) {
|
|
|
9661 |
// Select a new name based on a random number.
|
|
|
9662 |
$newfilepath = $basename . md5(mt_rand());
|
|
|
9663 |
|
|
|
9664 |
// Attempt a rename to that new name.
|
|
|
9665 |
if (@rename($filepath, $newfilepath)) {
|
|
|
9666 |
return $newfilepath;
|
|
|
9667 |
}
|
|
|
9668 |
|
|
|
9669 |
// The first time, do some sanity checks, maybe it is failing for a good reason and there
|
|
|
9670 |
// is no point trying 100 times if so.
|
|
|
9671 |
if ($limit === 0 && (!file_exists($filepath) || !is_writable($dir))) {
|
|
|
9672 |
return false;
|
|
|
9673 |
}
|
|
|
9674 |
$limit++;
|
|
|
9675 |
}
|
|
|
9676 |
return false;
|
|
|
9677 |
}
|
|
|
9678 |
|
|
|
9679 |
/**
|
|
|
9680 |
* Delete directory or only its content
|
|
|
9681 |
*
|
|
|
9682 |
* @param string $dir directory path
|
|
|
9683 |
* @param bool $contentonly
|
|
|
9684 |
* @return bool success, true also if dir does not exist
|
|
|
9685 |
*/
|
|
|
9686 |
function remove_dir($dir, $contentonly=false) {
|
|
|
9687 |
if (!is_dir($dir)) {
|
|
|
9688 |
// Nothing to do.
|
|
|
9689 |
return true;
|
|
|
9690 |
}
|
|
|
9691 |
|
|
|
9692 |
if (!$contentonly) {
|
|
|
9693 |
// Start by renaming the directory; this will guarantee that other processes don't write to it
|
|
|
9694 |
// while it is in the process of being deleted.
|
|
|
9695 |
$tempdir = rename_to_unused_name($dir);
|
|
|
9696 |
if ($tempdir) {
|
|
|
9697 |
// If the rename was successful then delete the $tempdir instead.
|
|
|
9698 |
$dir = $tempdir;
|
|
|
9699 |
}
|
|
|
9700 |
// If the rename fails, we will continue through and attempt to delete the directory
|
|
|
9701 |
// without renaming it since that is likely to at least delete most of the files.
|
|
|
9702 |
}
|
|
|
9703 |
|
|
|
9704 |
if (!$handle = opendir($dir)) {
|
|
|
9705 |
return false;
|
|
|
9706 |
}
|
|
|
9707 |
$result = true;
|
|
|
9708 |
while (false!==($item = readdir($handle))) {
|
|
|
9709 |
if ($item != '.' && $item != '..') {
|
|
|
9710 |
if (is_dir($dir.'/'.$item)) {
|
|
|
9711 |
$result = remove_dir($dir.'/'.$item) && $result;
|
|
|
9712 |
} else {
|
|
|
9713 |
$result = unlink($dir.'/'.$item) && $result;
|
|
|
9714 |
}
|
|
|
9715 |
}
|
|
|
9716 |
}
|
|
|
9717 |
closedir($handle);
|
|
|
9718 |
if ($contentonly) {
|
|
|
9719 |
clearstatcache(); // Make sure file stat cache is properly invalidated.
|
|
|
9720 |
return $result;
|
|
|
9721 |
}
|
|
|
9722 |
$result = rmdir($dir); // If anything left the result will be false, no need for && $result.
|
|
|
9723 |
clearstatcache(); // Make sure file stat cache is properly invalidated.
|
|
|
9724 |
return $result;
|
|
|
9725 |
}
|
|
|
9726 |
|
|
|
9727 |
/**
|
|
|
9728 |
* Detect if an object or a class contains a given property
|
|
|
9729 |
* will take an actual object or the name of a class
|
|
|
9730 |
*
|
|
|
9731 |
* @param mixed $obj Name of class or real object to test
|
|
|
9732 |
* @param string $property name of property to find
|
|
|
9733 |
* @return bool true if property exists
|
|
|
9734 |
*/
|
|
|
9735 |
function object_property_exists( $obj, $property ) {
|
|
|
9736 |
if (is_string( $obj )) {
|
|
|
9737 |
$properties = get_class_vars( $obj );
|
|
|
9738 |
} else {
|
|
|
9739 |
$properties = get_object_vars( $obj );
|
|
|
9740 |
}
|
|
|
9741 |
return array_key_exists( $property, $properties );
|
|
|
9742 |
}
|
|
|
9743 |
|
|
|
9744 |
/**
|
|
|
9745 |
* Converts an object into an associative array
|
|
|
9746 |
*
|
|
|
9747 |
* This function converts an object into an associative array by iterating
|
|
|
9748 |
* over its public properties. Because this function uses the foreach
|
|
|
9749 |
* construct, Iterators are respected. It works recursively on arrays of objects.
|
|
|
9750 |
* Arrays and simple values are returned as is.
|
|
|
9751 |
*
|
|
|
9752 |
* If class has magic properties, it can implement IteratorAggregate
|
|
|
9753 |
* and return all available properties in getIterator()
|
|
|
9754 |
*
|
|
|
9755 |
* @param mixed $var
|
|
|
9756 |
* @return array
|
|
|
9757 |
*/
|
|
|
9758 |
function convert_to_array($var) {
|
|
|
9759 |
$result = array();
|
|
|
9760 |
|
|
|
9761 |
// Loop over elements/properties.
|
|
|
9762 |
foreach ($var as $key => $value) {
|
|
|
9763 |
// Recursively convert objects.
|
|
|
9764 |
if (is_object($value) || is_array($value)) {
|
|
|
9765 |
$result[$key] = convert_to_array($value);
|
|
|
9766 |
} else {
|
|
|
9767 |
// Simple values are untouched.
|
|
|
9768 |
$result[$key] = $value;
|
|
|
9769 |
}
|
|
|
9770 |
}
|
|
|
9771 |
return $result;
|
|
|
9772 |
}
|
|
|
9773 |
|
|
|
9774 |
/**
|
|
|
9775 |
* Detect a custom script replacement in the data directory that will
|
|
|
9776 |
* replace an existing moodle script
|
|
|
9777 |
*
|
|
|
9778 |
* @return string|bool full path name if a custom script exists, false if no custom script exists
|
|
|
9779 |
*/
|
|
|
9780 |
function custom_script_path() {
|
|
|
9781 |
global $CFG, $SCRIPT;
|
|
|
9782 |
|
|
|
9783 |
if ($SCRIPT === null) {
|
|
|
9784 |
// Probably some weird external script.
|
|
|
9785 |
return false;
|
|
|
9786 |
}
|
|
|
9787 |
|
|
|
9788 |
$scriptpath = $CFG->customscripts . $SCRIPT;
|
|
|
9789 |
|
|
|
9790 |
// Check the custom script exists.
|
|
|
9791 |
if (file_exists($scriptpath) and is_file($scriptpath)) {
|
|
|
9792 |
return $scriptpath;
|
|
|
9793 |
} else {
|
|
|
9794 |
return false;
|
|
|
9795 |
}
|
|
|
9796 |
}
|
|
|
9797 |
|
|
|
9798 |
/**
|
|
|
9799 |
* Returns whether or not the user object is a remote MNET user. This function
|
|
|
9800 |
* is in moodlelib because it does not rely on loading any of the MNET code.
|
|
|
9801 |
*
|
|
|
9802 |
* @param object $user A valid user object
|
|
|
9803 |
* @return bool True if the user is from a remote Moodle.
|
|
|
9804 |
*/
|
|
|
9805 |
function is_mnet_remote_user($user) {
|
|
|
9806 |
global $CFG;
|
|
|
9807 |
|
|
|
9808 |
if (!isset($CFG->mnet_localhost_id)) {
|
|
|
9809 |
include_once($CFG->dirroot . '/mnet/lib.php');
|
|
|
9810 |
$env = new mnet_environment();
|
|
|
9811 |
$env->init();
|
|
|
9812 |
unset($env);
|
|
|
9813 |
}
|
|
|
9814 |
|
|
|
9815 |
return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
|
|
|
9816 |
}
|
|
|
9817 |
|
|
|
9818 |
/**
|
|
|
9819 |
* This function will search for browser prefereed languages, setting Moodle
|
|
|
9820 |
* to use the best one available if $SESSION->lang is undefined
|
|
|
9821 |
*/
|
|
|
9822 |
function setup_lang_from_browser() {
|
|
|
9823 |
global $CFG, $SESSION, $USER;
|
|
|
9824 |
|
|
|
9825 |
if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
|
|
|
9826 |
// Lang is defined in session or user profile, nothing to do.
|
|
|
9827 |
return;
|
|
|
9828 |
}
|
|
|
9829 |
|
|
|
9830 |
if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do.
|
|
|
9831 |
return;
|
|
|
9832 |
}
|
|
|
9833 |
|
|
|
9834 |
// Extract and clean langs from headers.
|
|
|
9835 |
$rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
|
|
9836 |
$rawlangs = str_replace('-', '_', $rawlangs); // We are using underscores.
|
|
|
9837 |
$rawlangs = explode(',', $rawlangs); // Convert to array.
|
|
|
9838 |
$langs = array();
|
|
|
9839 |
|
|
|
9840 |
$order = 1.0;
|
|
|
9841 |
foreach ($rawlangs as $lang) {
|
|
|
9842 |
if (strpos($lang, ';') === false) {
|
|
|
9843 |
$langs[(string)$order] = $lang;
|
|
|
9844 |
$order = $order-0.01;
|
|
|
9845 |
} else {
|
|
|
9846 |
$parts = explode(';', $lang);
|
|
|
9847 |
$pos = strpos($parts[1], '=');
|
|
|
9848 |
$langs[substr($parts[1], $pos+1)] = $parts[0];
|
|
|
9849 |
}
|
|
|
9850 |
}
|
|
|
9851 |
krsort($langs, SORT_NUMERIC);
|
|
|
9852 |
|
|
|
9853 |
// Look for such langs under standard locations.
|
|
|
9854 |
foreach ($langs as $lang) {
|
|
|
9855 |
// Clean it properly for include.
|
|
|
9856 |
$lang = strtolower(clean_param($lang, PARAM_SAFEDIR));
|
|
|
9857 |
if (get_string_manager()->translation_exists($lang, false)) {
|
|
|
9858 |
// If the translation for this language exists then try to set it
|
|
|
9859 |
// for the rest of the session, if this is a read only session then
|
|
|
9860 |
// we can only set it temporarily in $CFG.
|
|
|
9861 |
if (defined('READ_ONLY_SESSION') && !empty($CFG->enable_read_only_sessions)) {
|
|
|
9862 |
$CFG->lang = $lang;
|
|
|
9863 |
} else {
|
|
|
9864 |
$SESSION->lang = $lang;
|
|
|
9865 |
}
|
|
|
9866 |
// We have finished. Go out.
|
|
|
9867 |
break;
|
|
|
9868 |
}
|
|
|
9869 |
}
|
|
|
9870 |
return;
|
|
|
9871 |
}
|
|
|
9872 |
|
|
|
9873 |
/**
|
|
|
9874 |
* Check if $url matches anything in proxybypass list
|
|
|
9875 |
*
|
|
|
9876 |
* Any errors just result in the proxy being used (least bad)
|
|
|
9877 |
*
|
|
|
9878 |
* @param string $url url to check
|
|
|
9879 |
* @return boolean true if we should bypass the proxy
|
|
|
9880 |
*/
|
|
|
9881 |
function is_proxybypass( $url ) {
|
|
|
9882 |
global $CFG;
|
|
|
9883 |
|
|
|
9884 |
// Sanity check.
|
|
|
9885 |
if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
|
|
|
9886 |
return false;
|
|
|
9887 |
}
|
|
|
9888 |
|
|
|
9889 |
// Get the host part out of the url.
|
|
|
9890 |
if (!$host = parse_url( $url, PHP_URL_HOST )) {
|
|
|
9891 |
return false;
|
|
|
9892 |
}
|
|
|
9893 |
|
|
|
9894 |
// Get the possible bypass hosts into an array.
|
|
|
9895 |
$matches = explode( ',', $CFG->proxybypass );
|
|
|
9896 |
|
|
|
9897 |
// Check for a exact match on the IP or in the domains.
|
|
|
9898 |
$isdomaininallowedlist = \core\ip_utils::is_domain_in_allowed_list($host, $matches);
|
|
|
9899 |
$isipinsubnetlist = \core\ip_utils::is_ip_in_subnet_list($host, $CFG->proxybypass, ',');
|
|
|
9900 |
|
|
|
9901 |
if ($isdomaininallowedlist || $isipinsubnetlist) {
|
|
|
9902 |
return true;
|
|
|
9903 |
}
|
|
|
9904 |
|
|
|
9905 |
// Nothing matched.
|
|
|
9906 |
return false;
|
|
|
9907 |
}
|
|
|
9908 |
|
|
|
9909 |
/**
|
|
|
9910 |
* Check if the passed navigation is of the new style
|
|
|
9911 |
*
|
|
|
9912 |
* @param mixed $navigation
|
|
|
9913 |
* @return bool true for yes false for no
|
|
|
9914 |
*/
|
|
|
9915 |
function is_newnav($navigation) {
|
|
|
9916 |
if (is_array($navigation) && !empty($navigation['newnav'])) {
|
|
|
9917 |
return true;
|
|
|
9918 |
} else {
|
|
|
9919 |
return false;
|
|
|
9920 |
}
|
|
|
9921 |
}
|
|
|
9922 |
|
|
|
9923 |
/**
|
|
|
9924 |
* Checks whether the given variable name is defined as a variable within the given object.
|
|
|
9925 |
*
|
|
|
9926 |
* This will NOT work with stdClass objects, which have no class variables.
|
|
|
9927 |
*
|
|
|
9928 |
* @param string $var The variable name
|
|
|
9929 |
* @param object $object The object to check
|
|
|
9930 |
* @return boolean
|
|
|
9931 |
*/
|
|
|
9932 |
function in_object_vars($var, $object) {
|
|
|
9933 |
$classvars = get_class_vars(get_class($object));
|
|
|
9934 |
$classvars = array_keys($classvars);
|
|
|
9935 |
return in_array($var, $classvars);
|
|
|
9936 |
}
|
|
|
9937 |
|
|
|
9938 |
/**
|
|
|
9939 |
* Returns an array without repeated objects.
|
|
|
9940 |
* This function is similar to array_unique, but for arrays that have objects as values
|
|
|
9941 |
*
|
|
|
9942 |
* @param array $array
|
|
|
9943 |
* @param bool $keepkeyassoc
|
|
|
9944 |
* @return array
|
|
|
9945 |
*/
|
|
|
9946 |
function object_array_unique($array, $keepkeyassoc = true) {
|
|
|
9947 |
$duplicatekeys = array();
|
|
|
9948 |
$tmp = array();
|
|
|
9949 |
|
|
|
9950 |
foreach ($array as $key => $val) {
|
|
|
9951 |
// Convert objects to arrays, in_array() does not support objects.
|
|
|
9952 |
if (is_object($val)) {
|
|
|
9953 |
$val = (array)$val;
|
|
|
9954 |
}
|
|
|
9955 |
|
|
|
9956 |
if (!in_array($val, $tmp)) {
|
|
|
9957 |
$tmp[] = $val;
|
|
|
9958 |
} else {
|
|
|
9959 |
$duplicatekeys[] = $key;
|
|
|
9960 |
}
|
|
|
9961 |
}
|
|
|
9962 |
|
|
|
9963 |
foreach ($duplicatekeys as $key) {
|
|
|
9964 |
unset($array[$key]);
|
|
|
9965 |
}
|
|
|
9966 |
|
|
|
9967 |
return $keepkeyassoc ? $array : array_values($array);
|
|
|
9968 |
}
|
|
|
9969 |
|
|
|
9970 |
/**
|
|
|
9971 |
* Is a userid the primary administrator?
|
|
|
9972 |
*
|
|
|
9973 |
* @param int $userid int id of user to check
|
|
|
9974 |
* @return boolean
|
|
|
9975 |
*/
|
|
|
9976 |
function is_primary_admin($userid) {
|
|
|
9977 |
$primaryadmin = get_admin();
|
|
|
9978 |
|
|
|
9979 |
if ($userid == $primaryadmin->id) {
|
|
|
9980 |
return true;
|
|
|
9981 |
} else {
|
|
|
9982 |
return false;
|
|
|
9983 |
}
|
|
|
9984 |
}
|
|
|
9985 |
|
|
|
9986 |
/**
|
|
|
9987 |
* Returns the site identifier
|
|
|
9988 |
*
|
|
|
9989 |
* @return string $CFG->siteidentifier, first making sure it is properly initialised.
|
|
|
9990 |
*/
|
|
|
9991 |
function get_site_identifier() {
|
|
|
9992 |
global $CFG;
|
|
|
9993 |
// Check to see if it is missing. If so, initialise it.
|
|
|
9994 |
if (empty($CFG->siteidentifier)) {
|
|
|
9995 |
set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
|
|
|
9996 |
}
|
|
|
9997 |
// Return it.
|
|
|
9998 |
return $CFG->siteidentifier;
|
|
|
9999 |
}
|
|
|
10000 |
|
|
|
10001 |
/**
|
|
|
10002 |
* Check whether the given password has no more than the specified
|
|
|
10003 |
* number of consecutive identical characters.
|
|
|
10004 |
*
|
|
|
10005 |
* @param string $password password to be checked against the password policy
|
|
|
10006 |
* @param integer $maxchars maximum number of consecutive identical characters
|
|
|
10007 |
* @return bool
|
|
|
10008 |
*/
|
|
|
10009 |
function check_consecutive_identical_characters($password, $maxchars) {
|
|
|
10010 |
|
|
|
10011 |
if ($maxchars < 1) {
|
|
|
10012 |
return true; // Zero 0 is to disable this check.
|
|
|
10013 |
}
|
|
|
10014 |
if (strlen($password) <= $maxchars) {
|
|
|
10015 |
return true; // Too short to fail this test.
|
|
|
10016 |
}
|
|
|
10017 |
|
|
|
10018 |
$previouschar = '';
|
|
|
10019 |
$consecutivecount = 1;
|
|
|
10020 |
foreach (str_split($password) as $char) {
|
|
|
10021 |
if ($char != $previouschar) {
|
|
|
10022 |
$consecutivecount = 1;
|
|
|
10023 |
} else {
|
|
|
10024 |
$consecutivecount++;
|
|
|
10025 |
if ($consecutivecount > $maxchars) {
|
|
|
10026 |
return false; // Check failed already.
|
|
|
10027 |
}
|
|
|
10028 |
}
|
|
|
10029 |
|
|
|
10030 |
$previouschar = $char;
|
|
|
10031 |
}
|
|
|
10032 |
|
|
|
10033 |
return true;
|
|
|
10034 |
}
|
|
|
10035 |
|
|
|
10036 |
/**
|
|
|
10037 |
* Helper function to do partial function binding.
|
|
|
10038 |
* so we can use it for preg_replace_callback, for example
|
|
|
10039 |
* this works with php functions, user functions, static methods and class methods
|
|
|
10040 |
* it returns you a callback that you can pass on like so:
|
|
|
10041 |
*
|
|
|
10042 |
* $callback = partial('somefunction', $arg1, $arg2);
|
|
|
10043 |
* or
|
|
|
10044 |
* $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
|
|
|
10045 |
* or even
|
|
|
10046 |
* $obj = new someclass();
|
|
|
10047 |
* $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
|
|
|
10048 |
*
|
|
|
10049 |
* and then the arguments that are passed through at calltime are appended to the argument list.
|
|
|
10050 |
*
|
|
|
10051 |
* @param mixed $function a php callback
|
|
|
10052 |
* @param mixed $arg1,... $argv arguments to partially bind with
|
|
|
10053 |
* @return array Array callback
|
|
|
10054 |
*/
|
|
|
10055 |
function partial() {
|
|
|
10056 |
if (!class_exists('partial')) {
|
|
|
10057 |
/**
|
|
|
10058 |
* Used to manage function binding.
|
|
|
10059 |
* @copyright 2009 Penny Leach
|
|
|
10060 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
10061 |
*/
|
|
|
10062 |
class partial{
|
|
|
10063 |
/** @var array */
|
|
|
10064 |
public $values = array();
|
|
|
10065 |
/** @var string The function to call as a callback. */
|
|
|
10066 |
public $func;
|
|
|
10067 |
/**
|
|
|
10068 |
* Constructor
|
|
|
10069 |
* @param string $func
|
|
|
10070 |
* @param array $args
|
|
|
10071 |
*/
|
|
|
10072 |
public function __construct($func, $args) {
|
|
|
10073 |
$this->values = $args;
|
|
|
10074 |
$this->func = $func;
|
|
|
10075 |
}
|
|
|
10076 |
/**
|
|
|
10077 |
* Calls the callback function.
|
|
|
10078 |
* @return mixed
|
|
|
10079 |
*/
|
|
|
10080 |
public function method() {
|
|
|
10081 |
$args = func_get_args();
|
|
|
10082 |
return call_user_func_array($this->func, array_merge($this->values, $args));
|
|
|
10083 |
}
|
|
|
10084 |
}
|
|
|
10085 |
}
|
|
|
10086 |
$args = func_get_args();
|
|
|
10087 |
$func = array_shift($args);
|
|
|
10088 |
$p = new partial($func, $args);
|
|
|
10089 |
return array($p, 'method');
|
|
|
10090 |
}
|
|
|
10091 |
|
|
|
10092 |
/**
|
|
|
10093 |
* helper function to load up and initialise the mnet environment
|
|
|
10094 |
* this must be called before you use mnet functions.
|
|
|
10095 |
*
|
|
|
10096 |
* @return mnet_environment the equivalent of old $MNET global
|
|
|
10097 |
*/
|
|
|
10098 |
function get_mnet_environment() {
|
|
|
10099 |
global $CFG;
|
|
|
10100 |
require_once($CFG->dirroot . '/mnet/lib.php');
|
|
|
10101 |
static $instance = null;
|
|
|
10102 |
if (empty($instance)) {
|
|
|
10103 |
$instance = new mnet_environment();
|
|
|
10104 |
$instance->init();
|
|
|
10105 |
}
|
|
|
10106 |
return $instance;
|
|
|
10107 |
}
|
|
|
10108 |
|
|
|
10109 |
/**
|
|
|
10110 |
* during xmlrpc server code execution, any code wishing to access
|
|
|
10111 |
* information about the remote peer must use this to get it.
|
|
|
10112 |
*
|
|
|
10113 |
* @return mnet_remote_client|false the equivalent of old $MNETREMOTE_CLIENT global
|
|
|
10114 |
*/
|
|
|
10115 |
function get_mnet_remote_client() {
|
|
|
10116 |
if (!defined('MNET_SERVER')) {
|
|
|
10117 |
debugging(get_string('notinxmlrpcserver', 'mnet'));
|
|
|
10118 |
return false;
|
|
|
10119 |
}
|
|
|
10120 |
global $MNET_REMOTE_CLIENT;
|
|
|
10121 |
if (isset($MNET_REMOTE_CLIENT)) {
|
|
|
10122 |
return $MNET_REMOTE_CLIENT;
|
|
|
10123 |
}
|
|
|
10124 |
return false;
|
|
|
10125 |
}
|
|
|
10126 |
|
|
|
10127 |
/**
|
|
|
10128 |
* during the xmlrpc server code execution, this will be called
|
|
|
10129 |
* to setup the object returned by {@link get_mnet_remote_client}
|
|
|
10130 |
*
|
|
|
10131 |
* @param mnet_remote_client $client the client to set up
|
|
|
10132 |
* @throws moodle_exception
|
|
|
10133 |
*/
|
|
|
10134 |
function set_mnet_remote_client($client) {
|
|
|
10135 |
if (!defined('MNET_SERVER')) {
|
|
|
10136 |
throw new moodle_exception('notinxmlrpcserver', 'mnet');
|
|
|
10137 |
}
|
|
|
10138 |
global $MNET_REMOTE_CLIENT;
|
|
|
10139 |
$MNET_REMOTE_CLIENT = $client;
|
|
|
10140 |
}
|
|
|
10141 |
|
|
|
10142 |
/**
|
|
|
10143 |
* return the jump url for a given remote user
|
|
|
10144 |
* this is used for rewriting forum post links in emails, etc
|
|
|
10145 |
*
|
|
|
10146 |
* @param stdclass $user the user to get the idp url for
|
|
|
10147 |
*/
|
|
|
10148 |
function mnet_get_idp_jump_url($user) {
|
|
|
10149 |
global $CFG;
|
|
|
10150 |
|
|
|
10151 |
static $mnetjumps = array();
|
|
|
10152 |
if (!array_key_exists($user->mnethostid, $mnetjumps)) {
|
|
|
10153 |
$idp = mnet_get_peer_host($user->mnethostid);
|
|
|
10154 |
$idpjumppath = mnet_get_app_jumppath($idp->applicationid);
|
|
|
10155 |
$mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
|
|
|
10156 |
}
|
|
|
10157 |
return $mnetjumps[$user->mnethostid];
|
|
|
10158 |
}
|
|
|
10159 |
|
|
|
10160 |
/**
|
|
|
10161 |
* Gets the homepage to use for the current user
|
|
|
10162 |
*
|
|
|
10163 |
* @return int One of HOMEPAGE_*
|
|
|
10164 |
*/
|
|
|
10165 |
function get_home_page() {
|
|
|
10166 |
global $CFG;
|
|
|
10167 |
|
|
|
10168 |
if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
|
|
|
10169 |
// If dashboard is disabled, home will be set to default page.
|
|
|
10170 |
$defaultpage = get_default_home_page();
|
|
|
10171 |
if ($CFG->defaulthomepage == HOMEPAGE_MY) {
|
|
|
10172 |
if (!empty($CFG->enabledashboard)) {
|
|
|
10173 |
return HOMEPAGE_MY;
|
|
|
10174 |
} else {
|
|
|
10175 |
return $defaultpage;
|
|
|
10176 |
}
|
|
|
10177 |
} else if ($CFG->defaulthomepage == HOMEPAGE_MYCOURSES) {
|
|
|
10178 |
return HOMEPAGE_MYCOURSES;
|
|
|
10179 |
} else {
|
|
|
10180 |
$userhomepage = (int) get_user_preferences('user_home_page_preference', $defaultpage);
|
|
|
10181 |
if (empty($CFG->enabledashboard) && $userhomepage == HOMEPAGE_MY) {
|
|
|
10182 |
// If the user was using the dashboard but it's disabled, return the default home page.
|
|
|
10183 |
$userhomepage = $defaultpage;
|
|
|
10184 |
}
|
|
|
10185 |
return $userhomepage;
|
|
|
10186 |
}
|
|
|
10187 |
}
|
|
|
10188 |
return HOMEPAGE_SITE;
|
|
|
10189 |
}
|
|
|
10190 |
|
|
|
10191 |
/**
|
|
|
10192 |
* Returns the default home page to display if current one is not defined or can't be applied.
|
|
|
10193 |
* The default behaviour is to return Dashboard if it's enabled or My courses page if it isn't.
|
|
|
10194 |
*
|
|
|
10195 |
* @return int The default home page.
|
|
|
10196 |
*/
|
|
|
10197 |
function get_default_home_page(): int {
|
|
|
10198 |
global $CFG;
|
|
|
10199 |
|
|
|
10200 |
return (!isset($CFG->enabledashboard) || $CFG->enabledashboard) ? HOMEPAGE_MY : HOMEPAGE_MYCOURSES;
|
|
|
10201 |
}
|
|
|
10202 |
|
|
|
10203 |
/**
|
|
|
10204 |
* Gets the name of a course to be displayed when showing a list of courses.
|
|
|
10205 |
* By default this is just $course->fullname but user can configure it. The
|
|
|
10206 |
* result of this function should be passed through print_string.
|
|
|
10207 |
* @param stdClass|core_course_list_element $course Moodle course object
|
|
|
10208 |
* @return string Display name of course (either fullname or short + fullname)
|
|
|
10209 |
*/
|
|
|
10210 |
function get_course_display_name_for_list($course) {
|
|
|
10211 |
global $CFG;
|
|
|
10212 |
if (!empty($CFG->courselistshortnames)) {
|
|
|
10213 |
if (!($course instanceof stdClass)) {
|
|
|
10214 |
$course = (object)convert_to_array($course);
|
|
|
10215 |
}
|
|
|
10216 |
return get_string('courseextendednamedisplay', '', $course);
|
|
|
10217 |
} else {
|
|
|
10218 |
return $course->fullname;
|
|
|
10219 |
}
|
|
|
10220 |
}
|
|
|
10221 |
|
|
|
10222 |
/**
|
|
|
10223 |
* Safe analogue of unserialize() that can only parse arrays
|
|
|
10224 |
*
|
|
|
10225 |
* Arrays may contain only integers or strings as both keys and values. Nested arrays are allowed.
|
|
|
10226 |
*
|
|
|
10227 |
* @param string $expression
|
|
|
10228 |
* @return array|bool either parsed array or false if parsing was impossible.
|
|
|
10229 |
*/
|
|
|
10230 |
function unserialize_array($expression) {
|
|
|
10231 |
|
|
|
10232 |
// Check the expression is an array.
|
|
|
10233 |
if (!preg_match('/^a:(\d+):/', $expression)) {
|
|
|
10234 |
return false;
|
|
|
10235 |
}
|
|
|
10236 |
|
|
|
10237 |
$values = (array) unserialize_object($expression);
|
|
|
10238 |
|
|
|
10239 |
// Callback that returns true if the given value is an unserialized object, executes recursively.
|
|
|
10240 |
$invalidvaluecallback = static function($value) use (&$invalidvaluecallback): bool {
|
|
|
10241 |
if (is_array($value)) {
|
|
|
10242 |
return (bool) array_filter($value, $invalidvaluecallback);
|
|
|
10243 |
}
|
|
|
10244 |
return ($value instanceof stdClass) || ($value instanceof __PHP_Incomplete_Class);
|
|
|
10245 |
};
|
|
|
10246 |
|
|
|
10247 |
// Iterate over the result to ensure there are no stray objects.
|
|
|
10248 |
if (array_filter($values, $invalidvaluecallback)) {
|
|
|
10249 |
return false;
|
|
|
10250 |
}
|
|
|
10251 |
|
|
|
10252 |
return $values;
|
|
|
10253 |
}
|
|
|
10254 |
|
|
|
10255 |
/**
|
|
|
10256 |
* Safe method for unserializing given input that is expected to contain only a serialized instance of an stdClass object
|
|
|
10257 |
*
|
|
|
10258 |
* If any class type other than stdClass is included in the input string, it will not be instantiated and will be cast to an
|
|
|
10259 |
* stdClass object. The initial cast to array, then back to object is to ensure we are always returning the correct type,
|
|
|
10260 |
* otherwise we would return an instances of {@see __PHP_Incomplete_class} for malformed strings
|
|
|
10261 |
*
|
|
|
10262 |
* @param string $input
|
|
|
10263 |
* @return stdClass
|
|
|
10264 |
*/
|
|
|
10265 |
function unserialize_object(string $input): stdClass {
|
|
|
10266 |
$instance = (array) unserialize($input, ['allowed_classes' => [stdClass::class]]);
|
|
|
10267 |
return (object) $instance;
|
|
|
10268 |
}
|
|
|
10269 |
|
|
|
10270 |
/**
|
|
|
10271 |
* The lang_string class
|
|
|
10272 |
*
|
|
|
10273 |
* This special class is used to create an object representation of a string request.
|
|
|
10274 |
* It is special because processing doesn't occur until the object is first used.
|
|
|
10275 |
* The class was created especially to aid performance in areas where strings were
|
|
|
10276 |
* required to be generated but were not necessarily used.
|
|
|
10277 |
* As an example the admin tree when generated uses over 1500 strings, of which
|
|
|
10278 |
* normally only 1/3 are ever actually printed at any time.
|
|
|
10279 |
* The performance advantage is achieved by not actually processing strings that
|
|
|
10280 |
* arn't being used, as such reducing the processing required for the page.
|
|
|
10281 |
*
|
|
|
10282 |
* How to use the lang_string class?
|
|
|
10283 |
* There are two methods of using the lang_string class, first through the
|
|
|
10284 |
* forth argument of the get_string function, and secondly directly.
|
|
|
10285 |
* The following are examples of both.
|
|
|
10286 |
* 1. Through get_string calls e.g.
|
|
|
10287 |
* $string = get_string($identifier, $component, $a, true);
|
|
|
10288 |
* $string = get_string('yes', 'moodle', null, true);
|
|
|
10289 |
* 2. Direct instantiation
|
|
|
10290 |
* $string = new lang_string($identifier, $component, $a, $lang);
|
|
|
10291 |
* $string = new lang_string('yes');
|
|
|
10292 |
*
|
|
|
10293 |
* How do I use a lang_string object?
|
|
|
10294 |
* The lang_string object makes use of a magic __toString method so that you
|
|
|
10295 |
* are able to use the object exactly as you would use a string in most cases.
|
|
|
10296 |
* This means you are able to collect it into a variable and then directly
|
|
|
10297 |
* echo it, or concatenate it into another string, or similar.
|
|
|
10298 |
* The other thing you can do is manually get the string by calling the
|
|
|
10299 |
* lang_strings out method e.g.
|
|
|
10300 |
* $string = new lang_string('yes');
|
|
|
10301 |
* $string->out();
|
|
|
10302 |
* Also worth noting is that the out method can take one argument, $lang which
|
|
|
10303 |
* allows the developer to change the language on the fly.
|
|
|
10304 |
*
|
|
|
10305 |
* When should I use a lang_string object?
|
|
|
10306 |
* The lang_string object is designed to be used in any situation where a
|
|
|
10307 |
* string may not be needed, but needs to be generated.
|
|
|
10308 |
* The admin tree is a good example of where lang_string objects should be
|
|
|
10309 |
* used.
|
|
|
10310 |
* A more practical example would be any class that requries strings that may
|
|
|
10311 |
* not be printed (after all classes get renderer by renderers and who knows
|
|
|
10312 |
* what they will do ;))
|
|
|
10313 |
*
|
|
|
10314 |
* When should I not use a lang_string object?
|
|
|
10315 |
* Don't use lang_strings when you are going to use a string immediately.
|
|
|
10316 |
* There is no need as it will be processed immediately and there will be no
|
|
|
10317 |
* advantage, and in fact perhaps a negative hit as a class has to be
|
|
|
10318 |
* instantiated for a lang_string object, however get_string won't require
|
|
|
10319 |
* that.
|
|
|
10320 |
*
|
|
|
10321 |
* Limitations:
|
|
|
10322 |
* 1. You cannot use a lang_string object as an array offset. Doing so will
|
|
|
10323 |
* result in PHP throwing an error. (You can use it as an object property!)
|
|
|
10324 |
*
|
|
|
10325 |
* @package core
|
|
|
10326 |
* @category string
|
|
|
10327 |
* @copyright 2011 Sam Hemelryk
|
|
|
10328 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
10329 |
*/
|
|
|
10330 |
class lang_string {
|
|
|
10331 |
|
|
|
10332 |
/** @var string The strings identifier */
|
|
|
10333 |
protected $identifier;
|
|
|
10334 |
/** @var string The strings component. Default '' */
|
|
|
10335 |
protected $component = '';
|
|
|
10336 |
/** @var array|stdClass Any arguments required for the string. Default null */
|
|
|
10337 |
protected $a = null;
|
|
|
10338 |
/** @var string The language to use when processing the string. Default null */
|
|
|
10339 |
protected $lang = null;
|
|
|
10340 |
|
|
|
10341 |
/** @var string The processed string (once processed) */
|
|
|
10342 |
protected $string = null;
|
|
|
10343 |
|
|
|
10344 |
/**
|
|
|
10345 |
* A special boolean. If set to true then the object has been woken up and
|
|
|
10346 |
* cannot be regenerated. If this is set then $this->string MUST be used.
|
|
|
10347 |
* @var bool
|
|
|
10348 |
*/
|
|
|
10349 |
protected $forcedstring = false;
|
|
|
10350 |
|
|
|
10351 |
/**
|
|
|
10352 |
* Constructs a lang_string object
|
|
|
10353 |
*
|
|
|
10354 |
* This function should do as little processing as possible to ensure the best
|
|
|
10355 |
* performance for strings that won't be used.
|
|
|
10356 |
*
|
|
|
10357 |
* @param string $identifier The strings identifier
|
|
|
10358 |
* @param string $component The strings component
|
|
|
10359 |
* @param stdClass|array|mixed $a Any arguments the string requires
|
|
|
10360 |
* @param string $lang The language to use when processing the string.
|
|
|
10361 |
* @throws coding_exception
|
|
|
10362 |
*/
|
|
|
10363 |
public function __construct($identifier, $component = '', $a = null, $lang = null) {
|
|
|
10364 |
if (empty($component)) {
|
|
|
10365 |
$component = 'moodle';
|
|
|
10366 |
}
|
|
|
10367 |
|
|
|
10368 |
$this->identifier = $identifier;
|
|
|
10369 |
$this->component = $component;
|
|
|
10370 |
$this->lang = $lang;
|
|
|
10371 |
|
|
|
10372 |
// We MUST duplicate $a to ensure that it if it changes by reference those
|
|
|
10373 |
// changes are not carried across.
|
|
|
10374 |
// To do this we always ensure $a or its properties/values are strings
|
|
|
10375 |
// and that any properties/values that arn't convertable are forgotten.
|
|
|
10376 |
if ($a !== null) {
|
|
|
10377 |
if (is_scalar($a)) {
|
|
|
10378 |
$this->a = $a;
|
|
|
10379 |
} else if ($a instanceof lang_string) {
|
|
|
10380 |
$this->a = $a->out();
|
|
|
10381 |
} else if (is_object($a) or is_array($a)) {
|
|
|
10382 |
$a = (array)$a;
|
|
|
10383 |
$this->a = array();
|
|
|
10384 |
foreach ($a as $key => $value) {
|
|
|
10385 |
// Make sure conversion errors don't get displayed (results in '').
|
|
|
10386 |
if (is_array($value)) {
|
|
|
10387 |
$this->a[$key] = '';
|
|
|
10388 |
} else if (is_object($value)) {
|
|
|
10389 |
if (method_exists($value, '__toString')) {
|
|
|
10390 |
$this->a[$key] = $value->__toString();
|
|
|
10391 |
} else {
|
|
|
10392 |
$this->a[$key] = '';
|
|
|
10393 |
}
|
|
|
10394 |
} else {
|
|
|
10395 |
$this->a[$key] = (string)$value;
|
|
|
10396 |
}
|
|
|
10397 |
}
|
|
|
10398 |
}
|
|
|
10399 |
}
|
|
|
10400 |
|
|
|
10401 |
if (debugging(false, DEBUG_DEVELOPER)) {
|
|
|
10402 |
if (clean_param($this->identifier, PARAM_STRINGID) == '') {
|
|
|
10403 |
throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
|
|
|
10404 |
}
|
|
|
10405 |
if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') {
|
|
|
10406 |
throw new coding_exception('Invalid string compontent. Please check your string definition');
|
|
|
10407 |
}
|
|
|
10408 |
if (!get_string_manager()->string_exists($this->identifier, $this->component)) {
|
|
|
10409 |
debugging('String does not exist. Please check your string definition for '.$this->identifier.'/'.$this->component, DEBUG_DEVELOPER);
|
|
|
10410 |
}
|
|
|
10411 |
}
|
|
|
10412 |
}
|
|
|
10413 |
|
|
|
10414 |
/**
|
|
|
10415 |
* Processes the string.
|
|
|
10416 |
*
|
|
|
10417 |
* This function actually processes the string, stores it in the string property
|
|
|
10418 |
* and then returns it.
|
|
|
10419 |
* You will notice that this function is VERY similar to the get_string method.
|
|
|
10420 |
* That is because it is pretty much doing the same thing.
|
|
|
10421 |
* However as this function is an upgrade it isn't as tolerant to backwards
|
|
|
10422 |
* compatibility.
|
|
|
10423 |
*
|
|
|
10424 |
* @return string
|
|
|
10425 |
* @throws coding_exception
|
|
|
10426 |
*/
|
|
|
10427 |
protected function get_string() {
|
|
|
10428 |
global $CFG;
|
|
|
10429 |
|
|
|
10430 |
// Check if we need to process the string.
|
|
|
10431 |
if ($this->string === null) {
|
|
|
10432 |
// Check the quality of the identifier.
|
|
|
10433 |
if ($CFG->debugdeveloper && clean_param($this->identifier, PARAM_STRINGID) === '') {
|
|
|
10434 |
throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition', DEBUG_DEVELOPER);
|
|
|
10435 |
}
|
|
|
10436 |
|
|
|
10437 |
// Process the string.
|
|
|
10438 |
$this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
|
|
|
10439 |
// Debugging feature lets you display string identifier and component.
|
|
|
10440 |
if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
|
|
|
10441 |
$this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
|
|
|
10442 |
}
|
|
|
10443 |
}
|
|
|
10444 |
// Return the string.
|
|
|
10445 |
return $this->string;
|
|
|
10446 |
}
|
|
|
10447 |
|
|
|
10448 |
/**
|
|
|
10449 |
* Returns the string
|
|
|
10450 |
*
|
|
|
10451 |
* @param string $lang The langauge to use when processing the string
|
|
|
10452 |
* @return string
|
|
|
10453 |
*/
|
|
|
10454 |
public function out($lang = null) {
|
|
|
10455 |
if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
|
|
|
10456 |
if ($this->forcedstring) {
|
|
|
10457 |
debugging('lang_string objects that have been used cannot be printed in another language. ('.$this->lang.' used)', DEBUG_DEVELOPER);
|
|
|
10458 |
return $this->get_string();
|
|
|
10459 |
}
|
|
|
10460 |
$translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
|
|
|
10461 |
return $translatedstring->out();
|
|
|
10462 |
}
|
|
|
10463 |
return $this->get_string();
|
|
|
10464 |
}
|
|
|
10465 |
|
|
|
10466 |
/**
|
|
|
10467 |
* Magic __toString method for printing a string
|
|
|
10468 |
*
|
|
|
10469 |
* @return string
|
|
|
10470 |
*/
|
|
|
10471 |
public function __toString() {
|
|
|
10472 |
return $this->get_string();
|
|
|
10473 |
}
|
|
|
10474 |
|
|
|
10475 |
/**
|
|
|
10476 |
* Magic __set_state method used for var_export
|
|
|
10477 |
*
|
|
|
10478 |
* @param array $array
|
|
|
10479 |
* @return self
|
|
|
10480 |
*/
|
|
|
10481 |
public static function __set_state(array $array): self {
|
|
|
10482 |
$tmp = new lang_string($array['identifier'], $array['component'], $array['a'], $array['lang']);
|
|
|
10483 |
$tmp->string = $array['string'];
|
|
|
10484 |
$tmp->forcedstring = $array['forcedstring'];
|
|
|
10485 |
return $tmp;
|
|
|
10486 |
}
|
|
|
10487 |
|
|
|
10488 |
/**
|
|
|
10489 |
* Prepares the lang_string for sleep and stores only the forcedstring and
|
|
|
10490 |
* string properties... the string cannot be regenerated so we need to ensure
|
|
|
10491 |
* it is generated for this.
|
|
|
10492 |
*
|
|
|
10493 |
* @return array
|
|
|
10494 |
*/
|
|
|
10495 |
public function __sleep() {
|
|
|
10496 |
$this->get_string();
|
|
|
10497 |
$this->forcedstring = true;
|
|
|
10498 |
return array('forcedstring', 'string', 'lang');
|
|
|
10499 |
}
|
|
|
10500 |
|
|
|
10501 |
/**
|
|
|
10502 |
* Returns the identifier.
|
|
|
10503 |
*
|
|
|
10504 |
* @return string
|
|
|
10505 |
*/
|
|
|
10506 |
public function get_identifier() {
|
|
|
10507 |
return $this->identifier;
|
|
|
10508 |
}
|
|
|
10509 |
|
|
|
10510 |
/**
|
|
|
10511 |
* Returns the component.
|
|
|
10512 |
*
|
|
|
10513 |
* @return string
|
|
|
10514 |
*/
|
|
|
10515 |
public function get_component() {
|
|
|
10516 |
return $this->component;
|
|
|
10517 |
}
|
|
|
10518 |
}
|
|
|
10519 |
|
|
|
10520 |
/**
|
|
|
10521 |
* Get human readable name describing the given callable.
|
|
|
10522 |
*
|
|
|
10523 |
* This performs syntax check only to see if the given param looks like a valid function, method or closure.
|
|
|
10524 |
* It does not check if the callable actually exists.
|
|
|
10525 |
*
|
|
|
10526 |
* @param callable|string|array $callable
|
|
|
10527 |
* @return string|bool Human readable name of callable, or false if not a valid callable.
|
|
|
10528 |
*/
|
|
|
10529 |
function get_callable_name($callable) {
|
|
|
10530 |
|
|
|
10531 |
if (!is_callable($callable, true, $name)) {
|
|
|
10532 |
return false;
|
|
|
10533 |
|
|
|
10534 |
} else {
|
|
|
10535 |
return $name;
|
|
|
10536 |
}
|
|
|
10537 |
}
|
|
|
10538 |
|
|
|
10539 |
/**
|
|
|
10540 |
* Tries to guess if $CFG->wwwroot is publicly accessible or not.
|
|
|
10541 |
* Never put your faith on this function and rely on its accuracy as there might be false positives.
|
|
|
10542 |
* It just performs some simple checks, and mainly is used for places where we want to hide some options
|
|
|
10543 |
* such as site registration when $CFG->wwwroot is not publicly accessible.
|
|
|
10544 |
* Good thing is there is no false negative.
|
|
|
10545 |
* Note that it's possible to force the result of this check by specifying $CFG->site_is_public in config.php
|
|
|
10546 |
*
|
|
|
10547 |
* @return bool
|
|
|
10548 |
*/
|
|
|
10549 |
function site_is_public() {
|
|
|
10550 |
global $CFG;
|
|
|
10551 |
|
|
|
10552 |
// Return early if site admin has forced this setting.
|
|
|
10553 |
if (isset($CFG->site_is_public)) {
|
|
|
10554 |
return (bool)$CFG->site_is_public;
|
|
|
10555 |
}
|
|
|
10556 |
|
|
|
10557 |
$host = parse_url($CFG->wwwroot, PHP_URL_HOST);
|
|
|
10558 |
|
|
|
10559 |
if ($host === 'localhost' || preg_match('|^127\.\d+\.\d+\.\d+$|', $host)) {
|
|
|
10560 |
$ispublic = false;
|
|
|
10561 |
} else if (\core\ip_utils::is_ip_address($host) && !ip_is_public($host)) {
|
|
|
10562 |
$ispublic = false;
|
|
|
10563 |
} else if (($address = \core\ip_utils::get_ip_address($host)) && !ip_is_public($address)) {
|
|
|
10564 |
$ispublic = false;
|
|
|
10565 |
} else {
|
|
|
10566 |
$ispublic = true;
|
|
|
10567 |
}
|
|
|
10568 |
|
|
|
10569 |
return $ispublic;
|
|
|
10570 |
}
|
|
|
10571 |
|
|
|
10572 |
/**
|
|
|
10573 |
* Validates user's password length.
|
|
|
10574 |
*
|
|
|
10575 |
* @param string $password
|
|
|
10576 |
* @param int $pepperlength The length of the used peppers
|
|
|
10577 |
* @return bool
|
|
|
10578 |
*/
|
|
|
10579 |
function exceeds_password_length(string $password, int $pepperlength = 0): bool {
|
|
|
10580 |
return (strlen($password) > (MAX_PASSWORD_CHARACTERS + $pepperlength));
|
|
|
10581 |
}
|
|
|
10582 |
|
|
|
10583 |
/**
|
|
|
10584 |
* A helper to replace PHP 8.3 usage of array_keys with two args.
|
|
|
10585 |
*
|
|
|
10586 |
* There is an indication that this will become a new method in PHP 8.4, but that has not happened yet.
|
|
|
10587 |
* Therefore this non-polyfill has been created with a different naming convention.
|
|
|
10588 |
* In the future it can be deprecated if a core PHP method is created.
|
|
|
10589 |
*
|
|
|
10590 |
* https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#array_keys
|
|
|
10591 |
*
|
|
|
10592 |
* @param array $array
|
|
|
10593 |
* @param mixed $filter The value to filter on
|
|
|
10594 |
* @param bool $strict Whether to apply a strit test with the filter
|
|
|
10595 |
* @return array
|
|
|
10596 |
*/
|
|
|
10597 |
function moodle_array_keys_filter(array $array, mixed $filter, bool $strict = false): array {
|
|
|
10598 |
return array_keys(array_filter(
|
|
|
10599 |
$array,
|
|
|
10600 |
function($value, $key) use ($filter, $strict): bool {
|
|
|
10601 |
if ($strict) {
|
|
|
10602 |
return $value === $filter;
|
|
|
10603 |
}
|
|
|
10604 |
return $value == $filter;
|
|
|
10605 |
},
|
|
|
10606 |
ARRAY_FILTER_USE_BOTH,
|
|
|
10607 |
));
|
|
|
10608 |
}
|