| 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);
|
| 1441 |
ariadna |
37 |
/** All problems. Formerly, all problems, except the erstwhile strict PHP warnings before E_STRICT got deprecated. */
|
|
|
38 |
define('DEBUG_ALL', E_ALL);
|
|
|
39 |
/** Same as DEBUG_ALL since E_STRICT was deprecated. */
|
|
|
40 |
define('DEBUG_DEVELOPER', E_ALL);
|
| 1 |
efrain |
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 |
* Get the Whoops! handler.
|
|
|
56 |
*
|
|
|
57 |
* @return \Whoops\Run|null
|
|
|
58 |
*/
|
|
|
59 |
function get_whoops(): ?\Whoops\Run {
|
|
|
60 |
global $CFG;
|
|
|
61 |
|
|
|
62 |
if (CLI_SCRIPT || AJAX_SCRIPT) {
|
|
|
63 |
return null;
|
|
|
64 |
}
|
|
|
65 |
|
|
|
66 |
if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
|
|
|
67 |
return null;
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) {
|
|
|
71 |
return null;
|
|
|
72 |
}
|
|
|
73 |
|
| 11 |
efrain |
74 |
if (empty($CFG->debugdisplay)) {
|
| 1 |
efrain |
75 |
return null;
|
|
|
76 |
}
|
|
|
77 |
|
| 1441 |
ariadna |
78 |
if (empty($CFG->debug_developer_use_pretty_exceptions)) {
|
| 1 |
efrain |
79 |
return null;
|
|
|
80 |
}
|
|
|
81 |
|
|
|
82 |
$composerautoload = "{$CFG->dirroot}/vendor/autoload.php";
|
|
|
83 |
if (file_exists($composerautoload)) {
|
|
|
84 |
require_once($composerautoload);
|
|
|
85 |
}
|
|
|
86 |
|
|
|
87 |
if (!class_exists(\Whoops\Run::class)) {
|
|
|
88 |
return null;
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
// We have Whoops available, use it.
|
|
|
92 |
$whoops = new \Whoops\Run();
|
|
|
93 |
|
|
|
94 |
// Append a custom handler to add some more information to the frames.
|
|
|
95 |
$whoops->appendHandler(function ($exception, $inspector, $run) {
|
|
|
96 |
$collection = $inspector->getFrames();
|
|
|
97 |
|
|
|
98 |
// Detect if the Whoops handler was immediately invoked by a call to `debugging()`.
|
|
|
99 |
// If so, we remove the top frames in the collection to avoid showing the inner
|
|
|
100 |
// workings of debugging, and the point that we trigger the error that is picked up by Whoops.
|
|
|
101 |
$isdebugging = count($collection) > 2;
|
|
|
102 |
$isdebugging = $isdebugging && str_ends_with($collection[1]->getFile(), '/lib/weblib.php');
|
|
|
103 |
$isdebugging = $isdebugging && $collection[2]->getFunction() === 'debugging';
|
|
|
104 |
|
|
|
105 |
if ($isdebugging) {
|
|
|
106 |
$remove = array_slice($collection->getArray(), 0, 2);
|
|
|
107 |
$collection->filter(function ($frame) use ($remove): bool {
|
|
|
108 |
return array_search($frame, $remove) === false;
|
|
|
109 |
});
|
|
|
110 |
} else {
|
|
|
111 |
// Moodle exceptions often have a link to the Moodle docs pages for them.
|
|
|
112 |
// Add that to the first frame in the stack.
|
|
|
113 |
$info = get_exception_info($exception);
|
|
|
114 |
if ($info->moreinfourl) {
|
|
|
115 |
$collection[0]->addComment("{$info->moreinfourl}", 'More info');
|
|
|
116 |
}
|
|
|
117 |
}
|
|
|
118 |
});
|
|
|
119 |
|
|
|
120 |
// Add the Pretty page handler. It's the bee's knees.
|
|
|
121 |
$handler = new \Whoops\Handler\PrettyPageHandler();
|
|
|
122 |
if (isset($CFG->debug_developer_editor)) {
|
|
|
123 |
$handler->setEditor($CFG->debug_developer_editor ?: null);
|
|
|
124 |
}
|
|
|
125 |
$whoops->appendHandler($handler);
|
|
|
126 |
|
|
|
127 |
return $whoops;
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
/**
|
|
|
131 |
* Default exception handler.
|
|
|
132 |
*
|
| 1441 |
ariadna |
133 |
* @param Throwable $ex
|
| 1 |
efrain |
134 |
* @return void -does not return. Terminates execution!
|
|
|
135 |
*/
|
| 1441 |
ariadna |
136 |
function default_exception_handler(Throwable $ex): void {
|
| 1 |
efrain |
137 |
global $CFG, $DB, $OUTPUT, $USER, $FULLME, $SESSION, $PAGE;
|
|
|
138 |
|
|
|
139 |
// detect active db transactions, rollback and log as error
|
|
|
140 |
abort_all_db_transactions();
|
|
|
141 |
|
|
|
142 |
if (($ex instanceof required_capability_exception) && !CLI_SCRIPT && !AJAX_SCRIPT && !empty($CFG->autologinguests) && !empty($USER->autologinguest)) {
|
|
|
143 |
$SESSION->wantsurl = qualified_me();
|
|
|
144 |
redirect(get_login_url());
|
|
|
145 |
}
|
|
|
146 |
|
|
|
147 |
$info = get_exception_info($ex);
|
|
|
148 |
|
|
|
149 |
// If we already tried to send the header remove it, the content length
|
|
|
150 |
// should be either empty or the length of the error page.
|
|
|
151 |
@header_remove('Content-Length');
|
|
|
152 |
|
|
|
153 |
if ($whoops = get_whoops()) {
|
|
|
154 |
// If whoops is available we will use it. The get_whoops() function checks whether all conditions are met.
|
|
|
155 |
$whoops->handleException($ex);
|
|
|
156 |
}
|
|
|
157 |
|
|
|
158 |
if (is_early_init($info->backtrace)) {
|
|
|
159 |
echo bootstrap_renderer::early_error($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo, $info->errorcode);
|
|
|
160 |
} else {
|
|
|
161 |
if (debugging('', DEBUG_MINIMAL)) {
|
|
|
162 |
$logerrmsg = "Default exception handler: ".$info->message.' Debug: '.$info->debuginfo."\n".format_backtrace($info->backtrace, true);
|
|
|
163 |
error_log($logerrmsg);
|
|
|
164 |
}
|
|
|
165 |
|
|
|
166 |
try {
|
|
|
167 |
if ($DB) {
|
|
|
168 |
// If you enable db debugging and exception is thrown, the print footer prints a lot of rubbish
|
|
|
169 |
$DB->set_debug(0);
|
|
|
170 |
}
|
|
|
171 |
if (AJAX_SCRIPT) {
|
|
|
172 |
// If we are in an AJAX script we don't want to use PREFERRED_RENDERER_TARGET.
|
|
|
173 |
// Because we know we will want to use ajax format.
|
|
|
174 |
$renderer = new core_renderer_ajax($PAGE, 'ajax');
|
|
|
175 |
} else {
|
|
|
176 |
$renderer = $OUTPUT;
|
|
|
177 |
}
|
|
|
178 |
echo $renderer->fatal_error($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo,
|
|
|
179 |
$info->errorcode);
|
|
|
180 |
} catch (Exception $e) {
|
|
|
181 |
$out_ex = $e;
|
|
|
182 |
} catch (Throwable $e) {
|
|
|
183 |
// Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
|
|
|
184 |
$out_ex = $e;
|
|
|
185 |
}
|
|
|
186 |
|
|
|
187 |
if (isset($out_ex)) {
|
|
|
188 |
// default exception handler MUST not throw any exceptions!!
|
|
|
189 |
// 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
|
|
|
190 |
// so we just print at least something instead of "Exception thrown without a stack frame in Unknown on line 0":-(
|
|
|
191 |
if (CLI_SCRIPT or AJAX_SCRIPT) {
|
|
|
192 |
// just ignore the error and send something back using the safest method
|
|
|
193 |
echo bootstrap_renderer::early_error($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo, $info->errorcode);
|
|
|
194 |
} else {
|
|
|
195 |
echo bootstrap_renderer::early_error_content($info->message, $info->moreinfourl, $info->link, $info->backtrace, $info->debuginfo);
|
|
|
196 |
$outinfo = get_exception_info($out_ex);
|
|
|
197 |
echo bootstrap_renderer::early_error_content($outinfo->message, $outinfo->moreinfourl, $outinfo->link, $outinfo->backtrace, $outinfo->debuginfo);
|
|
|
198 |
}
|
|
|
199 |
}
|
|
|
200 |
}
|
|
|
201 |
|
|
|
202 |
exit(1); // General error code
|
|
|
203 |
}
|
|
|
204 |
|
|
|
205 |
/**
|
|
|
206 |
* Default error handler, prevents some white screens.
|
|
|
207 |
* @param int $errno
|
|
|
208 |
* @param string $errstr
|
|
|
209 |
* @param string $errfile
|
|
|
210 |
* @param int $errline
|
|
|
211 |
* @return bool false means use default error handler
|
|
|
212 |
*/
|
|
|
213 |
function default_error_handler($errno, $errstr, $errfile, $errline) {
|
|
|
214 |
if ($whoops = get_whoops()) {
|
|
|
215 |
// If whoops is available we will use it. The get_whoops() function checks whether all conditions are met.
|
|
|
216 |
$whoops->handleError($errno, $errstr, $errfile, $errline);
|
|
|
217 |
}
|
|
|
218 |
if ($errno == 4096) {
|
|
|
219 |
//fatal catchable error
|
|
|
220 |
throw new coding_exception('PHP catchable fatal error', $errstr);
|
|
|
221 |
}
|
|
|
222 |
return false;
|
|
|
223 |
}
|
|
|
224 |
|
|
|
225 |
/**
|
|
|
226 |
* Unconditionally abort all database transactions, this function
|
|
|
227 |
* should be called from exception handlers only.
|
|
|
228 |
* @return void
|
|
|
229 |
*/
|
|
|
230 |
function abort_all_db_transactions() {
|
|
|
231 |
global $CFG, $DB, $SCRIPT;
|
|
|
232 |
|
|
|
233 |
// default exception handler MUST not throw any exceptions!!
|
|
|
234 |
|
|
|
235 |
if ($DB && $DB->is_transaction_started()) {
|
|
|
236 |
error_log('Database transaction aborted automatically in ' . $CFG->dirroot . $SCRIPT);
|
|
|
237 |
// note: transaction blocks should never change current $_SESSION
|
|
|
238 |
$DB->force_transaction_rollback();
|
|
|
239 |
}
|
|
|
240 |
}
|
|
|
241 |
|
|
|
242 |
/**
|
|
|
243 |
* This function encapsulates the tests for whether an exception was thrown in
|
|
|
244 |
* early init -- either during setup.php or during init of $OUTPUT.
|
|
|
245 |
*
|
|
|
246 |
* If another exception is thrown then, and if we do not take special measures,
|
|
|
247 |
* we would just get a very cryptic message "Exception thrown without a stack
|
|
|
248 |
* frame in Unknown on line 0". That makes debugging very hard, so we do take
|
|
|
249 |
* special measures in default_exception_handler, with the help of this function.
|
|
|
250 |
*
|
|
|
251 |
* @param array $backtrace the stack trace to analyse.
|
|
|
252 |
* @return boolean whether the stack trace is somewhere in output initialisation.
|
|
|
253 |
*/
|
|
|
254 |
function is_early_init($backtrace) {
|
|
|
255 |
$dangerouscode = array(
|
|
|
256 |
array('function' => 'header', 'type' => '->'),
|
|
|
257 |
array('class' => 'bootstrap_renderer'),
|
|
|
258 |
array('file' => __DIR__.'/setup.php'),
|
|
|
259 |
);
|
|
|
260 |
foreach ($backtrace as $stackframe) {
|
|
|
261 |
foreach ($dangerouscode as $pattern) {
|
|
|
262 |
$matches = true;
|
|
|
263 |
foreach ($pattern as $property => $value) {
|
|
|
264 |
if (!isset($stackframe[$property]) || $stackframe[$property] != $value) {
|
|
|
265 |
$matches = false;
|
|
|
266 |
}
|
|
|
267 |
}
|
|
|
268 |
if ($matches) {
|
|
|
269 |
return true;
|
|
|
270 |
}
|
|
|
271 |
}
|
|
|
272 |
}
|
|
|
273 |
return false;
|
|
|
274 |
}
|
|
|
275 |
|
|
|
276 |
/**
|
|
|
277 |
* Returns detailed information about specified exception.
|
|
|
278 |
*
|
|
|
279 |
* @param Throwable $ex any sort of exception or throwable.
|
|
|
280 |
* @return stdClass standardised info to display. Fields are clear if you look at the end of this function.
|
|
|
281 |
*/
|
|
|
282 |
function get_exception_info($ex): stdClass {
|
|
|
283 |
global $CFG;
|
|
|
284 |
|
|
|
285 |
if ($ex instanceof moodle_exception) {
|
|
|
286 |
$errorcode = $ex->errorcode;
|
|
|
287 |
$module = $ex->module;
|
|
|
288 |
$a = $ex->a;
|
|
|
289 |
$link = $ex->link;
|
|
|
290 |
$debuginfo = $ex->debuginfo;
|
|
|
291 |
} else {
|
|
|
292 |
$errorcode = 'generalexceptionmessage';
|
|
|
293 |
$module = 'error';
|
|
|
294 |
$a = $ex->getMessage();
|
|
|
295 |
$link = '';
|
|
|
296 |
$debuginfo = '';
|
|
|
297 |
}
|
|
|
298 |
|
|
|
299 |
// Append the error code to the debug info to make grepping and googling easier
|
|
|
300 |
$debuginfo .= PHP_EOL."Error code: $errorcode";
|
|
|
301 |
|
|
|
302 |
$backtrace = $ex->getTrace();
|
|
|
303 |
$place = array('file'=>$ex->getFile(), 'line'=>$ex->getLine(), 'exception'=>get_class($ex));
|
|
|
304 |
array_unshift($backtrace, $place);
|
|
|
305 |
|
|
|
306 |
// Be careful, no guarantee moodlelib.php is loaded.
|
|
|
307 |
if (empty($module) || $module == 'moodle' || $module == 'core') {
|
|
|
308 |
$module = 'error';
|
|
|
309 |
}
|
|
|
310 |
// Search for the $errorcode's associated string
|
|
|
311 |
// If not found, append the contents of $a to $debuginfo so helpful information isn't lost
|
|
|
312 |
if (function_exists('get_string_manager')) {
|
|
|
313 |
if (get_string_manager()->string_exists($errorcode, $module)) {
|
|
|
314 |
$message = get_string($errorcode, $module, $a);
|
|
|
315 |
} elseif ($module == 'error' && get_string_manager()->string_exists($errorcode, 'moodle')) {
|
|
|
316 |
// Search in moodle file if error specified - needed for backwards compatibility
|
|
|
317 |
$message = get_string($errorcode, 'moodle', $a);
|
|
|
318 |
} else {
|
|
|
319 |
$message = $module . '/' . $errorcode;
|
|
|
320 |
$debuginfo .= PHP_EOL.'$a contents: '.print_r($a, true);
|
|
|
321 |
}
|
|
|
322 |
} else {
|
|
|
323 |
$message = $module . '/' . $errorcode;
|
|
|
324 |
$debuginfo .= PHP_EOL.'$a contents: '.print_r($a, true);
|
|
|
325 |
}
|
|
|
326 |
|
|
|
327 |
// Remove some absolute paths from message and debugging info.
|
|
|
328 |
$searches = array();
|
|
|
329 |
$replaces = array();
|
|
|
330 |
$cfgnames = array('backuptempdir', 'tempdir', 'cachedir', 'localcachedir', 'themedir', 'dataroot', 'dirroot');
|
|
|
331 |
foreach ($cfgnames as $cfgname) {
|
|
|
332 |
if (property_exists($CFG, $cfgname)) {
|
|
|
333 |
$searches[] = $CFG->$cfgname;
|
|
|
334 |
$replaces[] = "[$cfgname]";
|
|
|
335 |
}
|
|
|
336 |
}
|
|
|
337 |
if (!empty($searches)) {
|
|
|
338 |
$message = str_replace($searches, $replaces, $message);
|
|
|
339 |
$debuginfo = str_replace($searches, $replaces, $debuginfo);
|
|
|
340 |
}
|
|
|
341 |
|
|
|
342 |
// Be careful, no guarantee weblib.php is loaded.
|
|
|
343 |
if (function_exists('clean_text')) {
|
|
|
344 |
$message = clean_text($message);
|
|
|
345 |
} else {
|
|
|
346 |
$message = htmlspecialchars($message, ENT_COMPAT);
|
|
|
347 |
}
|
|
|
348 |
|
|
|
349 |
if (!empty($CFG->errordocroot)) {
|
|
|
350 |
$errordoclink = $CFG->errordocroot . '/en/';
|
|
|
351 |
} else {
|
|
|
352 |
// Only if the function is available. May be not for early errors.
|
|
|
353 |
if (function_exists('current_language')) {
|
|
|
354 |
$errordoclink = get_docs_url();
|
|
|
355 |
} else {
|
|
|
356 |
$errordoclink = 'https://docs.moodle.org/en/';
|
|
|
357 |
}
|
|
|
358 |
}
|
|
|
359 |
|
|
|
360 |
if ($module === 'error') {
|
|
|
361 |
$modulelink = 'moodle';
|
|
|
362 |
} else {
|
|
|
363 |
$modulelink = $module;
|
|
|
364 |
}
|
|
|
365 |
$moreinfourl = $errordoclink . 'error/' . $modulelink . '/' . $errorcode;
|
|
|
366 |
|
|
|
367 |
if (empty($link)) {
|
|
|
368 |
$link = get_local_referer(false) ?: ($CFG->wwwroot . '/');
|
|
|
369 |
}
|
|
|
370 |
|
|
|
371 |
// When printing an error the continue button should never link offsite.
|
|
|
372 |
// We cannot use clean_param() here as it is not guaranteed that it has been loaded yet.
|
|
|
373 |
if (stripos($link, $CFG->wwwroot) === 0) {
|
|
|
374 |
// Internal HTTP, all good.
|
|
|
375 |
} else {
|
|
|
376 |
// External link spotted!
|
|
|
377 |
$link = $CFG->wwwroot . '/';
|
|
|
378 |
}
|
|
|
379 |
|
|
|
380 |
$info = new stdClass();
|
|
|
381 |
$info->message = $message;
|
|
|
382 |
$info->errorcode = $errorcode;
|
|
|
383 |
$info->backtrace = $backtrace;
|
|
|
384 |
$info->link = $link;
|
|
|
385 |
$info->moreinfourl = $moreinfourl;
|
|
|
386 |
$info->a = $a;
|
|
|
387 |
$info->debuginfo = $debuginfo;
|
|
|
388 |
|
|
|
389 |
return $info;
|
|
|
390 |
}
|
|
|
391 |
|
|
|
392 |
/**
|
|
|
393 |
* Returns the Moodle Docs URL in the users language for a given 'More help' link.
|
|
|
394 |
*
|
|
|
395 |
* There are three cases:
|
|
|
396 |
*
|
|
|
397 |
* 1. In the normal case, $path will be a short relative path 'component/thing',
|
|
|
398 |
* like 'mod/folder/view' 'group/import'. This gets turned into an link to
|
|
|
399 |
* MoodleDocs in the user's language, and for the appropriate Moodle version.
|
|
|
400 |
* E.g. 'group/import' may become 'http://docs.moodle.org/2x/en/group/import'.
|
|
|
401 |
* The 'http://docs.moodle.org' bit comes from $CFG->docroot.
|
|
|
402 |
*
|
|
|
403 |
* This is the only option that should be used in standard Moodle code. The other
|
|
|
404 |
* two options have been implemented because they are useful for third-party plugins.
|
|
|
405 |
*
|
|
|
406 |
* 2. $path may be an absolute URL, starting http:// or https://. In this case,
|
|
|
407 |
* the link is used as is.
|
|
|
408 |
*
|
|
|
409 |
* 3. $path may start %%WWWROOT%%, in which case that is replaced by
|
|
|
410 |
* $CFG->wwwroot to make the link.
|
|
|
411 |
*
|
|
|
412 |
* @param string $path the place to link to. See above for details.
|
|
|
413 |
* @return string The MoodleDocs URL in the user's language. for example @link http://docs.moodle.org/2x/en/$path}
|
|
|
414 |
*/
|
|
|
415 |
function get_docs_url($path = null) {
|
|
|
416 |
global $CFG;
|
|
|
417 |
if ($path === null) {
|
|
|
418 |
$path = '';
|
|
|
419 |
}
|
|
|
420 |
|
|
|
421 |
$path = $path ?? '';
|
|
|
422 |
// Absolute URLs are used unmodified.
|
|
|
423 |
if (substr($path, 0, 7) === 'http://' || substr($path, 0, 8) === 'https://') {
|
|
|
424 |
return $path;
|
|
|
425 |
}
|
|
|
426 |
|
|
|
427 |
// Paths starting %%WWWROOT%% have that replaced by $CFG->wwwroot.
|
|
|
428 |
if (substr($path, 0, 11) === '%%WWWROOT%%') {
|
|
|
429 |
return $CFG->wwwroot . substr($path, 11);
|
|
|
430 |
}
|
|
|
431 |
|
|
|
432 |
// Otherwise we do the normal case, and construct a MoodleDocs URL relative to $CFG->docroot.
|
|
|
433 |
|
|
|
434 |
// Check that $CFG->branch has been set up, during installation it won't be.
|
|
|
435 |
if (empty($CFG->branch)) {
|
|
|
436 |
// It's not there yet so look at version.php.
|
|
|
437 |
include($CFG->dirroot.'/version.php');
|
|
|
438 |
} else {
|
|
|
439 |
// We can use $CFG->branch and avoid having to include version.php.
|
|
|
440 |
$branch = $CFG->branch;
|
|
|
441 |
}
|
|
|
442 |
// ensure branch is valid.
|
|
|
443 |
if (!$branch) {
|
|
|
444 |
// We should never get here but in case we do lets set $branch to .
|
|
|
445 |
// the smart one's will know that this is the current directory
|
|
|
446 |
// and the smarter ones will know that there is some smart matching
|
|
|
447 |
// that will ensure people end up at the latest version of the docs.
|
|
|
448 |
$branch = '.';
|
|
|
449 |
}
|
|
|
450 |
if (empty($CFG->doclang)) {
|
|
|
451 |
$lang = current_language();
|
|
|
452 |
} else {
|
|
|
453 |
$lang = $CFG->doclang;
|
|
|
454 |
}
|
|
|
455 |
$end = '/' . $branch . '/' . $lang . '/' . $path;
|
|
|
456 |
if (empty($CFG->docroot)) {
|
|
|
457 |
return 'http://docs.moodle.org'. $end;
|
|
|
458 |
} else {
|
|
|
459 |
return $CFG->docroot . $end ;
|
|
|
460 |
}
|
|
|
461 |
}
|
|
|
462 |
|
|
|
463 |
/**
|
|
|
464 |
* Formats a backtrace ready for output.
|
|
|
465 |
*
|
|
|
466 |
* This function does not include function arguments because they could contain sensitive information
|
|
|
467 |
* not suitable to be exposed in a response.
|
|
|
468 |
*
|
|
|
469 |
* @param array $callers backtrace array, as returned by debug_backtrace().
|
|
|
470 |
* @param boolean $plaintext if false, generates HTML, if true generates plain text.
|
|
|
471 |
* @return string formatted backtrace, ready for output.
|
|
|
472 |
*/
|
|
|
473 |
function format_backtrace($callers, $plaintext = false) {
|
|
|
474 |
// do not use $CFG->dirroot because it might not be available in destructors
|
|
|
475 |
$dirroot = dirname(__DIR__);
|
|
|
476 |
|
|
|
477 |
if (empty($callers)) {
|
|
|
478 |
return '';
|
|
|
479 |
}
|
|
|
480 |
|
|
|
481 |
$from = $plaintext ? '' : '<ul style="text-align: left" data-rel="backtrace">';
|
|
|
482 |
foreach ($callers as $caller) {
|
|
|
483 |
if (!isset($caller['line'])) {
|
|
|
484 |
$caller['line'] = '?'; // probably call_user_func()
|
|
|
485 |
}
|
|
|
486 |
if (!isset($caller['file'])) {
|
|
|
487 |
$caller['file'] = 'unknownfile'; // probably call_user_func()
|
|
|
488 |
}
|
|
|
489 |
$line = $plaintext ? '* ' : '<li>';
|
|
|
490 |
$line .= 'line ' . $caller['line'] . ' of ' . str_replace($dirroot, '', $caller['file']);
|
|
|
491 |
if (isset($caller['function'])) {
|
|
|
492 |
$line .= ': call to ';
|
|
|
493 |
if (isset($caller['class'])) {
|
|
|
494 |
$line .= $caller['class'] . $caller['type'];
|
|
|
495 |
}
|
|
|
496 |
$line .= $caller['function'] . '()';
|
|
|
497 |
} else if (isset($caller['exception'])) {
|
|
|
498 |
$line .= ': '.$caller['exception'].' thrown';
|
|
|
499 |
}
|
|
|
500 |
|
|
|
501 |
// Remove any non printable chars.
|
|
|
502 |
$line = preg_replace('/[[:^print:]]/', '', $line);
|
|
|
503 |
|
|
|
504 |
$line .= $plaintext ? "\n" : '</li>';
|
|
|
505 |
$from .= $line;
|
|
|
506 |
}
|
|
|
507 |
$from .= $plaintext ? '' : '</ul>';
|
|
|
508 |
|
|
|
509 |
return $from;
|
|
|
510 |
}
|
|
|
511 |
|
|
|
512 |
/**
|
|
|
513 |
* This function makes the return value of ini_get consistent if you are
|
|
|
514 |
* setting server directives through the .htaccess file in apache.
|
|
|
515 |
*
|
|
|
516 |
* Current behavior for value set from php.ini On = 1, Off = [blank]
|
|
|
517 |
* Current behavior for value set from .htaccess On = On, Off = Off
|
|
|
518 |
* Contributed by jdell @ unr.edu
|
|
|
519 |
*
|
|
|
520 |
* @param string $ini_get_arg The argument to get
|
|
|
521 |
* @return bool True for on false for not
|
|
|
522 |
*/
|
|
|
523 |
function ini_get_bool($ini_get_arg) {
|
|
|
524 |
$temp = ini_get($ini_get_arg);
|
|
|
525 |
|
|
|
526 |
if ($temp == '1' or strtolower($temp) == 'on') {
|
|
|
527 |
return true;
|
|
|
528 |
}
|
|
|
529 |
return false;
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
/**
|
|
|
533 |
* This function verifies the sanity of PHP configuration
|
|
|
534 |
* and stops execution if anything critical found.
|
|
|
535 |
*/
|
|
|
536 |
function setup_validate_php_configuration() {
|
|
|
537 |
// this must be very fast - no slow checks here!!!
|
|
|
538 |
|
|
|
539 |
if (ini_get_bool('session.auto_start')) {
|
|
|
540 |
throw new \moodle_exception('sessionautostartwarning', 'admin');
|
|
|
541 |
}
|
|
|
542 |
}
|
|
|
543 |
|
|
|
544 |
/**
|
|
|
545 |
* Initialise global $CFG variable.
|
|
|
546 |
* @private to be used only from lib/setup.php
|
|
|
547 |
*/
|
|
|
548 |
function initialise_cfg() {
|
|
|
549 |
global $CFG, $DB;
|
|
|
550 |
|
|
|
551 |
if (!$DB) {
|
|
|
552 |
// This should not happen.
|
|
|
553 |
return;
|
|
|
554 |
}
|
|
|
555 |
|
|
|
556 |
try {
|
|
|
557 |
$localcfg = get_config('core');
|
|
|
558 |
} catch (dml_exception $e) {
|
|
|
559 |
// Most probably empty db, going to install soon.
|
|
|
560 |
return;
|
|
|
561 |
}
|
|
|
562 |
|
|
|
563 |
foreach ($localcfg as $name => $value) {
|
|
|
564 |
// Note that get_config() keeps forced settings
|
|
|
565 |
// and normalises values to string if possible.
|
|
|
566 |
$CFG->{$name} = $value;
|
|
|
567 |
}
|
|
|
568 |
}
|
|
|
569 |
|
|
|
570 |
/**
|
|
|
571 |
* Cache any immutable config locally to avoid constant DB lookups.
|
|
|
572 |
*
|
|
|
573 |
* Only to be used only from lib/setup.php
|
|
|
574 |
*/
|
|
|
575 |
function initialise_local_config_cache() {
|
|
|
576 |
global $CFG;
|
|
|
577 |
|
| 11 |
efrain |
578 |
$bootstraplocalfile = $CFG->localcachedir . '/bootstrap.php';
|
|
|
579 |
$bootstrapsharedfile = $CFG->cachedir . '/bootstrap.php';
|
| 1 |
efrain |
580 |
|
| 11 |
efrain |
581 |
if (!is_readable($bootstraplocalfile) && is_readable($bootstrapsharedfile)) {
|
|
|
582 |
// If we don't have a local cache but do have a shared cache then clone it,
|
|
|
583 |
// for example when scaling up new front ends.
|
|
|
584 |
make_localcache_directory('', true);
|
|
|
585 |
copy($bootstrapsharedfile, $bootstraplocalfile);
|
|
|
586 |
}
|
|
|
587 |
|
|
|
588 |
if (!empty($CFG->siteidentifier) && !file_exists($bootstrapsharedfile) && defined('SYSCONTEXTID')) {
|
| 1 |
efrain |
589 |
$contents = "<?php
|
|
|
590 |
// ********** This file is generated DO NOT EDIT **********
|
|
|
591 |
\$CFG->siteidentifier = " . var_export($CFG->siteidentifier, true) . ";
|
|
|
592 |
\$CFG->bootstraphash = " . var_export(hash_local_config_cache(), true) . ";
|
|
|
593 |
// Only if the file is not stale and has not been defined.
|
|
|
594 |
if (\$CFG->bootstraphash === hash_local_config_cache() && !defined('SYSCONTEXTID')) {
|
|
|
595 |
define('SYSCONTEXTID', ".SYSCONTEXTID.");
|
|
|
596 |
}
|
|
|
597 |
";
|
|
|
598 |
|
| 11 |
efrain |
599 |
// Create the central bootstrap first.
|
|
|
600 |
$temp = $bootstrapsharedfile . '.tmp' . uniqid();
|
| 1 |
efrain |
601 |
file_put_contents($temp, $contents);
|
|
|
602 |
@chmod($temp, $CFG->filepermissions);
|
| 11 |
efrain |
603 |
rename($temp, $bootstrapsharedfile);
|
|
|
604 |
|
|
|
605 |
// Then prewarm the local cache as well.
|
|
|
606 |
make_localcache_directory('', true);
|
|
|
607 |
copy($bootstrapsharedfile, $bootstraplocalfile);
|
| 1 |
efrain |
608 |
}
|
|
|
609 |
}
|
|
|
610 |
|
|
|
611 |
/**
|
|
|
612 |
* Calculate a proper hash to be able to invalidate stale cached configs.
|
|
|
613 |
*
|
|
|
614 |
* Only to be used to verify bootstrap.php status.
|
|
|
615 |
*
|
|
|
616 |
* @return string md5 hash of all the sensible bits deciding if cached config is stale or no.
|
|
|
617 |
*/
|
|
|
618 |
function hash_local_config_cache() {
|
|
|
619 |
global $CFG;
|
|
|
620 |
|
|
|
621 |
// This is pretty much {@see moodle_database::get_settings_hash()} that is used
|
|
|
622 |
// as identifier for the database meta information MUC cache. Should be enough to
|
|
|
623 |
// react against any of the normal changes (new prefix, change of DB type) while
|
|
|
624 |
// *incorrectly* keeping the old dataroot directory unmodified with stale data.
|
|
|
625 |
// This may need more stuff to be considered if it's discovered that there are
|
|
|
626 |
// more variables making the file stale.
|
|
|
627 |
return md5($CFG->dbtype . $CFG->dbhost . $CFG->dbuser . $CFG->dbname . $CFG->prefix);
|
|
|
628 |
}
|
|
|
629 |
|
|
|
630 |
/**
|
|
|
631 |
* Initialises $FULLME and friends. Private function. Should only be called from
|
|
|
632 |
* setup.php.
|
|
|
633 |
*/
|
|
|
634 |
function initialise_fullme() {
|
|
|
635 |
global $CFG, $FULLME, $ME, $SCRIPT, $FULLSCRIPT;
|
|
|
636 |
|
|
|
637 |
// Detect common config error.
|
|
|
638 |
if (substr($CFG->wwwroot, -1) == '/') {
|
|
|
639 |
throw new \moodle_exception('wwwrootslash', 'error');
|
|
|
640 |
}
|
|
|
641 |
|
|
|
642 |
if (CLI_SCRIPT) {
|
|
|
643 |
initialise_fullme_cli();
|
|
|
644 |
return;
|
|
|
645 |
}
|
|
|
646 |
if (!empty($CFG->overridetossl)) {
|
|
|
647 |
if (strpos($CFG->wwwroot, 'http://') === 0) {
|
|
|
648 |
$CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
|
|
|
649 |
} else {
|
|
|
650 |
unset_config('overridetossl');
|
|
|
651 |
}
|
|
|
652 |
}
|
|
|
653 |
|
|
|
654 |
$rurl = setup_get_remote_url();
|
|
|
655 |
$wwwroot = parse_url($CFG->wwwroot.'/');
|
|
|
656 |
|
|
|
657 |
if (empty($rurl['host'])) {
|
|
|
658 |
// missing host in request header, probably not a real browser, let's ignore them
|
|
|
659 |
|
|
|
660 |
} else if (!empty($CFG->reverseproxy)) {
|
|
|
661 |
// $CFG->reverseproxy specifies if reverse proxy server used
|
|
|
662 |
// Used in load balancing scenarios.
|
|
|
663 |
// Do not abuse this to try to solve lan/wan access problems!!!!!
|
|
|
664 |
|
|
|
665 |
} else {
|
|
|
666 |
if (($rurl['host'] !== $wwwroot['host']) or
|
|
|
667 |
(!empty($wwwroot['port']) and $rurl['port'] != $wwwroot['port']) or
|
|
|
668 |
(strpos($rurl['path'], $wwwroot['path']) !== 0)) {
|
|
|
669 |
|
|
|
670 |
// Explain the problem and redirect them to the right URL
|
|
|
671 |
if (!defined('NO_MOODLE_COOKIES')) {
|
|
|
672 |
define('NO_MOODLE_COOKIES', true);
|
|
|
673 |
}
|
|
|
674 |
// The login/token.php script should call the correct url/port.
|
|
|
675 |
if (defined('REQUIRE_CORRECT_ACCESS') && REQUIRE_CORRECT_ACCESS) {
|
|
|
676 |
$wwwrootport = empty($wwwroot['port'])?'':$wwwroot['port'];
|
|
|
677 |
$calledurl = $rurl['host'];
|
|
|
678 |
if (!empty($rurl['port'])) {
|
|
|
679 |
$calledurl .= ':'. $rurl['port'];
|
|
|
680 |
}
|
|
|
681 |
$correcturl = $wwwroot['host'];
|
|
|
682 |
if (!empty($wwwrootport)) {
|
|
|
683 |
$correcturl .= ':'. $wwwrootport;
|
|
|
684 |
}
|
|
|
685 |
throw new moodle_exception('requirecorrectaccess', 'error', '', null,
|
|
|
686 |
'You called ' . $calledurl .', you should have called ' . $correcturl);
|
|
|
687 |
}
|
|
|
688 |
$rfullpath = $rurl['fullpath'];
|
|
|
689 |
// Check that URL is under $CFG->wwwroot.
|
|
|
690 |
if (strpos($rfullpath, $wwwroot['path']) === 0) {
|
|
|
691 |
$rfullpath = substr($rurl['fullpath'], strlen($wwwroot['path']) - 1);
|
|
|
692 |
$rfullpath = (new moodle_url($rfullpath))->out(false);
|
|
|
693 |
}
|
|
|
694 |
redirect($rfullpath, get_string('wwwrootmismatch', 'error', $CFG->wwwroot), 3);
|
|
|
695 |
}
|
|
|
696 |
}
|
|
|
697 |
|
|
|
698 |
// Check that URL is under $CFG->wwwroot.
|
|
|
699 |
if (strpos($rurl['path'], $wwwroot['path']) === 0) {
|
|
|
700 |
$SCRIPT = substr($rurl['path'], strlen($wwwroot['path'])-1);
|
|
|
701 |
} else {
|
|
|
702 |
// Probably some weird external script
|
|
|
703 |
$SCRIPT = $FULLSCRIPT = $FULLME = $ME = null;
|
|
|
704 |
return;
|
|
|
705 |
}
|
|
|
706 |
|
|
|
707 |
// $CFG->sslproxy specifies if external SSL appliance is used
|
|
|
708 |
// (That is, the Moodle server uses http, with an external box translating everything to https).
|
|
|
709 |
if (empty($CFG->sslproxy)) {
|
|
|
710 |
if ($rurl['scheme'] === 'http' and $wwwroot['scheme'] === 'https') {
|
|
|
711 |
if (defined('REQUIRE_CORRECT_ACCESS') && REQUIRE_CORRECT_ACCESS) {
|
|
|
712 |
throw new \moodle_exception('sslonlyaccess', 'error');
|
|
|
713 |
} else {
|
|
|
714 |
redirect($CFG->wwwroot, get_string('wwwrootmismatch', 'error', $CFG->wwwroot), 3);
|
|
|
715 |
}
|
|
|
716 |
}
|
|
|
717 |
} else {
|
|
|
718 |
if ($wwwroot['scheme'] !== 'https') {
|
|
|
719 |
throw new coding_exception('Must use https address in wwwroot when ssl proxy enabled!');
|
|
|
720 |
}
|
|
|
721 |
$rurl['scheme'] = 'https'; // make moodle believe it runs on https, squid or something else it doing it
|
|
|
722 |
$_SERVER['HTTPS'] = 'on'; // Override $_SERVER to help external libraries with their HTTPS detection.
|
|
|
723 |
$_SERVER['SERVER_PORT'] = 443; // Assume default ssl port for the proxy.
|
|
|
724 |
}
|
|
|
725 |
|
|
|
726 |
// Using Moodle in "reverse proxy" mode, it's expected that the HTTP Host Moodle receives is different
|
|
|
727 |
// from the wwwroot configured host. Those URLs being identical could be the consequence of various
|
|
|
728 |
// issues, including:
|
|
|
729 |
// - Intentionally trying to set up moodle with 2 distinct addresses for intranet and Internet: this
|
|
|
730 |
// configuration is unsupported and will lead to bigger problems down the road (the proper solution
|
|
|
731 |
// for this is adjusting the network routes, and avoid relying on the application for network concerns).
|
|
|
732 |
// - Misconfiguration of the reverse proxy that would be forwarding the Host header: while it is
|
|
|
733 |
// standard in many cases that the reverse proxy would do that, in our case, the reverse proxy
|
|
|
734 |
// must leave the Host header pointing to the internal name of the server.
|
|
|
735 |
// Port forwarding is allowed, though.
|
|
|
736 |
if (!empty($CFG->reverseproxy) && $rurl['host'] === $wwwroot['host'] && (empty($wwwroot['port']) || $rurl['port'] === $wwwroot['port'])) {
|
|
|
737 |
throw new \moodle_exception('reverseproxyabused', 'error');
|
|
|
738 |
}
|
|
|
739 |
|
|
|
740 |
$hostandport = $rurl['scheme'] . '://' . $wwwroot['host'];
|
|
|
741 |
if (!empty($wwwroot['port'])) {
|
|
|
742 |
$hostandport .= ':'.$wwwroot['port'];
|
|
|
743 |
}
|
|
|
744 |
|
|
|
745 |
$FULLSCRIPT = $hostandport . $rurl['path'];
|
|
|
746 |
$FULLME = $hostandport . $rurl['fullpath'];
|
|
|
747 |
$ME = $rurl['fullpath'];
|
|
|
748 |
}
|
|
|
749 |
|
|
|
750 |
/**
|
|
|
751 |
* Initialises $FULLME and friends for command line scripts.
|
|
|
752 |
* This is a private method for use by initialise_fullme.
|
|
|
753 |
*/
|
|
|
754 |
function initialise_fullme_cli() {
|
|
|
755 |
global $CFG, $FULLME, $ME, $SCRIPT, $FULLSCRIPT;
|
|
|
756 |
|
|
|
757 |
// Urls do not make much sense in CLI scripts
|
|
|
758 |
$backtrace = debug_backtrace();
|
|
|
759 |
$topfile = array_pop($backtrace);
|
|
|
760 |
$topfile = realpath($topfile['file']);
|
|
|
761 |
$dirroot = realpath($CFG->dirroot);
|
|
|
762 |
|
|
|
763 |
if (strpos($topfile, $dirroot) !== 0) {
|
|
|
764 |
// Probably some weird external script
|
|
|
765 |
$SCRIPT = $FULLSCRIPT = $FULLME = $ME = null;
|
|
|
766 |
} else {
|
|
|
767 |
$relativefile = substr($topfile, strlen($dirroot));
|
|
|
768 |
$relativefile = str_replace('\\', '/', $relativefile); // Win fix
|
|
|
769 |
$SCRIPT = $FULLSCRIPT = $relativefile;
|
|
|
770 |
$FULLME = $ME = null;
|
|
|
771 |
}
|
|
|
772 |
}
|
|
|
773 |
|
|
|
774 |
/**
|
|
|
775 |
* Get the URL that PHP/the web server thinks it is serving. Private function
|
|
|
776 |
* used by initialise_fullme. In your code, use $PAGE->url, $SCRIPT, etc.
|
|
|
777 |
* @return array in the same format that parse_url returns, with the addition of
|
|
|
778 |
* a 'fullpath' element, which includes any slasharguments path.
|
|
|
779 |
*/
|
|
|
780 |
function setup_get_remote_url() {
|
|
|
781 |
$rurl = array();
|
|
|
782 |
if (isset($_SERVER['HTTP_HOST'])) {
|
|
|
783 |
list($rurl['host']) = explode(':', $_SERVER['HTTP_HOST']);
|
|
|
784 |
} else {
|
|
|
785 |
$rurl['host'] = null;
|
|
|
786 |
}
|
|
|
787 |
$rurl['port'] = (int)$_SERVER['SERVER_PORT'];
|
|
|
788 |
$rurl['path'] = $_SERVER['SCRIPT_NAME']; // Script path without slash arguments
|
|
|
789 |
$rurl['scheme'] = (empty($_SERVER['HTTPS']) or $_SERVER['HTTPS'] === 'off' or $_SERVER['HTTPS'] === 'Off' or $_SERVER['HTTPS'] === 'OFF') ? 'http' : 'https';
|
|
|
790 |
|
|
|
791 |
if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== false) {
|
|
|
792 |
//Apache server
|
|
|
793 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI'];
|
|
|
794 |
|
|
|
795 |
// Fixing a known issue with:
|
|
|
796 |
// - Apache versions lesser than 2.4.11
|
|
|
797 |
// - PHP deployed in Apache as PHP-FPM via mod_proxy_fcgi
|
|
|
798 |
// - PHP versions lesser than 5.6.3 and 5.5.18.
|
|
|
799 |
if (isset($_SERVER['PATH_INFO']) && (php_sapi_name() === 'fpm-fcgi') && isset($_SERVER['SCRIPT_NAME'])) {
|
|
|
800 |
$pathinfodec = rawurldecode($_SERVER['PATH_INFO']);
|
|
|
801 |
$lenneedle = strlen($pathinfodec);
|
|
|
802 |
// Checks whether SCRIPT_NAME ends with PATH_INFO, URL-decoded.
|
|
|
803 |
if (substr($_SERVER['SCRIPT_NAME'], -$lenneedle) === $pathinfodec) {
|
|
|
804 |
// This is the "Apache 2.4.10- running PHP-FPM via mod_proxy_fcgi" fingerprint,
|
|
|
805 |
// 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)
|
|
|
806 |
// => SCRIPT_NAME contains 'slash arguments' data too, which is wrongly exposed via PATH_INFO as URL-encoded.
|
|
|
807 |
// Fix both $_SERVER['PATH_INFO'] and $_SERVER['SCRIPT_NAME'].
|
|
|
808 |
$lenhaystack = strlen($_SERVER['SCRIPT_NAME']);
|
|
|
809 |
$pos = $lenhaystack - $lenneedle;
|
|
|
810 |
// Here $pos is greater than 0 but let's double check it.
|
|
|
811 |
if ($pos > 0) {
|
|
|
812 |
$_SERVER['PATH_INFO'] = $pathinfodec;
|
|
|
813 |
$_SERVER['SCRIPT_NAME'] = substr($_SERVER['SCRIPT_NAME'], 0, $pos);
|
|
|
814 |
}
|
|
|
815 |
}
|
|
|
816 |
}
|
|
|
817 |
|
|
|
818 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'iis') !== false) {
|
|
|
819 |
//IIS - needs a lot of tweaking to make it work
|
|
|
820 |
$rurl['fullpath'] = $_SERVER['SCRIPT_NAME'];
|
|
|
821 |
|
|
|
822 |
// NOTE: we should ignore PATH_INFO because it is incorrectly encoded using 8bit filesystem legacy encoding in IIS.
|
|
|
823 |
// Since 2.0, we rely on IIS rewrite extensions like Helicon ISAPI_rewrite
|
|
|
824 |
// example rule: RewriteRule ^([^\?]+?\.php)(\/.+)$ $1\?file=$2 [QSA]
|
|
|
825 |
// OR
|
|
|
826 |
// we rely on a proper IIS 6.0+ configuration: the 'FastCGIUtf8ServerVariables' registry key.
|
|
|
827 |
if (isset($_SERVER['PATH_INFO']) and $_SERVER['PATH_INFO'] !== '') {
|
|
|
828 |
// Check that PATH_INFO works == must not contain the script name.
|
|
|
829 |
if (strpos($_SERVER['PATH_INFO'], $_SERVER['SCRIPT_NAME']) === false) {
|
|
|
830 |
$rurl['fullpath'] .= clean_param(urldecode($_SERVER['PATH_INFO']), PARAM_PATH);
|
|
|
831 |
}
|
|
|
832 |
}
|
|
|
833 |
|
|
|
834 |
if (isset($_SERVER['QUERY_STRING']) and $_SERVER['QUERY_STRING'] !== '') {
|
|
|
835 |
$rurl['fullpath'] .= '?'.$_SERVER['QUERY_STRING'];
|
|
|
836 |
}
|
|
|
837 |
$_SERVER['REQUEST_URI'] = $rurl['fullpath']; // extra IIS compatibility
|
|
|
838 |
|
|
|
839 |
} else if (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false) {
|
|
|
840 |
if (!isset($_SERVER['SCRIPT_NAME'])) {
|
|
|
841 |
die('Invalid server configuration detected, please try to add "fastcgi_param SCRIPT_NAME $fastcgi_script_name;" to the nginx server configuration.');
|
|
|
842 |
}
|
|
|
843 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI'];
|
|
|
844 |
} else {
|
| 1441 |
ariadna |
845 |
// Any other servers we can assume will pass the request_uri normally.
|
|
|
846 |
$rurl['fullpath'] = $_SERVER['REQUEST_URI'];
|
| 1 |
efrain |
847 |
}
|
|
|
848 |
|
|
|
849 |
// sanitize the url a bit more, the encoding style may be different in vars above
|
|
|
850 |
$rurl['fullpath'] = str_replace('"', '%22', $rurl['fullpath']);
|
|
|
851 |
$rurl['fullpath'] = str_replace('\'', '%27', $rurl['fullpath']);
|
|
|
852 |
|
|
|
853 |
return $rurl;
|
|
|
854 |
}
|
|
|
855 |
|
|
|
856 |
/**
|
|
|
857 |
* Try to work around the 'max_input_vars' restriction if necessary.
|
|
|
858 |
*/
|
|
|
859 |
function workaround_max_input_vars() {
|
|
|
860 |
// Make sure this gets executed only once from lib/setup.php!
|
|
|
861 |
static $executed = false;
|
|
|
862 |
if ($executed) {
|
|
|
863 |
debugging('workaround_max_input_vars() must be called only once!');
|
|
|
864 |
return;
|
|
|
865 |
}
|
|
|
866 |
$executed = true;
|
|
|
867 |
|
|
|
868 |
if (!isset($_SERVER["CONTENT_TYPE"]) or strpos($_SERVER["CONTENT_TYPE"], 'multipart/form-data') !== false) {
|
|
|
869 |
// Not a post or 'multipart/form-data' which is not compatible with "php://input" reading.
|
|
|
870 |
return;
|
|
|
871 |
}
|
|
|
872 |
|
|
|
873 |
if (!isloggedin() or isguestuser()) {
|
|
|
874 |
// Only real users post huge forms.
|
|
|
875 |
return;
|
|
|
876 |
}
|
|
|
877 |
|
|
|
878 |
$max = (int)ini_get('max_input_vars');
|
|
|
879 |
|
|
|
880 |
if ($max <= 0) {
|
|
|
881 |
// Most probably PHP < 5.3.9 that does not implement this limit.
|
|
|
882 |
return;
|
|
|
883 |
}
|
|
|
884 |
|
|
|
885 |
if ($max >= 200000) {
|
|
|
886 |
// This value should be ok for all our forms, by setting it in php.ini
|
|
|
887 |
// admins may prevent any unexpected regressions caused by this hack.
|
|
|
888 |
|
|
|
889 |
// Note there is no need to worry about DDoS caused by making this limit very high
|
|
|
890 |
// because there are very many easier ways to DDoS any Moodle server.
|
|
|
891 |
return;
|
|
|
892 |
}
|
|
|
893 |
|
|
|
894 |
// Worst case is advanced checkboxes which use up to two max_input_vars
|
|
|
895 |
// slots for each entry in $_POST, because of sending two fields with the
|
|
|
896 |
// same name. So count everything twice just in case.
|
|
|
897 |
if (count($_POST, COUNT_RECURSIVE) * 2 < $max) {
|
|
|
898 |
return;
|
|
|
899 |
}
|
|
|
900 |
|
|
|
901 |
// Large POST request with enctype supported by php://input.
|
|
|
902 |
// Parse php://input in chunks to bypass max_input_vars limit, which also applies to parse_str().
|
|
|
903 |
$str = file_get_contents("php://input");
|
|
|
904 |
if ($str === false or $str === '') {
|
|
|
905 |
// Some weird error.
|
|
|
906 |
return;
|
|
|
907 |
}
|
|
|
908 |
|
|
|
909 |
$delim = '&';
|
|
|
910 |
$fun = function($p) use ($delim) {
|
|
|
911 |
return implode($delim, $p);
|
|
|
912 |
};
|
|
|
913 |
$chunks = array_map($fun, array_chunk(explode($delim, $str), $max));
|
|
|
914 |
|
|
|
915 |
// Clear everything from existing $_POST array, otherwise it might be included
|
|
|
916 |
// twice (this affects array params primarily).
|
|
|
917 |
foreach ($_POST as $key => $value) {
|
|
|
918 |
unset($_POST[$key]);
|
|
|
919 |
// Also clear from request array - but only the things that are in $_POST,
|
|
|
920 |
// that way it will leave the things from a get request if any.
|
|
|
921 |
unset($_REQUEST[$key]);
|
|
|
922 |
}
|
|
|
923 |
|
|
|
924 |
foreach ($chunks as $chunk) {
|
|
|
925 |
$values = array();
|
|
|
926 |
parse_str($chunk, $values);
|
|
|
927 |
|
|
|
928 |
merge_query_params($_POST, $values);
|
|
|
929 |
merge_query_params($_REQUEST, $values);
|
|
|
930 |
}
|
|
|
931 |
}
|
|
|
932 |
|
|
|
933 |
/**
|
|
|
934 |
* Merge parsed POST chunks.
|
|
|
935 |
*
|
|
|
936 |
* NOTE: this is not perfect, but it should work in most cases hopefully.
|
|
|
937 |
*
|
|
|
938 |
* @param array $target
|
|
|
939 |
* @param array $values
|
|
|
940 |
*/
|
|
|
941 |
function merge_query_params(array &$target, array $values) {
|
|
|
942 |
if (isset($values[0]) and isset($target[0])) {
|
|
|
943 |
// This looks like a split [] array, lets verify the keys are continuous starting with 0.
|
|
|
944 |
$keys1 = array_keys($values);
|
|
|
945 |
$keys2 = array_keys($target);
|
|
|
946 |
if ($keys1 === array_keys($keys1) and $keys2 === array_keys($keys2)) {
|
|
|
947 |
foreach ($values as $v) {
|
|
|
948 |
$target[] = $v;
|
|
|
949 |
}
|
|
|
950 |
return;
|
|
|
951 |
}
|
|
|
952 |
}
|
|
|
953 |
foreach ($values as $k => $v) {
|
|
|
954 |
if (!isset($target[$k])) {
|
|
|
955 |
$target[$k] = $v;
|
|
|
956 |
continue;
|
|
|
957 |
}
|
|
|
958 |
if (is_array($target[$k]) and is_array($v)) {
|
|
|
959 |
merge_query_params($target[$k], $v);
|
|
|
960 |
continue;
|
|
|
961 |
}
|
|
|
962 |
// We should not get here unless there are duplicates in params.
|
|
|
963 |
$target[$k] = $v;
|
|
|
964 |
}
|
|
|
965 |
}
|
|
|
966 |
|
|
|
967 |
/**
|
|
|
968 |
* Initializes our performance info early.
|
|
|
969 |
*
|
|
|
970 |
* Pairs up with get_performance_info() which is actually
|
|
|
971 |
* in moodlelib.php. This function is here so that we can
|
|
|
972 |
* call it before all the libs are pulled in.
|
|
|
973 |
*
|
|
|
974 |
* @uses $PERF
|
|
|
975 |
*/
|
|
|
976 |
function init_performance_info() {
|
|
|
977 |
|
|
|
978 |
global $PERF, $CFG, $USER;
|
|
|
979 |
|
|
|
980 |
$PERF = new stdClass();
|
|
|
981 |
if (function_exists('microtime')) {
|
|
|
982 |
$PERF->starttime = microtime();
|
|
|
983 |
}
|
|
|
984 |
if (function_exists('memory_get_usage')) {
|
|
|
985 |
$PERF->startmemory = memory_get_usage();
|
|
|
986 |
}
|
|
|
987 |
if (function_exists('posix_times')) {
|
|
|
988 |
$PERF->startposixtimes = posix_times();
|
|
|
989 |
}
|
|
|
990 |
}
|
|
|
991 |
|
|
|
992 |
/**
|
|
|
993 |
* Indicates whether we are in the middle of the initial Moodle install.
|
|
|
994 |
*
|
|
|
995 |
* Very occasionally it is necessary avoid running certain bits of code before the
|
|
|
996 |
* Moodle installation has completed. The installed flag is set in admin/index.php
|
|
|
997 |
* after Moodle core and all the plugins have been installed, but just before
|
|
|
998 |
* the person doing the initial install is asked to choose the admin password.
|
|
|
999 |
*
|
|
|
1000 |
* @return boolean true if the initial install is not complete.
|
|
|
1001 |
*/
|
|
|
1002 |
function during_initial_install() {
|
|
|
1003 |
global $CFG;
|
|
|
1004 |
return empty($CFG->rolesactive);
|
|
|
1005 |
}
|
|
|
1006 |
|
|
|
1007 |
/**
|
|
|
1008 |
* Function to raise the memory limit to a new value.
|
|
|
1009 |
* Will respect the memory limit if it is higher, thus allowing
|
|
|
1010 |
* settings in php.ini, apache conf or command line switches
|
|
|
1011 |
* to override it.
|
|
|
1012 |
*
|
|
|
1013 |
* The memory limit should be expressed with a constant
|
|
|
1014 |
* MEMORY_STANDARD, MEMORY_EXTRA or MEMORY_HUGE.
|
|
|
1015 |
* It is possible to use strings or integers too (eg:'128M').
|
|
|
1016 |
*
|
|
|
1017 |
* @param mixed $newlimit the new memory limit
|
|
|
1018 |
* @return bool success
|
|
|
1019 |
*/
|
|
|
1020 |
function raise_memory_limit($newlimit) {
|
|
|
1021 |
global $CFG;
|
|
|
1022 |
|
|
|
1023 |
if ($newlimit == MEMORY_UNLIMITED) {
|
|
|
1024 |
ini_set('memory_limit', -1);
|
|
|
1025 |
return true;
|
|
|
1026 |
|
|
|
1027 |
} else if ($newlimit == MEMORY_STANDARD) {
|
|
|
1028 |
if (PHP_INT_SIZE > 4) {
|
|
|
1029 |
$newlimit = get_real_size('128M'); // 64bit needs more memory
|
|
|
1030 |
} else {
|
|
|
1031 |
$newlimit = get_real_size('96M');
|
|
|
1032 |
}
|
|
|
1033 |
|
|
|
1034 |
} else if ($newlimit == MEMORY_EXTRA) {
|
|
|
1035 |
if (PHP_INT_SIZE > 4) {
|
|
|
1036 |
$newlimit = get_real_size('384M'); // 64bit needs more memory
|
|
|
1037 |
} else {
|
|
|
1038 |
$newlimit = get_real_size('256M');
|
|
|
1039 |
}
|
|
|
1040 |
if (!empty($CFG->extramemorylimit)) {
|
|
|
1041 |
$extra = get_real_size($CFG->extramemorylimit);
|
|
|
1042 |
if ($extra > $newlimit) {
|
|
|
1043 |
$newlimit = $extra;
|
|
|
1044 |
}
|
|
|
1045 |
}
|
|
|
1046 |
|
|
|
1047 |
} else if ($newlimit == MEMORY_HUGE) {
|
|
|
1048 |
// MEMORY_HUGE uses 2G or MEMORY_EXTRA, whichever is bigger.
|
|
|
1049 |
$newlimit = get_real_size('2G');
|
|
|
1050 |
if (!empty($CFG->extramemorylimit)) {
|
|
|
1051 |
$extra = get_real_size($CFG->extramemorylimit);
|
|
|
1052 |
if ($extra > $newlimit) {
|
|
|
1053 |
$newlimit = $extra;
|
|
|
1054 |
}
|
|
|
1055 |
}
|
|
|
1056 |
|
|
|
1057 |
} else {
|
|
|
1058 |
$newlimit = get_real_size($newlimit);
|
|
|
1059 |
}
|
|
|
1060 |
|
|
|
1061 |
if ($newlimit <= 0) {
|
|
|
1062 |
debugging('Invalid memory limit specified.');
|
|
|
1063 |
return false;
|
|
|
1064 |
}
|
|
|
1065 |
|
|
|
1066 |
$cur = ini_get('memory_limit');
|
|
|
1067 |
if (empty($cur)) {
|
|
|
1068 |
// if php is compiled without --enable-memory-limits
|
|
|
1069 |
// apparently memory_limit is set to ''
|
|
|
1070 |
$cur = 0;
|
|
|
1071 |
} else {
|
|
|
1072 |
if ($cur == -1){
|
|
|
1073 |
return true; // unlimited mem!
|
|
|
1074 |
}
|
|
|
1075 |
$cur = get_real_size($cur);
|
|
|
1076 |
}
|
|
|
1077 |
|
|
|
1078 |
if ($newlimit > $cur) {
|
|
|
1079 |
ini_set('memory_limit', $newlimit);
|
|
|
1080 |
return true;
|
|
|
1081 |
}
|
|
|
1082 |
return false;
|
|
|
1083 |
}
|
|
|
1084 |
|
|
|
1085 |
/**
|
|
|
1086 |
* Function to reduce the memory limit to a new value.
|
|
|
1087 |
* Will respect the memory limit if it is lower, thus allowing
|
|
|
1088 |
* settings in php.ini, apache conf or command line switches
|
|
|
1089 |
* to override it
|
|
|
1090 |
*
|
|
|
1091 |
* The memory limit should be expressed with a string (eg:'64M')
|
|
|
1092 |
*
|
|
|
1093 |
* @param string $newlimit the new memory limit
|
|
|
1094 |
* @return bool
|
|
|
1095 |
*/
|
|
|
1096 |
function reduce_memory_limit($newlimit) {
|
|
|
1097 |
if (empty($newlimit)) {
|
|
|
1098 |
return false;
|
|
|
1099 |
}
|
|
|
1100 |
$cur = ini_get('memory_limit');
|
|
|
1101 |
if (empty($cur)) {
|
|
|
1102 |
// if php is compiled without --enable-memory-limits
|
|
|
1103 |
// apparently memory_limit is set to ''
|
|
|
1104 |
$cur = 0;
|
|
|
1105 |
} else {
|
|
|
1106 |
if ($cur == -1){
|
|
|
1107 |
return true; // unlimited mem!
|
|
|
1108 |
}
|
|
|
1109 |
$cur = get_real_size($cur);
|
|
|
1110 |
}
|
|
|
1111 |
|
|
|
1112 |
$new = get_real_size($newlimit);
|
|
|
1113 |
// -1 is smaller, but it means unlimited
|
|
|
1114 |
if ($new < $cur && $new != -1) {
|
|
|
1115 |
ini_set('memory_limit', $newlimit);
|
|
|
1116 |
return true;
|
|
|
1117 |
}
|
|
|
1118 |
return false;
|
|
|
1119 |
}
|
|
|
1120 |
|
|
|
1121 |
/**
|
|
|
1122 |
* Converts numbers like 10M into bytes.
|
|
|
1123 |
*
|
|
|
1124 |
* @param string $size The size to be converted
|
|
|
1125 |
* @return int
|
|
|
1126 |
*/
|
|
|
1127 |
function get_real_size($size = 0) {
|
|
|
1128 |
if (!$size) {
|
|
|
1129 |
return 0;
|
|
|
1130 |
}
|
|
|
1131 |
|
|
|
1132 |
static $binaryprefixes = array(
|
|
|
1133 |
'K' => 1024 ** 1,
|
|
|
1134 |
'k' => 1024 ** 1,
|
|
|
1135 |
'M' => 1024 ** 2,
|
|
|
1136 |
'm' => 1024 ** 2,
|
|
|
1137 |
'G' => 1024 ** 3,
|
|
|
1138 |
'g' => 1024 ** 3,
|
|
|
1139 |
'T' => 1024 ** 4,
|
|
|
1140 |
't' => 1024 ** 4,
|
|
|
1141 |
'P' => 1024 ** 5,
|
|
|
1142 |
'p' => 1024 ** 5,
|
|
|
1143 |
);
|
|
|
1144 |
|
|
|
1145 |
if (preg_match('/^([0-9]+)([KMGTP])/i', $size, $matches)) {
|
|
|
1146 |
return $matches[1] * $binaryprefixes[$matches[2]];
|
|
|
1147 |
}
|
|
|
1148 |
|
|
|
1149 |
return (int) $size;
|
|
|
1150 |
}
|
|
|
1151 |
|
|
|
1152 |
/**
|
|
|
1153 |
* Check whether a major upgrade is needed.
|
|
|
1154 |
*
|
|
|
1155 |
* That is defined as an upgrade that changes something really fundamental
|
|
|
1156 |
* in the database, so nothing can possibly work until the database has
|
|
|
1157 |
* been updated, and that is defined by the hard-coded version number in
|
|
|
1158 |
* this function.
|
|
|
1159 |
*
|
|
|
1160 |
* @return bool
|
|
|
1161 |
*/
|
|
|
1162 |
function is_major_upgrade_required() {
|
|
|
1163 |
global $CFG;
|
|
|
1164 |
$lastmajordbchanges = 2024010400.00; // This should be the version where the breaking changes happen.
|
|
|
1165 |
|
|
|
1166 |
$required = empty($CFG->version);
|
|
|
1167 |
$required = $required || (float)$CFG->version < $lastmajordbchanges;
|
|
|
1168 |
$required = $required || during_initial_install();
|
|
|
1169 |
$required = $required || !empty($CFG->adminsetuppending);
|
|
|
1170 |
|
|
|
1171 |
return $required;
|
|
|
1172 |
}
|
|
|
1173 |
|
|
|
1174 |
/**
|
|
|
1175 |
* Redirect to the Notifications page if a major upgrade is required, and
|
|
|
1176 |
* terminate the current user session.
|
|
|
1177 |
*/
|
|
|
1178 |
function redirect_if_major_upgrade_required() {
|
|
|
1179 |
global $CFG;
|
|
|
1180 |
if (is_major_upgrade_required()) {
|
|
|
1181 |
try {
|
|
|
1182 |
@\core\session\manager::terminate_current();
|
|
|
1183 |
} catch (Exception $e) {
|
|
|
1184 |
// Ignore any errors, redirect to upgrade anyway.
|
|
|
1185 |
}
|
|
|
1186 |
$url = $CFG->wwwroot . '/' . $CFG->admin . '/index.php';
|
|
|
1187 |
@header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other');
|
|
|
1188 |
@header('Location: ' . $url);
|
|
|
1189 |
echo bootstrap_renderer::plain_redirect_message(htmlspecialchars($url, ENT_COMPAT));
|
|
|
1190 |
exit;
|
|
|
1191 |
}
|
|
|
1192 |
}
|
|
|
1193 |
|
|
|
1194 |
/**
|
|
|
1195 |
* Makes sure that upgrade process is not running
|
|
|
1196 |
*
|
|
|
1197 |
* To be inserted in the core functions that can not be called by pluigns during upgrade.
|
|
|
1198 |
* Core upgrade should not use any API functions at all.
|
|
|
1199 |
* See {@link https://moodledev.io/docs/guides/upgrade#upgrade-code-restrictions}
|
|
|
1200 |
*
|
|
|
1201 |
* @throws moodle_exception if executed from inside of upgrade script and $warningonly is false
|
|
|
1202 |
* @param bool $warningonly if true displays a warning instead of throwing an exception
|
|
|
1203 |
* @return bool true if executed from outside of upgrade process, false if from inside upgrade process and function is used for warning only
|
|
|
1204 |
*/
|
|
|
1205 |
function upgrade_ensure_not_running($warningonly = false) {
|
|
|
1206 |
global $CFG;
|
|
|
1207 |
if (!empty($CFG->upgraderunning)) {
|
|
|
1208 |
if (!$warningonly) {
|
|
|
1209 |
throw new moodle_exception('cannotexecduringupgrade');
|
|
|
1210 |
} else {
|
|
|
1211 |
debugging(get_string('cannotexecduringupgrade', 'error'), DEBUG_DEVELOPER);
|
|
|
1212 |
return false;
|
|
|
1213 |
}
|
|
|
1214 |
}
|
|
|
1215 |
return true;
|
|
|
1216 |
}
|
|
|
1217 |
|
|
|
1218 |
/**
|
|
|
1219 |
* Function to check if a directory exists and by default create it if not exists.
|
|
|
1220 |
*
|
|
|
1221 |
* Previously this was accepting paths only from dataroot, but we now allow
|
|
|
1222 |
* files outside of dataroot if you supply custom paths for some settings in config.php.
|
|
|
1223 |
* This function does not verify that the directory is writable.
|
|
|
1224 |
*
|
|
|
1225 |
* NOTE: this function uses current file stat cache,
|
|
|
1226 |
* please use clearstatcache() before this if you expect that the
|
|
|
1227 |
* directories may have been removed recently from a different request.
|
|
|
1228 |
*
|
|
|
1229 |
* @param string $dir absolute directory path
|
|
|
1230 |
* @param boolean $create directory if does not exist
|
|
|
1231 |
* @param boolean $recursive create directory recursively
|
|
|
1232 |
* @return boolean true if directory exists or created, false otherwise
|
|
|
1233 |
*/
|
|
|
1234 |
function check_dir_exists($dir, $create = true, $recursive = true) {
|
|
|
1235 |
global $CFG;
|
|
|
1236 |
|
|
|
1237 |
umask($CFG->umaskpermissions);
|
|
|
1238 |
|
|
|
1239 |
if (is_dir($dir)) {
|
|
|
1240 |
return true;
|
|
|
1241 |
}
|
|
|
1242 |
|
|
|
1243 |
if (!$create) {
|
|
|
1244 |
return false;
|
|
|
1245 |
}
|
|
|
1246 |
|
|
|
1247 |
return mkdir($dir, $CFG->directorypermissions, $recursive);
|
|
|
1248 |
}
|
|
|
1249 |
|
|
|
1250 |
/**
|
|
|
1251 |
* Create a new unique directory within the specified directory.
|
|
|
1252 |
*
|
|
|
1253 |
* @param string $basedir The directory to create your new unique directory within.
|
|
|
1254 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1255 |
* @return string The created directory
|
|
|
1256 |
* @throws invalid_dataroot_permissions
|
|
|
1257 |
*/
|
|
|
1258 |
function make_unique_writable_directory($basedir, $exceptiononerror = true) {
|
|
|
1259 |
if (!is_dir($basedir) || !is_writable($basedir)) {
|
|
|
1260 |
// The basedir is not writable. We will not be able to create the child directory.
|
|
|
1261 |
if ($exceptiononerror) {
|
|
|
1262 |
throw new invalid_dataroot_permissions($basedir . ' is not writable. Unable to create a unique directory within it.');
|
|
|
1263 |
} else {
|
|
|
1264 |
return false;
|
|
|
1265 |
}
|
|
|
1266 |
}
|
|
|
1267 |
|
|
|
1268 |
do {
|
|
|
1269 |
// Let's use uniqid() because it's "unique enough" (microtime based). The loop does handle repetitions.
|
|
|
1270 |
// Windows and old PHP don't like very long paths, so try to keep this shorter. See MDL-69975.
|
|
|
1271 |
$uniquedir = $basedir . DIRECTORY_SEPARATOR . uniqid();
|
|
|
1272 |
} while (
|
|
|
1273 |
// Ensure that basedir is still writable - if we do not check, we could get stuck in a loop here.
|
|
|
1274 |
is_writable($basedir) &&
|
|
|
1275 |
|
|
|
1276 |
// Make the new unique directory. If the directory already exists, it will return false.
|
|
|
1277 |
!make_writable_directory($uniquedir, $exceptiononerror) &&
|
|
|
1278 |
|
|
|
1279 |
// Ensure that the directory now exists
|
|
|
1280 |
file_exists($uniquedir) && is_dir($uniquedir)
|
|
|
1281 |
);
|
|
|
1282 |
|
|
|
1283 |
// Check that the directory was correctly created.
|
|
|
1284 |
if (!file_exists($uniquedir) || !is_dir($uniquedir) || !is_writable($uniquedir)) {
|
|
|
1285 |
if ($exceptiononerror) {
|
|
|
1286 |
throw new invalid_dataroot_permissions('Unique directory creation failed.');
|
|
|
1287 |
} else {
|
|
|
1288 |
return false;
|
|
|
1289 |
}
|
|
|
1290 |
}
|
|
|
1291 |
|
|
|
1292 |
return $uniquedir;
|
|
|
1293 |
}
|
|
|
1294 |
|
|
|
1295 |
/**
|
|
|
1296 |
* Create a directory and make sure it is writable.
|
|
|
1297 |
*
|
|
|
1298 |
* @private
|
|
|
1299 |
* @param string $dir the full path of the directory to be created
|
|
|
1300 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1301 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1302 |
*/
|
|
|
1303 |
function make_writable_directory($dir, $exceptiononerror = true) {
|
|
|
1304 |
global $CFG;
|
|
|
1305 |
|
|
|
1306 |
if (file_exists($dir) and !is_dir($dir)) {
|
|
|
1307 |
if ($exceptiononerror) {
|
|
|
1308 |
throw new coding_exception($dir.' directory can not be created, file with the same name already exists.');
|
|
|
1309 |
} else {
|
|
|
1310 |
return false;
|
|
|
1311 |
}
|
|
|
1312 |
}
|
|
|
1313 |
|
|
|
1314 |
umask($CFG->umaskpermissions);
|
|
|
1315 |
|
|
|
1316 |
if (!file_exists($dir)) {
|
|
|
1317 |
if (!@mkdir($dir, $CFG->directorypermissions, true)) {
|
|
|
1318 |
clearstatcache();
|
|
|
1319 |
// There might be a race condition when creating directory.
|
|
|
1320 |
if (!is_dir($dir)) {
|
|
|
1321 |
if ($exceptiononerror) {
|
|
|
1322 |
throw new invalid_dataroot_permissions($dir.' can not be created, check permissions.');
|
|
|
1323 |
} else {
|
|
|
1324 |
debugging('Can not create directory: '.$dir, DEBUG_DEVELOPER);
|
|
|
1325 |
return false;
|
|
|
1326 |
}
|
|
|
1327 |
}
|
|
|
1328 |
}
|
|
|
1329 |
}
|
|
|
1330 |
|
|
|
1331 |
if (!is_writable($dir)) {
|
|
|
1332 |
if ($exceptiononerror) {
|
|
|
1333 |
throw new invalid_dataroot_permissions($dir.' is not writable, check permissions.');
|
|
|
1334 |
} else {
|
|
|
1335 |
return false;
|
|
|
1336 |
}
|
|
|
1337 |
}
|
|
|
1338 |
|
|
|
1339 |
return $dir;
|
|
|
1340 |
}
|
|
|
1341 |
|
|
|
1342 |
/**
|
|
|
1343 |
* Protect a directory from web access.
|
|
|
1344 |
* Could be extended in the future to support other mechanisms (e.g. other webservers).
|
|
|
1345 |
*
|
|
|
1346 |
* @private
|
|
|
1347 |
* @param string $dir the full path of the directory to be protected
|
|
|
1348 |
*/
|
|
|
1349 |
function protect_directory($dir) {
|
|
|
1350 |
global $CFG;
|
|
|
1351 |
// Make sure a .htaccess file is here, JUST IN CASE the files area is in the open and .htaccess is supported
|
|
|
1352 |
if (!file_exists("$dir/.htaccess")) {
|
|
|
1353 |
if ($handle = fopen("$dir/.htaccess", 'w')) { // For safety
|
|
|
1354 |
@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");
|
|
|
1355 |
@fclose($handle);
|
|
|
1356 |
@chmod("$dir/.htaccess", $CFG->filepermissions);
|
|
|
1357 |
}
|
|
|
1358 |
}
|
|
|
1359 |
}
|
|
|
1360 |
|
|
|
1361 |
/**
|
|
|
1362 |
* Create a directory under dataroot and make sure it is writable.
|
|
|
1363 |
* Do not use for temporary and cache files - see make_temp_directory() and make_cache_directory().
|
|
|
1364 |
*
|
|
|
1365 |
* @param string $directory the full path of the directory to be created under $CFG->dataroot
|
|
|
1366 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1367 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1368 |
*/
|
|
|
1369 |
function make_upload_directory($directory, $exceptiononerror = true) {
|
|
|
1370 |
global $CFG;
|
|
|
1371 |
|
|
|
1372 |
if (strpos($directory, 'temp/') === 0 or $directory === 'temp') {
|
|
|
1373 |
debugging('Use make_temp_directory() for creation of temporary directory and $CFG->tempdir to get the location.');
|
|
|
1374 |
|
|
|
1375 |
} else if (strpos($directory, 'cache/') === 0 or $directory === 'cache') {
|
|
|
1376 |
debugging('Use make_cache_directory() for creation of cache directory and $CFG->cachedir to get the location.');
|
|
|
1377 |
|
|
|
1378 |
} else if (strpos($directory, 'localcache/') === 0 or $directory === 'localcache') {
|
|
|
1379 |
debugging('Use make_localcache_directory() for creation of local cache directory and $CFG->localcachedir to get the location.');
|
|
|
1380 |
}
|
|
|
1381 |
|
|
|
1382 |
protect_directory($CFG->dataroot);
|
|
|
1383 |
return make_writable_directory("$CFG->dataroot/$directory", $exceptiononerror);
|
|
|
1384 |
}
|
|
|
1385 |
|
|
|
1386 |
/**
|
|
|
1387 |
* Get a per-request storage directory in the tempdir.
|
|
|
1388 |
*
|
|
|
1389 |
* The directory is automatically cleaned up during the shutdown handler.
|
|
|
1390 |
*
|
|
|
1391 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1392 |
* @param bool $forcecreate Force creation of a new parent directory
|
|
|
1393 |
* @return string Returns full path to directory if successful, false if not; may throw exception
|
|
|
1394 |
*/
|
|
|
1395 |
function get_request_storage_directory($exceptiononerror = true, bool $forcecreate = false) {
|
|
|
1396 |
global $CFG;
|
|
|
1397 |
|
|
|
1398 |
static $requestdir = null;
|
|
|
1399 |
|
|
|
1400 |
$writabledirectoryexists = (null !== $requestdir);
|
|
|
1401 |
$writabledirectoryexists = $writabledirectoryexists && file_exists($requestdir);
|
|
|
1402 |
$writabledirectoryexists = $writabledirectoryexists && is_dir($requestdir);
|
|
|
1403 |
$writabledirectoryexists = $writabledirectoryexists && is_writable($requestdir);
|
|
|
1404 |
$createnewdirectory = $forcecreate || !$writabledirectoryexists;
|
|
|
1405 |
|
|
|
1406 |
if ($createnewdirectory) {
|
|
|
1407 |
|
|
|
1408 |
// Let's add the first chars of siteidentifier only. This is to help separate
|
|
|
1409 |
// paths on systems which host multiple moodles. We don't use the full id
|
|
|
1410 |
// as Windows and old PHP don't like very long paths. See MDL-69975.
|
|
|
1411 |
$basedir = $CFG->localrequestdir . '/' . substr($CFG->siteidentifier, 0, 4);
|
|
|
1412 |
|
|
|
1413 |
make_writable_directory($basedir);
|
|
|
1414 |
protect_directory($basedir);
|
|
|
1415 |
|
|
|
1416 |
if ($dir = make_unique_writable_directory($basedir, $exceptiononerror)) {
|
|
|
1417 |
// Register a shutdown handler to remove the directory.
|
|
|
1418 |
\core_shutdown_manager::register_function('remove_dir', [$dir]);
|
|
|
1419 |
}
|
|
|
1420 |
|
|
|
1421 |
$requestdir = $dir;
|
|
|
1422 |
}
|
|
|
1423 |
|
|
|
1424 |
return $requestdir;
|
|
|
1425 |
}
|
|
|
1426 |
|
|
|
1427 |
/**
|
|
|
1428 |
* Create a per-request directory and make sure it is writable.
|
|
|
1429 |
* This can only be used during the current request and will be tidied away
|
|
|
1430 |
* automatically afterwards.
|
|
|
1431 |
*
|
|
|
1432 |
* A new, unique directory is always created within a shared base request directory.
|
|
|
1433 |
*
|
|
|
1434 |
* In some exceptional cases an alternative base directory may be required. This can be accomplished using the
|
|
|
1435 |
* $forcecreate parameter. Typically this will only be requried where the file may be required during a shutdown handler
|
|
|
1436 |
* which may or may not be registered after a previous request directory has been created.
|
|
|
1437 |
*
|
|
|
1438 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1439 |
* @param bool $forcecreate Force creation of a new parent directory
|
|
|
1440 |
* @return string The full path to directory if successful, false if not; may throw exception
|
|
|
1441 |
*/
|
|
|
1442 |
function make_request_directory(bool $exceptiononerror = true, bool $forcecreate = false) {
|
|
|
1443 |
$basedir = get_request_storage_directory($exceptiononerror, $forcecreate);
|
|
|
1444 |
return make_unique_writable_directory($basedir, $exceptiononerror);
|
|
|
1445 |
}
|
|
|
1446 |
|
|
|
1447 |
/**
|
|
|
1448 |
* Get the full path of a directory under $CFG->backuptempdir.
|
|
|
1449 |
*
|
|
|
1450 |
* @param string $directory the relative path of the directory under $CFG->backuptempdir
|
|
|
1451 |
* @return string|false Returns full path to directory given a valid string; otherwise, false.
|
|
|
1452 |
*/
|
|
|
1453 |
function get_backup_temp_directory($directory) {
|
|
|
1454 |
global $CFG;
|
|
|
1455 |
if (($directory === null) || ($directory === false)) {
|
|
|
1456 |
return false;
|
|
|
1457 |
}
|
|
|
1458 |
return "$CFG->backuptempdir/$directory";
|
|
|
1459 |
}
|
|
|
1460 |
|
|
|
1461 |
/**
|
|
|
1462 |
* Create a directory under $CFG->backuptempdir and make sure it is writable.
|
|
|
1463 |
*
|
|
|
1464 |
* Do not use for storing generic temp files - see make_temp_directory() instead for this purpose.
|
|
|
1465 |
*
|
|
|
1466 |
* Backup temporary files must be on a shared storage.
|
|
|
1467 |
*
|
|
|
1468 |
* @param string $directory the relative path of the directory to be created under $CFG->backuptempdir
|
|
|
1469 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1470 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1471 |
*/
|
|
|
1472 |
function make_backup_temp_directory($directory, $exceptiononerror = true) {
|
|
|
1473 |
global $CFG;
|
|
|
1474 |
if ($CFG->backuptempdir !== "$CFG->tempdir/backup") {
|
|
|
1475 |
check_dir_exists($CFG->backuptempdir, true, true);
|
|
|
1476 |
protect_directory($CFG->backuptempdir);
|
|
|
1477 |
} else {
|
|
|
1478 |
protect_directory($CFG->tempdir);
|
|
|
1479 |
}
|
|
|
1480 |
return make_writable_directory("$CFG->backuptempdir/$directory", $exceptiononerror);
|
|
|
1481 |
}
|
|
|
1482 |
|
|
|
1483 |
/**
|
|
|
1484 |
* Create a directory under tempdir and make sure it is writable.
|
|
|
1485 |
*
|
|
|
1486 |
* Where possible, please use make_request_directory() and limit the scope
|
|
|
1487 |
* of your data to the current HTTP request.
|
|
|
1488 |
*
|
|
|
1489 |
* Do not use for storing cache files - see make_cache_directory(), and
|
|
|
1490 |
* make_localcache_directory() instead for this purpose.
|
|
|
1491 |
*
|
|
|
1492 |
* Temporary files must be on a shared storage, and heavy usage is
|
|
|
1493 |
* discouraged due to the performance impact upon clustered environments.
|
|
|
1494 |
*
|
|
|
1495 |
* @param string $directory the full path of the directory to be created under $CFG->tempdir
|
|
|
1496 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1497 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1498 |
*/
|
|
|
1499 |
function make_temp_directory($directory, $exceptiononerror = true) {
|
|
|
1500 |
global $CFG;
|
|
|
1501 |
if ($CFG->tempdir !== "$CFG->dataroot/temp") {
|
|
|
1502 |
check_dir_exists($CFG->tempdir, true, true);
|
|
|
1503 |
protect_directory($CFG->tempdir);
|
|
|
1504 |
} else {
|
|
|
1505 |
protect_directory($CFG->dataroot);
|
|
|
1506 |
}
|
|
|
1507 |
return make_writable_directory("$CFG->tempdir/$directory", $exceptiononerror);
|
|
|
1508 |
}
|
|
|
1509 |
|
|
|
1510 |
/**
|
|
|
1511 |
* Create a directory under cachedir and make sure it is writable.
|
|
|
1512 |
*
|
|
|
1513 |
* Note: this cache directory is shared by all cluster nodes.
|
|
|
1514 |
*
|
|
|
1515 |
* @param string $directory the full path of the directory to be created under $CFG->cachedir
|
|
|
1516 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1517 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1518 |
*/
|
|
|
1519 |
function make_cache_directory($directory, $exceptiononerror = true) {
|
|
|
1520 |
global $CFG;
|
|
|
1521 |
if ($CFG->cachedir !== "$CFG->dataroot/cache") {
|
|
|
1522 |
check_dir_exists($CFG->cachedir, true, true);
|
|
|
1523 |
protect_directory($CFG->cachedir);
|
|
|
1524 |
} else {
|
|
|
1525 |
protect_directory($CFG->dataroot);
|
|
|
1526 |
}
|
|
|
1527 |
return make_writable_directory("$CFG->cachedir/$directory", $exceptiononerror);
|
|
|
1528 |
}
|
|
|
1529 |
|
|
|
1530 |
/**
|
|
|
1531 |
* Create a directory under localcachedir and make sure it is writable.
|
|
|
1532 |
* The files in this directory MUST NOT change, use revisions or content hashes to
|
|
|
1533 |
* work around this limitation - this means you can only add new files here.
|
|
|
1534 |
*
|
|
|
1535 |
* The content of this directory gets purged automatically on all cluster nodes
|
|
|
1536 |
* after calling purge_all_caches() before new data is written to this directory.
|
|
|
1537 |
*
|
|
|
1538 |
* Note: this local cache directory does not need to be shared by cluster nodes.
|
|
|
1539 |
*
|
|
|
1540 |
* @param string $directory the relative path of the directory to be created under $CFG->localcachedir
|
|
|
1541 |
* @param bool $exceptiononerror throw exception if error encountered
|
|
|
1542 |
* @return string|false Returns full path to directory if successful, false if not; may throw exception
|
|
|
1543 |
*/
|
|
|
1544 |
function make_localcache_directory($directory, $exceptiononerror = true) {
|
|
|
1545 |
global $CFG;
|
|
|
1546 |
|
|
|
1547 |
make_writable_directory($CFG->localcachedir, $exceptiononerror);
|
|
|
1548 |
|
|
|
1549 |
if ($CFG->localcachedir !== "$CFG->dataroot/localcache") {
|
|
|
1550 |
protect_directory($CFG->localcachedir);
|
|
|
1551 |
} else {
|
|
|
1552 |
protect_directory($CFG->dataroot);
|
|
|
1553 |
}
|
|
|
1554 |
|
|
|
1555 |
if (!isset($CFG->localcachedirpurged)) {
|
|
|
1556 |
$CFG->localcachedirpurged = 0;
|
|
|
1557 |
}
|
|
|
1558 |
$timestampfile = "$CFG->localcachedir/.lastpurged";
|
|
|
1559 |
|
|
|
1560 |
if (!file_exists($timestampfile)) {
|
|
|
1561 |
touch($timestampfile);
|
|
|
1562 |
@chmod($timestampfile, $CFG->filepermissions);
|
|
|
1563 |
|
|
|
1564 |
} else if (filemtime($timestampfile) < $CFG->localcachedirpurged) {
|
|
|
1565 |
// This means our local cached dir was not purged yet.
|
|
|
1566 |
remove_dir($CFG->localcachedir, true);
|
|
|
1567 |
if ($CFG->localcachedir !== "$CFG->dataroot/localcache") {
|
|
|
1568 |
protect_directory($CFG->localcachedir);
|
|
|
1569 |
}
|
|
|
1570 |
touch($timestampfile);
|
|
|
1571 |
@chmod($timestampfile, $CFG->filepermissions);
|
|
|
1572 |
clearstatcache();
|
| 11 |
efrain |
1573 |
|
|
|
1574 |
// Then prewarm the local boostrap.php file as well.
|
|
|
1575 |
initialise_local_config_cache();
|
| 1 |
efrain |
1576 |
}
|
|
|
1577 |
|
|
|
1578 |
if ($directory === '') {
|
|
|
1579 |
return $CFG->localcachedir;
|
|
|
1580 |
}
|
|
|
1581 |
|
|
|
1582 |
return make_writable_directory("$CFG->localcachedir/$directory", $exceptiononerror);
|
|
|
1583 |
}
|
|
|
1584 |
|
|
|
1585 |
/**
|
|
|
1586 |
* Webserver access user logging
|
|
|
1587 |
*/
|
|
|
1588 |
function set_access_log_user() {
|
|
|
1589 |
global $USER, $CFG;
|
|
|
1590 |
if ($USER && isset($USER->username)) {
|
|
|
1591 |
$logmethod = '';
|
|
|
1592 |
$logvalue = 0;
|
|
|
1593 |
if (!empty($CFG->apacheloguser) && function_exists('apache_note')) {
|
|
|
1594 |
$logmethod = 'apache';
|
|
|
1595 |
$logvalue = $CFG->apacheloguser;
|
|
|
1596 |
}
|
|
|
1597 |
if (!empty($CFG->headerloguser)) {
|
|
|
1598 |
$logmethod = 'header';
|
|
|
1599 |
$logvalue = $CFG->headerloguser;
|
|
|
1600 |
}
|
|
|
1601 |
if (!empty($logmethod)) {
|
|
|
1602 |
$loguserid = $USER->id;
|
|
|
1603 |
$logusername = clean_filename($USER->username);
|
|
|
1604 |
$logname = '';
|
|
|
1605 |
if (isset($USER->firstname)) {
|
|
|
1606 |
// We can assume both will be set
|
|
|
1607 |
// - even if to empty.
|
|
|
1608 |
$logname = clean_filename($USER->firstname . " " . $USER->lastname);
|
|
|
1609 |
}
|
|
|
1610 |
if (\core\session\manager::is_loggedinas()) {
|
|
|
1611 |
$realuser = \core\session\manager::get_realuser();
|
|
|
1612 |
$logusername = clean_filename($realuser->username." as ".$logusername);
|
|
|
1613 |
$logname = clean_filename($realuser->firstname." ".$realuser->lastname ." as ".$logname);
|
|
|
1614 |
$loguserid = clean_filename($realuser->id." as ".$loguserid);
|
|
|
1615 |
}
|
|
|
1616 |
switch ($logvalue) {
|
|
|
1617 |
case 3:
|
|
|
1618 |
$logname = $logusername;
|
|
|
1619 |
break;
|
|
|
1620 |
case 2:
|
|
|
1621 |
$logname = $logname;
|
|
|
1622 |
break;
|
|
|
1623 |
case 1:
|
|
|
1624 |
default:
|
|
|
1625 |
$logname = $loguserid;
|
|
|
1626 |
break;
|
|
|
1627 |
}
|
|
|
1628 |
if ($logmethod == 'apache') {
|
|
|
1629 |
apache_note('MOODLEUSER', $logname);
|
|
|
1630 |
}
|
|
|
1631 |
|
|
|
1632 |
if ($logmethod == 'header' && !headers_sent()) {
|
|
|
1633 |
header("X-MOODLEUSER: $logname");
|
|
|
1634 |
}
|
|
|
1635 |
}
|
|
|
1636 |
}
|
|
|
1637 |
}
|
|
|
1638 |
|
|
|
1639 |
|
|
|
1640 |
/**
|
|
|
1641 |
* Add http stream instrumentation
|
|
|
1642 |
*
|
|
|
1643 |
* This detects which any reads or writes to a php stream which uses
|
|
|
1644 |
* the 'http' handler. Ideally 100% of traffic uses the Moodle curl
|
|
|
1645 |
* libraries which do not use php streams.
|
|
|
1646 |
*
|
|
|
1647 |
* @param array $code stream callback code
|
|
|
1648 |
*/
|
|
|
1649 |
function proxy_log_callback($code) {
|
|
|
1650 |
if ($code == STREAM_NOTIFY_CONNECT) {
|
|
|
1651 |
$trace = debug_backtrace();
|
|
|
1652 |
$function = $trace[count($trace) - 1];
|
|
|
1653 |
$error = "Unsafe internet IO detected: {$function['function']} with arguments " . join(', ', $function['args']) . "\n";
|
|
|
1654 |
error_log($error . format_backtrace($trace, true)); // phpcs:ignore
|
|
|
1655 |
}
|
|
|
1656 |
}
|
|
|
1657 |
|
|
|
1658 |
/**
|
|
|
1659 |
* A helper function for deprecated files to use to ensure that, when they are included for unit tests,
|
|
|
1660 |
* they are run in an isolated process.
|
|
|
1661 |
*
|
|
|
1662 |
* @throws \coding_exception The exception thrown when the process is not isolated.
|
|
|
1663 |
*/
|
|
|
1664 |
function require_phpunit_isolation(): void {
|
|
|
1665 |
if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
|
|
|
1666 |
// Not a test.
|
|
|
1667 |
return;
|
|
|
1668 |
}
|
|
|
1669 |
|
|
|
1670 |
if (defined('PHPUNIT_ISOLATED_TEST') && PHPUNIT_ISOLATED_TEST) {
|
|
|
1671 |
// Already isolated.
|
|
|
1672 |
return;
|
|
|
1673 |
}
|
|
|
1674 |
|
|
|
1675 |
throw new \coding_exception(
|
|
|
1676 |
'When including this file for a unit test, the test must be run in an isolated process. ' .
|
|
|
1677 |
'See the PHPUnit @runInSeparateProcess and @runTestsInSeparateProcesses annotations.'
|
|
|
1678 |
);
|
|
|
1679 |
}
|