Version: current

      1 <?php
      2 // Include the DB class
      3 require_once("core/model/DB.php");
      4
      5 /**
      6  * DatabaseAdmin class
      7  *
      8  * Utility functions for administrating the database. These can be accessed
      9  * via URL, e.g. http://www.yourdomain.com/db/build.
     10  *
     11  * @package sapphire
     12  * @subpackage model
     13  */
     14 class DatabaseAdmin extends Controller {
     15
     16     /// SECURITY ///
     17     static $allowed_actions = array(
     18         'index',
     19         'build',
     20         'cleanup',
     21         'testinstall',
     22         'import'
     23     );
     24
     25     function init() {
     26         parent::init();
     27
     28         // We allow access to this controller regardless of live-status or ADMIN permission only
     29         // if on CLI or with the database not ready. The latter makes it less errorprone to do an
     30         // initial schema build without requiring a default-admin login.
     31         // Access to this controller is always allowed in "dev-mode", or of the user is ADMIN.
     32         $canAccess = (
     33             Director::isDev()
     34             || !Security::database_is_ready()
     35             || Director::is_cli()
     36             || Permission::check("ADMIN")
     37         );
     38         if(!$canAccess) {
     39             return Security::permissionFailure($this,
     40                 "This page is secured and you need administrator rights to access it. " .
     41                 "Enter your credentials below and we will send you right along.");
     42         }
     43     }
     44
     45     /**
     46      * Get the data classes, grouped by their root class
     47      *
     48      * @return array Array of data classes, grouped by their root class
     49      */
     50     function groupedDataClasses() {
     51         // Get all root data objects
     52         $allClasses = get_declared_classes();
     53         foreach($allClasses as $class) {
     54             if(get_parent_class($class) == "DataObject")
     55                 $rootClasses[$class] = array();
     56         }
     57
     58         // Assign every other data object one of those
     59         foreach($allClasses as $class) {
     60             if(!isset($rootClasses[$class]) && is_subclass_of($class, "DataObject")) {
     61                 foreach($rootClasses as $rootClass => $dummy) {
     62                     if(is_subclass_of($class, $rootClass)) {
     63                         $rootClasses[$rootClass][] = $class;
     64                         break;
     65                     }
     66                 }
     67             }
     68         }
     69         return $rootClasses;
     70     }
     71
     72
     73     /**
     74      * Display a simple HTML menu of database admin helpers.
     75      */
     76     function index() {
     77         echo "<h2>Database Administration Helpers</h2>";
     78         echo "<p><a href=\"build\">Add missing database fields (similar to sanity check).</a></p>";
     79         echo "<p><a href=\"../images/flush\">Flush <b>all</b> of the generated images.</a></p>";
     80     }
     81
     82
     83     /**
     84      * Updates the database schema, creating tables & fields as necessary.
     85      */
     86     function build() {
     87         // The default time limit of 30 seconds is normally not enough
     88         increase_time_limit_to(600);
     89
     90         // Get all our classes
     91         ManifestBuilder::create_manifest_file();
     92         require(MANIFEST_FILE);
     93
     94         if(isset($_GET['returnURL'])) {
     95             echo "<p>Setting up the database; you will be returned to your site shortly....</p>";
     96             $this->doBuild(true);
     97             echo "<p>Done!</p>";
     98             Director::redirect($_GET['returnURL']);
     99         } else {
    100             if(!Director::is_cli() && Director::urlParam('Controller') == __CLASS__) {
    101                 echo '<p style="color: red;"><i>db/build</i> has been deprecated. Please use <b>dev/build</b> instead.</p>';
    102             }
    103             $this->doBuild(isset($_REQUEST['quiet']) || isset($_REQUEST['from_installer']), !isset($_REQUEST['dont_populate']));
    104         }
    105     }
    106
    107     /**
    108      * Check if database needs to be built, and build it if it does.
    109      */
    110     static function autoBuild() {
    111         $dataClasses = ClassInfo::subclassesFor('DataObject');
    112         $lastBuilt = self::lastBuilt();
    113         foreach($dataClasses as $class) {
    114             if(filemtime(getClassFile($class)) > $lastBuilt) {
    115                 $da = new DatabaseAdmin();
    116                 $da->doBuild(true);
    117                 return;
    118             }
    119         }
    120     }
    121
    122
    123     /**
    124      * Returns the timestamp of the time that the database was last built
    125      *
    126      * @return string Returns the timestamp of the time that the database was
    127      *                last built
    128      */
    129     static function lastBuilt() {
    130         $file = TEMP_FOLDER . '/database-last-generated-' .
    131             str_replace(array('\\','/',':'), '.' , Director::baseFolder());
    132
    133         if(file_exists($file)) {
    134             return filemtime($file);
    135         }
    136     }
    137
    138
    139     /**
    140      * Updates the database schema, creating tables & fields as necessary.
    141      *
    142      * @param boolean $quiet Don't show messages
    143      * @param boolean $populate Populate the database, as well as setting up its schema
    144      */
    145     function doBuild($quiet = false, $populate = true, $testMode = false) {
    146         if($quiet) {
    147             DB::quiet();
    148         } else {
    149             $conn = DB::getConn();
    150             $databaseName = (method_exists($conn, 'currentDatabase')) ? $conn->currentDatabase() : "";
    151
    152             if(Director::is_cli()) echo "\n\nBuilding Database $databaseName\n\n";
    153             else echo "<h2>Building Database $databaseName</h2>";
    154         }
    155
    156         // Set up the initial database
    157         if(!DB::isActive()) {
    158             if(!$quiet) {
    159                 echo '<p><b>Creating database</b></p>';
    160             }
    161             global $databaseConfig;
    162             $parameters = $databaseConfig ? $databaseConfig : $_REQUEST['db'];
    163             $connect = DB::getConnect($parameters);
    164             $username = $parameters['username'];
    165             $password = $parameters['password'];
    166             $database = $parameters['database'];
    167
    168             if(!$database) {
    169                 user_error("No database name given; please give a value for \$databaseConfig['database']", E_USER_ERROR);
    170             }
    171
    172             DB::createDatabase($connect, $username, $password, $database);
    173         }
    174
    175         // Build the database.  Most of the hard work is handled by DataObject
    176         $dataClasses = ClassInfo::subclassesFor('DataObject');
    177         array_shift($dataClasses);
    178
    179         if(!$quiet) {
    180             if(Director::is_cli()) echo "\nCREATING DATABASE TABLES\n\n";
    181             else echo "\n<p><b>Creating database tables</b></p>\n\n";
    182         }
    183
    184         $conn = DB::getConn();
    185         $conn->beginSchemaUpdate();
    186         foreach($dataClasses as $dataClass) {
    187             // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
    188             if(class_exists($dataClass)) {
    189                 $SNG = singleton($dataClass);
    190                 if($testMode || !($SNG instanceof TestOnly)) {
    191                     if(!$quiet) {
    192                         if(Director::is_cli()) echo " * $dataClass\n";
    193                         else echo "<li>$dataClass</li>\n";
    194                     }
    195                     $SNG->requireTable();
    196                 }
    197             }
    198         }
    199         $conn->endSchemaUpdate();
    200         ClassInfo::reset_db_cache();
    201
    202         if($populate) {
    203             if(!$quiet) {
    204                 if(Director::is_cli()) echo "\nCREATING DATABASE RECORDS\n\n";
    205                 else echo "\n<p><b>Creating database records</b></p>\n\n";
    206             }
    207
    208             foreach($dataClasses as $dataClass) {
    209                 // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
    210                 // Test_ indicates that it's the data class is part of testing system
    211                 if(strpos($dataClass,'Test_') === false && class_exists($dataClass)) {
    212                     if(!$quiet) {
    213                         if(Director::is_cli()) echo " * $dataClass\n";
    214                         else echo "<li>$dataClass</li>\n";
    215                     }
    216
    217                     singleton($dataClass)->requireDefaultRecords();
    218                 }
    219             }
    220         }
    221
    222         touch(TEMP_FOLDER . '/database-last-generated-' .
    223                     str_replace(array('\\', '/', ':'), '.', Director::baseFolder()));
    224
    225         if(isset($_REQUEST['from_installer'])) {
    226             echo "OK";
    227         }
    228
    229         if(!$quiet) echo "<p>Database build completed!</p>";
    230         ClassInfo::reset_db_cache();
    231     }
    232
    233     /**
    234      * Clear all data out of the database
    235      * @todo Move this code into SS_Database class, for DB abstraction
    236      */
    237     function clearAllData() {
    238         $tables = DB::getConn()->tableList();
    239         foreach($tables as $table) {
    240             if(method_exists(DB::getConn(), 'clearTable')) DB::getConn()->clearTable($table);
    241             else DB::query("TRUNCATE \"$table\"");
    242         }
    243     }
    244
    245
    246     /**
    247      * Method used to check mod_rewrite is working correctly in the installer.
    248      */
    249     function testinstall() {
    250         echo "OK";
    251     }
    252
    253
    254     /**
    255      * Remove invalid records from tables - that is, records that don't have
    256      * corresponding records in their parent class tables.
    257      */
    258     function cleanup() {
    259         $allClasses = get_declared_classes();
    260         foreach($allClasses as $class) {
    261             if(get_parent_class($class) == 'DataObject') {
    262                 $baseClasses[] = $class;
    263             }
    264         }
    265
    266         foreach($baseClasses as $baseClass) {
    267             // Get data classes
    268             $subclasses = ClassInfo::subclassesFor($baseClass);
    269             unset($subclasses[0]);
    270             foreach($subclasses as $k => $subclass) {
    271                 if(DataObject::database_fields($subclass)) {
    272                     unset($subclasses[$k]);
    273                 }
    274             }
    275
    276             if($subclasses) {
    277                 $records = DB::query("SELECT * FROM \"$baseClass\"");
    278
    279
    280                 foreach($subclasses as $subclass) {
    281                     $recordExists[$subclass] =
    282                         DB::query("SELECT \"ID\" FROM \"$subclass\"")->keyedColumn();
    283                 }
    284
    285                 foreach($records as $record) {
    286                     foreach($subclasses as $subclass) {
    287                         $id = $record['ID'];
    288                         if(($record['ClassName'] != $subclass) &&
    289                              (!is_subclass_of($record['ClassName'], $subclass)) &&
    290                              (isset($recordExists[$subclass][$id]))) {
    291                             $sql = "DELETE FROM \"$subclass\" WHERE \"ID\" = $record[ID]";
    292                             echo "<li>$sql";
    293                             DB::query($sql);
    294                         }
    295                     }
    296                 }
    297             }
    298         }
    299     }
    300
    301 }
    302
    303 ?>
    304

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -