Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • Differences Between: [Versions 28 and 29] [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * Testing util classes
      19   *
      20   * @abstract
      21   * @package    core
      22   * @category   test
      23   * @copyright  2012 Petr Skoda {@link http://skodak.org}
      24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      25   */
      26  
      27  /**
      28   * Utils for test sites creation
      29   *
      30   * @package   core
      31   * @category  test
      32   * @copyright 2012 Petr Skoda {@link http://skodak.org}
      33   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      34   */
      35  abstract class testing_util {
      36  
      37      /**
      38       * @var string dataroot (likely to be $CFG->dataroot).
      39       */
      40      private static $dataroot = null;
      41  
      42      /**
      43       * @var testing_data_generator
      44       */
      45      protected static $generator = null;
      46  
      47      /**
      48       * @var string current version hash from php files
      49       */
      50      protected static $versionhash = null;
      51  
      52      /**
      53       * @var array original content of all database tables
      54       */
      55      protected static $tabledata = null;
      56  
      57      /**
      58       * @var array original structure of all database tables
      59       */
      60      protected static $tablestructure = null;
      61  
      62      /**
      63       * @var array original structure of all database tables
      64       */
      65      protected static $sequencenames = null;
      66  
      67      /**
      68       * @var string name of the json file where we store the list of dataroot files to not reset during reset_dataroot.
      69       */
      70      private static $originaldatafilesjson = 'originaldatafiles.json';
      71  
      72      /**
      73       * @var boolean set to true once $originaldatafilesjson file is created.
      74       */
      75      private static $originaldatafilesjsonadded = false;
      76  
      77      /**
      78       * @var int next sequence value for a single test cycle.
      79       */
      80      protected static $sequencenextstartingid = null;
      81      /**
      82       * Return the name of the JSON file containing the init filenames.
      83       *
      84       * @static
      85       * @return string
      86       */
      87      public static function get_originaldatafilesjson() {
      88          return self::$originaldatafilesjson;
      89      }
      90  
      91      /**
      92       * Return the dataroot. It's useful when mocking the dataroot when unit testing this class itself.
      93       *
      94       * @static
      95       * @return string the dataroot.
      96       */
      97      public static function get_dataroot() {
      98          global $CFG;
      99  
     100          //  By default it's the test framework dataroot.
     101          if (empty(self::$dataroot)) {
     102              self::$dataroot = $CFG->dataroot;
     103          }
     104  
     105          return self::$dataroot;
     106      }
     107  
     108      /**
     109       * Set the dataroot. It's useful when mocking the dataroot when unit testing this class itself.
     110       *
     111       * @param string $dataroot the dataroot of the test framework.
     112       * @static
     113       */
     114      public static function set_dataroot($dataroot) {
     115          self::$dataroot = $dataroot;
     116      }
     117  
     118      /**
     119       * Returns the testing framework name
     120       * @static
     121       * @return string
     122       */
     123      protected static final function get_framework() {
     124          $classname = get_called_class();
     125          return substr($classname, 0, strpos($classname, '_'));
     126      }
     127  
     128      /**
     129       * Get data generator
     130       * @static
     131       * @return testing_data_generator
     132       */
     133      public static function get_data_generator() {
     134          if (is_null(self::$generator)) {
     135              require_once (__DIR__.'/../generator/lib.php');
     136              self::$generator = new testing_data_generator();
     137          }
     138          return self::$generator;
     139      }
     140  
     141      /**
     142       * Does this site (db and dataroot) appear to be used for production?
     143       * We try very hard to prevent accidental damage done to production servers!!
     144       *
     145       * @static
     146       * @return bool
     147       */
     148      public static function is_test_site() {
     149          global $DB, $CFG;
     150  
     151          $framework = self::get_framework();
     152  
     153          if (!file_exists(self::get_dataroot() . '/' . $framework . 'testdir.txt')) {
     154              // this is already tested in bootstrap script,
     155              // but anyway presence of this file means the dataroot is for testing
     156              return false;
     157          }
     158  
     159          $tables = $DB->get_tables(false);
     160          if ($tables) {
     161              if (!$DB->get_manager()->table_exists('config')) {
     162                  return false;
     163              }
     164              if (!get_config('core', $framework . 'test')) {
     165                  return false;
     166              }
     167          }
     168  
     169          return true;
     170      }
     171  
     172      /**
     173       * Returns whether test database and dataroot were created using the current version codebase
     174       *
     175       * @return bool
     176       */
     177      public static function is_test_data_updated() {
     178          global $CFG;
     179  
     180          $framework = self::get_framework();
     181  
     182          $datarootpath = self::get_dataroot() . '/' . $framework;
     183          if (!file_exists($datarootpath . '/tabledata.ser') or !file_exists($datarootpath . '/tablestructure.ser')) {
     184              return false;
     185          }
     186  
     187          if (!file_exists($datarootpath . '/versionshash.txt')) {
     188              return false;
     189          }
     190  
     191          $hash = core_component::get_all_versions_hash();
     192          $oldhash = file_get_contents($datarootpath . '/versionshash.txt');
     193  
     194          if ($hash !== $oldhash) {
     195              return false;
     196          }
     197  
     198          $dbhash = get_config('core', $framework . 'test');
     199          if ($hash !== $dbhash) {
     200              return false;
     201          }
     202  
     203          return true;
     204      }
     205  
     206      /**
     207       * Stores the status of the database
     208       *
     209       * Serializes the contents and the structure and
     210       * stores it in the test framework space in dataroot
     211       */
     212      protected static function store_database_state() {
     213          global $DB, $CFG;
     214  
     215          $framework = self::get_framework();
     216  
     217          // store data for all tables
     218          $data = array();
     219          $structure = array();
     220          $tables = $DB->get_tables();
     221          foreach ($tables as $table) {
     222              $columns = $DB->get_columns($table);
     223              $structure[$table] = $columns;
     224              if (isset($columns['id']) and $columns['id']->auto_increment) {
     225                  $data[$table] = $DB->get_records($table, array(), 'id ASC');
     226              } else {
     227                  // there should not be many of these
     228                  $data[$table] = $DB->get_records($table, array());
     229              }
     230          }
     231          $data = serialize($data);
     232          $datafile = self::get_dataroot() . '/' . $framework . '/tabledata.ser';
     233          file_put_contents($datafile, $data);
     234          testing_fix_file_permissions($datafile);
     235  
     236          $structure = serialize($structure);
     237          $structurefile = self::get_dataroot() . '/' . $framework . '/tablestructure.ser';
     238          file_put_contents($structurefile, $structure);
     239          testing_fix_file_permissions($structurefile);
     240      }
     241  
     242      /**
     243       * Stores the version hash in both database and dataroot
     244       */
     245      protected static function store_versions_hash() {
     246          global $CFG;
     247  
     248          $framework = self::get_framework();
     249          $hash = core_component::get_all_versions_hash();
     250  
     251          // add test db flag
     252          set_config($framework . 'test', $hash);
     253  
     254          // hash all plugin versions - helps with very fast detection of db structure changes
     255          $hashfile = self::get_dataroot() . '/' . $framework . '/versionshash.txt';
     256          file_put_contents($hashfile, $hash);
     257          testing_fix_file_permissions($hashfile);
     258      }
     259  
     260      /**
     261       * Returns contents of all tables right after installation.
     262       * @static
     263       * @return array  $table=>$records
     264       */
     265      protected static function get_tabledata() {
     266          global $CFG;
     267  
     268          $framework = self::get_framework();
     269  
     270          $datafile = self::get_dataroot() . '/' . $framework . '/tabledata.ser';
     271          if (!file_exists($datafile)) {
     272              // Not initialised yet.
     273              return array();
     274          }
     275  
     276          if (!isset(self::$tabledata)) {
     277              $data = file_get_contents($datafile);
     278              self::$tabledata = unserialize($data);
     279          }
     280  
     281          if (!is_array(self::$tabledata)) {
     282              testing_error(1, 'Can not read dataroot/' . $framework . '/tabledata.ser or invalid format, reinitialize test database.');
     283          }
     284  
     285          return self::$tabledata;
     286      }
     287  
     288      /**
     289       * Returns structure of all tables right after installation.
     290       * @static
     291       * @return array $table=>$records
     292       */
     293      public static function get_tablestructure() {
     294          global $CFG;
     295  
     296          $framework = self::get_framework();
     297  
     298          $structurefile = self::get_dataroot() . '/' . $framework . '/tablestructure.ser';
     299          if (!file_exists($structurefile)) {
     300              // Not initialised yet.
     301              return array();
     302          }
     303  
     304          if (!isset(self::$tablestructure)) {
     305              $data = file_get_contents($structurefile);
     306              self::$tablestructure = unserialize($data);
     307          }
     308  
     309          if (!is_array(self::$tablestructure)) {
     310              testing_error(1, 'Can not read dataroot/' . $framework . '/tablestructure.ser or invalid format, reinitialize test database.');
     311          }
     312  
     313          return self::$tablestructure;
     314      }
     315  
     316      /**
     317       * Returns the names of sequences for each autoincrementing id field in all standard tables.
     318       * @static
     319       * @return array $table=>$sequencename
     320       */
     321      public static function get_sequencenames() {
     322          global $DB;
     323  
     324          if (isset(self::$sequencenames)) {
     325              return self::$sequencenames;
     326          }
     327  
     328          if (!$structure = self::get_tablestructure()) {
     329              return array();
     330          }
     331  
     332          self::$sequencenames = array();
     333          foreach ($structure as $table => $ignored) {
     334              $name = $DB->get_manager()->generator->getSequenceFromDB(new xmldb_table($table));
     335              if ($name !== false) {
     336                  self::$sequencenames[$table] = $name;
     337              }
     338          }
     339  
     340          return self::$sequencenames;
     341      }
     342  
     343      /**
     344       * Returns list of tables that are unmodified and empty.
     345       *
     346       * @static
     347       * @return array of table names, empty if unknown
     348       */
     349      protected static function guess_unmodified_empty_tables() {
     350          global $DB;
     351  
     352          $dbfamily = $DB->get_dbfamily();
     353  
     354          if ($dbfamily === 'mysql') {
     355              $empties = array();
     356              $prefix = $DB->get_prefix();
     357              $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
     358              foreach ($rs as $info) {
     359                  $table = strtolower($info->name);
     360                  if (strpos($table, $prefix) !== 0) {
     361                      // incorrect table match caused by _
     362                      continue;
     363                  }
     364                  if (!is_null($info->auto_increment)) {
     365                      $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
     366                      if ($info->auto_increment == 1) {
     367                          $empties[$table] = $table;
     368                      }
     369                  }
     370              }
     371              $rs->close();
     372              return $empties;
     373  
     374          } else if ($dbfamily === 'mssql') {
     375              $empties = array();
     376              $prefix = $DB->get_prefix();
     377              $sql = "SELECT t.name
     378                        FROM sys.identity_columns i
     379                        JOIN sys.tables t ON t.object_id = i.object_id
     380                       WHERE t.name LIKE ?
     381                         AND i.name = 'id'
     382                         AND i.last_value IS NULL";
     383              $rs = $DB->get_recordset_sql($sql, array($prefix.'%'));
     384              foreach ($rs as $info) {
     385                  $table = strtolower($info->name);
     386                  if (strpos($table, $prefix) !== 0) {
     387                      // incorrect table match caused by _
     388                      continue;
     389                  }
     390                  $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
     391                  $empties[$table] = $table;
     392              }
     393              $rs->close();
     394              return $empties;
     395  
     396          } else if ($dbfamily === 'oracle') {
     397              $sequences = self::get_sequencenames();
     398              $sequences = array_map('strtoupper', $sequences);
     399              $lookup = array_flip($sequences);
     400              $empties = array();
     401              list($seqs, $params) = $DB->get_in_or_equal($sequences);
     402              $sql = "SELECT sequence_name FROM user_sequences WHERE last_number = 1 AND sequence_name $seqs";
     403              $rs = $DB->get_recordset_sql($sql, $params);
     404              foreach ($rs as $seq) {
     405                  $table = $lookup[$seq->sequence_name];
     406                  $empties[$table] = $table;
     407              }
     408              $rs->close();
     409              return $empties;
     410  
     411          } else {
     412              return array();
     413          }
     414      }
     415  
     416      /**
     417       * Determine the next unique starting id sequences.
     418       *
     419       * @static
     420       * @param array $records The records to use to determine the starting value for the table.
     421       * @return int The value the sequence should be set to.
     422       */
     423      private static function get_next_sequence_starting_value($records) {
     424          $id = self::$sequencenextstartingid;
     425  
     426          // If there are records, calculate the minimum id we can use.
     427          // It must be bigger than the last record's id.
     428          if (!empty($records)) {
     429              $lastrecord = end($records);
     430              $id = max($id, $lastrecord->id + 1);
     431          }
     432  
     433          self::$sequencenextstartingid = $id + 1000;
     434          return $id;
     435      }
     436  
     437      /**
     438       * Reset all database sequences to initial values.
     439       *
     440       * @static
     441       * @param array $empties tables that are known to be unmodified and empty
     442       * @return void
     443       */
     444      public static function reset_all_database_sequences(array $empties = null) {
     445          global $DB;
     446  
     447          if (!$data = self::get_tabledata()) {
     448              // Not initialised yet.
     449              return;
     450          }
     451          if (!$structure = self::get_tablestructure()) {
     452              // Not initialised yet.
     453              return;
     454          }
     455  
     456          // If all starting Id's are the same, it's difficult to detect coding and testing
     457          // errors that use the incorrect id in tests.  The classic case is cmid vs instance id.
     458          // To reduce the chance of the coding error, we start sequences at different values where possible.
     459          // In a attempt to avoid tables with existing id's we start at a high number.
     460          // Reset the value each time all database sequences are reset.
     461          if (defined('PHPUNIT_SEQUENCE_START') and PHPUNIT_SEQUENCE_START) {
     462              self::$sequencenextstartingid = PHPUNIT_SEQUENCE_START;
     463          } else {
     464              self::$sequencenextstartingid = 100000;
     465          }
     466  
     467          $dbfamily = $DB->get_dbfamily();
     468          if ($dbfamily === 'postgres') {
     469              $queries = array();
     470              $prefix = $DB->get_prefix();
     471              foreach ($data as $table => $records) {
     472                  if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
     473                      $nextid = self::get_next_sequence_starting_value($records);
     474                      $queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
     475                  }
     476              }
     477              if ($queries) {
     478                  $DB->change_database_structure(implode(';', $queries));
     479              }
     480  
     481          } else if ($dbfamily === 'mysql') {
     482              $sequences = array();
     483              $prefix = $DB->get_prefix();
     484              $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
     485              foreach ($rs as $info) {
     486                  $table = strtolower($info->name);
     487                  if (strpos($table, $prefix) !== 0) {
     488                      // incorrect table match caused by _
     489                      continue;
     490                  }
     491                  if (!is_null($info->auto_increment)) {
     492                      $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
     493                      $sequences[$table] = $info->auto_increment;
     494                  }
     495              }
     496              $rs->close();
     497              $prefix = $DB->get_prefix();
     498              foreach ($data as $table => $records) {
     499                  if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
     500                      if (isset($sequences[$table])) {
     501                          $nextid = self::get_next_sequence_starting_value($records);
     502                          if ($sequences[$table] != $nextid) {
     503                              $DB->change_database_structure("ALTER TABLE {$prefix}{$table} AUTO_INCREMENT = $nextid");
     504                          }
     505  
     506                      } else {
     507                          // some problem exists, fallback to standard code
     508                          $DB->get_manager()->reset_sequence($table);
     509                      }
     510                  }
     511              }
     512  
     513          } else if ($dbfamily === 'oracle') {
     514              $sequences = self::get_sequencenames();
     515              $sequences = array_map('strtoupper', $sequences);
     516              $lookup = array_flip($sequences);
     517  
     518              $current = array();
     519              list($seqs, $params) = $DB->get_in_or_equal($sequences);
     520              $sql = "SELECT sequence_name, last_number FROM user_sequences WHERE sequence_name $seqs";
     521              $rs = $DB->get_recordset_sql($sql, $params);
     522              foreach ($rs as $seq) {
     523                  $table = $lookup[$seq->sequence_name];
     524                  $current[$table] = $seq->last_number;
     525              }
     526              $rs->close();
     527  
     528              foreach ($data as $table => $records) {
     529                  if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
     530                      $lastrecord = end($records);
     531                      if ($lastrecord) {
     532                          $nextid = $lastrecord->id + 1;
     533                      } else {
     534                          $nextid = 1;
     535                      }
     536                      if (!isset($current[$table])) {
     537                          $DB->get_manager()->reset_sequence($table);
     538                      } else if ($nextid == $current[$table]) {
     539                          continue;
     540                      }
     541                      // reset as fast as possible - alternatively we could use http://stackoverflow.com/questions/51470/how-do-i-reset-a-sequence-in-oracle
     542                      $seqname = $sequences[$table];
     543                      $cachesize = $DB->get_manager()->generator->sequence_cache_size;
     544                      $DB->change_database_structure("DROP SEQUENCE $seqname");
     545                      $DB->change_database_structure("CREATE SEQUENCE $seqname START WITH $nextid INCREMENT BY 1 NOMAXVALUE CACHE $cachesize");
     546                  }
     547              }
     548  
     549          } else {
     550              // note: does mssql support any kind of faster reset?
     551              // This also implies mssql will not use unique sequence values.
     552              if (is_null($empties)) {
     553                  $empties = self::guess_unmodified_empty_tables();
     554              }
     555              foreach ($data as $table => $records) {
     556                  if (isset($empties[$table])) {
     557                      continue;
     558                  }
     559                  if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
     560                      $DB->get_manager()->reset_sequence($table);
     561                  }
     562              }
     563          }
     564      }
     565  
     566      /**
     567       * Reset all database tables to default values.
     568       * @static
     569       * @return bool true if reset done, false if skipped
     570       */
     571      public static function reset_database() {
     572          global $DB;
     573  
     574          $tables = $DB->get_tables(false);
     575          if (!$tables or empty($tables['config'])) {
     576              // not installed yet
     577              return false;
     578          }
     579  
     580          if (!$data = self::get_tabledata()) {
     581              // not initialised yet
     582              return false;
     583          }
     584          if (!$structure = self::get_tablestructure()) {
     585              // not initialised yet
     586              return false;
     587          }
     588  
     589          $empties = self::guess_unmodified_empty_tables();
     590  
     591          $borkedmysql = false;
     592          if ($DB->get_dbfamily() === 'mysql') {
     593              $version = $DB->get_server_info();
     594              if (version_compare($version['version'], '5.6.0') == 1 and version_compare($version['version'], '5.6.16') == -1) {
     595                  // Everything that comes from Oracle is evil!
     596                  //
     597                  // See http://dev.mysql.com/doc/refman/5.6/en/alter-table.html
     598                  // You cannot reset the counter to a value less than or equal to to the value that is currently in use.
     599                  //
     600                  // From 5.6.16 release notes:
     601                  //   InnoDB: The ALTER TABLE INPLACE algorithm would fail to decrease the auto-increment value.
     602                  //           (Bug #17250787, Bug #69882)
     603                  $borkedmysql = true;
     604  
     605              } else if (version_compare($version['version'], '10.0.0') == 1) {
     606                  // And MariaDB is no better!
     607                  // Let's hope they pick the patch sometime later...
     608                  $borkedmysql = true;
     609              }
     610          }
     611  
     612          if ($borkedmysql) {
     613              $mysqlsequences = array();
     614              $prefix = $DB->get_prefix();
     615              $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
     616              foreach ($rs as $info) {
     617                  $table = strtolower($info->name);
     618                  if (strpos($table, $prefix) !== 0) {
     619                      // Incorrect table match caused by _ char.
     620                      continue;
     621                  }
     622                  if (!is_null($info->auto_increment)) {
     623                      $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
     624                      $mysqlsequences[$table] = $info->auto_increment;
     625                  }
     626              }
     627          }
     628  
     629          foreach ($data as $table => $records) {
     630              if ($borkedmysql) {
     631                  if (empty($records) and isset($empties[$table])) {
     632                      continue;
     633                  }
     634  
     635                  if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
     636                      $current = $DB->get_records($table, array(), 'id ASC');
     637                      if ($current == $records) {
     638                          if (isset($mysqlsequences[$table]) and $mysqlsequences[$table] == $structure[$table]['id']->auto_increment) {
     639                              continue;
     640                          }
     641                      }
     642                  }
     643  
     644                  // Use TRUNCATE as a workaround and reinsert everything.
     645                  $DB->delete_records($table, null);
     646                  foreach ($records as $record) {
     647                      $DB->import_record($table, $record, false, true);
     648                  }
     649                  continue;
     650              }
     651  
     652              if (empty($records)) {
     653                  if (isset($empties[$table])) {
     654                      // table was not modified and is empty
     655                  } else {
     656                      $DB->delete_records($table, array());
     657                  }
     658                  continue;
     659              }
     660  
     661              if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
     662                  $currentrecords = $DB->get_records($table, array(), 'id ASC');
     663                  $changed = false;
     664                  foreach ($records as $id => $record) {
     665                      if (!isset($currentrecords[$id])) {
     666                          $changed = true;
     667                          break;
     668                      }
     669                      if ((array)$record != (array)$currentrecords[$id]) {
     670                          $changed = true;
     671                          break;
     672                      }
     673                      unset($currentrecords[$id]);
     674                  }
     675                  if (!$changed) {
     676                      if ($currentrecords) {
     677                          $lastrecord = end($records);
     678                          $DB->delete_records_select($table, "id > ?", array($lastrecord->id));
     679                          continue;
     680                      } else {
     681                          continue;
     682                      }
     683                  }
     684              }
     685  
     686              $DB->delete_records($table, array());
     687              foreach ($records as $record) {
     688                  $DB->import_record($table, $record, false, true);
     689              }
     690          }
     691  
     692          // reset all next record ids - aka sequences
     693          self::reset_all_database_sequences($empties);
     694  
     695          // remove extra tables
     696          foreach ($tables as $table) {
     697              if (!isset($data[$table])) {
     698                  $DB->get_manager()->drop_table(new xmldb_table($table));
     699              }
     700          }
     701  
     702          return true;
     703      }
     704  
     705      /**
     706       * Purge dataroot directory
     707       * @static
     708       * @return void
     709       */
     710      public static function reset_dataroot() {
     711          global $CFG;
     712  
     713          $childclassname = self::get_framework() . '_util';
     714  
     715          // Do not delete automatically installed files.
     716          self::skip_original_data_files($childclassname);
     717  
     718          // Clear file status cache, before checking file_exists.
     719          clearstatcache();
     720  
     721          // Clean up the dataroot folder.
     722          $handle = opendir(self::get_dataroot());
     723          while (false !== ($item = readdir($handle))) {
     724              if (in_array($item, $childclassname::$datarootskiponreset)) {
     725                  continue;
     726              }
     727              if (is_dir(self::get_dataroot()."/$item")) {
     728                  remove_dir(self::get_dataroot()."/$item", false);
     729              } else {
     730                  unlink(self::get_dataroot()."/$item");
     731              }
     732          }
     733          closedir($handle);
     734  
     735          // Clean up the dataroot/filedir folder.
     736          if (file_exists(self::get_dataroot() . '/filedir')) {
     737              $handle = opendir(self::get_dataroot() . '/filedir');
     738              while (false !== ($item = readdir($handle))) {
     739                  if (in_array('filedir/' . $item, $childclassname::$datarootskiponreset)) {
     740                      continue;
     741                  }
     742                  if (is_dir(self::get_dataroot()."/filedir/$item")) {
     743                      remove_dir(self::get_dataroot()."/filedir/$item", false);
     744                  } else {
     745                      unlink(self::get_dataroot()."/filedir/$item");
     746                  }
     747              }
     748              closedir($handle);
     749          }
     750  
     751          make_temp_directory('');
     752          make_cache_directory('');
     753          make_localcache_directory('');
     754          // Reset the cache API so that it recreates it's required directories as well.
     755          cache_factory::reset();
     756          // Purge all data from the caches. This is required for consistency.
     757          // Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache)
     758          // and now we will purge any other caches as well.
     759          cache_helper::purge_all();
     760      }
     761  
     762      /**
     763       * Gets a text-based site version description.
     764       *
     765       * @return string The site info
     766       */
     767      public static function get_site_info() {
     768          global $CFG;
     769  
     770          $output = '';
     771  
     772          // All developers have to understand English, do not localise!
     773  
     774          $release = null;
     775          require("$CFG->dirroot/version.php");
     776  
     777          $output .= "Moodle $release, $CFG->dbtype";
     778          if ($hash = self::get_git_hash()) {
     779              $output .= ", $hash";
     780          }
     781          $output .= "\n";
     782  
     783          return $output;
     784      }
     785  
     786      /**
     787       * Try to get current git hash of the Moodle in $CFG->dirroot.
     788       * @return string null if unknown, sha1 hash if known
     789       */
     790      public static function get_git_hash() {
     791          global $CFG;
     792  
     793          // This is a bit naive, but it should mostly work for all platforms.
     794  
     795          if (!file_exists("$CFG->dirroot/.git/HEAD")) {
     796              return null;
     797          }
     798  
     799          $headcontent = file_get_contents("$CFG->dirroot/.git/HEAD");
     800          if ($headcontent === false) {
     801              return null;
     802          }
     803  
     804          $headcontent = trim($headcontent);
     805  
     806          // If it is pointing to a hash we return it directly.
     807          if (strlen($headcontent) === 40) {
     808              return $headcontent;
     809          }
     810  
     811          if (strpos($headcontent, 'ref: ') !== 0) {
     812              return null;
     813          }
     814  
     815          $ref = substr($headcontent, 5);
     816  
     817          if (!file_exists("$CFG->dirroot/.git/$ref")) {
     818              return null;
     819          }
     820  
     821          $hash = file_get_contents("$CFG->dirroot/.git/$ref");
     822  
     823          if ($hash === false) {
     824              return null;
     825          }
     826  
     827          $hash = trim($hash);
     828  
     829          if (strlen($hash) != 40) {
     830              return null;
     831          }
     832  
     833          return $hash;
     834      }
     835  
     836      /**
     837       * Drop the whole test database
     838       * @static
     839       * @param bool $displayprogress
     840       */
     841      protected static function drop_database($displayprogress = false) {
     842          global $DB;
     843  
     844          $tables = $DB->get_tables(false);
     845          if (isset($tables['config'])) {
     846              // config always last to prevent problems with interrupted drops!
     847              unset($tables['config']);
     848              $tables['config'] = 'config';
     849          }
     850  
     851          if ($displayprogress) {
     852              echo "Dropping tables:\n";
     853          }
     854          $dotsonline = 0;
     855          foreach ($tables as $tablename) {
     856              $table = new xmldb_table($tablename);
     857              $DB->get_manager()->drop_table($table);
     858  
     859              if ($dotsonline == 60) {
     860                  if ($displayprogress) {
     861                      echo "\n";
     862                  }
     863                  $dotsonline = 0;
     864              }
     865              if ($displayprogress) {
     866                  echo '.';
     867              }
     868              $dotsonline += 1;
     869          }
     870          if ($displayprogress) {
     871              echo "\n";
     872          }
     873      }
     874  
     875      /**
     876       * Drops the test framework dataroot
     877       * @static
     878       */
     879      protected static function drop_dataroot() {
     880          global $CFG;
     881  
     882          $framework = self::get_framework();
     883          $childclassname = $framework . '_util';
     884  
     885          $files = scandir(self::get_dataroot() . '/'  . $framework);
     886          foreach ($files as $file) {
     887              if (in_array($file, $childclassname::$datarootskipondrop)) {
     888                  continue;
     889              }
     890              $path = self::get_dataroot() . '/' . $framework . '/' . $file;
     891              if (is_dir($path)) {
     892                  remove_dir($path, false);
     893              } else {
     894                  unlink($path);
     895              }
     896          }
     897  
     898          $jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
     899          if (file_exists($jsonfilepath)) {
     900              // Delete the json file.
     901              unlink($jsonfilepath);
     902              // Delete the dataroot filedir.
     903              remove_dir(self::get_dataroot() . '/filedir', false);
     904          }
     905      }
     906  
     907      /**
     908       * Skip the original dataroot files to not been reset.
     909       *
     910       * @static
     911       * @param string $utilclassname the util class name..
     912       */
     913      protected static function skip_original_data_files($utilclassname) {
     914          $jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
     915          if (file_exists($jsonfilepath)) {
     916  
     917              $listfiles = file_get_contents($jsonfilepath);
     918  
     919              // Mark each files as to not be reset.
     920              if (!empty($listfiles) && !self::$originaldatafilesjsonadded) {
     921                  $originaldatarootfiles = json_decode($listfiles);
     922                  // Keep the json file. Only drop_dataroot() should delete it.
     923                  $originaldatarootfiles[] = self::$originaldatafilesjson;
     924                  $utilclassname::$datarootskiponreset = array_merge($utilclassname::$datarootskiponreset,
     925                      $originaldatarootfiles);
     926                  self::$originaldatafilesjsonadded = true;
     927              }
     928          }
     929      }
     930  
     931      /**
     932       * Save the list of the original dataroot files into a json file.
     933       */
     934      protected static function save_original_data_files() {
     935          global $CFG;
     936  
     937          $jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
     938  
     939          // Save the original dataroot files if not done (only executed the first time).
     940          if (!file_exists($jsonfilepath)) {
     941  
     942              $listfiles = array();
     943              $listfiles['filedir/.'] = 'filedir/.';
     944              $listfiles['filedir/..'] = 'filedir/..';
     945  
     946              $filedir = self::get_dataroot() . '/filedir';
     947              if (file_exists($filedir)) {
     948                  $directory = new RecursiveDirectoryIterator($filedir);
     949                  foreach (new RecursiveIteratorIterator($directory) as $file) {
     950                      if ($file->isDir()) {
     951                          $key = substr($file->getPath(), strlen(self::get_dataroot() . '/'));
     952                      } else {
     953                          $key = substr($file->getPathName(), strlen(self::get_dataroot() . '/'));
     954                      }
     955                      $listfiles[$key] = $key;
     956                  }
     957              }
     958  
     959              // Save the file list in a JSON file.
     960              $fp = fopen($jsonfilepath, 'w');
     961              fwrite($fp, json_encode(array_values($listfiles)));
     962              fclose($fp);
     963          }
     964      }
     965  }
    

    Search This Site: