Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
}