MDL-69475 install: Version, env. and minimal changes before branching
[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         $plugin = $environment_result->getPluginName();
144         $error_code = $environment_result->getErrorCode();
146         $a = new stdClass();
147         if ($error_code) {
148             $a->error_code = $error_code;
149             $errors[] = array($info, get_string('environmentxmlerror', 'admin', $a));
150             return $errors;
151         }
153         /// Calculate the status value
154         if ($environment_result->getBypassStr() != '') {
155             // not interesting
156             continue;
157         } else if ($environment_result->getRestrictStr() != '') {
158             // error
159         } else {
160             if ($status) {
161                 // ok
162                 continue;
163             } else {
164                 if ($environment_result->getLevel() == 'optional') {
165                     // just a warning
166                     continue;
167                 } else {
168                     // error
169                 }
170             }
171         }
173         // We are comparing versions
174         $rec = new stdClass();
175         if ($rec->needed = $environment_result->getNeededVersion()) {
176             $rec->current = $environment_result->getCurrentVersion();
177             if ($environment_result->getLevel() == 'required') {
178                 $stringtouse = 'environmentrequireversion';
179             } else {
180                 $stringtouse = 'environmentrecommendversion';
181             }
182         // We are checking installed & enabled things
183         } else if ($environment_result->getPart() == 'custom_check') {
184             if ($environment_result->getLevel() == 'required') {
185                 $stringtouse = 'environmentrequirecustomcheck';
186             } else {
187                 $stringtouse = 'environmentrecommendcustomcheck';
188             }
189         } else if ($environment_result->getPart() == 'php_setting') {
190             if ($status) {
191                 $stringtouse = 'environmentsettingok';
192             } else if ($environment_result->getLevel() == 'required') {
193                 $stringtouse = 'environmentmustfixsetting';
194             } else {
195                 $stringtouse = 'environmentshouldfixsetting';
196             }
197         } else {
198             if ($environment_result->getLevel() == 'required') {
199                 $stringtouse = 'environmentrequireinstall';
200             } else {
201                 $stringtouse = 'environmentrecommendinstall';
202             }
203         }
204         $report = get_string($stringtouse, 'admin', $rec);
206         // Here we'll store all the feedback found
207         $feedbacktext = '';
208         // Append  the feedback if there is some
209         $feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), 'error');
210         // Append the restrict if there is some
211         $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
213         if ($plugin === '') {
214             $report = '[' . get_string('coresystem') . '] ' . $report;
215         } else {
216             $report = '[' . $plugin . '] ' . $report;
217         }
219         $report .= ' - ' . html_to_text($feedbacktext);
221         if ($environment_result->getPart() == 'custom_check'){
222             $errors[] = array($info, $report);
223         } else {
224             $errors[] = array(($info !== '' ? "$type $info" : $type), $report);
225         }
226     }
228     return $errors;
232 /**
233  * This function will normalize any version to just a serie of numbers
234  * separated by dots. Everything else will be removed.
235  *
236  * @param string $version the original version
237  * @return string the normalized version
238  */
239 function normalize_version($version) {
241 /// 1.9 Beta 2 should be read 1.9 on enviromental checks, not 1.9.2
242 /// we can discard everything after the first space
243     $version = trim($version);
244     $versionarr = explode(" ",$version);
245     if (!empty($versionarr)) {
246         $version = $versionarr[0];
247     }
248 /// Replace everything but numbers and dots by dots
249     $version = preg_replace('/[^\.\d]/', '.', $version);
250 /// Combine multiple dots in one
251     $version = preg_replace('/(\.{2,})/', '.', $version);
252 /// Trim possible leading and trailing dots
253     $version = trim($version, '.');
255     return $version;
259 /**
260  * This function will load the environment.xml file and xmlize it
261  *
262  * @staticvar array $data
263  * @uses ENV_SELECT_NEWER
264  * @uses ENV_SELECT_DATAROOT
265  * @uses ENV_SELECT_RELEASE
266  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
267  * @return mixed the xmlized structure or false on error
268  */
269 function load_environment_xml($env_select=ENV_SELECT_NEWER) {
271     global $CFG;
273     static $data = array(); // Only load and xmlize once by request.
275     if (isset($data[$env_select])) {
276         return $data[$env_select];
277     }
278     $contents = false;
280     if (is_numeric($env_select)) {
281         $file = $CFG->dataroot.'/environment/environment.xml';
282         $internalfile = $CFG->dirroot.'/'.$CFG->admin.'/environment.xml';
283         switch ($env_select) {
284             case ENV_SELECT_NEWER:
285                 if (!is_file($file) || !is_readable($file) || filemtime($file) < filemtime($internalfile) ||
286                     !$contents = file_get_contents($file)) {
287                     /// Fallback to fixed $CFG->admin/environment.xml
288                     if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
289                         $contents = false;
290                     }
291                 }
292                 break;
293             case ENV_SELECT_DATAROOT:
294                 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
295                     $contents = false;
296                 }
297                 break;
298             case ENV_SELECT_RELEASE:
299                 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
300                     $contents = false;
301                 }
302                 break;
303         }
304     } else {
305         if ($plugindir = core_component::get_component_directory($env_select)) {
306             $pluginfile = "$plugindir/environment.xml";
307             if (!is_file($pluginfile) || !is_readable($pluginfile) || !$contents = file_get_contents($pluginfile)) {
308                 $contents = false;
309             }
310         }
311     }
312     // XML the whole file.
313     if ($contents !== false) {
314         $contents = xmlize($contents);
315     }
317     $data[$env_select] = $contents;
319     return $data[$env_select];
323 /**
324  * This function will return the list of Moodle versions available
325  *
326  * @return array of versions
327  */
328 function get_list_of_environment_versions($contents) {
329     $versions = array();
331     if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
332         foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
333             $versions[] = $version['@']['version'];
334         }
335     }
337     if (isset($contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'])) {
338         $versions[] = 'all';
339     }
341     return $versions;
345 /**
346  * This function will return the most recent version in the environment.xml
347  * file previous or equal to the version requested
348  *
349  * @param string $version top version from which we start to look backwards
350  * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
351  * @return string|bool string more recent version or false if not found
352  */
353 function get_latest_version_available($version, $env_select) {
354     if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
355         throw new coding_exception('Incorrect value of $env_select parameter');
356     }
358 /// Normalize the version requested
359     $version = normalize_version($version);
361 /// Load xml file
362     if (!$contents = load_environment_xml($env_select)) {
363         return false;
364     }
366 /// Detect available versions
367     if (!$versions = get_list_of_environment_versions($contents)) {
368         return false;
369     }
370 /// First we look for exact version
371     if (in_array($version, $versions, true)) {
372         return $version;
373     } else {
374         $found_version = false;
375     /// Not exact match, so we are going to iterate over the list searching
376     /// for the latest version before the requested one
377         foreach ($versions as $arrversion) {
378             if (version_compare($arrversion, $version, '<')) {
379                 $found_version = $arrversion;
380             }
381         }
382     }
384     return $found_version;
388 /**
389  * This function will return the xmlized data belonging to one Moodle version
390  *
391  * @param string $version top version from which we start to look backwards
392  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
393  * @return mixed the xmlized structure or false on error
394  */
395 function get_environment_for_version($version, $env_select) {
397 /// Normalize the version requested
398     $version = normalize_version($version);
400 /// Load xml file
401     if (!$contents = load_environment_xml($env_select)) {
402         return false;
403     }
405 /// Detect available versions
406     if (!$versions = get_list_of_environment_versions($contents)) {
407         return false;
408     }
410     // If $env_select is not numeric then this is being called on a plugin, and not the core environment.xml
411     // If a version of 'all' is in the arry is also means that the new <PLUGIN> tag was found, this should
412     // be matched against any version of Moodle.
413     if (!is_numeric($env_select) && in_array('all', $versions)
414             && environment_verify_plugin($env_select, $contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'][0])) {
415         return $contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'][0];
416     }
418 /// If the version requested is available
419     if (!in_array($version, $versions, true)) {
420         return false;
421     }
423 /// We now we have it. Extract from full contents.
424     $fl_arr = array_flip($versions);
426     return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
429 /**
430  * Checks if a plugin tag has a name attribute and it matches the plugin being tested.
431  *
432  * @param string $plugin the name of the plugin.
433  * @param array $pluginxml the xmlised structure for the plugin tag being tested.
434  * @return boolean true if the name attribute exists and matches the plugin being tested.
435  */
436 function environment_verify_plugin($plugin, $pluginxml) {
437     if (!isset($pluginxml['@']['name']) || $pluginxml['@']['name'] != $plugin) {
438         return false;
439     }
440     return true;
443 /**
444  * This function will check for everything (DB, PHP and PHP extensions for now)
445  * returning an array of environment_result objects.
446  *
447  * @global object
448  * @param string $version xml version we are going to use to test this server
449  * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
450  * @return environment_results[] array of results encapsulated in one environment_result object
451  */
452 function environment_check($version, $env_select) {
453     global $CFG;
455     if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
456         throw new coding_exception('Incorrect value of $env_select parameter');
457     }
459 /// Normalize the version requested
460     $version = normalize_version($version);
462     $results = array(); //To store all the results
464 /// Only run the moodle versions checker on upgrade, not on install
465     if (!empty($CFG->version)) {
466         $results[] = environment_check_moodle($version, $env_select);
467     }
468     $results[] = environment_check_unicode($version, $env_select);
469     $results[] = environment_check_database($version, $env_select);
470     $results[] = environment_check_php($version, $env_select);
472     if ($result = environment_check_pcre_unicode($version, $env_select)) {
473         $results[] = $result;
474     }
476     $phpext_results = environment_check_php_extensions($version, $env_select);
477     $results = array_merge($results, $phpext_results);
479     $phpsetting_results = environment_check_php_settings($version, $env_select);
480     $results = array_merge($results, $phpsetting_results);
482     $custom_results = environment_custom_checks($version, $env_select);
483     $results = array_merge($results, $custom_results);
485     // Always use the plugin directory version of environment.xml,
486     // add-on developers need to keep those up-to-date with future info.
487     foreach (core_component::get_plugin_types() as $plugintype => $unused) {
488         foreach (core_component::get_plugin_list_with_file($plugintype, 'environment.xml') as $pluginname => $unused) {
489             $plugin = $plugintype . '_' . $pluginname;
491             $result = environment_check_database($version, $plugin);
492             if ($result->error_code != NO_VERSION_DATA_FOUND
493                 and $result->error_code != NO_DATABASE_SECTION_FOUND
494                 and $result->error_code != NO_DATABASE_VENDORS_FOUND) {
496                 $result->plugin = $plugin;
497                 $results[] = $result;
498             }
500             $result = environment_check_php($version, $plugin);
501             if ($result->error_code != NO_VERSION_DATA_FOUND
502                 and $result->error_code != NO_PHP_SECTION_FOUND
503                 and $result->error_code != NO_PHP_VERSION_FOUND) {
505                 $result->plugin = $plugin;
506                 $results[] = $result;
507             }
509             $pluginresults = environment_check_php_extensions($version, $plugin);
510             foreach ($pluginresults as $result) {
511                 if ($result->error_code != NO_VERSION_DATA_FOUND
512                     and $result->error_code != NO_PHP_EXTENSIONS_SECTION_FOUND) {
514                     $result->plugin = $plugin;
515                     $results[] = $result;
516                 }
517             }
519             $pluginresults = environment_check_php_settings($version, $plugin);
520             foreach ($pluginresults as $result) {
521                 if ($result->error_code != NO_VERSION_DATA_FOUND) {
522                     $result->plugin = $plugin;
523                     $results[] = $result;
524                 }
525             }
527             $pluginresults = environment_custom_checks($version, $plugin);
528             foreach ($pluginresults as $result) {
529                 if ($result->error_code != NO_VERSION_DATA_FOUND) {
530                     $result->plugin = $plugin;
531                     $results[] = $result;
532                 }
533             }
534         }
535     }
537     return $results;
541 /**
542  * This function will check if php extensions requirements are satisfied
543  *
544  * @uses NO_VERSION_DATA_FOUND
545  * @uses NO_PHP_EXTENSIONS_SECTION_FOUND
546  * @uses NO_PHP_EXTENSIONS_NAME_FOUND
547  * @param string $version xml version we are going to use to test this server
548  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
549  * @return array array of results encapsulated in one environment_result object
550  */
551 function environment_check_php_extensions($version, $env_select) {
553     $results = array();
555 /// Get the enviroment version we need
556     if (!$data = get_environment_for_version($version, $env_select)) {
557     /// Error. No version data found
558         $result = new environment_results('php_extension');
559         $result->setStatus(false);
560         $result->setErrorCode(NO_VERSION_DATA_FOUND);
561         return array($result);
562     }
564 /// Extract the php_extension part
565     if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
566     /// Error. No PHP section found
567         $result = new environment_results('php_extension');
568         $result->setStatus(false);
569         $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
570         return array($result);
571     }
572 /// Iterate over extensions checking them and creating the needed environment_results
573     foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
574         $result = new environment_results('php_extension');
575     /// Check for level
576         $level = get_level($extension);
577     /// Check for extension name
578         if (!isset($extension['@']['name'])) {
579             $result->setStatus(false);
580             $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
581         } else {
582             $extension_name = $extension['@']['name'];
583         /// The name exists. Just check if it's an installed extension
584             if (!extension_loaded($extension_name)) {
585                 $result->setStatus(false);
586             } else {
587                 $result->setStatus(true);
588             }
589             $result->setLevel($level);
590             $result->setInfo($extension_name);
591         }
593     /// Do any actions defined in the XML file.
594         process_environment_result($extension, $result);
596     /// Add the result to the array of results
597         $results[] = $result;
598     }
601     return $results;
604 /**
605  * This function will check if php extensions requirements are satisfied
606  *
607  * @uses NO_VERSION_DATA_FOUND
608  * @uses NO_PHP_SETTINGS_NAME_FOUND
609  * @param string $version xml version we are going to use to test this server
610  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
611  * @return array array of results encapsulated in one environment_result object
612  */
613 function environment_check_php_settings($version, $env_select) {
615     $results = array();
617 /// Get the enviroment version we need
618     if (!$data = get_environment_for_version($version, $env_select)) {
619     /// Error. No version data found
620         $result = new environment_results('php_setting');
621         $result->setStatus(false);
622         $result->setErrorCode(NO_VERSION_DATA_FOUND);
623         $results[] = $result;
624         return $results;
625     }
627 /// Extract the php_setting part
628     if (!isset($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'])) {
629     /// No PHP section found - ignore
630         return $results;
631     }
632 /// Iterate over settings checking them and creating the needed environment_results
633     foreach($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'] as $setting) {
634         $result = new environment_results('php_setting');
635     /// Check for level
636         $level = get_level($setting);
637         $result->setLevel($level);
638     /// Check for extension name
639         if (!isset($setting['@']['name'])) {
640             $result->setStatus(false);
641             $result->setErrorCode(NO_PHP_SETTINGS_NAME_FOUND);
642         } else {
643             $setting_name  = $setting['@']['name'];
644             $setting_value = $setting['@']['value'];
645             $result->setInfo($setting_name);
647             if ($setting_name == 'memory_limit') {
648                 $current = ini_get('memory_limit');
649                 if ($current == -1) {
650                     $result->setStatus(true);
651                 } else {
652                     $current  = get_real_size($current);
653                     $minlimit = get_real_size($setting_value);
654                     if ($current < $minlimit) {
655                         @ini_set('memory_limit', $setting_value);
656                         $current = ini_get('memory_limit');
657                         $current = get_real_size($current);
658                     }
659                     $result->setStatus($current >= $minlimit);
660                 }
662             } else {
663                 $current = ini_get_bool($setting_name);
664             /// The name exists. Just check if it's an installed extension
665                 if ($current == $setting_value) {
666                     $result->setStatus(true);
667                 } else {
668                     $result->setStatus(false);
669                 }
670             }
671         }
673     /// Do any actions defined in the XML file.
674         process_environment_result($setting, $result);
676     /// Add the result to the array of results
677         $results[] = $result;
678     }
681     return $results;
684 /**
685  * This function will do the custom checks.
686  *
687  * @uses CUSTOM_CHECK_FUNCTION_MISSING
688  * @uses CUSTOM_CHECK_FILE_MISSING
689  * @uses NO_CUSTOM_CHECK_FOUND
690  * @param string $version xml version we are going to use to test this server.
691  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
692  * @return array array of results encapsulated in environment_result objects.
693  */
694 function environment_custom_checks($version, $env_select) {
695     global $CFG;
697     $results = array();
699 /// Get current Moodle version (release) for later compare
700     $release = isset($CFG->release) ? $CFG->release : $version; /// In case $CFG fails (at install) use $version
701     $current_version = normalize_version($release);
703 /// Get the enviroment version we need
704     if (!$data = get_environment_for_version($version, $env_select)) {
705     /// Error. No version data found - but this will already have been reported.
706         return $results;
707     }
709 /// Extract the CUSTOM_CHECKS part
710     if (!isset($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'])) {
711     /// No custom checks found - not a problem
712         return $results;
713     }
715 /// Iterate over extensions checking them and creating the needed environment_results
716     foreach($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'] as $check) {
717         $result = new environment_results('custom_check');
719     /// Check for level
720         $level = get_level($check);
722     /// Check for extension name
723         if (isset($check['@']['function'])) {
724             $function = $check['@']['function'];
725             $file = null;
726             if (isset($check['@']['file'])) {
727                 $file = $CFG->dirroot . '/' . $check['@']['file'];
728                 if (is_readable($file)) {
729                     include_once($file);
730                 }
731             }
733             if (is_callable($function)) {
734                 $result->setLevel($level);
735                 $result->setInfo($function);
736                 $result = call_user_func($function, $result);
737             } else if (!$file or is_readable($file)) {
738             /// Only show error for current version (where function MUST exist)
739             /// else, we are performing custom checks against future versiosn
740             /// and function MAY not exist, so it doesn't cause error, just skip
741             /// custom check by returning null. MDL-15939
742                 if (version_compare($current_version, $version, '>=')) {
743                     $result->setStatus(false);
744                     $result->setInfo($function);
745                     $result->setErrorCode(CUSTOM_CHECK_FUNCTION_MISSING);
746                 } else {
747                     $result = null;
748                 }
749             } else {
750             /// Only show error for current version (where function MUST exist)
751             /// else, we are performing custom checks against future versiosn
752             /// and function MAY not exist, so it doesn't cause error, just skip
753             /// custom check by returning null. MDL-15939
754                 if (version_compare($current_version, $version, '>=')) {
755                     $result->setStatus(false);
756                     $result->setInfo($function);
757                     $result->setErrorCode(CUSTOM_CHECK_FILE_MISSING);
758                 } else {
759                     $result = null;
760                 }
761             }
762         } else {
763             $result->setStatus(false);
764             $result->setErrorCode(NO_CUSTOM_CHECK_FOUND);
765         }
767         if (!is_null($result)) {
768         /// Do any actions defined in the XML file.
769             process_environment_result($check, $result);
771         /// Add the result to the array of results
772             $results[] = $result;
773         }
774     }
776     return $results;
779 /**
780  * This function will check if Moodle requirements are satisfied
781  *
782  * @uses NO_VERSION_DATA_FOUND
783  * @param string $version xml version we are going to use to test this server
784  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
785  * @return object results encapsulated in one environment_result object
786  */
787 function environment_check_moodle($version, $env_select) {
789     $result = new environment_results('moodle');
791 /// Get the enviroment version we need
792     if (!$data = get_environment_for_version($version, $env_select)) {
793     /// Error. No version data found
794         $result->setStatus(false);
795         $result->setErrorCode(NO_VERSION_DATA_FOUND);
796         return $result;
797     }
799 /// Extract the moodle part
800     if (!isset($data['@']['requires'])) {
801         $needed_version = '1.0'; /// Default to 1.0 if no moodle requires is found
802     } else {
803     /// Extract required moodle version
804         $needed_version = $data['@']['requires'];
805     }
807 /// Now search the version we are using
808     $release = get_config('', 'release');
809     $current_version = normalize_version($release);
810     if (strpos($release, 'dev') !== false) {
811         // When final version is required, dev is NOT enough so, at all effects
812         // it's like we are running the previous version.
813         $versionarr = explode('.', $current_version);
814         if (isset($versionarr[1]) and $versionarr[1] > 0) {
815             $versionarr[1]--;
816             $current_version = implode('.', $versionarr);
817         } else {
818             $current_version = $current_version - 0.1;
819         }
820     }
822 /// And finally compare them, saving results
823     if (version_compare($current_version, $needed_version, '>=')) {
824         $result->setStatus(true);
825     } else {
826         $result->setStatus(false);
827     }
828     $result->setLevel('required');
829     $result->setCurrentVersion($release);
830     $result->setNeededVersion($needed_version);
832     return $result;
835 /**
836  * This function will check if php requirements are satisfied
837  *
838  * @uses NO_VERSION_DATA_FOUND
839  * @uses NO_PHP_SECTION_FOUND
840  * @uses NO_PHP_VERSION_FOUND
841  * @param string $version xml version we are going to use to test this server
842  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
843  * @return object results encapsulated in one environment_result object
844  */
845 function environment_check_php($version, $env_select) {
847     $result = new environment_results('php');
849 /// Get the enviroment version we need
850     if (!$data = get_environment_for_version($version, $env_select)) {
851     /// Error. No version data found
852         $result->setStatus(false);
853         $result->setErrorCode(NO_VERSION_DATA_FOUND);
854         return $result;
855     }
857 /// Extract the php part
858     if (!isset($data['#']['PHP'])) {
859     /// Error. No PHP section found
860         $result->setStatus(false);
861         $result->setErrorCode(NO_PHP_SECTION_FOUND);
862         return $result;
863     } else {
864     /// Extract level and version
865         $level = get_level($data['#']['PHP']['0']);
866         if (!isset($data['#']['PHP']['0']['@']['version'])) {
867             $result->setStatus(false);
868             $result->setErrorCode(NO_PHP_VERSION_FOUND);
869             return $result;
870         } else {
871             $needed_version = $data['#']['PHP']['0']['@']['version'];
872         }
873     }
875 /// Now search the version we are using
876     $current_version = normalize_version(phpversion());
878 /// And finally compare them, saving results
879     if (version_compare($current_version, $needed_version, '>=')) {
880         $result->setStatus(true);
881     } else {
882         $result->setStatus(false);
883     }
884     $result->setLevel($level);
885     $result->setCurrentVersion($current_version);
886     $result->setNeededVersion($needed_version);
888 /// Do any actions defined in the XML file.
889     process_environment_result($data['#']['PHP'][0], $result);
891     return $result;
894 /**
895  * Looks for buggy PCRE implementation, we need unicode support in Moodle...
896  * @param string $version xml version we are going to use to test this server
897  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
898  * @return stdClass results encapsulated in one environment_result object, null if irrelevant
899  */
900 function environment_check_pcre_unicode($version, $env_select) {
901     $result = new environment_results('pcreunicode');
903     // Get the environment version we need
904     if (!$data = get_environment_for_version($version, $env_select)) {
905         // Error. No version data found!
906         $result->setStatus(false);
907         $result->setErrorCode(NO_VERSION_DATA_FOUND);
908         return $result;
909     }
911     if (!isset($data['#']['PCREUNICODE'])) {
912         return null;
913     }
915     $level = get_level($data['#']['PCREUNICODE']['0']);
916     $result->setLevel($level);
918     if (!function_exists('preg_match')) {
919         // The extension test fails instead.
920         return null;
922     } else if (@preg_match('/\pL/u', 'a') and @preg_match('/á/iu', 'Á')) {
923         $result->setStatus(true);
925     } else {
926         $result->setStatus(false);
927     }
929     // Do any actions defined in the XML file.
930     process_environment_result($data['#']['PCREUNICODE'][0], $result);
932     return $result;
935 /**
936  * This function will check if unicode database requirements are satisfied
937  *
938  * @uses NO_VERSION_DATA_FOUND
939  * @uses NO_UNICODE_SECTION_FOUND
940  * @param string $version xml version we are going to use to test this server
941  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
942  * @return object results encapsulated in one environment_result object
943  */
944 function environment_check_unicode($version, $env_select) {
945     global $DB;
947     $result = new environment_results('unicode');
949     /// Get the enviroment version we need
950     if (!$data = get_environment_for_version($version, $env_select)) {
951     /// Error. No version data found
952         $result->setStatus(false);
953         $result->setErrorCode(NO_VERSION_DATA_FOUND);
954         return $result;
955     }
957     /// Extract the unicode part
959     if (!isset($data['#']['UNICODE'])) {
960     /// Error. No UNICODE section found
961         $result->setStatus(false);
962         $result->setErrorCode(NO_UNICODE_SECTION_FOUND);
963         return $result;
964     } else {
965     /// Extract level
966         $level = get_level($data['#']['UNICODE']['0']);
967     }
969     if (!$unicodedb = $DB->setup_is_unicodedb()) {
970         $result->setStatus(false);
971     } else {
972         $result->setStatus(true);
973     }
975     $result->setLevel($level);
977 /// Do any actions defined in the XML file.
978     process_environment_result($data['#']['UNICODE'][0], $result);
980     return $result;
983 /**
984  * This function will check if database requirements are satisfied
985  *
986  * @uses NO_VERSION_DATA_FOUND
987  * @uses NO_DATABASE_SECTION_FOUND
988  * @uses NO_DATABASE_VENDORS_FOUND
989  * @uses NO_DATABASE_VENDOR_MYSQL_FOUND
990  * @uses NO_DATABASE_VENDOR_POSTGRES_FOUND
991  * @uses NO_DATABASE_VENDOR_VERSION_FOUND
992  * @param string $version xml version we are going to use to test this server
993  * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
994  * @return object results encapsulated in one environment_result object
995  */
996 function environment_check_database($version, $env_select) {
998     global $DB;
1000     $result = new environment_results('database');
1002     $vendors = array();  //Array of vendors in version
1004 /// Get the enviroment version we need
1005     if (!$data = get_environment_for_version($version, $env_select)) {
1006     /// Error. No version data found
1007         $result->setStatus(false);
1008         $result->setErrorCode(NO_VERSION_DATA_FOUND);
1009         return $result;
1010     }
1012 /// Extract the database part
1013     if (!isset($data['#']['DATABASE'])) {
1014     /// Error. No DATABASE section found
1015         $result->setStatus(false);
1016         $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
1017         return $result;
1018     } else {
1019     /// Extract level
1020         $level = get_level($data['#']['DATABASE']['0']);
1021     }
1023 /// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
1024     if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
1025     /// Error. No VENDORS found
1026         $result->setStatus(false);
1027         $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
1028         return $result;
1029     } else {
1030     /// Extract vendors
1031         foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
1032             if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
1033                 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
1034                 $vendorsxml[$vendor['@']['name']] = $vendor;
1035             }
1036         }
1037     }
1038 /// Check we have the mysql vendor version
1039     if (empty($vendors['mysql'])) {
1040         $result->setStatus(false);
1041         $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
1042         return $result;
1043     }
1044 /// Check we have the postgres vendor version
1045     if (empty($vendors['postgres'])) {
1046         $result->setStatus(false);
1047         $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
1048         return $result;
1049     }
1051 /// Now search the version we are using (depending of vendor)
1052     $current_vendor = $DB->get_dbvendor();
1054     $dbinfo = $DB->get_server_info();
1055     $current_version = normalize_version($dbinfo['version']);
1056     $needed_version = $vendors[$current_vendor];
1058 /// Check we have a needed version
1059     if (!$needed_version) {
1060         $result->setStatus(false);
1061         $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND);
1062         return $result;
1063     }
1065     // Check if the DB Vendor has been properly configured.
1066     // Hack: this is required when playing with MySQL and MariaDB since they share the same PHP module and base DB classes,
1067     // whilst they are slowly evolving using separate directions though MariaDB is still an "almost" drop-in replacement.
1068     $dbvendorismysql = ($current_vendor === 'mysql');
1069     $dbtypeismariadb = (stripos($dbinfo['description'], 'mariadb') !== false);
1070     if ($dbvendorismysql && $dbtypeismariadb) {
1071         $result->setStatus(false);
1072         $result->setLevel($level);
1073         $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1074         $result->setFeedbackStr('environmentmariadbwrongdbtype');
1075         return $result;
1076     }
1078 /// And finally compare them, saving results
1079     if (version_compare($current_version, $needed_version, '>=')) {
1080         $result->setStatus(true);
1081     } else {
1082         $result->setStatus(false);
1083     }
1084     $result->setLevel($level);
1085     $result->setCurrentVersion($current_version);
1086     $result->setNeededVersion($needed_version);
1087     $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1089 /// Do any actions defined in the XML file.
1090     process_environment_result($vendorsxml[$current_vendor], $result);
1092     return $result;
1096 /**
1097  * This function will post-process the result record by executing the specified
1098  * function, modifying it as necessary, also a custom message will be added
1099  * to the result object to be printed by the display layer.
1100  * Every bypass function must be defined in this file and it'll return
1101  * true/false to decide if the original test is bypassed or no. Also
1102  * such bypass functions are able to directly handling the result object
1103  * although it should be only under exceptional conditions.
1104  *
1105  * @param string xmldata containing the bypass data
1106  * @param object result object to be updated
1107  * @return void
1108  */
1109 function process_environment_bypass($xml, &$result) {
1111 /// Only try to bypass if we were in error and it was required
1112     if ($result->getStatus() || $result->getLevel() == 'optional') {
1113         return;
1114     }
1116 /// It there is bypass info (function and message)
1117     if (is_array($xml['#']) && isset($xml['#']['BYPASS'][0]['@']['function']) && isset($xml['#']['BYPASS'][0]['@']['message'])) {
1118         $function = $xml['#']['BYPASS'][0]['@']['function'];
1119         $message  = $xml['#']['BYPASS'][0]['@']['message'];
1120     /// Look for the function
1121         if (function_exists($function)) {
1122         /// Call it, and if bypass = true is returned, apply meesage
1123             if ($function($result)) {
1124             /// We only set the bypass message if the function itself hasn't defined it before
1125                 if (empty($result->getBypassStr)) {
1126                     if (isset($xml['#']['BYPASS'][0]['@']['plugin'])) {
1127                         $result->setBypassStr(array($message, $xml['#']['BYPASS'][0]['@']['plugin']));
1128                     } else {
1129                         $result->setBypassStr($message);
1130                     }
1131                 }
1132             }
1133         }
1134     }
1137 /**
1138  * This function will post-process the result record by executing the specified
1139  * function, modifying it as necessary, also a custom message will be added
1140  * to the result object to be printed by the display layer.
1141  * Every restrict function must be defined in this file and it'll return
1142  * true/false to decide if the original test is restricted or no. Also
1143  * such restrict functions are able to directly handling the result object
1144  * although it should be only under exceptional conditions.
1145  *
1146  * @param string xmldata containing the restrict data
1147  * @param object result object to be updated
1148  * @return void
1149  */
1150 function process_environment_restrict($xml, &$result) {
1152 /// Only try to restrict if we were not in error and it was required
1153     if (!$result->getStatus() || $result->getLevel() == 'optional') {
1154         return;
1155     }
1156 /// It there is restrict info (function and message)
1157     if (is_array($xml['#']) && isset($xml['#']['RESTRICT'][0]['@']['function']) && isset($xml['#']['RESTRICT'][0]['@']['message'])) {
1158         $function = $xml['#']['RESTRICT'][0]['@']['function'];
1159         $message  = $xml['#']['RESTRICT'][0]['@']['message'];
1160     /// Look for the function
1161         if (function_exists($function)) {
1162         /// Call it, and if restrict = true is returned, apply meesage
1163             if ($function($result)) {
1164             /// We only set the restrict message if the function itself hasn't defined it before
1165                 if (empty($result->getRestrictStr)) {
1166                     if (isset($xml['#']['RESTRICT'][0]['@']['plugin'])) {
1167                         $result->setRestrictStr(array($message, $xml['#']['RESTRICT'][0]['@']['plugin']));
1168                     } else {
1169                         $result->setRestrictStr($message);
1170                     }
1171                 }
1172             }
1173         }
1174     }
1177 /**
1178  * This function will detect if there is some message available to be added to the
1179  * result in order to clarify enviromental details.
1180  *
1181  * @uses INCORRECT_FEEDBACK_FOR_REQUIRED
1182  * @uses INCORRECT_FEEDBACK_FOR_OPTIONAL
1183  * @param string xmldata containing the feedback data
1184  * @param object reult object to be updated
1185  */
1186 function process_environment_messages($xml, &$result) {
1188 /// If there is feedback info
1189     if (is_array($xml['#']) && isset($xml['#']['FEEDBACK'][0]['#'])) {
1190         $feedbackxml = $xml['#']['FEEDBACK'][0]['#'];
1192         // Detect some incorrect feedback combinations.
1193         if ($result->getLevel() == 'required' and isset($feedbackxml['ON_CHECK'])) {
1194             $result->setStatus(false);
1195             $result->setErrorCode(INCORRECT_FEEDBACK_FOR_REQUIRED);
1196         } else if ($result->getLevel() == 'optional' and isset($feedbackxml['ON_ERROR'])) {
1197             $result->setStatus(false);
1198             $result->setErrorCode(INCORRECT_FEEDBACK_FOR_OPTIONAL);
1199         }
1201         if (!$result->status and $result->getLevel() == 'required') {
1202             if (isset($feedbackxml['ON_ERROR'][0]['@']['message'])) {
1203                 if (isset($feedbackxml['ON_ERROR'][0]['@']['plugin'])) {
1204                     $result->setFeedbackStr(array($feedbackxml['ON_ERROR'][0]['@']['message'], $feedbackxml['ON_ERROR'][0]['@']['plugin']));
1205                 } else {
1206                     $result->setFeedbackStr($feedbackxml['ON_ERROR'][0]['@']['message']);
1207                 }
1208             }
1209         } else if (!$result->status and $result->getLevel() == 'optional') {
1210             if (isset($feedbackxml['ON_CHECK'][0]['@']['message'])) {
1211                 if (isset($feedbackxml['ON_CHECK'][0]['@']['plugin'])) {
1212                     $result->setFeedbackStr(array($feedbackxml['ON_CHECK'][0]['@']['message'], $feedbackxml['ON_CHECK'][0]['@']['plugin']));
1213                 } else {
1214                     $result->setFeedbackStr($feedbackxml['ON_CHECK'][0]['@']['message']);
1215                 }
1216             }
1217         } else {
1218             if (isset($feedbackxml['ON_OK'][0]['@']['message'])) {
1219                 if (isset($feedbackxml['ON_OK'][0]['@']['plugin'])) {
1220                     $result->setFeedbackStr(array($feedbackxml['ON_OK'][0]['@']['message'], $feedbackxml['ON_OK'][0]['@']['plugin']));
1221                 } else {
1222                     $result->setFeedbackStr($feedbackxml['ON_OK'][0]['@']['message']);
1223                 }
1224             }
1225         }
1226     }
1230 //--- Helper Class to return results to caller ---//
1233 /**
1234  * Helper Class to return results to caller
1235  *
1236  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1237  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1238  * @package moodlecore
1239  */
1240 class environment_results {
1241     /**
1242      * @var string Which are we checking (database, php, php_extension, php_extension)
1243      */
1244     var $part;
1245     /**
1246      * @var bool true means the test passed and all is OK. false means it failed.
1247      */
1248     var $status;
1249     /**
1250      * @var integer See constants at the beginning of the file
1251      */
1252     var $error_code;
1253     /**
1254      * @var string required/optional
1255      */
1256     var $level;
1257     /**
1258      * @var string current version detected
1259      */
1260     var $current_version;
1261     /**
1262      * @var string version needed
1263      */
1264     var $needed_version;
1265     /**
1266      * @var string Aux. info (DB vendor, library...)
1267      */
1268     var $info;
1269     /**
1270      * @var string String to show on error|on check|on ok
1271      */
1272     var $feedback_str;
1273     /**
1274      * @var string String to show if some bypass has happened
1275      */
1276     var $bypass_str;
1277     /**
1278      * @var string String to show if some restrict has happened
1279      */
1280     var $restrict_str;
1281     /**
1282      * @var string|null full plugin name or null if main environment
1283      */
1284     var $plugin = null;
1285     /**
1286      * Constructor of the environment_result class. Just set default values
1287      *
1288      * @param string $part
1289      */
1290     public function __construct($part) {
1291         $this->part=$part;
1292         $this->status=false;
1293         $this->error_code=NO_ERROR;
1294         $this->level='required';
1295         $this->current_version='';
1296         $this->needed_version='';
1297         $this->info='';
1298         $this->feedback_str='';
1299         $this->bypass_str='';
1300         $this->restrict_str='';
1301     }
1303     /**
1304      * Old syntax of class constructor. Deprecated in PHP7.
1305      *
1306      * @deprecated since Moodle 3.1
1307      */
1308     public function environment_results($part) {
1309         debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
1310         self::__construct($part);
1311     }
1313     /**
1314      * Set the status
1315      *
1316      * @param bool $testpassed true means the test passed and all is OK. false means it failed.
1317      */
1318     function setStatus($testpassed) {
1319         $this->status = $testpassed;
1320         if ($testpassed) {
1321             $this->setErrorCode(NO_ERROR);
1322         }
1323     }
1325     /**
1326      * Set the error_code
1327      *
1328      * @param integer $error_code the error code (see constants above)
1329      */
1330     function setErrorCode($error_code) {
1331         $this->error_code=$error_code;
1332     }
1334     /**
1335      * Set the level
1336      *
1337      * @param string $level the level (required, optional)
1338      */
1339     function setLevel($level) {
1340         $this->level=$level;
1341     }
1343     /**
1344      * Set the current version
1345      *
1346      * @param string $current_version the current version
1347      */
1348     function setCurrentVersion($current_version) {
1349         $this->current_version=$current_version;
1350     }
1352     /**
1353      * Set the needed version
1354      *
1355      * @param string $needed_version the needed version
1356      */
1357     function setNeededVersion($needed_version) {
1358         $this->needed_version=$needed_version;
1359     }
1361     /**
1362      * Set the auxiliary info
1363      *
1364      * @param string $info the auxiliary info
1365      */
1366     function setInfo($info) {
1367         $this->info=$info;
1368     }
1370     /**
1371      * Set the feedback string
1372      *
1373      * @param mixed $str the feedback string that will be fetched from the admin lang file.
1374      *                  pass just the string or pass an array of params for get_string
1375      *                  You always should put your string in admin.php but a third param is useful
1376      *                  to pass an $a object / string to get_string
1377      */
1378     function setFeedbackStr($str) {
1379         $this->feedback_str=$str;
1380     }
1383     /**
1384      * Set the bypass string
1385      *
1386      * @param string $str the bypass string that will be fetched from the admin lang file.
1387      *                  pass just the string or pass an array of params for get_string
1388      *                  You always should put your string in admin.php but a third param is useful
1389      *                  to pass an $a object / string to get_string
1390      */
1391     function setBypassStr($str) {
1392         $this->bypass_str=$str;
1393     }
1395     /**
1396      * Set the restrict string
1397      *
1398      * @param string $str the restrict string that will be fetched from the admin lang file.
1399      *                  pass just the string or pass an array of params for get_string
1400      *                  You always should put your string in admin.php but a third param is useful
1401      *                  to pass an $a object / string to get_string
1402      */
1403     function setRestrictStr($str) {
1404         $this->restrict_str=$str;
1405     }
1407     /**
1408      * Get the status
1409      *
1410      * @return bool true means the test passed and all is OK. false means it failed.
1411      */
1412     function getStatus() {
1413         return $this->status;
1414     }
1416     /**
1417      * Get the error code
1418      *
1419      * @return integer error code
1420      */
1421     function getErrorCode() {
1422         return $this->error_code;
1423     }
1425     /**
1426      * Get the level
1427      *
1428      * @return string level
1429      */
1430     function getLevel() {
1431         return $this->level;
1432     }
1434     /**
1435      * Get the current version
1436      *
1437      * @return string current version
1438      */
1439     function getCurrentVersion() {
1440         return $this->current_version;
1441     }
1443     /**
1444      * Get the needed version
1445      *
1446      * @return string needed version
1447      */
1448     function getNeededVersion() {
1449         return $this->needed_version;
1450     }
1452     /**
1453      * Get the aux info
1454      *
1455      * @return string info
1456      */
1457     function getInfo() {
1458         return $this->info;
1459     }
1461     /**
1462      * Get the part this result belongs to
1463      *
1464      * @return string part
1465      */
1466     function getPart() {
1467         return $this->part;
1468     }
1470     /**
1471      * Get the feedback string
1472      *
1473      * @return mixed feedback string (can be an array of params for get_string or a single string to fetch from
1474      *                  admin.php lang file).
1475      */
1476     function getFeedbackStr() {
1477         return $this->feedback_str;
1478     }
1480     /**
1481      * Get the bypass string
1482      *
1483      * @return mixed bypass string (can be an array of params for get_string or a single string to fetch from
1484      *                  admin.php lang file).
1485      */
1486     function getBypassStr() {
1487         return $this->bypass_str;
1488     }
1490     /**
1491      * Get the restrict string
1492      *
1493      * @return mixed restrict string (can be an array of params for get_string or a single string to fetch from
1494      *                  admin.php lang file).
1495      */
1496     function getRestrictStr() {
1497         return $this->restrict_str;
1498     }
1500     /**
1501      * @todo Document this function
1502      *
1503      * @param mixed $string params for get_string, either a string to fetch from admin.php or an array of
1504      *                       params for get_string.
1505      * @param string $class css class(es) for message.
1506      * @return string feedback string fetched from lang file wrapped in p tag with class $class or returns
1507      *                              empty string if $string is empty.
1508      */
1509     function strToReport($string, $class){
1510         if (!empty($string)){
1511             if (is_array($string)){
1512                 $str = call_user_func_array('get_string', $string);
1513             } else {
1514                 $str = get_string($string, 'admin');
1515             }
1516             return '<p class="'.$class.'">'.$str.'</p>';
1517         } else {
1518             return '';
1519         }
1520     }
1522     /**
1523      * Get plugin name.
1524      *
1525      * @return string plugin name
1526      */
1527     function getPluginName() {
1528         if ($this->plugin) {
1529             $manager = core_plugin_manager::instance();
1530             list($plugintype, $pluginname) = core_component::normalize_component($this->plugin);
1531             return $manager->plugintype_name($plugintype) . ' / ' . $manager->plugin_name($this->plugin);
1532         } else {
1533             return '';
1534         }
1535     }
1538 /// Here all the restrict functions are coded to be used by the environment
1539 /// checker. All those functions will receive the result object and will
1540 /// return it modified as needed (status and bypass string)
1542 /**
1543  * @param array $element the element from the environment.xml file that should have
1544  *      either a level="required" or level="optional" attribute.
1545  * @return string "required" or "optional".
1546  */
1547 function get_level($element) {
1548     $level = 'required';
1549     if (isset($element['@']['level'])) {
1550         $level = $element['@']['level'];
1551         if (!in_array($level, array('required', 'optional'))) {
1552             debugging('The level of a check in the environment.xml file must be "required" or "optional".', DEBUG_DEVELOPER);
1553             $level = 'required';
1554         }
1555     } else {
1556         debugging('Checks in the environment.xml file must have a level="required" or level="optional" attribute.', DEBUG_DEVELOPER);
1557     }
1558     return $level;
1561 /**
1562  * Once the result has been determined, look in the XML for any
1563  * messages, or other things that should be done depending on the outcome.
1564  *
1565  * @param array $element the element from the environment.xml file which
1566  *      may have children defining what should be done with the outcome.
1567  * @param object $result the result of the test, which may be modified by
1568  *      this function as specified in the XML.
1569  */
1570 function process_environment_result($element, &$result) {
1571 /// Process messages, modifying the $result if needed.
1572     process_environment_messages($element, $result);
1573 /// Process bypass, modifying $result if needed.
1574     process_environment_bypass($element, $result);
1575 /// Process restrict, modifying $result if needed.
1576     process_environment_restrict($element, $result);
1579 /**
1580  * Check if the current PHP version is greater than or equal to
1581  * PHP version 7.
1582  *
1583  * @param object $result an environment_results instance
1584  * @return bool result of version check
1585  */
1586 function restrict_php_version_7(&$result) {
1587     return restrict_php_version($result, '7');
1590 /**
1591  * Check if the current PHP version is greater than or equal to an
1592  * unsupported version.
1593  *
1594  * @param object $result an environment_results instance
1595  * @param string $version the version of PHP that can't be used
1596  * @return bool result of version check
1597  */
1598 function restrict_php_version(&$result, $version) {
1600     // Get the current PHP version.
1601     $currentversion = normalize_version(phpversion());
1603     // Confirm we're using a supported PHP version.
1604     if (version_compare($currentversion, $version, '<')) {
1605         // Everything is ok, the restriction doesn't apply.
1606         return false;
1607     } else {
1608         // We're using an unsupported PHP version, apply restriction.
1609         return true;
1610     }
1613 /**
1614  * Check if the current PHP version is greater than or equal to
1615  * PHP version 7.1.
1616  *
1617  * @param object $result an environment_results instance
1618  * @return bool result of version check
1619  */
1620 function restrict_php_version_71(&$result) {
1621     return restrict_php_version($result, '7.1');
1624 /**
1625  * Check if the current PHP version is greater than or equal to
1626  * PHP version 7.2.
1627  *
1628  * @param object $result an environment_results instance
1629  * @return bool result of version check
1630  */
1631 function restrict_php_version_72(&$result) {
1632     return restrict_php_version($result, '7.2');
1635 /**
1636  * Check if the current PHP version is greater than or equal to
1637  * PHP version 7.3.
1638  *
1639  * @param object $result an environment_results instance
1640  * @return bool result of version check
1641  */
1642 function restrict_php_version_73(&$result) {
1643     return restrict_php_version($result, '7.3');
1646 /**
1647  * Check if the current PHP version is greater than or equal to
1648  * PHP version 7.4.
1649  *
1650  * @param object $result an environment_results instance
1651  * @return bool result of version check
1652  */
1653 function restrict_php_version_74(&$result) {
1654     return restrict_php_version($result, '7.4');