(merged from branches/roa. use "svn log -c <changeset> -g <module-svn-path>" for detailed commit message)

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60214 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-08-09 05:04:15 +00:00
parent 9515ef3fc1
commit 410fa9540f
2 changed files with 215 additions and 179 deletions

View File

@ -78,7 +78,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
else $passed = "The value '$record'"; else $passed = "The value '$record'";
user_error("DataObject::__construct passed $passed. It's supposed to be passed an array, user_error("DataObject::__construct passed $passed. It's supposed to be passed an array,
taken straight from the database. Perhaps you should use DataObject::get_one instead?", E_USER_WARNING); taken straight from the database. Perhaps you should use DataObject::get_one instead?", E_USER_WARNING);
$record = null; $record = null;
} }
@ -246,7 +246,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/ */
function i18n_singular_name() function i18n_singular_name()
{ {
$name = (!empty($this->add_action)) ? $this->add_action : $this->singular_name(); $name = (!empty($this->add_action)) ? $this->add_action : $this->singular_name();
return _t($this->class.'.SINGULARNAME', $name); return _t($this->class.'.SINGULARNAME', $name);
} }
@ -281,7 +281,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/ */
function i18n_plural_name() function i18n_plural_name()
{ {
$name = $this->plural_name(); $name = $this->plural_name();
return _t($this->class.'.PLURALNAME', $name); return _t($this->class.'.PLURALNAME', $name);
} }
@ -358,7 +358,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
if($leftObj->ClassName != $rightObj->ClassName) { if($leftObj->ClassName != $rightObj->ClassName) {
// we can't merge similiar subclasses because they might have additional relations // we can't merge similiar subclasses because they might have additional relations
user_error("DataObject->merge(): Invalid object class '{$rightObj->ClassName}' user_error("DataObject->merge(): Invalid object class '{$rightObj->ClassName}'
(expected '{$leftObj->ClassName}').", E_USER_WARNING); (expected '{$leftObj->ClassName}').", E_USER_WARNING);
return false; return false;
} }
@ -424,7 +424,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/ */
public function forceChange() { public function forceChange() {
foreach($this->record as $fieldName => $fieldVal) foreach($this->record as $fieldName => $fieldVal)
$this->changed[$fieldName] = 1; $this->changed[$fieldName] = 1;
} }
/** /**
@ -580,7 +580,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
if($this->changed) { if($this->changed) {
foreach($this->getClassAncestry() as $ancestor) { foreach($this->getClassAncestry() as $ancestor) {
if(ClassInfo::hasTable($ancestor)) if(ClassInfo::hasTable($ancestor))
$ancestry[] = $ancestor; $ancestry[] = $ancestor;
} }
// Look for some changes to make // Look for some changes to make
@ -833,7 +833,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* A cache used by component getting classes * A cache used by component getting classes
* @var array * @var array
*/ */
protected $componentCache; protected $componentCache;
/** /**
* Returns a one-to-many component, as a ComponentSet. * Returns a one-to-many component, as a ComponentSet.
@ -922,15 +922,15 @@ class DataObject extends ViewableData implements DataObjectInterface {
return $joinField; return $joinField;
} }
/** /**
* Sets the component of a relationship. * Sets the component of a relationship.
* *
* @param string $componentName Name of the component * @param string $componentName Name of the component
* @param DataObject|ComponentSet $componentValue Value of the component * @param DataObject|ComponentSet $componentValue Value of the component
*/ */
public function setComponent($componentName, $componentValue) { public function setComponent($componentName, $componentValue) {
$this->componentCache[$componentName] = $componentValue; $this->componentCache[$componentName] = $componentValue;
} }
/** /**
* Returns a many-to-many component, as a ComponentSet. * Returns a many-to-many component, as a ComponentSet.
@ -941,9 +941,9 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/ */
public function getManyManyComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") { public function getManyManyComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
$sum = md5("{$filter}_{$sort}_{$join}_{$limit}"); $sum = md5("{$filter}_{$sort}_{$join}_{$limit}");
if(isset($this->componentCache[$componentName . '_' . $sum]) && false != $this->componentCache[$componentName . '_' . $sum]) { if(isset($this->componentCache[$componentName . '_' . $sum]) && false != $this->componentCache[$componentName . '_' . $sum]) {
return $this->componentCache[$componentName . '_' . $sum]; return $this->componentCache[$componentName . '_' . $sum];
} }
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName); list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
@ -957,8 +957,8 @@ class DataObject extends ViewableData implements DataObjectInterface {
$query = $componentObj->extendedSQL( $query = $componentObj->extendedSQL(
"`$table`.$parentField = $this->ID", // filter "`$table`.$parentField = $this->ID", // filter
$sort, $sort,
$limit, $limit,
"INNER JOIN `$table` ON `$table`.$componentField = `$componentBaseClass`.ID" // join "INNER JOIN `$table` ON `$table`.$componentField = `$componentBaseClass`.ID" // join
); );
array_unshift($query->select, "`$table`.*"); array_unshift($query->select, "`$table`.*");
@ -1166,12 +1166,16 @@ class DataObject extends ViewableData implements DataObjectInterface {
* searchable, and map them to their default {@link FormField} * searchable, and map them to their default {@link FormField}
* representations. Used for scaffolding a searchform for {@link ModelAdmin}. * representations. Used for scaffolding a searchform for {@link ModelAdmin}.
* *
* Some additional logic is included for switching field labels, based on
* how generic or specific the field type is.
*
* @usedby {@link SearchContext} * @usedby {@link SearchContext}
* @return FieldSet * @return FieldSet
*/ */
public function scaffoldSearchFields() { public function scaffoldSearchFields() {
$fields = new FieldSet(); $fields = new FieldSet();
foreach($this->searchable_fields() as $fieldName => $fieldType) { foreach($this->searchable_fields() as $fieldName => $fieldType) {
if (is_int($fieldName)) $fieldName = $fieldType;
$field = $this->relObject($fieldName)->scaffoldSearchField(); $field = $this->relObject($fieldName)->scaffoldSearchField();
if (strstr($fieldName, '.')) { if (strstr($fieldName, '.')) {
$field->setName(str_replace('.', '__', $fieldName)); $field->setName(str_replace('.', '__', $fieldName));
@ -1207,6 +1211,8 @@ class DataObject extends ViewableData implements DataObjectInterface {
$fields->push(new HeaderField($this->singular_name())); $fields->push(new HeaderField($this->singular_name()));
foreach($this->db() as $fieldName => $fieldType) { foreach($this->db() as $fieldName => $fieldType) {
// @todo Pass localized title // @todo Pass localized title
// commented out, to be less of a pain in the ass
//$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField());
$fields->push($this->dbObject($fieldName)->scaffoldFormField()); $fields->push($this->dbObject($fieldName)->scaffoldFormField());
} }
foreach($this->has_one() as $relationship => $component) { foreach($this->has_one() as $relationship => $component) {
@ -1225,6 +1231,13 @@ class DataObject extends ViewableData implements DataObjectInterface {
protected function addScaffoldRelationFields($fieldSet) { protected function addScaffoldRelationFields($fieldSet) {
if($this->has_many()) { if($this->has_many()) {
// Refactor the fields that we have been given into a tab, "Main", in a tabset
$oldFields = $fieldSet;
$fieldSet = new FieldSet(
new TabSet("Root", new Tab("Main"))
);
foreach($oldFields as $field) $fieldSet->addFieldToTab("Root.Main", $field);
// Add each relation as a separate tab // Add each relation as a separate tab
foreach($this->has_many() as $relationship => $component) { foreach($this->has_many() as $relationship => $component) {
$relationshipFields = singleton($component)->summary_fields(); $relationshipFields = singleton($component)->summary_fields();
@ -1258,17 +1271,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return FieldSet * @return FieldSet
*/ */
public function getCMSFields() { public function getCMSFields() {
$fieldSet = new FieldSet(new TabSet("Root", new Tab("Main"))); $fields = $this->scaffoldFormFields();
$baseFields = $this->scaffoldFormFields();
foreach($baseFields as $field) $fieldSet->addFieldToTab("Root.Main", $field);
// If we don't have an ID, then relation fields don't work // If we don't have an ID, then relation fields don't work
if($this->ID) { if($this->ID) {
$fieldSet = $this->addScaffoldRelationFields($fieldSet); $fields = $this->addScaffoldRelationFields($fields);
} }
return $fields;
return $fieldSet;
} }
/** /**
@ -1370,7 +1378,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
$val->Name = $fieldName; $val->Name = $fieldName;
$this->record[$fieldName] = $val; $this->record[$fieldName] = $val;
// Situation 2: Passing a literal // Situation 2: Passing a literal
} else { } else {
$defaults = $this->stat('defaults'); $defaults = $this->stat('defaults');
// if a field is not existing or has strictly changed // if a field is not existing or has strictly changed
@ -1378,12 +1386,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
// TODO Add check for php-level defaults which are not set in the db // TODO Add check for php-level defaults which are not set in the db
// TODO Add check for hidden input-fields (readonly) which are not set in the db // TODO Add check for hidden input-fields (readonly) which are not set in the db
if( if(
// Only existing fields // Only existing fields
$this->fieldExists($fieldName) $this->fieldExists($fieldName)
// Catches "0"==NULL // Catches "0"==NULL
&& (isset($this->record[$fieldName]) && is_numeric($val) && (intval($val) != intval($this->record[$fieldName]))) && (isset($this->record[$fieldName]) && (intval($val) != intval($this->record[$fieldName])))
// Main non type-based check // Main non type-based check
&& (isset($this->record[$fieldName]) && $this->record[$fieldName] != $val) && (isset($this->record[$fieldName]) && $this->record[$fieldName] != $val)
) { ) {
// Non-strict check fails, so value really changed, e.g. "abc" != "cde" // Non-strict check fails, so value really changed, e.g. "abc" != "cde"
$this->changed[$fieldName] = 2; $this->changed[$fieldName] = 2;
@ -1482,7 +1490,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
$query = new SQLQuery( $query = new SQLQuery(
"`Page_Can$perm`.PageID", "`Page_Can$perm`.PageID",
array("`Page_Can$perm`"), array("`Page_Can$perm`"),
"GroupID IN ($groupList)"); "GroupID IN ($groupList)");
$permissionCache[$memberID][$perm] = $query->execute()->column(); $permissionCache[$memberID][$perm] = $query->execute()->column();
@ -1493,12 +1501,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
"LEFT JOIN `Page_CanView` ON `Page_CanView`.PageID = `SiteTree`.ID" "LEFT JOIN `Page_CanView` ON `Page_CanView`.PageID = `SiteTree`.ID"
), "`Page_CanView`.PageID IS NULL"); ), "`Page_CanView`.PageID IS NULL");
$unsecuredPages = $query->execute()->column(); $unsecuredPages = $query->execute()->column();
if($permissionCache[$memberID][$perm]) { if($permissionCache[$memberID][$perm]) {
$permissionCache[$memberID][$perm] = array_merge($permissionCache[$memberID][$perm], $unsecuredPages); $permissionCache[$memberID][$perm] = array_merge($permissionCache[$memberID][$perm], $unsecuredPages);
} else { } else {
$permissionCache[$memberID][$perm] = $unsecuredPages; $permissionCache[$memberID][$perm] = $unsecuredPages;
} }
} }
$this->set_uninherited('permissionCache', $permissionCache); $this->set_uninherited('permissionCache', $permissionCache);
@ -1537,7 +1545,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* *
* @return string The field type of the given field * @return string The field type of the given field
*/ */
public function fieldExists($field) { public function fieldExists($field) {
if($field == "ID") return "Int"; if($field == "ID") return "Int";
if($field == "ClassName" && get_parent_class($this) == "DataObject") return "Enum"; if($field == "ClassName" && get_parent_class($this) == "DataObject") return "Enum";
if($field == "LastEdited" && get_parent_class($this) == "DataObject") return "Datetime"; if($field == "LastEdited" && get_parent_class($this) == "DataObject") return "Datetime";
@ -1568,34 +1576,52 @@ class DataObject extends ViewableData implements DataObjectInterface {
public function dbObject($fieldName) { public function dbObject($fieldName) {
return $this->obj($fieldName); return $this->obj($fieldName);
/* /*
$helperPair = $this->castingHelperPair($fieldName); $helperPair = $this->castingHelperPair($fieldName);
$constructor = $helperPair['castingHelper']; $constructor = $helperPair['castingHelper'];
if($obj = eval($constructor)) { if($obj = eval($constructor)) {
$obj->setValue($this->$fieldName, $this->record); $obj->setValue($this->$fieldName, $this->record);
} }
return $obj; return $obj;
*/ */
} }
/** /**
* Traverses to a DBField referenced by a relationship. * Traverses to a DBField referenced by relationships between data objects.
* The path to the related field is specified with dot separated syntax (eg: Parent.Child.Child.FieldName)
* *
* @param $fieldPath string
* @return DBField * @return DBField
*/ */
public function relObject($fieldPath) { public function relObject($fieldPath) {
//Debug::message("relObject:$fieldPath");
$parts = explode('.', $fieldPath); $parts = explode('.', $fieldPath);
//Debug::dump($parts);
$fieldName = array_pop($parts); $fieldName = array_pop($parts);
$component = $this; $component = $this;
foreach($parts as $relation) { foreach($parts as $relation) {
if ($rel = $component->has_one($relation)) { if ($rel = $component->has_one($relation)) {
$component = singleton($rel); $component = singleton($rel);
} elseif ($rel = $component->has_many($relation)) { } elseif ($rel = $component->has_many($relation)) {
$component = singleton($rel); $component = singleton($rel);
//Debug::dump($component);
} elseif ($rel = $component->many_many($relation)) {
//Debug::dump($rel);
$component = singleton($rel[1]);
//Debug::dump($component);
} }
} }
return $component->dbObject($fieldName); $object = $component->dbObject($fieldName);
//Debug::message("$component->class.$fieldName");
//Debug::show($component);
//Debug::message("$component->class.$fieldName == $object->class");
if (!($object instanceof DBField)) {
user_error("Unable to traverse to related object field [$fieldPath]", E_USER_ERROR);
//Debug::dump($object);
}
return $object;
} }
/** /**
@ -1771,7 +1797,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
$fields = func_get_args(); $fields = func_get_args();
$result = "<ul>\n"; $result = "<ul>\n";
foreach($fields as $field) foreach($fields as $field)
$result .= "<li><b>$field:</b> " . $this->$field . "</li>\n"; $result .= "<li><b>$field:</b> " . $this->$field . "</li>\n";
$result .= "</ul>"; $result .= "</ul>";
return $result; return $result;
} }
@ -1840,11 +1866,11 @@ class DataObject extends ViewableData implements DataObjectInterface {
} }
} }
/** /**
* A cache used by get_one. * A cache used by get_one.
* @var array * @var array
*/ */
protected static $cache_get_one; protected static $cache_get_one;
/** /**
* Return the first item matching the given query. * Return the first item matching the given query.
@ -1952,7 +1978,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
$baseClass = array_shift($tableClasses); $baseClass = array_shift($tableClasses);
return DataObject::get_one($callerClass,"`$baseClass`.`ID` = $id"); return DataObject::get_one($callerClass,"`$baseClass`.`ID` = $id");
// This simpler code will be used by non-DataObject classes that implement DataObjectInterface // This simpler code will be used by non-DataObject classes that implement DataObjectInterface
} else { } else {
return DataObject::get_one($callerClass,"`ID` = $id"); return DataObject::get_one($callerClass,"`ID` = $id");
} }
@ -1976,7 +2002,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* This array is indexed by the name of the field with the index, and * This array is indexed by the name of the field with the index, and
* the value is the type of index. * the value is the type of index.
*/ */
public function databaseIndexes() { public function databaseIndexes() {
$has_one = $this->uninherited('has_one',true); $has_one = $this->uninherited('has_one',true);
$classIndexes = $this->uninherited('indexes',true); $classIndexes = $this->uninherited('indexes',true);
//$fileIndexes = $this->uninherited('fileIndexes', true); //$fileIndexes = $this->uninherited('fileIndexes', true);
@ -2008,7 +2034,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
public function requireTable() { public function requireTable() {
// Only build the table if we've actually got fields // Only build the table if we've actually got fields
$fields = $this->databaseFields(); $fields = $this->databaseFields();
$indexes = $this->databaseIndexes(); $indexes = $this->databaseIndexes();
if($fields) { if($fields) {
DB::requireTable($this->class, $fields, $indexes); DB::requireTable($this->class, $fields, $indexes);
@ -2023,7 +2049,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
// Build field list // Build field list
$manymanyFields = array( $manymanyFields = array(
"{$this->class}ID" => "Int", "{$this->class}ID" => "Int",
(($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => "Int", (($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => "Int",
); );
if($extras[$relationship]) { if($extras[$relationship]) {
$manymanyFields = array_merge($manymanyFields, $extras[$relationship]); $manymanyFields = array_merge($manymanyFields, $extras[$relationship]);
@ -2032,7 +2058,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
// Build index list // Build index list
$manymanyIndexes = array( $manymanyIndexes = array(
"{$this->class}ID" => true, "{$this->class}ID" => true,
(($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => true, (($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => true,
); );
DB::requireTable("{$this->class}_$relationship", $manymanyFields, $manymanyIndexes); DB::requireTable("{$this->class}_$relationship", $manymanyFields, $manymanyIndexes);
@ -2082,15 +2108,15 @@ class DataObject extends ViewableData implements DataObjectInterface {
if($this->parentClass() == 'DataObject') { if($this->parentClass() == 'DataObject') {
$childClasses = ClassInfo::subclassesFor($this->class); $childClasses = ClassInfo::subclassesFor($this->class);
return array_merge( return array_merge(
array( array(
"ClassName" => "Enum('" . implode(", ", $childClasses) . "')", "ClassName" => "Enum('" . implode(", ", $childClasses) . "')",
"Created" => "Datetime", "Created" => "Datetime",
"LastEdited" => "Datetime", "LastEdited" => "Datetime",
), ),
(array)$this->customDatabaseFields() (array)$this->customDatabaseFields()
); );
// Child table // Child table
} else { } else {
return $this->customDatabaseFields(); return $this->customDatabaseFields();
} }
@ -2194,8 +2220,8 @@ class DataObject extends ViewableData implements DataObjectInterface {
$filters[$type] = $this->relObject($type)->defaultSearchFilter(); $filters[$type] = $this->relObject($type)->defaultSearchFilter();
} else { } else {
if (is_array($type)) { if (is_array($type)) {
$filter = current($type); $filter = current($type);
$filters[$name] = new $filter($name); $filters[$name] = new $filter($name);
} else { } else {
if (is_subclass_of($type, 'SearchFilter')) { if (is_subclass_of($type, 'SearchFilter')) {
$filters[$name] = new $type($name); $filters[$name] = new $type($name);
@ -2222,12 +2248,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
} }
} }
/** /**
* @return boolean True if the object is in the database * @return boolean True if the object is in the database
*/ */
public function isInDB() { public function isInDB() {
return is_numeric( $this->ID ) && $this->ID > 0; return is_numeric( $this->ID ) && $this->ID > 0;
} }
/** /**
* Sets a 'context object' that can be used to provide hints about how to process a particular get / get_one request. * Sets a 'context object' that can be used to provide hints about how to process a particular get / get_one request.

View File

@ -13,7 +13,7 @@
class Year extends DBField { class Year extends DBField {
function requireField() { function requireField() {
DB::requireField($this->tableName, $this->name, "year"); DB::requireField($this->tableName, $this->name, "year(4)");
} }
public function scaffoldFormField($title = null) { public function scaffoldFormField($title = null) {
@ -22,9 +22,19 @@ class Year extends DBField {
return $selectBox; return $selectBox;
} }
private function getDefaultOptions() { /**
$start = (int)date('Y'); * Returns a list of default options that can
$end = 1900; * be used to populate a select box, or compare against
* input values. Starts by default at the current year,
* and counts back to 1900.
*
* @param int $start starting date to count down from
* @param int $end end date to count down to
* @return array
*/
private function getDefaultOptions($start=false, $end=false) {
if (!$start) $start = (int)date('Y');
if (!$end) $end = 1900;
$years = array(); $years = array();
for($i=$start;$i>=$end;$i--) { for($i=$start;$i>=$end;$i--) {
$years[] = $i; $years[] = $i;