public function waitUntilIndexingFinished() { $schema = DB::get_schema(); if (method_exists($schema, 'waitUntilIndexingFinished')) { $schema->waitUntilIndexingFinished(); } }
/** * Perform migration * * @param string $base Absolute base path (parent of assets folder). Will default to BASE_PATH * @return int Number of files successfully migrated */ public function run($base = null) { if (empty($base)) { $base = BASE_PATH; } // Check if the File dataobject has a "Filename" field. // If not, cannot migrate if (!DB::get_schema()->hasField('File', 'Filename')) { return 0; } // Set max time and memory limit increase_time_limit_to(); increase_memory_limit_to(); // Loop over all files $count = 0; $filenameMap = $this->getFilenameArray(); foreach ($this->getFileQuery() as $file) { // Get the name of the file to import $filename = $filenameMap[$file->ID]; $success = $this->migrateFile($base, $file, $filename); if ($success) { $count++; } } return $count; }
/** * Returns the manifest of all classes which are present in the database. * * @param string $class Class name to check enum values for ClassName field * @param boolean $includeUnbacked Flag indicating whether or not to include * types that don't exist as implemented classes. By default these are excluded. * @return array List of subclasses */ public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) { $classes = DB::get_schema()->enumValuesForField($class, 'ClassName'); if (!$includeUnbacked) { $classes = array_filter($classes, array('ClassInfo', 'exists')); } return $classes; }
protected function deleteAllTempDbs() { $prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_'; foreach (DB::get_schema()->databaseList() as $dbName) { if (preg_match(sprintf('/^%stmpdb[0-9]+$/', $prefix), $dbName)) { DB::get_schema()->dropDatabase($dbName); $this->info("Dropped database {$dbName}"); } } }
/** * Returns the manifest of all classes which are present in the database. * * @param string $class Class name to check enum values for ClassName field * @param boolean $includeUnbacked Flag indicating whether or not to include * types that don't exist as implemented classes. By default these are excluded. * @return array List of subclasses */ public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) { if (is_string($class) && !class_exists($class)) { return null; } $class = self::class_name($class); $classes = DB::get_schema()->enumValuesForField($class, 'ClassName'); if (!$includeUnbacked) { $classes = array_filter($classes, array('ClassInfo', 'exists')); } return $classes; }
/** * Check that once a schema has been generated, then it doesn't need any more updating */ public function testFieldsDontRerequestChanges() { // These are MySQL specific :-S if (DB::get_conn() instanceof MySQLDatabase) { $schema = DB::get_schema(); $test = $this; DB::quiet(); // Verify that it doesn't need to be recreated $schema->schemaUpdate(function () use($test, $schema) { $obj = new MySQLDatabaseTest_DO(); $obj->requireTable(); $needsUpdating = $schema->doesSchemaNeedUpdating(); $schema->cancelSchemaUpdate(); $test->assertFalse($needsUpdating); }); } }
/** * Check that updates to a dataobject's indexes are reflected in DDL */ public function testIndexesRerequestChanges() { $schema = DB::get_schema(); $test = $this; DB::quiet(); // Table will have been initially created by the $extraDataObjects setting // Update the SearchFields index here Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes', array('SearchFields' => array('value' => 'Title'))); // Verify that the above index change triggered a schema update $schema->schemaUpdate(function () use($test, $schema) { $obj = new DataObjectSchemaGenerationTest_IndexDO(); $obj->requireTable(); $needsUpdating = $schema->doesSchemaNeedUpdating(); $schema->cancelSchemaUpdate(); $test->assertTrue($needsUpdating); }); }
/** * Migrations for GroupSubsites data. */ public function requireDefaultRecords() { // Migration for Group.SubsiteID data from when Groups only had a single subsite $groupFields = DB::field_list('Group'); // Detection of SubsiteID field is the trigger for old-style-subsiteID migration if (isset($groupFields['SubsiteID'])) { // Migrate subsite-specific data DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID") SELECT "ID", "SubsiteID" FROM "Group" WHERE "SubsiteID" > 0'); // Migrate global-access data DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0'); // Move the field out of the way so that this migration doesn't get executed again DB::get_schema()->renameField('Group', 'SubsiteID', '_obsolete_SubsiteID'); // No subsite access on anything means that we've just installed the subsites module. // Make all previous groups global-access groups } elseif (!DB::query('SELECT "Group"."ID" FROM "Group" LEFT JOIN "Group_Subsites" ON "Group_Subsites"."GroupID" = "Group"."ID" AND "Group_Subsites"."SubsiteID" > 0 WHERE "AccessAllSubsites" = 1 OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()) { DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1'); } }
public function testUniqueIndexes() { $tableExpectations = array('VersionedTest_WithIndexes' => array('value' => true, 'message' => 'Unique indexes are unique in main table'), 'VersionedTest_WithIndexes_versions' => array('value' => false, 'message' => 'Unique indexes are no longer unique in _versions table'), 'VersionedTest_WithIndexes_Live' => array('value' => false, 'message' => 'Unique indexes are no longer unique in _Live table')); // Test each table's performance foreach ($tableExpectations as $tableName => $expectation) { $indexes = DB::get_schema()->indexList($tableName); // Check for presence of all unique indexes $indexColumns = array_map(function ($index) { return $index['value']; }, $indexes); sort($indexColumns); $expectedColumns = array('"UniqA"', '"UniqS"'); $this->assertEquals(array_values($expectedColumns), array_values(array_intersect($indexColumns, $expectedColumns)), "{$tableName} has both indexes"); // Check unique -> non-unique conversion foreach ($indexes as $indexKey => $indexSpec) { if (in_array($indexSpec['value'], $expectedColumns)) { $isUnique = $indexSpec['type'] === 'unique'; $this->assertEquals($isUnique, $expectation['value'], $expectation['message']); } } } }
public function requireField() { $spec = DB::get_schema()->IdColumn(false, $this->getAutoIncrement()); DB::require_field($this->getTable(), $this->getName(), $spec); }
public function requireDefaultRecords() { parent::requireDefaultRecords(); if ($this->config()->dont_upgrade_on_build) { return; } // Perform migrations DB::query(sprintf('UPDATE "%s" SET "%s" = \'%s\'', 'OrderStatusLog', 'ClassName', 'OrderLog')); if (DB::get_schema()->hasField('OrderStatusLog', 'Changes')) { $fields = '"' . implode('", "', array_intersect(array_keys(DB::get_schema()->fieldList('OrderLog')), array_keys(DB::get_schema()->fieldList('OrderStatusLog')))) . '"'; DB::query(sprintf('INSERT INTO "%s" (%s) SELECT %s FROM "%s" ON DUPLICATE KEY UPDATE ID=VALUES(ID)', 'OrderLog', $fields, $fields, 'OrderStatusLog')); } DB::alteration_message('Migrated order status logs', 'changed'); }
/** * Reset the testing database's schema. * @param $includeExtraDataObjects If true, the extraDataObjects tables will also be included */ public function resetDBSchema($includeExtraDataObjects = false) { if (self::using_temp_db()) { DataObject::reset(); // clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild() Injector::inst()->unregisterAllObjects(); $dataClasses = ClassInfo::subclassesFor('DataObject'); array_shift($dataClasses); DB::quiet(); $schema = DB::get_schema(); $extraDataObjects = $includeExtraDataObjects ? $this->extraDataObjects : null; $schema->schemaUpdate(function () use($dataClasses, $extraDataObjects) { foreach ($dataClasses as $dataClass) { // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness if (class_exists($dataClass)) { $SNG = singleton($dataClass); if (!$SNG instanceof TestOnly) { $SNG->requireTable(); } } } // If we have additional dataobjects which need schema, do so here: if ($extraDataObjects) { foreach ($extraDataObjects as $dataClass) { $SNG = singleton($dataClass); if (singleton($dataClass) instanceof DataObject) { $SNG->requireTable(); } } } }); ClassInfo::reset_db_cache(); singleton('DataObject')->flushCache(); } }
/** * Updates the database schema, creating tables & fields as necessary. * * @param boolean $quiet Don't show messages * @param boolean $populate Populate the database, as well as setting up its schema */ public function doBuild($quiet = false, $populate = true, $testMode = false) { if ($quiet) { DB::quiet(); } else { $conn = DB::get_conn(); // Assumes database class is like "MySQLDatabase" or "MSSQLDatabase" (suffixed with "Database") $dbType = substr(get_class($conn), 0, -8); $dbVersion = $conn->getVersion(); $databaseName = method_exists($conn, 'currentDatabase') ? $conn->getSelectedDatabase() : ""; if (Director::is_cli()) { echo sprintf("\n\nBuilding database %s using %s %s\n\n", $databaseName, $dbType, $dbVersion); } else { echo sprintf("<h2>Building database %s using %s %s</h2>", $databaseName, $dbType, $dbVersion); } } // Set up the initial database if (!DB::is_active()) { if (!$quiet) { echo '<p><b>Creating database</b></p>'; } // Load parameters from existing configuration global $databaseConfig; if (empty($databaseConfig) && empty($_REQUEST['db'])) { user_error("No database configuration available", E_USER_ERROR); } $parameters = !empty($databaseConfig) ? $databaseConfig : $_REQUEST['db']; // Check database name is given if (empty($parameters['database'])) { user_error("No database name given; please give a value for \$databaseConfig['database']", E_USER_ERROR); } $database = $parameters['database']; // Establish connection and create database in two steps unset($parameters['database']); DB::connect($parameters); DB::create_database($database); } // Build the database. Most of the hard work is handled by DataObject $dataClasses = ClassInfo::subclassesFor('DataObject'); array_shift($dataClasses); if (!$quiet) { if (Director::is_cli()) { echo "\nCREATING DATABASE TABLES\n\n"; } else { echo "\n<p><b>Creating database tables</b></p>\n\n"; } } // Initiate schema update $dbSchema = DB::get_schema(); $dbSchema->schemaUpdate(function () use($dataClasses, $testMode, $quiet) { foreach ($dataClasses as $dataClass) { // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness if (!class_exists($dataClass)) { continue; } // Check if this class should be excluded as per testing conventions $SNG = singleton($dataClass); if (!$testMode && $SNG instanceof TestOnly) { continue; } // Log data if (!$quiet) { if (Director::is_cli()) { echo " * {$dataClass}\n"; } else { echo "<li>{$dataClass}</li>\n"; } } // Instruct the class to apply its schema to the database $SNG->requireTable(); } }); ClassInfo::reset_db_cache(); if ($populate) { if (!$quiet) { if (Director::is_cli()) { echo "\nCREATING DATABASE RECORDS\n\n"; } else { echo "\n<p><b>Creating database records</b></p>\n\n"; } } foreach ($dataClasses as $dataClass) { // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness // Test_ indicates that it's the data class is part of testing system if (strpos($dataClass, 'Test_') === false && class_exists($dataClass)) { if (!$quiet) { if (Director::is_cli()) { echo " * {$dataClass}\n"; } else { echo "<li>{$dataClass}</li>\n"; } } singleton($dataClass)->requireDefaultRecords(); } } } touch(TEMP_FOLDER . '/database-last-generated-' . str_replace(array('\\', '/', ':'), '.', Director::baseFolder())); if (isset($_REQUEST['from_installer'])) { echo "OK"; } if (!$quiet) { echo Director::is_cli() ? "\n Database build completed!\n\n" : "<p>Database build completed!</p>"; } ClassInfo::reset_db_cache(); }
public function augmentDatabase() { $classTable = $this->owner->class; $isRootClass = $this->owner->class == ClassInfo::baseDataClass($this->owner->class); // Build a list of suffixes whose tables need versioning $allSuffixes = array(); foreach (Versioned::$versionableExtensions as $versionableExtension => $suffixes) { if ($this->owner->hasExtension($versionableExtension)) { $allSuffixes = array_merge($allSuffixes, (array) $suffixes); foreach ((array) $suffixes as $suffix) { $allSuffixes[$suffix] = $versionableExtension; } } } // Add the default table with an empty suffix to the list (table name = class name) array_push($allSuffixes, ''); foreach ($allSuffixes as $key => $suffix) { // check that this is a valid suffix if (!is_int($key)) { continue; } if ($suffix) { $table = "{$classTable}_{$suffix}"; } else { $table = $classTable; } if ($fields = DataObject::database_fields($this->owner->class)) { $options = Config::inst()->get($this->owner->class, 'create_table_options', Config::FIRST_SET); $indexes = $this->owner->databaseIndexes(); if ($suffix && ($ext = $this->owner->getExtensionInstance($allSuffixes[$suffix]))) { if (!$ext->isVersionedTable($table)) { continue; } $ext->setOwner($this->owner); $fields = $ext->fieldsInExtraTables($suffix); $ext->clearOwner(); $indexes = $fields['indexes']; $fields = $fields['db']; } // Create tables for other stages foreach ($this->stages as $stage) { // Extra tables for _Live, etc. // Change unique indexes to 'index'. Versioned tables may run into unique indexing difficulties // otherwise. $indexes = $this->uniqueToIndex($indexes); if ($stage != $this->defaultStage) { DB::require_table("{$table}_{$stage}", $fields, $indexes, false, $options); } // Version fields on each root table (including Stage) /* if($isRootClass) { $stageTable = ($stage == $this->defaultStage) ? $table : "{$table}_$stage"; $parts=Array('datatype'=>'int', 'precision'=>11, 'null'=>'not null', 'default'=>(int)0); $values=Array('type'=>'int', 'parts'=>$parts); DB::requireField($stageTable, 'Version', $values); } */ } if ($isRootClass) { // Create table for all versions $versionFields = array_merge(Config::inst()->get('Versioned', 'db_for_versions_table'), (array) $fields); $versionIndexes = array_merge(Config::inst()->get('Versioned', 'indexes_for_versions_table'), (array) $indexes); } else { // Create fields for any tables of subclasses $versionFields = array_merge(array("RecordID" => "Int", "Version" => "Int"), (array) $fields); //Unique indexes will not work on versioned tables, so we'll convert them to standard indexes: $indexes = $this->uniqueToIndex($indexes); $versionIndexes = array_merge(array('RecordID_Version' => array('type' => 'unique', 'value' => '"RecordID","Version"'), 'RecordID' => true, 'Version' => true), (array) $indexes); } if (DB::get_schema()->hasTable("{$table}_versions")) { // Fix data that lacks the uniqueness constraint (since this was added later and // bugs meant that the constraint was validated) $duplications = DB::query("SELECT MIN(\"ID\") AS \"ID\", \"RecordID\", \"Version\"\n\t\t\t\t\t\tFROM \"{$table}_versions\" GROUP BY \"RecordID\", \"Version\"\n\t\t\t\t\t\tHAVING COUNT(*) > 1"); foreach ($duplications as $dup) { DB::alteration_message("Removing {$table}_versions duplicate data for " . "{$dup['RecordID']}/{$dup['Version']}", "deleted"); DB::prepared_query("DELETE FROM \"{$table}_versions\" WHERE \"RecordID\" = ?\n\t\t\t\t\t\t\tAND \"Version\" = ? AND \"ID\" != ?", array($dup['RecordID'], $dup['Version'], $dup['ID'])); } // Remove junk which has no data in parent classes. Only needs to run the following // when versioned data is spread over multiple tables if (!$isRootClass && ($versionedTables = ClassInfo::dataClassesFor($table))) { foreach ($versionedTables as $child) { if ($table == $child) { break; } // only need subclasses $count = DB::query("\n\t\t\t\t\t\t\t\tSELECT COUNT(*) FROM \"{$table}_versions\"\n\t\t\t\t\t\t\t\tLEFT JOIN \"{$child}_versions\"\n\t\t\t\t\t\t\t\t\tON \"{$child}_versions\".\"RecordID\" = \"{$table}_versions\".\"RecordID\"\n\t\t\t\t\t\t\t\t\tAND \"{$child}_versions\".\"Version\" = \"{$table}_versions\".\"Version\"\n\t\t\t\t\t\t\t\tWHERE \"{$child}_versions\".\"ID\" IS NULL\n\t\t\t\t\t\t\t")->value(); if ($count > 0) { DB::alteration_message("Removing orphaned versioned records", "deleted"); $affectedIDs = DB::query("\n\t\t\t\t\t\t\t\t\tSELECT \"{$table}_versions\".\"ID\" FROM \"{$table}_versions\"\n\t\t\t\t\t\t\t\t\tLEFT JOIN \"{$child}_versions\"\n\t\t\t\t\t\t\t\t\t\tON \"{$child}_versions\".\"RecordID\" = \"{$table}_versions\".\"RecordID\"\n\t\t\t\t\t\t\t\t\t\tAND \"{$child}_versions\".\"Version\" = \"{$table}_versions\".\"Version\"\n\t\t\t\t\t\t\t\t\tWHERE \"{$child}_versions\".\"ID\" IS NULL\n\t\t\t\t\t\t\t\t")->column(); if (is_array($affectedIDs)) { foreach ($affectedIDs as $key => $value) { DB::prepared_query("DELETE FROM \"{$table}_versions\" WHERE \"ID\" = ?", array($value)); } } } } } } DB::require_table("{$table}_versions", $versionFields, $versionIndexes, true, $options); } else { DB::dont_require_table("{$table}_versions"); foreach ($this->stages as $stage) { if ($stage != $this->defaultStage) { DB::dont_require_table("{$table}_{$stage}"); } } } } }
public function compositeDatabaseFields() { // Ensure the table level cache exists if (empty(self::$classname_spec_cache[$this->tableName])) { self::$classname_spec_cache[$this->tableName] = array(); } // Ensure the field level cache exists if (empty(self::$classname_spec_cache[$this->tableName][$this->name])) { // Get all class names $classNames = ClassInfo::subclassesFor('DataObject'); unset($classNames['DataObject']); $schema = DB::get_schema(); if ($schema->hasField($this->tableName, "{$this->name}Class")) { $existing = DB::query("SELECT DISTINCT \"{$this->name}Class\" FROM \"{$this->tableName}\"")->column(); $classNames = array_unique(array_merge($classNames, $existing)); } self::$classname_spec_cache[$this->tableName][$this->name] = "Enum(array('" . implode("', '", array_filter($classNames)) . "'))"; } return array('ID' => 'Int', 'Class' => self::$classname_spec_cache[$this->tableName][$this->name]); }
public function testHasTable() { $this->assertTrue(DB::get_schema()->hasTable('DatabaseTest_MyObject')); $this->assertFalse(DB::get_schema()->hasTable('asdfasdfasdf')); }
/** * Add default records to database. * * This function is called whenever the database is built, after the database tables have all been created. Overload * this to add default records when the database is built, but make sure you call parent::requireDefaultRecords(). */ public function requireDefaultRecords() { parent::requireDefaultRecords(); // default pages if ($this->class == 'SiteTree' && $this->config()->create_default_pages) { if (!SiteTree::get_by_link(Config::inst()->get('RootURLController', 'default_homepage_link'))) { $homepage = new Page(); $homepage->Title = _t('SiteTree.DEFAULTHOMETITLE', 'Home'); $homepage->Content = _t('SiteTree.DEFAULTHOMECONTENT', '<p>Welcome to SilverStripe! This is the default homepage. You can edit this page by opening <a href="admin/">the CMS</a>.</p><p>You can now access the <a href="http://docs.silverstripe.org">developer documentation</a>, or begin the <a href="http://www.silverstripe.org/learn/lessons">SilverStripe lessons</a>.</p>'); $homepage->URLSegment = Config::inst()->get('RootURLController', 'default_homepage_link'); $homepage->Sort = 1; $homepage->write(); $homepage->publish('Stage', 'Live'); $homepage->flushCache(); DB::alteration_message('Home page created', 'created'); } if (DB::query("SELECT COUNT(*) FROM \"SiteTree\"")->value() == 1) { $aboutus = new Page(); $aboutus->Title = _t('SiteTree.DEFAULTABOUTTITLE', 'About Us'); $aboutus->Content = _t('SiteTree.DEFAULTABOUTCONTENT', '<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>'); $aboutus->Sort = 2; $aboutus->write(); $aboutus->publish('Stage', 'Live'); $aboutus->flushCache(); DB::alteration_message('About Us page created', 'created'); $contactus = new Page(); $contactus->Title = _t('SiteTree.DEFAULTCONTACTTITLE', 'Contact Us'); $contactus->Content = _t('SiteTree.DEFAULTCONTACTCONTENT', '<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>'); $contactus->Sort = 3; $contactus->write(); $contactus->publish('Stage', 'Live'); $contactus->flushCache(); DB::alteration_message('Contact Us page created', 'created'); } } // schema migration // @todo Move to migration task once infrastructure is implemented if ($this->class == 'SiteTree') { $conn = DB::get_schema(); // only execute command if fields haven't been renamed to _obsolete_<fieldname> already by the task if ($conn->hasField('SiteTree', 'Viewers')) { $task = new UpgradeSiteTreePermissionSchemaTask(); $task->run(new SS_HTTPRequest('GET', '/')); } } }
/** * Determines the specification for the ClassName field for the given class * * @param string $class * @param boolean $queryDB Determine if the DB may be queried for additional information * @return string Resulting ClassName spec. If $queryDB is true this will include all * legacy types that no longer have concrete classes in PHP */ public static function get_classname_spec($class, $queryDB = true) { // Check cache if (!empty(self::$classname_spec_cache[$class])) { return self::$classname_spec_cache[$class]; } // Build known class names $classNames = ClassInfo::subclassesFor($class); // Enhance with existing classes in order to prevent legacy details being lost if ($queryDB && DB::get_schema()->hasField($class, 'ClassName')) { $existing = DB::query("SELECT DISTINCT \"ClassName\" FROM \"{$class}\"")->column(); $classNames = array_unique(array_merge($classNames, $existing)); } $spec = "Enum('" . implode(', ', $classNames) . "')"; // Only cache full information if queried if ($queryDB) { self::$classname_spec_cache[$class] = $spec; } return $spec; }
public function testDatabaseIsReadyWithInsufficientMemberColumns() { $old = Security::$force_database_is_ready; Security::$force_database_is_ready = null; Security::$database_is_ready = false; DataObject::clear_classname_spec_cache(); // Assumption: The database has been built correctly by the test runner, // and has all columns present in the ORM DB::get_schema()->renameField('Member', 'Email', 'Email_renamed'); // Email column is now missing, which means we're not ready to do permission checks $this->assertFalse(Security::database_is_ready()); // Rebuild the database (which re-adds the Email column), and try again $this->resetDBSchema(true); $this->assertTrue(Security::database_is_ready()); Security::$force_database_is_ready = $old; }
/** * Get the list of classnames, including obsolete classes. * * If table or name are not set, or if it is not a valid field on the given table, * then only known classnames are returned. * * Values cached in this method can be cleared via `DBClassName::clear_classname_cache();` * * @return array */ public function getEnumObsolete() { // Without a table or field specified, we can only retrieve known classes $table = $this->getTable(); $name = $this->getName(); if (empty($table) || empty($name)) { return $this->getEnum(); } // Ensure the table level cache exists if (empty(self::$classname_cache[$table])) { self::$classname_cache[$table] = array(); } // Check existing cache if (!empty(self::$classname_cache[$table][$name])) { return self::$classname_cache[$table][$name]; } // Get all class names $classNames = $this->getEnum(); if (DB::get_schema()->hasField($table, $name)) { $existing = DB::query("SELECT DISTINCT \"{$name}\" FROM \"{$table}\"")->column(); $classNames = array_unique(array_merge($classNames, $existing)); } // Cache and return self::$classname_cache[$table][$name] = $classNames; return $classNames; }
public function requireTable() { // Migrate permission columns if (DB::get_schema()->hasTable('Forum')) { $fields = DB::get_schema()->fieldList('Forum'); if (in_array('ForumPosters', array_keys($fields)) && !in_array('CanPostType', array_keys($fields))) { DB::get_schema()->renameField('Forum', 'ForumPosters', 'CanPostType'); DB::alteration_message('Migrated forum permissions from "ForumPosters" to "CanPostType"', "created"); } } parent::requireTable(); }