Merge branch 'MDL-49041_27' of git://github.com/timhunt/moodle into MOODLE_27_STABLE
[moodle.git] / lib / environmentlib.php
blob28f2dc9543d28495c57ce02296eaa136fb21e0b5
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.
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.
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
28 * @package core
29 * @subpackage admin
32 defined('MOODLE_INTERNAL') || die();
34 /// Add required files
35 /**
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);
86 /**
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
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;
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) {
135 global $CFG;
136 $errors = array();
138 // Iterate over each environment_result
139 foreach ($environment_results as $environment_result) {
140 $type = $environment_result->getPart();
141 $info = $environment_result->getInfo();
142 $status = $environment_result->getStatus();
143 $error_code = $environment_result->getErrorCode();
145 $a = new stdClass();
146 if ($error_code) {
147 $a->error_code = $error_code;
148 $errors[] = array($info, get_string('environmentxmlerror', 'admin', $a));
149 return $errors;
152 /// Calculate the status value
153 if ($environment_result->getBypassStr() != '') {
154 // not interesting
155 continue;
156 } else if ($environment_result->getRestrictStr() != '') {
157 // error
158 } else {
159 if ($status) {
160 // ok
161 continue;
162 } else {
163 if ($environment_result->getLevel() == 'optional') {
164 // just a warning
165 continue;
166 } else {
167 // error
172 // We are comparing versions
173 $rec = new stdClass();
174 if ($rec->needed = $environment_result->getNeededVersion()) {
175 $rec->current = $environment_result->getCurrentVersion();
176 if ($environment_result->getLevel() == 'required') {
177 $stringtouse = 'environmentrequireversion';
178 } else {
179 $stringtouse = 'environmentrecommendversion';
181 // We are checking installed & enabled things
182 } else if ($environment_result->getPart() == 'custom_check') {
183 if ($environment_result->getLevel() == 'required') {
184 $stringtouse = 'environmentrequirecustomcheck';
185 } else {
186 $stringtouse = 'environmentrecommendcustomcheck';
188 } else if ($environment_result->getPart() == 'php_setting') {
189 if ($status) {
190 $stringtouse = 'environmentsettingok';
191 } else if ($environment_result->getLevel() == 'required') {
192 $stringtouse = 'environmentmustfixsetting';
193 } else {
194 $stringtouse = 'environmentshouldfixsetting';
196 } else {
197 if ($environment_result->getLevel() == 'required') {
198 $stringtouse = 'environmentrequireinstall';
199 } else {
200 $stringtouse = 'environmentrecommendinstall';
203 $report = get_string($stringtouse, 'admin', $rec);
205 // Here we'll store all the feedback found
206 $feedbacktext = '';
207 // Append the feedback if there is some
208 $feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), 'error');
209 // Append the restrict if there is some
210 $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
212 $report .= html_to_text($feedbacktext);
214 if ($environment_result->getPart() == 'custom_check'){
215 $errors[] = array($info, $report);
216 } else {
217 $errors[] = array(($info !== '' ? "$type $info" : $type), $report);
221 return $errors;
226 * This function will normalize any version to just a serie of numbers
227 * separated by dots. Everything else will be removed.
229 * @param string $version the original version
230 * @return string the normalized version
232 function normalize_version($version) {
234 /// 1.9 Beta 2 should be read 1.9 on enviromental checks, not 1.9.2
235 /// we can discard everything after the first space
236 $version = trim($version);
237 $versionarr = explode(" ",$version);
238 if (!empty($versionarr)) {
239 $version = $versionarr[0];
241 /// Replace everything but numbers and dots by dots
242 $version = preg_replace('/[^\.\d]/', '.', $version);
243 /// Combine multiple dots in one
244 $version = preg_replace('/(\.{2,})/', '.', $version);
245 /// Trim possible leading and trailing dots
246 $version = trim($version, '.');
248 return $version;
253 * This function will load the environment.xml file and xmlize it
255 * @staticvar array $data
256 * @uses ENV_SELECT_NEWER
257 * @uses ENV_SELECT_DATAROOT
258 * @uses ENV_SELECT_RELEASE
259 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
260 * @return mixed the xmlized structure or false on error
262 function load_environment_xml($env_select=ENV_SELECT_NEWER) {
264 global $CFG;
266 static $data = array(); // Only load and xmlize once by request.
268 if (isset($data[$env_select])) {
269 return $data[$env_select];
271 $contents = false;
273 if (is_numeric($env_select)) {
274 $file = $CFG->dataroot.'/environment/environment.xml';
275 $internalfile = $CFG->dirroot.'/'.$CFG->admin.'/environment.xml';
276 switch ($env_select) {
277 case ENV_SELECT_NEWER:
278 if (!is_file($file) || !is_readable($file) || filemtime($file) < filemtime($internalfile) ||
279 !$contents = file_get_contents($file)) {
280 /// Fallback to fixed $CFG->admin/environment.xml
281 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
282 $contents = false;
285 break;
286 case ENV_SELECT_DATAROOT:
287 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
288 $contents = false;
290 break;
291 case ENV_SELECT_RELEASE:
292 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
293 $contents = false;
295 break;
297 } else {
298 if ($plugindir = core_component::get_component_directory($env_select)) {
299 $pluginfile = "$plugindir/environment.xml";
300 if (!is_file($pluginfile) || !is_readable($pluginfile) || !$contents = file_get_contents($pluginfile)) {
301 $contents = false;
305 // XML the whole file.
306 if ($contents !== false) {
307 $contents = xmlize($contents);
310 $data[$env_select] = $contents;
312 return $data[$env_select];
317 * This function will return the list of Moodle versions available
319 * @return array of versions
321 function get_list_of_environment_versions($contents) {
322 $versions = array();
324 if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
325 foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
326 $versions[] = $version['@']['version'];
330 return $versions;
335 * This function will return the most recent version in the environment.xml
336 * file previous or equal to the version requested
338 * @param string $version top version from which we start to look backwards
339 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
340 * @return string|bool string more recent version or false if not found
342 function get_latest_version_available($version, $env_select) {
343 if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
344 throw new coding_exception('Incorrect value of $env_select parameter');
347 /// Normalize the version requested
348 $version = normalize_version($version);
350 /// Load xml file
351 if (!$contents = load_environment_xml($env_select)) {
352 return false;
355 /// Detect available versions
356 if (!$versions = get_list_of_environment_versions($contents)) {
357 return false;
359 /// First we look for exact version
360 if (in_array($version, $versions)) {
361 return $version;
362 } else {
363 $found_version = false;
364 /// Not exact match, so we are going to iterate over the list searching
365 /// for the latest version before the requested one
366 foreach ($versions as $arrversion) {
367 if (version_compare($arrversion, $version, '<')) {
368 $found_version = $arrversion;
373 return $found_version;
378 * This function will return the xmlized data belonging to one Moodle version
380 * @param string $version top version from which we start to look backwards
381 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
382 * @return mixed the xmlized structure or false on error
384 function get_environment_for_version($version, $env_select) {
386 /// Normalize the version requested
387 $version = normalize_version($version);
389 /// Load xml file
390 if (!$contents = load_environment_xml($env_select)) {
391 return false;
394 /// Detect available versions
395 if (!$versions = get_list_of_environment_versions($contents)) {
396 return false;
399 /// If the version requested is available
400 if (!in_array($version, $versions)) {
401 return false;
404 /// We now we have it. Extract from full contents.
405 $fl_arr = array_flip($versions);
407 return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
412 * This function will check for everything (DB, PHP and PHP extensions for now)
413 * returning an array of environment_result objects.
415 * @global object
416 * @param string $version xml version we are going to use to test this server
417 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
418 * @return environment_results[] array of results encapsulated in one environment_result object
420 function environment_check($version, $env_select) {
421 global $CFG;
423 if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
424 throw new coding_exception('Incorrect value of $env_select parameter');
427 /// Normalize the version requested
428 $version = normalize_version($version);
430 $results = array(); //To store all the results
432 /// Only run the moodle versions checker on upgrade, not on install
433 if (!empty($CFG->version)) {
434 $results[] = environment_check_moodle($version, $env_select);
436 $results[] = environment_check_unicode($version, $env_select);
437 $results[] = environment_check_database($version, $env_select);
438 $results[] = environment_check_php($version, $env_select);
440 if ($result = environment_check_pcre_unicode($version, $env_select)) {
441 $results[] = $result;
444 $phpext_results = environment_check_php_extensions($version, $env_select);
445 $results = array_merge($results, $phpext_results);
447 $phpsetting_results = environment_check_php_settings($version, $env_select);
448 $results = array_merge($results, $phpsetting_results);
450 $custom_results = environment_custom_checks($version, $env_select);
451 $results = array_merge($results, $custom_results);
453 // Always use the plugin directory version of environment.xml,
454 // add-on developers need to keep those up-to-date with future info.
455 foreach (core_component::get_plugin_types() as $plugintype => $unused) {
456 foreach (core_component::get_plugin_list_with_file($plugintype, 'environment.xml') as $pluginname => $unused) {
457 $plugin = $plugintype . '_' . $pluginname;
459 $result = environment_check_database($version, $plugin);
460 if ($result->error_code != NO_VERSION_DATA_FOUND
461 and $result->error_code != NO_DATABASE_SECTION_FOUND
462 and $result->error_code != NO_DATABASE_VENDORS_FOUND) {
464 $result->plugin = $plugin;
465 $results[] = $result;
468 $result = environment_check_php($version, $plugin);
469 if ($result->error_code != NO_VERSION_DATA_FOUND
470 and $result->error_code != NO_PHP_SECTION_FOUND
471 and $result->error_code != NO_PHP_VERSION_FOUND) {
473 $result->plugin = $plugin;
474 $results[] = $result;
477 $pluginresults = environment_check_php_extensions($version, $plugin);
478 foreach ($pluginresults as $result) {
479 if ($result->error_code != NO_VERSION_DATA_FOUND
480 and $result->error_code != NO_PHP_EXTENSIONS_SECTION_FOUND) {
482 $result->plugin = $plugin;
483 $results[] = $result;
487 $pluginresults = environment_check_php_settings($version, $plugin);
488 foreach ($pluginresults as $result) {
489 if ($result->error_code != NO_VERSION_DATA_FOUND) {
490 $result->plugin = $plugin;
491 $results[] = $result;
495 $pluginresults = environment_custom_checks($version, $plugin);
496 foreach ($pluginresults as $result) {
497 if ($result->error_code != NO_VERSION_DATA_FOUND) {
498 $result->plugin = $plugin;
499 $results[] = $result;
505 return $results;
510 * This function will check if php extensions requirements are satisfied
512 * @uses NO_VERSION_DATA_FOUND
513 * @uses NO_PHP_EXTENSIONS_SECTION_FOUND
514 * @uses NO_PHP_EXTENSIONS_NAME_FOUND
515 * @param string $version xml version we are going to use to test this server
516 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
517 * @return array array of results encapsulated in one environment_result object
519 function environment_check_php_extensions($version, $env_select) {
521 $results = array();
523 /// Get the enviroment version we need
524 if (!$data = get_environment_for_version($version, $env_select)) {
525 /// Error. No version data found
526 $result = new environment_results('php_extension');
527 $result->setStatus(false);
528 $result->setErrorCode(NO_VERSION_DATA_FOUND);
529 return array($result);
532 /// Extract the php_extension part
533 if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
534 /// Error. No PHP section found
535 $result = new environment_results('php_extension');
536 $result->setStatus(false);
537 $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
538 return array($result);
540 /// Iterate over extensions checking them and creating the needed environment_results
541 foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
542 $result = new environment_results('php_extension');
543 /// Check for level
544 $level = get_level($extension);
545 /// Check for extension name
546 if (!isset($extension['@']['name'])) {
547 $result->setStatus(false);
548 $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
549 } else {
550 $extension_name = $extension['@']['name'];
551 /// The name exists. Just check if it's an installed extension
552 if (!extension_loaded($extension_name)) {
553 $result->setStatus(false);
554 } else {
555 $result->setStatus(true);
557 $result->setLevel($level);
558 $result->setInfo($extension_name);
561 /// Do any actions defined in the XML file.
562 process_environment_result($extension, $result);
564 /// Add the result to the array of results
565 $results[] = $result;
569 return $results;
573 * This function will check if php extensions requirements are satisfied
575 * @uses NO_VERSION_DATA_FOUND
576 * @uses NO_PHP_SETTINGS_NAME_FOUND
577 * @param string $version xml version we are going to use to test this server
578 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
579 * @return array array of results encapsulated in one environment_result object
581 function environment_check_php_settings($version, $env_select) {
583 $results = array();
585 /// Get the enviroment version we need
586 if (!$data = get_environment_for_version($version, $env_select)) {
587 /// Error. No version data found
588 $result = new environment_results('php_setting');
589 $result->setStatus(false);
590 $result->setErrorCode(NO_VERSION_DATA_FOUND);
591 $results[] = $result;
592 return $results;
595 /// Extract the php_setting part
596 if (!isset($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'])) {
597 /// No PHP section found - ignore
598 return $results;
600 /// Iterate over settings checking them and creating the needed environment_results
601 foreach($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'] as $setting) {
602 $result = new environment_results('php_setting');
603 /// Check for level
604 $level = get_level($setting);
605 $result->setLevel($level);
606 /// Check for extension name
607 if (!isset($setting['@']['name'])) {
608 $result->setStatus(false);
609 $result->setErrorCode(NO_PHP_SETTINGS_NAME_FOUND);
610 } else {
611 $setting_name = $setting['@']['name'];
612 $setting_value = $setting['@']['value'];
613 $result->setInfo($setting_name);
615 if ($setting_name == 'memory_limit') {
616 $current = ini_get('memory_limit');
617 if ($current == -1) {
618 $result->setStatus(true);
619 } else {
620 $current = get_real_size($current);
621 $minlimit = get_real_size($setting_value);
622 if ($current < $minlimit) {
623 @ini_set('memory_limit', $setting_value);
624 $current = ini_get('memory_limit');
625 $current = get_real_size($current);
627 $result->setStatus($current >= $minlimit);
630 } else {
631 $current = ini_get_bool($setting_name);
632 /// The name exists. Just check if it's an installed extension
633 if ($current == $setting_value) {
634 $result->setStatus(true);
635 } else {
636 $result->setStatus(false);
641 /// Do any actions defined in the XML file.
642 process_environment_result($setting, $result);
644 /// Add the result to the array of results
645 $results[] = $result;
649 return $results;
653 * This function will do the custom checks.
655 * @uses CUSTOM_CHECK_FUNCTION_MISSING
656 * @uses CUSTOM_CHECK_FILE_MISSING
657 * @uses NO_CUSTOM_CHECK_FOUND
658 * @param string $version xml version we are going to use to test this server.
659 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
660 * @return array array of results encapsulated in environment_result objects.
662 function environment_custom_checks($version, $env_select) {
663 global $CFG;
665 $results = array();
667 /// Get current Moodle version (release) for later compare
668 $release = isset($CFG->release) ? $CFG->release : $version; /// In case $CFG fails (at install) use $version
669 $current_version = normalize_version($release);
671 /// Get the enviroment version we need
672 if (!$data = get_environment_for_version($version, $env_select)) {
673 /// Error. No version data found - but this will already have been reported.
674 return $results;
677 /// Extract the CUSTOM_CHECKS part
678 if (!isset($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'])) {
679 /// No custom checks found - not a problem
680 return $results;
683 /// Iterate over extensions checking them and creating the needed environment_results
684 foreach($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'] as $check) {
685 $result = new environment_results('custom_check');
687 /// Check for level
688 $level = get_level($check);
690 /// Check for extension name
691 if (isset($check['@']['function'])) {
692 $function = $check['@']['function'];
693 $file = null;
694 if (isset($check['@']['file'])) {
695 $file = $CFG->dirroot . '/' . $check['@']['file'];
696 if (is_readable($file)) {
697 include_once($file);
701 if (is_callable($function)) {
702 $result->setLevel($level);
703 $result->setInfo($function);
704 $result = call_user_func($function, $result);
705 } else if (!$file or is_readable($file)) {
706 /// Only show error for current version (where function MUST exist)
707 /// else, we are performing custom checks against future versiosn
708 /// and function MAY not exist, so it doesn't cause error, just skip
709 /// custom check by returning null. MDL-15939
710 if (version_compare($current_version, $version, '>=')) {
711 $result->setStatus(false);
712 $result->setInfo($function);
713 $result->setErrorCode(CUSTOM_CHECK_FUNCTION_MISSING);
714 } else {
715 $result = null;
717 } else {
718 /// Only show error for current version (where function MUST exist)
719 /// else, we are performing custom checks against future versiosn
720 /// and function MAY not exist, so it doesn't cause error, just skip
721 /// custom check by returning null. MDL-15939
722 if (version_compare($current_version, $version, '>=')) {
723 $result->setStatus(false);
724 $result->setInfo($function);
725 $result->setErrorCode(CUSTOM_CHECK_FILE_MISSING);
726 } else {
727 $result = null;
730 } else {
731 $result->setStatus(false);
732 $result->setErrorCode(NO_CUSTOM_CHECK_FOUND);
735 if (!is_null($result)) {
736 /// Do any actions defined in the XML file.
737 process_environment_result($check, $result);
739 /// Add the result to the array of results
740 $results[] = $result;
744 return $results;
748 * This function will check if Moodle requirements are satisfied
750 * @uses NO_VERSION_DATA_FOUND
751 * @param string $version xml version we are going to use to test this server
752 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
753 * @return object results encapsulated in one environment_result object
755 function environment_check_moodle($version, $env_select) {
757 $result = new environment_results('moodle');
759 /// Get the enviroment version we need
760 if (!$data = get_environment_for_version($version, $env_select)) {
761 /// Error. No version data found
762 $result->setStatus(false);
763 $result->setErrorCode(NO_VERSION_DATA_FOUND);
764 return $result;
767 /// Extract the moodle part
768 if (!isset($data['@']['requires'])) {
769 $needed_version = '1.0'; /// Default to 1.0 if no moodle requires is found
770 } else {
771 /// Extract required moodle version
772 $needed_version = $data['@']['requires'];
775 /// Now search the version we are using
776 $release = get_config('', 'release');
777 $current_version = normalize_version($release);
778 if (strpos($release, 'dev') !== false) {
779 // when final version is required, dev is NOT enough!
780 $current_version = $current_version - 0.1;
783 /// And finally compare them, saving results
784 if (version_compare($current_version, $needed_version, '>=')) {
785 $result->setStatus(true);
786 } else {
787 $result->setStatus(false);
789 $result->setLevel('required');
790 $result->setCurrentVersion($release);
791 $result->setNeededVersion($needed_version);
793 return $result;
797 * This function will check if php requirements are satisfied
799 * @uses NO_VERSION_DATA_FOUND
800 * @uses NO_PHP_SECTION_FOUND
801 * @uses NO_PHP_VERSION_FOUND
802 * @param string $version xml version we are going to use to test this server
803 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
804 * @return object results encapsulated in one environment_result object
806 function environment_check_php($version, $env_select) {
808 $result = new environment_results('php');
810 /// Get the enviroment version we need
811 if (!$data = get_environment_for_version($version, $env_select)) {
812 /// Error. No version data found
813 $result->setStatus(false);
814 $result->setErrorCode(NO_VERSION_DATA_FOUND);
815 return $result;
818 /// Extract the php part
819 if (!isset($data['#']['PHP'])) {
820 /// Error. No PHP section found
821 $result->setStatus(false);
822 $result->setErrorCode(NO_PHP_SECTION_FOUND);
823 return $result;
824 } else {
825 /// Extract level and version
826 $level = get_level($data['#']['PHP']['0']);
827 if (!isset($data['#']['PHP']['0']['@']['version'])) {
828 $result->setStatus(false);
829 $result->setErrorCode(NO_PHP_VERSION_FOUND);
830 return $result;
831 } else {
832 $needed_version = $data['#']['PHP']['0']['@']['version'];
836 /// Now search the version we are using
837 $current_version = normalize_version(phpversion());
839 /// And finally compare them, saving results
840 if (version_compare($current_version, $needed_version, '>=')) {
841 $result->setStatus(true);
842 } else {
843 $result->setStatus(false);
845 $result->setLevel($level);
846 $result->setCurrentVersion($current_version);
847 $result->setNeededVersion($needed_version);
849 /// Do any actions defined in the XML file.
850 process_environment_result($data['#']['PHP'][0], $result);
852 return $result;
856 * Looks for buggy PCRE implementation, we need unicode support in Moodle...
857 * @param string $version xml version we are going to use to test this server
858 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
859 * @return stdClass results encapsulated in one environment_result object, null if irrelevant
861 function environment_check_pcre_unicode($version, $env_select) {
862 $result = new environment_results('pcreunicode');
864 // Get the environment version we need
865 if (!$data = get_environment_for_version($version, $env_select)) {
866 // Error. No version data found!
867 $result->setStatus(false);
868 $result->setErrorCode(NO_VERSION_DATA_FOUND);
869 return $result;
872 if (!isset($data['#']['PCREUNICODE'])) {
873 return null;
876 $level = get_level($data['#']['PCREUNICODE']['0']);
877 $result->setLevel($level);
879 if (!function_exists('preg_match')) {
880 // The extension test fails instead.
881 return null;
883 } else if (@preg_match('/\pL/u', 'a') and @preg_match('/á/iu', 'Á')) {
884 $result->setStatus(true);
886 } else {
887 $result->setStatus(false);
890 // Do any actions defined in the XML file.
891 process_environment_result($data['#']['PCREUNICODE'][0], $result);
893 return $result;
897 * This function will check if unicode database requirements are satisfied
899 * @uses NO_VERSION_DATA_FOUND
900 * @uses NO_UNICODE_SECTION_FOUND
901 * @param string $version xml version we are going to use to test this server
902 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
903 * @return object results encapsulated in one environment_result object
905 function environment_check_unicode($version, $env_select) {
906 global $DB;
908 $result = new environment_results('unicode');
910 /// Get the enviroment version we need
911 if (!$data = get_environment_for_version($version, $env_select)) {
912 /// Error. No version data found
913 $result->setStatus(false);
914 $result->setErrorCode(NO_VERSION_DATA_FOUND);
915 return $result;
918 /// Extract the unicode part
920 if (!isset($data['#']['UNICODE'])) {
921 /// Error. No UNICODE section found
922 $result->setStatus(false);
923 $result->setErrorCode(NO_UNICODE_SECTION_FOUND);
924 return $result;
925 } else {
926 /// Extract level
927 $level = get_level($data['#']['UNICODE']['0']);
930 if (!$unicodedb = $DB->setup_is_unicodedb()) {
931 $result->setStatus(false);
932 } else {
933 $result->setStatus(true);
936 $result->setLevel($level);
938 /// Do any actions defined in the XML file.
939 process_environment_result($data['#']['UNICODE'][0], $result);
941 return $result;
945 * This function will check if database requirements are satisfied
947 * @uses NO_VERSION_DATA_FOUND
948 * @uses NO_DATABASE_SECTION_FOUND
949 * @uses NO_DATABASE_VENDORS_FOUND
950 * @uses NO_DATABASE_VENDOR_MYSQL_FOUND
951 * @uses NO_DATABASE_VENDOR_POSTGRES_FOUND
952 * @uses NO_DATABASE_VENDOR_VERSION_FOUND
953 * @param string $version xml version we are going to use to test this server
954 * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
955 * @return object results encapsulated in one environment_result object
957 function environment_check_database($version, $env_select) {
959 global $DB;
961 $result = new environment_results('database');
963 $vendors = array(); //Array of vendors in version
965 /// Get the enviroment version we need
966 if (!$data = get_environment_for_version($version, $env_select)) {
967 /// Error. No version data found
968 $result->setStatus(false);
969 $result->setErrorCode(NO_VERSION_DATA_FOUND);
970 return $result;
973 /// Extract the database part
974 if (!isset($data['#']['DATABASE'])) {
975 /// Error. No DATABASE section found
976 $result->setStatus(false);
977 $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
978 return $result;
979 } else {
980 /// Extract level
981 $level = get_level($data['#']['DATABASE']['0']);
984 /// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
985 if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
986 /// Error. No VENDORS found
987 $result->setStatus(false);
988 $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
989 return $result;
990 } else {
991 /// Extract vendors
992 foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
993 if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
994 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
995 $vendorsxml[$vendor['@']['name']] = $vendor;
999 /// Check we have the mysql vendor version
1000 if (empty($vendors['mysql'])) {
1001 $result->setStatus(false);
1002 $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
1003 return $result;
1005 /// Check we have the postgres vendor version
1006 if (empty($vendors['postgres'])) {
1007 $result->setStatus(false);
1008 $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
1009 return $result;
1012 /// Now search the version we are using (depending of vendor)
1013 $current_vendor = $DB->get_dbvendor();
1015 $dbinfo = $DB->get_server_info();
1016 $current_version = normalize_version($dbinfo['version']);
1017 $needed_version = $vendors[$current_vendor];
1019 /// Check we have a needed version
1020 if (!$needed_version) {
1021 $result->setStatus(false);
1022 $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND);
1023 return $result;
1026 /// And finally compare them, saving results
1027 if (version_compare($current_version, $needed_version, '>=')) {
1028 $result->setStatus(true);
1029 } else {
1030 $result->setStatus(false);
1032 $result->setLevel($level);
1033 $result->setCurrentVersion($current_version);
1034 $result->setNeededVersion($needed_version);
1035 $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1037 /// Do any actions defined in the XML file.
1038 process_environment_result($vendorsxml[$current_vendor], $result);
1040 return $result;
1045 * This function will post-process the result record by executing the specified
1046 * function, modifying it as necessary, also a custom message will be added
1047 * to the result object to be printed by the display layer.
1048 * Every bypass function must be defined in this file and it'll return
1049 * true/false to decide if the original test is bypassed or no. Also
1050 * such bypass functions are able to directly handling the result object
1051 * although it should be only under exceptional conditions.
1053 * @param string xmldata containing the bypass data
1054 * @param object result object to be updated
1055 * @return void
1057 function process_environment_bypass($xml, &$result) {
1059 /// Only try to bypass if we were in error and it was required
1060 if ($result->getStatus() || $result->getLevel() == 'optional') {
1061 return;
1064 /// It there is bypass info (function and message)
1065 if (is_array($xml['#']) && isset($xml['#']['BYPASS'][0]['@']['function']) && isset($xml['#']['BYPASS'][0]['@']['message'])) {
1066 $function = $xml['#']['BYPASS'][0]['@']['function'];
1067 $message = $xml['#']['BYPASS'][0]['@']['message'];
1068 /// Look for the function
1069 if (function_exists($function)) {
1070 /// Call it, and if bypass = true is returned, apply meesage
1071 if ($function($result)) {
1072 /// We only set the bypass message if the function itself hasn't defined it before
1073 if (empty($result->getBypassStr)) {
1074 if (isset($xml['#']['BYPASS'][0]['@']['plugin'])) {
1075 $result->setBypassStr(array($message, $xml['#']['BYPASS'][0]['@']['plugin']));
1076 } else {
1077 $result->setBypassStr($message);
1086 * This function will post-process the result record by executing the specified
1087 * function, modifying it as necessary, also a custom message will be added
1088 * to the result object to be printed by the display layer.
1089 * Every restrict function must be defined in this file and it'll return
1090 * true/false to decide if the original test is restricted or no. Also
1091 * such restrict functions are able to directly handling the result object
1092 * although it should be only under exceptional conditions.
1094 * @param string xmldata containing the restrict data
1095 * @param object result object to be updated
1096 * @return void
1098 function process_environment_restrict($xml, &$result) {
1100 /// Only try to restrict if we were not in error and it was required
1101 if (!$result->getStatus() || $result->getLevel() == 'optional') {
1102 return;
1104 /// It there is restrict info (function and message)
1105 if (is_array($xml['#']) && isset($xml['#']['RESTRICT'][0]['@']['function']) && isset($xml['#']['RESTRICT'][0]['@']['message'])) {
1106 $function = $xml['#']['RESTRICT'][0]['@']['function'];
1107 $message = $xml['#']['RESTRICT'][0]['@']['message'];
1108 /// Look for the function
1109 if (function_exists($function)) {
1110 /// Call it, and if restrict = true is returned, apply meesage
1111 if ($function($result)) {
1112 /// We only set the restrict message if the function itself hasn't defined it before
1113 if (empty($result->getRestrictStr)) {
1114 if (isset($xml['#']['RESTRICT'][0]['@']['plugin'])) {
1115 $result->setRestrictStr(array($message, $xml['#']['RESTRICT'][0]['@']['plugin']));
1116 } else {
1117 $result->setRestrictStr($message);
1126 * This function will detect if there is some message available to be added to the
1127 * result in order to clarify enviromental details.
1129 * @uses INCORRECT_FEEDBACK_FOR_REQUIRED
1130 * @uses INCORRECT_FEEDBACK_FOR_OPTIONAL
1131 * @param string xmldata containing the feedback data
1132 * @param object reult object to be updated
1134 function process_environment_messages($xml, &$result) {
1136 /// If there is feedback info
1137 if (is_array($xml['#']) && isset($xml['#']['FEEDBACK'][0]['#'])) {
1138 $feedbackxml = $xml['#']['FEEDBACK'][0]['#'];
1140 // Detect some incorrect feedback combinations.
1141 if ($result->getLevel() == 'required' and isset($feedbackxml['ON_CHECK'])) {
1142 $result->setStatus(false);
1143 $result->setErrorCode(INCORRECT_FEEDBACK_FOR_REQUIRED);
1144 } else if ($result->getLevel() == 'optional' and isset($feedbackxml['ON_ERROR'])) {
1145 $result->setStatus(false);
1146 $result->setErrorCode(INCORRECT_FEEDBACK_FOR_OPTIONAL);
1149 if (!$result->status and $result->getLevel() == 'required') {
1150 if (isset($feedbackxml['ON_ERROR'][0]['@']['message'])) {
1151 if (isset($feedbackxml['ON_ERROR'][0]['@']['plugin'])) {
1152 $result->setFeedbackStr(array($feedbackxml['ON_ERROR'][0]['@']['message'], $feedbackxml['ON_ERROR'][0]['@']['plugin']));
1153 } else {
1154 $result->setFeedbackStr($feedbackxml['ON_ERROR'][0]['@']['message']);
1157 } else if (!$result->status and $result->getLevel() == 'optional') {
1158 if (isset($feedbackxml['ON_CHECK'][0]['@']['message'])) {
1159 if (isset($feedbackxml['ON_CHECK'][0]['@']['plugin'])) {
1160 $result->setFeedbackStr(array($feedbackxml['ON_CHECK'][0]['@']['message'], $feedbackxml['ON_CHECK'][0]['@']['plugin']));
1161 } else {
1162 $result->setFeedbackStr($feedbackxml['ON_CHECK'][0]['@']['message']);
1165 } else {
1166 if (isset($feedbackxml['ON_OK'][0]['@']['message'])) {
1167 if (isset($feedbackxml['ON_OK'][0]['@']['plugin'])) {
1168 $result->setFeedbackStr(array($feedbackxml['ON_OK'][0]['@']['message'], $feedbackxml['ON_OK'][0]['@']['plugin']));
1169 } else {
1170 $result->setFeedbackStr($feedbackxml['ON_OK'][0]['@']['message']);
1178 //--- Helper Class to return results to caller ---//
1182 * Helper Class to return results to caller
1184 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1185 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1186 * @package moodlecore
1188 class environment_results {
1190 * @var string Which are we checking (database, php, php_extension, php_extension)
1192 var $part;
1194 * @var bool true means the test passed and all is OK. false means it failed.
1196 var $status;
1198 * @var integer See constants at the beginning of the file
1200 var $error_code;
1202 * @var string required/optional
1204 var $level;
1206 * @var string current version detected
1208 var $current_version;
1210 * @var string version needed
1212 var $needed_version;
1214 * @var string Aux. info (DB vendor, library...)
1216 var $info;
1218 * @var string String to show on error|on check|on ok
1220 var $feedback_str;
1222 * @var string String to show if some bypass has happened
1224 var $bypass_str;
1226 * @var string String to show if some restrict has happened
1228 var $restrict_str;
1230 * @var string|null full plugin name or null if main environment
1232 var $plugin = null;
1234 * Constructor of the environment_result class. Just set default values
1236 * @param string $part
1238 function environment_results($part) {
1239 $this->part=$part;
1240 $this->status=false;
1241 $this->error_code=NO_ERROR;
1242 $this->level='required';
1243 $this->current_version='';
1244 $this->needed_version='';
1245 $this->info='';
1246 $this->feedback_str='';
1247 $this->bypass_str='';
1248 $this->restrict_str='';
1252 * Set the status
1254 * @param bool $testpassed true means the test passed and all is OK. false means it failed.
1256 function setStatus($testpassed) {
1257 $this->status = $testpassed;
1258 if ($testpassed) {
1259 $this->setErrorCode(NO_ERROR);
1264 * Set the error_code
1266 * @param integer $error_code the error code (see constants above)
1268 function setErrorCode($error_code) {
1269 $this->error_code=$error_code;
1273 * Set the level
1275 * @param string $level the level (required, optional)
1277 function setLevel($level) {
1278 $this->level=$level;
1282 * Set the current version
1284 * @param string $current_version the current version
1286 function setCurrentVersion($current_version) {
1287 $this->current_version=$current_version;
1291 * Set the needed version
1293 * @param string $needed_version the needed version
1295 function setNeededVersion($needed_version) {
1296 $this->needed_version=$needed_version;
1300 * Set the auxiliary info
1302 * @param string $info the auxiliary info
1304 function setInfo($info) {
1305 $this->info=$info;
1309 * Set the feedback string
1311 * @param mixed $str the feedback string that will be fetched from the admin lang file.
1312 * pass just the string or pass an array of params for get_string
1313 * You always should put your string in admin.php but a third param is useful
1314 * to pass an $a object / string to get_string
1316 function setFeedbackStr($str) {
1317 $this->feedback_str=$str;
1322 * Set the bypass string
1324 * @param string $str the bypass string that will be fetched from the admin lang file.
1325 * pass just the string or pass an array of params for get_string
1326 * You always should put your string in admin.php but a third param is useful
1327 * to pass an $a object / string to get_string
1329 function setBypassStr($str) {
1330 $this->bypass_str=$str;
1334 * Set the restrict string
1336 * @param string $str the restrict string that will be fetched from the admin lang file.
1337 * pass just the string or pass an array of params for get_string
1338 * You always should put your string in admin.php but a third param is useful
1339 * to pass an $a object / string to get_string
1341 function setRestrictStr($str) {
1342 $this->restrict_str=$str;
1346 * Get the status
1348 * @return bool true means the test passed and all is OK. false means it failed.
1350 function getStatus() {
1351 return $this->status;
1355 * Get the error code
1357 * @return integer error code
1359 function getErrorCode() {
1360 return $this->error_code;
1364 * Get the level
1366 * @return string level
1368 function getLevel() {
1369 return $this->level;
1373 * Get the current version
1375 * @return string current version
1377 function getCurrentVersion() {
1378 return $this->current_version;
1382 * Get the needed version
1384 * @return string needed version
1386 function getNeededVersion() {
1387 return $this->needed_version;
1391 * Get the aux info
1393 * @return string info
1395 function getInfo() {
1396 return $this->info;
1400 * Get the part this result belongs to
1402 * @return string part
1404 function getPart() {
1405 return $this->part;
1409 * Get the feedback string
1411 * @return mixed feedback string (can be an array of params for get_string or a single string to fetch from
1412 * admin.php lang file).
1414 function getFeedbackStr() {
1415 return $this->feedback_str;
1419 * Get the bypass string
1421 * @return mixed bypass string (can be an array of params for get_string or a single string to fetch from
1422 * admin.php lang file).
1424 function getBypassStr() {
1425 return $this->bypass_str;
1429 * Get the restrict string
1431 * @return mixed restrict string (can be an array of params for get_string or a single string to fetch from
1432 * admin.php lang file).
1434 function getRestrictStr() {
1435 return $this->restrict_str;
1439 * @todo Document this function
1441 * @param mixed $string params for get_string, either a string to fetch from admin.php or an array of
1442 * params for get_string.
1443 * @param string $class css class(es) for message.
1444 * @return string feedback string fetched from lang file wrapped in p tag with class $class or returns
1445 * empty string if $string is empty.
1447 function strToReport($string, $class){
1448 if (!empty($string)){
1449 if (is_array($string)){
1450 $str = call_user_func_array('get_string', $string);
1451 } else {
1452 $str = get_string($string, 'admin');
1454 return '<p class="'.$class.'">'.$str.'</p>';
1455 } else {
1456 return '';
1461 * Get plugin name.
1463 * @return string plugin name
1465 function getPluginName() {
1466 if ($this->plugin) {
1467 $manager = core_plugin_manager::instance();
1468 list($plugintype, $pluginname) = core_component::normalize_component($this->plugin);
1469 return $manager->plugintype_name($plugintype) . ' / ' . $manager->plugin_name($this->plugin);
1470 } else {
1471 return '';
1476 /// Here all the restrict functions are coded to be used by the environment
1477 /// checker. All those functions will receive the result object and will
1478 /// return it modified as needed (status and bypass string)
1481 * @param array $element the element from the environment.xml file that should have
1482 * either a level="required" or level="optional" attribute.
1483 * @return string "required" or "optional".
1485 function get_level($element) {
1486 $level = 'required';
1487 if (isset($element['@']['level'])) {
1488 $level = $element['@']['level'];
1489 if (!in_array($level, array('required', 'optional'))) {
1490 debugging('The level of a check in the environment.xml file must be "required" or "optional".', DEBUG_DEVELOPER);
1491 $level = 'required';
1493 } else {
1494 debugging('Checks in the environment.xml file must have a level="required" or level="optional" attribute.', DEBUG_DEVELOPER);
1496 return $level;
1500 * Once the result has been determined, look in the XML for any
1501 * messages, or other things that should be done depending on the outcome.
1503 * @param array $element the element from the environment.xml file which
1504 * may have children defining what should be done with the outcome.
1505 * @param object $result the result of the test, which may be modified by
1506 * this function as specified in the XML.
1508 function process_environment_result($element, &$result) {
1509 /// Process messages, modifying the $result if needed.
1510 process_environment_messages($element, $result);
1511 /// Process bypass, modifying $result if needed.
1512 process_environment_bypass($element, $result);
1513 /// Process restrict, modifying $result if needed.
1514 process_environment_restrict($element, $result);