public function index() { foreach (ClassInfo::subclassesFor($this->class) as $subclass) { echo $subclass . "\n"; /** @var CliController $task */ $task = Injector::inst()->create($subclass); $task->doInit(); $task->process(); } }
public function testClassInfoIsCorrect() { $this->assertContains('SilverStripe\\Framework\\Tests\\ClassI', ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider')); //because we're using a nested manifest we have to "coalesce" the descendants again to correctly populate the // descendants of the core classes we want to test against - this is a limitation of the test manifest not // including all core classes $method = new ReflectionMethod($this->manifest, 'coalesceDescendants'); $method->setAccessible(true); $method->invoke($this->manifest, 'SilverStripe\\Admin\\ModelAdmin'); $this->assertContains('SilverStripe\\Framework\\Tests\\ClassI', ClassInfo::subclassesFor('SilverStripe\\Admin\\ModelAdmin')); }
/** * @return array Array of associative arrays for each task (Keys: 'class', 'title', 'description') */ protected function getTasks() { $availableTasks = array(); $taskClasses = ClassInfo::subclassesFor('SilverStripe\\Dev\\BuildTask'); // remove the base class array_shift($taskClasses); foreach ($taskClasses as $class) { if (!$this->taskEnabled($class)) { continue; } $singleton = BuildTask::singleton($class); $desc = Director::is_cli() ? Convert::html2raw($singleton->getDescription()) : $singleton->getDescription(); $availableTasks[] = array('class' => $class, 'title' => $singleton->getTitle(), 'segment' => $singleton->config()->segment ?: str_replace('\\', '-', $class), 'description' => $desc); } return $availableTasks; }
public function providePermissions() { $perms = array("CMS_ACCESS_LeftAndMain" => array('name' => _t('CMSMain.ACCESSALLINTERFACES', 'Access to all CMS sections'), 'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'), 'help' => _t('CMSMain.ACCESSALLINTERFACESHELP', 'Overrules more specific access settings.'), 'sort' => -100)); // Add any custom ModelAdmin subclasses. Can't put this on ModelAdmin itself // since its marked abstract, and needs to be singleton instanciated. foreach (ClassInfo::subclassesFor('SilverStripe\\Admin\\ModelAdmin') as $i => $class) { if ($class == 'SilverStripe\\Admin\\ModelAdmin') { continue; } if (ClassInfo::classImplements($class, 'SilverStripe\\Dev\\TestOnly')) { continue; } // Check if modeladmin has explicit required_permission_codes option. // If a modeladmin is namespaced you can apply this config to override // the default permission generation based on fully qualified class name. $code = $this->getRequiredPermissions(); if (!$code) { continue; } // Get first permission if multiple specified if (is_array($code)) { $code = reset($code); } $title = LeftAndMain::menu_title($class); $perms[$code] = array('name' => _t('CMSMain.ACCESS', "Access to '{title}' section", "Item in permission selection identifying the admin section. Example: Access to 'Files & Images'", array('title' => $title)), 'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')); } return $perms; }
/** * Remove invalid records from tables - that is, records that don't have * corresponding records in their parent class tables. */ public function cleanup() { $baseClasses = []; foreach (ClassInfo::subclassesFor(DataObject::class) as $class) { if (get_parent_class($class) == DataObject::class) { $baseClasses[] = $class; } } $schema = DataObject::getSchema(); foreach ($baseClasses as $baseClass) { // Get data classes $baseTable = $schema->baseDataTable($baseClass); $subclasses = ClassInfo::subclassesFor($baseClass); unset($subclasses[0]); foreach ($subclasses as $k => $subclass) { if (!DataObject::getSchema()->classHasTable($subclass)) { unset($subclasses[$k]); } } if ($subclasses) { $records = DB::query("SELECT * FROM \"{$baseTable}\""); foreach ($subclasses as $subclass) { $subclassTable = $schema->tableName($subclass); $recordExists[$subclass] = DB::query("SELECT \"ID\" FROM \"{$subclassTable}\"")->keyedColumn(); } foreach ($records as $record) { foreach ($subclasses as $subclass) { $subclassTable = $schema->tableName($subclass); $id = $record['ID']; if ($record['ClassName'] != $subclass && !is_subclass_of($record['ClassName'], $subclass) && isset($recordExists[$subclass][$id])) { $sql = "DELETE FROM \"{$subclassTable}\" WHERE \"ID\" = ?"; echo "<li>{$sql} [{$id}]</li>"; DB::prepared_query($sql, [$id]); } } } } } }
/** * Reset the testing database's schema. * @param bool $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('SilverStripe\\ORM\\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('SilverStripe\\ORM\\DataObject')->flushCache(); } }
/** * Remove an extension from a class. * * Keep in mind that this won't revert any datamodel additions * of the extension at runtime, unless its used before the * schema building kicks in (in your _config.php). * Doesn't remove the extension from any {@link Object} * instances which are already created, but will have an * effect on new extensions. * Clears any previously created singletons through {@link singleton()} * to avoid side-effects from stale extension information. * * @todo Add support for removing extensions with parameters * * @param string $extension class name of an {@link Extension} subclass, without parameters */ public static function remove_extension($extension) { $class = get_called_class(); Config::inst()->remove($class, 'extensions', Config::anything(), $extension); // remove any instances of the extension with parameters $config = Config::inst()->get($class, 'extensions'); if ($config) { foreach ($config as $k => $v) { // extensions with parameters will be stored in config as // ExtensionName("Param"). if (preg_match(sprintf("/^(%s)\\(/", preg_quote($extension, '/')), $v)) { Config::inst()->remove($class, 'extensions', Config::anything(), $v); } } } Config::inst()->extraConfigSourcesChanged($class); // unset singletons to avoid side-effects Injector::inst()->unregisterAllObjects(); // unset some caches $subclasses = ClassInfo::subclassesFor($class); $subclasses[] = $class; if ($subclasses) { foreach ($subclasses as $subclass) { unset(self::$classes_constructed[$subclass]); unset(self::$extra_methods[$subclass]); } } }
/** * Remove an item from this relation. * Doesn't actually remove the item, it just clears the foreign key value. * * @param DataObject $item The DataObject to be removed * @todo Maybe we should delete the object instead? */ public function remove($item) { if (!$item instanceof $this->dataClass) { throw new InvalidArgumentException("HasManyList::remove() expecting a {$this->dataClass} object, or ID", E_USER_ERROR); } // Don't remove item with unrelated class key $foreignClass = $this->getForeignClass(); $classNames = ClassInfo::subclassesFor($foreignClass); $classForeignKey = $this->classForeignKey; if (!in_array($item->{$classForeignKey}, $classNames)) { return; } // Don't remove item which doesn't belong to this list $foreignID = $this->getForeignID(); $foreignKey = $this->foreignKey; if (empty($foreignID) || is_array($foreignID) && in_array($item->{$foreignKey}, $foreignID) || $foreignID == $item->{$foreignKey}) { $item->{$foreignKey} = null; $item->{$classForeignKey} = null; $item->write(); } }
/** * Find a list of classes, each of which with a list of methods to invoke * to lookup owners. * * @return array */ protected function lookupReverseOwners() { // Find all classes with 'owns' config $lookup = array(); foreach (ClassInfo::subclassesFor('SilverStripe\\ORM\\DataObject') as $class) { // Ensure this class is versioned if (!Object::has_extension($class, static::class)) { continue; } // Check owned objects for this class $owns = Config::inst()->get($class, 'owns', Config::UNINHERITED); if (empty($owns)) { continue; } $instance = DataObject::singleton($class); foreach ($owns as $owned) { // Find owned class $ownedClass = $instance->getRelationClass($owned); // Skip custom methods that don't have db relationsm if (!$ownedClass) { continue; } if ($ownedClass === DataObject::class) { throw new LogicException(sprintf("Relation %s on class %s cannot be owned as it is polymorphic", $owned, $class)); } // Add lookup for owned class if (!isset($lookup[$ownedClass])) { $lookup[$ownedClass] = array(); } $lookup[$ownedClass][] = ['class' => $class, 'relation' => $owned]; } } return $lookup; }
/** * A utility funciton to retrieve subclasses of a given class that * are instantiable (ie, not abstract) and have a valid menu title. * * Sorted by url_priority config. * * @todo A variation of this function could probably be moved to {@link ClassInfo} * @param string $root The root class to begin finding subclasses * @param boolean $recursive Look for subclasses recursively? * @param string $sort Name of config on which to sort. Can be 'menu_priority' or 'url_priority' * @return array Valid, unique subclasses */ public static function get_cms_classes($root = null, $recursive = true, $sort = self::MENU_PRIORITY) { if (!$root) { $root = 'SilverStripe\\Admin\\LeftAndMain'; } /** @todo Make these actual abstract classes */ $abstractClasses = ['SilverStripe\\Admin\\LeftAndMain', 'SilverStripe\\CMS\\Controllers\\CMSMain']; $subClasses = array_values(ClassInfo::subclassesFor($root)); foreach ($subClasses as $className) { if ($recursive && $className != $root) { $subClasses = array_merge($subClasses, array_values(ClassInfo::subclassesFor($className))); } } $subClasses = array_unique($subClasses); foreach ($subClasses as $key => $className) { // Remove abstract classes and LeftAndMain if (in_array($className, $abstractClasses) || ClassInfo::classImplements($className, 'SilverStripe\\Dev\\TestOnly')) { unset($subClasses[$key]); } else { // Separate conditional to avoid autoloading the class $classReflection = new ReflectionClass($className); if (!$classReflection->isInstantiable()) { unset($subClasses[$key]); } } } // Sort by specified sorting config usort($subClasses, function ($a, $b) use($sort) { $priorityA = Config::inst()->get($a, $sort); $priorityB = Config::inst()->get($b, $sort); return $priorityB - $priorityA; }); return $subClasses; }
/** * Get list of classnames that should be selectable * * @return array */ public function getEnum() { $classNames = ClassInfo::subclassesFor($this->getBaseClass()); unset($classNames[DataObject::class]); return $classNames; }
public function testEveryFieldTransformsDisabledAsClone() { $fieldClasses = ClassInfo::subclassesFor('SilverStripe\\Forms\\FormField'); foreach ($fieldClasses as $fieldClass) { $reflectionClass = new ReflectionClass($fieldClass); if (!$reflectionClass->isInstantiable()) { continue; } $constructor = $reflectionClass->getMethod('__construct'); if ($constructor->getNumberOfRequiredParameters() > 1) { continue; } if (is_a($fieldClass, 'SilverStripe\\Forms\\CompositeField', true)) { continue; } $fieldName = $reflectionClass->getShortName() . '_instance'; if ($fieldClass = 'SilverStripe\\Forms\\NullableField') { $instance = new $fieldClass(new TextField($fieldName)); } else { $instance = new $fieldClass($fieldName); } $isDisabledBefore = $instance->isDisabled(); $disabledInstance = $instance->performDisabledTransformation(); $this->assertEquals($isDisabledBefore, $instance->isDisabled(), "FormField class {$fieldClass} retains its disabled state after calling performDisabledTransformation()"); $this->assertTrue($disabledInstance->isDisabled(), "FormField class {$fieldClass} returns a valid disabled representation as of isDisabled()"); $this->assertNotSame($disabledInstance, $instance, "FormField class {$fieldClass} returns a valid cloned disabled representation"); } }
/** * Cache all table names if necessary */ protected function cacheTableNames() { if ($this->tableNames) { return; } $this->tableNames = []; foreach (ClassInfo::subclassesFor(DataObject::class) as $class) { if ($class === DataObject::class) { continue; } $table = $this->buildTableName($class); // Check for conflicts $conflict = array_search($table, $this->tableNames, true); if ($conflict) { throw new LogicException("Multiple classes (\"{$class}\", \"{$conflict}\") map to the same table: \"{$table}\""); } $this->tableNames[$class] = $table; } }
public function testSubclassesFor() { $this->assertEquals(ClassInfo::subclassesFor('ClassInfoTest_BaseClass'), array('ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass', 'ClassInfoTest_ChildClass' => 'ClassInfoTest_ChildClass', 'ClassInfoTest_GrandChildClass' => 'ClassInfoTest_GrandChildClass'), 'ClassInfo::subclassesFor() returns only direct subclasses and doesnt include base class'); ClassInfo::reset_db_cache(); $this->assertEquals(ClassInfo::subclassesFor('classinfotest_baseclass'), array('ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass', 'ClassInfoTest_ChildClass' => 'ClassInfoTest_ChildClass', 'ClassInfoTest_GrandChildClass' => 'ClassInfoTest_GrandChildClass'), 'ClassInfo::subclassesFor() is acting in a case sensitive way when it should not'); }
/** * Ensure that the query is ready to execute. * * @param array|null $queriedColumns Any columns to filter the query by * @return SQLSelect The finalised sql query */ public function getFinalisedQuery($queriedColumns = null) { if (!$queriedColumns) { $queriedColumns = $this->queriedColumns; } if ($queriedColumns) { $queriedColumns = array_merge($queriedColumns, array('Created', 'LastEdited', 'ClassName')); } $query = clone $this->query; // Apply manipulators before finalising query foreach ($this->getDataQueryManipulators() as $manipulator) { $manipulator->beforeGetFinalisedQuery($this, $queriedColumns, $query); } $schema = DataObject::getSchema(); $baseDataClass = $schema->baseDataClass($this->dataClass()); $baseIDColumn = $schema->sqlColumnForField($baseDataClass, 'ID'); $ancestorClasses = ClassInfo::ancestry($this->dataClass(), true); // Generate the list of tables to iterate over and the list of columns required // by any existing where clauses. This second step is skipped if we're fetching // the whole dataobject as any required columns will get selected regardless. if ($queriedColumns) { // Specifying certain columns allows joining of child tables $tableClasses = ClassInfo::dataClassesFor($this->dataClass); // Ensure that any filtered columns are included in the selected columns foreach ($query->getWhereParameterised($parameters) as $where) { // Check for any columns in the form '"Column" = ?' or '"Table"."Column"' = ? if (preg_match_all('/(?:"(?<table>[^"]+)"\\.)?"(?<column>[^"]+)"(?:[^\\.]|$)/', $where, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $column = $match['column']; if (!in_array($column, $queriedColumns)) { $queriedColumns[] = $column; } } } } } else { $tableClasses = $ancestorClasses; } // Iterate over the tables and check what we need to select from them. If any selects are made (or the table is // required for a select) foreach ($tableClasses as $tableClass) { // Determine explicit columns to select $selectColumns = null; if ($queriedColumns) { // Restrict queried columns to that on the selected table $tableFields = $schema->databaseFields($tableClass, false); unset($tableFields['ID']); $selectColumns = array_intersect($queriedColumns, array_keys($tableFields)); } // If this is a subclass without any explicitly requested columns, omit this from the query if (!in_array($tableClass, $ancestorClasses) && empty($selectColumns)) { continue; } // Select necessary columns (unless an explicitly empty array) if ($selectColumns !== array()) { $this->selectColumnsFromTable($query, $tableClass, $selectColumns); } // Join if not the base table if ($tableClass !== $baseDataClass) { $tableName = $schema->tableName($tableClass); $query->addLeftJoin($tableName, "\"{$tableName}\".\"ID\" = {$baseIDColumn}", $tableName, 10); } } // Resolve colliding fields if ($this->collidingFields) { foreach ($this->collidingFields as $collisionField => $collisions) { $caseClauses = array(); foreach ($collisions as $collision) { if (preg_match('/^"(?<table>[^"]+)"\\./', $collision, $matches)) { $collisionTable = $matches['table']; $collisionClass = $schema->tableClass($collisionTable); if ($collisionClass) { $collisionClassColumn = $schema->sqlColumnForField($collisionClass, 'ClassName'); $collisionClasses = ClassInfo::subclassesFor($collisionClass); $collisionClassesSQL = implode(', ', Convert::raw2sql($collisionClasses, true)); $caseClauses[] = "WHEN {$collisionClassColumn} IN ({$collisionClassesSQL}) THEN {$collision}"; } } else { user_error("Bad collision item '{$collision}'", E_USER_WARNING); } } $query->selectField("CASE " . implode(" ", $caseClauses) . " ELSE NULL END", $collisionField); } } if ($this->filterByClassName) { // If querying the base class, don't bother filtering on class name if ($this->dataClass != $baseDataClass) { // Get the ClassName values to filter to $classNames = ClassInfo::subclassesFor($this->dataClass); $classNamesPlaceholders = DB::placeholders($classNames); $baseClassColumn = $schema->sqlColumnForField($baseDataClass, 'ClassName'); $query->addWhere(array("{$baseClassColumn} IN ({$classNamesPlaceholders})" => $classNames)); } } // Select ID $query->selectField($baseIDColumn, "ID"); // Select RecordClassName $baseClassColumn = $schema->sqlColumnForField($baseDataClass, 'ClassName'); $query->selectField("\n\t\t\tCASE WHEN {$baseClassColumn} IS NOT NULL THEN {$baseClassColumn}\n\t\t\tELSE " . Convert::raw2sql($baseDataClass, true) . " END", "RecordClassName"); // TODO: Versioned, Translatable, SiteTreeSubsites, etc, could probably be better implemented as subclasses // of DataQuery $obj = Injector::inst()->get($this->dataClass); $obj->extend('augmentSQL', $query, $this); $this->ensureSelectContainsOrderbyColumns($query); // Apply post-finalisation manipulations foreach ($this->getDataQueryManipulators() as $manipulator) { $manipulator->afterGetFinalisedQuery($this, $queriedColumns, $query); } return $query; }