MDL-39416 do not try to get detailed perflog info before PAGE int
[moodle.git] / lib / environmentlib.php
blobbe7aabf68a750fada77aa5b1b606aa6cc22a2702
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);
74 /// Define algorithm used to select the xml file
75 /** To select the newer file available to perform checks */
76 define('ENV_SELECT_NEWER', 0);
77 /** To enforce the use of the file under dataroot */
78 define('ENV_SELECT_DATAROOT', 1);
79 /** To enforce the use of the file under admin (release) */
80 define('ENV_SELECT_RELEASE', 2);
82 /**
83 * This function checks all the requirements defined in environment.xml.
85 * @staticvar bool $result
86 * @staticvar array $env_results
87 * @staticvar bool $cache_exists
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) {
97 $status = true;
99 /// This are cached per request
100 static $result = true;
101 static $env_results;
102 static $cache_exists = false;
104 /// if we have results cached, use them
105 if ($cache_exists) {
106 $environment_results = $env_results;
107 /// No cache exists, calculate everything
108 } else {
109 /// Get the more recent version before the requested
110 if (!$version = get_latest_version_available($version, $env_select)) {
111 $status = false;
114 /// Perform all the checks
115 if (!($environment_results = environment_check($version, $env_select)) && $status) {
116 $status = false;
119 /// Iterate over all the results looking for some error in required items
120 /// or some error_code
121 if ($status) {
122 foreach ($environment_results as $environment_result) {
123 if (!$environment_result->getStatus() && $environment_result->getLevel() == 'required'
124 && !$environment_result->getBypassStr()) {
125 $result = false; // required item that is not bypased
126 } else if ($environment_result->getStatus() && $environment_result->getLevel() == 'required'
127 && $environment_result->getRestrictStr()) {
128 $result = false; // required item that is restricted
129 } else if ($environment_result->getErrorCode()) {
130 $result = false;
134 /// Going to end, we store environment_results to cache
135 $env_results = $environment_results;
136 $cache_exists = true;
137 } ///End of cache block
139 return array($result && $status, $environment_results);
144 * Returns array of critical errors in plain text format
145 * @param array $environment_results array of results gathered
146 * @return array errors
148 function environment_get_errors($environment_results) {
149 global $CFG;
150 $errors = array();
152 // Iterate over each environment_result
153 foreach ($environment_results as $environment_result) {
154 $type = $environment_result->getPart();
155 $info = $environment_result->getInfo();
156 $status = $environment_result->getStatus();
157 $error_code = $environment_result->getErrorCode();
159 $a = new stdClass();
160 if ($error_code) {
161 $a->error_code = $error_code;
162 $errors[] = array($info, get_string('environmentxmlerror', 'admin', $a));
163 return $errors;
166 /// Calculate the status value
167 if ($environment_result->getBypassStr() != '') {
168 // not interesting
169 continue;
170 } else if ($environment_result->getRestrictStr() != '') {
171 // error
172 } else {
173 if ($status) {
174 // ok
175 continue;
176 } else {
177 if ($environment_result->getLevel() == 'optional') {
178 // just a warning
179 continue;
180 } else {
181 // error
186 // We are comparing versions
187 $rec = new stdClass();
188 if ($rec->needed = $environment_result->getNeededVersion()) {
189 $rec->current = $environment_result->getCurrentVersion();
190 if ($environment_result->getLevel() == 'required') {
191 $stringtouse = 'environmentrequireversion';
192 } else {
193 $stringtouse = 'environmentrecommendversion';
195 // We are checking installed & enabled things
196 } else if ($environment_result->getPart() == 'custom_check') {
197 if ($environment_result->getLevel() == 'required') {
198 $stringtouse = 'environmentrequirecustomcheck';
199 } else {
200 $stringtouse = 'environmentrecommendcustomcheck';
202 } else if ($environment_result->getPart() == 'php_setting') {
203 if ($status) {
204 $stringtouse = 'environmentsettingok';
205 } else if ($environment_result->getLevel() == 'required') {
206 $stringtouse = 'environmentmustfixsetting';
207 } else {
208 $stringtouse = 'environmentshouldfixsetting';
210 } else {
211 if ($environment_result->getLevel() == 'required') {
212 $stringtouse = 'environmentrequireinstall';
213 } else {
214 $stringtouse = 'environmentrecommendinstall';
217 $report = get_string($stringtouse, 'admin', $rec);
219 // Here we'll store all the feedback found
220 $feedbacktext = '';
221 // Append the feedback if there is some
222 $feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), 'error');
223 // Append the restrict if there is some
224 $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
226 $report .= html_to_text($feedbacktext);
228 if ($environment_result->getPart() == 'custom_check'){
229 $errors[] = array($info, $report);
230 } else {
231 $errors[] = array(($info !== '' ? "$type $info" : $type), $report);
235 return $errors;
240 * This function will normalize any version to just a serie of numbers
241 * separated by dots. Everything else will be removed.
243 * @param string $version the original version
244 * @return string the normalized version
246 function normalize_version($version) {
248 /// 1.9 Beta 2 should be read 1.9 on enviromental checks, not 1.9.2
249 /// we can discard everything after the first space
250 $version = trim($version);
251 $versionarr = explode(" ",$version);
252 if (!empty($versionarr)) {
253 $version = $versionarr[0];
255 /// Replace everything but numbers and dots by dots
256 $version = preg_replace('/[^\.\d]/', '.', $version);
257 /// Combine multiple dots in one
258 $version = preg_replace('/(\.{2,})/', '.', $version);
259 /// Trim possible leading and trailing dots
260 $version = trim($version, '.');
262 return $version;
267 * This function will load the environment.xml file and xmlize it
269 * @global object
270 * @staticvar mixed $data
271 * @uses ENV_SELECT_NEWER
272 * @uses ENV_SELECT_DATAROOT
273 * @uses ENV_SELECT_RELEASE
274 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. Default ENV_SELECT_NEWER (BC)
275 * @return mixed the xmlized structure or false on error
277 function load_environment_xml($env_select=ENV_SELECT_NEWER) {
279 global $CFG;
281 static $data; //Only load and xmlize once by request
283 if (!empty($data)) {
284 return $data;
287 /// First of all, take a look inside $CFG->dataroot/environment/environment.xml
288 $file = $CFG->dataroot.'/environment/environment.xml';
289 $internalfile = $CFG->dirroot.'/'.$CFG->admin.'/environment.xml';
290 switch ($env_select) {
291 case ENV_SELECT_NEWER:
292 if (!is_file($file) || !is_readable($file) || filemtime($file) < filemtime($internalfile) ||
293 !$contents = file_get_contents($file)) {
294 /// Fallback to fixed $CFG->admin/environment.xml
295 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
296 return false;
299 break;
300 case ENV_SELECT_DATAROOT:
301 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
302 return false;
304 break;
305 case ENV_SELECT_RELEASE:
306 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
307 return false;
309 break;
311 /// XML the whole file
312 $data = xmlize($contents);
314 return $data;
319 * This function will return the list of Moodle versions available
321 * @staticvar array $versions
322 * @return mixed array of versions. False on error.
324 function get_list_of_environment_versions ($contents) {
326 static $versions = array();
328 if (!empty($versions)) {
329 return $versions;
332 if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
333 foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
334 $versions[] = $version['@']['version'];
338 return $versions;
343 * This function will return the most recent version in the environment.xml
344 * file previous or equal to the version requested
346 * @param string $version top version from which we start to look backwards
347 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
348 * @return string|bool string more recent version or false if not found
350 function get_latest_version_available ($version, $env_select) {
352 /// Normalize the version requested
353 $version = normalize_version($version);
355 /// Load xml file
356 if (!$contents = load_environment_xml($env_select)) {
357 return false;
360 /// Detect available versions
361 if (!$versions = get_list_of_environment_versions($contents)) {
362 return false;
364 /// First we look for exact version
365 if (in_array($version, $versions)) {
366 return $version;
367 } else {
368 $found_version = false;
369 /// Not exact match, so we are going to iterate over the list searching
370 /// for the latest version before the requested one
371 foreach ($versions as $arrversion) {
372 if (version_compare($arrversion, $version, '<')) {
373 $found_version = $arrversion;
378 return $found_version;
383 * This function will return the xmlized data belonging to one Moodle version
385 * @param string $version top version from which we start to look backwards
386 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
387 * @return mixed the xmlized structure or false on error
389 function get_environment_for_version($version, $env_select) {
391 /// Normalize the version requested
392 $version = normalize_version($version);
394 /// Load xml file
395 if (!$contents = load_environment_xml($env_select)) {
396 return false;
399 /// Detect available versions
400 if (!$versions = get_list_of_environment_versions($contents)) {
401 return false;
404 /// If the version requested is available
405 if (!in_array($version, $versions)) {
406 return false;
409 /// We now we have it. Extract from full contents.
410 $fl_arr = array_flip($versions);
412 return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
417 * This function will check for everything (DB, PHP and PHP extensions for now)
418 * returning an array of environment_result objects.
420 * @global object
421 * @param string $version xml version we are going to use to test this server
422 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
423 * @return array array of results encapsulated in one environment_result object
425 function environment_check($version, $env_select) {
426 global $CFG;
428 /// Normalize the version requested
429 $version = normalize_version($version);
431 $results = array(); //To store all the results
433 /// Only run the moodle versions checker on upgrade, not on install
434 if (!empty($CFG->version)) {
435 $results[] = environment_check_moodle($version, $env_select);
437 $results[] = environment_check_unicode($version, $env_select);
438 $results[] = environment_check_database($version, $env_select);
439 $results[] = environment_check_php($version, $env_select);
441 if ($result = environment_check_pcre_unicode($version, $env_select)) {
442 $results[] = $result;
445 $phpext_results = environment_check_php_extensions($version, $env_select);
446 $results = array_merge($results, $phpext_results);
448 $phpsetting_results = environment_check_php_settings($version, $env_select);
449 $results = array_merge($results, $phpsetting_results);
451 $custom_results = environment_custom_checks($version, $env_select);
452 $results = array_merge($results, $custom_results);
454 return $results;
459 * This function will check if php extensions requirements are satisfied
461 * @uses NO_VERSION_DATA_FOUND
462 * @uses NO_PHP_EXTENSIONS_SECTION_FOUND
463 * @uses NO_PHP_EXTENSIONS_NAME_FOUND
464 * @param string $version xml version we are going to use to test this server
465 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
466 * @return array array of results encapsulated in one environment_result object
468 function environment_check_php_extensions($version, $env_select) {
470 $results = array();
472 /// Get the enviroment version we need
473 if (!$data = get_environment_for_version($version, $env_select)) {
474 /// Error. No version data found
475 $result = new environment_results('php_extension');
476 $result->setStatus(false);
477 $result->setErrorCode(NO_VERSION_DATA_FOUND);
478 return array($result);
481 /// Extract the php_extension part
482 if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
483 /// Error. No PHP section found
484 $result = new environment_results('php_extension');
485 $result->setStatus(false);
486 $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
487 return array($result);
489 /// Iterate over extensions checking them and creating the needed environment_results
490 foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
491 $result = new environment_results('php_extension');
492 /// Check for level
493 $level = get_level($extension);
494 /// Check for extension name
495 if (!isset($extension['@']['name'])) {
496 $result->setStatus(false);
497 $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
498 } else {
499 $extension_name = $extension['@']['name'];
500 /// The name exists. Just check if it's an installed extension
501 if (!extension_loaded($extension_name)) {
502 $result->setStatus(false);
503 } else {
504 $result->setStatus(true);
506 $result->setLevel($level);
507 $result->setInfo($extension_name);
510 /// Do any actions defined in the XML file.
511 process_environment_result($extension, $result);
513 /// Add the result to the array of results
514 $results[] = $result;
518 return $results;
522 * This function will check if php extensions requirements are satisfied
524 * @uses NO_VERSION_DATA_FOUND
525 * @uses NO_PHP_SETTINGS_NAME_FOUND
526 * @param string $version xml version we are going to use to test this server
527 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
528 * @return array array of results encapsulated in one environment_result object
530 function environment_check_php_settings($version, $env_select) {
532 $results = array();
534 /// Get the enviroment version we need
535 if (!$data = get_environment_for_version($version, $env_select)) {
536 /// Error. No version data found
537 $result = new environment_results('php_setting');
538 $result->setStatus(false);
539 $result->setErrorCode(NO_VERSION_DATA_FOUND);
540 $results[] = $result;
541 return $results;
544 /// Extract the php_setting part
545 if (!isset($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'])) {
546 /// No PHP section found - ignore
547 return $results;
549 /// Iterate over settings checking them and creating the needed environment_results
550 foreach($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'] as $setting) {
551 $result = new environment_results('php_setting');
552 /// Check for level
553 $level = get_level($setting);
554 $result->setLevel($level);
555 /// Check for extension name
556 if (!isset($setting['@']['name'])) {
557 $result->setStatus(false);
558 $result->setErrorCode(NO_PHP_SETTINGS_NAME_FOUND);
559 } else {
560 $setting_name = $setting['@']['name'];
561 $setting_value = $setting['@']['value'];
562 $result->setInfo($setting_name);
564 if ($setting_name == 'memory_limit') {
565 $current = ini_get('memory_limit');
566 if ($current == -1) {
567 $result->setStatus(true);
568 } else {
569 $current = get_real_size($current);
570 $minlimit = get_real_size($setting_value);
571 if ($current < $minlimit) {
572 @ini_set('memory_limit', $setting_value);
573 $current = ini_get('memory_limit');
574 $current = get_real_size($current);
576 $result->setStatus($current >= $minlimit);
579 } else {
580 $current = ini_get_bool($setting_name);
581 /// The name exists. Just check if it's an installed extension
582 if ($current == $setting_value) {
583 $result->setStatus(true);
584 } else {
585 $result->setStatus(false);
590 /// Do any actions defined in the XML file.
591 process_environment_result($setting, $result);
593 /// Add the result to the array of results
594 $results[] = $result;
598 return $results;
602 * This function will do the custom checks.
604 * @global object
605 * @uses CUSTOM_CHECK_FUNCTION_MISSING
606 * @uses CUSTOM_CHECK_FILE_MISSING
607 * @uses NO_CUSTOM_CHECK_FOUND
608 * @param string $version xml version we are going to use to test this server.
609 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
610 * @return array array of results encapsulated in environment_result objects.
612 function environment_custom_checks($version, $env_select) {
613 global $CFG;
615 $results = array();
617 /// Get current Moodle version (release) for later compare
618 $release = isset($CFG->release) ? $CFG->release : $version; /// In case $CFG fails (at install) use $version
619 $current_version = normalize_version($release);
621 /// Get the enviroment version we need
622 if (!$data = get_environment_for_version($version, $env_select)) {
623 /// Error. No version data found - but this will already have been reported.
624 return $results;
627 /// Extract the CUSTOM_CHECKS part
628 if (!isset($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'])) {
629 /// No custom checks found - not a problem
630 return $results;
633 /// Iterate over extensions checking them and creating the needed environment_results
634 foreach($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'] as $check) {
635 $result = new environment_results('custom_check');
637 /// Check for level
638 $level = get_level($check);
640 /// Check for extension name
641 if (isset($check['@']['file']) && isset($check['@']['function'])) {
642 $file = $CFG->dirroot . '/' . $check['@']['file'];
643 $function = $check['@']['function'];
644 if (is_readable($file)) {
645 include_once($file);
646 if (function_exists($function)) {
647 $result->setLevel($level);
648 $result->setInfo($function);
649 $result = $function($result);
650 } else {
651 /// Only show error for current version (where function MUST exist)
652 /// else, we are performing custom checks against future versiosn
653 /// and function MAY not exist, so it doesn't cause error, just skip
654 /// custom check by returning null. MDL-15939
655 if (version_compare($current_version, $version, '>=')) {
656 $result->setStatus(false);
657 $result->setInfo($function);
658 $result->setErrorCode(CUSTOM_CHECK_FUNCTION_MISSING);
659 } else {
660 $result = null;
663 } else {
664 /// Only show error for current version (where function MUST exist)
665 /// else, we are performing custom checks against future versiosn
666 /// and function MAY not exist, so it doesn't cause error, just skip
667 /// custom check by returning null. MDL-15939
668 if (version_compare($current_version, $version, '>=')) {
669 $result->setStatus(false);
670 $result->setInfo($function);
671 $result->setErrorCode(CUSTOM_CHECK_FILE_MISSING);
672 } else {
673 $result = null;
676 } else {
677 $result->setStatus(false);
678 $result->setErrorCode(NO_CUSTOM_CHECK_FOUND);
681 if (!is_null($result)) {
682 /// Do any actions defined in the XML file.
683 process_environment_result($check, $result);
685 /// Add the result to the array of results
686 $results[] = $result;
690 return $results;
694 * This function will check if Moodle requirements are satisfied
696 * @uses NO_VERSION_DATA_FOUND
697 * @param string $version xml version we are going to use to test this server
698 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
699 * @return object results encapsulated in one environment_result object
701 function environment_check_moodle($version, $env_select) {
703 $result = new environment_results('moodle');
705 /// Get the enviroment version we need
706 if (!$data = get_environment_for_version($version, $env_select)) {
707 /// Error. No version data found
708 $result->setStatus(false);
709 $result->setErrorCode(NO_VERSION_DATA_FOUND);
710 return $result;
713 /// Extract the moodle part
714 if (!isset($data['@']['requires'])) {
715 $needed_version = '1.0'; /// Default to 1.0 if no moodle requires is found
716 } else {
717 /// Extract required moodle version
718 $needed_version = $data['@']['requires'];
721 /// Now search the version we are using
722 $release = get_config('', 'release');
723 $current_version = normalize_version($release);
724 if (strpos($release, 'dev') !== false) {
725 // when final version is required, dev is NOT enough!
726 $current_version = $current_version - 0.1;
729 /// And finally compare them, saving results
730 if (version_compare($current_version, $needed_version, '>=')) {
731 $result->setStatus(true);
732 } else {
733 $result->setStatus(false);
735 $result->setLevel('required');
736 $result->setCurrentVersion($release);
737 $result->setNeededVersion($needed_version);
739 return $result;
743 * This function will check if php requirements are satisfied
745 * @uses NO_VERSION_DATA_FOUND
746 * @uses NO_PHP_SECTION_FOUND
747 * @uses NO_PHP_VERSION_FOUND
748 * @param string $version xml version we are going to use to test this server
749 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
750 * @return object results encapsulated in one environment_result object
752 function environment_check_php($version, $env_select) {
754 $result = new environment_results('php');
756 /// Get the enviroment version we need
757 if (!$data = get_environment_for_version($version, $env_select)) {
758 /// Error. No version data found
759 $result->setStatus(false);
760 $result->setErrorCode(NO_VERSION_DATA_FOUND);
761 return $result;
764 /// Extract the php part
765 if (!isset($data['#']['PHP'])) {
766 /// Error. No PHP section found
767 $result->setStatus(false);
768 $result->setErrorCode(NO_PHP_SECTION_FOUND);
769 return $result;
770 } else {
771 /// Extract level and version
772 $level = get_level($data['#']['PHP']['0']);
773 if (!isset($data['#']['PHP']['0']['@']['version'])) {
774 $result->setStatus(false);
775 $result->setErrorCode(NO_PHP_VERSION_FOUND);
776 return $result;
777 } else {
778 $needed_version = $data['#']['PHP']['0']['@']['version'];
782 /// Now search the version we are using
783 $current_version = normalize_version(phpversion());
785 /// And finally compare them, saving results
786 if (version_compare($current_version, $needed_version, '>=')) {
787 $result->setStatus(true);
788 } else {
789 $result->setStatus(false);
791 $result->setLevel($level);
792 $result->setCurrentVersion($current_version);
793 $result->setNeededVersion($needed_version);
795 /// Do any actions defined in the XML file.
796 process_environment_result($data['#']['PHP'][0], $result);
798 return $result;
802 * Looks for buggy PCRE implementation, we need unicode support in Moodle...
803 * @param string $version xml version we are going to use to test this server
804 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
805 * @return stdClass results encapsulated in one environment_result object, null if irrelevant
807 function environment_check_pcre_unicode($version, $env_select) {
808 $result = new environment_results('pcreunicode');
810 // Get the environment 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 if (!isset($data['#']['PCREUNICODE'])) {
819 return null;
822 $level = get_level($data['#']['PCREUNICODE']['0']);
823 $result->setLevel($level);
825 if (!function_exists('preg_match')) {
826 // The extension test fails instead.
827 return null;
829 } else if (@preg_match('/\pL/u', 'a') and @preg_match('/á/iu', 'Á')) {
830 $result->setStatus(true);
832 } else {
833 $result->setStatus(false);
836 // Do any actions defined in the XML file.
837 process_environment_result($data['#']['PCREUNICODE'][0], $result);
839 return $result;
843 * This function will check if unicode database requirements are satisfied
845 * @global object
846 * @uses NO_VERSION_DATA_FOUND
847 * @uses NO_UNICODE_SECTION_FOUND
848 * @param string $version xml version we are going to use to test this server
849 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
850 * @return object results encapsulated in one environment_result object
852 function environment_check_unicode($version, $env_select) {
853 global $DB;
855 $result = new environment_results('unicode');
857 /// Get the enviroment version we need
858 if (!$data = get_environment_for_version($version, $env_select)) {
859 /// Error. No version data found
860 $result->setStatus(false);
861 $result->setErrorCode(NO_VERSION_DATA_FOUND);
862 return $result;
865 /// Extract the unicode part
867 if (!isset($data['#']['UNICODE'])) {
868 /// Error. No UNICODE section found
869 $result->setStatus(false);
870 $result->setErrorCode(NO_UNICODE_SECTION_FOUND);
871 return $result;
872 } else {
873 /// Extract level
874 $level = get_level($data['#']['UNICODE']['0']);
877 if (!$unicodedb = $DB->setup_is_unicodedb()) {
878 $result->setStatus(false);
879 } else {
880 $result->setStatus(true);
883 $result->setLevel($level);
885 /// Do any actions defined in the XML file.
886 process_environment_result($data['#']['UNICODE'][0], $result);
888 return $result;
892 * This function will check if database requirements are satisfied
894 * @global object
895 * @uses NO_VERSION_DATA_FOUND
896 * @uses NO_DATABASE_SECTION_FOUND
897 * @uses NO_DATABASE_VENDORS_FOUND
898 * @uses NO_DATABASE_VENDOR_MYSQL_FOUND
899 * @uses NO_DATABASE_VENDOR_POSTGRES_FOUND
900 * @uses NO_DATABASE_VENDOR_VERSION_FOUND
901 * @param string $version xml version we are going to use to test this server
902 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
903 * @return object results encapsulated in one environment_result object
905 function environment_check_database($version, $env_select) {
907 global $DB;
909 $result = new environment_results('database');
911 $vendors = array(); //Array of vendors in version
913 /// Get the enviroment version we need
914 if (!$data = get_environment_for_version($version, $env_select)) {
915 /// Error. No version data found
916 $result->setStatus(false);
917 $result->setErrorCode(NO_VERSION_DATA_FOUND);
918 return $result;
921 /// Extract the database part
922 if (!isset($data['#']['DATABASE'])) {
923 /// Error. No DATABASE section found
924 $result->setStatus(false);
925 $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
926 return $result;
927 } else {
928 /// Extract level
929 $level = get_level($data['#']['DATABASE']['0']);
932 /// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
933 if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
934 /// Error. No VENDORS found
935 $result->setStatus(false);
936 $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
937 return $result;
938 } else {
939 /// Extract vendors
940 foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
941 if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
942 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
943 $vendorsxml[$vendor['@']['name']] = $vendor;
947 /// Check we have the mysql vendor version
948 if (empty($vendors['mysql'])) {
949 $result->setStatus(false);
950 $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
951 return $result;
953 /// Check we have the postgres vendor version
954 if (empty($vendors['postgres'])) {
955 $result->setStatus(false);
956 $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
957 return $result;
960 /// Now search the version we are using (depending of vendor)
961 $current_vendor = $DB->get_dbfamily();
963 $dbinfo = $DB->get_server_info();
964 $current_version = normalize_version($dbinfo['version']);
965 $needed_version = $vendors[$current_vendor];
967 /// Check we have a needed version
968 if (!$needed_version) {
969 $result->setStatus(false);
970 $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND);
971 return $result;
974 /// And finally compare them, saving results
975 if (version_compare($current_version, $needed_version, '>=')) {
976 $result->setStatus(true);
977 } else {
978 $result->setStatus(false);
980 $result->setLevel($level);
981 $result->setCurrentVersion($current_version);
982 $result->setNeededVersion($needed_version);
983 $result->setInfo($current_vendor);
985 /// Do any actions defined in the XML file.
986 process_environment_result($vendorsxml[$current_vendor], $result);
988 return $result;
993 * This function will post-process the result record by executing the specified
994 * function, modifying it as necessary, also a custom message will be added
995 * to the result object to be printed by the display layer.
996 * Every bypass function must be defined in this file and it'll return
997 * true/false to decide if the original test is bypassed or no. Also
998 * such bypass functions are able to directly handling the result object
999 * although it should be only under exceptional conditions.
1001 * @param string xmldata containing the bypass data
1002 * @param object result object to be updated
1003 * @return void
1005 function process_environment_bypass($xml, &$result) {
1007 /// Only try to bypass if we were in error and it was required
1008 if ($result->getStatus() || $result->getLevel() == 'optional') {
1009 return;
1012 /// It there is bypass info (function and message)
1013 if (is_array($xml['#']) && isset($xml['#']['BYPASS'][0]['@']['function']) && isset($xml['#']['BYPASS'][0]['@']['message'])) {
1014 $function = $xml['#']['BYPASS'][0]['@']['function'];
1015 $message = $xml['#']['BYPASS'][0]['@']['message'];
1016 /// Look for the function
1017 if (function_exists($function)) {
1018 /// Call it, and if bypass = true is returned, apply meesage
1019 if ($function($result)) {
1020 /// We only set the bypass message if the function itself hasn't defined it before
1021 if (empty($result->getBypassStr)) {
1022 $result->setBypassStr($message);
1030 * This function will post-process the result record by executing the specified
1031 * function, modifying it as necessary, also a custom message will be added
1032 * to the result object to be printed by the display layer.
1033 * Every restrict function must be defined in this file and it'll return
1034 * true/false to decide if the original test is restricted or no. Also
1035 * such restrict functions are able to directly handling the result object
1036 * although it should be only under exceptional conditions.
1038 * @param string xmldata containing the restrict data
1039 * @param object result object to be updated
1040 * @return void
1042 function process_environment_restrict($xml, &$result) {
1044 /// Only try to restrict if we were not in error and it was required
1045 if (!$result->getStatus() || $result->getLevel() == 'optional') {
1046 return;
1048 /// It there is restrict info (function and message)
1049 if (is_array($xml['#']) && isset($xml['#']['RESTRICT'][0]['@']['function']) && isset($xml['#']['RESTRICT'][0]['@']['message'])) {
1050 $function = $xml['#']['RESTRICT'][0]['@']['function'];
1051 $message = $xml['#']['RESTRICT'][0]['@']['message'];
1052 /// Look for the function
1053 if (function_exists($function)) {
1054 /// Call it, and if restrict = true is returned, apply meesage
1055 if ($function($result)) {
1056 /// We only set the restrict message if the function itself hasn't defined it before
1057 if (empty($result->getRestrictStr)) {
1058 $result->setRestrictStr($message);
1066 * This function will detect if there is some message available to be added to the
1067 * result in order to clarify enviromental details.
1069 * @param string xmldata containing the feedback data
1070 * @param object reult object to be updated
1072 function process_environment_messages($xml, &$result) {
1074 /// If there is feedback info
1075 if (is_array($xml['#']) && isset($xml['#']['FEEDBACK'][0]['#'])) {
1076 $feedbackxml = $xml['#']['FEEDBACK'][0]['#'];
1078 if (!$result->status and $result->getLevel() == 'required') {
1079 if (isset($feedbackxml['ON_ERROR'][0]['@']['message'])) {
1080 $result->setFeedbackStr($feedbackxml['ON_ERROR'][0]['@']['message']);
1082 } else if (!$result->status and $result->getLevel() == 'optional') {
1083 if (isset($feedbackxml['ON_CHECK'][0]['@']['message'])) {
1084 $result->setFeedbackStr($feedbackxml['ON_CHECK'][0]['@']['message']);
1086 } else {
1087 if (isset($feedbackxml['ON_OK'][0]['@']['message'])) {
1088 $result->setFeedbackStr($feedbackxml['ON_OK'][0]['@']['message']);
1095 //--- Helper Class to return results to caller ---//
1099 * Helper Class to return results to caller
1101 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1102 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1103 * @package moodlecore
1105 class environment_results {
1107 * @var string Which are we checking (database, php, php_extension, php_extension)
1109 var $part;
1111 * @var bool
1113 var $status;
1115 * @var integer See constants at the beginning of the file
1117 var $error_code;
1119 * @var string required/optional
1121 var $level;
1123 * @var string current version detected
1125 var $current_version;
1127 * @var string version needed
1129 var $needed_version;
1131 * @var string Aux. info (DB vendor, library...)
1133 var $info;
1135 * @var string String to show on error|on check|on ok
1137 var $feedback_str;
1139 * @var string String to show if some bypass has happened
1141 var $bypass_str;
1143 * @var string String to show if some restrict has happened
1145 var $restrict_str;
1148 * Constructor of the environment_result class. Just set default values
1150 * @param string $part
1152 function environment_results($part) {
1153 $this->part=$part;
1154 $this->status=false;
1155 $this->error_code=NO_ERROR;
1156 $this->level='required';
1157 $this->current_version='';
1158 $this->needed_version='';
1159 $this->info='';
1160 $this->feedback_str='';
1161 $this->bypass_str='';
1162 $this->restrict_str='';
1166 * Set the status
1168 * @param boolean $status the status (true/false)
1170 function setStatus($status) {
1171 $this->status=$status;
1172 if ($status) {
1173 $this->setErrorCode(NO_ERROR);
1178 * Set the error_code
1180 * @param integer $error_code the error code (see constants above)
1182 function setErrorCode($error_code) {
1183 $this->error_code=$error_code;
1187 * Set the level
1189 * @param string $level the level (required, optional)
1191 function setLevel($level) {
1192 $this->level=$level;
1196 * Set the current version
1198 * @param string $current_version the current version
1200 function setCurrentVersion($current_version) {
1201 $this->current_version=$current_version;
1205 * Set the needed version
1207 * @param string $needed_version the needed version
1209 function setNeededVersion($needed_version) {
1210 $this->needed_version=$needed_version;
1214 * Set the auxiliary info
1216 * @param string $info the auxiliary info
1218 function setInfo($info) {
1219 $this->info=$info;
1223 * Set the feedback string
1225 * @param mixed $str the feedback string that will be fetched from the admin lang file.
1226 * pass just the string or pass an array of params for get_string
1227 * You always should put your string in admin.php but a third param is useful
1228 * to pass an $a object / string to get_string
1230 function setFeedbackStr($str) {
1231 $this->feedback_str=$str;
1236 * Set the bypass string
1238 * @param string $str the bypass string that will be fetched from the admin lang file.
1239 * pass just the string or pass an array of params for get_string
1240 * You always should put your string in admin.php but a third param is useful
1241 * to pass an $a object / string to get_string
1243 function setBypassStr($str) {
1244 $this->bypass_str=$str;
1248 * Set the restrict string
1250 * @param string $str the restrict string that will be fetched from the admin lang file.
1251 * pass just the string or pass an array of params for get_string
1252 * You always should put your string in admin.php but a third param is useful
1253 * to pass an $a object / string to get_string
1255 function setRestrictStr($str) {
1256 $this->restrict_str=$str;
1260 * Get the status
1262 * @return boolean result
1264 function getStatus() {
1265 return $this->status;
1269 * Get the error code
1271 * @return integer error code
1273 function getErrorCode() {
1274 return $this->error_code;
1278 * Get the level
1280 * @return string level
1282 function getLevel() {
1283 return $this->level;
1287 * Get the current version
1289 * @return string current version
1291 function getCurrentVersion() {
1292 return $this->current_version;
1296 * Get the needed version
1298 * @return string needed version
1300 function getNeededVersion() {
1301 return $this->needed_version;
1305 * Get the aux info
1307 * @return string info
1309 function getInfo() {
1310 return $this->info;
1314 * Get the part this result belongs to
1316 * @return string part
1318 function getPart() {
1319 return $this->part;
1323 * Get the feedback string
1325 * @return mixed feedback string (can be an array of params for get_string or a single string to fetch from
1326 * admin.php lang file).
1328 function getFeedbackStr() {
1329 return $this->feedback_str;
1333 * Get the bypass string
1335 * @return mixed bypass string (can be an array of params for get_string or a single string to fetch from
1336 * admin.php lang file).
1338 function getBypassStr() {
1339 return $this->bypass_str;
1343 * Get the restrict string
1345 * @return mixed restrict string (can be an array of params for get_string or a single string to fetch from
1346 * admin.php lang file).
1348 function getRestrictStr() {
1349 return $this->restrict_str;
1353 * @todo Document this function
1355 * @param mixed $string params for get_string, either a string to fetch from admin.php or an array of
1356 * params for get_string.
1357 * @param string $class css class(es) for message.
1358 * @return string feedback string fetched from lang file wrapped in p tag with class $class or returns
1359 * empty string if $string is empty.
1361 function strToReport($string, $class){
1362 if (!empty($string)){
1363 if (is_array($string)){
1364 $str = call_user_func_array('get_string', $string);
1365 } else {
1366 $str = get_string($string, 'admin');
1368 return '<p class="'.$class.'">'.$str.'</p>';
1369 } else {
1370 return '';
1375 /// Here all the bypass functions are coded to be used by the environment
1376 /// checker. All those functions will receive the result object and will
1377 /// return it modified as needed (status and bypass string)
1380 * This function will bypass MySQL 4.1.16 reqs if:
1381 * - We are using MySQL > 4.1.12, informing about problems with non latin chars in the future
1383 * @param object result object to handle
1384 * @return boolean true/false to determinate if the bypass has to be performed (true) or no (false)
1386 function bypass_mysql416_reqs ($result) {
1387 /// See if we are running MySQL >= 4.1.12
1388 if (version_compare($result->getCurrentVersion(), '4.1.12', '>=')) {
1389 return true;
1392 return false;
1395 /// Here all the restrict functions are coded to be used by the environment
1396 /// checker. All those functions will receive the result object and will
1397 /// return it modified as needed (status and bypass string)
1400 * This function will restrict PHP reqs if:
1401 * - We are using PHP 5.0.x, informing about the buggy version
1403 * @param object $result object to handle
1404 * @return boolean true/false to determinate if the restrict has to be performed (true) or no (false)
1406 function restrict_php50_version($result) {
1407 if (version_compare($result->getCurrentVersion(), '5.0.0', '>=')
1408 and version_compare($result->getCurrentVersion(), '5.0.99', '<')) {
1409 return true;
1411 return false;
1415 * @param array $element the element from the environment.xml file that should have
1416 * either a level="required" or level="optional" attribute.
1417 * @return string "required" or "optional".
1419 function get_level($element) {
1420 $level = 'required';
1421 if (isset($element['@']['level'])) {
1422 $level = $element['@']['level'];
1423 if (!in_array($level, array('required', 'optional'))) {
1424 debugging('The level of a check in the environment.xml file must be "required" or "optional".', DEBUG_DEVELOPER);
1425 $level = 'required';
1427 } else {
1428 debugging('Checks in the environment.xml file must have a level="required" or level="optional" attribute.', DEBUG_DEVELOPER);
1430 return $level;
1434 * Once the result has been determined, look in the XML for any
1435 * messages, or other things that should be done depending on the outcome.
1437 * @param array $element the element from the environment.xml file which
1438 * may have children defining what should be done with the outcome.
1439 * @param object $result the result of the test, which may be modified by
1440 * this function as specified in the XML.
1442 function process_environment_result($element, &$result) {
1443 /// Process messages, modifying the $result if needed.
1444 process_environment_messages($element, $result);
1445 /// Process bypass, modifying $result if needed.
1446 process_environment_bypass($element, $result);
1447 /// Process restrict, modifying $result if needed.
1448 process_environment_restrict($element, $result);