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 | |
34 | require_once($CFG->libdir.'/xmlize.php'); |
35 | |
36 | define(NO_ERROR, 0); |
37 | define(NO_VERSION_DATA_FOUND, 1); |
38 | define(NO_DATABASE_SECTION_FOUND, 2); |
39 | define(NO_DATABASE_VENDORS_FOUND, 3); |
40 | define(NO_DATABASE_VENDOR_MYSQL_FOUND, 4); |
41 | define(NO_DATABASE_VENDOR_POSTGRES_FOUND, 5); |
42 | define(DATABASE_OLD_VERSION, 6); |
43 | define(NO_PHP_SECTION_FOUND, 7); |
44 | define(NO_PHP_VERSION_FOUND, 8); |
45 | define(PHP_OLD_VERSION, 9); |
46 | define(NO_PHP_EXTENSIONS_SECTION_FOUND, 10); |
47 | define(NO_PHP_EXTENSIONS_NAME_FOUND, 11); |
48 | define(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 | */ |
62 | function 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 | */ |
91 | function 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 | */ |
107 | function 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 | */ |
137 | function 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 | */ |
161 | function 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 | */ |
197 | function 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 | */ |
230 | function 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 | */ |
253 | function 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 | */ |
314 | function 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 | */ |
372 | function 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 | */ |
461 | class 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 | ?> |