Added to enviroment feedback messages. More soon...
[moodle.git] / lib / environmentlib.php
CommitLineData
f58b518f 1<?php //$Id$
2
3///////////////////////////////////////////////////////////////////////////
4// //
5// NOTICE OF COPYRIGHT //
6// //
7// Moodle - Modular Object-Oriented Dynamic Learning Environment //
8// http://moodle.com //
9// //
10// Copyright (C) 2001-3001 Martin Dougiamas http://dougiamas.com //
11// (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
12// //
13// This program is free software; you can redistribute it and/or modify //
14// it under the terms of the GNU General Public License as published by //
15// the Free Software Foundation; either version 2 of the License, or //
16// (at your option) any later version. //
17// //
18// This program is distributed in the hope that it will be useful, //
19// but WITHOUT ANY WARRANTY; without even the implied warranty of //
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
21// GNU General Public License for more details: //
22// //
23// http://www.gnu.org/copyleft/gpl.html //
24// //
25///////////////////////////////////////////////////////////////////////////
26
27// This library includes all the necessary stuff to execute some standard
28// tests of required versions and libraries to run Moodle. It can be
29// used from the admin interface, and both at install and upgrade.
30//
31// All the info is stored in the admin/environment.xml file,
00d3a0fd 32// supporting to have an updated version in dataroot/environment
f58b518f 33
049c0f4a 34/// Add required files
35 require_once($CFG->libdir.'/xmlize.php');
36
37/// Define a buch of XML processing errors
00d3a0fd 38 define('NO_ERROR', 0);
39 define('NO_VERSION_DATA_FOUND', 1);
40 define('NO_DATABASE_SECTION_FOUND', 2);
41 define('NO_DATABASE_VENDORS_FOUND', 3);
42 define('NO_DATABASE_VENDOR_MYSQL_FOUND', 4);
43 define('NO_DATABASE_VENDOR_POSTGRES_FOUND', 5);
44 define('NO_PHP_SECTION_FOUND', 6);
45 define('NO_PHP_VERSION_FOUND', 7);
46 define('NO_PHP_EXTENSIONS_SECTION_FOUND', 8);
47 define('NO_PHP_EXTENSIONS_NAME_FOUND', 9);
48 define('NO_DATABASE_VENDOR_VERSION_FOUND', 10);
f58b518f 49
50/**
51 * This function will perform the whole check, returning
52 * true or false as final result. Also, he full array of
53 * environment_result will be returned in the parameter list.
54 * The function looks for the best version to compare and
55 * everything. This is the only function that should be called
56 * ever from the rest of Moodle.
57 * @param string version version to check.
58 * @param array results array of results checked.
59 * @return boolean true/false, depending of results
60 */
049c0f4a 61function check_moodle_environment($version, &$environment_results, $print_table=true) {
62
63 $status = true;
f58b518f 64
878d309c 65/// This are cached per request
66 static $result = true;
67 static $env_results;
68 static $cache_exists = false;
69
70/// if we have results cached, use them
71 if ($cache_exists) {
72 $environment_results = $env_results;
73/// No cache exists, calculate everything
74 } else {
75 /// Get the more recent version before the requested
76 if (!$version = get_latest_version_available($version)) {
77 $status = false;
78 }
f58b518f 79
878d309c 80 /// Perform all the checks
81 if (!($environment_results = environment_check($version)) && $status) {
82 $status = false;
83 }
f58b518f 84
878d309c 85 /// Iterate over all the results looking for some error in required items
86 /// or some error_code
87 if ($status) {
88 foreach ($environment_results as $environment_result) {
89 if ((!$environment_result->getStatus() &&
90 $environment_result->getLevel() == 'required') ||
91 $environment_result->getErrorCode()) {
92 $result = false;
93 }
049c0f4a 94 }
f58b518f 95 }
878d309c 96 /// Going to end, we store environment_results to cache
97 $env_results = $environment_results;
98 $cache_exists = true;
99 } ///End of cache block
f58b518f 100
049c0f4a 101/// If we have decided to print all the information, just do it
102 if ($print_table) {
e909788d 103 print_moodle_environment($result && $status, $environment_results);
049c0f4a 104 }
105
878d309c 106
049c0f4a 107 return ($result && $status);
108}
109
110/**
111 * This function will print one beautiful table with all the environmental
112 * configuration and how it suits Moodle needs.
113 * @param boolean final result of the check (true/false)
114 * @param array environment_results array of results gathered
115 */
116function print_moodle_environment($result, $environment_results) {
117
118/// Get some strings
119 $strname = get_string('name');
120 $strinfo = get_string('info');
121 $strreport = get_string('report');
122 $strstatus = get_string('status');
123 $strok = get_string('ok');
124 $strerror = get_string('error');
125 $strcheck = get_string('check');
e909788d 126 $strenvironmenterrortodo = get_string('environmenterrortodo', 'admin');
049c0f4a 127
128/// Table header
129 $table->head = array ($strname, $strinfo, $strreport, $strstatus);
130 $table->align = array ('center', 'center', 'left', 'center');
131 $table->wrap = array ('nowrap', '', '', 'nowrap');
132 $table->size = array ('10', 10, '100%', '10');
133 $table->width = '90%';
134 $table->class = 'environmenttable';
135
136/// Iterate over each environment_result
137 $continue = true;
138 foreach ($environment_results as $environment_result) {
139 $errorline = false;
140 if ($continue) {
141 $type = $environment_result->getPart();
142 $info = $environment_result->getInfo();
143 $status = $environment_result->getStatus();
144 $error_code = $environment_result->getErrorCode();
145 /// Process Report field
878d309c 146 $rec = new stdClass();
049c0f4a 147 /// Something has gone wrong at parsing time
148 if ($error_code) {
149 $stringtouse = 'environmentxmlerror';
150 $rec->error_code = $error_code;
151 $status = $strerror;
152 $errorline = true;
153 $continue = false;
154 }
155
156 if ($continue) {
157 /// We are comparing versions
158 if ($rec->needed = $environment_result->getNeededVersion()) {
159 $rec->current = $environment_result->getCurrentVersion();
160 if ($environment_result->getLevel() == 'required') {
161 $stringtouse = 'environmentrequireversion';
162 } else {
163 $stringtouse = 'environmentrecommendversion';
164 }
165 /// We are checking installed & enabled things
166 } else {
167 if ($environment_result->getLevel() == 'required') {
168 $stringtouse = 'environmentrequireinstall';
169 } else {
170 $stringtouse = 'environmentrecommendinstall';
171 }
172 }
173 /// Calculate the status value
174 if (!$status and $environment_result->getLevel() == 'required') {
175 $status = $strerror;
176 $errorline = true;
177 } else if (!$status and $environment_result->getLevel() == 'optional') {
178 $status = $strcheck;
179 } else {
180 $status = $strok;
181 }
182 }
183
184 /// Build the text
185 $report = get_string($stringtouse, 'admin', $rec);
186 /// Format error line
187 if ($errorline) {
188 $type = '<span class="error">'.$type.'</span>';
189 $info = '<span class="error">'.$info.'</span>';
190 $report = '<span class="error">'.$report.'</span>';
191 $status = '<span class="error">'.$status.'</span>';
192 }
193 /// Add the row to the table
194 $table->data[] = array ($type, $info, $report, $status);
195 }
196 }
197
198/// Print table
199 print_table($table);
e909788d 200
201/// Finally, if any error has happened, print the summary box
202 if (!$result) {
203 print_simple_box($strenvironmenterrortodo, 'center', '', '', '', 'errorbox');
204 }
f58b518f 205}
206
207
208/**
209 * This function will normalize any version to just a serie of numbers
210 * separated by dots. Everything else will be removed.
211 * @param string $version the original version
212 * @return string the normalized version
213 */
214function normalize_version($version) {
215/// Replace everything but numbers and dots by dots
216 $version = preg_replace('/[^\.\d]/', '.', $version);
217/// Combine multiple dots in one
218 $version = preg_replace('/(\.{2,})/', '.', $version);
219/// Trim possible leading and trailing dots
220 $version = trim($version, '.');
221
222 return $version;
223}
224
225
226/**
227 * This function will load the environment.xml file and xmlize it
228 * @return mixed the xmlized structure or false on error
229 */
230function load_environment_xml() {
231
232 global $CFG;
233
234 static $data; //Only load and xmlize once by request
235
236 if (!empty($data)) {
237 return $data;
238 }
239
00d3a0fd 240/// First of all, take a look inside $CFG->dataroot/environment/environment.xml
241 $file = $CFG->dataroot.'/environment/environment.xml';
f58b518f 242 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
d83f8373 243 /// Fallback to fixed $CFG->admin/environment.xml
244 $file = $CFG->dirroot.'/'.$CFG->admin.'/environment.xml';
f58b518f 245 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
246 return false;
247 }
248 }
249/// XML the whole file
250 $data = xmlize($contents);
251
252 return $data;
253}
254
255
256/**
257 * This function will return the list of Moodle versions available
258 * @return mixed array of versions. False on error.
259 */
260function get_list_of_environment_versions ($contents) {
261
262 static $versions = array();
263
264 if (!empty($versions)) {
265 return $versions;
266 }
267
268 if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
269 foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
270 $versions[] = $version['@']['version'];
271 }
272 }
273
274 return $versions;
275}
276
277
278/**
279 * This function will return the most recent version in the environment.xml
280 * file previous or equal to the version requested
281 * @param string version top version from which we start to look backwards
282 * @return string more recent version or false if not found
283 */
284function get_latest_version_available ($version) {
285
286/// Normalize the version requested
287 $version = normalize_version($version);
288
289/// Load xml file
290 if (!$contents = load_environment_xml()) {
291 return false;
292 }
293
294/// Detect available versions
295 if (!$versions = get_list_of_environment_versions($contents)) {
296 return false;
297 }
298/// First we look for exact version
299 if (in_array($version, $versions)) {
300 return $version;
301 } else {
302 $found_version = false;
303 /// Not exact match, so we are going to iterate over the list searching
304 /// for the latest version before the requested one
305 foreach ($versions as $arrversion) {
306 if (version_compare($arrversion, $version, '<')) {
307 $found_version = $arrversion;
308 }
309 }
310 }
311
312 return $found_version;
313}
314
315
316/**
317 * This function will return the xmlized data belonging to one Moodle version
318 * @return mixed the xmlized structure or false on error
319 */
320function get_environment_for_version($version) {
321
322/// Normalize the version requested
323 $version = normalize_version($version);
324
325/// Load xml file
326 if (!$contents = load_environment_xml()) {
327 return false;
328 }
329
330/// Detect available versions
331 if (!$versions = get_list_of_environment_versions($contents)) {
332 return false;
333 }
334
335/// If the version requested is available
336 if (!in_array($version, $versions)) {
337 return false;
338 }
339
340/// We now we have it. Extract from full contents.
341 $fl_arr = array_flip($versions);
342
343 return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
344}
345
346
347/**
348 * This function will check for everything (DB, PHP and PHP extensions for now)
349 * returning an array of environment_result objects.
350 * @param string $version xml version we are going to use to test this server
351 * @return array array of results encapsulated in one environment_result object
352 */
353function environment_check($version) {
354
355/// Normalize the version requested
356 $version = normalize_version($version);
357
358 $results = array(); //To store all the results
359
360 $results[] = environment_check_database($version);
361 $results[] = environment_check_php($version);
362
363 $phpext_results = environment_check_php_extensions($version);
364
365 $results = array_merge ($results, $phpext_results);
366
367 return $results;
368}
369
370
371/**
372 * This function will check if php extensions requirements are satisfied
373 * @param string $version xml version we are going to use to test this server
374 * @return array array of results encapsulated in one environment_result object
375 */
376function environment_check_php_extensions($version) {
377
378 $results = array();
379
380/// Get the enviroment version we need
381 if (!$data = get_environment_for_version($version)) {
382 /// Error. No version data found
049c0f4a 383 $result = new environment_results('php_extension');
f58b518f 384 $result->setStatus(false);
385 $result->setErrorCode(NO_VERSION_DATA_FOUND);
386 return $result;
387 }
388
389/// Extract the php_extension part
390 if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
391 /// Error. No PHP section found
049c0f4a 392 $result = new environment_results('php_extension');
f58b518f 393 $result->setStatus(false);
394 $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
395 return $result;
396 } else {
397 /// Iterate over extensions checking them and creating the needed environment_results
398 foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
049c0f4a 399 $result = new environment_results('php_extension');
f58b518f 400 /// Check for level
401 if (isset($extension['@']['level'])) {
402 $level = $extension['@']['level'];
403 if ($level != 'optional') {
404 $level = 'required';
405 }
406 }
407 /// Check for extension name
408 if (!isset($extension['@']['name'])) {
409 $result->setStatus(false);
410 $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
411 } else {
412 $extension_name = $extension['@']['name'];
413 /// The name exists. Just check if it's an installed extension
414 if (!extension_loaded($extension_name)) {
415 $result->setStatus(false);
f58b518f 416 } else {
417 $result->setStatus(true);
418 }
419 $result->setLevel($level);
420 $result->setInfo($extension_name);
421 }
422 /// Add the result to the array of results
423 $results[] = $result;
424 }
425 }
426
427 return $results;
428}
429
430
431/**
432 * This function will check if php requirements are satisfied
433 * @param string $version xml version we are going to use to test this server
434 * @return object results encapsulated in one environment_result object
435 */
436function environment_check_php($version) {
437
438 $result = new environment_results('php');
439
440/// Get the enviroment version we need
441 if (!$data = get_environment_for_version($version)) {
442 /// Error. No version data found
443 $result->setStatus(false);
444 $result->setErrorCode(NO_VERSION_DATA_FOUND);
445 return $result;
446 }
447
448/// Extract the php part
449 if (!isset($data['#']['PHP'])) {
450 /// Error. No PHP section found
451 $result->setStatus(false);
452 $result->setErrorCode(NO_PHP_SECTION_FOUND);
453 return $result;
454 } else {
455 /// Extract level and version
456 if (isset($data['#']['PHP']['0']['@']['level'])) {
00d3a0fd 457 $level = $data['#']['PHP']['0']['@']['level'];
f58b518f 458 if ($level != 'optional') {
459 $level = 'required';
460 }
461 }
462 if (!isset($data['#']['PHP']['0']['@']['version'])) {
463 $result->setStatus(false);
464 $result->setErrorCode(NO_PHP_VERSION_FOUND);
465 return $result;
466 } else {
467 $needed_version = $data['#']['PHP']['0']['@']['version'];
468 }
469 }
470
471/// Now search the version we are using
472 $current_version = normalize_version(phpversion());
473
474/// And finally compare them, saving results
475 if (version_compare($current_version, $needed_version, '>=')) {
476 $result->setStatus(true);
477 } else {
478 $result->setStatus(false);
f58b518f 479 }
480 $result->setLevel($level);
481 $result->setCurrentVersion($current_version);
482 $result->setNeededVersion($needed_version);
483
484 return $result;
485}
486
487
488/**
489 * This function will check if database requirements are satisfied
490 * @param string $version xml version we are going to use to test this server
491 * @return object results encapsulated in one environment_result object
492 */
493function environment_check_database($version) {
494
495 global $db;
496
497 $result = new environment_results('database');
498
499 $vendors = array(); //Array of vendors in version
500
501/// Get the enviroment version we need
502 if (!$data = get_environment_for_version($version)) {
503 /// Error. No version data found
504 $result->setStatus(false);
505 $result->setErrorCode(NO_VERSION_DATA_FOUND);
506 return $result;
507 }
508
509/// Extract the database part
510 if (!isset($data['#']['DATABASE'])) {
511 /// Error. No DATABASE section found
512 $result->setStatus(false);
513 $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
514 return $result;
515 } else {
516 /// Extract level
517 if (isset($data['#']['DATABASE']['0']['@']['level'])) {
00d3a0fd 518 $level = $data['#']['DATABASE']['0']['@']['level'];
f58b518f 519 if ($level != 'optional') {
520 $level = 'required';
521 }
522 }
523 }
524
525/// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
526 if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
527 /// Error. No VENDORS found
528 $result->setStatus(false);
529 $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
530 return $result;
531 } else {
532 /// Extract vendors
533 foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
534 if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
535 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
536 }
537 }
538 }
539/// Check we have the mysql vendor version
540 if (empty($vendors['mysql'])) {
541 $result->setStatus(false);
542 $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
543 return $result;
544 }
545/// Check we have the postgres vendor version
546 if (empty($vendors['postgres'])) {
547 $result->setStatus(false);
548 $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
549 return $result;
550 }
551
552/// Now search the version we are using (depending of vendor)
553 $current_vendor = $db->databaseType;
e3058eb3 554 if ($current_vendor == 'postgres7') { //Normalize a bit postgresql
555 $current_vendor ='postgres';
556 }
f58b518f 557 $dbinfo = $db->ServerInfo();
558 $current_version = normalize_version($dbinfo['version']);
559 $needed_version = $vendors[$current_vendor];
560
e3058eb3 561/// Check we have a needed version
562 if (!$needed_version) {
563 $result->setStatus(false);
564 $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND);
565 return $result;
566 }
567
f58b518f 568/// And finally compare them, saving results
569 if (version_compare($current_version, $needed_version, '>=')) {
570 $result->setStatus(true);
571 } else {
572 $result->setStatus(false);
f58b518f 573 }
574 $result->setLevel($level);
575 $result->setCurrentVersion($current_version);
576 $result->setNeededVersion($needed_version);
577 $result->setInfo($current_vendor);
578
579 return $result;
580
581}
582
583
584//--- Helper Class to return results to caller ---//
585
586
587/**
588 * This class is used to return the results of the environment
589 * main functions (environment_check_xxxx)
590 */
591class environment_results {
592
049c0f4a 593 var $part; //which are we checking (database, php, php_extension)
f58b518f 594 var $status; //true/false
595 var $error_code; //integer. See constants at the beginning of the file
596 var $level; //required/optional
597 var $current_version; //current version detected
598 var $needed_version; //version needed
599 var $info; //Aux. info (DB vendor, library...)
600
601 /**
602 * Constructor of the environment_result class. Just set default values
603 */
604 function environment_results($part) {
605 $this->part=$part;
606 $this->status=false;
049c0f4a 607 $this->error_code=NO_ERROR;
f58b518f 608 $this->level='required';
609 $this->current_version='';
610 $this->needed_version='';
611 $this->info='';
612 }
613
614 /**
615 * Set the status
616 * @param boolean the status (true/false)
617 */
618 function setStatus($status) {
619 $this->status=$status;
620 if ($status) {
621 $this->setErrorCode(NO_ERROR);
622 }
623 }
624
625 /**
626 * Set the error_code
627 * @param integer the error code (see constants above)
628 */
629 function setErrorCode($error_code) {
630 $this->error_code=$error_code;
631 }
632
633 /**
634 * Set the level
635 * @param string the level (required, optional)
636 */
637 function setLevel($level) {
638 $this->level=$level;
639 }
640
641 /**
642 * Set the current version
643 * @param string the current version
644 */
645 function setCurrentVersion($current_version) {
646 $this->current_version=$current_version;
647 }
648
649 /**
650 * Set the needed version
651 * @param string the needed version
652 */
653 function setNeededVersion($needed_version) {
654 $this->needed_version=$needed_version;
655 }
656
657 /**
658 * Set the auxiliary info
659 * @param string the auxiliary info
660 */
661 function setInfo($info) {
662 $this->info=$info;
663 }
664
665 /**
666 * Get the status
667 * @return boolean result
668 */
669 function getStatus() {
670 return $this->status;
671 }
672
673 /**
674 * Get the error code
675 * @return integer error code
676 */
677 function getErrorCode() {
678 return $this->error_code;
679 }
680
681 /**
682 * Get the level
683 * @return string level
684 */
685 function getLevel() {
686 return $this->level;
687 }
688
689 /**
690 * Get the current version
691 * @return string current version
692 */
693 function getCurrentVersion() {
694 return $this->current_version;
695 }
696
697 /**
698 * Get the needed version
699 * @return string needed version
700 */
701 function getNeededVersion() {
702 return $this->needed_version;
703 }
704
705 /**
706 * Get the aux info
707 * @return string info
708 */
709 function getInfo() {
710 return $this->info;
711 }
712
713 /**
714 * Get the part this result belongs to
715 * @return string part
716 */
717 function getPart() {
718 return $this->part;
719 }
720}
721
722?>