After thinking a bit about it, this file is, exactly,
[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,
32// supporting to have an updated version in moodledata/environment
33
34require_once($CFG->libdir.'/xmlize.php');
35
36define(NO_ERROR, 0);
37define(NO_VERSION_DATA_FOUND, 1);
38define(NO_DATABASE_SECTION_FOUND, 2);
39define(NO_DATABASE_VENDORS_FOUND, 3);
40define(NO_DATABASE_VENDOR_MYSQL_FOUND, 4);
41define(NO_DATABASE_VENDOR_POSTGRES_FOUND, 5);
42define(DATABASE_OLD_VERSION, 6);
43define(NO_PHP_SECTION_FOUND, 7);
44define(NO_PHP_VERSION_FOUND, 8);
45define(PHP_OLD_VERSION, 9);
46define(NO_PHP_EXTENSIONS_SECTION_FOUND, 10);
47define(NO_PHP_EXTENSIONS_NAME_FOUND, 11);
48define(NO_PHP_EXTENSION_WITH_NAME, 12);
49
50
51/**
52 * This function will perform the whole check, returning
53 * true or false as final result. Also, he full array of
54 * environment_result will be returned in the parameter list.
55 * The function looks for the best version to compare and
56 * everything. This is the only function that should be called
57 * ever from the rest of Moodle.
58 * @param string version version to check.
59 * @param array results array of results checked.
60 * @return boolean true/false, depending of results
61 */
62function check_moodle_environment($version, &$environment_results) {
63
64/// Get the more recent version before the requested
65 if (!$version = get_latest_version_available($version)) {
66 return false;
67 }
68
69/// Perform all the checks
70 if(!$environment_results = environment_check($version)) {
71 return false;
72 }
73
74/// Iterate over all the results looking for some error in required items
75 foreach ($environment_results as $environment_result) {
76 if (!$environment_result->getStatus() && $environment_result->getLevel() == 'required') {
77 return false;
78 }
79 }
80
81 return true;
82}
83
84
85/**
86 * This function will normalize any version to just a serie of numbers
87 * separated by dots. Everything else will be removed.
88 * @param string $version the original version
89 * @return string the normalized version
90 */
91function normalize_version($version) {
92/// Replace everything but numbers and dots by dots
93 $version = preg_replace('/[^\.\d]/', '.', $version);
94/// Combine multiple dots in one
95 $version = preg_replace('/(\.{2,})/', '.', $version);
96/// Trim possible leading and trailing dots
97 $version = trim($version, '.');
98
99 return $version;
100}
101
102
103/**
104 * This function will load the environment.xml file and xmlize it
105 * @return mixed the xmlized structure or false on error
106 */
107function load_environment_xml() {
108
109 global $CFG;
110
111 static $data; //Only load and xmlize once by request
112
113 if (!empty($data)) {
114 return $data;
115 }
116
117/// First of all, take a look inside $CFG->moodledata/environment/environment.xml
118 $file = $CFG->moodledata.'/environment/environment.xml';
119 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
120 /// Fallback to fixed admin/environment.xml
121 $file = $CFG->dirroot.'/admin/environment.xml';
122 if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
123 return false;
124 }
125 }
126/// XML the whole file
127 $data = xmlize($contents);
128
129 return $data;
130}
131
132
133/**
134 * This function will return the list of Moodle versions available
135 * @return mixed array of versions. False on error.
136 */
137function get_list_of_environment_versions ($contents) {
138
139 static $versions = array();
140
141 if (!empty($versions)) {
142 return $versions;
143 }
144
145 if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
146 foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
147 $versions[] = $version['@']['version'];
148 }
149 }
150
151 return $versions;
152}
153
154
155/**
156 * This function will return the most recent version in the environment.xml
157 * file previous or equal to the version requested
158 * @param string version top version from which we start to look backwards
159 * @return string more recent version or false if not found
160 */
161function get_latest_version_available ($version) {
162
163/// Normalize the version requested
164 $version = normalize_version($version);
165
166/// Load xml file
167 if (!$contents = load_environment_xml()) {
168 return false;
169 }
170
171/// Detect available versions
172 if (!$versions = get_list_of_environment_versions($contents)) {
173 return false;
174 }
175/// First we look for exact version
176 if (in_array($version, $versions)) {
177 return $version;
178 } else {
179 $found_version = false;
180 /// Not exact match, so we are going to iterate over the list searching
181 /// for the latest version before the requested one
182 foreach ($versions as $arrversion) {
183 if (version_compare($arrversion, $version, '<')) {
184 $found_version = $arrversion;
185 }
186 }
187 }
188
189 return $found_version;
190}
191
192
193/**
194 * This function will return the xmlized data belonging to one Moodle version
195 * @return mixed the xmlized structure or false on error
196 */
197function get_environment_for_version($version) {
198
199/// Normalize the version requested
200 $version = normalize_version($version);
201
202/// Load xml file
203 if (!$contents = load_environment_xml()) {
204 return false;
205 }
206
207/// Detect available versions
208 if (!$versions = get_list_of_environment_versions($contents)) {
209 return false;
210 }
211
212/// If the version requested is available
213 if (!in_array($version, $versions)) {
214 return false;
215 }
216
217/// We now we have it. Extract from full contents.
218 $fl_arr = array_flip($versions);
219
220 return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
221}
222
223
224/**
225 * This function will check for everything (DB, PHP and PHP extensions for now)
226 * returning an array of environment_result objects.
227 * @param string $version xml version we are going to use to test this server
228 * @return array array of results encapsulated in one environment_result object
229 */
230function environment_check($version) {
231
232/// Normalize the version requested
233 $version = normalize_version($version);
234
235 $results = array(); //To store all the results
236
237 $results[] = environment_check_database($version);
238 $results[] = environment_check_php($version);
239
240 $phpext_results = environment_check_php_extensions($version);
241
242 $results = array_merge ($results, $phpext_results);
243
244 return $results;
245}
246
247
248/**
249 * This function will check if php extensions requirements are satisfied
250 * @param string $version xml version we are going to use to test this server
251 * @return array array of results encapsulated in one environment_result object
252 */
253function environment_check_php_extensions($version) {
254
255 $results = array();
256
257/// Get the enviroment version we need
258 if (!$data = get_environment_for_version($version)) {
259 /// Error. No version data found
260 $result = new environment_results('php_extensions');
261 $result->setStatus(false);
262 $result->setErrorCode(NO_VERSION_DATA_FOUND);
263 return $result;
264 }
265
266/// Extract the php_extension part
267 if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
268 /// Error. No PHP section found
269 $result = new environment_results('php_extensions');
270 $result->setStatus(false);
271 $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
272 return $result;
273 } else {
274 /// Iterate over extensions checking them and creating the needed environment_results
275 foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
276 $result = new environment_results('php_extensions');
277 /// Check for level
278 if (isset($extension['@']['level'])) {
279 $level = $extension['@']['level'];
280 if ($level != 'optional') {
281 $level = 'required';
282 }
283 }
284 /// Check for extension name
285 if (!isset($extension['@']['name'])) {
286 $result->setStatus(false);
287 $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
288 } else {
289 $extension_name = $extension['@']['name'];
290 /// The name exists. Just check if it's an installed extension
291 if (!extension_loaded($extension_name)) {
292 $result->setStatus(false);
293 $result->setErrorCode(NO_PHP_EXTENSION_WITH_NAME);
294 } else {
295 $result->setStatus(true);
296 }
297 $result->setLevel($level);
298 $result->setInfo($extension_name);
299 }
300 /// Add the result to the array of results
301 $results[] = $result;
302 }
303 }
304
305 return $results;
306}
307
308
309/**
310 * This function will check if php requirements are satisfied
311 * @param string $version xml version we are going to use to test this server
312 * @return object results encapsulated in one environment_result object
313 */
314function environment_check_php($version) {
315
316 $result = new environment_results('php');
317
318/// Get the enviroment version we need
319 if (!$data = get_environment_for_version($version)) {
320 /// Error. No version data found
321 $result->setStatus(false);
322 $result->setErrorCode(NO_VERSION_DATA_FOUND);
323 return $result;
324 }
325
326/// Extract the php part
327 if (!isset($data['#']['PHP'])) {
328 /// Error. No PHP section found
329 $result->setStatus(false);
330 $result->setErrorCode(NO_PHP_SECTION_FOUND);
331 return $result;
332 } else {
333 /// Extract level and version
334 if (isset($data['#']['PHP']['0']['@']['level'])) {
335 $level = $data['#']['PHP']['0']['level'];
336 if ($level != 'optional') {
337 $level = 'required';
338 }
339 }
340 if (!isset($data['#']['PHP']['0']['@']['version'])) {
341 $result->setStatus(false);
342 $result->setErrorCode(NO_PHP_VERSION_FOUND);
343 return $result;
344 } else {
345 $needed_version = $data['#']['PHP']['0']['@']['version'];
346 }
347 }
348
349/// Now search the version we are using
350 $current_version = normalize_version(phpversion());
351
352/// And finally compare them, saving results
353 if (version_compare($current_version, $needed_version, '>=')) {
354 $result->setStatus(true);
355 } else {
356 $result->setStatus(false);
357 $result->setErrorCode(PHP_OLD_VERSION);
358 }
359 $result->setLevel($level);
360 $result->setCurrentVersion($current_version);
361 $result->setNeededVersion($needed_version);
362
363 return $result;
364}
365
366
367/**
368 * This function will check if database requirements are satisfied
369 * @param string $version xml version we are going to use to test this server
370 * @return object results encapsulated in one environment_result object
371 */
372function environment_check_database($version) {
373
374 global $db;
375
376 $result = new environment_results('database');
377
378 $vendors = array(); //Array of vendors in version
379
380/// Get the enviroment version we need
381 if (!$data = get_environment_for_version($version)) {
382 /// Error. No version data found
383 $result->setStatus(false);
384 $result->setErrorCode(NO_VERSION_DATA_FOUND);
385 return $result;
386 }
387
388/// Extract the database part
389 if (!isset($data['#']['DATABASE'])) {
390 /// Error. No DATABASE section found
391 $result->setStatus(false);
392 $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
393 return $result;
394 } else {
395 /// Extract level
396 if (isset($data['#']['DATABASE']['0']['@']['level'])) {
397 $level = $data['#']['DATABASE']['0']['level'];
398 if ($level != 'optional') {
399 $level = 'required';
400 }
401 }
402 }
403
404/// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
405 if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
406 /// Error. No VENDORS found
407 $result->setStatus(false);
408 $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
409 return $result;
410 } else {
411 /// Extract vendors
412 foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
413 if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
414 $vendors[$vendor['@']['name']] = $vendor['@']['version'];
415 }
416 }
417 }
418/// Check we have the mysql vendor version
419 if (empty($vendors['mysql'])) {
420 $result->setStatus(false);
421 $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
422 return $result;
423 }
424/// Check we have the postgres vendor version
425 if (empty($vendors['postgres'])) {
426 $result->setStatus(false);
427 $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
428 return $result;
429 }
430
431/// Now search the version we are using (depending of vendor)
432 $current_vendor = $db->databaseType;
433 $dbinfo = $db->ServerInfo();
434 $current_version = normalize_version($dbinfo['version']);
435 $needed_version = $vendors[$current_vendor];
436
437/// And finally compare them, saving results
438 if (version_compare($current_version, $needed_version, '>=')) {
439 $result->setStatus(true);
440 } else {
441 $result->setStatus(false);
442 $result->setErrorCode(DATABASE_OLD_VERSION);
443 }
444 $result->setLevel($level);
445 $result->setCurrentVersion($current_version);
446 $result->setNeededVersion($needed_version);
447 $result->setInfo($current_vendor);
448
449 return $result;
450
451}
452
453
454//--- Helper Class to return results to caller ---//
455
456
457/**
458 * This class is used to return the results of the environment
459 * main functions (environment_check_xxxx)
460 */
461class environment_results {
462
463 var $part; //which are we checking (database, php, php_extensions)
464 var $status; //true/false
465 var $error_code; //integer. See constants at the beginning of the file
466 var $level; //required/optional
467 var $current_version; //current version detected
468 var $needed_version; //version needed
469 var $info; //Aux. info (DB vendor, library...)
470
471 /**
472 * Constructor of the environment_result class. Just set default values
473 */
474 function environment_results($part) {
475 $this->part=$part;
476 $this->status=false;
477 $this->error_code=NOTHING_CHECKED_YET;
478 $this->level='required';
479 $this->current_version='';
480 $this->needed_version='';
481 $this->info='';
482 }
483
484 /**
485 * Set the status
486 * @param boolean the status (true/false)
487 */
488 function setStatus($status) {
489 $this->status=$status;
490 if ($status) {
491 $this->setErrorCode(NO_ERROR);
492 }
493 }
494
495 /**
496 * Set the error_code
497 * @param integer the error code (see constants above)
498 */
499 function setErrorCode($error_code) {
500 $this->error_code=$error_code;
501 }
502
503 /**
504 * Set the level
505 * @param string the level (required, optional)
506 */
507 function setLevel($level) {
508 $this->level=$level;
509 }
510
511 /**
512 * Set the current version
513 * @param string the current version
514 */
515 function setCurrentVersion($current_version) {
516 $this->current_version=$current_version;
517 }
518
519 /**
520 * Set the needed version
521 * @param string the needed version
522 */
523 function setNeededVersion($needed_version) {
524 $this->needed_version=$needed_version;
525 }
526
527 /**
528 * Set the auxiliary info
529 * @param string the auxiliary info
530 */
531 function setInfo($info) {
532 $this->info=$info;
533 }
534
535 /**
536 * Get the status
537 * @return boolean result
538 */
539 function getStatus() {
540 return $this->status;
541 }
542
543 /**
544 * Get the error code
545 * @return integer error code
546 */
547 function getErrorCode() {
548 return $this->error_code;
549 }
550
551 /**
552 * Get the level
553 * @return string level
554 */
555 function getLevel() {
556 return $this->level;
557 }
558
559 /**
560 * Get the current version
561 * @return string current version
562 */
563 function getCurrentVersion() {
564 return $this->current_version;
565 }
566
567 /**
568 * Get the needed version
569 * @return string needed version
570 */
571 function getNeededVersion() {
572 return $this->needed_version;
573 }
574
575 /**
576 * Get the aux info
577 * @return string info
578 */
579 function getInfo() {
580 return $this->info;
581 }
582
583 /**
584 * Get the part this result belongs to
585 * @return string part
586 */
587 function getPart() {
588 return $this->part;
589 }
590}
591
592?>