Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

   1  <?php
   2  
   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.
  14  //
  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/>.
  17  
  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.
  22   *
  23   * All the info is stored in the admin/environment.xml file,
  24   * supporting to have an updated version in dataroot/environment
  25   *
  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
  30   */
  31  
  32  defined('MOODLE_INTERNAL') || die();
  33  
  34  /// Add required files
  35  /**
  36   * Include the necessary
  37   */
  38      require_once($CFG->libdir.'/xmlize.php');
  39  
  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);
  73      /** XML Processing Error */
  74      define('INCORRECT_FEEDBACK_FOR_REQUIRED',   16);
  75      /** XML Processing Error */
  76      define('INCORRECT_FEEDBACK_FOR_OPTIONAL',   17);
  77  
  78  /// Define algorithm used to select the xml file
  79      /** To select the newer file available to perform checks */
  80      define('ENV_SELECT_NEWER',                   0);
  81      /** To enforce the use of the file under dataroot */
  82      define('ENV_SELECT_DATAROOT',                1);
  83      /** To enforce the use of the file under admin (release) */
  84      define('ENV_SELECT_RELEASE',                 2);
  85  
  86  /**
  87   * This function checks all the requirements defined in environment.xml.
  88   *
  89   * @param string $version version to check.
  90   * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. Default ENV_SELECT_NEWER (BC)
  91   * @return array with two elements. The first element true/false, depending on
  92   *      on whether the check passed. The second element is an array of environment_results
  93   *      objects that has detailed information about the checks and which ones passed.
  94   */
  95  function check_moodle_environment($version, $env_select = ENV_SELECT_NEWER) {
  96      if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
  97          throw new coding_exception('Incorrect value of $env_select parameter');
  98      }
  99  
 100  /// Get the more recent version before the requested
 101      if (!$version = get_latest_version_available($version, $env_select)) {
 102          return array(false, array());
 103      }
 104  
 105  /// Perform all the checks
 106      if (!$environment_results = environment_check($version, $env_select)) {
 107          return array(false, array());
 108      }
 109  
 110  /// Iterate over all the results looking for some error in required items
 111  /// or some error_code
 112      $result = true;
 113      foreach ($environment_results as $environment_result) {
 114          if (!$environment_result->getStatus() && $environment_result->getLevel() == 'required'
 115            && !$environment_result->getBypassStr()) {
 116              $result = false; // required item that is not bypased
 117          } else if ($environment_result->getStatus() && $environment_result->getLevel() == 'required'
 118            && $environment_result->getRestrictStr()) {
 119              $result = false; // required item that is restricted
 120          } else if ($environment_result->getErrorCode()) {
 121              $result = false;
 122          }
 123      }
 124  
 125      return array($result, $environment_results);
 126  }
 127  
 128  
 129  /**
 130   * Returns array of critical errors in plain text format
 131   * @param array $environment_results array of results gathered
 132   * @return array errors
 133   */
 134  function environment_get_errors($environment_results) {
 135      global $CFG;
 136      $errors = array();
 137  
 138      // Iterate over each environment_result
 139      foreach ($environment_results as $environment_result) {
 140          $type = $environment_result->getPart();
 141          $info = $environment_result->getInfo();
 142          $status = $environment_result->getStatus();
 143          $plugin = $environment_result->getPluginName();
 144          $error_code = $environment_result->getErrorCode();
 145  
 146          $a = new stdClass();
 147          if ($error_code) {
 148              $a->error_code = $error_code;
 149              $errors[] = array($info, get_string('environmentxmlerror', 'admin', $a));
 150              return $errors;
 151          }
 152  
 153          /// Calculate the status value
 154          if ($environment_result->getBypassStr() != '') {
 155              // not interesting
 156              continue;
 157          } else if ($environment_result->getRestrictStr() != '') {
 158              // error
 159          } else {
 160              if ($status) {
 161                  // ok
 162                  continue;
 163              } else {
 164                  if ($environment_result->getLevel() == 'optional') {
 165                      // just a warning
 166                      continue;
 167                  } else {
 168                      // error
 169                  }
 170              }
 171          }
 172  
 173          // We are comparing versions
 174          $rec = new stdClass();
 175          if ($rec->needed = $environment_result->getNeededVersion()) {
 176              $rec->current = $environment_result->getCurrentVersion();
 177              if ($environment_result->getLevel() == 'required') {
 178                  $stringtouse = 'environmentrequireversion';
 179              } else {
 180                  $stringtouse = 'environmentrecommendversion';
 181              }
 182          // We are checking installed & enabled things
 183          } else if ($environment_result->getPart() == 'custom_check') {
 184              if ($environment_result->getLevel() == 'required') {
 185                  $stringtouse = 'environmentrequirecustomcheck';
 186              } else {
 187                  $stringtouse = 'environmentrecommendcustomcheck';
 188              }
 189          } else if ($environment_result->getPart() == 'php_setting') {
 190              if ($status) {
 191                  $stringtouse = 'environmentsettingok';
 192              } else if ($environment_result->getLevel() == 'required') {
 193                  $stringtouse = 'environmentmustfixsetting';
 194              } else {
 195                  $stringtouse = 'environmentshouldfixsetting';
 196              }
 197          } else {
 198              if ($environment_result->getLevel() == 'required') {
 199                  $stringtouse = 'environmentrequireinstall';
 200              } else {
 201                  $stringtouse = 'environmentrecommendinstall';
 202              }
 203          }
 204          $report = get_string($stringtouse, 'admin', $rec);
 205  
 206          // Here we'll store all the feedback found
 207          $feedbacktext = '';
 208          // Append  the feedback if there is some
 209          $feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), 'error');
 210          // Append the restrict if there is some
 211          $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
 212  
 213          if ($plugin === '') {
 214              $report = '[' . get_string('coresystem') . '] ' . $report;
 215          } else {
 216              $report = '[' . $plugin . '] ' . $report;
 217          }
 218  
 219          $report .= ' - ' . html_to_text($feedbacktext);
 220  
 221          if ($environment_result->getPart() == 'custom_check'){
 222              $errors[] = array($info, $report);
 223          } else {
 224              $errors[] = array(($info !== '' ? "$type $info" : $type), $report);
 225          }
 226      }
 227  
 228      return $errors;
 229  }
 230  
 231  
 232  /**
 233   * This function will normalize any version to just a serie of numbers
 234   * separated by dots. Everything else will be removed.
 235   *
 236   * @param string $version the original version
 237   * @return string the normalized version
 238   */
 239  function normalize_version($version) {
 240  
 241  /// 1.9 Beta 2 should be read 1.9 on enviromental checks, not 1.9.2
 242  /// we can discard everything after the first space
 243      $version = trim($version);
 244      $versionarr = explode(" ",$version);
 245      if (!empty($versionarr)) {
 246          $version = $versionarr[0];
 247      }
 248  /// Replace everything but numbers and dots by dots
 249      $version = preg_replace('/[^\.\d]/', '.', $version);
 250  /// Combine multiple dots in one
 251      $version = preg_replace('/(\.{2,})/', '.', $version);
 252  /// Trim possible leading and trailing dots
 253      $version = trim($version, '.');
 254  
 255      return $version;
 256  }
 257  
 258  
 259  /**
 260   * This function will load the environment.xml file and xmlize it
 261   *
 262   * @staticvar array $data
 263   * @uses ENV_SELECT_NEWER
 264   * @uses ENV_SELECT_DATAROOT
 265   * @uses ENV_SELECT_RELEASE
 266   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 267   * @return mixed the xmlized structure or false on error
 268   */
 269  function load_environment_xml($env_select=ENV_SELECT_NEWER) {
 270  
 271      global $CFG;
 272  
 273      static $data = array(); // Only load and xmlize once by request.
 274  
 275      if (isset($data[$env_select])) {
 276          return $data[$env_select];
 277      }
 278      $contents = false;
 279  
 280      if (is_numeric($env_select)) {
 281          $file = $CFG->dataroot.'/environment/environment.xml';
 282          $internalfile = $CFG->dirroot.'/'.$CFG->admin.'/environment.xml';
 283          switch ($env_select) {
 284              case ENV_SELECT_NEWER:
 285                  if (!is_file($file) || !is_readable($file) || filemtime($file) < filemtime($internalfile) ||
 286                      !$contents = file_get_contents($file)) {
 287                      /// Fallback to fixed $CFG->admin/environment.xml
 288                      if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
 289                          $contents = false;
 290                      }
 291                  }
 292                  break;
 293              case ENV_SELECT_DATAROOT:
 294                  if (!is_file($file) || !is_readable($file) || !$contents = file_get_contents($file)) {
 295                      $contents = false;
 296                  }
 297                  break;
 298              case ENV_SELECT_RELEASE:
 299                  if (!is_file($internalfile) || !is_readable($internalfile) || !$contents = file_get_contents($internalfile)) {
 300                      $contents = false;
 301                  }
 302                  break;
 303          }
 304      } else {
 305          if ($plugindir = core_component::get_component_directory($env_select)) {
 306              $pluginfile = "$plugindir/environment.xml";
 307              if (!is_file($pluginfile) || !is_readable($pluginfile) || !$contents = file_get_contents($pluginfile)) {
 308                  $contents = false;
 309              }
 310          }
 311      }
 312      // XML the whole file.
 313      if ($contents !== false) {
 314          $contents = xmlize($contents);
 315      }
 316  
 317      $data[$env_select] = $contents;
 318  
 319      return $data[$env_select];
 320  }
 321  
 322  
 323  /**
 324   * This function will return the list of Moodle versions available
 325   *
 326   * @return array of versions
 327   */
 328  function get_list_of_environment_versions($contents) {
 329      $versions = array();
 330  
 331      if (isset($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'])) {
 332          foreach ($contents['COMPATIBILITY_MATRIX']['#']['MOODLE'] as $version) {
 333              $versions[] = $version['@']['version'];
 334          }
 335      }
 336  
 337      if (isset($contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'])) {
 338          $versions[] = 'all';
 339      }
 340  
 341      return $versions;
 342  }
 343  
 344  
 345  /**
 346   * This function will return the most recent version in the environment.xml
 347   * file previous or equal to the version requested
 348   *
 349   * @param string $version top version from which we start to look backwards
 350   * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
 351   * @return string|bool string more recent version or false if not found
 352   */
 353  function get_latest_version_available($version, $env_select) {
 354      if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
 355          throw new coding_exception('Incorrect value of $env_select parameter');
 356      }
 357  
 358  /// Normalize the version requested
 359      $version = normalize_version($version);
 360  
 361  /// Load xml file
 362      if (!$contents = load_environment_xml($env_select)) {
 363          return false;
 364      }
 365  
 366  /// Detect available versions
 367      if (!$versions = get_list_of_environment_versions($contents)) {
 368          return false;
 369      }
 370  /// First we look for exact version
 371      if (in_array($version, $versions, true)) {
 372          return $version;
 373      } else {
 374          $found_version = false;
 375      /// Not exact match, so we are going to iterate over the list searching
 376      /// for the latest version before the requested one
 377          foreach ($versions as $arrversion) {
 378              if (version_compare($arrversion, $version, '<')) {
 379                  $found_version = $arrversion;
 380              }
 381          }
 382      }
 383  
 384      return $found_version;
 385  }
 386  
 387  
 388  /**
 389   * This function will return the xmlized data belonging to one Moodle version
 390   *
 391   * @param string $version top version from which we start to look backwards
 392   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 393   * @return mixed the xmlized structure or false on error
 394   */
 395  function get_environment_for_version($version, $env_select) {
 396  
 397  /// Normalize the version requested
 398      $version = normalize_version($version);
 399  
 400  /// Load xml file
 401      if (!$contents = load_environment_xml($env_select)) {
 402          return false;
 403      }
 404  
 405  /// Detect available versions
 406      if (!$versions = get_list_of_environment_versions($contents)) {
 407          return false;
 408      }
 409  
 410      // If $env_select is not numeric then this is being called on a plugin, and not the core environment.xml
 411      // If a version of 'all' is in the arry is also means that the new <PLUGIN> tag was found, this should
 412      // be matched against any version of Moodle.
 413      if (!is_numeric($env_select) && in_array('all', $versions)
 414              && environment_verify_plugin($env_select, $contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'][0])) {
 415          return $contents['COMPATIBILITY_MATRIX']['#']['PLUGIN'][0];
 416      }
 417  
 418  /// If the version requested is available
 419      if (!in_array($version, $versions, true)) {
 420          return false;
 421      }
 422  
 423  /// We now we have it. Extract from full contents.
 424      $fl_arr = array_flip($versions);
 425  
 426      return $contents['COMPATIBILITY_MATRIX']['#']['MOODLE'][$fl_arr[$version]];
 427  }
 428  
 429  /**
 430   * Checks if a plugin tag has a name attribute and it matches the plugin being tested.
 431   *
 432   * @param string $plugin the name of the plugin.
 433   * @param array $pluginxml the xmlised structure for the plugin tag being tested.
 434   * @return boolean true if the name attribute exists and matches the plugin being tested.
 435   */
 436  function environment_verify_plugin($plugin, $pluginxml) {
 437      if (!isset($pluginxml['@']['name']) || $pluginxml['@']['name'] != $plugin) {
 438          return false;
 439      }
 440      return true;
 441  }
 442  
 443  /**
 444   * This function will check for everything (DB, PHP and PHP extensions for now)
 445   * returning an array of environment_result objects.
 446   *
 447   * @global object
 448   * @param string $version xml version we are going to use to test this server
 449   * @param int $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use.
 450   * @return environment_results[] array of results encapsulated in one environment_result object
 451   */
 452  function environment_check($version, $env_select) {
 453      global $CFG;
 454  
 455      if ($env_select != ENV_SELECT_NEWER and $env_select != ENV_SELECT_DATAROOT and $env_select != ENV_SELECT_RELEASE) {
 456          throw new coding_exception('Incorrect value of $env_select parameter');
 457      }
 458  
 459  /// Normalize the version requested
 460      $version = normalize_version($version);
 461  
 462      $results = array(); //To store all the results
 463  
 464  /// Only run the moodle versions checker on upgrade, not on install
 465      if (!empty($CFG->version)) {
 466          $results[] = environment_check_moodle($version, $env_select);
 467      }
 468      $results[] = environment_check_unicode($version, $env_select);
 469      $results[] = environment_check_database($version, $env_select);
 470      $results[] = environment_check_php($version, $env_select);
 471  
 472      if ($result = environment_check_pcre_unicode($version, $env_select)) {
 473          $results[] = $result;
 474      }
 475  
 476      $phpext_results = environment_check_php_extensions($version, $env_select);
 477      $results = array_merge($results, $phpext_results);
 478  
 479      $phpsetting_results = environment_check_php_settings($version, $env_select);
 480      $results = array_merge($results, $phpsetting_results);
 481  
 482      $custom_results = environment_custom_checks($version, $env_select);
 483      $results = array_merge($results, $custom_results);
 484  
 485      // Always use the plugin directory version of environment.xml,
 486      // add-on developers need to keep those up-to-date with future info.
 487      foreach (core_component::get_plugin_types() as $plugintype => $unused) {
 488          foreach (core_component::get_plugin_list_with_file($plugintype, 'environment.xml') as $pluginname => $unused) {
 489              $plugin = $plugintype . '_' . $pluginname;
 490  
 491              $result = environment_check_database($version, $plugin);
 492              if ($result->error_code != NO_VERSION_DATA_FOUND
 493                  and $result->error_code != NO_DATABASE_SECTION_FOUND
 494                  and $result->error_code != NO_DATABASE_VENDORS_FOUND) {
 495  
 496                  $result->plugin = $plugin;
 497                  $results[] = $result;
 498              }
 499  
 500              $result = environment_check_php($version, $plugin);
 501              if ($result->error_code != NO_VERSION_DATA_FOUND
 502                  and $result->error_code != NO_PHP_SECTION_FOUND
 503                  and $result->error_code != NO_PHP_VERSION_FOUND) {
 504  
 505                  $result->plugin = $plugin;
 506                  $results[] = $result;
 507              }
 508  
 509              $pluginresults = environment_check_php_extensions($version, $plugin);
 510              foreach ($pluginresults as $result) {
 511                  if ($result->error_code != NO_VERSION_DATA_FOUND
 512                      and $result->error_code != NO_PHP_EXTENSIONS_SECTION_FOUND) {
 513  
 514                      $result->plugin = $plugin;
 515                      $results[] = $result;
 516                  }
 517              }
 518  
 519              $pluginresults = environment_check_php_settings($version, $plugin);
 520              foreach ($pluginresults as $result) {
 521                  if ($result->error_code != NO_VERSION_DATA_FOUND) {
 522                      $result->plugin = $plugin;
 523                      $results[] = $result;
 524                  }
 525              }
 526  
 527              $pluginresults = environment_custom_checks($version, $plugin);
 528              foreach ($pluginresults as $result) {
 529                  if ($result->error_code != NO_VERSION_DATA_FOUND) {
 530                      $result->plugin = $plugin;
 531                      $results[] = $result;
 532                  }
 533              }
 534          }
 535      }
 536  
 537      return $results;
 538  }
 539  
 540  
 541  /**
 542   * This function will check if php extensions requirements are satisfied
 543   *
 544   * @uses NO_VERSION_DATA_FOUND
 545   * @uses NO_PHP_EXTENSIONS_SECTION_FOUND
 546   * @uses NO_PHP_EXTENSIONS_NAME_FOUND
 547   * @param string $version xml version we are going to use to test this server
 548   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 549   * @return array array of results encapsulated in one environment_result object
 550   */
 551  function environment_check_php_extensions($version, $env_select) {
 552  
 553      $results = array();
 554  
 555  /// Get the enviroment version we need
 556      if (!$data = get_environment_for_version($version, $env_select)) {
 557      /// Error. No version data found
 558          $result = new environment_results('php_extension');
 559          $result->setStatus(false);
 560          $result->setErrorCode(NO_VERSION_DATA_FOUND);
 561          return array($result);
 562      }
 563  
 564  /// Extract the php_extension part
 565      if (!isset($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'])) {
 566      /// Error. No PHP section found
 567          $result = new environment_results('php_extension');
 568          $result->setStatus(false);
 569          $result->setErrorCode(NO_PHP_EXTENSIONS_SECTION_FOUND);
 570          return array($result);
 571      }
 572  /// Iterate over extensions checking them and creating the needed environment_results
 573      foreach($data['#']['PHP_EXTENSIONS']['0']['#']['PHP_EXTENSION'] as $extension) {
 574          $result = new environment_results('php_extension');
 575      /// Check for level
 576          $level = get_level($extension);
 577      /// Check for extension name
 578          if (!isset($extension['@']['name'])) {
 579              $result->setStatus(false);
 580              $result->setErrorCode(NO_PHP_EXTENSIONS_NAME_FOUND);
 581          } else {
 582              $extension_name = $extension['@']['name'];
 583          /// The name exists. Just check if it's an installed extension
 584              if (!extension_loaded($extension_name)) {
 585                  $result->setStatus(false);
 586              } else {
 587                  $result->setStatus(true);
 588              }
 589              $result->setLevel($level);
 590              $result->setInfo($extension_name);
 591          }
 592  
 593      /// Do any actions defined in the XML file.
 594          process_environment_result($extension, $result);
 595  
 596      /// Add the result to the array of results
 597          $results[] = $result;
 598      }
 599  
 600  
 601      return $results;
 602  }
 603  
 604  /**
 605   * This function will check if php extensions requirements are satisfied
 606   *
 607   * @uses NO_VERSION_DATA_FOUND
 608   * @uses NO_PHP_SETTINGS_NAME_FOUND
 609   * @param string $version xml version we are going to use to test this server
 610   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 611   * @return array array of results encapsulated in one environment_result object
 612   */
 613  function environment_check_php_settings($version, $env_select) {
 614  
 615      $results = array();
 616  
 617  /// Get the enviroment version we need
 618      if (!$data = get_environment_for_version($version, $env_select)) {
 619      /// Error. No version data found
 620          $result = new environment_results('php_setting');
 621          $result->setStatus(false);
 622          $result->setErrorCode(NO_VERSION_DATA_FOUND);
 623          $results[] = $result;
 624          return $results;
 625      }
 626  
 627  /// Extract the php_setting part
 628      if (!isset($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'])) {
 629      /// No PHP section found - ignore
 630          return $results;
 631      }
 632  /// Iterate over settings checking them and creating the needed environment_results
 633      foreach($data['#']['PHP_SETTINGS']['0']['#']['PHP_SETTING'] as $setting) {
 634          $result = new environment_results('php_setting');
 635      /// Check for level
 636          $level = get_level($setting);
 637          $result->setLevel($level);
 638      /// Check for extension name
 639          if (!isset($setting['@']['name'])) {
 640              $result->setStatus(false);
 641              $result->setErrorCode(NO_PHP_SETTINGS_NAME_FOUND);
 642          } else {
 643              $setting_name  = $setting['@']['name'];
 644              $setting_value = $setting['@']['value'];
 645              $result->setInfo($setting_name);
 646  
 647              if ($setting_name == 'memory_limit') {
 648                  $current = ini_get('memory_limit');
 649                  if ($current == -1) {
 650                      $result->setStatus(true);
 651                  } else {
 652                      $current  = get_real_size($current);
 653                      $minlimit = get_real_size($setting_value);
 654                      if ($current < $minlimit) {
 655                          @ini_set('memory_limit', $setting_value);
 656                          $current = ini_get('memory_limit');
 657                          $current = get_real_size($current);
 658                      }
 659                      $result->setStatus($current >= $minlimit);
 660                  }
 661  
 662              } else {
 663                  $current = ini_get_bool($setting_name);
 664              /// The name exists. Just check if it's an installed extension
 665                  if ($current == $setting_value) {
 666                      $result->setStatus(true);
 667                  } else {
 668                      $result->setStatus(false);
 669                  }
 670              }
 671          }
 672  
 673      /// Do any actions defined in the XML file.
 674          process_environment_result($setting, $result);
 675  
 676      /// Add the result to the array of results
 677          $results[] = $result;
 678      }
 679  
 680  
 681      return $results;
 682  }
 683  
 684  /**
 685   * This function will do the custom checks.
 686   *
 687   * @uses CUSTOM_CHECK_FUNCTION_MISSING
 688   * @uses CUSTOM_CHECK_FILE_MISSING
 689   * @uses NO_CUSTOM_CHECK_FOUND
 690   * @param string $version xml version we are going to use to test this server.
 691   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 692   * @return array array of results encapsulated in environment_result objects.
 693   */
 694  function environment_custom_checks($version, $env_select) {
 695      global $CFG;
 696  
 697      $results = array();
 698  
 699  /// Get current Moodle version (release) for later compare
 700      $release = isset($CFG->release) ? $CFG->release : $version; /// In case $CFG fails (at install) use $version
 701      $current_version = normalize_version($release);
 702  
 703  /// Get the enviroment version we need
 704      if (!$data = get_environment_for_version($version, $env_select)) {
 705      /// Error. No version data found - but this will already have been reported.
 706          return $results;
 707      }
 708  
 709  /// Extract the CUSTOM_CHECKS part
 710      if (!isset($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'])) {
 711      /// No custom checks found - not a problem
 712          return $results;
 713      }
 714  
 715  /// Iterate over extensions checking them and creating the needed environment_results
 716      foreach($data['#']['CUSTOM_CHECKS']['0']['#']['CUSTOM_CHECK'] as $check) {
 717          $result = new environment_results('custom_check');
 718  
 719      /// Check for level
 720          $level = get_level($check);
 721  
 722      /// Check for extension name
 723          if (isset($check['@']['function'])) {
 724              $function = $check['@']['function'];
 725              $file = null;
 726              if (isset($check['@']['file'])) {
 727                  $file = $CFG->dirroot . '/' . $check['@']['file'];
 728                  if (is_readable($file)) {
 729                      include_once($file);
 730                  }
 731              }
 732  
 733              if (is_callable($function)) {
 734                  $result->setLevel($level);
 735                  $result->setInfo($function);
 736                  $result = call_user_func($function, $result);
 737              } else if (!$file or is_readable($file)) {
 738              /// Only show error for current version (where function MUST exist)
 739              /// else, we are performing custom checks against future versiosn
 740              /// and function MAY not exist, so it doesn't cause error, just skip
 741              /// custom check by returning null. MDL-15939
 742                  if (version_compare($current_version, $version, '>=')) {
 743                      $result->setStatus(false);
 744                      $result->setInfo($function);
 745                      $result->setErrorCode(CUSTOM_CHECK_FUNCTION_MISSING);
 746                  } else {
 747                      $result = null;
 748                  }
 749              } else {
 750              /// Only show error for current version (where function MUST exist)
 751              /// else, we are performing custom checks against future versiosn
 752              /// and function MAY not exist, so it doesn't cause error, just skip
 753              /// custom check by returning null. MDL-15939
 754                  if (version_compare($current_version, $version, '>=')) {
 755                      $result->setStatus(false);
 756                      $result->setInfo($function);
 757                      $result->setErrorCode(CUSTOM_CHECK_FILE_MISSING);
 758                  } else {
 759                      $result = null;
 760                  }
 761              }
 762          } else {
 763              $result->setStatus(false);
 764              $result->setErrorCode(NO_CUSTOM_CHECK_FOUND);
 765          }
 766  
 767          if (!is_null($result)) {
 768          /// Do any actions defined in the XML file.
 769              process_environment_result($check, $result);
 770  
 771          /// Add the result to the array of results
 772              $results[] = $result;
 773          }
 774      }
 775  
 776      return $results;
 777  }
 778  
 779  /**
 780   * This function will check if Moodle requirements are satisfied
 781   *
 782   * @uses NO_VERSION_DATA_FOUND
 783   * @param string $version xml version we are going to use to test this server
 784   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 785   * @return object results encapsulated in one environment_result object
 786   */
 787  function environment_check_moodle($version, $env_select) {
 788  
 789      $result = new environment_results('moodle');
 790  
 791  /// Get the enviroment version we need
 792      if (!$data = get_environment_for_version($version, $env_select)) {
 793      /// Error. No version data found
 794          $result->setStatus(false);
 795          $result->setErrorCode(NO_VERSION_DATA_FOUND);
 796          return $result;
 797      }
 798  
 799  /// Extract the moodle part
 800      if (!isset($data['@']['requires'])) {
 801          $needed_version = '1.0'; /// Default to 1.0 if no moodle requires is found
 802      } else {
 803      /// Extract required moodle version
 804          $needed_version = $data['@']['requires'];
 805      }
 806  
 807  /// Now search the version we are using
 808      $release = get_config('', 'release');
 809      $current_version = normalize_version($release);
 810      if (strpos($release, 'dev') !== false) {
 811          // When final version is required, dev is NOT enough so, at all effects
 812          // it's like we are running the previous version.
 813          $versionarr = explode('.', $current_version);
 814          if (isset($versionarr[1]) and $versionarr[1] > 0) {
 815              $versionarr[1]--;
 816              $current_version = implode('.', $versionarr);
 817          } else {
 818              $current_version = $current_version - 0.1;
 819          }
 820      }
 821  
 822  /// And finally compare them, saving results
 823      if (version_compare($current_version, $needed_version, '>=')) {
 824          $result->setStatus(true);
 825      } else {
 826          $result->setStatus(false);
 827      }
 828      $result->setLevel('required');
 829      $result->setCurrentVersion($release);
 830      $result->setNeededVersion($needed_version);
 831  
 832      return $result;
 833  }
 834  
 835  /**
 836   * This function will check if php requirements are satisfied
 837   *
 838   * @uses NO_VERSION_DATA_FOUND
 839   * @uses NO_PHP_SECTION_FOUND
 840   * @uses NO_PHP_VERSION_FOUND
 841   * @param string $version xml version we are going to use to test this server
 842   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 843   * @return object results encapsulated in one environment_result object
 844   */
 845  function environment_check_php($version, $env_select) {
 846  
 847      $result = new environment_results('php');
 848  
 849  /// Get the enviroment version we need
 850      if (!$data = get_environment_for_version($version, $env_select)) {
 851      /// Error. No version data found
 852          $result->setStatus(false);
 853          $result->setErrorCode(NO_VERSION_DATA_FOUND);
 854          return $result;
 855      }
 856  
 857  /// Extract the php part
 858      if (!isset($data['#']['PHP'])) {
 859      /// Error. No PHP section found
 860          $result->setStatus(false);
 861          $result->setErrorCode(NO_PHP_SECTION_FOUND);
 862          return $result;
 863      } else {
 864      /// Extract level and version
 865          $level = get_level($data['#']['PHP']['0']);
 866          if (!isset($data['#']['PHP']['0']['@']['version'])) {
 867              $result->setStatus(false);
 868              $result->setErrorCode(NO_PHP_VERSION_FOUND);
 869              return $result;
 870          } else {
 871              $needed_version = $data['#']['PHP']['0']['@']['version'];
 872          }
 873      }
 874  
 875  /// Now search the version we are using
 876      $current_version = normalize_version(phpversion());
 877  
 878  /// And finally compare them, saving results
 879      if (version_compare($current_version, $needed_version, '>=')) {
 880          $result->setStatus(true);
 881      } else {
 882          $result->setStatus(false);
 883      }
 884      $result->setLevel($level);
 885      $result->setCurrentVersion($current_version);
 886      $result->setNeededVersion($needed_version);
 887  
 888  /// Do any actions defined in the XML file.
 889      process_environment_result($data['#']['PHP'][0], $result);
 890  
 891      return $result;
 892  }
 893  
 894  /**
 895   * Looks for buggy PCRE implementation, we need unicode support in Moodle...
 896   * @param string $version xml version we are going to use to test this server
 897   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 898   * @return stdClass results encapsulated in one environment_result object, null if irrelevant
 899   */
 900  function environment_check_pcre_unicode($version, $env_select) {
 901      $result = new environment_results('pcreunicode');
 902  
 903      // Get the environment version we need
 904      if (!$data = get_environment_for_version($version, $env_select)) {
 905          // Error. No version data found!
 906          $result->setStatus(false);
 907          $result->setErrorCode(NO_VERSION_DATA_FOUND);
 908          return $result;
 909      }
 910  
 911      if (!isset($data['#']['PCREUNICODE'])) {
 912          return null;
 913      }
 914  
 915      $level = get_level($data['#']['PCREUNICODE']['0']);
 916      $result->setLevel($level);
 917  
 918      if (!function_exists('preg_match')) {
 919          // The extension test fails instead.
 920          return null;
 921  
 922      } else if (@preg_match('/\pL/u', 'a') and @preg_match('/á/iu', 'Á')) {
 923          $result->setStatus(true);
 924  
 925      } else {
 926          $result->setStatus(false);
 927      }
 928  
 929      // Do any actions defined in the XML file.
 930      process_environment_result($data['#']['PCREUNICODE'][0], $result);
 931  
 932      return $result;
 933  }
 934  
 935  /**
 936   * This function will check if unicode database requirements are satisfied
 937   *
 938   * @uses NO_VERSION_DATA_FOUND
 939   * @uses NO_UNICODE_SECTION_FOUND
 940   * @param string $version xml version we are going to use to test this server
 941   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 942   * @return object results encapsulated in one environment_result object
 943   */
 944  function environment_check_unicode($version, $env_select) {
 945      global $DB;
 946  
 947      $result = new environment_results('unicode');
 948  
 949      /// Get the enviroment version we need
 950      if (!$data = get_environment_for_version($version, $env_select)) {
 951      /// Error. No version data found
 952          $result->setStatus(false);
 953          $result->setErrorCode(NO_VERSION_DATA_FOUND);
 954          return $result;
 955      }
 956  
 957      /// Extract the unicode part
 958  
 959      if (!isset($data['#']['UNICODE'])) {
 960      /// Error. No UNICODE section found
 961          $result->setStatus(false);
 962          $result->setErrorCode(NO_UNICODE_SECTION_FOUND);
 963          return $result;
 964      } else {
 965      /// Extract level
 966          $level = get_level($data['#']['UNICODE']['0']);
 967      }
 968  
 969      if (!$unicodedb = $DB->setup_is_unicodedb()) {
 970          $result->setStatus(false);
 971      } else {
 972          $result->setStatus(true);
 973      }
 974  
 975      $result->setLevel($level);
 976  
 977  /// Do any actions defined in the XML file.
 978      process_environment_result($data['#']['UNICODE'][0], $result);
 979  
 980      return $result;
 981  }
 982  
 983  /**
 984   * This function will check if database requirements are satisfied
 985   *
 986   * @uses NO_VERSION_DATA_FOUND
 987   * @uses NO_DATABASE_SECTION_FOUND
 988   * @uses NO_DATABASE_VENDORS_FOUND
 989   * @uses NO_DATABASE_VENDOR_MYSQL_FOUND
 990   * @uses NO_DATABASE_VENDOR_POSTGRES_FOUND
 991   * @uses NO_DATABASE_VENDOR_VERSION_FOUND
 992   * @param string $version xml version we are going to use to test this server
 993   * @param int|string $env_select one of ENV_SELECT_NEWER | ENV_SELECT_DATAROOT | ENV_SELECT_RELEASE decide xml to use. String means plugin name.
 994   * @return object results encapsulated in one environment_result object
 995   */
 996  function environment_check_database($version, $env_select) {
 997  
 998      global $DB;
 999  
1000      $result = new environment_results('database');
1001  
1002      $vendors = array();  //Array of vendors in version
1003  
1004  /// Get the enviroment version we need
1005      if (!$data = get_environment_for_version($version, $env_select)) {
1006      /// Error. No version data found
1007          $result->setStatus(false);
1008          $result->setErrorCode(NO_VERSION_DATA_FOUND);
1009          return $result;
1010      }
1011  
1012  /// Extract the database part
1013      if (!isset($data['#']['DATABASE'])) {
1014      /// Error. No DATABASE section found
1015          $result->setStatus(false);
1016          $result->setErrorCode(NO_DATABASE_SECTION_FOUND);
1017          return $result;
1018      } else {
1019      /// Extract level
1020          $level = get_level($data['#']['DATABASE']['0']);
1021      }
1022  
1023  /// Extract DB vendors. At least 2 are mandatory (mysql & postgres)
1024      if (!isset($data['#']['DATABASE']['0']['#']['VENDOR'])) {
1025      /// Error. No VENDORS found
1026          $result->setStatus(false);
1027          $result->setErrorCode(NO_DATABASE_VENDORS_FOUND);
1028          return $result;
1029      } else {
1030      /// Extract vendors
1031          foreach ($data['#']['DATABASE']['0']['#']['VENDOR'] as $vendor) {
1032              if (isset($vendor['@']['name']) && isset($vendor['@']['version'])) {
1033                  $vendors[$vendor['@']['name']] = $vendor['@']['version'];
1034                  $vendorsxml[$vendor['@']['name']] = $vendor;
1035              }
1036          }
1037      }
1038  /// Check we have the mysql vendor version
1039      if (empty($vendors['mysql'])) {
1040          $result->setStatus(false);
1041          $result->setErrorCode(NO_DATABASE_VENDOR_MYSQL_FOUND);
1042          return $result;
1043      }
1044  /// Check we have the postgres vendor version
1045      if (empty($vendors['postgres'])) {
1046          $result->setStatus(false);
1047          $result->setErrorCode(NO_DATABASE_VENDOR_POSTGRES_FOUND);
1048          return $result;
1049      }
1050  
1051  /// Now search the version we are using (depending of vendor)
1052      $current_vendor = $DB->get_dbvendor();
1053  
1054      $dbinfo = $DB->get_server_info();
1055      $current_version = normalize_version($dbinfo['version']);
1056      $needed_version = $vendors[$current_vendor];
1057  
1058  /// Check we have a needed version
1059      if (!$needed_version) {
1060          $result->setStatus(false);
1061          $result->setErrorCode(NO_DATABASE_VENDOR_VERSION_FOUND);
1062          return $result;
1063      }
1064  
1065      // Check if the DB Vendor has been properly configured.
1066      // Hack: this is required when playing with MySQL and MariaDB since they share the same PHP module and base DB classes,
1067      // whilst they are slowly evolving using separate directions though MariaDB is still an "almost" drop-in replacement.
1068      $dbvendorismysql = ($current_vendor === 'mysql');
1069      $dbtypeismariadb = (stripos($dbinfo['description'], 'mariadb') !== false);
1070      if ($dbvendorismysql && $dbtypeismariadb) {
1071          $result->setStatus(false);
1072          $result->setLevel($level);
1073          $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1074          $result->setFeedbackStr('environmentmariadbwrongdbtype');
1075          return $result;
1076      }
1077  
1078  /// And finally compare them, saving results
1079      if (version_compare($current_version, $needed_version, '>=')) {
1080          $result->setStatus(true);
1081      } else {
1082          $result->setStatus(false);
1083      }
1084      $result->setLevel($level);
1085      $result->setCurrentVersion($current_version);
1086      $result->setNeededVersion($needed_version);
1087      $result->setInfo($current_vendor . ' (' . $dbinfo['description'] . ')');
1088  
1089  /// Do any actions defined in the XML file.
1090      process_environment_result($vendorsxml[$current_vendor], $result);
1091  
1092      return $result;
1093  
1094  }
1095  
1096  /**
1097   * This function will post-process the result record by executing the specified
1098   * function, modifying it as necessary, also a custom message will be added
1099   * to the result object to be printed by the display layer.
1100   * Every bypass function must be defined in this file and it'll return
1101   * true/false to decide if the original test is bypassed or no. Also
1102   * such bypass functions are able to directly handling the result object
1103   * although it should be only under exceptional conditions.
1104   *
1105   * @param string xmldata containing the bypass data
1106   * @param object result object to be updated
1107   * @return void
1108   */
1109  function process_environment_bypass($xml, &$result) {
1110  
1111  /// Only try to bypass if we were in error and it was required
1112      if ($result->getStatus() || $result->getLevel() == 'optional') {
1113          return;
1114      }
1115  
1116  /// It there is bypass info (function and message)
1117      if (is_array($xml['#']) && isset($xml['#']['BYPASS'][0]['@']['function']) && isset($xml['#']['BYPASS'][0]['@']['message'])) {
1118          $function = $xml['#']['BYPASS'][0]['@']['function'];
1119          $message  = $xml['#']['BYPASS'][0]['@']['message'];
1120      /// Look for the function
1121          if (function_exists($function)) {
1122          /// Call it, and if bypass = true is returned, apply meesage
1123              if ($function($result)) {
1124              /// We only set the bypass message if the function itself hasn't defined it before
1125                  if (empty($result->getBypassStr)) {
1126                      if (isset($xml['#']['BYPASS'][0]['@']['plugin'])) {
1127                          $result->setBypassStr(array($message, $xml['#']['BYPASS'][0]['@']['plugin']));
1128                      } else {
1129                          $result->setBypassStr($message);
1130                      }
1131                  }
1132              }
1133          }
1134      }
1135  }
1136  
1137  /**
1138   * This function will post-process the result record by executing the specified
1139   * function, modifying it as necessary, also a custom message will be added
1140   * to the result object to be printed by the display layer.
1141   * Every restrict function must be defined in this file and it'll return
1142   * true/false to decide if the original test is restricted or no. Also
1143   * such restrict functions are able to directly handling the result object
1144   * although it should be only under exceptional conditions.
1145   *
1146   * @param string xmldata containing the restrict data
1147   * @param object result object to be updated
1148   * @return void
1149   */
1150  function process_environment_restrict($xml, &$result) {
1151  
1152  /// Only try to restrict if we were not in error and it was required
1153      if (!$result->getStatus() || $result->getLevel() == 'optional') {
1154          return;
1155      }
1156  /// It there is restrict info (function and message)
1157      if (is_array($xml['#']) && isset($xml['#']['RESTRICT'][0]['@']['function']) && isset($xml['#']['RESTRICT'][0]['@']['message'])) {
1158          $function = $xml['#']['RESTRICT'][0]['@']['function'];
1159          $message  = $xml['#']['RESTRICT'][0]['@']['message'];
1160      /// Look for the function
1161          if (function_exists($function)) {
1162          /// Call it, and if restrict = true is returned, apply meesage
1163              if ($function($result)) {
1164              /// We only set the restrict message if the function itself hasn't defined it before
1165                  if (empty($result->getRestrictStr)) {
1166                      if (isset($xml['#']['RESTRICT'][0]['@']['plugin'])) {
1167                          $result->setRestrictStr(array($message, $xml['#']['RESTRICT'][0]['@']['plugin']));
1168                      } else {
1169                          $result->setRestrictStr($message);
1170                      }
1171                  }
1172              }
1173          }
1174      }
1175  }
1176  
1177  /**
1178   * This function will detect if there is some message available to be added to the
1179   * result in order to clarify enviromental details.
1180   *
1181   * @uses INCORRECT_FEEDBACK_FOR_REQUIRED
1182   * @uses INCORRECT_FEEDBACK_FOR_OPTIONAL
1183   * @param string xmldata containing the feedback data
1184   * @param object reult object to be updated
1185   */
1186  function process_environment_messages($xml, &$result) {
1187  
1188  /// If there is feedback info
1189      if (is_array($xml['#']) && isset($xml['#']['FEEDBACK'][0]['#'])) {
1190          $feedbackxml = $xml['#']['FEEDBACK'][0]['#'];
1191  
1192          // Detect some incorrect feedback combinations.
1193          if ($result->getLevel() == 'required' and isset($feedbackxml['ON_CHECK'])) {
1194              $result->setStatus(false);
1195              $result->setErrorCode(INCORRECT_FEEDBACK_FOR_REQUIRED);
1196          } else if ($result->getLevel() == 'optional' and isset($feedbackxml['ON_ERROR'])) {
1197              $result->setStatus(false);
1198              $result->setErrorCode(INCORRECT_FEEDBACK_FOR_OPTIONAL);
1199          }
1200  
1201          if (!$result->status and $result->getLevel() == 'required') {
1202              if (isset($feedbackxml['ON_ERROR'][0]['@']['message'])) {
1203                  if (isset($feedbackxml['ON_ERROR'][0]['@']['plugin'])) {
1204                      $result->setFeedbackStr(array($feedbackxml['ON_ERROR'][0]['@']['message'], $feedbackxml['ON_ERROR'][0]['@']['plugin']));
1205                  } else {
1206                      $result->setFeedbackStr($feedbackxml['ON_ERROR'][0]['@']['message']);
1207                  }
1208              }
1209          } else if (!$result->status and $result->getLevel() == 'optional') {
1210              if (isset($feedbackxml['ON_CHECK'][0]['@']['message'])) {
1211                  if (isset($feedbackxml['ON_CHECK'][0]['@']['plugin'])) {
1212                      $result->setFeedbackStr(array($feedbackxml['ON_CHECK'][0]['@']['message'], $feedbackxml['ON_CHECK'][0]['@']['plugin']));
1213                  } else {
1214                      $result->setFeedbackStr($feedbackxml['ON_CHECK'][0]['@']['message']);
1215                  }
1216              }
1217          } else {
1218              if (isset($feedbackxml['ON_OK'][0]['@']['message'])) {
1219                  if (isset($feedbackxml['ON_OK'][0]['@']['plugin'])) {
1220                      $result->setFeedbackStr(array($feedbackxml['ON_OK'][0]['@']['message'], $feedbackxml['ON_OK'][0]['@']['plugin']));
1221                  } else {
1222                      $result->setFeedbackStr($feedbackxml['ON_OK'][0]['@']['message']);
1223                  }
1224              }
1225          }
1226      }
1227  }
1228  
1229  
1230  //--- Helper Class to return results to caller ---//
1231  
1232  
1233  /**
1234   * Helper Class to return results to caller
1235   *
1236   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1237   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1238   * @package moodlecore
1239   */
1240  class environment_results {
1241      /**
1242       * @var string Which are we checking (database, php, php_extension, php_extension)
1243       */
1244      var $part;
1245      /**
1246       * @var bool true means the test passed and all is OK. false means it failed.
1247       */
1248      var $status;
1249      /**
1250       * @var integer See constants at the beginning of the file
1251       */
1252      var $error_code;
1253      /**
1254       * @var string required/optional
1255       */
1256      var $level;
1257      /**
1258       * @var string current version detected
1259       */
1260      var $current_version;
1261      /**
1262       * @var string version needed
1263       */
1264      var $needed_version;
1265      /**
1266       * @var string Aux. info (DB vendor, library...)
1267       */
1268      var $info;
1269      /**
1270       * @var string String to show on error|on check|on ok
1271       */
1272      var $feedback_str;
1273      /**
1274       * @var string String to show if some bypass has happened
1275       */
1276      var $bypass_str;
1277      /**
1278       * @var string String to show if some restrict has happened
1279       */
1280      var $restrict_str;
1281      /**
1282       * @var string|null full plugin name or null if main environment
1283       */
1284      var $plugin = null;
1285      /**
1286       * Constructor of the environment_result class. Just set default values
1287       *
1288       * @param string $part
1289       */
1290      public function __construct($part) {
1291          $this->part=$part;
1292          $this->status=false;
1293          $this->error_code=NO_ERROR;
1294          $this->level='required';
1295          $this->current_version='';
1296          $this->needed_version='';
1297          $this->info='';
1298          $this->feedback_str='';
1299          $this->bypass_str='';
1300          $this->restrict_str='';
1301      }
1302  
1303      /**
1304       * Old syntax of class constructor. Deprecated in PHP7.
1305       *
1306       * @deprecated since Moodle 3.1
1307       */
1308      public function environment_results($part) {
1309          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
1310          self::__construct($part);
1311      }
1312  
1313      /**
1314       * Set the status
1315       *
1316       * @param bool $testpassed true means the test passed and all is OK. false means it failed.
1317       */
1318      function setStatus($testpassed) {
1319          $this->status = $testpassed;
1320          if ($testpassed) {
1321              $this->setErrorCode(NO_ERROR);
1322          }
1323      }
1324  
1325      /**
1326       * Set the error_code
1327       *
1328       * @param integer $error_code the error code (see constants above)
1329       */
1330      function setErrorCode($error_code) {
1331          $this->error_code=$error_code;
1332      }
1333  
1334      /**
1335       * Set the level
1336       *
1337       * @param string $level the level (required, optional)
1338       */
1339      function setLevel($level) {
1340          $this->level=$level;
1341      }
1342  
1343      /**
1344       * Set the current version
1345       *
1346       * @param string $current_version the current version
1347       */
1348      function setCurrentVersion($current_version) {
1349          $this->current_version=$current_version;
1350      }
1351  
1352      /**
1353       * Set the needed version
1354       *
1355       * @param string $needed_version the needed version
1356       */
1357      function setNeededVersion($needed_version) {
1358          $this->needed_version=$needed_version;
1359      }
1360  
1361      /**
1362       * Set the auxiliary info
1363       *
1364       * @param string $info the auxiliary info
1365       */
1366      function setInfo($info) {
1367          $this->info=$info;
1368      }
1369  
1370      /**
1371       * Set the feedback string
1372       *
1373       * @param mixed $str the feedback string that will be fetched from the admin lang file.
1374       *                  pass just the string or pass an array of params for get_string
1375       *                  You always should put your string in admin.php but a third param is useful
1376       *                  to pass an $a object / string to get_string
1377       */
1378      function setFeedbackStr($str) {
1379          $this->feedback_str=$str;
1380      }
1381  
1382  
1383      /**
1384       * Set the bypass string
1385       *
1386       * @param string $str the bypass string that will be fetched from the admin lang file.
1387       *                  pass just the string or pass an array of params for get_string
1388       *                  You always should put your string in admin.php but a third param is useful
1389       *                  to pass an $a object / string to get_string
1390       */
1391      function setBypassStr($str) {
1392          $this->bypass_str=$str;
1393      }
1394  
1395      /**
1396       * Set the restrict string
1397       *
1398       * @param string $str the restrict string that will be fetched from the admin lang file.
1399       *                  pass just the string or pass an array of params for get_string
1400       *                  You always should put your string in admin.php but a third param is useful
1401       *                  to pass an $a object / string to get_string
1402       */
1403      function setRestrictStr($str) {
1404          $this->restrict_str=$str;
1405      }
1406  
1407      /**
1408       * Get the status
1409       *
1410       * @return bool true means the test passed and all is OK. false means it failed.
1411       */
1412      function getStatus() {
1413          return $this->status;
1414      }
1415  
1416      /**
1417       * Get the error code
1418       *
1419       * @return integer error code
1420       */
1421      function getErrorCode() {
1422          return $this->error_code;
1423      }
1424  
1425      /**
1426       * Get the level
1427       *
1428       * @return string level
1429       */
1430      function getLevel() {
1431          return $this->level;
1432      }
1433  
1434      /**
1435       * Get the current version
1436       *
1437       * @return string current version
1438       */
1439      function getCurrentVersion() {
1440          return $this->current_version;
1441      }
1442  
1443      /**
1444       * Get the needed version
1445       *
1446       * @return string needed version
1447       */
1448      function getNeededVersion() {
1449          return $this->needed_version;
1450      }
1451  
1452      /**
1453       * Get the aux info
1454       *
1455       * @return string info
1456       */
1457      function getInfo() {
1458          return $this->info;
1459      }
1460  
1461      /**
1462       * Get the part this result belongs to
1463       *
1464       * @return string part
1465       */
1466      function getPart() {
1467          return $this->part;
1468      }
1469  
1470      /**
1471       * Get the feedback string
1472       *
1473       * @return mixed feedback string (can be an array of params for get_string or a single string to fetch from
1474       *                  admin.php lang file).
1475       */
1476      function getFeedbackStr() {
1477          return $this->feedback_str;
1478      }
1479  
1480      /**
1481       * Get the bypass string
1482       *
1483       * @return mixed bypass string (can be an array of params for get_string or a single string to fetch from
1484       *                  admin.php lang file).
1485       */
1486      function getBypassStr() {
1487          return $this->bypass_str;
1488      }
1489  
1490      /**
1491       * Get the restrict string
1492       *
1493       * @return mixed restrict string (can be an array of params for get_string or a single string to fetch from
1494       *                  admin.php lang file).
1495       */
1496      function getRestrictStr() {
1497          return $this->restrict_str;
1498      }
1499  
1500      /**
1501       * @todo Document this function
1502       *
1503       * @param mixed $string params for get_string, either a string to fetch from admin.php or an array of
1504       *                       params for get_string.
1505       * @param string $class css class(es) for message.
1506       * @return string feedback string fetched from lang file wrapped in p tag with class $class or returns
1507       *                              empty string if $string is empty.
1508       */
1509      function strToReport($string, $class){
1510          if (!empty($string)){
1511              if (is_array($string)){
1512                  $str = call_user_func_array('get_string', $string);
1513              } else {
1514                  $str = get_string($string, 'admin');
1515              }
1516              return '<p class="'.$class.'">'.$str.'</p>';
1517          } else {
1518              return '';
1519          }
1520      }
1521  
1522      /**
1523       * Get plugin name.
1524       *
1525       * @return string plugin name
1526       */
1527      function getPluginName() {
1528          if ($this->plugin) {
1529              $manager = core_plugin_manager::instance();
1530              list($plugintype, $pluginname) = core_component::normalize_component($this->plugin);
1531              return $manager->plugintype_name($plugintype) . ' / ' . $manager->plugin_name($this->plugin);
1532          } else {
1533              return '';
1534          }
1535      }
1536  }
1537  
1538  /// Here all the restrict functions are coded to be used by the environment
1539  /// checker. All those functions will receive the result object and will
1540  /// return it modified as needed (status and bypass string)
1541  
1542  /**
1543   * @param array $element the element from the environment.xml file that should have
1544   *      either a level="required" or level="optional" attribute.
1545   * @return string "required" or "optional".
1546   */
1547  function get_level($element) {
1548      $level = 'required';
1549      if (isset($element['@']['level'])) {
1550          $level = $element['@']['level'];
1551          if (!in_array($level, array('required', 'optional'))) {
1552              debugging('The level of a check in the environment.xml file must be "required" or "optional".', DEBUG_DEVELOPER);
1553              $level = 'required';
1554          }
1555      } else {
1556          debugging('Checks in the environment.xml file must have a level="required" or level="optional" attribute.', DEBUG_DEVELOPER);
1557      }
1558      return $level;
1559  }
1560  
1561  /**
1562   * Once the result has been determined, look in the XML for any
1563   * messages, or other things that should be done depending on the outcome.
1564   *
1565   * @param array $element the element from the environment.xml file which
1566   *      may have children defining what should be done with the outcome.
1567   * @param object $result the result of the test, which may be modified by
1568   *      this function as specified in the XML.
1569   */
1570  function process_environment_result($element, &$result) {
1571  /// Process messages, modifying the $result if needed.
1572      process_environment_messages($element, $result);
1573  /// Process bypass, modifying $result if needed.
1574      process_environment_bypass($element, $result);
1575  /// Process restrict, modifying $result if needed.
1576      process_environment_restrict($element, $result);
1577  }
1578  
1579  /**
1580   * Check if the current PHP version is greater than or equal to
1581   * PHP version 7.
1582   *
1583   * @param object $result an environment_results instance
1584   * @return bool result of version check
1585   */
1586  function restrict_php_version_7(&$result) {
1587      return restrict_php_version($result, '7');
1588  }
1589  
1590  /**
1591   * Check if the current PHP version is greater than or equal to an
1592   * unsupported version.
1593   *
1594   * @param object $result an environment_results instance
1595   * @param string $version the version of PHP that can't be used
1596   * @return bool result of version check
1597   */
1598  function restrict_php_version(&$result, $version) {
1599  
1600      // Get the current PHP version.
1601      $currentversion = normalize_version(phpversion());
1602  
1603      // Confirm we're using a supported PHP version.
1604      if (version_compare($currentversion, $version, '<')) {
1605          // Everything is ok, the restriction doesn't apply.
1606          return false;
1607      } else {
1608          // We're using an unsupported PHP version, apply restriction.
1609          return true;
1610      }
1611  }
1612  
1613  /**
1614   * Check if the current PHP version is greater than or equal to
1615   * PHP version 7.1.
1616   *
1617   * @param object $result an environment_results instance
1618   * @return bool result of version check
1619   */
1620  function restrict_php_version_71(&$result) {
1621      return restrict_php_version($result, '7.1');
1622  }
1623  
1624  /**
1625   * Check if the current PHP version is greater than or equal to
1626   * PHP version 7.2.
1627   *
1628   * @param object $result an environment_results instance
1629   * @return bool result of version check
1630   */
1631  function restrict_php_version_72(&$result) {
1632      return restrict_php_version($result, '7.2');
1633  }
1634  
1635  /**
1636   * Check if the current PHP version is greater than or equal to
1637   * PHP version 7.3.
1638   *
1639   * @param object $result an environment_results instance
1640   * @return bool result of version check
1641   */
1642  function restrict_php_version_73(&$result) {
1643      return restrict_php_version($result, '7.3');
1644  }
1645  
1646  /**
1647   * Check if the current PHP version is greater than or equal to
1648   * PHP version 7.4.
1649   *
1650   * @param object $result an environment_results instance
1651   * @return bool result of version check
1652   */
1653  function restrict_php_version_74(&$result) {
1654      return restrict_php_version($result, '7.4');
1655  }
1656  
1657  /**
1658   * Check if the current PHP version is greater than or equal to
1659   * PHP version 8.0
1660   *
1661   * @param object $result an environment_results instance
1662   * @return bool result of version check
1663   */
1664  function restrict_php_version_80($result) {
1665      return restrict_php_version($result, '8.0');
1666  }
1667  
1668  /**
1669   * Check if the current PHP version is greater than or equal to
1670   * PHP version 8.1
1671   *
1672   * @param object $result an environment_results instance
1673   * @return bool result of version check
1674   */
1675  function restrict_php_version_81($result) {
1676      return restrict_php_version($result, '8.1');
1677  }
1678  
1679  /**
1680   * Check if the current PHP version is greater than or equal to
1681   * PHP version 8.2
1682   *
1683   * @param object $result an environment_results instance
1684   * @return bool result of version check
1685   */
1686  function restrict_php_version_82($result) {
1687      return restrict_php_version($result, '8.2');
1688  }