From 95d4634e2e5c1bd0ec812527b97d3074d6eba37b Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sun, 10 Aug 2008 23:17:51 +0000 Subject: [PATCH] (merged from branches/roa. use "svn log -c -g " for detailed commit message) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60264 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- Makefile | 2 +- core/model/DataObject.php | 171 ++++++++++-------- core/model/YamlFixture.php | 169 +++++++++++++++++ core/model/fieldtypes/DBField.php | 10 +- core/model/fieldtypes/PrimaryKey.php | 30 +++ dev/SapphireTest.php | 71 ++------ forms/FormField.php | 23 +++ forms/TableListField.php | 54 ++++-- javascript/TableListField.js | 2 +- .../Includes/TableListField_PageControls.ss | 4 +- tests/DataObjectTest.php | 14 +- tests/SiteTreeTest.php | 4 +- 12 files changed, 387 insertions(+), 167 deletions(-) create mode 100644 core/model/YamlFixture.php create mode 100644 core/model/fieldtypes/PrimaryKey.php diff --git a/Makefile b/Makefile index 72446f96b..04b988cd7 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ URL=`./cli-script.php SapphireInfo/baseurl` test: phpunit phpunit: - php5 ./cli-script.php TestRunner flush=1 + php5 ./cli-script.php dev/tests/all flush=1 windmill: functest ../cms/tests/test_windmill url=${URL}admin browser=firefox diff --git a/core/model/DataObject.php b/core/model/DataObject.php index a45707cfa..65e09491a 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -376,7 +376,7 @@ class DataObject extends ViewableData implements DataObjectInterface { foreach($rightData as $key=>$rightVal) { // don't merge conflicting values if priority is 'left' if($priority == 'left' && $leftObj->{$key} !== $rightObj->{$key}) continue; - + // don't overwrite existing left values with empty right values (if $overwriteWithEmpty is set) if($priority == 'right' && !$overwriteWithEmpty && empty($rightObj->{$key})) continue; @@ -623,7 +623,12 @@ class DataObject extends ViewableData implements DataObjectInterface { // (used mainly for has_one/has_many) if(!$fieldObj) $fieldObj = DBField::create('Varchar', $this->record[$fieldName], $fieldName); - $fieldObj->setValue($this->record[$fieldName], $this->record); + // CompositeDBFields handle their own value storage; regular fields need to be + // re-populated from the database + if(!$fieldObj instanceof CompositeDBField) { + $fieldObj->setValue($this->record[$fieldName], $this->record); + } + $fieldObj->writeToManipulation($manipulation[$class]); } } @@ -829,7 +834,7 @@ class DataObject extends ViewableData implements DataObjectInterface { user_error("DataObject::getComponent(): Unknown 1-to-1 component '$componentName' on class '$this->class'", E_USER_ERROR); } } - + /** * A cache used by component getting classes * @var array @@ -878,10 +883,10 @@ class DataObject extends ViewableData implements DataObjectInterface { return $result; } - + /** * Get the query object for a $has_many Component. - * + * * Use {@link DataObjectSet->setComponentInfo()} to attach metadata to the * resultset you're building with this query. * Use {@link DataObject->buildDataObjectSet()} to build a set out of the {@link SQLQuery} @@ -900,7 +905,7 @@ class DataObject extends ViewableData implements DataObjectInterface { } $joinField = $this->getComponentJoinField($componentName); - + $id = $this->getField("ID"); // get filter @@ -977,9 +982,9 @@ class DataObject extends ViewableData implements DataObjectInterface { // Join expression is done on SiteTree.ID even if we link to Page; it helps work around // database inconsistencies $componentBaseClass = ClassInfo::baseDataClass($componentClass); - + if($this->ID && is_numeric($this->ID)) { - + if($componentClass) { $query = $this->getManyManyComponentsQuery($componentName, $filter, $sort, $join, $limit); @@ -1003,7 +1008,7 @@ class DataObject extends ViewableData implements DataObjectInterface { return $result; } - + /** * Get the query object for a $many_many Component. * Use {@link DataObjectSet->setComponentInfo()} to attach metadata to the @@ -1020,7 +1025,7 @@ class DataObject extends ViewableData implements DataObjectInterface { */ public function getManyManyComponentsQuery($componentName, $filter = "", $sort = "", $join = "", $limit = "") { list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName); - + $componentObj = singleton($componentClass); // Join expression is done on SiteTree.ID even if we link to Page; it helps work around @@ -1037,7 +1042,7 @@ class DataObject extends ViewableData implements DataObjectInterface { if($filter) $query->where[] = $filter; if($join) $query->from[] = $join; - + return $query; } @@ -1065,23 +1070,23 @@ class DataObject extends ViewableData implements DataObjectInterface { if ($this->has_many() || $this->many_many()) { $oldFields = $fieldSet; $fieldSet = new FieldSet( - new TabSet("Root", new Tab("Main")) + new TabSet("Root", new Tab("Main")) ); foreach($oldFields as $field) { $fieldSet->addFieldToTab("Root.Main", $field); } } - + if($this->has_many()) { // Add each relation as a separate tab foreach($this->has_many() as $relationship => $component) { $relationshipFields = singleton($component)->summaryFields(); $foreignKey = $this->getComponentJoinField($relationship); $ctf = new ComplexTableField( - $this, - $relationship, - $component, - $relationshipFields, + $this, + $relationship, + $component, + $relationshipFields, "getCMSFields", "$foreignKey = $this->ID" ); @@ -1089,18 +1094,18 @@ class DataObject extends ViewableData implements DataObjectInterface { $fieldSet->addFieldToTab("Root.$relationship", $ctf); } } - if ($this->many_many()) { + if ($this->many_many()) { foreach($this->many_many() as $relationship => $component) { $relationshipFields = singleton($component)->summaryFields(); $filterWhere = $this->getManyManyFilter($relationship, $component); $filterJoin = $this->getManyManyJoin($relationship, $component); $ctf = new ComplexTableField( - $this, - $relationship, - $component, - $relationshipFields, + $this, + $relationship, + $component, + $relationshipFields, "getCMSFields", - $filterWhere, + $filterWhere, '', $filterJoin ); @@ -1126,17 +1131,17 @@ class DataObject extends ViewableData implements DataObjectInterface { $classes = array_reverse(ClassInfo::ancestry($this->class)); list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName); - + if($baseTable == $parentClass) { return "LEFT JOIN `$table` ON (`$parentField` = `$parentClass`.`ID` AND `$componentField` = '{$this->ID}')"; } else { return "LEFT JOIN `$table` ON (`$componentField` = `$componentClass`.`ID` AND `$parentField` = '{$this->ID}')"; } } - + function getManyManyFilter($componentName, $baseTable) { list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName); - + return "`$table`.`$parentField` = '{$this->ID}'"; } @@ -1294,14 +1299,14 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return SearchContext */ public function getDefaultSearchContext() { - return new SearchContext($this->class, $this->searchable_fields(), $this->defaultSearchFilters()); + return new SearchContext($this->class, new FieldSet($this->searchable_fields()), $this->defaultSearchFilters()); } /** * Determine which properties on the DataObject are * searchable, and map them to their default {@link FormField} * 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. * @@ -1349,7 +1354,7 @@ class DataObject extends ViewableData implements DataObjectInterface { $fieldClass = $fieldClasses[$fieldName]; $fieldObject = new $fieldClass($fieldName); } else { - $fieldObject = $this->dbObject($fieldName)->scaffoldFormField(); + $fieldObject = $this->dbObject($fieldName)->scaffoldFormField(); } $fields->push($fieldObject); } @@ -1434,7 +1439,7 @@ class DataObject extends ViewableData implements DataObjectInterface { $fieldObj = eval($constructor); if(isset($this->record[$field])) $fieldObj->setValue($this->record[$field], $this->record); $this->record[$field] = $fieldObj; - + return $this->record[$field]; } @@ -1492,7 +1497,7 @@ class DataObject extends ViewableData implements DataObjectInterface { if($val instanceof DBField) { $val->Name = $fieldName; $this->record[$fieldName] = $val; - + // Situation 2: Passing a literal } else { $defaults = $this->stat('defaults'); @@ -1660,17 +1665,17 @@ class DataObject extends ViewableData implements DataObjectInterface { public function canDelete($member = null) { return Permission::check('ADMIN'); } - + /** * @todo Should canCreate be a static method? - * + * * @param Member $member * @return boolean */ public function canCreate($member = null) { return Permission::check('ADMIN'); } - + /** * Debugging used by Debug::show() * @@ -1723,7 +1728,11 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return DBField The field as a DBField object */ public function dbObject($fieldName) { - return $this->obj($fieldName); + if($fieldName == 'ID') { + return new PrimaryKey($fieldName, $this); + } else { + return $this->obj($fieldName); + } /* $helperPair = $this->castingHelperPair($fieldName); $constructor = $helperPair['castingHelper']; @@ -1744,9 +1753,7 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return DBField */ public function relObject($fieldPath) { - //Debug::message("relObject:$fieldPath"); $parts = explode('.', $fieldPath); - //Debug::dump($parts); $fieldName = array_pop($parts); $component = $this; foreach($parts as $relation) { @@ -1754,30 +1761,30 @@ class DataObject extends ViewableData implements DataObjectInterface { $component = singleton($rel); } elseif ($rel = $component->has_many($relation)) { $component = singleton($rel); - //Debug::dump($component); } elseif ($rel = $component->many_many($relation)) { - //Debug::dump($rel); $component = singleton($rel[1]); - //Debug::dump($component); } } + $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; } /** - * Temporary hack to return an association name, based on class, toget around the mangle + * Temporary hack to return an association name, based on class, to get around the mangle * of having to deal with reverse lookup of relationships to determine autogenerated foreign keys. + * + * @return String */ - public function getReverseAssociation($className) { + public function getReverseAssociation($className) { + if (is_array($this->many_many())) { + $many_many = array_flip($this->many_many()); + if (array_key_exists($className, $many_many)) return $many_many[$className]; + } if (is_array($this->has_many())) { $has_many = array_flip($this->has_many()); if (array_key_exists($className, $has_many)) return $has_many[$className]; @@ -1786,6 +1793,8 @@ class DataObject extends ViewableData implements DataObjectInterface { $has_one = array_flip($this->has_one()); if (array_key_exists($className, $has_one)) return $has_one[$className]; } + + return false; } /** @@ -1821,7 +1830,7 @@ class DataObject extends ViewableData implements DataObjectInterface { $query->where($filter); $query->orderby($sort); $query->limit($limit); - + // Add SQL for multi-value fields on the base table $databaseFields = $this->databaseFields(); if($databaseFields) foreach($databaseFields as $k => $v) { @@ -2284,6 +2293,13 @@ class DataObject extends ViewableData implements DataObjectInterface { */ public function searchable_fields() { $fields = $this->stat('searchable_fields'); + + // if fields were passed in numeric array, + // convert to an associative array + if($fields && array_key_exists(0, $fields)) { + $fields = array_fill_keys(array_values($fields), 'TextField'); + } + if (!$fields) { $fields = array_fill_keys(array_keys($this->summaryFields()), 'TextField'); } else { @@ -2297,54 +2313,49 @@ class DataObject extends ViewableData implements DataObjectInterface { } return $fields; } - + /** * 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 + * 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 exists. - * + * + * Generates labels based on name of the field itself, if no static property + * {@link self::searchable_fields_labels} exists. + * * @todo fix bad code - * + * * @param $fieldName name of the field to retrieve * @return array of all element labels if no argument given * @return string of label if field */ public function searchable_fields_labels($fieldName=false) { - $labels = $this->stat('searchable_fields_labels'); - if (is_array($labels)) { - if ($fieldName) { - if (isset($labels[$fieldName])) { - return $labels[$fieldName]; - } + $custom_labels = $this->stat('searchable_fields_labels'); + + $fields = array_keys($this->searchable_fields()); + $labels = array_combine($fields, $fields); + if(is_array($custom_labels)) $labels = array_merge($labels, $custom_labels); + if ($fieldName) { + if(array_key_exists($fieldName, $labels)) { + return $labels[$fieldName]; + } elseif (strstr($fieldName, '.')) { + $parts = explode('.', $fieldName); + $label = $parts[count($parts)-2] . ' ' . $parts[count($parts)-1]; + return $this->toLabel($label); } else { - return $labels; + return $this->toLabel($fieldName); } } else { - $fields = array_keys($this->searchable_fields()); - $labels = array_combine($fields, $fields); - if ($fieldName) { - if (strstr($fieldName, '.')) { - $parts = explode('.', $fieldName); - $label = $parts[count($parts)-2] . ' ' . $parts[count($parts)-1]; - return $this->toLabel($label); - } else { - return $this->toLabel($fieldName); - } - } else { - return $labels; - } + return $labels; } } - + /** * Get the default summary fields for this object. * @@ -2354,13 +2365,13 @@ class DataObject extends ViewableData implements DataObjectInterface { */ public function summaryFields() { $fields = $this->stat('summary_fields'); - + // if fields were passed in numeric array, // convert to an associative array if($fields && array_key_exists(0, $fields)) { $fields = array_combine(array_values($fields), array_values($fields)); } - + if (!$fields) { $fields = array(); // try to scaffold a couple of usual suspects @@ -2397,11 +2408,11 @@ class DataObject extends ViewableData implements DataObjectInterface { if (is_int($name)) { $filters[$type] = $this->relObject($type)->defaultSearchFilter(); } else { - if (is_array($type)) { + if(is_array($type)) { $filter = current($type); $filters[$name] = new $filter($name); } else { - if (is_subclass_of($type, 'SearchFilter')) { + if(is_subclass_of($type, 'SearchFilter')) { $filters[$name] = new $type($name); } else { $filters[$name] = $this->relObject($name)->defaultSearchFilter($name); @@ -2596,7 +2607,7 @@ class DataObject extends ViewableData implements DataObjectInterface { * default display in the search form. */ public static $searchable_fields_labels = null; - + /** * Provides a default list of fields to be used by a 'summary' * view of this object. diff --git a/core/model/YamlFixture.php b/core/model/YamlFixture.php new file mode 100644 index 000000000..fea20b7af --- /dev/null +++ b/core/model/YamlFixture.php @@ -0,0 +1,169 @@ +" + * + * There are a couple of lines like this: + * @example Parent: =>Page.about + * This will tell the system to set the ParentID database field to the ID of the Page object with the identifier ŇaboutÓ. + * This can be used on any has-one or many-many relationship. + * Note that we use the name of the relationship (Parent), and not the name of the database field (ParentID) + * + * On many-many relationships, you should specify a comma separated list of values. + * @example MyRelation: =>Class.inst1,=>Class.inst2,=>Class.inst3 + * An crucial thing to note is that the YAML file specifies DataObjects, not database records. + * The database is populated by instantiating DataObject objects, setting the fields listed, and calling write(). + * This means that any onBeforeWrite() or default value logic will be executed as part of the test. + * This forms the basis of our testURLGeneration() test above. + * + * For example, the URLSegment value of Page.staffduplicate is the same as the URLSegment value of Page.staff. + * When the fixture is set up, the URLSegment value of Page.staffduplicate will actually be my-staff-2. + * + * Finally, be aware that requireDefaultRecords() is not called by the database populator - + * so you will need to specify standard pages such as 404 and home in your YAML file. + * + * + * Page: + * home: + * Title: Home + * about: + * Title: About Us + * staff: + * Title: Staff + * URLSegment: my-staff + * Parent: =>Page.about + * staffduplicate: + * Title: Staff + * URLSegment: my-staff + * Parent: =>Page.about + * products: + * Title: Products + * ErrorPage: + * 404: + * Title: Page not Found + * ErrorCode: 404 + * + * + * @package sapphire + * @subpackage core + * + * @see http://spyc.sourceforge.net/ + * + * @todo Write unit test for YamlFixture + * + * @param $fixtureFile The location of the .yml fixture file, relative to the site base dir + */ +class YamlFixture extends Object { + + /** + * The location of the .yml fixture file, relative to the site base dir + * + * @var string + */ + protected $fixtureFile; + + /** + * Array of fixture items + * + * @var array + */ + protected $fixtureDictionary; + + function __construct($fixtureFile) { + $this->fixtureFile = $fixtureFile; + } + + /** + * Get the ID of an object from the fixture. + * @param $className The data class, as specified in your fixture file. Parent classes won't work + * @param $identifier The identifier string, as provided in your fixture file + */ + protected function idFromFixture($className, $identifier) { + return $this->fixtureDictionary[$className][$identifier]; + } + + /** + * Return all of the IDs in the fixture of a particular class name. + * + * @return A map of fixture-identifier => object-id + */ + protected function allFixtureIDs($className) { + return $this->fixtureDictionary[$className]; + } + + /** + * Get an object from the fixture. + * + * @param $className The data class, as specified in your fixture file. Parent classes won't work + * @param $identifier The identifier string, as provided in your fixture file + */ + protected function objFromFixture($className, $identifier) { + return DataObject::get_by_id($className, $this->idFromFixture($className, $identifier)); + } + + /** + * Load a YAML fixture file into the database. + * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture + */ + function saveIntoDatabase() { + $parser = new Spyc(); + $fixtureContent = $parser->load(Director::baseFolder().'/'.$this->fixtureFile); + + $this->fixtureDictionary = array(); + + foreach($fixtureContent as $dataClass => $items) { + foreach($items as $identifier => $fields) { + $obj = new $dataClass(); + foreach($fields as $fieldName => $fieldVal) { + if($obj->many_many($fieldName) || $obj->has_many($fieldName)) { + $parsedItems = array(); + $items = split(' *, *',trim($fieldVal)); + foreach($items as $item) { + $parsedItems[] = $this->parseFixtureVal($item); + } + $obj->write(); + if($obj->has_many($fieldName)) { + $obj->getComponents($fieldName)->setByIDList($parsedItems); + } elseif($obj->many_many($fieldName)) { + $obj->getManyManyComponents($fieldName)->setByIDList($parsedItems); + } + } elseif($obj->has_one($fieldName)) { + $obj->{$fieldName . 'ID'} = $this->parseFixtureVal($fieldVal); + } else { + $obj->$fieldName = $this->parseFixtureVal($fieldVal); + } + } + $obj->write(); + + // Populate the dictionary with the ID + $this->fixtureDictionary[$dataClass][$identifier] = $obj->ID; + } + } + } + + /** + * Parse a value from a fixture file. If it starts with => it will get an ID from the fixture dictionary + */ + protected function parseFixtureVal($fieldVal) { + // Parse a dictionary reference - used to set foreign keys + if(substr($fieldVal,0,2) == '=>') { + list($a, $b) = explode('.', substr($fieldVal,2), 2); + return $this->fixtureDictionary[$a][$b]; + + // Regular field value setting + } else { + return $fieldVal; + } + } +} +?> \ No newline at end of file diff --git a/core/model/fieldtypes/DBField.php b/core/model/fieldtypes/DBField.php index 8d7860e45..03433454a 100644 --- a/core/model/fieldtypes/DBField.php +++ b/core/model/fieldtypes/DBField.php @@ -21,6 +21,13 @@ abstract class DBField extends ViewableData { protected $name; + /** + * Subclass of {@link SearchFilter} for usage in {@link defaultSearchFilter()}. + * + * @var string + */ + protected static $default_search_filter_class = 'PartialMatchFilter'; + /** * @var $default mixed Default-value in the database. * Might be overridden on DataObject-level, but still useful for setting defaults on @@ -223,7 +230,8 @@ abstract class DBField extends ViewableData { */ public function defaultSearchFilter($name = false) { $name = ($name) ? $name : $this->name; - return new PartialMatchFilter($name); + $filterClass = $this->stat('default_search_filter_class'); + return new $filterClass($name); } /** diff --git a/core/model/fieldtypes/PrimaryKey.php b/core/model/fieldtypes/PrimaryKey.php new file mode 100644 index 000000000..c626503bb --- /dev/null +++ b/core/model/fieldtypes/PrimaryKey.php @@ -0,0 +1,30 @@ +object = $object; + parent::__construct($name); + } + + public function scaffoldFormField($title = null) { + $objs = DataObject::get($this->object->class); + $map = ($objs) ? $objs->toDropdownMap() : false; + + return new DropdownField($this->name, $title, $map, null, null, ' '); + } +} + +?> \ No newline at end of file diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index 108d510cc..b32ad4cae 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -1,10 +1,4 @@ doBuild(true, false, true); - - // Load the fixture into the database - $className = get_class($this); - $this->loadFixture($fixtureFile); + + $this->fixture = new YamlFixture($fixtureFile); + $this->fixture->saveIntoDatabase(); } // Set up email @@ -100,51 +102,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase { $parser = new Spyc(); $fixtureContent = $parser->load(Director::baseFolder().'/'.$fixtureFile); - $this->fixtureDictionary = array(); - - foreach($fixtureContent as $dataClass => $items) { - foreach($items as $identifier => $fields) { - $obj = new $dataClass(); - foreach($fields as $fieldName => $fieldVal) { - if($obj->many_many($fieldName) || $obj->has_many($fieldName)) { - $parsedItems = array(); - $items = split(' *, *',trim($fieldVal)); - foreach($items as $item) { - $parsedItems[] = $this->parseFixtureVal($item); - } - $obj->write(); - if($obj->has_many($fieldName)) { - $obj->getComponents($fieldName)->setByIDList($parsedItems); - } elseif($obj->many_many($fieldName)) { - $obj->getManyManyComponents($fieldName)->setByIDList($parsedItems); - } - } elseif($obj->has_one($fieldName)) { - $obj->{$fieldName . 'ID'} = $this->parseFixtureVal($fieldVal); - } else { - $obj->$fieldName = $this->parseFixtureVal($fieldVal); - } - } - $obj->write(); - - // Populate the dictionary with the ID - $this->fixtureDictionary[$dataClass][$identifier] = $obj->ID; - } - } - } - - /** - * Parse a value from a fixture file. If it starts with => it will get an ID from the fixture dictionary - */ - protected function parseFixtureVal($fieldVal) { - // Parse a dictionary reference - used to set foreign keys - if(substr($fieldVal,0,2) == '=>') { - list($a, $b) = explode('.', substr($fieldVal,2), 2); - return $this->fixtureDictionary[$a][$b]; - - // Regular field value setting - } else { - return $fieldVal; - } + $this->fixture = new YamlFixture($fixtureFile); + $this->fixture->saveIntoDatabase(); } function tearDown() { diff --git a/forms/FormField.php b/forms/FormField.php index 8534c7038..3fd3b1f4c 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -454,6 +454,29 @@ HTML; } } + /** + * Takes a fieldname and converts camelcase to spaced + * words. Also resolves combined fieldnames with dot syntax + * to spaced words. + * + * @example 'TotalAmount' will return 'Total Amount' + * @example 'Organisation.ZipCode' will return 'Organisation Zip Code' + * + * @param string $fieldName + * @return string + */ + public function name_to_label($fieldName) { + if(strpos('.', $fieldName) !== false) { + $parts = explode('.', $fieldName); + $label = $parts[count($parts)-2] . ' ' . $parts[count($parts)-1]; + } else { + $label = $fieldName; + } + $label = preg_replace("/([a-z]+)([A-Z])/","$1 $2", $label); + + return $label; + } + // ################### // DEPRECATED // ################### diff --git a/forms/TableListField.php b/forms/TableListField.php index 1616fffdb..0806cbd43 100755 --- a/forms/TableListField.php +++ b/forms/TableListField.php @@ -176,6 +176,11 @@ class TableListField extends FormField { */ public $groupByField = null; + /** + * @var array + */ + protected $extraLinkParams; + function __construct($name, $sourceClass, $fieldList = null, $sourceFilter = null, $sourceSort = null, $sourceJoin = null) { @@ -603,11 +608,7 @@ JS function getPermissions($arr) { return $this->permissions; } - - - - - + /** * ################################# * Pagination @@ -639,14 +640,29 @@ JS return $_REQUEST['ctf'][$this->Name()]['start']; } + /** + * @param array + */ + function setExtraLinkParams($params){ + $this->extraLinkParams = $params; + } + + /** + * @return array + */ + function getExtraLinkParams(){ + return $this->extraLinkParams; + } + function FirstLink() { $start = 0; if(!isset($_REQUEST['ctf'][$this->Name()]['start']) || !is_numeric($_REQUEST['ctf'][$this->Name()]['start']) || $_REQUEST['ctf'][$this->Name()]['start'] == 0) { return null; } - - return $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}{$this->filterString()}"; + $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}"; + if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams); + return $link; } function PrevLink() { @@ -658,7 +674,9 @@ JS $start = ($_REQUEST['ctf'][$this->Name()]['start'] - $this->pageSize < 0) ? 0 : $_REQUEST['ctf'][$this->Name()]['start'] - $this->pageSize; - return $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]=$start{$this->filterString()}"; + $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}"; + if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams); + return $link; } function NextLink() { @@ -667,20 +685,22 @@ JS if($currentStart >= $start-1) { return null; } - return $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}{$this->filterString()}"; + $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}"; + if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams); + return $link; } function LastLink() { $pageSize = ($this->TotalCount() % $this->pageSize > 0) ? $this->TotalCount() % $this->pageSize : $this->pageSize; - $start = $this->totalCount - $pageSize; - - + $start = $this->TotalCount() - $pageSize; // Check if there is only one page, or if we are on last page - if($this->totalCount <= $pageSize || (isset($_REQUEST['ctf'][$this->Name()]['start']) && $_REQUEST['ctf'][$this->Name()]['start'] >= $start)) { + if($this->TotalCount() <= $pageSize || (isset($_REQUEST['ctf'][$this->Name()]['start']) && $_REQUEST['ctf'][$this->Name()]['start'] >= $start)) { return null; } - return $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]=$start{$this->filterString()}"; + $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}"; + if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams); + return $link; } function FirstItem() { @@ -739,7 +759,7 @@ JS * * @return String URL-parameters */ - function filterString() { + function filterString() { } @@ -1004,9 +1024,9 @@ class TableListField_Item extends ViewableData { $relationMethod = $fieldNameParts[$j]; $idField = $relationMethod . 'ID'; if($j == sizeof($fieldNameParts)-1) { - $value = $tmpItem->$relationMethod; + if($tmpItem) $value = $tmpItem->$relationMethod; } else { - $tmpItem = $tmpItem->$relationMethod(); + if($tmpItem) $tmpItem = $tmpItem->$relationMethod(); } } } diff --git a/javascript/TableListField.js b/javascript/TableListField.js index c9d9a62b4..c4bf7846e 100755 --- a/javascript/TableListField.js +++ b/javascript/TableListField.js @@ -113,7 +113,7 @@ TableListField.prototype = { new Ajax.Request( el.href, { - postBody: 'update=1', + postBody: 'update=1&paginate=1', onComplete: Ajax.Evaluator, onFailure: this.ajaxErrorHandler.bind(this) } diff --git a/templates/Includes/TableListField_PageControls.ss b/templates/Includes/TableListField_PageControls.ss index 53091b2a8..7ae602ce0 100755 --- a/templates/Includes/TableListField_PageControls.ss +++ b/templates/Includes/TableListField_PageControls.ss @@ -1,7 +1,5 @@ <% if ShowPagination %>
- <% if LastLink %><% _t('VIEWLAST', 'View last') %> $PageSize - <% else %><% _t('VIEWLAST', 'View last') %> $PageSize<% end_if %> <% if FirstLink %><% _t('VIEWFIRST', 'View first') %> $PageSize <% else %><% _t('VIEWFIRST', 'View first') %> $PageSize<% end_if %> <% if PrevLink %> @@ -11,5 +9,7 @@ <% if NextLink %> <% else %><% _t('VIEWNEXT', 'View next') %> $PageSize<% end_if %> + <% if LastLink %><% _t('VIEWLAST', 'View last') %> $PageSize + <% else %><% _t('VIEWLAST', 'View last') %> $PageSize<% end_if %>
<% end_if %> \ No newline at end of file diff --git a/tests/DataObjectTest.php b/tests/DataObjectTest.php index e9b14825e..471beb23b 100644 --- a/tests/DataObjectTest.php +++ b/tests/DataObjectTest.php @@ -14,25 +14,25 @@ class DataObjectTest extends SapphireTest { function testDelete() { // Test deleting using delete() on the DataObject // Get the first page - $page = $this->objFromFixture('Page', 'page1'); + $page = $this->fixture->objFromFixture('Page', 'page1'); // Check the page exists before deleting $this->assertTrue(is_object($page) && $page->exists()); // Delete the page $page->delete(); // Check that page does not exist after deleting - $page = $this->objFromFixture('Page', 'page1'); + $page = $this->fixture->objFromFixture('Page', 'page1'); $this->assertTrue(!$page || !$page->exists()); // Test deleting using DataObject::delete_by_id() // Get the second page - $page2 = $this->objFromFixture('Page', 'page2'); + $page2 = $this->fixture->objFromFixture('Page', 'page2'); // Check the page exists before deleting $this->assertTrue(is_object($page2) && $page2->exists()); // Delete the page DataObject::delete_by_id('Page', $page2->ID); // Check that page does not exist after deleting - $page2 = $this->objFromFixture('Page', 'page2'); + $page2 = $this->fixture->objFromFixture('Page', 'page2'); $this->assertTrue(!$page2 || !$page2->exists()); } @@ -91,7 +91,7 @@ class DataObjectTest extends SapphireTest { // Test get_by_id() - $homepage = $this->objFromFixture('Page', 'home'); + $homepage = $this->fixture->objFromFixture('Page', 'home'); $page = DataObject::get_by_id('Page', $homepage->ID); $this->assertTrue($page->Title == 'Home'); @@ -130,7 +130,7 @@ class DataObjectTest extends SapphireTest { * */ function testWritePropertyWithoutDBField() { - $page = $this->objFromFixture('Page', 'page1'); + $page = $this->fixture->objFromFixture('Page', 'page1'); $page->ParentID = 99; $page->write(); // reload the page from the database @@ -144,7 +144,7 @@ class DataObjectTest extends SapphireTest { * - Test the IDs on the DataObjects are set correctly */ function testHasManyRelationships() { - $page = $this->objFromFixture('Page', 'home'); + $page = $this->fixture->objFromFixture('Page', 'home'); // Test getComponents() gets the ComponentSet of the other side of the relation $this->assertTrue($page->getComponents('Comments')->Count() == 2); diff --git a/tests/SiteTreeTest.php b/tests/SiteTreeTest.php index ab479b63f..9c32a1d9b 100644 --- a/tests/SiteTreeTest.php +++ b/tests/SiteTreeTest.php @@ -26,7 +26,7 @@ class SiteTreeTest extends SapphireTest { ); foreach($expectedURLs as $fixture => $urlSegment) { - $obj = $this->objFromFixture('Page', $fixture); + $obj = $this->fixture->objFromFixture('Page', $fixture); $this->assertEquals($urlSegment, $obj->URLSegment); } } @@ -35,7 +35,7 @@ class SiteTreeTest extends SapphireTest { * Test that publication copies data to SiteTree_Live */ function testPublishCopiesToLiveTable() { - $obj = $this->objFromFixture('Page','about'); + $obj = $this->fixture->objFromFixture('Page','about'); $obj->publish('Stage', 'Live'); $createdID = DB::query("SELECT ID FROM SiteTree_Live WHERE URLSegment = '$obj->URLSegment'")->value();