MDL-63421 env: Moodle 3.4.x and 3.5.x do not support PHP 7.3
[moodle.git] / lib / environmentlib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * This library includes all the necessary stuff to execute some standard
20  * tests of required versions and libraries to run Moodle. It can be
21  * used from the admin interface, and both at install and upgrade.
22  *
23  * All the info is stored in the admin/environment.xml file,
24  * supporting to have an updated version in dataroot/environment
25  *
26  * @copyright  (C) 2001-3001 Eloy Lafuente (stronk7) {@link http://contiento.com}
27  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28  * @package    core
29  * @subpackage admin
30  */
32 defined('MOODLE_INTERNAL') || die();
34 /// Add required files
35 /**
36  * Include the necessary
37  */
38     require_once($CFG->libdir.'/xmlize.php');
40 /// Define a bunch of XML processing errors
41     /** XML Processing Error */
42     define('NO_ERROR',                           0);
43     /** XML Processing Error */
44     define('NO_VERSION_DATA_FOUND',              1);
45     /** XML Processing Error */
46     define('NO_DATABASE_SECTION_FOUND',          2);
47     /** XML Processing Error */
48     define('NO_DATABASE_VENDORS_FOUND',          3);
49     /** XML Processing Error */
50     define('NO_DATABASE_VENDOR_MYSQL_FOUND',     4);
51     /** XML Processing Error */
52     define('NO_DATABASE_VENDOR_POSTGRES_FOUND',  5);
53     /** XML Processing Error */
54     define('NO_PHP_SECTION_FOUND',               6);
55     /** XML Processing Error */
56     define('NO_PHP_VERSION_FOUND',               7);
57     /** XML Processing Error */
58     define('NO_PHP_EXTENSIONS_SECTION_FOUND',    8);
59     /** XML Processing Error */
60     define('NO_PHP_EXTENSIONS_NAME_FOUND',       9);
61     /** XML Processing Error */
62     define('NO_DATABASE_VENDOR_VERSION_FOUND',  10);
63     /** XML Processing Error */
64     define('NO_UNICODE_SECTION_FOUND',          11);
65     /** XML Processing Error */
66     define('NO_CUSTOM_CHECK_FOUND',             12);
67     /** XML Processing Error */
68     define('CUSTOM_CHECK_FILE_MISSING',         13);
69     /** XML Processing Error */
70     define('CUSTOM_CHECK_FUNCTION_MISSING',     14);
71     /** XML Processing Error */
72     define('NO_PHP_SETTINGS_NAME_FOUND',        15);
73     /** XML Processing Error */
74     define('INCORRECT_FEEDBACK_FOR_REQUIRED',   16);
75     /** XML Processing Error */
76     define('INCORRECT_FEEDBACK_FOR_OPTIONAL',   17);
78 /// Define algorithm used to select the xml file
79     /** To select the newer file available to perform checks */
80     define('ENV_SELECT_NEWER',                   0);
81     /** To enforce the use of the file under dataroot */
82     define('ENV_SELECT_DATAROOT',                1);
83     /** To enforce the use of the file under admin (release) */
84     define('ENV_SELECT_RELEASE',                 2);
86 /**
87  * This function checks all the requirements defined in environment.xml.
88  *
89  * @param string $version version to check.
90  * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. Default ENV_SELECT_NEWER (BC)
91  * @return array with two elements. The first element true/false, depending on
92  *      on whether the check passed. The second element is an array of environment_results
93  *      objects that has detailed information about the checks and which ones passed.
94  */
95 function check_moodle_environment($version, $env_select = ENV_SELECT_NEWER) {
96     if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
97         throw new coding_exception('Incorrect value of $env_select parameter');
98     }
100 /// Get the more recent version before the requested
101     if (!$version = get_latest_version_available($version, $env_select)) {
102         return array(false, array());
103     }
105 /// Perform all the checks
106     if (!$environment_results = environment_check($version, $env_select)) {
107         return array(false, array());
108     }
110 /// Iterate over all the results looking for some error in required items
111 /// or some error_code
112     $result = true;
113     foreach ($environment_results as $environment_result) {
114         if (!$environment_result->getStatus() && $environment_result->getLevel() == 'required'
115           && !$environment_result->getBypassStr()) {
116             $result = false; // required item that is not bypased
117         } else if ($environment_result->getStatus() && $environment_result->getLevel() == 'required'
118           && $environment_result->getRestrictStr()) {
119             $result = false; // required item that is restricted
120         } else if ($environment_result->getErrorCode()) {
121             $result = false;
122         }
123     }
125     return array($result, $environment_results);
129 /**
130  * Returns array of critical errors in plain text format
131  * @param array $environment_results array of results gathered
132  * @return array errors
133  */
134 function environment_get_errors($environment_results) {
135     global $CFG;
136     $errors = array();
138     // Iterate over each environment_result
139     foreach ($environment_results as $environment_result) {
140         $type = $environment_result->getPart();
141         $info = $environment_result->getInfo();
142         $status = $environment_result->getStatus();
143         $error_code = $environment_result->getErrorCode();
145         $a = new stdClass();
146         if ($error_code) {
147             $a->error_code = $error_code;
148             $errors[] = array($info, get_string('environmentxmlerror', 'admin', $a));
149             return $errors;
150         }
152         /// Calculate the status value
153         if ($environment_result->getBypassStr() != '') {
154             // not interesting
155             continue;
156         } else if ($environment_result->getRestrictStr() != '') {
157             // error
158         } else {
159             if ($status) {
160                 // ok
161                 continue;
162             } else {
163                 if ($environment_result->getLevel() == 'optional') {
164                     // just a warning
165                     continue;
166                 } else {
167                     // error
168                 }
169             }
170         }
172         // We are comparing versions
173         $rec = new stdClass();
174         if ($rec->needed = $environment_result->getNeededVersion()) {
175             $rec->current = $environment_result->getCurrentVersion();
176             if ($environment_result->getLevel() == 'required') {
177                 $stringtouse = 'environmentrequireversion';
178             } else {
179                 $stringtouse = 'environmentrecommendversion';
180             }
181         // We are checking installed & enabled things
182         } else if ($environment_result->getPart() == 'custom_check') {
183             if ($environment_result->getLevel() == 'required') {
184                 $stringtouse = 'environmentrequirecustomcheck';
185             } else {
186                 $stringtouse = 'environmentrecommendcustomcheck';
187             }
188         } else if ($environment_result->getPart() == 'php_setting') {
189             if ($status) {
190                 $stringtouse = 'environmentsettingok';
191             } else if ($environment_result->getLevel() == 'required') {
192                 $stringtouse = 'environmentmustfixsetting';
193             } else {
194                 $stringtouse = 'environmentshouldfixsetting';
195             }
196         } else {
197             if ($environment_result->getLevel() == 'required') {
198                 $stringtouse = 'environmentrequireinstall';
199             } else {
200                 $stringtouse = 'environmentrecommendinstall';
201             }
202         }
203         $report = get_string($stringtouse, 'admin', $rec);
205         // Here we'll store all the feedback found
206         $feedbacktext = '';
207         // Append  the feedback if there is some
208         $feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), 'error');
209         // Append the restrict if there is some
210         $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
212         $report .= html_to_text($feedbacktext);
214         if ($environment_result->getPart() == 'custom_check'){
215             $errors[] = array($info, $report);
216         } else {
217             $errors[] = array(($info !== '' ? "$type $info" : $type), $report);
218         }
219     }
221     return $errors;
225 /**
226  * This function will normalize any version to just a serie of numbers
227  * separated by dots. Everything else will be removed.
228  *
229  * @param string $version the original version
230  * @return string the normalized version
231  */
232 function normalize_version($version) {
234 /// 1.9 Beta 2 should be read 1.9 on enviromental checks, not 1.9.2
235 /// we can discard everything after the first space
236     $version = trim($version);
237     $versionarr = explode(" ",$version);
238     if (!empty($versionarr)) {
239         $version = $versionarr[0];
240     }
241 /// Replace everything but numbers and dots by dots
242     $version = preg_replace('/[^\.\d]/', '.', $version);
243 /// Combine multiple dots in one
244     $version = preg_replace('/(\.{2,})/', '.', $version);
245 /// Trim possible leading and trailing dots
246     $version = trim($version, '.');
248     return $version;
252 /**
253  * This function will load the environment.xml file and xmlize it
254  *
255  * @staticvar array $data
256  * @uses ENV_SELECT_NEWER
257  * @uses ENV_SELECT_DATAROOT
258  * @uses ENV_SELECT_RELEASE
259  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
260  * @return mixed the xmlized structure or false on error
261  */
262 function load_environment_xml($env_select=ENV_SELECT_NEWER) {
264     global $CFG;
266     static $data = array(); // Only load and xmlize once by request.
268     if (isset($data[$env_select])) {
269         return $data[$env_select];
270     }
271     $contents = false;
273     if (is_numeric($env_select)) {
274         $file = $CFG->dataroot.'/environment/environment.xml';
275         $internalfile = $CFG->dirroot.'/'.$CFG->admin.'/environment.xml';
276         switch ($env_select) {
277             case ENV_SELECT_NEWER:
278                 if (!is_file($file) || !is_readable($file) || filemtime($file) < filemtime($internalfile) ||
279                     !$contents = file_get_contents($file)) {
280                     /// Fallback to fixed $CFG->admin/environment.xml
281                     if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
282                         $contents = false;
283                     }
284                 }
285                 break;
286             case ENV_SELECT_DATAROOT:
287                 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
288                     $contents = false;
289                 }
290                 break;
291             case ENV_SELECT_RELEASE:
292                 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
293                     $contents = false;
294                 }
295                 break;
296         }
297     } else {
298         if ($plugindir = core_component::get_component_directory($env_select)) {
299             $pluginfile = "$plugindir/environment.xml";
300             if (!is_file($pluginfile) || !is_readable($pluginfile) || !$contents = file_get_contents($pluginfile)) {
301                 $contents = false;
302             }
303         }
304     }
305     // XML the whole file.
306     if ($contents !== false) {
307         $contents = xmlize($contents);
308     }
310     $data[$env_select] = $contents;
312     return $data[$env_select];
316 /**
317  * This function will return the list of Moodle versions available
318  *
319  * @return array of versions
320  */
321 function get_list_of_environment_versions($contents) {
322     $versions = array();
324     if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
325         foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
326             $versions[] = $version['@']['version'];
327         }
328     }
330     if (isset($contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'])) {
331         $versions[] = 'all';
332     }
334     return $versions;
338 /**
339  * This function will return the most recent version in the environment.xml
340  * file previous or equal to the version requested
341  *
342  * @param string $version top version from which we start to look backwards
343  * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
344  * @return string|bool string more recent version or false if not found
345  */
346 function get_latest_version_available($version, $env_select) {
347     if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
348         throw new coding_exception('Incorrect value of $env_select parameter');
349     }
351 /// Normalize the version requested
352     $version = normalize_version($version);
354 /// Load xml file
355     if (!$contents = load_environment_xml($env_select)) {
356         return false;
357     }
359 /// Detect available versions
360     if (!$versions = get_list_of_environment_versions($contents)) {
361         return false;
362     }
363 /// First we look for exact version
364     if (in_array($version, $versions)) {
365         return $version;
366     } else {
367         $found_version = false;
368     /// Not exact match, so we are going to iterate over the list searching
369     /// for the latest version before the requested one
370         foreach ($versions as $arrversion) {
371             if (version_compare($arrversion, $version, '<')) {
372                 $found_version = $arrversion;
373             }
374         }
375     }
377     return $found_version;
381 /**
382  * This function will return the xmlized data belonging to one Moodle version
383  *
384  * @param string $version top version from which we start to look backwards
385  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
386  * @return mixed the xmlized structure or false on error
387  */
388 function get_environment_for_version($version, $env_select) {
390 /// Normalize the version requested
391     $version = normalize_version($version);
393 /// Load xml file
394     if (!$contents = load_environment_xml($env_select)) {
395         return false;
396     }
398 /// Detect available versions
399     if (!$versions = get_list_of_environment_versions($contents)) {
400         return false;
401     }
403     // If $env_select is not numeric then this is being called on a plugin, and not the core environment.xml
404     // If a version of 'all' is in the arry is also means that the new <PLUGIN> tag was found, this should
405     // be matched against any version of Moodle.
406     if (!is_numeric($env_select) && in_array('all', $versions)
407             && environment_verify_plugin($env_select, $contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'][0])) {
408         return $contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'][0];
409     }
411 /// If the version requested is available
412     if (!in_array($version, $versions)) {
413         return false;
414     }
416 /// We now we have it. Extract from full contents.
417     $fl_arr = array_flip($versions);
419     return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
422 /**
423  * Checks if a plugin tag has a name attribute and it matches the plugin being tested.
424  *
425  * @param string $plugin the name of the plugin.
426  * @param array $pluginxml the xmlised structure for the plugin tag being tested.
427  * @return boolean true if the name attribute exists and matches the plugin being tested.
428  */
429 function environment_verify_plugin($plugin, $pluginxml) {
430     if (!isset($pluginxml['@']['name']) || $pluginxml['@']['name'] != $plugin) {
431         return false;
432     }
433     return true;
436 /**
437  * This function will check for everything (DB, PHP and PHP extensions for now)
438  * returning an array of environment_result objects.
439  *
440  * @global object
441  * @param string $version xml version we are going to use to test this server
442  * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
443  * @return environment_results[] array of results encapsulated in one environment_result object
444  */
445 function environment_check($version, $env_select) {
446     global $CFG;
448     if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
449         throw new coding_exception('Incorrect value of $env_select parameter');
450     }
452 /// Normalize the version requested
453     $version = normalize_version($version);
455     $results = array(); //To store all the results
457 /// Only run the moodle versions checker on upgrade, not on install
458     if (!empty($CFG->version)) {
459         $results[] = environment_check_moodle($version, $env_select);
460     }
461     $results[] = environment_check_unicode($version, $env_select);
462     $results[] = environment_check_database($version, $env_select);
463     $results[] = environment_check_php($version, $env_select);
465     if ($result = environment_check_pcre_unicode($version, $env_select)) {
466         $results[] = $result;
467     }
469     $phpext_results = environment_check_php_extensions($version, $env_select);
470     $results = array_merge($results, $phpext_results);
472     $phpsetting_results = environment_check_php_settings($version, $env_select);
473     $results = array_merge($results, $phpsetting_results);
475     $custom_results = environment_custom_checks($version, $env_select);
476     $results = array_merge($results, $custom_results);
478     // Always use the plugin directory version of environment.xml,
479     // add-on developers need to keep those up-to-date with future info.
480     foreach (core_component::get_plugin_types() as $plugintype => $unused) {
481         foreach (core_component::get_plugin_list_with_file($plugintype, 'environment.xml') as $pluginname => $unused) {
482             $plugin = $plugintype . '_' . $pluginname;
484             $result = environment_check_database($version, $plugin);
485             if ($result->error_code != NO_VERSION_DATA_FOUND
486                 and $result->error_code != NO_DATABASE_SECTION_FOUND
487                 and $result->error_code != NO_DATABASE_VENDORS_FOUND) {
489                 $result->plugin = $plugin;
490                 $results[] = $result;
491             }
493             $result = environment_check_php($version, $plugin);
494             if ($result->error_code != NO_VERSION_DATA_FOUND
495                 and $result->error_code != NO_PHP_SECTION_FOUND
496                 and $result->error_code != NO_PHP_VERSION_FOUND) {
498                 $result->plugin = $plugin;
499                 $results[] = $result;
500             }
502             $pluginresults = environment_check_php_extensions($version, $plugin);
503             foreach ($pluginresults as $result) {
504                 if ($result->error_code != NO_VERSION_DATA_FOUND
505                     and $result->error_code != NO_PHP_EXTENSIONS_SECTION_FOUND) {
507                     $result->plugin = $plugin;
508                     $results[] = $result;
509                 }
510             }
512             $pluginresults = environment_check_php_settings($version, $plugin);
513             foreach ($pluginresults as $result) {
514                 if ($result->error_code != NO_VERSION_DATA_FOUND) {
515                     $result->plugin = $plugin;
516                     $results[] = $result;
517                 }
518             }
520             $pluginresults = environment_custom_checks($version, $plugin);
521             foreach ($pluginresults as $result) {
522                 if ($result->error_code != NO_VERSION_DATA_FOUND) {
523                     $result->plugin = $plugin;
524                     $results[] = $result;
525                 }
526             }
527         }
528     }
530     return $results;
534 /**
535  * This function will check if php extensions requirements are satisfied
536  *
537  * @uses NO_VERSION_DATA_FOUND
538  * @uses NO_PHP_EXTENSIONS_SECTION_FOUND
539  * @uses NO_PHP_EXTENSIONS_NAME_FOUND
540  * @param string $version xml version we are going to use to test this server
541  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
542  * @return array array of results encapsulated in one environment_result object
543  */
544 function environment_check_php_extensions($version, $env_select) {
546     $results = array();
548 /// Get the enviroment version we need
549     if (!$data = get_environment_for_version($version, $env_select)) {
550     /// Error. No version data found
551         $result = new environment_results('php_extension');
552         $result->setStatus(false);
553         $result->setErrorCode(NO_VERSION_DATA_FOUND);
554         return array($result);
555     }
557 /// Extract the php_extension part
558     if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
559     /// Error. No PHP section found
560         $result = new environment_results('php_extension');
561         $result->setStatus(false);
562         $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
563         return array($result);
564     }
565 /// Iterate over extensions checking them and creating the needed environment_results
566     foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
567         $result = new environment_results('php_extension');
568     /// Check for level
569         $level = get_level($extension);
570     /// Check for extension name
571         if (!isset($extension['@']['name'])) {
572             $result->setStatus(false);
573             $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
574         } else {
575             $extension_name = $extension['@']['name'];
576         /// The name exists. Just check if it's an installed extension
577             if (!extension_loaded($extension_name)) {
578                 $result->setStatus(false);
579             } else {
580                 $result->setStatus(true);
581             }
582             $result->setLevel($level);
583             $result->setInfo($extension_name);
584         }
586     /// Do any actions defined in the XML file.
587         process_environment_result($extension, $result);
589     /// Add the result to the array of results
590         $results[] = $result;
591     }
594     return $results;
597 /**
598  * This function will check if php extensions requirements are satisfied
599  *
600  * @uses NO_VERSION_DATA_FOUND
601  * @uses NO_PHP_SETTINGS_NAME_FOUND
602  * @param string $version xml version we are going to use to test this server
603  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
604  * @return array array of results encapsulated in one environment_result object
605  */
606 function environment_check_php_settings($version, $env_select) {
608     $results = array();
610 /// Get the enviroment version we need
611     if (!$data = get_environment_for_version($version, $env_select)) {
612     /// Error. No version data found
613         $result = new environment_results('php_setting');
614         $result->setStatus(false);
615         $result->setErrorCode(NO_VERSION_DATA_FOUND);
616         $results[] = $result;
617         return $results;
618     }
620 /// Extract the php_setting part
621     if (!isset($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'])) {
622     /// No PHP section found - ignore
623         return $results;
624     }
625 /// Iterate over settings checking them and creating the needed environment_results
626     foreach($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'] as $setting) {
627         $result = new environment_results('php_setting');
628     /// Check for level
629         $level = get_level($setting);
630         $result->setLevel($level);
631     /// Check for extension name
632         if (!isset($setting['@']['name'])) {
633             $result->setStatus(false);
634             $result->setErrorCode(NO_PHP_SETTINGS_NAME_FOUND);
635         } else {
636             $setting_name  = $setting['@']['name'];
637             $setting_value = $setting['@']['value'];
638             $result->setInfo($setting_name);
640             if ($setting_name == 'memory_limit') {
641                 $current = ini_get('memory_limit');
642                 if ($current == -1) {
643                     $result->setStatus(true);
644                 } else {
645                     $current  = get_real_size($current);
646                     $minlimit = get_real_size($setting_value);
647                     if ($current < $minlimit) {
648                         @ini_set('memory_limit', $setting_value);
649                         $current = ini_get('memory_limit');
650                         $current = get_real_size($current);
651                     }
652                     $result->setStatus($current >= $minlimit);
653                 }
655             } else {
656                 $current = ini_get_bool($setting_name);
657             /// The name exists. Just check if it's an installed extension
658                 if ($current == $setting_value) {
659                     $result->setStatus(true);
660                 } else {
661                     $result->setStatus(false);
662                 }
663             }
664         }
666     /// Do any actions defined in the XML file.
667         process_environment_result($setting, $result);
669     /// Add the result to the array of results
670         $results[] = $result;
671     }
674     return $results;
677 /**
678  * This function will do the custom checks.
679  *
680  * @uses CUSTOM_CHECK_FUNCTION_MISSING
681  * @uses CUSTOM_CHECK_FILE_MISSING
682  * @uses NO_CUSTOM_CHECK_FOUND
683  * @param string $version xml version we are going to use to test this server.
684  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
685  * @return array array of results encapsulated in environment_result objects.
686  */
687 function environment_custom_checks($version, $env_select) {
688     global $CFG;
690     $results = array();
692 /// Get current Moodle version (release) for later compare
693     $release = isset($CFG->release) ? $CFG->release : $version; /// In case $CFG fails (at install) use $version
694     $current_version = normalize_version($release);
696 /// Get the enviroment version we need
697     if (!$data = get_environment_for_version($version, $env_select)) {
698     /// Error. No version data found - but this will already have been reported.
699         return $results;
700     }
702 /// Extract the CUSTOM_CHECKS part
703     if (!isset($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'])) {
704     /// No custom checks found - not a problem
705         return $results;
706     }
708 /// Iterate over extensions checking them and creating the needed environment_results
709     foreach($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'] as $check) {
710         $result = new environment_results('custom_check');
712     /// Check for level
713         $level = get_level($check);
715     /// Check for extension name
716         if (isset($check['@']['function'])) {
717             $function = $check['@']['function'];
718             $file = null;
719             if (isset($check['@']['file'])) {
720                 $file = $CFG->dirroot . '/' . $check['@']['file'];
721                 if (is_readable($file)) {
722                     include_once($file);
723                 }
724             }
726             if (is_callable($function)) {
727                 $result->setLevel($level);
728                 $result->setInfo($function);
729                 $result = call_user_func($function, $result);
730             } else if (!$file or is_readable($file)) {
731             /// Only show error for current version (where function MUST exist)
732             /// else, we are performing custom checks against future versiosn
733             /// and function MAY not exist, so it doesn't cause error, just skip
734             /// custom check by returning null. MDL-15939
735                 if (version_compare($current_version, $version, '>=')) {
736                     $result->setStatus(false);
737                     $result->setInfo($function);
738                     $result->setErrorCode(CUSTOM_CHECK_FUNCTION_MISSING);
739                 } else {
740                     $result = null;
741                 }
742             } else {
743             /// Only show error for current version (where function MUST exist)
744             /// else, we are performing custom checks against future versiosn
745             /// and function MAY not exist, so it doesn't cause error, just skip
746             /// custom check by returning null. MDL-15939
747                 if (version_compare($current_version, $version, '>=')) {
748                     $result->setStatus(false);
749                     $result->setInfo($function);
750                     $result->setErrorCode(CUSTOM_CHECK_FILE_MISSING);
751                 } else {
752                     $result = null;
753                 }
754             }
755         } else {
756             $result->setStatus(false);
757             $result->setErrorCode(NO_CUSTOM_CHECK_FOUND);
758         }
760         if (!is_null($result)) {
761         /// Do any actions defined in the XML file.
762             process_environment_result($check, $result);
764         /// Add the result to the array of results
765             $results[] = $result;
766         }
767     }
769     return $results;
772 /**
773  * This function will check if Moodle requirements are satisfied
774  *
775  * @uses NO_VERSION_DATA_FOUND
776  * @param string $version xml version we are going to use to test this server
777  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
778  * @return object results encapsulated in one environment_result object
779  */
780 function environment_check_moodle($version, $env_select) {
782     $result = new environment_results('moodle');
784 /// Get the enviroment version we need
785     if (!$data = get_environment_for_version($version, $env_select)) {
786     /// Error. No version data found
787         $result->setStatus(false);
788         $result->setErrorCode(NO_VERSION_DATA_FOUND);
789         return $result;
790     }
792 /// Extract the moodle part
793     if (!isset($data['@']['requires'])) {
794         $needed_version = '1.0'; /// Default to 1.0 if no moodle requires is found
795     } else {
796     /// Extract required moodle version
797         $needed_version = $data['@']['requires'];
798     }
800 /// Now search the version we are using
801     $release = get_config('', 'release');
802     $current_version = normalize_version($release);
803     if (strpos($release, 'dev') !== false) {
804         // when final version is required, dev is NOT enough!
805         $current_version = $current_version - 0.1;
806     }
808 /// And finally compare them, saving results
809     if (version_compare($current_version, $needed_version, '>=')) {
810         $result->setStatus(true);
811     } else {
812         $result->setStatus(false);
813     }
814     $result->setLevel('required');
815     $result->setCurrentVersion($release);
816     $result->setNeededVersion($needed_version);
818     return $result;
821 /**
822  * This function will check if php requirements are satisfied
823  *
824  * @uses NO_VERSION_DATA_FOUND
825  * @uses NO_PHP_SECTION_FOUND
826  * @uses NO_PHP_VERSION_FOUND
827  * @param string $version xml version we are going to use to test this server
828  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
829  * @return object results encapsulated in one environment_result object
830  */
831 function environment_check_php($version, $env_select) {
833     $result = new environment_results('php');
835 /// Get the enviroment version we need
836     if (!$data = get_environment_for_version($version, $env_select)) {
837     /// Error. No version data found
838         $result->setStatus(false);
839         $result->setErrorCode(NO_VERSION_DATA_FOUND);
840         return $result;
841     }
843 /// Extract the php part
844     if (!isset($data['#']['PHP'])) {
845     /// Error. No PHP section found
846         $result->setStatus(false);
847         $result->setErrorCode(NO_PHP_SECTION_FOUND);
848         return $result;
849     } else {
850     /// Extract level and version
851         $level = get_level($data['#']['PHP']['0']);
852         if (!isset($data['#']['PHP']['0']['@']['version'])) {
853             $result->setStatus(false);
854             $result->setErrorCode(NO_PHP_VERSION_FOUND);
855             return $result;
856         } else {
857             $needed_version = $data['#']['PHP']['0']['@']['version'];
858         }
859     }
861 /// Now search the version we are using
862     $current_version = normalize_version(phpversion());
864 /// And finally compare them, saving results
865     if (version_compare($current_version, $needed_version, '>=')) {
866         $result->setStatus(true);
867     } else {
868         $result->setStatus(false);
869     }
870     $result->setLevel($level);
871     $result->setCurrentVersion($current_version);
872     $result->setNeededVersion($needed_version);
874 /// Do any actions defined in the XML file.
875     process_environment_result($data['#']['PHP'][0], $result);
877     return $result;
880 /**
881  * Looks for buggy PCRE implementation, we need unicode support in Moodle...
882  * @param string $version xml version we are going to use to test this server
883  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
884  * @return stdClass results encapsulated in one environment_result object, null if irrelevant
885  */
886 function environment_check_pcre_unicode($version, $env_select) {
887     $result = new environment_results('pcreunicode');
889     // Get the environment version we need
890     if (!$data = get_environment_for_version($version, $env_select)) {
891         // Error. No version data found!
892         $result->setStatus(false);
893         $result->setErrorCode(NO_VERSION_DATA_FOUND);
894         return $result;
895     }
897     if (!isset($data['#']['PCREUNICODE'])) {
898         return null;
899     }
901     $level = get_level($data['#']['PCREUNICODE']['0']);
902     $result->setLevel($level);
904     if (!function_exists('preg_match')) {
905         // The extension test fails instead.
906         return null;
908     } else if (@preg_match('/\pL/u', 'a') and @preg_match('/á/iu', 'Á')) {
909         $result->setStatus(true);
911     } else {
912         $result->setStatus(false);
913     }
915     // Do any actions defined in the XML file.
916     process_environment_result($data['#']['PCREUNICODE'][0], $result);
918     return $result;
921 /**
922  * This function will check if unicode database requirements are satisfied
923  *
924  * @uses NO_VERSION_DATA_FOUND
925  * @uses NO_UNICODE_SECTION_FOUND
926  * @param string $version xml version we are going to use to test this server
927  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
928  * @return object results encapsulated in one environment_result object
929  */
930 function environment_check_unicode($version, $env_select) {
931     global $DB;
933     $result = new environment_results('unicode');
935     /// Get the enviroment version we need
936     if (!$data = get_environment_for_version($version, $env_select)) {
937     /// Error. No version data found
938         $result->setStatus(false);
939         $result->setErrorCode(NO_VERSION_DATA_FOUND);
940         return $result;
941     }
943     /// Extract the unicode part
945     if (!isset($data['#']['UNICODE'])) {
946     /// Error. No UNICODE section found
947         $result->setStatus(false);
948         $result->setErrorCode(NO_UNICODE_SECTION_FOUND);
949         return $result;
950     } else {
951     /// Extract level
952         $level = get_level($data['#']['UNICODE']['0']);
953     }
955     if (!$unicodedb = $DB->setup_is_unicodedb()) {
956         $result->setStatus(false);
957     } else {
958         $result->setStatus(true);
959     }
961     $result->setLevel($level);
963 /// Do any actions defined in the XML file.
964     process_environment_result($data['#']['UNICODE'][0], $result);
966     return $result;
969 /**
970  * This function will check if database requirements are satisfied
971  *
972  * @uses NO_VERSION_DATA_FOUND
973  * @uses NO_DATABASE_SECTION_FOUND
974  * @uses NO_DATABASE_VENDORS_FOUND
975  * @uses NO_DATABASE_VENDOR_MYSQL_FOUND
976  * @uses NO_DATABASE_VENDOR_POSTGRES_FOUND
977  * @uses NO_DATABASE_VENDOR_VERSION_FOUND
978  * @param string $version xml version we are going to use to test this server
979  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
980  * @return object results encapsulated in one environment_result object
981  */
982 function environment_check_database($version, $env_select) {
984     global $DB;
986     $result = new environment_results('database');
988     $vendors = array();  //Array of vendors in version
990 /// Get the enviroment version we need
991     if (!$data = get_environment_for_version($version, $env_select)) {
992     /// Error. No version data found
993         $result->setStatus(false);
994         $result->setErrorCode(NO_VERSION_DATA_FOUND);
995         return $result;
996     }
998 /// Extract the database part
999     if (!isset($data['#']['DATABASE'])) {
1000     /// Error. No DATABASE section found
1001         $result->setStatus(false);
1002         $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
1003         return $result;
1004     } else {
1005     /// Extract level
1006         $level = get_level($data['#']['DATABASE']['0']);
1007     }
1009 /// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
1010     if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
1011     /// Error. No VENDORS found
1012         $result->setStatus(false);
1013         $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
1014         return $result;
1015     } else {
1016     /// Extract vendors
1017         foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
1018             if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
1019                 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
1020                 $vendorsxml[$vendor['@']['name']] = $vendor;
1021             }
1022         }
1023     }
1024 /// Check we have the mysql vendor version
1025     if (empty($vendors['mysql'])) {
1026         $result->setStatus(false);
1027         $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
1028         return $result;
1029     }
1030 /// Check we have the postgres vendor version
1031     if (empty($vendors['postgres'])) {
1032         $result->setStatus(false);
1033         $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
1034         return $result;
1035     }
1037 /// Now search the version we are using (depending of vendor)
1038     $current_vendor = $DB->get_dbvendor();
1040     $dbinfo = $DB->get_server_info();
1041     $current_version = normalize_version($dbinfo['version']);
1042     $needed_version = $vendors[$current_vendor];
1044 /// Check we have a needed version
1045     if (!$needed_version) {
1046         $result->setStatus(false);
1047         $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND);
1048         return $result;
1049     }
1051     // Check if the DB Vendor has been properly configured.
1052     // Hack: this is required when playing with MySQL and MariaDB since they share the same PHP module and base DB classes,
1053     // whilst they are slowly evolving using separate directions though MariaDB is still an "almost" drop-in replacement.
1054     $dbvendorismysql = ($current_vendor === 'mysql');
1055     $dbtypeismariadb = (stripos($dbinfo['description'], 'mariadb') !== false);
1056     if ($dbvendorismysql && $dbtypeismariadb) {
1057         $result->setStatus(false);
1058         $result->setLevel($level);
1059         $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1060         $result->setFeedbackStr('environmentmariadbwrongdbtype');
1061         return $result;
1062     }
1064 /// And finally compare them, saving results
1065     if (version_compare($current_version, $needed_version, '>=')) {
1066         $result->setStatus(true);
1067     } else {
1068         $result->setStatus(false);
1069     }
1070     $result->setLevel($level);
1071     $result->setCurrentVersion($current_version);
1072     $result->setNeededVersion($needed_version);
1073     $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1075 /// Do any actions defined in the XML file.
1076     process_environment_result($vendorsxml[$current_vendor], $result);
1078     return $result;
1082 /**
1083  * This function will post-process the result record by executing the specified
1084  * function, modifying it as necessary, also a custom message will be added
1085  * to the result object to be printed by the display layer.
1086  * Every bypass function must be defined in this file and it'll return
1087  * true/false to decide if the original test is bypassed or no. Also
1088  * such bypass functions are able to directly handling the result object
1089  * although it should be only under exceptional conditions.
1090  *
1091  * @param string xmldata containing the bypass data
1092  * @param object result object to be updated
1093  * @return void
1094  */
1095 function process_environment_bypass($xml, &$result) {
1097 /// Only try to bypass if we were in error and it was required
1098     if ($result->getStatus() || $result->getLevel() == 'optional') {
1099         return;
1100     }
1102 /// It there is bypass info (function and message)
1103     if (is_array($xml['#']) && isset($xml['#']['BYPASS'][0]['@']['function']) && isset($xml['#']['BYPASS'][0]['@']['message'])) {
1104         $function = $xml['#']['BYPASS'][0]['@']['function'];
1105         $message  = $xml['#']['BYPASS'][0]['@']['message'];
1106     /// Look for the function
1107         if (function_exists($function)) {
1108         /// Call it, and if bypass = true is returned, apply meesage
1109             if ($function($result)) {
1110             /// We only set the bypass message if the function itself hasn't defined it before
1111                 if (empty($result->getBypassStr)) {
1112                     if (isset($xml['#']['BYPASS'][0]['@']['plugin'])) {
1113                         $result->setBypassStr(array($message, $xml['#']['BYPASS'][0]['@']['plugin']));
1114                     } else {
1115                         $result->setBypassStr($message);
1116                     }
1117                 }
1118             }
1119         }
1120     }
1123 /**
1124  * This function will post-process the result record by executing the specified
1125  * function, modifying it as necessary, also a custom message will be added
1126  * to the result object to be printed by the display layer.
1127  * Every restrict function must be defined in this file and it'll return
1128  * true/false to decide if the original test is restricted or no. Also
1129  * such restrict functions are able to directly handling the result object
1130  * although it should be only under exceptional conditions.
1131  *
1132  * @param string xmldata containing the restrict data
1133  * @param object result object to be updated
1134  * @return void
1135  */
1136 function process_environment_restrict($xml, &$result) {
1138 /// Only try to restrict if we were not in error and it was required
1139     if (!$result->getStatus() || $result->getLevel() == 'optional') {
1140         return;
1141     }
1142 /// It there is restrict info (function and message)
1143     if (is_array($xml['#']) && isset($xml['#']['RESTRICT'][0]['@']['function']) && isset($xml['#']['RESTRICT'][0]['@']['message'])) {
1144         $function = $xml['#']['RESTRICT'][0]['@']['function'];
1145         $message  = $xml['#']['RESTRICT'][0]['@']['message'];
1146     /// Look for the function
1147         if (function_exists($function)) {
1148         /// Call it, and if restrict = true is returned, apply meesage
1149             if ($function($result)) {
1150             /// We only set the restrict message if the function itself hasn't defined it before
1151                 if (empty($result->getRestrictStr)) {
1152                     if (isset($xml['#']['RESTRICT'][0]['@']['plugin'])) {
1153                         $result->setRestrictStr(array($message, $xml['#']['RESTRICT'][0]['@']['plugin']));
1154                     } else {
1155                         $result->setRestrictStr($message);
1156                     }
1157                 }
1158             }
1159         }
1160     }
1163 /**
1164  * This function will detect if there is some message available to be added to the
1165  * result in order to clarify enviromental details.
1166  *
1167  * @uses INCORRECT_FEEDBACK_FOR_REQUIRED
1168  * @uses INCORRECT_FEEDBACK_FOR_OPTIONAL
1169  * @param string xmldata containing the feedback data
1170  * @param object reult object to be updated
1171  */
1172 function process_environment_messages($xml, &$result) {
1174 /// If there is feedback info
1175     if (is_array($xml['#']) && isset($xml['#']['FEEDBACK'][0]['#'])) {
1176         $feedbackxml = $xml['#']['FEEDBACK'][0]['#'];
1178         // Detect some incorrect feedback combinations.
1179         if ($result->getLevel() == 'required' and isset($feedbackxml['ON_CHECK'])) {
1180             $result->setStatus(false);
1181             $result->setErrorCode(INCORRECT_FEEDBACK_FOR_REQUIRED);
1182         } else if ($result->getLevel() == 'optional' and isset($feedbackxml['ON_ERROR'])) {
1183             $result->setStatus(false);
1184             $result->setErrorCode(INCORRECT_FEEDBACK_FOR_OPTIONAL);
1185         }
1187         if (!$result->status and $result->getLevel() == 'required') {
1188             if (isset($feedbackxml['ON_ERROR'][0]['@']['message'])) {
1189                 if (isset($feedbackxml['ON_ERROR'][0]['@']['plugin'])) {
1190                     $result->setFeedbackStr(array($feedbackxml['ON_ERROR'][0]['@']['message'], $feedbackxml['ON_ERROR'][0]['@']['plugin']));
1191                 } else {
1192                     $result->setFeedbackStr($feedbackxml['ON_ERROR'][0]['@']['message']);
1193                 }
1194             }
1195         } else if (!$result->status and $result->getLevel() == 'optional') {
1196             if (isset($feedbackxml['ON_CHECK'][0]['@']['message'])) {
1197                 if (isset($feedbackxml['ON_CHECK'][0]['@']['plugin'])) {
1198                     $result->setFeedbackStr(array($feedbackxml['ON_CHECK'][0]['@']['message'], $feedbackxml['ON_CHECK'][0]['@']['plugin']));
1199                 } else {
1200                     $result->setFeedbackStr($feedbackxml['ON_CHECK'][0]['@']['message']);
1201                 }
1202             }
1203         } else {
1204             if (isset($feedbackxml['ON_OK'][0]['@']['message'])) {
1205                 if (isset($feedbackxml['ON_OK'][0]['@']['plugin'])) {
1206                     $result->setFeedbackStr(array($feedbackxml['ON_OK'][0]['@']['message'], $feedbackxml['ON_OK'][0]['@']['plugin']));
1207                 } else {
1208                     $result->setFeedbackStr($feedbackxml['ON_OK'][0]['@']['message']);
1209                 }
1210             }
1211         }
1212     }
1216 //--- Helper Class to return results to caller ---//
1219 /**
1220  * Helper Class to return results to caller
1221  *
1222  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1223  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1224  * @package moodlecore
1225  */
1226 class environment_results {
1227     /**
1228      * @var string Which are we checking (database, php, php_extension, php_extension)
1229      */
1230     var $part;
1231     /**
1232      * @var bool true means the test passed and all is OK. false means it failed.
1233      */
1234     var $status;
1235     /**
1236      * @var integer See constants at the beginning of the file
1237      */
1238     var $error_code;
1239     /**
1240      * @var string required/optional
1241      */
1242     var $level;
1243     /**
1244      * @var string current version detected
1245      */
1246     var $current_version;
1247     /**
1248      * @var string version needed
1249      */
1250     var $needed_version;
1251     /**
1252      * @var string Aux. info (DB vendor, library...)
1253      */
1254     var $info;
1255     /**
1256      * @var string String to show on error|on check|on ok
1257      */
1258     var $feedback_str;
1259     /**
1260      * @var string String to show if some bypass has happened
1261      */
1262     var $bypass_str;
1263     /**
1264      * @var string String to show if some restrict has happened
1265      */
1266     var $restrict_str;
1267     /**
1268      * @var string|null full plugin name or null if main environment
1269      */
1270     var $plugin = null;
1271     /**
1272      * Constructor of the environment_result class. Just set default values
1273      *
1274      * @param string $part
1275      */
1276     public function __construct($part) {
1277         $this->part=$part;
1278         $this->status=false;
1279         $this->error_code=NO_ERROR;
1280         $this->level='required';
1281         $this->current_version='';
1282         $this->needed_version='';
1283         $this->info='';
1284         $this->feedback_str='';
1285         $this->bypass_str='';
1286         $this->restrict_str='';
1287     }
1289     /**
1290      * Old syntax of class constructor. Deprecated in PHP7.
1291      *
1292      * @deprecated since Moodle 3.1
1293      */
1294     public function environment_results($part) {
1295         debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
1296         self::__construct($part);
1297     }
1299     /**
1300      * Set the status
1301      *
1302      * @param bool $testpassed true means the test passed and all is OK. false means it failed.
1303      */
1304     function setStatus($testpassed) {
1305         $this->status = $testpassed;
1306         if ($testpassed) {
1307             $this->setErrorCode(NO_ERROR);
1308         }
1309     }
1311     /**
1312      * Set the error_code
1313      *
1314      * @param integer $error_code the error code (see constants above)
1315      */
1316     function setErrorCode($error_code) {
1317         $this->error_code=$error_code;
1318     }
1320     /**
1321      * Set the level
1322      *
1323      * @param string $level the level (required, optional)
1324      */
1325     function setLevel($level) {
1326         $this->level=$level;
1327     }
1329     /**
1330      * Set the current version
1331      *
1332      * @param string $current_version the current version
1333      */
1334     function setCurrentVersion($current_version) {
1335         $this->current_version=$current_version;
1336     }
1338     /**
1339      * Set the needed version
1340      *
1341      * @param string $needed_version the needed version
1342      */
1343     function setNeededVersion($needed_version) {
1344         $this->needed_version=$needed_version;
1345     }
1347     /**
1348      * Set the auxiliary info
1349      *
1350      * @param string $info the auxiliary info
1351      */
1352     function setInfo($info) {
1353         $this->info=$info;
1354     }
1356     /**
1357      * Set the feedback string
1358      *
1359      * @param mixed $str the feedback string that will be fetched from the admin lang file.
1360      *                  pass just the string or pass an array of params for get_string
1361      *                  You always should put your string in admin.php but a third param is useful
1362      *                  to pass an $a object / string to get_string
1363      */
1364     function setFeedbackStr($str) {
1365         $this->feedback_str=$str;
1366     }
1369     /**
1370      * Set the bypass string
1371      *
1372      * @param string $str the bypass string that will be fetched from the admin lang file.
1373      *                  pass just the string or pass an array of params for get_string
1374      *                  You always should put your string in admin.php but a third param is useful
1375      *                  to pass an $a object / string to get_string
1376      */
1377     function setBypassStr($str) {
1378         $this->bypass_str=$str;
1379     }
1381     /**
1382      * Set the restrict string
1383      *
1384      * @param string $str the restrict string that will be fetched from the admin lang file.
1385      *                  pass just the string or pass an array of params for get_string
1386      *                  You always should put your string in admin.php but a third param is useful
1387      *                  to pass an $a object / string to get_string
1388      */
1389     function setRestrictStr($str) {
1390         $this->restrict_str=$str;
1391     }
1393     /**
1394      * Get the status
1395      *
1396      * @return bool true means the test passed and all is OK. false means it failed.
1397      */
1398     function getStatus() {
1399         return $this->status;
1400     }
1402     /**
1403      * Get the error code
1404      *
1405      * @return integer error code
1406      */
1407     function getErrorCode() {
1408         return $this->error_code;
1409     }
1411     /**
1412      * Get the level
1413      *
1414      * @return string level
1415      */
1416     function getLevel() {
1417         return $this->level;
1418     }
1420     /**
1421      * Get the current version
1422      *
1423      * @return string current version
1424      */
1425     function getCurrentVersion() {
1426         return $this->current_version;
1427     }
1429     /**
1430      * Get the needed version
1431      *
1432      * @return string needed version
1433      */
1434     function getNeededVersion() {
1435         return $this->needed_version;
1436     }
1438     /**
1439      * Get the aux info
1440      *
1441      * @return string info
1442      */
1443     function getInfo() {
1444         return $this->info;
1445     }
1447     /**
1448      * Get the part this result belongs to
1449      *
1450      * @return string part
1451      */
1452     function getPart() {
1453         return $this->part;
1454     }
1456     /**
1457      * Get the feedback string
1458      *
1459      * @return mixed feedback string (can be an array of params for get_string or a single string to fetch from
1460      *                  admin.php lang file).
1461      */
1462     function getFeedbackStr() {
1463         return $this->feedback_str;
1464     }
1466     /**
1467      * Get the bypass string
1468      *
1469      * @return mixed bypass string (can be an array of params for get_string or a single string to fetch from
1470      *                  admin.php lang file).
1471      */
1472     function getBypassStr() {
1473         return $this->bypass_str;
1474     }
1476     /**
1477      * Get the restrict string
1478      *
1479      * @return mixed restrict string (can be an array of params for get_string or a single string to fetch from
1480      *                  admin.php lang file).
1481      */
1482     function getRestrictStr() {
1483         return $this->restrict_str;
1484     }
1486     /**
1487      * @todo Document this function
1488      *
1489      * @param mixed $string params for get_string, either a string to fetch from admin.php or an array of
1490      *                       params for get_string.
1491      * @param string $class css class(es) for message.
1492      * @return string feedback string fetched from lang file wrapped in p tag with class $class or returns
1493      *                              empty string if $string is empty.
1494      */
1495     function strToReport($string, $class){
1496         if (!empty($string)){
1497             if (is_array($string)){
1498                 $str = call_user_func_array('get_string', $string);
1499             } else {
1500                 $str = get_string($string, 'admin');
1501             }
1502             return '<p class="'.$class.'">'.$str.'</p>';
1503         } else {
1504             return '';
1505         }
1506     }
1508     /**
1509      * Get plugin name.
1510      *
1511      * @return string plugin name
1512      */
1513     function getPluginName() {
1514         if ($this->plugin) {
1515             $manager = core_plugin_manager::instance();
1516             list($plugintype, $pluginname) = core_component::normalize_component($this->plugin);
1517             return $manager->plugintype_name($plugintype) . ' / ' . $manager->plugin_name($this->plugin);
1518         } else {
1519             return '';
1520         }
1521     }
1524 /// Here all the restrict functions are coded to be used by the environment
1525 /// checker. All those functions will receive the result object and will
1526 /// return it modified as needed (status and bypass string)
1528 /**
1529  * @param array $element the element from the environment.xml file that should have
1530  *      either a level="required" or level="optional" attribute.
1531  * @return string "required" or "optional".
1532  */
1533 function get_level($element) {
1534     $level = 'required';
1535     if (isset($element['@']['level'])) {
1536         $level = $element['@']['level'];
1537         if (!in_array($level, array('required', 'optional'))) {
1538             debugging('The level of a check in the environment.xml file must be "required" or "optional".', DEBUG_DEVELOPER);
1539             $level = 'required';
1540         }
1541     } else {
1542         debugging('Checks in the environment.xml file must have a level="required" or level="optional" attribute.', DEBUG_DEVELOPER);
1543     }
1544     return $level;
1547 /**
1548  * Once the result has been determined, look in the XML for any
1549  * messages, or other things that should be done depending on the outcome.
1550  *
1551  * @param array $element the element from the environment.xml file which
1552  *      may have children defining what should be done with the outcome.
1553  * @param object $result the result of the test, which may be modified by
1554  *      this function as specified in the XML.
1555  */
1556 function process_environment_result($element, &$result) {
1557 /// Process messages, modifying the $result if needed.
1558     process_environment_messages($element, $result);
1559 /// Process bypass, modifying $result if needed.
1560     process_environment_bypass($element, $result);
1561 /// Process restrict, modifying $result if needed.
1562     process_environment_restrict($element, $result);
1565 /**
1566  * Check if the current PHP version is greater than or equal to
1567  * PHP version 7.
1568  *
1569  * @param object $result an environment_results instance
1570  * @return bool result of version check
1571  */
1572 function restrict_php_version_7(&$result) {
1573     return restrict_php_version($result, '7');
1576 /**
1577  * Check if the current PHP version is greater than or equal to an
1578  * unsupported version.
1579  *
1580  * @param object $result an environment_results instance
1581  * @param string $version the version of PHP that can't be used
1582  * @return bool result of version check
1583  */
1584 function restrict_php_version(&$result, $version) {
1586     // Get the current PHP version.
1587     $currentversion = normalize_version(phpversion());
1589     // Confirm we're using a supported PHP version.
1590     if (version_compare($currentversion, $version, '<')) {
1591         // Everything is ok, the restriction doesn't apply.
1592         return false;
1593     } else {
1594         // We're using an unsupported PHP version, apply restriction.
1595         return true;
1596     }
1599 /**
1600  * Check if the current PHP version is greater than or equal to
1601  * PHP version 7.1.
1602  *
1603  * @param object $result an environment_results instance
1604  * @return bool result of version check
1605  */
1606 function restrict_php_version_71(&$result) {
1607     return restrict_php_version($result, '7.1');
1610 /**
1611  * Check if the current PHP version is greater than or equal to
1612  * PHP version 7.2.
1613  *
1614  * @param object $result an environment_results instance
1615  * @return bool result of version check
1616  */
1617 function restrict_php_version_72(&$result) {
1618     return restrict_php_version($result, '7.2');
1621 /**
1622  * Check if the current PHP version is greater than or equal to
1623  * PHP version 7.3.
1624  *
1625  * @param object $result an environment_results instance
1626  * @return bool result of version check
1627  */
1628 function restrict_php_version_73(&$result) {
1629     return restrict_php_version($result, '7.3');