3 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
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.
23 * All the info is stored in the admin/environment.xml file,
24 * supporting to have an updated version in dataroot/environment
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
32 defined('MOODLE_INTERNAL') ||
die();
34 /// Add required files
36 * Include the necessary
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);
87 * This function checks all the requirements defined in environment.xml.
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.
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');
100 /// Get the more recent version before the requested
101 if (!$version = get_latest_version_available($version, $env_select)) {
102 return array(false, array());
105 /// Perform all the checks
106 if (!$environment_results = environment_check($version, $env_select)) {
107 return array(false, array());
110 /// Iterate over all the results looking for some error in required items
111 /// or some error_code
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()) {
125 return array($result, $environment_results);
130 * Returns array of critical errors in plain text format
131 * @param array $environment_results array of results gathered
132 * @return array errors
134 function environment_get_errors($environment_results) {
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();
148 $a->error_code
= $error_code;
149 $errors[] = array($info, get_string('environmentxmlerror', 'admin', $a));
153 /// Calculate the status value
154 if ($environment_result->getBypassStr() != '') {
157 } else if ($environment_result->getRestrictStr() != '') {
164 if ($environment_result->getLevel() == 'optional') {
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';
180 $stringtouse = 'environmentrecommendversion';
182 // We are checking installed & enabled things
183 } else if ($environment_result->getPart() == 'custom_check') {
184 if ($environment_result->getLevel() == 'required') {
185 $stringtouse = 'environmentrequirecustomcheck';
187 $stringtouse = 'environmentrecommendcustomcheck';
189 } else if ($environment_result->getPart() == 'php_setting') {
191 $stringtouse = 'environmentsettingok';
192 } else if ($environment_result->getLevel() == 'required') {
193 $stringtouse = 'environmentmustfixsetting';
195 $stringtouse = 'environmentshouldfixsetting';
198 if ($environment_result->getLevel() == 'required') {
199 $stringtouse = 'environmentrequireinstall';
201 $stringtouse = 'environmentrecommendinstall';
204 $report = get_string($stringtouse, 'admin', $rec);
206 // Here we'll store all the feedback found
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;
216 $report = '[' . $plugin . '] ' . $report;
219 $report .= ' - ' . html_to_text($feedbacktext);
221 if ($environment_result->getPart() == 'custom_check'){
222 $errors[] = array($info, $report);
224 $errors[] = array(($info !== '' ?
"$type $info" : $type), $report);
233 * This function will normalize any version to just a serie of numbers
234 * separated by dots. Everything else will be removed.
236 * @param string $version the original version
237 * @return string the normalized version
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];
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, '.');
260 * This function will load the environment.xml file and xmlize it
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
269 function load_environment_xml($env_select=ENV_SELECT_NEWER
) {
273 static $data = array(); // Only load and xmlize once by request.
275 if (isset($data[$env_select])) {
276 return $data[$env_select];
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)) {
293 case ENV_SELECT_DATAROOT
:
294 if (!is_file($file) ||
!is_readable($file) ||
!$contents = file_get_contents($file)) {
298 case ENV_SELECT_RELEASE
:
299 if (!is_file($internalfile) ||
!is_readable($internalfile) ||
!$contents = file_get_contents($internalfile)) {
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)) {
312 // XML the whole file.
313 if ($contents !== false) {
314 $contents = xmlize($contents);
317 $data[$env_select] = $contents;
319 return $data[$env_select];
324 * This function will return the list of Moodle versions available
326 * @return array of versions
328 function get_list_of_environment_versions($contents) {
331 if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
332 foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
333 $versions[] = $version['@']['version'];
337 if (isset($contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'])) {
346 * This function will return the most recent version in the environment.xml
347 * file previous or equal to the version requested
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
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');
358 /// Normalize the version requested
359 $version = normalize_version($version);
362 if (!$contents = load_environment_xml($env_select)) {
366 /// Detect available versions
367 if (!$versions = get_list_of_environment_versions($contents)) {
370 /// First we look for exact version
371 if (in_array($version, $versions)) {
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;
384 return $found_version;
389 * This function will return the xmlized data belonging to one Moodle version
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
395 function get_environment_for_version($version, $env_select) {
397 /// Normalize the version requested
398 $version = normalize_version($version);
401 if (!$contents = load_environment_xml($env_select)) {
405 /// Detect available versions
406 if (!$versions = get_list_of_environment_versions($contents)) {
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];
418 /// If the version requested is available
419 if (!in_array($version, $versions)) {
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]];
430 * Checks if a plugin tag has a name attribute and it matches the plugin being tested.
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.
436 function environment_verify_plugin($plugin, $pluginxml) {
437 if (!isset($pluginxml['@']['name']) ||
$pluginxml['@']['name'] != $plugin) {
444 * This function will check for everything (DB, PHP and PHP extensions for now)
445 * returning an array of environment_result objects.
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
452 function environment_check($version, $env_select) {
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');
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);
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;
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;
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;
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;
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;
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;
542 * This function will check if php extensions requirements are satisfied
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
551 function environment_check_php_extensions($version, $env_select) {
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);
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);
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');
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
);
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);
587 $result->setStatus(true);
589 $result->setLevel($level);
590 $result->setInfo($extension_name);
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;
605 * This function will check if php extensions requirements are satisfied
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
613 function environment_check_php_settings($version, $env_select) {
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;
627 /// Extract the php_setting part
628 if (!isset($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'])) {
629 /// No PHP section found - ignore
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');
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
);
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);
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);
659 $result->setStatus($current >= $minlimit);
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);
668 $result->setStatus(false);
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;
685 * This function will do the custom checks.
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.
694 function environment_custom_checks($version, $env_select) {
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.
709 /// Extract the CUSTOM_CHECKS part
710 if (!isset($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'])) {
711 /// No custom checks found - not a problem
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');
720 $level = get_level($check);
722 /// Check for extension name
723 if (isset($check['@']['function'])) {
724 $function = $check['@']['function'];
726 if (isset($check['@']['file'])) {
727 $file = $CFG->dirroot
. '/' . $check['@']['file'];
728 if (is_readable($file)) {
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
);
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
);
763 $result->setStatus(false);
764 $result->setErrorCode(NO_CUSTOM_CHECK_FOUND
);
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;
780 * This function will check if Moodle requirements are satisfied
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
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
);
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
803 /// Extract required moodle version
804 $needed_version = $data['@']['requires'];
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!
812 $current_version = $current_version - 0.1;
815 /// And finally compare them, saving results
816 if (version_compare($current_version, $needed_version, '>=')) {
817 $result->setStatus(true);
819 $result->setStatus(false);
821 $result->setLevel('required');
822 $result->setCurrentVersion($release);
823 $result->setNeededVersion($needed_version);
829 * This function will check if php requirements are satisfied
831 * @uses NO_VERSION_DATA_FOUND
832 * @uses NO_PHP_SECTION_FOUND
833 * @uses NO_PHP_VERSION_FOUND
834 * @param string $version xml version we are going to use to test this server
835 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
836 * @return object results encapsulated in one environment_result object
838 function environment_check_php($version, $env_select) {
840 $result = new environment_results('php');
842 /// Get the enviroment version we need
843 if (!$data = get_environment_for_version($version, $env_select)) {
844 /// Error. No version data found
845 $result->setStatus(false);
846 $result->setErrorCode(NO_VERSION_DATA_FOUND
);
850 /// Extract the php part
851 if (!isset($data['#']['PHP'])) {
852 /// Error. No PHP section found
853 $result->setStatus(false);
854 $result->setErrorCode(NO_PHP_SECTION_FOUND
);
857 /// Extract level and version
858 $level = get_level($data['#']['PHP']['0']);
859 if (!isset($data['#']['PHP']['0']['@']['version'])) {
860 $result->setStatus(false);
861 $result->setErrorCode(NO_PHP_VERSION_FOUND
);
864 $needed_version = $data['#']['PHP']['0']['@']['version'];
868 /// Now search the version we are using
869 $current_version = normalize_version(phpversion());
871 /// And finally compare them, saving results
872 if (version_compare($current_version, $needed_version, '>=')) {
873 $result->setStatus(true);
875 $result->setStatus(false);
877 $result->setLevel($level);
878 $result->setCurrentVersion($current_version);
879 $result->setNeededVersion($needed_version);
881 /// Do any actions defined in the XML file.
882 process_environment_result($data['#']['PHP'][0], $result);
888 * Looks for buggy PCRE implementation, we need unicode support in Moodle...
889 * @param string $version xml version we are going to use to test this server
890 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
891 * @return stdClass results encapsulated in one environment_result object, null if irrelevant
893 function environment_check_pcre_unicode($version, $env_select) {
894 $result = new environment_results('pcreunicode');
896 // Get the environment version we need
897 if (!$data = get_environment_for_version($version, $env_select)) {
898 // Error. No version data found!
899 $result->setStatus(false);
900 $result->setErrorCode(NO_VERSION_DATA_FOUND
);
904 if (!isset($data['#']['PCREUNICODE'])) {
908 $level = get_level($data['#']['PCREUNICODE']['0']);
909 $result->setLevel($level);
911 if (!function_exists('preg_match')) {
912 // The extension test fails instead.
915 } else if (@preg_match
('/\pL/u', 'a') and @preg_match
('/á/iu', 'Á')) {
916 $result->setStatus(true);
919 $result->setStatus(false);
922 // Do any actions defined in the XML file.
923 process_environment_result($data['#']['PCREUNICODE'][0], $result);
929 * This function will check if unicode database requirements are satisfied
931 * @uses NO_VERSION_DATA_FOUND
932 * @uses NO_UNICODE_SECTION_FOUND
933 * @param string $version xml version we are going to use to test this server
934 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
935 * @return object results encapsulated in one environment_result object
937 function environment_check_unicode($version, $env_select) {
940 $result = new environment_results('unicode');
942 /// Get the enviroment version we need
943 if (!$data = get_environment_for_version($version, $env_select)) {
944 /// Error. No version data found
945 $result->setStatus(false);
946 $result->setErrorCode(NO_VERSION_DATA_FOUND
);
950 /// Extract the unicode part
952 if (!isset($data['#']['UNICODE'])) {
953 /// Error. No UNICODE section found
954 $result->setStatus(false);
955 $result->setErrorCode(NO_UNICODE_SECTION_FOUND
);
959 $level = get_level($data['#']['UNICODE']['0']);
962 if (!$unicodedb = $DB->setup_is_unicodedb()) {
963 $result->setStatus(false);
965 $result->setStatus(true);
968 $result->setLevel($level);
970 /// Do any actions defined in the XML file.
971 process_environment_result($data['#']['UNICODE'][0], $result);
977 * This function will check if database requirements are satisfied
979 * @uses NO_VERSION_DATA_FOUND
980 * @uses NO_DATABASE_SECTION_FOUND
981 * @uses NO_DATABASE_VENDORS_FOUND
982 * @uses NO_DATABASE_VENDOR_MYSQL_FOUND
983 * @uses NO_DATABASE_VENDOR_POSTGRES_FOUND
984 * @uses NO_DATABASE_VENDOR_VERSION_FOUND
985 * @param string $version xml version we are going to use to test this server
986 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
987 * @return object results encapsulated in one environment_result object
989 function environment_check_database($version, $env_select) {
993 $result = new environment_results('database');
995 $vendors = array(); //Array of vendors in version
997 /// Get the enviroment version we need
998 if (!$data = get_environment_for_version($version, $env_select)) {
999 /// Error. No version data found
1000 $result->setStatus(false);
1001 $result->setErrorCode(NO_VERSION_DATA_FOUND
);
1005 /// Extract the database part
1006 if (!isset($data['#']['DATABASE'])) {
1007 /// Error. No DATABASE section found
1008 $result->setStatus(false);
1009 $result->setErrorCode(NO_DATABASE_SECTION_FOUND
);
1013 $level = get_level($data['#']['DATABASE']['0']);
1016 /// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
1017 if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
1018 /// Error. No VENDORS found
1019 $result->setStatus(false);
1020 $result->setErrorCode(NO_DATABASE_VENDORS_FOUND
);
1024 foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
1025 if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
1026 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
1027 $vendorsxml[$vendor['@']['name']] = $vendor;
1031 /// Check we have the mysql vendor version
1032 if (empty($vendors['mysql'])) {
1033 $result->setStatus(false);
1034 $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND
);
1037 /// Check we have the postgres vendor version
1038 if (empty($vendors['postgres'])) {
1039 $result->setStatus(false);
1040 $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND
);
1044 /// Now search the version we are using (depending of vendor)
1045 $current_vendor = $DB->get_dbvendor();
1047 $dbinfo = $DB->get_server_info();
1048 $current_version = normalize_version($dbinfo['version']);
1049 $needed_version = $vendors[$current_vendor];
1051 /// Check we have a needed version
1052 if (!$needed_version) {
1053 $result->setStatus(false);
1054 $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND
);
1058 // Check if the DB Vendor has been properly configured.
1059 // Hack: this is required when playing with MySQL and MariaDB since they share the same PHP module and base DB classes,
1060 // whilst they are slowly evolving using separate directions though MariaDB is still an "almost" drop-in replacement.
1061 $dbvendorismysql = ($current_vendor === 'mysql');
1062 $dbtypeismariadb = (stripos($dbinfo['description'], 'mariadb') !== false);
1063 if ($dbvendorismysql && $dbtypeismariadb) {
1064 $result->setStatus(false);
1065 $result->setLevel($level);
1066 $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1067 $result->setFeedbackStr('environmentmariadbwrongdbtype');
1071 /// And finally compare them, saving results
1072 if (version_compare($current_version, $needed_version, '>=')) {
1073 $result->setStatus(true);
1075 $result->setStatus(false);
1077 $result->setLevel($level);
1078 $result->setCurrentVersion($current_version);
1079 $result->setNeededVersion($needed_version);
1080 $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1082 /// Do any actions defined in the XML file.
1083 process_environment_result($vendorsxml[$current_vendor], $result);
1090 * This function will post-process the result record by executing the specified
1091 * function, modifying it as necessary, also a custom message will be added
1092 * to the result object to be printed by the display layer.
1093 * Every bypass function must be defined in this file and it'll return
1094 * true/false to decide if the original test is bypassed or no. Also
1095 * such bypass functions are able to directly handling the result object
1096 * although it should be only under exceptional conditions.
1098 * @param string xmldata containing the bypass data
1099 * @param object result object to be updated
1102 function process_environment_bypass($xml, &$result) {
1104 /// Only try to bypass if we were in error and it was required
1105 if ($result->getStatus() ||
$result->getLevel() == 'optional') {
1109 /// It there is bypass info (function and message)
1110 if (is_array($xml['#']) && isset($xml['#']['BYPASS'][0]['@']['function']) && isset($xml['#']['BYPASS'][0]['@']['message'])) {
1111 $function = $xml['#']['BYPASS'][0]['@']['function'];
1112 $message = $xml['#']['BYPASS'][0]['@']['message'];
1113 /// Look for the function
1114 if (function_exists($function)) {
1115 /// Call it, and if bypass = true is returned, apply meesage
1116 if ($function($result)) {
1117 /// We only set the bypass message if the function itself hasn't defined it before
1118 if (empty($result->getBypassStr
)) {
1119 if (isset($xml['#']['BYPASS'][0]['@']['plugin'])) {
1120 $result->setBypassStr(array($message, $xml['#']['BYPASS'][0]['@']['plugin']));
1122 $result->setBypassStr($message);
1131 * This function will post-process the result record by executing the specified
1132 * function, modifying it as necessary, also a custom message will be added
1133 * to the result object to be printed by the display layer.
1134 * Every restrict function must be defined in this file and it'll return
1135 * true/false to decide if the original test is restricted or no. Also
1136 * such restrict functions are able to directly handling the result object
1137 * although it should be only under exceptional conditions.
1139 * @param string xmldata containing the restrict data
1140 * @param object result object to be updated
1143 function process_environment_restrict($xml, &$result) {
1145 /// Only try to restrict if we were not in error and it was required
1146 if (!$result->getStatus() ||
$result->getLevel() == 'optional') {
1149 /// It there is restrict info (function and message)
1150 if (is_array($xml['#']) && isset($xml['#']['RESTRICT'][0]['@']['function']) && isset($xml['#']['RESTRICT'][0]['@']['message'])) {
1151 $function = $xml['#']['RESTRICT'][0]['@']['function'];
1152 $message = $xml['#']['RESTRICT'][0]['@']['message'];
1153 /// Look for the function
1154 if (function_exists($function)) {
1155 /// Call it, and if restrict = true is returned, apply meesage
1156 if ($function($result)) {
1157 /// We only set the restrict message if the function itself hasn't defined it before
1158 if (empty($result->getRestrictStr
)) {
1159 if (isset($xml['#']['RESTRICT'][0]['@']['plugin'])) {
1160 $result->setRestrictStr(array($message, $xml['#']['RESTRICT'][0]['@']['plugin']));
1162 $result->setRestrictStr($message);
1171 * This function will detect if there is some message available to be added to the
1172 * result in order to clarify enviromental details.
1174 * @uses INCORRECT_FEEDBACK_FOR_REQUIRED
1175 * @uses INCORRECT_FEEDBACK_FOR_OPTIONAL
1176 * @param string xmldata containing the feedback data
1177 * @param object reult object to be updated
1179 function process_environment_messages($xml, &$result) {
1181 /// If there is feedback info
1182 if (is_array($xml['#']) && isset($xml['#']['FEEDBACK'][0]['#'])) {
1183 $feedbackxml = $xml['#']['FEEDBACK'][0]['#'];
1185 // Detect some incorrect feedback combinations.
1186 if ($result->getLevel() == 'required' and isset($feedbackxml['ON_CHECK'])) {
1187 $result->setStatus(false);
1188 $result->setErrorCode(INCORRECT_FEEDBACK_FOR_REQUIRED
);
1189 } else if ($result->getLevel() == 'optional' and isset($feedbackxml['ON_ERROR'])) {
1190 $result->setStatus(false);
1191 $result->setErrorCode(INCORRECT_FEEDBACK_FOR_OPTIONAL
);
1194 if (!$result->status
and $result->getLevel() == 'required') {
1195 if (isset($feedbackxml['ON_ERROR'][0]['@']['message'])) {
1196 if (isset($feedbackxml['ON_ERROR'][0]['@']['plugin'])) {
1197 $result->setFeedbackStr(array($feedbackxml['ON_ERROR'][0]['@']['message'], $feedbackxml['ON_ERROR'][0]['@']['plugin']));
1199 $result->setFeedbackStr($feedbackxml['ON_ERROR'][0]['@']['message']);
1202 } else if (!$result->status
and $result->getLevel() == 'optional') {
1203 if (isset($feedbackxml['ON_CHECK'][0]['@']['message'])) {
1204 if (isset($feedbackxml['ON_CHECK'][0]['@']['plugin'])) {
1205 $result->setFeedbackStr(array($feedbackxml['ON_CHECK'][0]['@']['message'], $feedbackxml['ON_CHECK'][0]['@']['plugin']));
1207 $result->setFeedbackStr($feedbackxml['ON_CHECK'][0]['@']['message']);
1211 if (isset($feedbackxml['ON_OK'][0]['@']['message'])) {
1212 if (isset($feedbackxml['ON_OK'][0]['@']['plugin'])) {
1213 $result->setFeedbackStr(array($feedbackxml['ON_OK'][0]['@']['message'], $feedbackxml['ON_OK'][0]['@']['plugin']));
1215 $result->setFeedbackStr($feedbackxml['ON_OK'][0]['@']['message']);
1223 //--- Helper Class to return results to caller ---//
1227 * Helper Class to return results to caller
1229 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1230 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1231 * @package moodlecore
1233 class environment_results
{
1235 * @var string Which are we checking (database, php, php_extension, php_extension)
1239 * @var bool true means the test passed and all is OK. false means it failed.
1243 * @var integer See constants at the beginning of the file
1247 * @var string required/optional
1251 * @var string current version detected
1253 var $current_version;
1255 * @var string version needed
1257 var $needed_version;
1259 * @var string Aux. info (DB vendor, library...)
1263 * @var string String to show on error|on check|on ok
1267 * @var string String to show if some bypass has happened
1271 * @var string String to show if some restrict has happened
1275 * @var string|null full plugin name or null if main environment
1279 * Constructor of the environment_result class. Just set default values
1281 * @param string $part
1283 public function __construct($part) {
1285 $this->status
=false;
1286 $this->error_code
=NO_ERROR
;
1287 $this->level
='required';
1288 $this->current_version
='';
1289 $this->needed_version
='';
1291 $this->feedback_str
='';
1292 $this->bypass_str
='';
1293 $this->restrict_str
='';
1297 * Old syntax of class constructor. Deprecated in PHP7.
1299 * @deprecated since Moodle 3.1
1301 public function environment_results($part) {
1302 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER
);
1303 self
::__construct($part);
1309 * @param bool $testpassed true means the test passed and all is OK. false means it failed.
1311 function setStatus($testpassed) {
1312 $this->status
= $testpassed;
1314 $this->setErrorCode(NO_ERROR
);
1319 * Set the error_code
1321 * @param integer $error_code the error code (see constants above)
1323 function setErrorCode($error_code) {
1324 $this->error_code
=$error_code;
1330 * @param string $level the level (required, optional)
1332 function setLevel($level) {
1333 $this->level
=$level;
1337 * Set the current version
1339 * @param string $current_version the current version
1341 function setCurrentVersion($current_version) {
1342 $this->current_version
=$current_version;
1346 * Set the needed version
1348 * @param string $needed_version the needed version
1350 function setNeededVersion($needed_version) {
1351 $this->needed_version
=$needed_version;
1355 * Set the auxiliary info
1357 * @param string $info the auxiliary info
1359 function setInfo($info) {
1364 * Set the feedback string
1366 * @param mixed $str the feedback string that will be fetched from the admin lang file.
1367 * pass just the string or pass an array of params for get_string
1368 * You always should put your string in admin.php but a third param is useful
1369 * to pass an $a object / string to get_string
1371 function setFeedbackStr($str) {
1372 $this->feedback_str
=$str;
1377 * Set the bypass string
1379 * @param string $str the bypass string that will be fetched from the admin lang file.
1380 * pass just the string or pass an array of params for get_string
1381 * You always should put your string in admin.php but a third param is useful
1382 * to pass an $a object / string to get_string
1384 function setBypassStr($str) {
1385 $this->bypass_str
=$str;
1389 * Set the restrict string
1391 * @param string $str the restrict string that will be fetched from the admin lang file.
1392 * pass just the string or pass an array of params for get_string
1393 * You always should put your string in admin.php but a third param is useful
1394 * to pass an $a object / string to get_string
1396 function setRestrictStr($str) {
1397 $this->restrict_str
=$str;
1403 * @return bool true means the test passed and all is OK. false means it failed.
1405 function getStatus() {
1406 return $this->status
;
1410 * Get the error code
1412 * @return integer error code
1414 function getErrorCode() {
1415 return $this->error_code
;
1421 * @return string level
1423 function getLevel() {
1424 return $this->level
;
1428 * Get the current version
1430 * @return string current version
1432 function getCurrentVersion() {
1433 return $this->current_version
;
1437 * Get the needed version
1439 * @return string needed version
1441 function getNeededVersion() {
1442 return $this->needed_version
;
1448 * @return string info
1450 function getInfo() {
1455 * Get the part this result belongs to
1457 * @return string part
1459 function getPart() {
1464 * Get the feedback string
1466 * @return mixed feedback string (can be an array of params for get_string or a single string to fetch from
1467 * admin.php lang file).
1469 function getFeedbackStr() {
1470 return $this->feedback_str
;
1474 * Get the bypass string
1476 * @return mixed bypass string (can be an array of params for get_string or a single string to fetch from
1477 * admin.php lang file).
1479 function getBypassStr() {
1480 return $this->bypass_str
;
1484 * Get the restrict string
1486 * @return mixed restrict string (can be an array of params for get_string or a single string to fetch from
1487 * admin.php lang file).
1489 function getRestrictStr() {
1490 return $this->restrict_str
;
1494 * @todo Document this function
1496 * @param mixed $string params for get_string, either a string to fetch from admin.php or an array of
1497 * params for get_string.
1498 * @param string $class css class(es) for message.
1499 * @return string feedback string fetched from lang file wrapped in p tag with class $class or returns
1500 * empty string if $string is empty.
1502 function strToReport($string, $class){
1503 if (!empty($string)){
1504 if (is_array($string)){
1505 $str = call_user_func_array('get_string', $string);
1507 $str = get_string($string, 'admin');
1509 return '<p class="'.$class.'">'.$str.'</p>';
1518 * @return string plugin name
1520 function getPluginName() {
1521 if ($this->plugin
) {
1522 $manager = core_plugin_manager
::instance();
1523 list($plugintype, $pluginname) = core_component
::normalize_component($this->plugin
);
1524 return $manager->plugintype_name($plugintype) . ' / ' . $manager->plugin_name($this->plugin
);
1531 /// Here all the restrict functions are coded to be used by the environment
1532 /// checker. All those functions will receive the result object and will
1533 /// return it modified as needed (status and bypass string)
1536 * @param array $element the element from the environment.xml file that should have
1537 * either a level="required" or level="optional" attribute.
1538 * @return string "required" or "optional".
1540 function get_level($element) {
1541 $level = 'required';
1542 if (isset($element['@']['level'])) {
1543 $level = $element['@']['level'];
1544 if (!in_array($level, array('required', 'optional'))) {
1545 debugging('The level of a check in the environment.xml file must be "required" or "optional".', DEBUG_DEVELOPER
);
1546 $level = 'required';
1549 debugging('Checks in the environment.xml file must have a level="required" or level="optional" attribute.', DEBUG_DEVELOPER
);
1555 * Once the result has been determined, look in the XML for any
1556 * messages, or other things that should be done depending on the outcome.
1558 * @param array $element the element from the environment.xml file which
1559 * may have children defining what should be done with the outcome.
1560 * @param object $result the result of the test, which may be modified by
1561 * this function as specified in the XML.
1563 function process_environment_result($element, &$result) {
1564 /// Process messages, modifying the $result if needed.
1565 process_environment_messages($element, $result);
1566 /// Process bypass, modifying $result if needed.
1567 process_environment_bypass($element, $result);
1568 /// Process restrict, modifying $result if needed.
1569 process_environment_restrict($element, $result);
1573 * Check if the current PHP version is greater than or equal to
1576 * @param object $result an environment_results instance
1577 * @return bool result of version check
1579 function restrict_php_version_7(&$result) {
1580 return restrict_php_version($result, '7');
1584 * Check if the current PHP version is greater than or equal to an
1585 * unsupported version.
1587 * @param object $result an environment_results instance
1588 * @param string $version the version of PHP that can't be used
1589 * @return bool result of version check
1591 function restrict_php_version(&$result, $version) {
1593 // Get the current PHP version.
1594 $currentversion = normalize_version(phpversion());
1596 // Confirm we're using a supported PHP version.
1597 if (version_compare($currentversion, $version, '<')) {
1598 // Everything is ok, the restriction doesn't apply.
1601 // We're using an unsupported PHP version, apply restriction.
1607 * Check if the current PHP version is greater than or equal to
1610 * @param object $result an environment_results instance
1611 * @return bool result of version check
1613 function restrict_php_version_71(&$result) {
1614 return restrict_php_version($result, '7.1');
1618 * Check if the current PHP version is greater than or equal to
1621 * @param object $result an environment_results instance
1622 * @return bool result of version check
1624 function restrict_php_version_72(&$result) {
1625 return restrict_php_version($result, '7.2');
1629 * Check if the current PHP version is greater than or equal to
1632 * @param object $result an environment_results instance
1633 * @return bool result of version check
1635 function restrict_php_version_73(&$result) {
1636 return restrict_php_version($result, '7.3');
1640 * Check if the current PHP version is greater than or equal to
1643 * @param object $result an environment_results instance
1644 * @return bool result of version check
1646 function restrict_php_version_74(&$result) {
1647 return restrict_php_version($result, '7.4');