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 |
* These functions are required very early in the Moodle
|
|
|
19 |
* setup process, before any of the main libraries are
|
|
|
20 |
* loaded.
|
|
|
21 |
*
|
|
|
22 |
* @package core
|
|
|
23 |
* @subpackage lib
|
|
|
24 |
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
|
|
25 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
26 |
*/
|
|
|
27 |
|
|
|
28 |
defined('MOODLE_INTERNAL') || die();
|
|
|
29 |
|
|
|
30 |
// Debug levels - always keep the values in ascending order!
|
|
|
31 |
/** No warnings and errors at all */
|
|
|
32 |
define('DEBUG_NONE', 0);
|
|
|
33 |
/** Fatal errors only */
|
|
|
34 |
define('DEBUG_MINIMAL', E_ERROR | E_PARSE);
|
|
|
35 |
/** Errors, warnings and notices */
|
|
|
36 |
define('DEBUG_NORMAL', E_ERROR | E_PARSE | E_WARNING | E_NOTICE);
|
|
|
37 |
/** All problems except strict PHP warnings */
|
|
|
38 |
define('DEBUG_ALL', E_ALL & ~E_STRICT);
|
|
|
39 |
/** DEBUG_ALL with all debug messages and strict warnings */
|
|
|
40 |
define('DEBUG_DEVELOPER', E_ALL | E_STRICT);
|
|
|
41 |
|
|
|
42 |
/** Remove any memory limits */
|
|
|
43 |
define('MEMORY_UNLIMITED', -1);
|
|
|
44 |
/** Standard memory limit for given platform */
|
|
|
45 |
define('MEMORY_STANDARD', -2);
|
|
|
46 |
/**
|
|
|
47 |
* Large memory limit for given platform - used in cron, upgrade, and other places that need a lot of memory.
|
|
|
48 |
* Can be overridden with $CFG->extramemorylimit setting.
|
|
|
49 |
*/
|
|
|
50 |
define('MEMORY_EXTRA', -3);
|
|
|
51 |
/** Extremely large memory limit - not recommended for standard scripts */
|
|
|
52 |
define('MEMORY_HUGE', -4);
|
|
|
53 |
|
|
|
54 |
/**
|
|
|
55 |
* Base Moodle Exception class
|
|
|
56 |
*
|
|
|
57 |
* Although this class is defined here, you cannot throw a moodle_exception until
|
|
|
58 |
* after moodlelib.php has been included (which will happen very soon).
|
|
|
59 |
*
|
|
|
60 |
* @package core
|
|
|
61 |
* @subpackage lib
|
|
|
62 |
* @copyright 2008 Petr Skoda {@link http://skodak.org}
|
|
|
63 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
64 |
*/
|
|
|
65 |
class moodle_exception extends Exception {
|
|
|
66 |
|
|
|
67 |
/**
|
|
|
68 |
* @var string The name of the string from error.php to print
|
|
|
69 |
*/
|
|
|
70 |
public $errorcode;
|
|
|
71 |
|
|
|
72 |
/**
|
|
|
73 |
* @var string The name of module
|
|
|
74 |
*/
|
|
|
75 |
public $module;
|
|
|
76 |
|
|
|
77 |
/**
|
|
|
78 |
* @var mixed Extra words and phrases that might be required in the error string
|
|
|
79 |
*/
|
|
|
80 |
public $a;
|
|
|
81 |
|
|
|
82 |
/**
|
|
|
83 |
* @var string The url where the user will be prompted to continue. If no url is provided the user will be directed to the site index page.
|
|
|
84 |
*/
|
|
|
85 |
public $link;
|
|
|
86 |
|
|
|
87 |
/**
|
|
|
88 |
* @var string Optional information to aid the debugging process
|
|
|
89 |
*/
|
|
|
90 |
public $debuginfo;
|
|
|
91 |
|
|
|
92 |
/**
|
|
|
93 |
* Constructor
|
|
|
94 |
* @param string $errorcode The name of the string from error.php to print
|
|
|
95 |
* @param string $module name of module
|
|
|
96 |
* @param string $link The url where the user will be prompted to continue. If no url is provided the user will be directed to the site index page.
|
|
|
97 |
* @param mixed $a Extra words and phrases that might be required in the error string
|
|
|
98 |
* @param string $debuginfo optional debugging information
|
|
|
99 |
*/
|
|
|
100 |
function __construct($errorcode, $module='', $link='', $a=NULL, $debuginfo=null) {
|
|
|
101 |
global $CFG;
|
|
|
102 |
|
|
|
103 |
if (empty($module) || $module == 'moodle' || $module == 'core') {
|
|
|
104 |
$module = 'error';
|
|
|
105 |
}
|
|
|
106 |
|
|
|
107 |
$this->errorcode = $errorcode;
|
|
|
108 |
$this->module = $module;
|
|
|
109 |
$this->link = $link;
|
|
|
110 |
$this->a = $a;
|
|
|
111 |
$this->debuginfo = is_null($debuginfo) ? null : (string)$debuginfo;
|
|
|
112 |
|
|
|
113 |
if (get_string_manager()->string_exists($errorcode, $module)) {
|
|
|
114 |
$message = get_string($errorcode, $module, $a);
|
|
|
115 |
$haserrorstring = true;
|
|
|
116 |
} else {
|
|
|
117 |
$message = $module . '/' . $errorcode;
|
|
|
118 |
$haserrorstring = false;
|
|
|
119 |
}
|
|
|
120 |
|
|
|
121 |
$isinphpunittest = (defined('PHPUNIT_TEST') && PHPUNIT_TEST);
|
|
|
122 |
$hasdebugdeveloper = (
|
|
|
123 |
isset($CFG->debugdisplay) &&
|
|
|
124 |
isset($CFG->debug) &&
|
|
|
125 |
$CFG->debugdisplay &&
|
|
|
126 |
$CFG->debug === DEBUG_DEVELOPER
|
|
|
127 |
);
|
|
|
128 |
|
|
|
129 |
if ($debuginfo) {
|
|
|
130 |
if ($isinphpunittest || $hasdebugdeveloper) {
|
|
|
131 |
$message = "$message ($debuginfo)";
|
|
|
132 |
}
|
|
|
133 |
}
|
|
|
134 |
|
|
|
135 |
if (!$haserrorstring and $isinphpunittest) {
|
|
|
136 |
// Append the contents of $a to $debuginfo so helpful information isn't lost.
|
|
|
137 |
// This emulates what {@link get_exception_info()} does. Unfortunately that
|
|
|
138 |
// function is not used by phpunit.
|
|
|
139 |
$message .= PHP_EOL.'$a contents: '.print_r($a, true);
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
parent::__construct($message, 0);
|
|
|
143 |
}
|
|
|
144 |
}
|
|
|
145 |
|
|
|
146 |
/**
|
|
|
147 |
* Course/activity access exception.
|
|
|
148 |
*
|
|
|
149 |
* This exception is thrown from require_login()
|
|
|
150 |
*
|
|
|
151 |
* @package core_access
|
|
|
152 |
* @copyright 2010 Petr Skoda {@link http://skodak.org}
|
|
|
153 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
154 |
*/
|
|
|
155 |
class require_login_exception extends moodle_exception {
|
|
|
156 |
/**
|
|
|
157 |
* Constructor
|
|
|
158 |
* @param string $debuginfo Information to aid the debugging process
|
|
|
159 |
*/
|
|
|
160 |
function __construct($debuginfo) {
|
|
|
161 |
parent::__construct('requireloginerror', 'error', '', NULL, $debuginfo);
|
|
|
162 |
}
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
/**
|
|
|
166 |
* Session timeout exception.
|
|
|
167 |
*
|
|
|
168 |
* This exception is thrown from require_login()
|
|
|
169 |
*
|
|
|
170 |
* @package core_access
|
|
|
171 |
* @copyright 2015 Andrew Nicols <andrew@nicols.co.uk>
|
|
|
172 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
173 |
*/
|
|
|
174 |
class require_login_session_timeout_exception extends require_login_exception {
|
|
|
175 |
/**
|
|
|
176 |
* Constructor
|
|
|
177 |
*/
|
|
|
178 |
public function __construct() {
|
|
|
179 |
moodle_exception::__construct('sessionerroruser', 'error');
|
|
|
180 |
}
|
|
|
181 |
}
|
|
|
182 |
|
|
|
183 |
/**
|
|
|
184 |
* Web service parameter exception class
|
|
|
185 |
* @deprecated since Moodle 2.2 - use moodle exception instead
|
|
|
186 |
* This exception must be thrown to the web service client when a web service parameter is invalid
|
|
|
187 |
* The error string is gotten from webservice.php
|
|
|
188 |
*/
|
|
|
189 |
class webservice_parameter_exception extends moodle_exception {
|
|
|
190 |
/**
|
|
|
191 |
* Constructor
|
|
|
192 |
* @param string $errorcode The name of the string from webservice.php to print
|
|
|
193 |
* @param string $a The name of the parameter
|
|
|
194 |
* @param string $debuginfo Optional information to aid debugging
|
|
|
195 |
*/
|
|
|
196 |
function __construct($errorcode=null, $a = '', $debuginfo = null) {
|
|
|
197 |
parent::__construct($errorcode, 'webservice', '', $a, $debuginfo);
|
|
|
198 |
}
|
|
|
199 |
}
|
|
|
200 |
|
|
|
201 |
/**
|
|
|
202 |
* Exceptions indicating user does not have permissions to do something
|
|
|
203 |
* and the execution can not continue.
|
|
|
204 |
*
|
|
|
205 |
* @package core_access
|
|
|
206 |
* @copyright 2009 Petr Skoda {@link http://skodak.org}
|
|
|
207 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
208 |
*/
|
|
|
209 |
class required_capability_exception extends moodle_exception {
|
|
|
210 |
/**
|
|
|
211 |
* Constructor
|
|
|
212 |
* @param context $context The context used for the capability check
|
|
|
213 |
* @param string $capability The required capability
|
|
|
214 |
* @param string $errormessage The error message to show the user
|
|
|
215 |
* @param string $stringfile
|
|
|
216 |
*/
|
|
|
217 |
function __construct($context, $capability, $errormessage, $stringfile) {
|
|
|
218 |
$capabilityname = get_capability_string($capability);
|
|
|
219 |
if ($context->contextlevel == CONTEXT_MODULE and preg_match('/:view$/', $capability)) {
|
|
|
220 |
// we can not go to mod/xx/view.php because we most probably do not have cap to view it, let's go to course instead
|
|
|
221 |
$parentcontext = $context->get_parent_context();
|
|
|
222 |
$link = $parentcontext->get_url();
|
|
|
223 |
} else {
|
|
|
224 |
$link = $context->get_url();
|
|
|
225 |
}
|
|
|
226 |
parent::__construct($errormessage, $stringfile, $link, $capabilityname);
|
|
|
227 |
}
|
|
|
228 |
}
|
|
|
229 |
|
|
|
230 |
/**
|
|
|
231 |
* Exception indicating programming error, must be fixed by a programer. For example
|
|
|
232 |
* a core API might throw this type of exception if a plugin calls it incorrectly.
|
|
|
233 |
*
|
|
|
234 |
* @package core
|
|
|
235 |
* @subpackage lib
|
|
|
236 |
* @copyright 2008 Petr Skoda {@link http://skodak.org}
|
|
|
237 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
238 |
*/
|
|
|
239 |
class coding_exception extends moodle_exception {
|
|
|
240 |
/**
|
|
|
241 |
* Constructor
|
|
|
242 |
* @param string $hint short description of problem
|
|
|
243 |
* @param string $debuginfo detailed information how to fix problem
|
|
|
244 |
*/
|
|
|
245 |
function __construct($hint, $debuginfo=null) {
|
|
|
246 |
parent::__construct('codingerror', 'debug', '', $hint, $debuginfo);
|
|
|
247 |
}
|
|
|
248 |
}
|
|
|
249 |
|
|
|
250 |
/**
|
|
|
251 |
* Exception indicating malformed parameter problem.
|
|
|
252 |
* This exception is not supposed to be thrown when processing
|
|
|
253 |
* user submitted data in forms. It is more suitable
|
|
|
254 |
* for WS and other low level stuff.
|
|
|
255 |
*
|
|
|
256 |
* @package core
|
|
|
257 |
* @subpackage lib
|
|
|
258 |
* @copyright 2009 Petr Skoda {@link http://skodak.org}
|
|
|
259 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
260 |
*/
|
|
|
261 |
class invalid_parameter_exception extends moodle_exception {
|
|
|
262 |
/**
|
|
|
263 |
* Constructor
|
|
|
264 |
* @param string $debuginfo some detailed information
|
|
|
265 |
*/
|
|
|
266 |
function __construct($debuginfo=null) {
|
|
|
267 |
parent::__construct('invalidparameter', 'debug', '', null, $debuginfo);
|
|
|
268 |
}
|
|
|
269 |
}
|
|
|
270 |
|
|
|
271 |
/**
|
|
|
272 |
* Exception indicating malformed response problem.
|
|
|
273 |
* This exception is not supposed to be thrown when processing
|
|
|
274 |
* user submitted data in forms. It is more suitable
|
|
|
275 |
* for WS and other low level stuff.
|
|
|
276 |
*/
|
|
|
277 |
class invalid_response_exception extends moodle_exception {
|
|
|
278 |
/**
|
|
|
279 |
* Constructor
|
|
|
280 |
* @param string $debuginfo some detailed information
|
|
|
281 |
*/
|
|
|
282 |
function __construct($debuginfo=null) {
|
|
|
283 |
parent::__construct('invalidresponse', 'debug', '', null, $debuginfo);
|
|
|
284 |
}
|
|
|
285 |
}
|
|
|
286 |
|
|
|
287 |
/**
|
|
|
288 |
* An exception that indicates something really weird happened. For example,
|
|
|
289 |
* if you do switch ($context->contextlevel), and have one case for each
|
|
|
290 |
* CONTEXT_... constant. You might throw an invalid_state_exception in the
|
|
|
291 |
* default case, to just in case something really weird is going on, and
|
|
|
292 |
* $context->contextlevel is invalid - rather than ignoring this possibility.
|
|
|
293 |
*
|
|
|
294 |
* @package core
|
|
|
295 |
* @subpackage lib
|
|
|
296 |
* @copyright 2009 onwards Martin Dougiamas {@link http://moodle.com}
|
|
|
297 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
298 |
*/
|
|
|
299 |
class invalid_state_exception extends moodle_exception {
|
|
|
300 |
/**
|
|
|
301 |
* Constructor
|
|
|
302 |
* @param string $hint short description of problem
|
|
|
303 |
* @param string $debuginfo optional more detailed information
|
|
|
304 |
*/
|
|
|
305 |
function __construct($hint, $debuginfo=null) {
|
|
|
306 |
parent::__construct('invalidstatedetected', 'debug', '', $hint, $debuginfo);
|
|
|
307 |
}
|
|
|
308 |
}
|
|
|
309 |
|
|
|
310 |
/**
|
|
|
311 |
* An exception that indicates incorrect permissions in $CFG->dataroot
|
|
|
312 |
*
|
|
|
313 |
* @package core
|
|
|
314 |
* @subpackage lib
|
|
|
315 |
* @copyright 2010 Petr Skoda {@link http://skodak.org}
|
|
|
316 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
317 |
*/
|
|
|
318 |
class invalid_dataroot_permissions extends moodle_exception {
|
|
|
319 |
/**
|
|
|
320 |
* Constructor
|
|
|
321 |
* @param string $debuginfo optional more detailed information
|
|
|
322 |
*/
|
|
|
323 |
function __construct($debuginfo = NULL) {
|
|
|
324 |
parent::__construct('invaliddatarootpermissions', 'error', '', NULL, $debuginfo);
|
|
|
325 |
}
|
|
|
326 |
}
|
|
|
327 |
|
|
|
328 |
/**
|
|
|
329 |
* An exception that indicates that file can not be served
|
|
|
330 |
*
|
|
|
331 |
* @package core
|
|
|
332 |
* @subpackage lib
|
|
|
333 |
* @copyright 2010 Petr Skoda {@link http://skodak.org}
|
|
|
334 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
335 |
*/
|
|
|
336 |
class file_serving_exception extends moodle_exception {
|
|
|
337 |
/**
|
|
|
338 |
* Constructor
|
|
|
339 |
* @param string $debuginfo optional more detailed information
|
|
|
340 |
*/
|
|
|
341 |
function __construct($debuginfo = NULL) {
|
|
|
342 |
parent::__construct('cannotservefile', 'error', '', NULL, $debuginfo);
|
|
|
343 |
}
|
|
|
344 |
}
|
|
|
345 |
|
|
|
346 |
/**
|
|
|
347 |
* Get the Whoops! handler.
|
|
|
348 |
*
|
|
|
349 |
* @return \Whoops\Run|null
|
|
|
350 |
*/
|
|
|
351 |
function get_whoops(): ?\Whoops\Run {
|
|
|
352 |
global $CFG;
|
|
|
353 |
|
|
|
354 |
if (CLI_SCRIPT || AJAX_SCRIPT) {
|
|
|
355 |
return null;
|
|
|
356 |
}
|
|
|
357 |
|
|
|
358 |
if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
|
|
|
359 |
return null;
|
|
|
360 |
}
|
|
|
361 |
|
|
|
362 |
if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) {
|
|
|
363 |
return null;
|
|
|
364 |
}
|
|
|
365 |
|
|
|
366 |
if (!$CFG->debugdisplay) {
|
|
|
367 |
return null;
|
|
|
368 |
}
|
|
|
369 |
|
|
|
370 |
if (!$CFG->debug_developer_use_pretty_exceptions) {
|
|
|
371 |
return null;
|
|
|
372 |
}
|
|
|
373 |
|
|
|
374 |
$composerautoload = "{$CFG->dirroot}/vendor/autoload.php";
|
|
|
375 |
if (file_exists($composerautoload)) {
|
|
|
376 |
require_once($composerautoload);
|
|
|
377 |
}
|
|
|
378 |
|
|
|
379 |
if (!class_exists(\Whoops\Run::class)) {
|
|
|
380 |
return null;
|
|
|
381 |
}
|
|
|
382 |
|
|
|
383 |
// We have Whoops available, use it.
|
|
|
384 |
$whoops = new \Whoops\Run();
|
|
|
385 |
|
|
|
386 |
// Append a custom handler to add some more information to the frames.
|
|
|
387 |
$whoops->appendHandler(function ($exception, $inspector, $run) {
|
|
|
388 |
$collection = $inspector->getFrames();
|
|
|
389 |
|
|
|
390 |
// Detect if the Whoops handler was immediately invoked by a call to `debugging()`.
|
|
|
391 |
// If so, we remove the top frames in the collection to avoid showing the inner
|
|
|
392 |
// workings of debugging, and the point that we trigger the error that is picked up by Whoops.
|
|
|
393 |
$isdebugging = count($collection) > 2;
|
|
|
394 |
$isdebugging = $isdebugging && str_ends_with($collection[1]->getFile(), '/lib/weblib.php');
|
|
|
395 |
$isdebugging = $isdebugging && $collection[2]->getFunction() === 'debugging';
|
|
|
396 |
|
|
|
397 |
if ($isdebugging) {
|
|
|
398 |
$remove = array_slice($collection->getArray(), 0, 2);
|
|
|
399 |
$collection->filter(function ($frame) use ($remove): bool {
|
|
|
400 |
return array_search($frame, $remove) === false;
|
|
|
401 |
});
|
|
|
402 |
} else {
|
|
|
403 |
// Moodle exceptions often have a link to the Moodle docs pages for them.
|
|
|
404 |
// Add that to the first frame in the stack.
|
|
|
405 |
$info = get_exception_info($exception);
|
|
|
406 |
if ($info->moreinfourl) {
|
|
|
407 |
$collection[0]->addComment("{$info->moreinfourl}", 'More info');
|
|
|
408 |
}
|
|
|
409 |
}
|
|
|
410 |
});
|
|
|
411 |
|
|
|
412 |
// Add the Pretty page handler. It's the bee's knees.
|
|
|
413 |
$handler = new \Whoops\Handler\PrettyPageHandler();
|
|
|
414 |
if (isset($CFG->debug_developer_editor)) {
|
|
|
415 |
$handler->setEditor($CFG->debug_developer_editor ?: null);
|
|
|
416 |
}
|
|
|
417 |
$whoops->appendHandler($handler);
|
|
|
418 |
|
|
|
419 |
return $whoops;
|
|
|
420 |
}
|
|
|
421 |
|
|
|
422 |
/**
|
|
|
423 |
* Default exception handler.
|
|
|
424 |
*
|
|
|
425 |
* @param Exception $ex
|
|
|
426 |
* @return void -does not return. Terminates execution!
|
|
|
427 |
*/
|
|
|
428 |
function default_exception_handler($ex) {
|
|
|
429 |
global $CFG, $DB, $OUTPUT, $USER, $FULLME, $SESSION, $PAGE;
|
|
|
430 |
|
|
|
431 |
// detect active db transactions, rollback and log as error
|
|
|
432 |
abort_all_db_transactions();
|
|
|
433 |
|
|
|
434 |
if (($ex instanceof required_capability_exception) && !CLI_SCRIPT && !AJAX_SCRIPT && !empty($CFG->autologinguests) && !empty($USER->autologinguest)) {
|
|
|
435 |
$SESSION->wantsurl = qualified_me();
|
|
|
436 |
redirect(get_login_url());
|
|
|
437 |
}
|
|
|
438 |
|
|
|
439 |
$info = get_exception_info($ex);
|
|
|
440 |
|
|
|
441 |
// If we already tried to send the header remove it, the content length
|
|
|
442 |
// should be either empty or the length of the error page.
|
|
|
443 |
@header_remove('Content-Length');
|
|
|
444 |
|
|
|
445 |
if ($whoops = get_whoops()) {
|
|
|
446 |
// If whoops is available we will use it. The get_whoops() function checks whether all conditions are met.
|
|
|
447 |
$whoops->handleException($ex);
|
|
|
448 |
}
|
|
|
449 |
|
|
|
450 |
if (is_early_init($info->backtrace)) {
|
|
|
451 |
echo bootstrap_renderer::early_error($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo, $info->errorcode);
|
|
|
452 |
} else {
|
|
|
453 |
if (debugging('', DEBUG_MINIMAL)) {
|
|
|
454 |
$logerrmsg = "Default exception handler: ".$info->message.' Debug: '.$info->debuginfo."\n".format_backtrace($info->backtrace, true);
|
|
|
455 |
error_log($logerrmsg);
|
|
|
456 |
}
|
|
|
457 |
|
|
|
458 |
try {
|
|
|
459 |
if ($DB) {
|
|
|
460 |
// If you enable db debugging and exception is thrown, the print footer prints a lot of rubbish
|
|
|
461 |
$DB->set_debug(0);
|
|
|
462 |
}
|
|
|
463 |
if (AJAX_SCRIPT) {
|
|
|
464 |
// If we are in an AJAX script we don't want to use PREFERRED_RENDERER_TARGET.
|
|
|
465 |
// Because we know we will want to use ajax format.
|
|
|
466 |
$renderer = new core_renderer_ajax($PAGE, 'ajax');
|
|
|
467 |
} else {
|
|
|
468 |
$renderer = $OUTPUT;
|
|
|
469 |
}
|
|
|
470 |
echo $renderer->fatal_error($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo,
|
|
|
471 |
$info->errorcode);
|
|
|
472 |
} catch (Exception $e) {
|
|
|
473 |
$out_ex = $e;
|
|
|
474 |
} catch (Throwable $e) {
|
|
|
475 |
// Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
|
|
|
476 |
$out_ex = $e;
|
|
|
477 |
}
|
|
|
478 |
|
|
|
479 |
if (isset($out_ex)) {
|
|
|
480 |
// default exception handler MUST not throw any exceptions!!
|
|
|
481 |
// the problem here is we do not know if page already started or not, we only know that somebody messed up in outputlib or theme
|
|
|
482 |
// so we just print at least something instead of "Exception thrown without a stack frame in Unknown on line 0":-(
|
|
|
483 |
if (CLI_SCRIPT or AJAX_SCRIPT) {
|
|
|
484 |
// just ignore the error and send something back using the safest method
|
|
|
485 |
echo bootstrap_renderer::early_error($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo, $info->errorcode);
|
|
|
486 |
} else {
|
|
|
487 |
echo bootstrap_renderer::early_error_content($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo);
|
|
|
488 |
$outinfo = get_exception_info($out_ex);
|
|
|
489 |
echo bootstrap_renderer::early_error_content($outinfo->message, $outinfo->moreinfourl, $outinfo->link, $outinfo->backtrace, $outinfo->debuginfo);
|
|
|
490 |
}
|
|
|
491 |
}
|
|
|
492 |
}
|
|
|
493 |
|
|
|
494 |
exit(1); // General error code
|
|
|
495 |
}
|
|
|
496 |
|
|
|
497 |
/**
|
|
|
498 |
* Default error handler, prevents some white screens.
|
|
|
499 |
* @param int $errno
|
|
|
500 |
* @param string $errstr
|
|
|
501 |
* @param string $errfile
|
|
|
502 |
* @param int $errline
|
|
|
503 |
* @return bool false means use default error handler
|
|
|
504 |
*/
|
|
|
505 |
function default_error_handler($errno, $errstr, $errfile, $errline) {
|
|
|
506 |
if ($whoops = get_whoops()) {
|
|
|
507 |
// If whoops is available we will use it. The get_whoops() function checks whether all conditions are met.
|
|
|
508 |
$whoops->handleError($errno, $errstr, $errfile, $errline);
|
|
|
509 |
}
|
|
|
510 |
if ($errno == 4096) {
|
|
|
511 |
//fatal catchable error
|
|
|
512 |
throw new coding_exception('PHP catchable fatal error', $errstr);
|
|
|
513 |
}
|
|
|
514 |
return false;
|
|
|
515 |
}
|
|
|
516 |
|
|
|
517 |
/**
|
|
|
518 |
* Unconditionally abort all database transactions, this function
|
|
|
519 |
* should be called from exception handlers only.
|
|
|
520 |
* @return void
|
|
|
521 |
*/
|
|
|
522 |
function abort_all_db_transactions() {
|
|
|
523 |
global $CFG, $DB, $SCRIPT;
|
|
|
524 |
|
|
|
525 |
// default exception handler MUST not throw any exceptions!!
|
|
|
526 |
|
|
|
527 |
if ($DB && $DB->is_transaction_started()) {
|
|
|
528 |
error_log('Database transaction aborted automatically in ' . $CFG->dirroot . $SCRIPT);
|
|
|
529 |
// note: transaction blocks should never change current $_SESSION
|
|
|
530 |
$DB->force_transaction_rollback();
|
|
|
531 |
}
|
|
|
532 |
}
|
|
|
533 |
|
|
|
534 |
/**
|
|
|
535 |
* This function encapsulates the tests for whether an exception was thrown in
|
|
|
536 |
* early init -- either during setup.php or during init of $OUTPUT.
|
|
|
537 |
*
|
|
|
538 |
* If another exception is thrown then, and if we do not take special measures,
|
|
|
539 |
* we would just get a very cryptic message "Exception thrown without a stack
|
|
|
540 |
* frame in Unknown on line 0". That makes debugging very hard, so we do take
|
|
|
541 |
* special measures in default_exception_handler, with the help of this function.
|
|
|
542 |
*
|
|
|
543 |
* @param array $backtrace the stack trace to analyse.
|
|
|
544 |
* @return boolean whether the stack trace is somewhere in output initialisation.
|
|
|
545 |
*/
|
|
|
546 |
function is_early_init($backtrace) {
|
|
|
547 |
$dangerouscode = array(
|
|
|
548 |
array('function' => 'header', 'type' => '->'),
|
|
|
549 |
array('class' => 'bootstrap_renderer'),
|
|
|
550 |
array('file' => __DIR__.'/setup.php'),
|
|
|
551 |
);
|
|
|
552 |
foreach ($backtrace as $stackframe) {
|
|
|
553 |
foreach ($dangerouscode as $pattern) {
|
|
|
554 |
$matches = true;
|
|
|
555 |
foreach ($pattern as $property => $value) {
|
|
|
556 |
if (!isset($stackframe[$property]) || $stackframe[$property] != $value) {
|
|
|
557 |
$matches = false;
|
|
|
558 |
}
|
|
|
559 |
}
|
|
|
560 |
if ($matches) {
|
|
|
561 |
return true;
|
|
|
562 |
}
|
|
|
563 |
}
|
|
|
564 |
}
|
|
|
565 |
return false;
|
|
|
566 |
}
|
|
|
567 |
|
|
|
568 |
/**
|
|
|
569 |
* Returns detailed information about specified exception.
|
|
|
570 |
*
|
|
|
571 |
* @param Throwable $ex any sort of exception or throwable.
|
|
|
572 |
* @return stdClass standardised info to display. Fields are clear if you look at the end of this function.
|
|
|
573 |
*/
|
|
|
574 |
function get_exception_info($ex): stdClass {
|
|
|
575 |
global $CFG;
|
|
|
576 |
|
|
|
577 |
if ($ex instanceof moodle_exception) {
|
|
|
578 |
$errorcode = $ex->errorcode;
|
|
|
579 |
$module = $ex->module;
|
|
|
580 |
$a = $ex->a;
|
|
|
581 |
$link = $ex->link;
|
|
|
582 |
$debuginfo = $ex->debuginfo;
|
|
|
583 |
} else {
|
|
|
584 |
$errorcode = 'generalexceptionmessage';
|
|
|
585 |
$module = 'error';
|
|
|
586 |
$a = $ex->getMessage();
|
|
|
587 |
$link = '';
|
|
|
588 |
$debuginfo = '';
|
|
|
589 |
}
|
|
|
590 |
|
|
|
591 |
// Append the error code to the debug info to make grepping and googling easier
|
|
|
592 |
$debuginfo .= PHP_EOL."Error code: $errorcode";
|
|
|
593 |
|
|
|
594 |
$backtrace = $ex->getTrace();
|
|
|
595 |
$place = array('file'=>$ex->getFile(), 'line'=>$ex->getLine(), 'exception'=>get_class($ex));
|
|
|
596 |
array_unshift($backtrace, $place);
|
|
|
597 |
|
|
|
598 |
// Be careful, no guarantee moodlelib.php is loaded.
|
|
|
599 |
if (empty($module) || $module == 'moodle' || $module == 'core') {
|
|
|
600 |
$module = 'error';
|
|
|
601 |
}
|
|
|
602 |
// Search for the $errorcode's associated string
|
|
|
603 |
// If not found, append the contents of $a to $debuginfo so helpful information isn't lost
|
|
|
604 |
if (function_exists('get_string_manager')) {
|
|
|
605 |
if (get_string_manager()->string_exists($errorcode, $module)) {
|
|
|
606 |
$message = get_string($errorcode, $module, $a);
|
|
|
607 |
} elseif ($module == 'error' && get_string_manager()->string_exists($errorcode, 'moodle')) {
|
|
|
608 |
// Search in moodle file if error specified - needed for backwards compatibility
|
|
|
609 |
$message = get_string($errorcode, 'moodle', $a);
|
|
|
610 |
} else {
|
|
|
611 |
$message = $module . '/' . $errorcode;
|
|
|
612 |
$debuginfo .= PHP_EOL.'$a contents: '.print_r($a, true);
|
|
|
613 |
}
|
|
|
614 |
} else {
|
|
|
615 |
$message = $module . '/' . $errorcode;
|
|
|
616 |
$debuginfo .= PHP_EOL.'$a contents: '.print_r($a, true);
|
|
|
617 |
}
|
|
|
618 |
|
|
|
619 |
// Remove some absolute paths from message and debugging info.
|
|
|
620 |
$searches = array();
|
|
|
621 |
$replaces = array();
|
|
|
622 |
$cfgnames = array('backuptempdir', 'tempdir', 'cachedir', 'localcachedir', 'themedir', 'dataroot', 'dirroot');
|
|
|
623 |
foreach ($cfgnames as $cfgname) {
|
|
|
624 |
if (property_exists($CFG, $cfgname)) {
|
|
|
625 |
$searches[] = $CFG->$cfgname;
|
|
|
626 |
$replaces[] = "[$cfgname]";
|
|
|
627 |
}
|
|
|
628 |
}
|
|
|
629 |
if (!empty($searches)) {
|
|
|
630 |
$message = str_replace($searches, $replaces, $message);
|
|
|
631 |
$debuginfo = str_replace($searches, $replaces, $debuginfo);
|
|
|
632 |
}
|
|
|
633 |
|
|
|
634 |
// Be careful, no guarantee weblib.php is loaded.
|
|
|
635 |
if (function_exists('clean_text')) {
|
|
|
636 |
$message = clean_text($message);
|
|
|
637 |
} else {
|
|
|
638 |
$message = htmlspecialchars($message, ENT_COMPAT);
|
|
|
639 |
}
|
|
|
640 |
|
|
|
641 |
if (!empty($CFG->errordocroot)) {
|
|
|
642 |
$errordoclink = $CFG->errordocroot . '/en/';
|
|
|
643 |
} else {
|
|
|
644 |
// Only if the function is available. May be not for early errors.
|
|
|
645 |
if (function_exists('current_language')) {
|
|
|
646 |
$errordoclink = get_docs_url();
|
|
|
647 |
} else {
|
|
|
648 |
$errordoclink = 'https://docs.moodle.org/en/';
|
|
|
649 |
}
|
|
|
650 |
}
|
|
|
651 |
|
|
|
652 |
if ($module === 'error') {
|
|
|
653 |
$modulelink = 'moodle';
|
|
|
654 |
} else {
|
|
|
655 |
$modulelink = $module;
|
|
|
656 |
}
|
|
|
657 |
$moreinfourl = $errordoclink . 'error/' . $modulelink . '/' . $errorcode;
|
|
|
658 |
|
|
|
659 |
if (empty($link)) {
|
|
|
660 |
$link = get_local_referer(false) ?: ($CFG->wwwroot . '/');
|
|
|
661 |
}
|
|
|
662 |
|
|
|
663 |
// When printing an error the continue button should never link offsite.
|
|
|
664 |
// We cannot use clean_param() here as it is not guaranteed that it has been loaded yet.
|
|
|
665 |
if (stripos($link, $CFG->wwwroot) === 0) {
|
|
|
666 |
// Internal HTTP, all good.
|
|
|
667 |
} else {
|
|
|
668 |
// External link spotted!
|
|
|
669 |
$link = $CFG->wwwroot . '/';
|
|
|
670 |
}
|
|
|
671 |
|
|
|
672 |
$info = new stdClass();
|
|
|
673 |
$info->message = $message;
|
|
|
674 |
$info->errorcode = $errorcode;
|
|
|
675 |
$info->backtrace = $backtrace;
|
|
|
676 |
$info->link = $link;
|
|
|
677 |
$info->moreinfourl = $moreinfourl;
|
|
|
678 |
$info->a = $a;
|
|
|
679 |
$info->debuginfo = $debuginfo;
|
|
|
680 |
|
|
|
681 |
return $info;
|
|
|
682 |
}
|
|
|
683 |
|
|
|
684 |
/**
|
|
|
685 |
* @deprecated since Moodle 3.8 MDL-61038 - please do not use this function any more.
|
|
|
686 |
* @see \core\uuid::generate()
|
|
|
687 |
*/
|
|
|
688 |
function generate_uuid() {
|
|
|
689 |
throw new coding_exception('generate_uuid() cannot be used anymore. Please use ' .
|
|
|
690 |
'\core\uuid::generate() instead.');
|
|
|
691 |
}
|
|
|
692 |
|
|
|
693 |
/**
|
|
|
694 |
* Returns the Moodle Docs URL in the users language for a given 'More help' link.
|
|
|
695 |
*
|
|
|
696 |
* There are three cases:
|
|
|
697 |
*
|
|
|
698 |
* 1. In the normal case, $path will be a short relative path 'component/thing',
|
|
|
699 |
* like 'mod/folder/view' 'group/import'. This gets turned into an link to
|
|
|
700 |
* MoodleDocs in the user's language, and for the appropriate Moodle version.
|
|
|
701 |
* E.g. 'group/import' may become 'http://docs.moodle.org/2x/en/group/import'.
|
|
|
702 |
* The 'http://docs.moodle.org' bit comes from $CFG->docroot.
|
|
|
703 |
*
|
|
|
704 |
* This is the only option that should be used in standard Moodle code. The other
|
|
|
705 |
* two options have been implemented because they are useful for third-party plugins.
|
|
|
706 |
*
|
|
|
707 |
* 2. $path may be an absolute URL, starting http:// or https://. In this case,
|
|
|
708 |
* the link is used as is.
|
|
|
709 |
*
|
|
|
710 |
* 3. $path may start %%WWWROOT%%, in which case that is replaced by
|
|
|
711 |
* $CFG->wwwroot to make the link.
|
|
|
712 |
*
|
|
|
713 |
* @param string $path the place to link to. See above for details.
|
|
|
714 |
* @return string The MoodleDocs URL in the user's language. for example @link http://docs.moodle.org/2x/en/$path}
|
|
|
715 |
*/
|
|
|
716 |
function get_docs_url($path = null) {
|
|
|
717 |
global $CFG;
|
|
|
718 |
if ($path === null) {
|
|
|
719 |
$path = '';
|
|
|
720 |
}
|
|
|
721 |
|
|
|
722 |
$path = $path ?? '';
|
|
|
723 |
// Absolute URLs are used unmodified.
|
|
|
724 |
if (substr($path, 0, 7) === 'http://' || substr($path, 0, 8) === 'https://') {
|
|
|
725 |
return $path;
|
|
|
726 |
}
|
|
|
727 |
|
|
|
728 |
// Paths starting %%WWWROOT%% have that replaced by $CFG->wwwroot.
|
|
|
729 |
if (substr($path, 0, 11) === '%%WWWROOT%%') {
|
|
|
730 |
return $CFG->wwwroot . substr($path, 11);
|
|
|
731 |
}
|
|
|
732 |
|
|
|
733 |
// Otherwise we do the normal case, and construct a MoodleDocs URL relative to $CFG->docroot.
|
|
|
734 |
|
|
|
735 |
// Check that $CFG->branch has been set up, during installation it won't be.
|
|
|
736 |
if (empty($CFG->branch)) {
|
|
|
737 |
// It's not there yet so look at version.php.
|
|
|
738 |
include($CFG->dirroot.'/version.php');
|
|
|
739 |
} else {
|
|
|
740 |
// We can use $CFG->branch and avoid having to include version.php.
|
|
|
741 |
$branch = $CFG->branch;
|
|
|
742 |
}
|
|
|
743 |
// ensure branch is valid.
|
|
|
744 |
if (!$branch) {
|
|
|
745 |
// We should never get here but in case we do lets set $branch to .
|
|
|
746 |
// the smart one's will know that this is the current directory
|
|
|
747 |
// and the smarter ones will know that there is some smart matching
|
|
|
748 |
// that will ensure people end up at the latest version of the docs.
|
|
|
749 |
$branch = '.';
|
|
|
750 |
}
|
|
|
751 |
if (empty($CFG->doclang)) {
|
|
|
752 |
$lang = current_language();
|
|
|
753 |
} else {
|
|
|
754 |
$lang = $CFG->doclang;
|
|
|
755 |
}
|
|
|
756 |
$end = '/' . $branch . '/' . $lang . '/' . $path;
|
|
|
757 |
if (empty($CFG->docroot)) {
|
|
|
758 |
return 'http://docs.moodle.org'. $end;
|
|
|
759 |
} else {
|
|
|
760 |
return $CFG->docroot . $end ;
|
|
|
761 |
}
|
|
|
762 |
}
|
|
|
763 |
|
|
|
764 |
/**
|
|
|
765 |
* Formats a backtrace ready for output.
|
|
|
766 |
*
|
|
|
767 |
* This function does not include function arguments because they could contain sensitive information
|
|
|
768 |
* not suitable to be exposed in a response.
|
|
|
769 |
*
|
|
|
770 |
* @param array $callers backtrace array, as returned by debug_backtrace().
|
|
|
771 |
* @param boolean $plaintext if false, generates HTML, if true generates plain text.
|
|
|
772 |
* @return string formatted backtrace, ready for output.
|
|
|
773 |
*/
|
|
|
774 |
function format_backtrace($callers, $plaintext = false) {
|
|
|
775 |
// do not use $CFG->dirroot because it might not be available in destructors
|
|
|
776 |
$dirroot = dirname(__DIR__);
|
|
|
777 |
|
|
|
778 |
if (empty($callers)) {
|
|
|
779 |
return '';
|
|
|
780 |
}
|
|
|
781 |
|
|
|
782 |
$from = $plaintext ? '' : '<ul style="text-align: left" data-rel="backtrace">';
|
|
|
783 |
foreach ($callers as $caller) {
|
|
|
784 |
if (!isset($caller['line'])) {
|
|
|
785 |
$caller['line'] = '?'; // probably call_user_func()
|
|
|
786 |
}
|
|
|
787 |
if (!isset($caller['file'])) {
|
|
|
788 |
$caller['file'] = 'unknownfile'; // probably call_user_func()
|
|
|
789 |
}
|
|
|
790 |
$line = $plaintext ? '* ' : '<li>';
|
|
|
791 |
$line .= 'line ' . $caller['line'] . ' of ' . str_replace($dirroot, '', $caller['file']);
|
|
|
792 |
if (isset($caller['function'])) {
|
|
|
793 |
$line .= ': call to ';
|
|
|
794 |
if (isset($caller['class'])) {
|
|
|
795 |
$line .= $caller['class'] . $caller['type'];
|
|
|
796 |
}
|
|
|
797 |
$line .= $caller['function'] . '()';
|
|
|
798 |
} else if (isset($caller['exception'])) {
|
|
|
799 |
$line .= ': '.$caller['exception'].' thrown';
|
|
|
800 |
}
|
|
|
801 |
|
|
|
802 |
// Remove any non printable chars.
|
|
|
803 |
$line = preg_replace('/[[:^print:]]/', '', $line);
|
|
|
804 |
|
|
|
805 |
$line .= $plaintext ? "\n" : '</li>';
|
|
|
806 |
$from .= $line;
|
|
|
807 |
}
|
|
|
808 |
$from .= $plaintext ? '' : '</ul>';
|
|
|
809 |
|
|
|
810 |
return $from;
|
|
|
811 |
}
|
|
|
812 |
|
|
|
813 |
/**
|
|
|
814 |
* This function makes the return value of ini_get consistent if you are
|
|
|
815 |
* setting server directives through the .htaccess file in apache.
|
|
|
816 |
*
|
|
|
817 |
* Current behavior for value set from php.ini On = 1, Off = [blank]
|
|
|
818 |
* Current behavior for value set from .htaccess On = On, Off = Off
|
|
|
819 |
* Contributed by jdell @ unr.edu
|
|
|
820 |
*
|
|
|
821 |
* @param string $ini_get_arg The argument to get
|
|
|
822 |
* @return bool True for on false for not
|
|
|
823 |
*/
|
|
|
824 |
function ini_get_bool($ini_get_arg) {
|
|
|
825 |
$temp = ini_get($ini_get_arg);
|
|
|
826 |
|
|
|
827 |
if ($temp == '1' or strtolower($temp) == 'on') {
|
|
|
828 |
return true;
|
|
|
829 |
}
|
|
|
830 |
return false;
|
|
|
831 |
}
|
|
|
832 |
|
|
|
833 |
/**
|
|
|
834 |
* This function verifies the sanity of PHP configuration
|
|
|
835 |
* and stops execution if anything critical found.
|
|
|
836 |
*/
|
|
|
837 |
function setup_validate_php_configuration() {
|
|
|
838 |
// this must be very fast - no slow checks here!!!
|
|
|
839 |
|
|
|
840 |
if (ini_get_bool('session.auto_start')) {
|
|
|
841 |
throw new \moodle_exception('sessionautostartwarning', 'admin');
|
|
|
842 |
}
|
|
|
843 |
}
|
|
|
844 |
|
|
|
845 |
/**
|
|
|
846 |
* Initialise global $CFG variable.
|
|
|
847 |
* @private to be used only from lib/setup.php
|
|
|
848 |
*/
|
|
|
849 |
function initialise_cfg() {
|
|
|
850 |
global $CFG, $DB;
|
|
|
851 |
|
|
|
852 |
if (!$DB) {
|
|
|
853 |
// This should not happen.
|
|
|
854 |
return;
|
|
|
855 |
}
|
|
|
856 |
|
|
|
857 |
try {
|
|
|
858 |
$localcfg = get_config('core');
|
|
|
859 |
} catch (dml_exception $e) {
|
|
|
860 |
// Most probably empty db, going to install soon.
|
|
|
861 |
return;
|
|
|
862 |
}
|
|
|
863 |
|
|
|
864 |
foreach ($localcfg as $name => $value) {
|
|
|
865 |
// Note that get_config() keeps forced settings
|
|
|
866 |
// and normalises values to string if possible.
|
|
|
867 |
$CFG->{$name} = $value;
|
|
|
868 |
}
|
|
|
869 |
}
|
|
|
870 |
|
|
|
871 |
/**
|
|
|
872 |
* Cache any immutable config locally to avoid constant DB lookups.
|
|
|
873 |
*
|
|
|
874 |
* Only to be used only from lib/setup.php
|
|
|
875 |
*/
|
|
|
876 |
function initialise_local_config_cache() {
|
|
|
877 |
global $CFG;
|
|
|
878 |
|
|
|
879 |
$bootstrapcachefile = $CFG->localcachedir . '/bootstrap.php';
|
|
|
880 |
|
|
|
881 |
if (!empty($CFG->siteidentifier) && !file_exists($bootstrapcachefile)) {
|
|
|
882 |
$contents = "<?php
|
|
|
883 |
// ********** This file is generated DO NOT EDIT **********
|
|
|
884 |
\$CFG->siteidentifier = " . var_export($CFG->siteidentifier, true) . ";
|
|
|
885 |
\$CFG->bootstraphash = " . var_export(hash_local_config_cache(), true) . ";
|
|
|
886 |
// Only if the file is not stale and has not been defined.
|
|
|
887 |
if (\$CFG->bootstraphash === hash_local_config_cache() && !defined('SYSCONTEXTID')) {
|
|
|
888 |
define('SYSCONTEXTID', ".SYSCONTEXTID.");
|
|
|
889 |
}
|
|
|
890 |
";
|
|
|
891 |
|
|
|
892 |
$temp = $bootstrapcachefile . '.tmp' . uniqid();
|
|
|
893 |
file_put_contents($temp, $contents);
|
|
|
894 |
@chmod($temp, $CFG->filepermissions);
|
|
|
895 |
rename($temp, $bootstrapcachefile);
|
|
|
896 |
}
|
|
|
897 |
}
|
|
|
898 |
|
|
|
899 |
/**
|
|
|
900 |
* Calculate a proper hash to be able to invalidate stale cached configs.
|
|
|
901 |
*
|
|
|
902 |
* Only to be used to verify bootstrap.php status.
|
|
|
903 |
*
|
|
|
904 |
* @return string md5 hash of all the sensible bits deciding if cached config is stale or no.
|
|
|
905 |
*/
|
|
|
906 |
function hash_local_config_cache() {
|
|
|
907 |
global $CFG;
|
|
|
908 |
|
|
|
909 |
// This is pretty much {@see moodle_database::get_settings_hash()} that is used
|
|
|
910 |
// as identifier for the database meta information MUC cache. Should be enough to
|
|
|
911 |
// react against any of the normal changes (new prefix, change of DB type) while
|
|
|
912 |
// *incorrectly* keeping the old dataroot directory unmodified with stale data.
|
|
|
913 |
// This may need more stuff to be considered if it's discovered that there are
|
|
|
914 |
// more variables making the file stale.
|
|
|
915 |
return md5($CFG->dbtype . $CFG->dbhost . $CFG->dbuser . $CFG->dbname . $CFG->prefix);
|
|
|
916 |
}
|
|
|
917 |
|
|
|
918 |
/**
|
|
|
919 |
* Initialises $FULLME and friends. Private function. Should only be called from
|
|
|
920 |
* setup.php.
|
|
|
921 |
*/
|
|
|
922 |
function initialise_fullme() {
|
|
|
923 |
global $CFG, $FULLME, $ME, $SCRIPT, $FULLSCRIPT;
|
|
|
924 |
|
|
|
925 |
// Detect common config error.
|
|
|
926 |
if (substr($CFG->wwwroot, -1) == '/') {
|
|
|
927 |
throw new \moodle_exception('wwwrootslash', 'error');
|
|
|
928 |
}
|
|
|
929 |
|
|
|
930 |
if (CLI_SCRIPT) {
|
|
|
931 |
initialise_fullme_cli();
|
|
|
932 |
return;
|
|
|
933 |
}
|
|
|
934 |
if (!empty($CFG->overridetossl)) {
|
|
|
935 |
if (strpos($CFG->wwwroot, 'http://') === 0) {
|
|
|
936 |
$CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
|
|
|
937 |
} else {
|
|
|
938 |
unset_config('overridetossl');
|
|
|
939 |
}
|
|
|
940 |
}
|
|
|
941 |
|
|
|
942 |
$rurl = setup_get_remote_url();
|
|
|
943 |
$wwwroot = parse_url($CFG->wwwroot.'/');
|
|
|
944 |
|
|
|
945 |
if (empty($rurl['host'])) {
|
|
|
946 |
// missing host in request header, probably not a real browser, let's ignore them
|
|
|
947 |
|
|
|
948 |
} else if (!empty($CFG->reverseproxy)) {
|
|
|
949 |
// $CFG->reverseproxy specifies if reverse proxy server used
|
|
|
950 |
// Used in load balancing scenarios.
|
|
|
951 |
// Do not abuse this to try to solve lan/wan access problems!!!!!
|
|
|
952 |
|
|
|
953 |
} else {
|
|
|
954 |
if (($rurl['host'] !== $wwwroot['host']) or
|
|
|
955 |
(!empty($wwwroot['port']) and $rurl['port'] != $wwwroot['port']) or
|
|
|
956 |
(strpos($rurl['path'], $wwwroot['path']) !== 0)) {
|
|
|
957 |
|
|
|
958 |
// Explain the problem and redirect them to the right URL
|
|
|
959 |
if (!defined('NO_MOODLE_COOKIES')) {
|
|
|
960 |
define('NO_MOODLE_COOKIES', true);
|
|
|
961 |
}
|
|
|
962 |
// The login/token.php script should call the correct url/port.
|
|
|
963 |
if (defined('REQUIRE_CORRECT_ACCESS') && REQUIRE_CORRECT_ACCESS) {
|
|
|
964 |
$wwwrootport = empty($wwwroot['port'])?'':$wwwroot['port'];
|
|
|
965 |
$calledurl = $rurl['host'];
|
|
|
966 |
if (!empty($rurl['port'])) {
|
|
|
967 |
$calledurl .= ':'. $rurl['port'];
|
|
|
968 |
}
|
|
|
969 |
$correcturl = $wwwroot['host'];
|
|
|
970 |
if (!empty($wwwrootport)) {
|
|
|
971 |
$correcturl .= ':'. $wwwrootport;
|
|
|
972 |
}
|
|
|
973 |
throw new moodle_exception('requirecorrectaccess', 'error', '', null,
|
|
|
974 |
'You called ' . $calledurl .', you should have called ' . $correcturl);
|
|
|
975 |
}
|
|
|
976 |
$rfullpath = $rurl['fullpath'];
|
|
|
977 |
// Check that URL is under $CFG->wwwroot.
|
|
|
978 |
if (strpos($rfullpath, $wwwroot['path']) === 0) {
|
|
|
979 |
$rfullpath = substr($rurl['fullpath'], strlen($wwwroot['path']) - 1);
|
|
|
980 |
$rfullpath = (new moodle_url($rfullpath))->out(false);
|
|
|
981 |
}
|
|
|
982 |
redirect($rfullpath, get_string('wwwrootmismatch', 'error', $CFG->wwwroot), 3);
|
|
|
983 |
}
|
|
|
984 |
}
|
|
|
985 |
|
|
|
986 |
// Check that URL is under $CFG->wwwroot.
|
|
|
987 |
if (strpos($rurl['path'], $wwwroot['path']) === 0) {
|
|
|
988 |
$SCRIPT = substr($rurl['path'], strlen($wwwroot['path'])-1);
|
|
|
989 |
} else {
|
|
|
990 |
// Probably some weird external script
|
|
|
991 |
$SCRIPT = $FULLSCRIPT = $FULLME = $ME = null;
|
|
|
992 |
return;
|
|
|
993 |
}
|
|
|
994 |
|
|
|
995 |
// $CFG->sslproxy specifies if external SSL appliance is used
|
|
|
996 |
// (That is, the Moodle server uses http, with an external box translating everything to https).
|
|
|
997 |
if (empty($CFG->sslproxy)) {
|
|
|
998 |
if ($rurl['scheme'] === 'http' and $wwwroot['scheme'] === 'https') {
|
|
|
999 |
if (defined('REQUIRE_CORRECT_ACCESS') && REQUIRE_CORRECT_ACCESS) {
|
|
|
1000 |
throw new \moodle_exception('sslonlyaccess', 'error');
|
|
|
1001 |
} else {
|
|
|
1002 |
redirect($CFG->wwwroot, get_string('wwwrootmismatch', 'error', $CFG->wwwroot), 3);
|
|
|
1003 |
}
|
|
|
1004 |
}
|
|
|
1005 |
} else {
|
|
|
1006 |
if ($wwwroot['scheme'] !== 'https') {
|
|
|
1007 |
throw new coding_exception('Must use https address in wwwroot when ssl proxy enabled!');
|
|
|
1008 |
}
|
|
|
1009 |
$rurl['scheme'] = 'https'; // make moodle believe it runs on https, squid or something else it doing it
|
|
|
1010 |
$_SERVER['HTTPS'] = 'on'; // Override $_SERVER to help external libraries with their HTTPS detection.
|
|
|
1011 |
$_SERVER['SERVER_PORT'] = 443; // Assume default ssl port for the proxy.
|
|
|
1012 |
}
|
|
|
1013 |
|
|
|
1014 |
// Using Moodle in "reverse proxy" mode, it's expected that the HTTP Host Moodle receives is different
|
|
|
1015 |
// from the wwwroot configured host. Those URLs being identical could be the consequence of various
|
|
|
1016 |
// issues, including:
|
|
|
1017 |
// - Intentionally trying to set up moodle with 2 distinct addresses for intranet and Internet: this
|
|
|
1018 |
// configuration is unsupported and will lead to bigger problems down the road (the proper solution
|
|
|
1019 |
// for this is adjusting the network routes, and avoid relying on the application for network concerns).
|
|
|
1020 |
// - Misconfiguration of the reverse proxy that would be forwarding the Host header: while it is
|
|
|
1021 |
// standard in many cases that the reverse proxy would do that, in our case, the reverse proxy
|
|
|
1022 |
// must leave the Host header pointing to the internal name of the server.
|
|
|
1023 |
// Port forwarding is allowed, though.
|
|
|
1024 |
if (!empty($CFG->reverseproxy) && $rurl['host'] === $wwwroot['host'] && (empty($wwwroot['port']) || $rurl['port'] === $wwwroot['port'])) {
|
|
|
1025 |
throw new \moodle_exception('reverseproxyabused', 'error');
|
|
|
1026 |
}
|
|
|
1027 |
|
|
|
1028 |
$hostandport = $rurl['scheme'] . '://' . $wwwroot['host'];
|
|
|
1029 |
if (!empty($wwwroot['port'])) {
|
|
|
1030 |
$hostandport .= ':'.$wwwroot['port'];
|
|
|
1031 |
}
|
|
|
1032 |
|
|
|
1033 |
$FULLSCRIPT = $hostandport . $rurl['path'];
|
|
|
1034 |
$FULLME = $hostandport . $rurl['fullpath'];
|
|
|
1035 |
$ME = $rurl['fullpath'];
|
|
|
1036 |
}
|
|
|
1037 |
|
|
|
1038 |
/**
|
|
|
1039 |
* Initialises $FULLME and friends for command line scripts.
|
|
|
1040 |
* This is a private method for use by initialise_fullme.
|
|
|
1041 |
*/
|
|
|
1042 |
function initialise_fullme_cli() {
|
|
|
1043 |
global $CFG, $FULLME, $ME, $SCRIPT, $FULLSCRIPT;
|
|
|
1044 |
|
|
|
1045 |
// Urls do not make much sense in CLI scripts
|
|
|
1046 |
$backtrace = debug_backtrace();
|
|
|
1047 |
$topfile = array_pop($backtrace);
|
|
|
1048 |
$topfile = realpath($topfile['file']);
|
|
|
1049 |
$dirroot = realpath($CFG->dirroot);
|
|
|
1050 |
|
|
|
1051 |
if (strpos($topfile, $dirroot) !== 0) {
|
|
|
1052 |
// Probably some weird external script
|
|
|
1053 |
$SCRIPT = $FULLSCRIPT = $FULLME = $ME = null;
|
|
|
1054 |
} else {
|
|
|
1055 |
$relativefile = substr($topfile, strlen($dirroot));
|
|
|
1056 |
$relativefile = str_replace('\\', '/', $relativefile); // Win fix
|
|
|
1057 |
$SCRIPT = $FULLSCRIPT = $relativefile;
|
|
|
1058 |
$FULLME = $ME = null;
|
|
|
1059 |
}
|
|
|
1060 |
}
|
|
|
1061 |
|
|
|
1062 |
/**
|
|
|
1063 |
* Get the URL that PHP/the web server thinks it is serving. Private function
|
|
|
1064 |
* used by initialise_fullme. In your code, use $PAGE->url, $SCRIPT, etc.
|
|
|
1065 |
* @return array in the same format that parse_url returns, with the addition of
|
|
|
1066 |
* a 'fullpath' element, which includes any slasharguments path.
|
|
|
1067 |
*/
|
|
|
1068 |
function setup_get_remote_url() {
|
|
|
1069 |
$rurl = array();
|
|
|
1070 |
if (isset($_SERVER['HTTP_HOST'])) {
|
|
|
1071 |
list($rurl['host']) = explode(':', $_SERVER['HTTP_HOST']);
|
|
|
1072 |
} else {
|
|
|
1073 |
$rurl['host'] = null;
|
|
|
1074 |
}
|
|
|
1075 |
$rurl['port'] = (int)$_SERVER['SERVER_PORT'];
|
|
|
1076 |
$rurl['path'] = $_SERVER['SCRIPT_NAME']; // Script path without slash arguments
|
|
|
1077 |
$rurl['scheme'] = (empty($_SERVER['HTTPS']) or $_SERVER['HTTPS'] === 'off' or $_SERVER['HTTPS'] === 'Off' or $_SERVER['HTTPS'] === 'OFF') ? 'http' : 'https';
|
|
|
1078 |
|
|
|
1079 |
if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== false) {
|
|
|
1080 |
//Apache server
|
|
|
1081 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI'];
|
|
|
1082 |
|
|
|
1083 |
// Fixing a known issue with:
|
|
|
1084 |
// - Apache versions lesser than 2.4.11
|
|
|
1085 |
// - PHP deployed in Apache as PHP-FPM via mod_proxy_fcgi
|
|
|
1086 |
// - PHP versions lesser than 5.6.3 and 5.5.18.
|
|
|
1087 |
if (isset($_SERVER['PATH_INFO']) && (php_sapi_name() === 'fpm-fcgi') && isset($_SERVER['SCRIPT_NAME'])) {
|
|
|
1088 |
$pathinfodec = rawurldecode($_SERVER['PATH_INFO']);
|
|
|
1089 |
$lenneedle = strlen($pathinfodec);
|
|
|
1090 |
// Checks whether SCRIPT_NAME ends with PATH_INFO, URL-decoded.
|
|
|
1091 |
if (substr($_SERVER['SCRIPT_NAME'], -$lenneedle) === $pathinfodec) {
|
|
|
1092 |
// This is the "Apache 2.4.10- running PHP-FPM via mod_proxy_fcgi" fingerprint,
|
|
|
1093 |
// at least on CentOS 7 (Apache/2.4.6 PHP/5.4.16) and Ubuntu 14.04 (Apache/2.4.7 PHP/5.5.9)
|
|
|
1094 |
// => SCRIPT_NAME contains 'slash arguments' data too, which is wrongly exposed via PATH_INFO as URL-encoded.
|
|
|
1095 |
// Fix both $_SERVER['PATH_INFO'] and $_SERVER['SCRIPT_NAME'].
|
|
|
1096 |
$lenhaystack = strlen($_SERVER['SCRIPT_NAME']);
|
|
|
1097 |
$pos = $lenhaystack - $lenneedle;
|
|
|
1098 |
// Here $pos is greater than 0 but let's double check it.
|
|
|
1099 |
if ($pos > 0) {
|
|
|
1100 |
$_SERVER['PATH_INFO'] = $pathinfodec;
|
|
|
1101 |
$_SERVER['SCRIPT_NAME'] = substr($_SERVER['SCRIPT_NAME'], 0, $pos);
|
|
|
1102 |
}
|
|
|
1103 |
}
|
|
|
1104 |
}
|
|
|
1105 |
|
|
|
1106 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'iis') !== false) {
|
|
|
1107 |
//IIS - needs a lot of tweaking to make it work
|
|
|
1108 |
$rurl['fullpath'] = $_SERVER['SCRIPT_NAME'];
|
|
|
1109 |
|
|
|
1110 |
// NOTE: we should ignore PATH_INFO because it is incorrectly encoded using 8bit filesystem legacy encoding in IIS.
|
|
|
1111 |
// Since 2.0, we rely on IIS rewrite extensions like Helicon ISAPI_rewrite
|
|
|
1112 |
// example rule: RewriteRule ^([^\?]+?\.php)(\/.+)$ $1\?file=$2 [QSA]
|
|
|
1113 |
// OR
|
|
|
1114 |
// we rely on a proper IIS 6.0+ configuration: the 'FastCGIUtf8ServerVariables' registry key.
|
|
|
1115 |
if (isset($_SERVER['PATH_INFO']) and $_SERVER['PATH_INFO'] !== '') {
|
|
|
1116 |
// Check that PATH_INFO works == must not contain the script name.
|
|
|
1117 |
if (strpos($_SERVER['PATH_INFO'], $_SERVER['SCRIPT_NAME']) === false) {
|
|
|
1118 |
$rurl['fullpath'] .= clean_param(urldecode($_SERVER['PATH_INFO']), PARAM_PATH);
|
|
|
1119 |
}
|
|
|
1120 |
}
|
|
|
1121 |
|
|
|
1122 |
if (isset($_SERVER['QUERY_STRING']) and $_SERVER['QUERY_STRING'] !== '') {
|
|
|
1123 |
$rurl['fullpath'] .= '?'.$_SERVER['QUERY_STRING'];
|
|
|
1124 |
}
|
|
|
1125 |
$_SERVER['REQUEST_URI'] = $rurl['fullpath']; // extra IIS compatibility
|
|
|
1126 |
|
|
|
1127 |
/* NOTE: following servers are not fully tested! */
|
|
|
1128 |
|
|
|
1129 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) {
|
|
|
1130 |
//lighttpd - not officially supported
|
|
|
1131 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI']; // TODO: verify this is always properly encoded
|
|
|
1132 |
|
|
|
1133 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false) {
|
|
|
1134 |
//nginx - not officially supported
|
|
|
1135 |
if (!isset($_SERVER['SCRIPT_NAME'])) {
|
|
|
1136 |
die('Invalid server configuration detected, please try to add "fastcgi_param SCRIPT_NAME $fastcgi_script_name;" to the nginx server configuration.');
|
|
|
1137 |
}
|
|
|
1138 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI']; // TODO: verify this is always properly encoded
|
|
|
1139 |
|
|
|
1140 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'cherokee') !== false) {
|
|
|
1141 |
//cherokee - not officially supported
|
|
|
1142 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI']; // TODO: verify this is always properly encoded
|
|
|
1143 |
|
|
|
1144 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'zeus') !== false) {
|
|
|
1145 |
//zeus - not officially supported
|
|
|
1146 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI']; // TODO: verify this is always properly encoded
|
|
|
1147 |
|
|
|
1148 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false) {
|
|
|
1149 |
//LiteSpeed - not officially supported
|
|
|
1150 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI']; // TODO: verify this is always properly encoded
|
|
|
1151 |
|
|
|
1152 |
} else if ($_SERVER['SERVER_SOFTWARE'] === 'HTTPD') {
|
|
|
1153 |
//obscure name found on some servers - this is definitely not supported
|
|
|
1154 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI']; // TODO: verify this is always properly encoded
|
|
|
1155 |
|
|
|
1156 |
} else if (strpos($_SERVER['SERVER_SOFTWARE'], 'PHP') === 0) {
|
|
|
1157 |
// built-in PHP Development Server
|
|
|
1158 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI'];
|
|
|
1159 |
|
|
|
1160 |
} else {
|
|
|
1161 |
throw new moodle_exception('unsupportedwebserver', 'error', '', $_SERVER['SERVER_SOFTWARE']);
|
|
|
1162 |
}
|
|
|
1163 |
|
|
|
1164 |
// sanitize the url a bit more, the encoding style may be different in vars above
|
|
|
1165 |
$rurl['fullpath'] = str_replace('"', '%22', $rurl['fullpath']);
|
|
|
1166 |
$rurl['fullpath'] = str_replace('\'', '%27', $rurl['fullpath']);
|
|
|
1167 |
|
|
|
1168 |
return $rurl;
|
|
|
1169 |
}
|
|
|
1170 |
|
|
|
1171 |
/**
|
|
|
1172 |
* Try to work around the 'max_input_vars' restriction if necessary.
|
|
|
1173 |
*/
|
|
|
1174 |
function workaround_max_input_vars() {
|
|
|
1175 |
// Make sure this gets executed only once from lib/setup.php!
|
|
|
1176 |
static $executed = false;
|
|
|
1177 |
if ($executed) {
|
|
|
1178 |
debugging('workaround_max_input_vars() must be called only once!');
|
|
|
1179 |
return;
|
|
|
1180 |
}
|
|
|
1181 |
$executed = true;
|
|
|
1182 |
|
|
|
1183 |
if (!isset($_SERVER["CONTENT_TYPE"]) or strpos($_SERVER["CONTENT_TYPE"], 'multipart/form-data') !== false) {
|
|
|
1184 |
// Not a post or 'multipart/form-data' which is not compatible with "php://input" reading.
|
|
|
1185 |
return;
|
|
|
1186 |
}
|
|
|
1187 |
|
|
|
1188 |
if (!isloggedin() or isguestuser()) {
|
|
|
1189 |
// Only real users post huge forms.
|
|
|
1190 |
return;
|
|
|
1191 |
}
|
|
|
1192 |
|
|
|
1193 |
$max = (int)ini_get('max_input_vars');
|
|
|
1194 |
|
|
|
1195 |
if ($max <= 0) {
|
|
|
1196 |
// Most probably PHP < 5.3.9 that does not implement this limit.
|
|
|
1197 |
return;
|
|
|
1198 |
}
|
|
|
1199 |
|
|
|
1200 |
if ($max >= 200000) {
|
|
|
1201 |
// This value should be ok for all our forms, by setting it in php.ini
|
|
|
1202 |
// admins may prevent any unexpected regressions caused by this hack.
|
|
|
1203 |
|
|
|
1204 |
// Note there is no need to worry about DDoS caused by making this limit very high
|
|
|
1205 |
// because there are very many easier ways to DDoS any Moodle server.
|
|
|
1206 |
return;
|
|
|
1207 |
}
|
|
|
1208 |
|
|
|
1209 |
// Worst case is advanced checkboxes which use up to two max_input_vars
|
|
|
1210 |
// slots for each entry in $_POST, because of sending two fields with the
|
|
|
1211 |
// same name. So count everything twice just in case.
|
|
|
1212 |
if (count($_POST, COUNT_RECURSIVE) * 2 < $max) {
|
|
|
1213 |
return;
|
|
|
1214 |
}
|
|
|
1215 |
|
|
|
1216 |
// Large POST request with enctype supported by php://input.
|
|
|
1217 |
// Parse php://input in chunks to bypass max_input_vars limit, which also applies to parse_str().
|
|
|
1218 |
$str = file_get_contents("php://input");
|
|
|
1219 |
if ($str === false or $str === '') {
|
|
|
1220 |
// Some weird error.
|
|
|
1221 |
return;
|
|
|
1222 |
}
|
|
|
1223 |
|
|
|
1224 |
$delim = '&';
|
|
|
1225 |
$fun = function($p) use ($delim) {
|
|
|
1226 |
return implode($delim, $p);
|
|
|
1227 |
};
|
|
|
1228 |
$chunks = array_map($fun, array_chunk(explode($delim, $str), $max));
|
|
|
1229 |
|
|
|
1230 |
// Clear everything from existing $_POST array, otherwise it might be included
|
|
|
1231 |
// twice (this affects array params primarily).
|
|
|
1232 |
foreach ($_POST as $key => $value) {
|
|
|
1233 |
unset($_POST[$key]);
|
|
|
1234 |
// Also clear from request array - but only the things that are in $_POST,
|
|
|
1235 |
// that way it will leave the things from a get request if any.
|
|
|
1236 |
unset($_REQUEST[$key]);
|
|
|
1237 |
}
|
|
|
1238 |
|
|
|
1239 |
foreach ($chunks as $chunk) {
|
|
|
1240 |
$values = array();
|
|
|
1241 |
parse_str($chunk, $values);
|
|
|
1242 |
|
|
|
1243 |
merge_query_params($_POST, $values);
|
|
|
1244 |
merge_query_params($_REQUEST, $values);
|
|
|
1245 |
}
|
|
|
1246 |
}
|
|
|
1247 |
|
|
|
1248 |
/**
|
|
|
1249 |
* Merge parsed POST chunks.
|
|
|
1250 |
*
|
|
|
1251 |
* NOTE: this is not perfect, but it should work in most cases hopefully.
|
|
|
1252 |
*
|
|
|
1253 |
* @param array $target
|
|
|
1254 |
* @param array $values
|
|
|
1255 |
*/
|
|
|
1256 |
function merge_query_params(array &$target, array $values) {
|
|
|
1257 |
if (isset($values[0]) and isset($target[0])) {
|
|
|
1258 |
// This looks like a split [] array, lets verify the keys are continuous starting with 0.
|
|
|
1259 |
$keys1 = array_keys($values);
|
|
|
1260 |
$keys2 = array_keys($target);
|
|
|
1261 |
if ($keys1 === array_keys($keys1) and $keys2 === array_keys($keys2)) {
|
|
|
1262 |
foreach ($values as $v) {
|
|
|
1263 |
$target[] = $v;
|
|
|
1264 |
}
|
|
|
1265 |
return;
|
|
|
1266 |
}
|
|
|
1267 |
}
|
|
|
1268 |
foreach ($values as $k => $v) {
|
|
|
1269 |
if (!isset($target[$k])) {
|
|
|
1270 |
$target[$k] = $v;
|
|
|
1271 |
continue;
|
|
|
1272 |
}
|
|
|
1273 |
if (is_array($target[$k]) and is_array($v)) {
|
|
|
1274 |
merge_query_params($target[$k], $v);
|
|
|
1275 |
continue;
|
|
|
1276 |
}
|
|
|
1277 |
// We should not get here unless there are duplicates in params.
|
|
|
1278 |
$target[$k] = $v;
|
|
|
1279 |
}
|
|
|
1280 |
}
|
|
|
1281 |
|
|
|
1282 |
/**
|
|
|
1283 |
* Initializes our performance info early.
|
|
|
1284 |
*
|
|
|
1285 |
* Pairs up with get_performance_info() which is actually
|
|
|
1286 |
* in moodlelib.php. This function is here so that we can
|
|
|
1287 |
* call it before all the libs are pulled in.
|
|
|
1288 |
*
|
|
|
1289 |
* @uses $PERF
|
|
|
1290 |
*/
|
|
|
1291 |
function init_performance_info() {
|
|
|
1292 |
|
|
|
1293 |
global $PERF, $CFG, $USER;
|
|
|
1294 |
|
|
|
1295 |
$PERF = new stdClass();
|
|
|
1296 |
if (function_exists('microtime')) {
|
|
|
1297 |
$PERF->starttime = microtime();
|
|
|
1298 |
}
|
|
|
1299 |
if (function_exists('memory_get_usage')) {
|
|
|
1300 |
$PERF->startmemory = memory_get_usage();
|
|
|
1301 |
}
|
|
|
1302 |
if (function_exists('posix_times')) {
|
|
|
1303 |
$PERF->startposixtimes = posix_times();
|
|
|
1304 |
}
|
|
|
1305 |
}
|
|
|
1306 |
|
|
|
1307 |
/**
|
|
|
1308 |
* Indicates whether we are in the middle of the initial Moodle install.
|
|
|
1309 |
*
|
|
|
1310 |
* Very occasionally it is necessary avoid running certain bits of code before the
|
|
|
1311 |
* Moodle installation has completed. The installed flag is set in admin/index.php
|
|
|
1312 |
* after Moodle core and all the plugins have been installed, but just before
|
|
|
1313 |
* the person doing the initial install is asked to choose the admin password.
|
|
|
1314 |
*
|
|
|
1315 |
* @return boolean true if the initial install is not complete.
|
|
|
1316 |
*/
|
|
|
1317 |
function during_initial_install() {
|
|
|
1318 |
global $CFG;
|
|
|
1319 |
return empty($CFG->rolesactive);
|
|
|
1320 |
}
|
|
|
1321 |
|
|
|
1322 |
/**
|
|
|
1323 |
* Function to raise the memory limit to a new value.
|
|
|
1324 |
* Will respect the memory limit if it is higher, thus allowing
|
|
|
1325 |
* settings in php.ini, apache conf or command line switches
|
|
|
1326 |
* to override it.
|
|
|
1327 |
*
|
|
|
1328 |
* The memory limit should be expressed with a constant
|
|
|
1329 |
* MEMORY_STANDARD, MEMORY_EXTRA or MEMORY_HUGE.
|
|
|
1330 |
* It is possible to use strings or integers too (eg:'128M').
|
|
|
1331 |
*
|
|
|
1332 |
* @param mixed $newlimit the new memory limit
|
|
|
1333 |
* @return bool success
|
|
|
1334 |
*/
|
|
|
1335 |
function raise_memory_limit($newlimit) {
|
|
|
1336 |
global $CFG;
|
|
|
1337 |
|
|
|
1338 |
if ($newlimit == MEMORY_UNLIMITED) {
|
|
|
1339 |
ini_set('memory_limit', -1);
|
|
|
1340 |
return true;
|
|
|
1341 |
|
|
|
1342 |
} else if ($newlimit == MEMORY_STANDARD) {
|
|
|
1343 |
if (PHP_INT_SIZE > 4) {
|
|
|
1344 |
$newlimit = get_real_size('128M'); // 64bit needs more memory
|
|
|
1345 |
} else {
|
|
|
1346 |
$newlimit = get_real_size('96M');
|
|
|
1347 |
}
|
|
|
1348 |
|
|
|
1349 |
} else if ($newlimit == MEMORY_EXTRA) {
|
|
|
1350 |
if (PHP_INT_SIZE > 4) {
|
|
|
1351 |
$newlimit = get_real_size('384M'); // 64bit needs more memory
|
|
|
1352 |
} else {
|
|
|
1353 |
$newlimit = get_real_size('256M');
|
|
|
1354 |
}
|
|
|
1355 |
if (!empty($CFG->extramemorylimit)) {
|
|
|
1356 |
$extra = get_real_size($CFG->extramemorylimit);
|
|
|
1357 |
if ($extra > $newlimit) {
|
|
|
1358 |
$newlimit = $extra;
|
|
|
1359 |
}
|
|
|
1360 |
}
|
|
|
1361 |
|
|
|
1362 |
} else if ($newlimit == MEMORY_HUGE) {
|
|
|
1363 |
// MEMORY_HUGE uses 2G or MEMORY_EXTRA, whichever is bigger.
|
|
|
1364 |
$newlimit = get_real_size('2G');
|
|
|
1365 |
if (!empty($CFG->extramemorylimit)) {
|
|
|
1366 |
$extra = get_real_size($CFG->extramemorylimit);
|
|
|
1367 |
if ($extra > $newlimit) {
|
|
|
1368 |
$newlimit = $extra;
|
|
|
1369 |
}
|
|
|
1370 |
}
|
|
|
1371 |
|
|
|
1372 |
} else {
|
|
|
1373 |
$newlimit = get_real_size($newlimit);
|
|
|
1374 |
}
|
|
|
1375 |
|
|
|
1376 |
if ($newlimit <= 0) {
|
|
|
1377 |
debugging('Invalid memory limit specified.');
|
|
|
1378 |
return false;
|
|
|
1379 |
}
|
|
|
1380 |
|
|
|
1381 |
$cur = ini_get('memory_limit');
|
|
|
1382 |
if (empty($cur)) {
|
|
|
1383 |
// if php is compiled without --enable-memory-limits
|
|
|
1384 |
// apparently memory_limit is set to ''
|
|
|
1385 |
$cur = 0;
|
|
|
1386 |
} else {
|
|
|
1387 |
if ($cur == -1){
|
|
|
1388 |
return true; // unlimited mem!
|
|
|
1389 |
}
|
|
|
1390 |
$cur = get_real_size($cur);
|
|
|
1391 |
}
|
|
|
1392 |
|
|
|
1393 |
if ($newlimit > $cur) {
|
|
|
1394 |
ini_set('memory_limit', $newlimit);
|
|
|
1395 |
return true;
|
|
|
1396 |
}
|
|
|
1397 |
return false;
|
|
|
1398 |
}
|
|
|
1399 |
|
|
|
1400 |
/**
|
|
|
1401 |
* Function to reduce the memory limit to a new value.
|
|
|
1402 |
* Will respect the memory limit if it is lower, thus allowing
|
|
|
1403 |
* settings in php.ini, apache conf or command line switches
|
|
|
1404 |
* to override it
|
|
|
1405 |
*
|
|
|
1406 |
* The memory limit should be expressed with a string (eg:'64M')
|
|
|
1407 |
*
|
|
|
1408 |
* @param string $newlimit the new memory limit
|
|
|
1409 |
* @return bool
|
|
|
1410 |
*/
|
|
|
1411 |
function reduce_memory_limit($newlimit) {
|
|
|
1412 |
if (empty($newlimit)) {
|
|
|
1413 |
return false;
|
|
|
1414 |
}
|
|
|
1415 |
$cur = ini_get('memory_limit');
|
|
|
1416 |
if (empty($cur)) {
|
|
|
1417 |
// if php is compiled without --enable-memory-limits
|
|
|
1418 |
// apparently memory_limit is set to ''
|
|
|
1419 |
$cur = 0;
|
|
|
1420 |
} else {
|
|
|
1421 |
if ($cur == -1){
|
|
|
1422 |
return true; // unlimited mem!
|
|
|
1423 |
}
|
|
|
1424 |
$cur = get_real_size($cur);
|
|
|
1425 |
}
|
|
|
1426 |
|
|
|
1427 |
$new = get_real_size($newlimit);
|
|
|
1428 |
// -1 is smaller, but it means unlimited
|
|
|
1429 |
if ($new < $cur && $new != -1) {
|
|
|
1430 |
ini_set('memory_limit', $newlimit);
|
|
|
1431 |
return true;
|
|
|
1432 |
}
|
|
|
1433 |
return false;
|
|
|
1434 |
}
|
|
|
1435 |
|
|
|
1436 |
/**
|
|
|
1437 |
* Converts numbers like 10M into bytes.
|
|
|
1438 |
*
|
|
|
1439 |
* @param string $size The size to be converted
|
|
|
1440 |
* @return int
|
|
|
1441 |
*/
|
|
|
1442 |
function get_real_size($size = 0) {
|
|
|
1443 |
if (!$size) {
|
|
|
1444 |
return 0;
|
|
|
1445 |
}
|
|
|
1446 |
|
|
|
1447 |
static $binaryprefixes = array(
|
|
|
1448 |
'K' => 1024 ** 1,
|
|
|
1449 |
'k' => 1024 ** 1,
|
|
|
1450 |
'M' => 1024 ** 2,
|
|
|
1451 |
'm' => 1024 ** 2,
|
|
|
1452 |
'G' => 1024 ** 3,
|
|
|
1453 |
'g' => 1024 ** 3,
|
|
|
1454 |
'T' => 1024 ** 4,
|
|
|
1455 |
't' => 1024 ** 4,
|
|
|
1456 |
'P' => 1024 ** 5,
|
|
|
1457 |
'p' => 1024 ** 5,
|
|
|
1458 |
);
|
|
|
1459 |
|
|
|
1460 |
if (preg_match('/^([0-9]+)([KMGTP])/i', $size, $matches)) {
|
|
|
1461 |
return $matches[1] * $binaryprefixes[$matches[2]];
|
|
|
1462 |
}
|
|
|
1463 |
|
|
|
1464 |
return (int) $size;
|
|
|
1465 |
}
|
|
|
1466 |
|
|
|
1467 |
/**
|
|
|
1468 |
* Try to disable all output buffering and purge
|
|
|
1469 |
* all headers.
|
|
|
1470 |
*
|
|
|
1471 |
* @access private to be called only from lib/setup.php !
|
|
|
1472 |
* @return void
|
|
|
1473 |
*/
|
|
|
1474 |
function disable_output_buffering() {
|
|
|
1475 |
$olddebug = error_reporting(0);
|
|
|
1476 |
|
|
|
1477 |
// disable compression, it would prevent closing of buffers
|
|
|
1478 |
if (ini_get_bool('zlib.output_compression')) {
|
|
|
1479 |
ini_set('zlib.output_compression', 'Off');
|
|
|
1480 |
}
|
|
|
1481 |
|
|
|
1482 |
// try to flush everything all the time
|
|
|
1483 |
ob_implicit_flush(true);
|
|
|
1484 |
|
|
|
1485 |
// close all buffers if possible and discard any existing output
|
|
|
1486 |
// this can actually work around some whitespace problems in config.php
|
|
|
1487 |
while(ob_get_level()) {
|
|
|
1488 |
if (!ob_end_clean()) {
|
|
|
1489 |
// prevent infinite loop when buffer can not be closed
|
|
|
1490 |
break;
|
|
|
1491 |
}
|
|
|
1492 |
}
|
|
|
1493 |
|
|
|
1494 |
// disable any other output handlers
|
|
|
1495 |
ini_set('output_handler', '');
|
|
|
1496 |
|
|
|
1497 |
error_reporting($olddebug);
|
|
|
1498 |
|
|
|
1499 |
// Disable buffering in nginx.
|
|
|
1500 |
header('X-Accel-Buffering: no');
|
|
|
1501 |
|
|
|
1502 |
}
|
|
|
1503 |
|
|
|
1504 |
/**
|
|
|
1505 |
* Check whether a major upgrade is needed.
|
|
|
1506 |
*
|
|
|
1507 |
* That is defined as an upgrade that changes something really fundamental
|
|
|
1508 |
* in the database, so nothing can possibly work until the database has
|
|
|
1509 |
* been updated, and that is defined by the hard-coded version number in
|
|
|
1510 |
* this function.
|
|
|
1511 |
*
|
|
|
1512 |
* @return bool
|
|
|
1513 |
*/
|
|
|
1514 |
function is_major_upgrade_required() {
|
|
|
1515 |
global $CFG;
|
|
|
1516 |
$lastmajordbchanges = 2024010400.00; // This should be the version where the breaking changes happen.
|
|
|
1517 |
|
|
|
1518 |
$required = empty($CFG->version);
|
|
|
1519 |
$required = $required || (float)$CFG->version < $lastmajordbchanges;
|
|
|
1520 |
$required = $required || during_initial_install();
|
|
|
1521 |
$required = $required || !empty($CFG->adminsetuppending);
|
|
|
1522 |
|
|
|
1523 |
return $required;
|
|
|
1524 |
}
|
|
|
1525 |
|
|
|
1526 |
/**
|
|
|
1527 |
* Redirect to the Notifications page if a major upgrade is required, and
|
|
|
1528 |
* terminate the current user session.
|
|
|
1529 |
*/
|
|
|
1530 |
function redirect_if_major_upgrade_required() {
|
|
|
1531 |
global $CFG;
|
|
|
1532 |
if (is_major_upgrade_required()) {
|
|
|
1533 |
try {
|
|
|
1534 |
@\core\session\manager::terminate_current();
|
|
|
1535 |
} catch (Exception $e) {
|
|
|
1536 |
// Ignore any errors, redirect to upgrade anyway.
|
|
|
1537 |
}
|
|
|
1538 |
$url = $CFG->wwwroot . '/' . $CFG->admin . '/index.php';
|
|
|
1539 |
@header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other');
|
|
|
1540 |
@header('Location: ' . $url);
|
|
|
1541 |
echo bootstrap_renderer::plain_redirect_message(htmlspecialchars($url, ENT_COMPAT));
|
|
|
1542 |
exit;
|
|
|
1543 |
}
|
|
|
1544 |
}
|
|
|
1545 |
|
|
|
1546 |
/**
|
|
|
1547 |
* Makes sure that upgrade process is not running
|
|
|
1548 |
*
|
|
|
1549 |
* To be inserted in the core functions that can not be called by pluigns during upgrade.
|
|
|
1550 |
* Core upgrade should not use any API functions at all.
|
|
|
1551 |
* See {@link https://moodledev.io/docs/guides/upgrade#upgrade-code-restrictions}
|
|
|
1552 |
*
|
|
|
1553 |
* @throws moodle_exception if executed from inside of upgrade script and $warningonly is false
|
|
|
1554 |
* @param bool $warningonly if true displays a warning instead of throwing an exception
|
|
|
1555 |
* @return bool true if executed from outside of upgrade process, false if from inside upgrade process and function is used for warning only
|
|
|
1556 |
*/
|
|
|
1557 |
function upgrade_ensure_not_running($warningonly = false) {
|
|
|
1558 |
global $CFG;
|
|
|
1559 |
if (!empty($CFG->upgraderunning)) {
|
|
|
1560 |
if (!$warningonly) {
|
|
|
1561 |
throw new moodle_exception('cannotexecduringupgrade');
|
|
|
1562 |
} else {
|
|
|
1563 |
debugging(get_string('cannotexecduringupgrade', 'error'), DEBUG_DEVELOPER);
|
|
|
1564 |
return false;
|
|
|
1565 |
}
|
|
|
1566 |
}
|
|
|
1567 |
return true;
|
|
|
1568 |
}
|
|
|
1569 |
|
|
|
1570 |
/**
|
|
|
1571 |
* Function to check if a directory exists and by default create it if not exists.
|
|
|
1572 |
*
|
|
|
1573 |
* Previously this was accepting paths only from dataroot, but we now allow
|
|
|
1574 |
* files outside of dataroot if you supply custom paths for some settings in config.php.
|
|
|
1575 |
* This function does not verify that the directory is writable.
|
|
|
1576 |
*
|
|
|
1577 |
* NOTE: this function uses current file stat cache,
|
|
|
1578 |
* please use clearstatcache() before this if you expect that the
|
|
|
1579 |
* directories may have been removed recently from a different request.
|
|
|
1580 |
*
|
|
|
1581 |
* @param string $dir absolute directory path
|
|
|
1582 |
* @param boolean $create directory if does not exist
|
|
|
1583 |
* @param boolean $recursive create directory recursively
|
|
|
1584 |
* @return boolean true if directory exists or created, false otherwise
|
|
|
1585 |
*/
|
|
|
1586 |
function check_dir_exists($dir, $create = true, $recursive = true) {
|
|
|
1587 |
global $CFG;
|
|
|
1588 |
|
|
|
1589 |
umask($CFG->umaskpermissions);
|
|
|
1590 |
|
|
|
1591 |
if (is_dir($dir)) {
|
|
|
1592 |
return true;
|
|
|
1593 |
}
|
|
|
1594 |
|
|
|
1595 |
if (!$create) {
|
|
|
1596 |
return false;
|
|
|
1597 |
}
|
|
|
1598 |
|
|
|
1599 |
return mkdir($dir, $CFG->directorypermissions, $recursive);
|
|
|
1600 |
}
|
|
|
1601 |
|
|
|
1602 |
/**
|
|
|
1603 |
* Create a new unique directory within the specified directory.
|
|
|
1604 |
*
|
|
|
1605 |
* @param string $basedir The directory to create your new unique directory within.
|
|
|
1606 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1607 |
* @return string The created directory
|
|
|
1608 |
* @throws invalid_dataroot_permissions
|
|
|
1609 |
*/
|
|
|
1610 |
function make_unique_writable_directory($basedir, $exceptiononerror = true) {
|
|
|
1611 |
if (!is_dir($basedir) || !is_writable($basedir)) {
|
|
|
1612 |
// The basedir is not writable. We will not be able to create the child directory.
|
|
|
1613 |
if ($exceptiononerror) {
|
|
|
1614 |
throw new invalid_dataroot_permissions($basedir . ' is not writable. Unable to create a unique directory within it.');
|
|
|
1615 |
} else {
|
|
|
1616 |
return false;
|
|
|
1617 |
}
|
|
|
1618 |
}
|
|
|
1619 |
|
|
|
1620 |
do {
|
|
|
1621 |
// Let's use uniqid() because it's "unique enough" (microtime based). The loop does handle repetitions.
|
|
|
1622 |
// Windows and old PHP don't like very long paths, so try to keep this shorter. See MDL-69975.
|
|
|
1623 |
$uniquedir = $basedir . DIRECTORY_SEPARATOR . uniqid();
|
|
|
1624 |
} while (
|
|
|
1625 |
// Ensure that basedir is still writable - if we do not check, we could get stuck in a loop here.
|
|
|
1626 |
is_writable($basedir) &&
|
|
|
1627 |
|
|
|
1628 |
// Make the new unique directory. If the directory already exists, it will return false.
|
|
|
1629 |
!make_writable_directory($uniquedir, $exceptiononerror) &&
|
|
|
1630 |
|
|
|
1631 |
// Ensure that the directory now exists
|
|
|
1632 |
file_exists($uniquedir) && is_dir($uniquedir)
|
|
|
1633 |
);
|
|
|
1634 |
|
|
|
1635 |
// Check that the directory was correctly created.
|
|
|
1636 |
if (!file_exists($uniquedir) || !is_dir($uniquedir) || !is_writable($uniquedir)) {
|
|
|
1637 |
if ($exceptiononerror) {
|
|
|
1638 |
throw new invalid_dataroot_permissions('Unique directory creation failed.');
|
|
|
1639 |
} else {
|
|
|
1640 |
return false;
|
|
|
1641 |
}
|
|
|
1642 |
}
|
|
|
1643 |
|
|
|
1644 |
return $uniquedir;
|
|
|
1645 |
}
|
|
|
1646 |
|
|
|
1647 |
/**
|
|
|
1648 |
* Create a directory and make sure it is writable.
|
|
|
1649 |
*
|
|
|
1650 |
* @private
|
|
|
1651 |
* @param string $dir the full path of the directory to be created
|
|
|
1652 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1653 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1654 |
*/
|
|
|
1655 |
function make_writable_directory($dir, $exceptiononerror = true) {
|
|
|
1656 |
global $CFG;
|
|
|
1657 |
|
|
|
1658 |
if (file_exists($dir) and !is_dir($dir)) {
|
|
|
1659 |
if ($exceptiononerror) {
|
|
|
1660 |
throw new coding_exception($dir.' directory can not be created, file with the same name already exists.');
|
|
|
1661 |
} else {
|
|
|
1662 |
return false;
|
|
|
1663 |
}
|
|
|
1664 |
}
|
|
|
1665 |
|
|
|
1666 |
umask($CFG->umaskpermissions);
|
|
|
1667 |
|
|
|
1668 |
if (!file_exists($dir)) {
|
|
|
1669 |
if (!@mkdir($dir, $CFG->directorypermissions, true)) {
|
|
|
1670 |
clearstatcache();
|
|
|
1671 |
// There might be a race condition when creating directory.
|
|
|
1672 |
if (!is_dir($dir)) {
|
|
|
1673 |
if ($exceptiononerror) {
|
|
|
1674 |
throw new invalid_dataroot_permissions($dir.' can not be created, check permissions.');
|
|
|
1675 |
} else {
|
|
|
1676 |
debugging('Can not create directory: '.$dir, DEBUG_DEVELOPER);
|
|
|
1677 |
return false;
|
|
|
1678 |
}
|
|
|
1679 |
}
|
|
|
1680 |
}
|
|
|
1681 |
}
|
|
|
1682 |
|
|
|
1683 |
if (!is_writable($dir)) {
|
|
|
1684 |
if ($exceptiononerror) {
|
|
|
1685 |
throw new invalid_dataroot_permissions($dir.' is not writable, check permissions.');
|
|
|
1686 |
} else {
|
|
|
1687 |
return false;
|
|
|
1688 |
}
|
|
|
1689 |
}
|
|
|
1690 |
|
|
|
1691 |
return $dir;
|
|
|
1692 |
}
|
|
|
1693 |
|
|
|
1694 |
/**
|
|
|
1695 |
* Protect a directory from web access.
|
|
|
1696 |
* Could be extended in the future to support other mechanisms (e.g. other webservers).
|
|
|
1697 |
*
|
|
|
1698 |
* @private
|
|
|
1699 |
* @param string $dir the full path of the directory to be protected
|
|
|
1700 |
*/
|
|
|
1701 |
function protect_directory($dir) {
|
|
|
1702 |
global $CFG;
|
|
|
1703 |
// Make sure a .htaccess file is here, JUST IN CASE the files area is in the open and .htaccess is supported
|
|
|
1704 |
if (!file_exists("$dir/.htaccess")) {
|
|
|
1705 |
if ($handle = fopen("$dir/.htaccess", 'w')) { // For safety
|
|
|
1706 |
@fwrite($handle, "deny from all\r\nAllowOverride None\r\nNote: this file is broken intentionally, we do not want anybody to undo it in subdirectory!\r\n");
|
|
|
1707 |
@fclose($handle);
|
|
|
1708 |
@chmod("$dir/.htaccess", $CFG->filepermissions);
|
|
|
1709 |
}
|
|
|
1710 |
}
|
|
|
1711 |
}
|
|
|
1712 |
|
|
|
1713 |
/**
|
|
|
1714 |
* Create a directory under dataroot and make sure it is writable.
|
|
|
1715 |
* Do not use for temporary and cache files - see make_temp_directory() and make_cache_directory().
|
|
|
1716 |
*
|
|
|
1717 |
* @param string $directory the full path of the directory to be created under $CFG->dataroot
|
|
|
1718 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1719 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1720 |
*/
|
|
|
1721 |
function make_upload_directory($directory, $exceptiononerror = true) {
|
|
|
1722 |
global $CFG;
|
|
|
1723 |
|
|
|
1724 |
if (strpos($directory, 'temp/') === 0 or $directory === 'temp') {
|
|
|
1725 |
debugging('Use make_temp_directory() for creation of temporary directory and $CFG->tempdir to get the location.');
|
|
|
1726 |
|
|
|
1727 |
} else if (strpos($directory, 'cache/') === 0 or $directory === 'cache') {
|
|
|
1728 |
debugging('Use make_cache_directory() for creation of cache directory and $CFG->cachedir to get the location.');
|
|
|
1729 |
|
|
|
1730 |
} else if (strpos($directory, 'localcache/') === 0 or $directory === 'localcache') {
|
|
|
1731 |
debugging('Use make_localcache_directory() for creation of local cache directory and $CFG->localcachedir to get the location.');
|
|
|
1732 |
}
|
|
|
1733 |
|
|
|
1734 |
protect_directory($CFG->dataroot);
|
|
|
1735 |
return make_writable_directory("$CFG->dataroot/$directory", $exceptiononerror);
|
|
|
1736 |
}
|
|
|
1737 |
|
|
|
1738 |
/**
|
|
|
1739 |
* Get a per-request storage directory in the tempdir.
|
|
|
1740 |
*
|
|
|
1741 |
* The directory is automatically cleaned up during the shutdown handler.
|
|
|
1742 |
*
|
|
|
1743 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1744 |
* @param bool $forcecreate Force creation of a new parent directory
|
|
|
1745 |
* @return string Returns full path to directory if successful, false if not; may throw exception
|
|
|
1746 |
*/
|
|
|
1747 |
function get_request_storage_directory($exceptiononerror = true, bool $forcecreate = false) {
|
|
|
1748 |
global $CFG;
|
|
|
1749 |
|
|
|
1750 |
static $requestdir = null;
|
|
|
1751 |
|
|
|
1752 |
$writabledirectoryexists = (null !== $requestdir);
|
|
|
1753 |
$writabledirectoryexists = $writabledirectoryexists && file_exists($requestdir);
|
|
|
1754 |
$writabledirectoryexists = $writabledirectoryexists && is_dir($requestdir);
|
|
|
1755 |
$writabledirectoryexists = $writabledirectoryexists && is_writable($requestdir);
|
|
|
1756 |
$createnewdirectory = $forcecreate || !$writabledirectoryexists;
|
|
|
1757 |
|
|
|
1758 |
if ($createnewdirectory) {
|
|
|
1759 |
|
|
|
1760 |
// Let's add the first chars of siteidentifier only. This is to help separate
|
|
|
1761 |
// paths on systems which host multiple moodles. We don't use the full id
|
|
|
1762 |
// as Windows and old PHP don't like very long paths. See MDL-69975.
|
|
|
1763 |
$basedir = $CFG->localrequestdir . '/' . substr($CFG->siteidentifier, 0, 4);
|
|
|
1764 |
|
|
|
1765 |
make_writable_directory($basedir);
|
|
|
1766 |
protect_directory($basedir);
|
|
|
1767 |
|
|
|
1768 |
if ($dir = make_unique_writable_directory($basedir, $exceptiononerror)) {
|
|
|
1769 |
// Register a shutdown handler to remove the directory.
|
|
|
1770 |
\core_shutdown_manager::register_function('remove_dir', [$dir]);
|
|
|
1771 |
}
|
|
|
1772 |
|
|
|
1773 |
$requestdir = $dir;
|
|
|
1774 |
}
|
|
|
1775 |
|
|
|
1776 |
return $requestdir;
|
|
|
1777 |
}
|
|
|
1778 |
|
|
|
1779 |
/**
|
|
|
1780 |
* Create a per-request directory and make sure it is writable.
|
|
|
1781 |
* This can only be used during the current request and will be tidied away
|
|
|
1782 |
* automatically afterwards.
|
|
|
1783 |
*
|
|
|
1784 |
* A new, unique directory is always created within a shared base request directory.
|
|
|
1785 |
*
|
|
|
1786 |
* In some exceptional cases an alternative base directory may be required. This can be accomplished using the
|
|
|
1787 |
* $forcecreate parameter. Typically this will only be requried where the file may be required during a shutdown handler
|
|
|
1788 |
* which may or may not be registered after a previous request directory has been created.
|
|
|
1789 |
*
|
|
|
1790 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1791 |
* @param bool $forcecreate Force creation of a new parent directory
|
|
|
1792 |
* @return string The full path to directory if successful, false if not; may throw exception
|
|
|
1793 |
*/
|
|
|
1794 |
function make_request_directory(bool $exceptiononerror = true, bool $forcecreate = false) {
|
|
|
1795 |
$basedir = get_request_storage_directory($exceptiononerror, $forcecreate);
|
|
|
1796 |
return make_unique_writable_directory($basedir, $exceptiononerror);
|
|
|
1797 |
}
|
|
|
1798 |
|
|
|
1799 |
/**
|
|
|
1800 |
* Get the full path of a directory under $CFG->backuptempdir.
|
|
|
1801 |
*
|
|
|
1802 |
* @param string $directory the relative path of the directory under $CFG->backuptempdir
|
|
|
1803 |
* @return string|false Returns full path to directory given a valid string; otherwise, false.
|
|
|
1804 |
*/
|
|
|
1805 |
function get_backup_temp_directory($directory) {
|
|
|
1806 |
global $CFG;
|
|
|
1807 |
if (($directory === null) || ($directory === false)) {
|
|
|
1808 |
return false;
|
|
|
1809 |
}
|
|
|
1810 |
return "$CFG->backuptempdir/$directory";
|
|
|
1811 |
}
|
|
|
1812 |
|
|
|
1813 |
/**
|
|
|
1814 |
* Create a directory under $CFG->backuptempdir and make sure it is writable.
|
|
|
1815 |
*
|
|
|
1816 |
* Do not use for storing generic temp files - see make_temp_directory() instead for this purpose.
|
|
|
1817 |
*
|
|
|
1818 |
* Backup temporary files must be on a shared storage.
|
|
|
1819 |
*
|
|
|
1820 |
* @param string $directory the relative path of the directory to be created under $CFG->backuptempdir
|
|
|
1821 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1822 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1823 |
*/
|
|
|
1824 |
function make_backup_temp_directory($directory, $exceptiononerror = true) {
|
|
|
1825 |
global $CFG;
|
|
|
1826 |
if ($CFG->backuptempdir !== "$CFG->tempdir/backup") {
|
|
|
1827 |
check_dir_exists($CFG->backuptempdir, true, true);
|
|
|
1828 |
protect_directory($CFG->backuptempdir);
|
|
|
1829 |
} else {
|
|
|
1830 |
protect_directory($CFG->tempdir);
|
|
|
1831 |
}
|
|
|
1832 |
return make_writable_directory("$CFG->backuptempdir/$directory", $exceptiononerror);
|
|
|
1833 |
}
|
|
|
1834 |
|
|
|
1835 |
/**
|
|
|
1836 |
* Create a directory under tempdir and make sure it is writable.
|
|
|
1837 |
*
|
|
|
1838 |
* Where possible, please use make_request_directory() and limit the scope
|
|
|
1839 |
* of your data to the current HTTP request.
|
|
|
1840 |
*
|
|
|
1841 |
* Do not use for storing cache files - see make_cache_directory(), and
|
|
|
1842 |
* make_localcache_directory() instead for this purpose.
|
|
|
1843 |
*
|
|
|
1844 |
* Temporary files must be on a shared storage, and heavy usage is
|
|
|
1845 |
* discouraged due to the performance impact upon clustered environments.
|
|
|
1846 |
*
|
|
|
1847 |
* @param string $directory the full path of the directory to be created under $CFG->tempdir
|
|
|
1848 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1849 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1850 |
*/
|
|
|
1851 |
function make_temp_directory($directory, $exceptiononerror = true) {
|
|
|
1852 |
global $CFG;
|
|
|
1853 |
if ($CFG->tempdir !== "$CFG->dataroot/temp") {
|
|
|
1854 |
check_dir_exists($CFG->tempdir, true, true);
|
|
|
1855 |
protect_directory($CFG->tempdir);
|
|
|
1856 |
} else {
|
|
|
1857 |
protect_directory($CFG->dataroot);
|
|
|
1858 |
}
|
|
|
1859 |
return make_writable_directory("$CFG->tempdir/$directory", $exceptiononerror);
|
|
|
1860 |
}
|
|
|
1861 |
|
|
|
1862 |
/**
|
|
|
1863 |
* Create a directory under cachedir and make sure it is writable.
|
|
|
1864 |
*
|
|
|
1865 |
* Note: this cache directory is shared by all cluster nodes.
|
|
|
1866 |
*
|
|
|
1867 |
* @param string $directory the full path of the directory to be created under $CFG->cachedir
|
|
|
1868 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1869 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1870 |
*/
|
|
|
1871 |
function make_cache_directory($directory, $exceptiononerror = true) {
|
|
|
1872 |
global $CFG;
|
|
|
1873 |
if ($CFG->cachedir !== "$CFG->dataroot/cache") {
|
|
|
1874 |
check_dir_exists($CFG->cachedir, true, true);
|
|
|
1875 |
protect_directory($CFG->cachedir);
|
|
|
1876 |
} else {
|
|
|
1877 |
protect_directory($CFG->dataroot);
|
|
|
1878 |
}
|
|
|
1879 |
return make_writable_directory("$CFG->cachedir/$directory", $exceptiononerror);
|
|
|
1880 |
}
|
|
|
1881 |
|
|
|
1882 |
/**
|
|
|
1883 |
* Create a directory under localcachedir and make sure it is writable.
|
|
|
1884 |
* The files in this directory MUST NOT change, use revisions or content hashes to
|
|
|
1885 |
* work around this limitation - this means you can only add new files here.
|
|
|
1886 |
*
|
|
|
1887 |
* The content of this directory gets purged automatically on all cluster nodes
|
|
|
1888 |
* after calling purge_all_caches() before new data is written to this directory.
|
|
|
1889 |
*
|
|
|
1890 |
* Note: this local cache directory does not need to be shared by cluster nodes.
|
|
|
1891 |
*
|
|
|
1892 |
* @param string $directory the relative path of the directory to be created under $CFG->localcachedir
|
|
|
1893 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1894 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1895 |
*/
|
|
|
1896 |
function make_localcache_directory($directory, $exceptiononerror = true) {
|
|
|
1897 |
global $CFG;
|
|
|
1898 |
|
|
|
1899 |
make_writable_directory($CFG->localcachedir, $exceptiononerror);
|
|
|
1900 |
|
|
|
1901 |
if ($CFG->localcachedir !== "$CFG->dataroot/localcache") {
|
|
|
1902 |
protect_directory($CFG->localcachedir);
|
|
|
1903 |
} else {
|
|
|
1904 |
protect_directory($CFG->dataroot);
|
|
|
1905 |
}
|
|
|
1906 |
|
|
|
1907 |
if (!isset($CFG->localcachedirpurged)) {
|
|
|
1908 |
$CFG->localcachedirpurged = 0;
|
|
|
1909 |
}
|
|
|
1910 |
$timestampfile = "$CFG->localcachedir/.lastpurged";
|
|
|
1911 |
|
|
|
1912 |
if (!file_exists($timestampfile)) {
|
|
|
1913 |
touch($timestampfile);
|
|
|
1914 |
@chmod($timestampfile, $CFG->filepermissions);
|
|
|
1915 |
|
|
|
1916 |
} else if (filemtime($timestampfile) < $CFG->localcachedirpurged) {
|
|
|
1917 |
// This means our local cached dir was not purged yet.
|
|
|
1918 |
remove_dir($CFG->localcachedir, true);
|
|
|
1919 |
if ($CFG->localcachedir !== "$CFG->dataroot/localcache") {
|
|
|
1920 |
protect_directory($CFG->localcachedir);
|
|
|
1921 |
}
|
|
|
1922 |
touch($timestampfile);
|
|
|
1923 |
@chmod($timestampfile, $CFG->filepermissions);
|
|
|
1924 |
clearstatcache();
|
|
|
1925 |
}
|
|
|
1926 |
|
|
|
1927 |
if ($directory === '') {
|
|
|
1928 |
return $CFG->localcachedir;
|
|
|
1929 |
}
|
|
|
1930 |
|
|
|
1931 |
return make_writable_directory("$CFG->localcachedir/$directory", $exceptiononerror);
|
|
|
1932 |
}
|
|
|
1933 |
|
|
|
1934 |
/**
|
|
|
1935 |
* Webserver access user logging
|
|
|
1936 |
*/
|
|
|
1937 |
function set_access_log_user() {
|
|
|
1938 |
global $USER, $CFG;
|
|
|
1939 |
if ($USER && isset($USER->username)) {
|
|
|
1940 |
$logmethod = '';
|
|
|
1941 |
$logvalue = 0;
|
|
|
1942 |
if (!empty($CFG->apacheloguser) && function_exists('apache_note')) {
|
|
|
1943 |
$logmethod = 'apache';
|
|
|
1944 |
$logvalue = $CFG->apacheloguser;
|
|
|
1945 |
}
|
|
|
1946 |
if (!empty($CFG->headerloguser)) {
|
|
|
1947 |
$logmethod = 'header';
|
|
|
1948 |
$logvalue = $CFG->headerloguser;
|
|
|
1949 |
}
|
|
|
1950 |
if (!empty($logmethod)) {
|
|
|
1951 |
$loguserid = $USER->id;
|
|
|
1952 |
$logusername = clean_filename($USER->username);
|
|
|
1953 |
$logname = '';
|
|
|
1954 |
if (isset($USER->firstname)) {
|
|
|
1955 |
// We can assume both will be set
|
|
|
1956 |
// - even if to empty.
|
|
|
1957 |
$logname = clean_filename($USER->firstname . " " . $USER->lastname);
|
|
|
1958 |
}
|
|
|
1959 |
if (\core\session\manager::is_loggedinas()) {
|
|
|
1960 |
$realuser = \core\session\manager::get_realuser();
|
|
|
1961 |
$logusername = clean_filename($realuser->username." as ".$logusername);
|
|
|
1962 |
$logname = clean_filename($realuser->firstname." ".$realuser->lastname ." as ".$logname);
|
|
|
1963 |
$loguserid = clean_filename($realuser->id." as ".$loguserid);
|
|
|
1964 |
}
|
|
|
1965 |
switch ($logvalue) {
|
|
|
1966 |
case 3:
|
|
|
1967 |
$logname = $logusername;
|
|
|
1968 |
break;
|
|
|
1969 |
case 2:
|
|
|
1970 |
$logname = $logname;
|
|
|
1971 |
break;
|
|
|
1972 |
case 1:
|
|
|
1973 |
default:
|
|
|
1974 |
$logname = $loguserid;
|
|
|
1975 |
break;
|
|
|
1976 |
}
|
|
|
1977 |
if ($logmethod == 'apache') {
|
|
|
1978 |
apache_note('MOODLEUSER', $logname);
|
|
|
1979 |
}
|
|
|
1980 |
|
|
|
1981 |
if ($logmethod == 'header' && !headers_sent()) {
|
|
|
1982 |
header("X-MOODLEUSER: $logname");
|
|
|
1983 |
}
|
|
|
1984 |
}
|
|
|
1985 |
}
|
|
|
1986 |
}
|
|
|
1987 |
|
|
|
1988 |
/**
|
|
|
1989 |
* This class solves the problem of how to initialise $OUTPUT.
|
|
|
1990 |
*
|
|
|
1991 |
* The problem is caused be two factors
|
|
|
1992 |
* <ol>
|
|
|
1993 |
* <li>On the one hand, we cannot be sure when output will start. In particular,
|
|
|
1994 |
* an error, which needs to be displayed, could be thrown at any time.</li>
|
|
|
1995 |
* <li>On the other hand, we cannot be sure when we will have all the information
|
|
|
1996 |
* necessary to correctly initialise $OUTPUT. $OUTPUT depends on the theme, which
|
|
|
1997 |
* (potentially) depends on the current course, course categories, and logged in user.
|
|
|
1998 |
* It also depends on whether the current page requires HTTPS.</li>
|
|
|
1999 |
* </ol>
|
|
|
2000 |
*
|
|
|
2001 |
* So, it is hard to find a single natural place during Moodle script execution,
|
|
|
2002 |
* which we can guarantee is the right time to initialise $OUTPUT. Instead we
|
|
|
2003 |
* adopt the following strategy
|
|
|
2004 |
* <ol>
|
|
|
2005 |
* <li>We will initialise $OUTPUT the first time it is used.</li>
|
|
|
2006 |
* <li>If, after $OUTPUT has been initialised, the script tries to change something
|
|
|
2007 |
* that $OUTPUT depends on, we throw an exception making it clear that the script
|
|
|
2008 |
* did something wrong.
|
|
|
2009 |
* </ol>
|
|
|
2010 |
*
|
|
|
2011 |
* The only problem with that is, how do we initialise $OUTPUT on first use if,
|
|
|
2012 |
* it is going to be used like $OUTPUT->somthing(...)? Well that is where this
|
|
|
2013 |
* class comes in. Initially, we set up $OUTPUT = new bootstrap_renderer(). Then,
|
|
|
2014 |
* when any method is called on that object, we initialise $OUTPUT, and pass the call on.
|
|
|
2015 |
*
|
|
|
2016 |
* Note that this class is used before lib/outputlib.php has been loaded, so we
|
|
|
2017 |
* must be careful referring to classes/functions from there, they may not be
|
|
|
2018 |
* defined yet, and we must avoid fatal errors.
|
|
|
2019 |
*
|
|
|
2020 |
* @copyright 2009 Tim Hunt
|
|
|
2021 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
2022 |
* @since Moodle 2.0
|
|
|
2023 |
*/
|
|
|
2024 |
class bootstrap_renderer {
|
|
|
2025 |
/**
|
|
|
2026 |
* Handles re-entrancy. Without this, errors or debugging output that occur
|
|
|
2027 |
* during the initialisation of $OUTPUT, cause infinite recursion.
|
|
|
2028 |
* @var boolean
|
|
|
2029 |
*/
|
|
|
2030 |
protected $initialising = false;
|
|
|
2031 |
|
|
|
2032 |
/**
|
|
|
2033 |
* Have we started output yet?
|
|
|
2034 |
* @return boolean true if the header has been printed.
|
|
|
2035 |
*/
|
|
|
2036 |
public function has_started() {
|
|
|
2037 |
return false;
|
|
|
2038 |
}
|
|
|
2039 |
|
|
|
2040 |
/**
|
|
|
2041 |
* Constructor - to be used by core code only.
|
|
|
2042 |
* @param string $method The method to call
|
|
|
2043 |
* @param array $arguments Arguments to pass to the method being called
|
|
|
2044 |
* @return string
|
|
|
2045 |
*/
|
|
|
2046 |
public function __call($method, $arguments) {
|
|
|
2047 |
global $OUTPUT, $PAGE;
|
|
|
2048 |
|
|
|
2049 |
$recursing = false;
|
|
|
2050 |
if ($method == 'notification') {
|
|
|
2051 |
// Catch infinite recursion caused by debugging output during print_header.
|
|
|
2052 |
$backtrace = debug_backtrace();
|
|
|
2053 |
array_shift($backtrace);
|
|
|
2054 |
array_shift($backtrace);
|
|
|
2055 |
$recursing = is_early_init($backtrace);
|
|
|
2056 |
}
|
|
|
2057 |
|
|
|
2058 |
$earlymethods = array(
|
|
|
2059 |
'fatal_error' => 'early_error',
|
|
|
2060 |
'notification' => 'early_notification',
|
|
|
2061 |
);
|
|
|
2062 |
|
|
|
2063 |
// If lib/outputlib.php has been loaded, call it.
|
|
|
2064 |
if (!empty($PAGE) && !$recursing) {
|
|
|
2065 |
if (array_key_exists($method, $earlymethods)) {
|
|
|
2066 |
//prevent PAGE->context warnings - exceptions might appear before we set any context
|
|
|
2067 |
$PAGE->set_context(null);
|
|
|
2068 |
}
|
|
|
2069 |
$PAGE->initialise_theme_and_output();
|
|
|
2070 |
return call_user_func_array(array($OUTPUT, $method), $arguments);
|
|
|
2071 |
}
|
|
|
2072 |
|
|
|
2073 |
$this->initialising = true;
|
|
|
2074 |
|
|
|
2075 |
// Too soon to initialise $OUTPUT, provide a couple of key methods.
|
|
|
2076 |
if (array_key_exists($method, $earlymethods)) {
|
|
|
2077 |
return call_user_func_array(array('bootstrap_renderer', $earlymethods[$method]), $arguments);
|
|
|
2078 |
}
|
|
|
2079 |
|
|
|
2080 |
throw new coding_exception('Attempt to start output before enough information is known to initialise the theme.');
|
|
|
2081 |
}
|
|
|
2082 |
|
|
|
2083 |
/**
|
|
|
2084 |
* Returns nicely formatted error message in a div box.
|
|
|
2085 |
* @static
|
|
|
2086 |
* @param string $message error message
|
|
|
2087 |
* @param ?string $moreinfourl (ignored in early errors)
|
|
|
2088 |
* @param ?string $link (ignored in early errors)
|
|
|
2089 |
* @param ?array $backtrace
|
|
|
2090 |
* @param ?string $debuginfo
|
|
|
2091 |
* @return string
|
|
|
2092 |
*/
|
|
|
2093 |
public static function early_error_content($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
|
|
|
2094 |
global $CFG;
|
|
|
2095 |
|
|
|
2096 |
$content = "<div class='alert-danger'>$message</div>";
|
|
|
2097 |
// Check whether debug is set.
|
|
|
2098 |
$debug = (!empty($CFG->debug) && $CFG->debug >= DEBUG_DEVELOPER);
|
|
|
2099 |
// Also check we have it set in the config file. This occurs if the method to read the config table from the
|
|
|
2100 |
// database fails, reading from the config table is the first database interaction we have.
|
|
|
2101 |
$debug = $debug || (!empty($CFG->config_php_settings['debug']) && $CFG->config_php_settings['debug'] >= DEBUG_DEVELOPER );
|
|
|
2102 |
if ($debug) {
|
|
|
2103 |
if (!empty($debuginfo)) {
|
|
|
2104 |
// Remove all nasty JS.
|
|
|
2105 |
if (function_exists('s')) { // Function may be not available for some early errors.
|
|
|
2106 |
$debuginfo = s($debuginfo);
|
|
|
2107 |
} else {
|
|
|
2108 |
// Because weblib is not available for these early errors, we
|
|
|
2109 |
// just duplicate s() code here to be safe.
|
|
|
2110 |
$debuginfo = preg_replace('/&#(\d+|x[0-9a-f]+);/i', '&#$1;',
|
|
|
2111 |
htmlspecialchars($debuginfo, ENT_QUOTES | ENT_HTML401 | ENT_SUBSTITUTE));
|
|
|
2112 |
}
|
|
|
2113 |
$debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines
|
|
|
2114 |
$content .= '<div class="notifytiny">Debug info: ' . $debuginfo . '</div>';
|
|
|
2115 |
}
|
|
|
2116 |
if (!empty($backtrace)) {
|
|
|
2117 |
$content .= '<div class="notifytiny">Stack trace: ' . format_backtrace($backtrace, false) . '</div>';
|
|
|
2118 |
}
|
|
|
2119 |
}
|
|
|
2120 |
|
|
|
2121 |
return $content;
|
|
|
2122 |
}
|
|
|
2123 |
|
|
|
2124 |
/**
|
|
|
2125 |
* This function should only be called by this class, or from exception handlers
|
|
|
2126 |
* @static
|
|
|
2127 |
* @param string $message error message
|
|
|
2128 |
* @param string $moreinfourl (ignored in early errors)
|
|
|
2129 |
* @param string $link (ignored in early errors)
|
|
|
2130 |
* @param array $backtrace
|
|
|
2131 |
* @param string $debuginfo extra information for developers
|
|
|
2132 |
* @return ?string
|
|
|
2133 |
*/
|
|
|
2134 |
public static function early_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null, $errorcode = null) {
|
|
|
2135 |
global $CFG;
|
|
|
2136 |
|
|
|
2137 |
if (CLI_SCRIPT) {
|
|
|
2138 |
echo "!!! $message !!!\n";
|
|
|
2139 |
if (!empty($CFG->debug) and $CFG->debug >= DEBUG_DEVELOPER) {
|
|
|
2140 |
if (!empty($debuginfo)) {
|
|
|
2141 |
echo "\nDebug info: $debuginfo";
|
|
|
2142 |
}
|
|
|
2143 |
if (!empty($backtrace)) {
|
|
|
2144 |
echo "\nStack trace: " . format_backtrace($backtrace, true);
|
|
|
2145 |
}
|
|
|
2146 |
}
|
|
|
2147 |
return;
|
|
|
2148 |
|
|
|
2149 |
} else if (AJAX_SCRIPT) {
|
|
|
2150 |
$e = new stdClass();
|
|
|
2151 |
$e->error = $message;
|
|
|
2152 |
$e->stacktrace = NULL;
|
|
|
2153 |
$e->debuginfo = NULL;
|
|
|
2154 |
if (!empty($CFG->debug) and $CFG->debug >= DEBUG_DEVELOPER) {
|
|
|
2155 |
if (!empty($debuginfo)) {
|
|
|
2156 |
$e->debuginfo = $debuginfo;
|
|
|
2157 |
}
|
|
|
2158 |
if (!empty($backtrace)) {
|
|
|
2159 |
$e->stacktrace = format_backtrace($backtrace, true);
|
|
|
2160 |
}
|
|
|
2161 |
}
|
|
|
2162 |
$e->errorcode = $errorcode;
|
|
|
2163 |
@header('Content-Type: application/json; charset=utf-8');
|
|
|
2164 |
echo json_encode($e);
|
|
|
2165 |
return;
|
|
|
2166 |
}
|
|
|
2167 |
|
|
|
2168 |
// In the name of protocol correctness, monitoring and performance
|
|
|
2169 |
// profiling, set the appropriate error headers for machine consumption.
|
|
|
2170 |
$protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
|
|
|
2171 |
@header($protocol . ' 500 Internal Server Error');
|
|
|
2172 |
|
|
|
2173 |
// better disable any caching
|
|
|
2174 |
@header('Content-Type: text/html; charset=utf-8');
|
|
|
2175 |
@header('X-UA-Compatible: IE=edge');
|
|
|
2176 |
@header('Cache-Control: no-store, no-cache, must-revalidate');
|
|
|
2177 |
@header('Cache-Control: post-check=0, pre-check=0', false);
|
|
|
2178 |
@header('Pragma: no-cache');
|
|
|
2179 |
@header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
|
|
|
2180 |
@header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
|
|
2181 |
|
|
|
2182 |
if (function_exists('get_string')) {
|
|
|
2183 |
$strerror = get_string('error');
|
|
|
2184 |
} else {
|
|
|
2185 |
$strerror = 'Error';
|
|
|
2186 |
}
|
|
|
2187 |
|
|
|
2188 |
$content = self::early_error_content($message, $moreinfourl, $link, $backtrace, $debuginfo);
|
|
|
2189 |
|
|
|
2190 |
return self::plain_page($strerror, $content);
|
|
|
2191 |
}
|
|
|
2192 |
|
|
|
2193 |
/**
|
|
|
2194 |
* Early notification message
|
|
|
2195 |
* @static
|
|
|
2196 |
* @param string $message
|
|
|
2197 |
* @param string $classes usually notifyproblem or notifysuccess
|
|
|
2198 |
* @return string
|
|
|
2199 |
*/
|
|
|
2200 |
public static function early_notification($message, $classes = 'notifyproblem') {
|
|
|
2201 |
return '<div class="' . $classes . '">' . $message . '</div>';
|
|
|
2202 |
}
|
|
|
2203 |
|
|
|
2204 |
/**
|
|
|
2205 |
* Page should redirect message.
|
|
|
2206 |
* @static
|
|
|
2207 |
* @param string $encodedurl redirect url
|
|
|
2208 |
* @return string
|
|
|
2209 |
*/
|
|
|
2210 |
public static function plain_redirect_message($encodedurl) {
|
|
|
2211 |
$message = '<div style="margin-top: 3em; margin-left:auto; margin-right:auto; text-align:center;">' . get_string('pageshouldredirect') . '<br /><a href="'.
|
|
|
2212 |
$encodedurl .'">'. get_string('continue') .'</a></div>';
|
|
|
2213 |
return self::plain_page(get_string('redirect'), $message);
|
|
|
2214 |
}
|
|
|
2215 |
|
|
|
2216 |
/**
|
|
|
2217 |
* Early redirection page, used before full init of $PAGE global
|
|
|
2218 |
* @static
|
|
|
2219 |
* @param string $encodedurl redirect url
|
|
|
2220 |
* @param string $message redirect message
|
|
|
2221 |
* @param int $delay time in seconds
|
|
|
2222 |
* @return string redirect page
|
|
|
2223 |
*/
|
|
|
2224 |
public static function early_redirect_message($encodedurl, $message, $delay) {
|
|
|
2225 |
$meta = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />';
|
|
|
2226 |
$content = self::early_error_content($message, null, null, null);
|
|
|
2227 |
$content .= self::plain_redirect_message($encodedurl);
|
|
|
2228 |
|
|
|
2229 |
return self::plain_page(get_string('redirect'), $content, $meta);
|
|
|
2230 |
}
|
|
|
2231 |
|
|
|
2232 |
/**
|
|
|
2233 |
* Output basic html page.
|
|
|
2234 |
* @static
|
|
|
2235 |
* @param string $title page title
|
|
|
2236 |
* @param string $content page content
|
|
|
2237 |
* @param string $meta meta tag
|
|
|
2238 |
* @return string html page
|
|
|
2239 |
*/
|
|
|
2240 |
public static function plain_page($title, $content, $meta = '') {
|
|
|
2241 |
global $CFG;
|
|
|
2242 |
|
|
|
2243 |
if (function_exists('get_string') && function_exists('get_html_lang')) {
|
|
|
2244 |
$htmllang = get_html_lang();
|
|
|
2245 |
} else {
|
|
|
2246 |
$htmllang = '';
|
|
|
2247 |
}
|
|
|
2248 |
|
|
|
2249 |
$footer = '';
|
|
|
2250 |
if (function_exists('get_performance_info')) { // Function may be not available for some early errors.
|
|
|
2251 |
if (MDL_PERF_TEST) {
|
|
|
2252 |
$perfinfo = get_performance_info();
|
|
|
2253 |
$footer = '<footer>' . $perfinfo['html'] . '</footer>';
|
|
|
2254 |
}
|
|
|
2255 |
}
|
|
|
2256 |
|
|
|
2257 |
ob_start();
|
|
|
2258 |
include($CFG->dirroot . '/error/plainpage.php');
|
|
|
2259 |
$html = ob_get_contents();
|
|
|
2260 |
ob_end_clean();
|
|
|
2261 |
|
|
|
2262 |
return $html;
|
|
|
2263 |
}
|
|
|
2264 |
}
|
|
|
2265 |
|
|
|
2266 |
/**
|
|
|
2267 |
* Add http stream instrumentation
|
|
|
2268 |
*
|
|
|
2269 |
* This detects which any reads or writes to a php stream which uses
|
|
|
2270 |
* the 'http' handler. Ideally 100% of traffic uses the Moodle curl
|
|
|
2271 |
* libraries which do not use php streams.
|
|
|
2272 |
*
|
|
|
2273 |
* @param array $code stream callback code
|
|
|
2274 |
*/
|
|
|
2275 |
function proxy_log_callback($code) {
|
|
|
2276 |
if ($code == STREAM_NOTIFY_CONNECT) {
|
|
|
2277 |
$trace = debug_backtrace();
|
|
|
2278 |
$function = $trace[count($trace) - 1];
|
|
|
2279 |
$error = "Unsafe internet IO detected: {$function['function']} with arguments " . join(', ', $function['args']) . "\n";
|
|
|
2280 |
error_log($error . format_backtrace($trace, true)); // phpcs:ignore
|
|
|
2281 |
}
|
|
|
2282 |
}
|
|
|
2283 |
|
|
|
2284 |
/**
|
|
|
2285 |
* A helper function for deprecated files to use to ensure that, when they are included for unit tests,
|
|
|
2286 |
* they are run in an isolated process.
|
|
|
2287 |
*
|
|
|
2288 |
* @throws \coding_exception The exception thrown when the process is not isolated.
|
|
|
2289 |
*/
|
|
|
2290 |
function require_phpunit_isolation(): void {
|
|
|
2291 |
if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
|
|
|
2292 |
// Not a test.
|
|
|
2293 |
return;
|
|
|
2294 |
}
|
|
|
2295 |
|
|
|
2296 |
if (defined('PHPUNIT_ISOLATED_TEST') && PHPUNIT_ISOLATED_TEST) {
|
|
|
2297 |
// Already isolated.
|
|
|
2298 |
return;
|
|
|
2299 |
}
|
|
|
2300 |
|
|
|
2301 |
throw new \coding_exception(
|
|
|
2302 |
'When including this file for a unit test, the test must be run in an isolated process. ' .
|
|
|
2303 |
'See the PHPUnit @runInSeparateProcess and @runTestsInSeparateProcesses annotations.'
|
|
|
2304 |
);
|
|
|
2305 |
}
|