public function transform(FormField $field)
 {
     // Look for a performXXTransformation() method on the field itself.
     // performReadonlyTransformation() is a pretty commonly applied method.
     // Otherwise, look for a transformXXXField() method on this object.
     // This is more commonly done in custom transformations
     // We iterate through each array simultaneously, looking at [0] of both, then [1] of both.
     // This provides a more natural failover scheme.
     $transNames = array_reverse(array_map(function ($name) {
         return ClassInfo::shortName($name);
     }, array_values(ClassInfo::ancestry($this->class))));
     $fieldClasses = array_reverse(array_map(function ($name) {
         return ClassInfo::shortName($name);
     }, array_values(ClassInfo::ancestry($field->class))));
     $len = max(sizeof($transNames), sizeof($fieldClasses));
     for ($i = 0; $i < $len; $i++) {
         // This is lets fieldClasses be longer than transNames
         if (!empty($transNames[$i])) {
             $funcName = 'perform' . $transNames[$i];
             if ($field->hasMethod($funcName)) {
                 //echo "<li>$field->class used $funcName";
                 return $field->{$funcName}($this);
             }
         }
         // And this one does the reverse.
         if (!empty($fieldClasses[$i])) {
             $funcName = 'transform' . $fieldClasses[$i];
             if ($this->hasMethod($funcName)) {
                 //echo "<li>$field->class used $funcName";
                 return $this->{$funcName}($field);
             }
         }
     }
     throw new \BadMethodCallException("FormTransformation:: Can't perform '{$this->class}' on '{$field->class}'");
 }
 /**
  * @covers SilverStripe\Core\ClassInfo::ancestry()
  */
 public function testAncestry()
 {
     $ancestry = ClassInfo::ancestry('ClassInfoTest_ChildClass');
     $expect = ArrayLib::valuekey(array('SilverStripe\\Core\\Object', 'SilverStripe\\View\\ViewableData', 'SilverStripe\\ORM\\DataObject', 'ClassInfoTest_BaseClass', 'ClassInfoTest_ChildClass'));
     $this->assertEquals($expect, $ancestry);
     ClassInfo::reset_db_cache();
     $this->assertEquals($expect, ClassInfo::ancestry('classINFOTest_Childclass'));
     ClassInfo::reset_db_cache();
     $this->assertEquals($expect, ClassInfo::ancestry('classINFOTest_Childclass'));
     ClassInfo::reset_db_cache();
     $ancestry = ClassInfo::ancestry('ClassInfoTest_ChildClass', true);
     $this->assertEquals(array('ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass'), $ancestry, '$tablesOnly option excludes memory-only inheritance classes');
 }
 /**
  * Traverses the given the given class context looking for candidate template names
  * which match each item in the class hierarchy. The resulting list of template candidates
  * may or may not exist, but you can invoke {@see SSViewer::chooseTemplate} on any list
  * to determine the best candidate based on the current themes.
  *
  * @param string|object $classOrObject Valid class name, or object
  * @param string $suffix
  * @param string $baseClass Class to halt ancestry search at
  * @return array
  */
 public static function get_templates_by_class($classOrObject, $suffix = '', $baseClass = null)
 {
     // Figure out the class name from the supplied context.
     if (!is_object($classOrObject) && !(is_string($classOrObject) && class_exists($classOrObject))) {
         throw new InvalidArgumentException('SSViewer::get_templates_by_class() expects a valid class name as its first parameter.');
     }
     $templates = array();
     $classes = array_reverse(ClassInfo::ancestry($classOrObject));
     foreach ($classes as $class) {
         $template = $class . $suffix;
         $templates[] = $template;
         $templates[] = ['type' => 'Includes', $template];
         // If the class is "Page_Controller", look for Page.ss
         if (stripos($class, '_controller') !== false) {
             $templates[] = str_ireplace('_controller', '', $class) . $suffix;
         }
         if ($baseClass && $class == $baseClass) {
             break;
         }
     }
     return $templates;
 }
 /**
  * Get any user defined searchable fields labels that
  * exist. Allows overriding of default field names in the form
  * interface actually presented to the user.
  *
  * The reason for keeping this separate from searchable_fields,
  * which would be a logical place for this functionality, is to
  * avoid bloating and complicating the configuration array. Currently
  * much of this system is based on sensible defaults, and this property
  * would generally only be set in the case of more complex relationships
  * between data object being required in the search interface.
  *
  * Generates labels based on name of the field itself, if no static property
  * {@link self::field_labels} exists.
  *
  * @uses $field_labels
  * @uses FormField::name_to_label()
  *
  * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
  *
  * @return array|string Array of all element labels if no argument given, otherwise the label of the field
  */
 public function fieldLabels($includerelations = true)
 {
     $cacheKey = $this->class . '_' . $includerelations;
     if (!isset(self::$_cache_field_labels[$cacheKey])) {
         $customLabels = $this->stat('field_labels');
         $autoLabels = array();
         // get all translated static properties as defined in i18nCollectStatics()
         $ancestry = ClassInfo::ancestry($this->class);
         $ancestry = array_reverse($ancestry);
         if ($ancestry) {
             foreach ($ancestry as $ancestorClass) {
                 if ($ancestorClass === ViewableData::class) {
                     break;
                 }
                 $types = array('db' => (array) Config::inst()->get($ancestorClass, 'db', Config::UNINHERITED));
                 if ($includerelations) {
                     $types['has_one'] = (array) Config::inst()->get($ancestorClass, 'has_one', Config::UNINHERITED);
                     $types['has_many'] = (array) Config::inst()->get($ancestorClass, 'has_many', Config::UNINHERITED);
                     $types['many_many'] = (array) Config::inst()->get($ancestorClass, 'many_many', Config::UNINHERITED);
                     $types['belongs_many_many'] = (array) Config::inst()->get($ancestorClass, 'belongs_many_many', Config::UNINHERITED);
                 }
                 foreach ($types as $type => $attrs) {
                     foreach ($attrs as $name => $spec) {
                         $autoLabels[$name] = _t("{$ancestorClass}.{$type}_{$name}", FormField::name_to_label($name));
                     }
                 }
             }
         }
         $labels = array_merge((array) $autoLabels, (array) $customLabels);
         $this->extend('updateFieldLabels', $labels);
         self::$_cache_field_labels[$cacheKey] = $labels;
     }
     return self::$_cache_field_labels[$cacheKey];
 }
 /**
  * Get part of the current classes ancestry to be used as a CSS class.
  *
  * This method returns an escaped string of CSS classes representing the current classes ancestry until it hits a
  * stop point - e.g. "Page DataObject ViewableData".
  *
  * @param string $stopAtClass the class to stop at (default: ViewableData)
  * @return string
  * @uses ClassInfo
  */
 public function CSSClasses($stopAtClass = 'SilverStripe\\View\\ViewableData')
 {
     $classes = array();
     $classAncestry = array_reverse(ClassInfo::ancestry($this->class));
     $stopClasses = ClassInfo::ancestry($stopAtClass);
     foreach ($classAncestry as $class) {
         if (in_array($class, $stopClasses)) {
             break;
         }
         $classes[] = $class;
     }
     // optionally add template identifier
     if (isset($this->template) && !in_array($this->template, $classes)) {
         $classes[] = $this->template;
     }
     // Strip out namespaces
     $classes = preg_replace('#.*\\\\#', '', $classes);
     return Convert::raw2att(implode(' ', $classes));
 }
 public static function get_extra_config_sources($class = null)
 {
     if ($class === null) {
         $class = get_called_class();
     }
     // If this class is unextendable, NOP
     if (in_array($class, self::$unextendable_classes)) {
         return null;
     }
     // Variable to hold sources in
     $sources = null;
     // Get a list of extensions
     $extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES);
     if (!$extensions) {
         return null;
     }
     // Build a list of all sources;
     $sources = array();
     foreach ($extensions as $extension) {
         list($extensionClass, $extensionArgs) = Object::parse_class_spec($extension);
         $sources[] = $extensionClass;
         if (!class_exists($extensionClass)) {
             throw new InvalidArgumentException("{$class} references nonexistent {$extensionClass} in \$extensions");
         }
         call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass, $extensionArgs);
         foreach (array_reverse(ClassInfo::ancestry($extensionClass)) as $extensionClassParent) {
             if (ClassInfo::has_method_from($extensionClassParent, 'get_extra_config', $extensionClassParent)) {
                 $extras = $extensionClassParent::get_extra_config($class, $extensionClass, $extensionArgs);
                 if ($extras) {
                     $sources[] = $extras;
                 }
             }
         }
     }
     return $sources;
 }
 /**
  * Ensures that the latest version of a record is the expected value
  *
  * @param DataObject $record
  * @param int $version
  */
 protected function assertRecordHasLatestVersion($record, $version)
 {
     foreach (ClassInfo::ancestry(get_class($record), true) as $table) {
         $versionForClass = DB::prepared_query($sql = "SELECT MAX(\"Version\") FROM \"{$table}_Versions\" WHERE \"RecordID\" = ?", array($record->ID))->value();
         $this->assertEquals($version, $versionForClass, "That the table {$table} has the latest version {$version}");
     }
 }
 /**
  * Tries to find the database key on another object that is used to store a
  * relationship to this class. If no join field can be found it defaults to 'ParentID'.
  *
  * If the remote field is polymorphic then $polymorphic is set to true, and the return value
  * is in the form 'Relation' instead of 'RelationID', referencing the composite DBField.
  *
  * @param string $class
  * @param string $component Name of the relation on the current object pointing to the
  * remote object.
  * @param string $type the join type - either 'has_many' or 'belongs_to'
  * @param boolean $polymorphic Flag set to true if the remote join field is polymorphic.
  * @return string
  * @throws Exception
  */
 public function getRemoteJoinField($class, $component, $type = 'has_many', &$polymorphic = false)
 {
     // Extract relation from current object
     if ($type === 'has_many') {
         $remoteClass = $this->hasManyComponent($class, $component, false);
     } else {
         $remoteClass = $this->belongsToComponent($class, $component, false);
     }
     if (empty($remoteClass)) {
         throw new Exception("Unknown {$type} component '{$component}' on class '{$class}'");
     }
     if (!ClassInfo::exists(strtok($remoteClass, '.'))) {
         throw new Exception("Class '{$remoteClass}' not found, but used in {$type} component '{$component}' on class '{$class}'");
     }
     // If presented with an explicit field name (using dot notation) then extract field name
     $remoteField = null;
     if (strpos($remoteClass, '.') !== false) {
         list($remoteClass, $remoteField) = explode('.', $remoteClass);
     }
     // Reference remote has_one to check against
     $remoteRelations = Config::inst()->get($remoteClass, 'has_one');
     // Without an explicit field name, attempt to match the first remote field
     // with the same type as the current class
     if (empty($remoteField)) {
         // look for remote has_one joins on this class or any parent classes
         $remoteRelationsMap = array_flip($remoteRelations);
         foreach (array_reverse(ClassInfo::ancestry($class)) as $class) {
             if (array_key_exists($class, $remoteRelationsMap)) {
                 $remoteField = $remoteRelationsMap[$class];
                 break;
             }
         }
     }
     // In case of an indeterminate remote field show an error
     if (empty($remoteField)) {
         $polymorphic = false;
         $message = "No has_one found on class '{$remoteClass}'";
         if ($type == 'has_many') {
             // include a hint for has_many that is missing a has_one
             $message .= ", the has_many relation from '{$class}' to '{$remoteClass}'";
             $message .= " requires a has_one on '{$remoteClass}'";
         }
         throw new Exception($message);
     }
     // If given an explicit field name ensure the related class specifies this
     if (empty($remoteRelations[$remoteField])) {
         throw new Exception("Missing expected has_one named '{$remoteField}'\n\t\t\t\ton class '{$remoteClass}' referenced by {$type} named '{$component}'\n\t\t\t\ton class {$class}");
     }
     // Inspect resulting found relation
     if ($remoteRelations[$remoteField] === DataObject::class) {
         $polymorphic = true;
         return $remoteField;
         // Composite polymorphic field does not include 'ID' suffix
     } else {
         $polymorphic = false;
         return $remoteField . 'ID';
     }
 }
 /**
  * Join table via many_many relationship
  *
  * @param string $relationClass
  * @param string $parentClass
  * @param string $componentClass
  * @param string $parentField
  * @param string $componentField
  * @param string $relationClassOrTable Name of relation table
  */
 protected function joinManyManyRelationship($relationClass, $parentClass, $componentClass, $parentField, $componentField, $relationClassOrTable)
 {
     $schema = DataObject::getSchema();
     if (class_exists($relationClassOrTable)) {
         $relationClassOrTable = $schema->tableName($relationClassOrTable);
     }
     // Join on parent table
     $parentIDColumn = $schema->sqlColumnForField($parentClass, 'ID');
     $this->query->addLeftJoin($relationClassOrTable, "\"{$relationClassOrTable}\".\"{$parentField}\" = {$parentIDColumn}");
     // Join on base table of component class
     $componentBaseClass = $schema->baseDataClass($componentClass);
     $componentBaseTable = $schema->tableName($componentBaseClass);
     $componentIDColumn = $schema->sqlColumnForField($componentBaseClass, 'ID');
     if (!$this->query->isJoinedTo($componentBaseTable)) {
         $this->query->addLeftJoin($componentBaseTable, "\"{$relationClassOrTable}\".\"{$componentField}\" = {$componentIDColumn}");
     }
     // Add join clause to the component's ancestry classes so that the search filter could search on
     // its ancestor fields.
     $ancestry = ClassInfo::ancestry($componentClass, true);
     $ancestry = array_reverse($ancestry);
     foreach ($ancestry as $ancestor) {
         $ancestorTable = $schema->tableName($ancestor);
         if ($ancestorTable != $componentBaseTable && !$this->query->isJoinedTo($ancestorTable)) {
             $this->query->addLeftJoin($ancestorTable, "{$componentIDColumn} = \"{$ancestorTable}\".\"ID\"");
         }
     }
 }