weekly release 2.1.7+
[moodle.git] / lib / environmentlib.php
blobf530b043604d0f710f3e14dc4c27971d3e3887f8
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 will perform the whole check, returning
84 * true or false as final result. Also, he full array of
85 * environment_result will be returned in the parameter list.
86 * The function looks for the best version to compare and
87 * everything. This is the only function that should be called
88 * ever from the rest of Moodle.
90 * @staticvar bool $result
91 * @staticvar array $env_results
92 * @staticvar bool $cache_exists
93 * @param string $version version to check.
94 * @param array $environment_results results array of results checked.
95 * @param boolean $print_table true/false, whether to print the table or just return results array
96 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. Default ENV_SELECT_NEWER (BC)
97 * @return boolean true/false, depending of results
99 function check_moodle_environment($version, &$environment_results, $print_table=true, $env_select=ENV_SELECT_NEWER) {
101 $status = true;
103 /// This are cached per request
104 static $result = true;
105 static $env_results;
106 static $cache_exists = false;
108 /// if we have results cached, use them
109 if ($cache_exists) {
110 $environment_results = $env_results;
111 /// No cache exists, calculate everything
112 } else {
113 /// Get the more recent version before the requested
114 if (!$version = get_latest_version_available($version, $env_select)) {
115 $status = false;
118 /// Perform all the checks
119 if (!($environment_results = environment_check($version, $env_select)) && $status) {
120 $status = false;
123 /// Iterate over all the results looking for some error in required items
124 /// or some error_code
125 if ($status) {
126 foreach ($environment_results as $environment_result) {
127 if (!$environment_result->getStatus() && $environment_result->getLevel() == 'required'
128 && !$environment_result->getBypassStr()) {
129 $result = false; // required item that is not bypased
130 } else if ($environment_result->getStatus() && $environment_result->getLevel() == 'required'
131 && $environment_result->getRestrictStr()) {
132 $result = false; // required item that is restricted
133 } else if ($environment_result->getErrorCode()) {
134 $result = false;
138 /// Going to end, we store environment_results to cache
139 $env_results = $environment_results;
140 $cache_exists = true;
141 } ///End of cache block
143 /// If we have decided to print all the information, just do it
144 if ($print_table) {
145 print_moodle_environment($result && $status, $environment_results);
147 return ($result && $status);
151 * This function will print one beautiful table with all the environmental
152 * configuration and how it suits Moodle needs.
154 * @global object
155 * @param boolean $result final result of the check (true/false)
156 * @param array $environment_results array of results gathered
157 * @return void
159 function print_moodle_environment($result, $environment_results) {
160 global $CFG, $OUTPUT;
162 /// Get some strings
163 $strname = get_string('name');
164 $strinfo = get_string('info');
165 $strreport = get_string('report');
166 $strstatus = get_string('status');
167 $strok = get_string('ok');
168 $strerror = get_string('error');
169 $strcheck = get_string('check');
170 $strbypassed = get_string('bypassed');
171 $strrestricted = get_string('restricted');
172 $strenvironmenterrortodo = get_string('environmenterrortodo', 'admin');
173 /// Table headers
174 $servertable = new html_table();//table for server checks
175 $servertable->head = array ($strname, $strinfo, $strreport, $strstatus);
176 $servertable->align = array ('center', 'center', 'left', 'center');
177 $servertable->wrap = array ('nowrap', '', '', 'nowrap');
178 $servertable->size = array ('10', 10, '100%', '10');
179 $servertable->attributes['class'] = 'environmenttable generaltable';
181 $serverdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
183 $othertable = new html_table();//table for custom checks
184 $othertable->head = array ($strinfo, $strreport, $strstatus);
185 $othertable->align = array ('center', 'left', 'center');
186 $othertable->wrap = array ('', '', 'nowrap');
187 $othertable->size = array (10, '100%', '10');
188 $othertable->attributes['class'] = 'environmenttable generaltable';
190 $otherdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
192 /// Iterate over each environment_result
193 $continue = true;
194 foreach ($environment_results as $environment_result) {
195 $errorline = false;
196 $warningline = false;
197 $stringtouse = '';
198 if ($continue) {
199 $type = $environment_result->getPart();
200 $info = $environment_result->getInfo();
201 $status = $environment_result->getStatus();
202 $error_code = $environment_result->getErrorCode();
203 /// Process Report field
204 $rec = new stdClass();
205 /// Something has gone wrong at parsing time
206 if ($error_code) {
207 $stringtouse = 'environmentxmlerror';
208 $rec->error_code = $error_code;
209 $status = $strerror;
210 $errorline = true;
211 $continue = false;
214 if ($continue) {
215 /// We are comparing versions
216 if ($rec->needed = $environment_result->getNeededVersion()) {
217 $rec->current = $environment_result->getCurrentVersion();
218 if ($environment_result->getLevel() == 'required') {
219 $stringtouse = 'environmentrequireversion';
220 } else {
221 $stringtouse = 'environmentrecommendversion';
223 /// We are checking installed & enabled things
224 } else if ($environment_result->getPart() == 'custom_check') {
225 if ($environment_result->getLevel() == 'required') {
226 $stringtouse = 'environmentrequirecustomcheck';
227 } else {
228 $stringtouse = 'environmentrecommendcustomcheck';
230 } else if ($environment_result->getPart() == 'php_setting') {
231 if ($status) {
232 $stringtouse = 'environmentsettingok';
233 } else if ($environment_result->getLevel() == 'required') {
234 $stringtouse = 'environmentmustfixsetting';
235 } else {
236 $stringtouse = 'environmentshouldfixsetting';
238 } else {
239 if ($environment_result->getLevel() == 'required') {
240 $stringtouse = 'environmentrequireinstall';
241 } else {
242 $stringtouse = 'environmentrecommendinstall';
245 /// Calculate the status value
246 if ($environment_result->getBypassStr() != '') { //Handle bypassed result (warning)
247 $status = $strbypassed;
248 $warningline = true;
249 } else if ($environment_result->getRestrictStr() != '') { //Handle restricted result (error)
250 $status = $strrestricted;
251 $errorline = true;
252 } else {
253 if ($status) { //Handle ok result (ok)
254 $status = $strok;
255 } else {
256 if ($environment_result->getLevel() == 'optional') {//Handle check result (warning)
257 $status = $strcheck;
258 $warningline = true;
259 } else { //Handle error result (error)
260 $status = $strcheck;
261 $errorline = true;
267 /// Build the text
268 $linkparts = array();
269 $linkparts[] = 'admin/environment';
270 $linkparts[] = $type;
271 if (!empty($info)){
272 $linkparts[] = $info;
274 if (empty($CFG->docroot)) {
275 $report = get_string($stringtouse, 'admin', $rec);
276 } else {
277 $report = $OUTPUT->doc_link(join($linkparts, '/'), get_string($stringtouse, 'admin', $rec));
281 /// Format error or warning line
282 if ($errorline || $warningline) {
283 $messagetype = $errorline? 'error':'warn';
284 } else {
285 $messagetype = 'ok';
287 $status = '<span class="'.$messagetype.'">'.$status.'</span>';
288 /// Here we'll store all the feedback found
289 $feedbacktext = '';
290 ///Append the feedback if there is some
291 $feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), $messagetype);
292 ///Append the bypass if there is some
293 $feedbacktext .= $environment_result->strToReport($environment_result->getBypassStr(), 'warn');
294 ///Append the restrict if there is some
295 $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
297 $report .= $feedbacktext;
298 /// Add the row to the table
300 if ($environment_result->getPart() == 'custom_check'){
301 $otherdata[$messagetype][] = array ($info, $report, $status);
302 } else {
303 $serverdata[$messagetype][] = array ($type, $info, $report, $status);
307 //put errors first in
308 $servertable->data = array_merge($serverdata['error'], $serverdata['warn'], $serverdata['ok']);
309 $othertable->data = array_merge($otherdata['error'], $otherdata['warn'], $otherdata['ok']);
311 /// Print table
312 echo $OUTPUT->heading(get_string('serverchecks', 'admin'));
313 echo html_writer::table($servertable);
314 if (count($othertable->data)){
315 echo $OUTPUT->heading(get_string('customcheck', 'admin'));
316 echo html_writer::table($othertable);
319 /// Finally, if any error has happened, print the summary box
320 if (!$result) {
321 echo $OUTPUT->box($strenvironmenterrortodo, 'environmentbox errorbox');
327 * Returns array of critical errors in plain text format
328 * @param array $environment_results array of results gathered
329 * @return array errors
331 function environment_get_errors($environment_results) {
332 global $CFG;
333 $errors = array();
335 // Iterate over each environment_result
336 foreach ($environment_results as $environment_result) {
337 $type = $environment_result->getPart();
338 $info = $environment_result->getInfo();
339 $status = $environment_result->getStatus();
340 $error_code = $environment_result->getErrorCode();
342 $a = new stdClass();
343 if ($error_code) {
344 $a->error_code = $error_code;
345 $errors[] = array($info, get_string('environmentxmlerror', 'admin', $a));
346 return $errors;
349 /// Calculate the status value
350 if ($environment_result->getBypassStr() != '') {
351 // not interesting
352 continue;
353 } else if ($environment_result->getRestrictStr() != '') {
354 // error
355 } else {
356 if ($status) {
357 // ok
358 continue;
359 } else {
360 if ($environment_result->getLevel() == 'optional') {
361 // just a warning
362 continue;
363 } else {
364 // error
369 // We are comparing versions
370 $rec = new stdClass();
371 if ($rec->needed = $environment_result->getNeededVersion()) {
372 $rec->current = $environment_result->getCurrentVersion();
373 if ($environment_result->getLevel() == 'required') {
374 $stringtouse = 'environmentrequireversion';
375 } else {
376 $stringtouse = 'environmentrecommendversion';
378 // We are checking installed & enabled things
379 } else if ($environment_result->getPart() == 'custom_check') {
380 if ($environment_result->getLevel() == 'required') {
381 $stringtouse = 'environmentrequirecustomcheck';
382 } else {
383 $stringtouse = 'environmentrecommendcustomcheck';
385 } else if ($environment_result->getPart() == 'php_setting') {
386 if ($status) {
387 $stringtouse = 'environmentsettingok';
388 } else if ($environment_result->getLevel() == 'required') {
389 $stringtouse = 'environmentmustfixsetting';
390 } else {
391 $stringtouse = 'environmentshouldfixsetting';
393 } else {
394 if ($environment_result->getLevel() == 'required') {
395 $stringtouse = 'environmentrequireinstall';
396 } else {
397 $stringtouse = 'environmentrecommendinstall';
400 $report = get_string($stringtouse, 'admin', $rec);
402 // Here we'll store all the feedback found
403 $feedbacktext = '';
404 // Append the feedback if there is some
405 $feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), 'error');
406 // Append the restrict if there is some
407 $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
409 $report .= html_to_text($feedbacktext);
411 if ($environment_result->getPart() == 'custom_check'){
412 $errors[] = array($info, $report);
413 } else {
414 $errors[] = array(($info !== '' ? "$type $info" : $type), $report);
418 return $errors;
423 * This function will normalize any version to just a serie of numbers
424 * separated by dots. Everything else will be removed.
426 * @param string $version the original version
427 * @return string the normalized version
429 function normalize_version($version) {
431 /// 1.9 Beta 2 should be read 1.9 on enviromental checks, not 1.9.2
432 /// we can discard everything after the first space
433 $version = trim($version);
434 $versionarr = explode(" ",$version);
435 if (!empty($versionarr)) {
436 $version = $versionarr[0];
438 /// Replace everything but numbers and dots by dots
439 $version = preg_replace('/[^\.\d]/', '.', $version);
440 /// Combine multiple dots in one
441 $version = preg_replace('/(\.{2,})/', '.', $version);
442 /// Trim possible leading and trailing dots
443 $version = trim($version, '.');
445 return $version;
450 * This function will load the environment.xml file and xmlize it
452 * @global object
453 * @staticvar mixed $data
454 * @uses ENV_SELECT_NEWER
455 * @uses ENV_SELECT_DATAROOT
456 * @uses ENV_SELECT_RELEASE
457 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. Default ENV_SELECT_NEWER (BC)
458 * @return mixed the xmlized structure or false on error
460 function load_environment_xml($env_select=ENV_SELECT_NEWER) {
462 global $CFG;
464 static $data; //Only load and xmlize once by request
466 if (!empty($data)) {
467 return $data;
470 /// First of all, take a look inside $CFG->dataroot/environment/environment.xml
471 $file = $CFG->dataroot.'/environment/environment.xml';
472 $internalfile = $CFG->dirroot.'/'.$CFG->admin.'/environment.xml';
473 switch ($env_select) {
474 case ENV_SELECT_NEWER:
475 if (!is_file($file) || !is_readable($file) || filemtime($file) < filemtime($internalfile) ||
476 !$contents = file_get_contents($file)) {
477 /// Fallback to fixed $CFG->admin/environment.xml
478 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
479 return false;
482 break;
483 case ENV_SELECT_DATAROOT:
484 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
485 return false;
487 break;
488 case ENV_SELECT_RELEASE:
489 if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
490 return false;
492 break;
494 /// XML the whole file
495 $data = xmlize($contents);
497 return $data;
502 * This function will return the list of Moodle versions available
504 * @staticvar array $versions
505 * @return mixed array of versions. False on error.
507 function get_list_of_environment_versions ($contents) {
509 static $versions = array();
511 if (!empty($versions)) {
512 return $versions;
515 if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
516 foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
517 $versions[] = $version['@']['version'];
521 return $versions;
526 * This function will return the most recent version in the environment.xml
527 * file previous or equal to the version requested
529 * @param string $version top version from which we start to look backwards
530 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
531 * @return string|bool string more recent version or false if not found
533 function get_latest_version_available ($version, $env_select) {
535 /// Normalize the version requested
536 $version = normalize_version($version);
538 /// Load xml file
539 if (!$contents = load_environment_xml($env_select)) {
540 return false;
543 /// Detect available versions
544 if (!$versions = get_list_of_environment_versions($contents)) {
545 return false;
547 /// First we look for exact version
548 if (in_array($version, $versions)) {
549 return $version;
550 } else {
551 $found_version = false;
552 /// Not exact match, so we are going to iterate over the list searching
553 /// for the latest version before the requested one
554 foreach ($versions as $arrversion) {
555 if (version_compare($arrversion, $version, '<')) {
556 $found_version = $arrversion;
561 return $found_version;
566 * This function will return the xmlized data belonging to one Moodle version
568 * @param string $version top version from which we start to look backwards
569 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
570 * @return mixed the xmlized structure or false on error
572 function get_environment_for_version($version, $env_select) {
574 /// Normalize the version requested
575 $version = normalize_version($version);
577 /// Load xml file
578 if (!$contents = load_environment_xml($env_select)) {
579 return false;
582 /// Detect available versions
583 if (!$versions = get_list_of_environment_versions($contents)) {
584 return false;
587 /// If the version requested is available
588 if (!in_array($version, $versions)) {
589 return false;
592 /// We now we have it. Extract from full contents.
593 $fl_arr = array_flip($versions);
595 return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
600 * This function will check for everything (DB, PHP and PHP extensions for now)
601 * returning an array of environment_result objects.
603 * @global object
604 * @param string $version xml version we are going to use to test this server
605 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
606 * @return array array of results encapsulated in one environment_result object
608 function environment_check($version, $env_select) {
609 global $CFG;
611 /// Normalize the version requested
612 $version = normalize_version($version);
614 $results = array(); //To store all the results
616 /// Only run the moodle versions checker on upgrade, not on install
617 if (!empty($CFG->version)) {
618 $results[] = environment_check_moodle($version, $env_select);
620 $results[] = environment_check_unicode($version, $env_select);
621 $results[] = environment_check_database($version, $env_select);
622 $results[] = environment_check_php($version, $env_select);
624 $phpext_results = environment_check_php_extensions($version, $env_select);
625 $results = array_merge($results, $phpext_results);
627 $phpsetting_results = environment_check_php_settings($version, $env_select);
628 $results = array_merge($results, $phpsetting_results);
630 $custom_results = environment_custom_checks($version, $env_select);
631 $results = array_merge($results, $custom_results);
633 return $results;
638 * This function will check if php extensions requirements are satisfied
640 * @uses NO_VERSION_DATA_FOUND
641 * @uses NO_PHP_EXTENSIONS_SECTION_FOUND
642 * @uses NO_PHP_EXTENSIONS_NAME_FOUND
643 * @param string $version xml version we are going to use to test this server
644 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
645 * @return array array of results encapsulated in one environment_result object
647 function environment_check_php_extensions($version, $env_select) {
649 $results = array();
651 /// Get the enviroment version we need
652 if (!$data = get_environment_for_version($version, $env_select)) {
653 /// Error. No version data found
654 $result = new environment_results('php_extension');
655 $result->setStatus(false);
656 $result->setErrorCode(NO_VERSION_DATA_FOUND);
657 return array($result);
660 /// Extract the php_extension part
661 if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
662 /// Error. No PHP section found
663 $result = new environment_results('php_extension');
664 $result->setStatus(false);
665 $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
666 return array($result);
668 /// Iterate over extensions checking them and creating the needed environment_results
669 foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
670 $result = new environment_results('php_extension');
671 /// Check for level
672 $level = get_level($extension);
673 /// Check for extension name
674 if (!isset($extension['@']['name'])) {
675 $result->setStatus(false);
676 $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
677 } else {
678 $extension_name = $extension['@']['name'];
679 /// The name exists. Just check if it's an installed extension
680 if (!extension_loaded($extension_name)) {
681 $result->setStatus(false);
682 } else {
683 $result->setStatus(true);
685 $result->setLevel($level);
686 $result->setInfo($extension_name);
689 /// Do any actions defined in the XML file.
690 process_environment_result($extension, $result);
692 /// Add the result to the array of results
693 $results[] = $result;
697 return $results;
701 * This function will check if php extensions requirements are satisfied
703 * @uses NO_VERSION_DATA_FOUND
704 * @uses NO_PHP_SETTINGS_NAME_FOUND
705 * @param string $version xml version we are going to use to test this server
706 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
707 * @return array array of results encapsulated in one environment_result object
709 function environment_check_php_settings($version, $env_select) {
711 $results = array();
713 /// Get the enviroment version we need
714 if (!$data = get_environment_for_version($version, $env_select)) {
715 /// Error. No version data found
716 $result = new environment_results('php_setting');
717 $result->setStatus(false);
718 $result->setErrorCode(NO_VERSION_DATA_FOUND);
719 $results[] = $result;
720 return $results;
723 /// Extract the php_setting part
724 if (!isset($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'])) {
725 /// No PHP section found - ignore
726 return $results;
728 /// Iterate over settings checking them and creating the needed environment_results
729 foreach($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'] as $setting) {
730 $result = new environment_results('php_setting');
731 /// Check for level
732 $level = get_level($setting);
733 $result->setLevel($level);
734 /// Check for extension name
735 if (!isset($setting['@']['name'])) {
736 $result->setStatus(false);
737 $result->setErrorCode(NO_PHP_SETTINGS_NAME_FOUND);
738 } else {
739 $setting_name = $setting['@']['name'];
740 $setting_value = $setting['@']['value'];
741 $result->setInfo($setting_name);
743 if ($setting_name == 'memory_limit') {
744 $current = ini_get('memory_limit');
745 if ($current == -1) {
746 $result->setStatus(true);
747 } else {
748 $current = get_real_size($current);
749 $minlimit = get_real_size($setting_value);
750 if ($current < $minlimit) {
751 @ini_set('memory_limit', $setting_value);
752 $current = ini_get('memory_limit');
753 $current = get_real_size($current);
755 $result->setStatus($current >= $minlimit);
758 } else {
759 $current = ini_get_bool($setting_name);
760 /// The name exists. Just check if it's an installed extension
761 if ($current == $setting_value) {
762 $result->setStatus(true);
763 } else {
764 $result->setStatus(false);
769 /// Do any actions defined in the XML file.
770 process_environment_result($setting, $result);
772 /// Add the result to the array of results
773 $results[] = $result;
777 return $results;
781 * This function will do the custom checks.
783 * @global object
784 * @uses CUSTOM_CHECK_FUNCTION_MISSING
785 * @uses CUSTOM_CHECK_FILE_MISSING
786 * @uses NO_CUSTOM_CHECK_FOUND
787 * @param string $version xml version we are going to use to test this server.
788 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
789 * @return array array of results encapsulated in environment_result objects.
791 function environment_custom_checks($version, $env_select) {
792 global $CFG;
794 $results = array();
796 /// Get current Moodle version (release) for later compare
797 $release = isset($CFG->release) ? $CFG->release : $version; /// In case $CFG fails (at install) use $version
798 $current_version = normalize_version($release);
800 /// Get the enviroment version we need
801 if (!$data = get_environment_for_version($version, $env_select)) {
802 /// Error. No version data found - but this will already have been reported.
803 return $results;
806 /// Extract the CUSTOM_CHECKS part
807 if (!isset($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'])) {
808 /// No custom checks found - not a problem
809 return $results;
812 /// Iterate over extensions checking them and creating the needed environment_results
813 foreach($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'] as $check) {
814 $result = new environment_results('custom_check');
816 /// Check for level
817 $level = get_level($check);
819 /// Check for extension name
820 if (isset($check['@']['file']) && isset($check['@']['function'])) {
821 $file = $CFG->dirroot . '/' . $check['@']['file'];
822 $function = $check['@']['function'];
823 if (is_readable($file)) {
824 include_once($file);
825 if (function_exists($function)) {
826 $result->setLevel($level);
827 $result->setInfo($function);
828 $result = $function($result);
829 } else {
830 /// Only show error for current version (where function MUST exist)
831 /// else, we are performing custom checks against future versiosn
832 /// and function MAY not exist, so it doesn't cause error, just skip
833 /// custom check by returning null. MDL-15939
834 if (version_compare($current_version, $version, '>=')) {
835 $result->setStatus(false);
836 $result->setInfo($function);
837 $result->setErrorCode(CUSTOM_CHECK_FUNCTION_MISSING);
838 } else {
839 $result = null;
842 } else {
843 /// Only show error for current version (where function MUST exist)
844 /// else, we are performing custom checks against future versiosn
845 /// and function MAY not exist, so it doesn't cause error, just skip
846 /// custom check by returning null. MDL-15939
847 if (version_compare($current_version, $version, '>=')) {
848 $result->setStatus(false);
849 $result->setInfo($function);
850 $result->setErrorCode(CUSTOM_CHECK_FILE_MISSING);
851 } else {
852 $result = null;
855 } else {
856 $result->setStatus(false);
857 $result->setErrorCode(NO_CUSTOM_CHECK_FOUND);
860 if (!is_null($result)) {
861 /// Do any actions defined in the XML file.
862 process_environment_result($check, $result);
864 /// Add the result to the array of results
865 $results[] = $result;
869 return $results;
873 * This function will check if Moodle requirements are satisfied
875 * @uses NO_VERSION_DATA_FOUND
876 * @param string $version xml version we are going to use to test this server
877 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
878 * @return object results encapsulated in one environment_result object
880 function environment_check_moodle($version, $env_select) {
882 $result = new environment_results('moodle');
884 /// Get the enviroment version we need
885 if (!$data = get_environment_for_version($version, $env_select)) {
886 /// Error. No version data found
887 $result->setStatus(false);
888 $result->setErrorCode(NO_VERSION_DATA_FOUND);
889 return $result;
892 /// Extract the moodle part
893 if (!isset($data['@']['requires'])) {
894 $needed_version = '1.0'; /// Default to 1.0 if no moodle requires is found
895 } else {
896 /// Extract required moodle version
897 $needed_version = $data['@']['requires'];
900 /// Now search the version we are using
901 $current_version = normalize_version(get_config('', 'release'));
903 /// And finally compare them, saving results
904 if (version_compare($current_version, $needed_version, '>=')) {
905 $result->setStatus(true);
906 } else {
907 $result->setStatus(false);
909 $result->setLevel('required');
910 $result->setCurrentVersion($current_version);
911 $result->setNeededVersion($needed_version);
913 return $result;
917 * This function will check if php requirements are satisfied
919 * @uses NO_VERSION_DATA_FOUND
920 * @uses NO_PHP_SECTION_FOUND
921 * @uses NO_PHP_VERSION_FOUND
922 * @param string $version xml version we are going to use to test this server
923 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
924 * @return object results encapsulated in one environment_result object
926 function environment_check_php($version, $env_select) {
928 $result = new environment_results('php');
930 /// Get the enviroment version we need
931 if (!$data = get_environment_for_version($version, $env_select)) {
932 /// Error. No version data found
933 $result->setStatus(false);
934 $result->setErrorCode(NO_VERSION_DATA_FOUND);
935 return $result;
938 /// Extract the php part
939 if (!isset($data['#']['PHP'])) {
940 /// Error. No PHP section found
941 $result->setStatus(false);
942 $result->setErrorCode(NO_PHP_SECTION_FOUND);
943 return $result;
944 } else {
945 /// Extract level and version
946 $level = get_level($data['#']['PHP']['0']);
947 if (!isset($data['#']['PHP']['0']['@']['version'])) {
948 $result->setStatus(false);
949 $result->setErrorCode(NO_PHP_VERSION_FOUND);
950 return $result;
951 } else {
952 $needed_version = $data['#']['PHP']['0']['@']['version'];
956 /// Now search the version we are using
957 $current_version = normalize_version(phpversion());
959 /// And finally compare them, saving results
960 if (version_compare($current_version, $needed_version, '>=')) {
961 $result->setStatus(true);
962 } else {
963 $result->setStatus(false);
965 $result->setLevel($level);
966 $result->setCurrentVersion($current_version);
967 $result->setNeededVersion($needed_version);
969 /// Do any actions defined in the XML file.
970 process_environment_result($data['#']['PHP'][0], $result);
972 return $result;
977 * This function will check if unicode database requirements are satisfied
979 * @global object
980 * @uses NO_VERSION_DATA_FOUND
981 * @uses NO_UNICODE_SECTION_FOUND
982 * @param string $version xml version we are going to use to test this server
983 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
984 * @return object results encapsulated in one environment_result object
986 function environment_check_unicode($version, $env_select) {
987 global $DB;
989 $result = new environment_results('unicode');
991 /// Get the enviroment version we need
992 if (!$data = get_environment_for_version($version, $env_select)) {
993 /// Error. No version data found
994 $result->setStatus(false);
995 $result->setErrorCode(NO_VERSION_DATA_FOUND);
996 return $result;
999 /// Extract the unicode part
1001 if (!isset($data['#']['UNICODE'])) {
1002 /// Error. No UNICODE section found
1003 $result->setStatus(false);
1004 $result->setErrorCode(NO_UNICODE_SECTION_FOUND);
1005 return $result;
1006 } else {
1007 /// Extract level
1008 $level = get_level($data['#']['UNICODE']['0']);
1011 if (!$unicodedb = $DB->setup_is_unicodedb()) {
1012 $result->setStatus(false);
1013 } else {
1014 $result->setStatus(true);
1017 $result->setLevel($level);
1019 /// Do any actions defined in the XML file.
1020 process_environment_result($data['#']['UNICODE'][0], $result);
1022 return $result;
1026 * This function will check if database requirements are satisfied
1028 * @global object
1029 * @uses NO_VERSION_DATA_FOUND
1030 * @uses NO_DATABASE_SECTION_FOUND
1031 * @uses NO_DATABASE_VENDORS_FOUND
1032 * @uses NO_DATABASE_VENDOR_MYSQL_FOUND
1033 * @uses NO_DATABASE_VENDOR_POSTGRES_FOUND
1034 * @uses NO_DATABASE_VENDOR_VERSION_FOUND
1035 * @param string $version xml version we are going to use to test this server
1036 * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
1037 * @return object results encapsulated in one environment_result object
1039 function environment_check_database($version, $env_select) {
1041 global $DB;
1043 $result = new environment_results('database');
1045 $vendors = array(); //Array of vendors in version
1047 /// Get the enviroment version we need
1048 if (!$data = get_environment_for_version($version, $env_select)) {
1049 /// Error. No version data found
1050 $result->setStatus(false);
1051 $result->setErrorCode(NO_VERSION_DATA_FOUND);
1052 return $result;
1055 /// Extract the database part
1056 if (!isset($data['#']['DATABASE'])) {
1057 /// Error. No DATABASE section found
1058 $result->setStatus(false);
1059 $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
1060 return $result;
1061 } else {
1062 /// Extract level
1063 $level = get_level($data['#']['DATABASE']['0']);
1066 /// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
1067 if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
1068 /// Error. No VENDORS found
1069 $result->setStatus(false);
1070 $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
1071 return $result;
1072 } else {
1073 /// Extract vendors
1074 foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
1075 if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
1076 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
1077 $vendorsxml[$vendor['@']['name']] = $vendor;
1081 /// Check we have the mysql vendor version
1082 if (empty($vendors['mysql'])) {
1083 $result->setStatus(false);
1084 $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
1085 return $result;
1087 /// Check we have the postgres vendor version
1088 if (empty($vendors['postgres'])) {
1089 $result->setStatus(false);
1090 $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
1091 return $result;
1094 /// Now search the version we are using (depending of vendor)
1095 $current_vendor = $DB->get_dbfamily();
1097 $dbinfo = $DB->get_server_info();
1098 $current_version = normalize_version($dbinfo['version']);
1099 $needed_version = $vendors[$current_vendor];
1101 /// Check we have a needed version
1102 if (!$needed_version) {
1103 $result->setStatus(false);
1104 $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND);
1105 return $result;
1108 /// And finally compare them, saving results
1109 if (version_compare($current_version, $needed_version, '>=')) {
1110 $result->setStatus(true);
1111 } else {
1112 $result->setStatus(false);
1114 $result->setLevel($level);
1115 $result->setCurrentVersion($current_version);
1116 $result->setNeededVersion($needed_version);
1117 $result->setInfo($current_vendor);
1119 /// Do any actions defined in the XML file.
1120 process_environment_result($vendorsxml[$current_vendor], $result);
1122 return $result;
1127 * This function will post-process the result record by executing the specified
1128 * function, modifying it as necessary, also a custom message will be added
1129 * to the result object to be printed by the display layer.
1130 * Every bypass function must be defined in this file and it'll return
1131 * true/false to decide if the original test is bypassed or no. Also
1132 * such bypass functions are able to directly handling the result object
1133 * although it should be only under exceptional conditions.
1135 * @param string xmldata containing the bypass data
1136 * @param object result object to be updated
1137 * @return void
1139 function process_environment_bypass($xml, &$result) {
1141 /// Only try to bypass if we were in error and it was required
1142 if ($result->getStatus() || $result->getLevel() == 'optional') {
1143 return;
1146 /// It there is bypass info (function and message)
1147 if (is_array($xml['#']) && isset($xml['#']['BYPASS'][0]['@']['function']) && isset($xml['#']['BYPASS'][0]['@']['message'])) {
1148 $function = $xml['#']['BYPASS'][0]['@']['function'];
1149 $message = $xml['#']['BYPASS'][0]['@']['message'];
1150 /// Look for the function
1151 if (function_exists($function)) {
1152 /// Call it, and if bypass = true is returned, apply meesage
1153 if ($function($result)) {
1154 /// We only set the bypass message if the function itself hasn't defined it before
1155 if (empty($result->getBypassStr)) {
1156 $result->setBypassStr($message);
1164 * This function will post-process the result record by executing the specified
1165 * function, modifying it as necessary, also a custom message will be added
1166 * to the result object to be printed by the display layer.
1167 * Every restrict function must be defined in this file and it'll return
1168 * true/false to decide if the original test is restricted or no. Also
1169 * such restrict functions are able to directly handling the result object
1170 * although it should be only under exceptional conditions.
1172 * @param string xmldata containing the restrict data
1173 * @param object result object to be updated
1174 * @return void
1176 function process_environment_restrict($xml, &$result) {
1178 /// Only try to restrict if we were not in error and it was required
1179 if (!$result->getStatus() || $result->getLevel() == 'optional') {
1180 return;
1182 /// It there is restrict info (function and message)
1183 if (is_array($xml['#']) && isset($xml['#']['RESTRICT'][0]['@']['function']) && isset($xml['#']['RESTRICT'][0]['@']['message'])) {
1184 $function = $xml['#']['RESTRICT'][0]['@']['function'];
1185 $message = $xml['#']['RESTRICT'][0]['@']['message'];
1186 /// Look for the function
1187 if (function_exists($function)) {
1188 /// Call it, and if restrict = true is returned, apply meesage
1189 if ($function($result)) {
1190 /// We only set the restrict message if the function itself hasn't defined it before
1191 if (empty($result->getRestrictStr)) {
1192 $result->setRestrictStr($message);
1200 * This function will detect if there is some message available to be added to the
1201 * result in order to clarify enviromental details.
1203 * @param string xmldata containing the feedback data
1204 * @param object reult object to be updated
1206 function process_environment_messages($xml, &$result) {
1208 /// If there is feedback info
1209 if (is_array($xml['#']) && isset($xml['#']['FEEDBACK'][0]['#'])) {
1210 $feedbackxml = $xml['#']['FEEDBACK'][0]['#'];
1212 if (!$result->status and $result->getLevel() == 'required') {
1213 if (isset($feedbackxml['ON_ERROR'][0]['@']['message'])) {
1214 $result->setFeedbackStr($feedbackxml['ON_ERROR'][0]['@']['message']);
1216 } else if (!$result->status and $result->getLevel() == 'optional') {
1217 if (isset($feedbackxml['ON_CHECK'][0]['@']['message'])) {
1218 $result->setFeedbackStr($feedbackxml['ON_CHECK'][0]['@']['message']);
1220 } else {
1221 if (isset($feedbackxml['ON_OK'][0]['@']['message'])) {
1222 $result->setFeedbackStr($feedbackxml['ON_OK'][0]['@']['message']);
1229 //--- Helper Class to return results to caller ---//
1233 * Helper Class to return results to caller
1235 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1236 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1237 * @package moodlecore
1239 class environment_results {
1241 * @var string Which are we checking (database, php, php_extension, php_extension)
1243 var $part;
1245 * @var bool
1247 var $status;
1249 * @var integer See constants at the beginning of the file
1251 var $error_code;
1253 * @var string required/optional
1255 var $level;
1257 * @var string current version detected
1259 var $current_version;
1261 * @var string version needed
1263 var $needed_version;
1265 * @var string Aux. info (DB vendor, library...)
1267 var $info;
1269 * @var string String to show on error|on check|on ok
1271 var $feedback_str;
1273 * @var string String to show if some bypass has happened
1275 var $bypass_str;
1277 * @var string String to show if some restrict has happened
1279 var $restrict_str;
1282 * Constructor of the environment_result class. Just set default values
1284 * @param string $part
1286 function environment_results($part) {
1287 $this->part=$part;
1288 $this->status=false;
1289 $this->error_code=NO_ERROR;
1290 $this->level='required';
1291 $this->current_version='';
1292 $this->needed_version='';
1293 $this->info='';
1294 $this->feedback_str='';
1295 $this->bypass_str='';
1296 $this->restrict_str='';
1300 * Set the status
1302 * @param boolean $status the status (true/false)
1304 function setStatus($status) {
1305 $this->status=$status;
1306 if ($status) {
1307 $this->setErrorCode(NO_ERROR);
1312 * Set the error_code
1314 * @param integer $error_code the error code (see constants above)
1316 function setErrorCode($error_code) {
1317 $this->error_code=$error_code;
1321 * Set the level
1323 * @param string $level the level (required, optional)
1325 function setLevel($level) {
1326 $this->level=$level;
1330 * Set the current version
1332 * @param string $current_version the current version
1334 function setCurrentVersion($current_version) {
1335 $this->current_version=$current_version;
1339 * Set the needed version
1341 * @param string $needed_version the needed version
1343 function setNeededVersion($needed_version) {
1344 $this->needed_version=$needed_version;
1348 * Set the auxiliary info
1350 * @param string $info the auxiliary info
1352 function setInfo($info) {
1353 $this->info=$info;
1357 * Set the feedback string
1359 * @param mixed $str the feedback string that will be fetched from the admin lang file.
1360 * pass just the string or pass an array of params for get_string
1361 * You always should put your string in admin.php but a third param is useful
1362 * to pass an $a object / string to get_string
1364 function setFeedbackStr($str) {
1365 $this->feedback_str=$str;
1370 * Set the bypass string
1372 * @param string $str the bypass string that will be fetched from the admin lang file.
1373 * pass just the string or pass an array of params for get_string
1374 * You always should put your string in admin.php but a third param is useful
1375 * to pass an $a object / string to get_string
1377 function setBypassStr($str) {
1378 $this->bypass_str=$str;
1382 * Set the restrict string
1384 * @param string $str the restrict string that will be fetched from the admin lang file.
1385 * pass just the string or pass an array of params for get_string
1386 * You always should put your string in admin.php but a third param is useful
1387 * to pass an $a object / string to get_string
1389 function setRestrictStr($str) {
1390 $this->restrict_str=$str;
1394 * Get the status
1396 * @return boolean result
1398 function getStatus() {
1399 return $this->status;
1403 * Get the error code
1405 * @return integer error code
1407 function getErrorCode() {
1408 return $this->error_code;
1412 * Get the level
1414 * @return string level
1416 function getLevel() {
1417 return $this->level;
1421 * Get the current version
1423 * @return string current version
1425 function getCurrentVersion() {
1426 return $this->current_version;
1430 * Get the needed version
1432 * @return string needed version
1434 function getNeededVersion() {
1435 return $this->needed_version;
1439 * Get the aux info
1441 * @return string info
1443 function getInfo() {
1444 return $this->info;
1448 * Get the part this result belongs to
1450 * @return string part
1452 function getPart() {
1453 return $this->part;
1457 * Get the feedback string
1459 * @return mixed feedback string (can be an array of params for get_string or a single string to fetch from
1460 * admin.php lang file).
1462 function getFeedbackStr() {
1463 return $this->feedback_str;
1467 * Get the bypass string
1469 * @return mixed bypass string (can be an array of params for get_string or a single string to fetch from
1470 * admin.php lang file).
1472 function getBypassStr() {
1473 return $this->bypass_str;
1477 * Get the restrict string
1479 * @return mixed restrict string (can be an array of params for get_string or a single string to fetch from
1480 * admin.php lang file).
1482 function getRestrictStr() {
1483 return $this->restrict_str;
1487 * @todo Document this function
1489 * @param mixed $string params for get_string, either a string to fetch from admin.php or an array of
1490 * params for get_string.
1491 * @param string $class css class(es) for message.
1492 * @return string feedback string fetched from lang file wrapped in p tag with class $class or returns
1493 * empty string if $string is empty.
1495 function strToReport($string, $class){
1496 if (!empty($string)){
1497 if (is_array($string)){
1498 $str = call_user_func_array('get_string', $string);
1499 } else {
1500 $str = get_string($string, 'admin');
1502 return '<p class="'.$class.'">'.$str.'</p>';
1503 } else {
1504 return '';
1509 /// Here all the bypass functions are coded to be used by the environment
1510 /// checker. All those functions will receive the result object and will
1511 /// return it modified as needed (status and bypass string)
1514 * This function will bypass MySQL 4.1.16 reqs if:
1515 * - We are using MySQL > 4.1.12, informing about problems with non latin chars in the future
1517 * @param object result object to handle
1518 * @return boolean true/false to determinate if the bypass has to be performed (true) or no (false)
1520 function bypass_mysql416_reqs ($result) {
1521 /// See if we are running MySQL >= 4.1.12
1522 if (version_compare($result->getCurrentVersion(), '4.1.12', '>=')) {
1523 return true;
1526 return false;
1529 /// Here all the restrict functions are coded to be used by the environment
1530 /// checker. All those functions will receive the result object and will
1531 /// return it modified as needed (status and bypass string)
1534 * This function will restrict PHP reqs if:
1535 * - We are using PHP 5.0.x, informing about the buggy version
1537 * @param object $result object to handle
1538 * @return boolean true/false to determinate if the restrict has to be performed (true) or no (false)
1540 function restrict_php50_version($result) {
1541 if (version_compare($result->getCurrentVersion(), '5.0.0', '>=')
1542 and version_compare($result->getCurrentVersion(), '5.0.99', '<')) {
1543 return true;
1545 return false;
1549 * @param array $element the element from the environment.xml file that should have
1550 * either a level="required" or level="optional" attribute.
1551 * @return string "required" or "optional".
1553 function get_level($element) {
1554 $level = 'required';
1555 if (isset($element['@']['level'])) {
1556 $level = $element['@']['level'];
1557 if (!in_array($level, array('required', 'optional'))) {
1558 debugging('The level of a check in the environment.xml file must be "required" or "optional".', DEBUG_DEVELOPER);
1559 $level = 'required';
1561 } else {
1562 debugging('Checks in the environment.xml file must have a level="required" or level="optional" attribute.', DEBUG_DEVELOPER);
1564 return $level;
1568 * Once the result has been determined, look in the XML for any
1569 * messages, or other things that should be done depending on the outcome.
1571 * @param array $element the element from the environment.xml file which
1572 * may have children defining what should be done with the outcome.
1573 * @param object $result the result of the test, which may be modified by
1574 * this function as specified in the XML.
1576 function process_environment_result($element, &$result) {
1577 /// Process messages, modifying the $result if needed.
1578 process_environment_messages($element, $result);
1579 /// Process bypass, modifying $result if needed.
1580 process_environment_bypass($element, $result);
1581 /// Process restrict, modifying $result if needed.
1582 process_environment_restrict($element, $result);