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