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;
 }