(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@60264 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-08-10 23:17:51 +00:00
parent 46bbde18e7
commit 95d4634e2e
12 changed files with 387 additions and 167 deletions

View File

@ -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

View File

@ -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.

169
core/model/YamlFixture.php Normal file
View File

@ -0,0 +1,169 @@
<?php
/**
* Uses the Spyc library to parse a YAML document (see http://yaml.org).
* YAML is a simple markup languages that uses tabs and colons instead of the more verbose XML tags,
* and because of this much better for developers creating files by hand.
*
* The contents of the YAML file are broken into three levels:
* - Top level: class names - Page and ErrorPage. This is the name of the dataobject class that should be created.
* The fact that ErrorPage is actually a subclass is irrelevant to the system populating the database.
* Each identifier you specify delimits a new database record.
* This means that every record needs to have an identifier, whether you use it or not.
* - Third level: fields - each field for the record is listed as a 3rd level entry.
* In most cases, the fieldÕs raw content is provided.
* However, if you want to define a relationship, you can do so using "=>"
*
* 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.
*
* <code>
* 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
* </code>
*
* @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;
}
}
}
?>

View File

@ -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);
}
/**

View File

@ -0,0 +1,30 @@
<?php
/**
* A special type Int field used for primary keys.
*
* @todo Allow for custom limiting/filtering of scaffoldFormField dropdown
*
* @param string $name
* @param DataOject $object The object that this is primary key for (should have a relation with $name)
*/
class PrimaryKey extends Int {
/**
* @var DataObject
*/
protected $object;
function __construct($name, $object) {
$this->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, ' ');
}
}
?>

View File

@ -1,10 +1,4 @@
<?php
/**
* @package sapphire
* @subpackage testing
*/
require_once 'TestRunner.php';
if(hasPhpUnit()) {
require_once 'PHPUnit/Framework.php';
@ -13,18 +7,27 @@ require_once 'PHPUnit/Framework.php';
/**
* Test case class for the Sapphire framework.
* Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier to work with.
*
* @package sapphire
* @subpackage testing
*/
class SapphireTest extends PHPUnit_Framework_TestCase {
/**
* Path to fixture data for this test run
* Path to fixture data for this test run.
*
* @var string
*/
protected static $fixture_file = null;
protected $originalMailer;
protected $mailer;
/**
* @var YamlFixture
*/
protected $fixture;
function setUp() {
$className = get_class($this);
$fixtureFile = eval("return {$className}::\$fixture_file;");
@ -47,10 +50,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
$dbadmin = new DatabaseAdmin();
$dbadmin->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() {

View File

@ -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
// ###################

View File

@ -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();
}
}
}

View File

@ -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)
}

View File

@ -1,7 +1,5 @@
<% if ShowPagination %>
<div class="PageControls">
<% if LastLink %><a class="Last" href="$LastLink" title="<% _t('VIEWLAST', 'View last') %> $PageSize"><img src="cms/images/pagination/record-last.png" alt="<% _t('VIEWLAST', 'View last') %> $PageSize" /></a>
<% else %><span class="Last"><img src="cms/images/pagination/record-last-g.png" alt="<% _t('VIEWLAST', 'View last') %> $PageSize" /></span><% end_if %>
<% if FirstLink %><a class="First" href="$FirstLink" title="<% _t('VIEWFIRST', 'View first') %> $PageSize"><img src="cms/images/pagination/record-first.png" alt="<% _t('VIEWFIRST', 'View first') %> $PageSize" /></a>
<% else %><span class="First"><img src="cms/images/pagination/record-first-g.png" alt="<% _t('VIEWFIRST', 'View first') %> $PageSize" /></span><% end_if %>
<% if PrevLink %><a class="Prev" href="$PrevLink" title="<% _t('VIEWPREVIOUS', 'View previous') %> $PageSize"><img src="cms/images/pagination/record-prev.png" alt="<% _t('VIEWPREVIOUS', 'View previous') %> $PageSize" /></a>
@ -11,5 +9,7 @@
</span>
<% if NextLink %><a class="Next" href="$NextLink" title="<% _t('VIEWNEXT', 'View next') %> $PageSize"><img src="cms/images/pagination/record-next.png" alt="<% _t('VIEWNEXT', 'View next') %> $PageSize" /></a>
<% else %><img class="Next" src="cms/images/pagination/record-next-g.png" alt="<% _t('VIEWNEXT', 'View next') %> $PageSize" /><% end_if %>
<% if LastLink %><a class="Last" href="$LastLink" title="<% _t('VIEWLAST', 'View last') %> $PageSize"><img src="cms/images/pagination/record-last.png" alt="<% _t('VIEWLAST', 'View last') %> $PageSize" /></a>
<% else %><span class="Last"><img src="cms/images/pagination/record-last-g.png" alt="<% _t('VIEWLAST', 'View last') %> $PageSize" /></span><% end_if %>
</div>
<% end_if %>

View File

@ -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);

View File

@ -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();