[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/testing/classes/ -> util.php (source)

   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          // Clean up the dataroot folder.
 719          $handle = opendir(self::get_dataroot());
 720          while (false !== ($item = readdir($handle))) {
 721              if (in_array($item, $childclassname::$datarootskiponreset)) {
 722                  continue;
 723              }
 724              if (is_dir(self::get_dataroot()."/$item")) {
 725                  remove_dir(self::get_dataroot()."/$item", false);
 726              } else {
 727                  unlink(self::get_dataroot()."/$item");
 728              }
 729          }
 730          closedir($handle);
 731  
 732          // Clean up the dataroot/filedir folder.
 733          if (file_exists(self::get_dataroot() . '/filedir')) {
 734              $handle = opendir(self::get_dataroot() . '/filedir');
 735              while (false !== ($item = readdir($handle))) {
 736                  if (in_array('filedir/' . $item, $childclassname::$datarootskiponreset)) {
 737                      continue;
 738                  }
 739                  if (is_dir(self::get_dataroot()."/filedir/$item")) {
 740                      remove_dir(self::get_dataroot()."/filedir/$item", false);
 741                  } else {
 742                      unlink(self::get_dataroot()."/filedir/$item");
 743                  }
 744              }
 745              closedir($handle);
 746          }
 747  
 748          make_temp_directory('');
 749          make_cache_directory('');
 750          make_localcache_directory('');
 751          // Reset the cache API so that it recreates it's required directories as well.
 752          cache_factory::reset();
 753          // Purge all data from the caches. This is required for consistency.
 754          // Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache)
 755          // and now we will purge any other caches as well.
 756          cache_helper::purge_all();
 757      }
 758  
 759      /**
 760       * Gets a text-based site version description.
 761       *
 762       * @return string The site info
 763       */
 764      public static function get_site_info() {
 765          global $CFG;
 766  
 767          $output = '';
 768  
 769          // All developers have to understand English, do not localise!
 770  
 771          $release = null;
 772          require("$CFG->dirroot/version.php");
 773  
 774          $output .= "Moodle $release, $CFG->dbtype";
 775          if ($hash = self::get_git_hash()) {
 776              $output .= ", $hash";
 777          }
 778          $output .= "\n";
 779  
 780          return $output;
 781      }
 782  
 783      /**
 784       * Try to get current git hash of the Moodle in $CFG->dirroot.
 785       * @return string null if unknown, sha1 hash if known
 786       */
 787      public static function get_git_hash() {
 788          global $CFG;
 789  
 790          // This is a bit naive, but it should mostly work for all platforms.
 791  
 792          if (!file_exists("$CFG->dirroot/.git/HEAD")) {
 793              return null;
 794          }
 795  
 796          $headcontent = file_get_contents("$CFG->dirroot/.git/HEAD");
 797          if ($headcontent === false) {
 798              return null;
 799          }
 800  
 801          $headcontent = trim($headcontent);
 802  
 803          // If it is pointing to a hash we return it directly.
 804          if (strlen($headcontent) === 40) {
 805              return $headcontent;
 806          }
 807  
 808          if (strpos($headcontent, 'ref: ') !== 0) {
 809              return null;
 810          }
 811  
 812          $ref = substr($headcontent, 5);
 813  
 814          if (!file_exists("$CFG->dirroot/.git/$ref")) {
 815              return null;
 816          }
 817  
 818          $hash = file_get_contents("$CFG->dirroot/.git/$ref");
 819  
 820          if ($hash === false) {
 821              return null;
 822          }
 823  
 824          $hash = trim($hash);
 825  
 826          if (strlen($hash) != 40) {
 827              return null;
 828          }
 829  
 830          return $hash;
 831      }
 832  
 833      /**
 834       * Drop the whole test database
 835       * @static
 836       * @param bool $displayprogress
 837       */
 838      protected static function drop_database($displayprogress = false) {
 839          global $DB;
 840  
 841          $tables = $DB->get_tables(false);
 842          if (isset($tables['config'])) {
 843              // config always last to prevent problems with interrupted drops!
 844              unset($tables['config']);
 845              $tables['config'] = 'config';
 846          }
 847  
 848          if ($displayprogress) {
 849              echo "Dropping tables:\n";
 850          }
 851          $dotsonline = 0;
 852          foreach ($tables as $tablename) {
 853              $table = new xmldb_table($tablename);
 854              $DB->get_manager()->drop_table($table);
 855  
 856              if ($dotsonline == 60) {
 857                  if ($displayprogress) {
 858                      echo "\n";
 859                  }
 860                  $dotsonline = 0;
 861              }
 862              if ($displayprogress) {
 863                  echo '.';
 864              }
 865              $dotsonline += 1;
 866          }
 867          if ($displayprogress) {
 868              echo "\n";
 869          }
 870      }
 871  
 872      /**
 873       * Drops the test framework dataroot
 874       * @static
 875       */
 876      protected static function drop_dataroot() {
 877          global $CFG;
 878  
 879          $framework = self::get_framework();
 880          $childclassname = $framework . '_util';
 881  
 882          $files = scandir(self::get_dataroot() . '/'  . $framework);
 883          foreach ($files as $file) {
 884              if (in_array($file, $childclassname::$datarootskipondrop)) {
 885                  continue;
 886              }
 887              $path = self::get_dataroot() . '/' . $framework . '/' . $file;
 888              if (is_dir($path)) {
 889                  remove_dir($path, false);
 890              } else {
 891                  unlink($path);
 892              }
 893          }
 894  
 895          $jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
 896          if (file_exists($jsonfilepath)) {
 897              // Delete the json file.
 898              unlink($jsonfilepath);
 899              // Delete the dataroot filedir.
 900              remove_dir(self::get_dataroot() . '/filedir', false);
 901          }
 902      }
 903  
 904      /**
 905       * Skip the original dataroot files to not been reset.
 906       *
 907       * @static
 908       * @param string $utilclassname the util class name..
 909       */
 910      protected static function skip_original_data_files($utilclassname) {
 911          $jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
 912          if (file_exists($jsonfilepath)) {
 913  
 914              $listfiles = file_get_contents($jsonfilepath);
 915  
 916              // Mark each files as to not be reset.
 917              if (!empty($listfiles) && !self::$originaldatafilesjsonadded) {
 918                  $originaldatarootfiles = json_decode($listfiles);
 919                  // Keep the json file. Only drop_dataroot() should delete it.
 920                  $originaldatarootfiles[] = self::$originaldatafilesjson;
 921                  $utilclassname::$datarootskiponreset = array_merge($utilclassname::$datarootskiponreset,
 922                      $originaldatarootfiles);
 923                  self::$originaldatafilesjsonadded = true;
 924              }
 925          }
 926      }
 927  
 928      /**
 929       * Save the list of the original dataroot files into a json file.
 930       */
 931      protected static function save_original_data_files() {
 932          global $CFG;
 933  
 934          $jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
 935  
 936          // Save the original dataroot files if not done (only executed the first time).
 937          if (!file_exists($jsonfilepath)) {
 938  
 939              $listfiles = array();
 940              $listfiles['filedir/.'] = 'filedir/.';
 941              $listfiles['filedir/..'] = 'filedir/..';
 942  
 943              $filedir = self::get_dataroot() . '/filedir';
 944              if (file_exists($filedir)) {
 945                  $directory = new RecursiveDirectoryIterator($filedir);
 946                  foreach (new RecursiveIteratorIterator($directory) as $file) {
 947                      if ($file->isDir()) {
 948                          $key = substr($file->getPath(), strlen(self::get_dataroot() . '/'));
 949                      } else {
 950                          $key = substr($file->getPathName(), strlen(self::get_dataroot() . '/'));
 951                      }
 952                      $listfiles[$key] = $key;
 953                  }
 954              }
 955  
 956              // Save the file list in a JSON file.
 957              $fp = fopen($jsonfilepath, 'w');
 958              fwrite($fp, json_encode(array_values($listfiles)));
 959              fclose($fp);
 960          }
 961      }
 962  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1