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\""); } } }