Merged from branches/2.3

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@75845 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sean Harvey 2009-05-01 03:49:34 +00:00
parent 4965352bc3
commit 9b75cb1987
14 changed files with 611 additions and 126 deletions

View File

@ -364,11 +364,10 @@ class SSViewer extends Object {
if(isset($_GET['debug_profile'])) Profiler::unmark("SSViewer::process", " for $template");
// If we have our crazy base tag, then fix # links referencing the current page.
if(strpos($output, '<base') !== false) {
$thisURLRelativeToBase = Director::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
$output = preg_replace('/(<a[^>+]href *= *")#/i', '\\1' . $thisURLRelativeToBase . '#', $output);
$output = preg_replace('/(<a[^>+]href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
}
return $output;

View File

@ -64,35 +64,35 @@ class ComponentSet extends DataObjectSet {
* Find the extra field data for a single row of the relationship
* join table, given the known child ID.
*
* @todo This should return casted fields, like Enum, Varchar, Date
* instead of just the raw value of the field.
*
* @param string $componentName The name of the component
* @param int $childID The ID of the child for the relationship
* @return array Map of fieldName => fieldValue
* @param string|null $fieldName To get a specific extra data field, specify it here
* @return array|string Array of field => value or single string of value
*/
function getExtraData($componentName, $childID) {
function getExtraData($componentName, $childID, $fieldName = null) {
$ownerObj = $this->ownerObj;
$parentField = $this->ownerClass . 'ID';
$childField = ($this->childClass == $this->ownerClass) ? 'ChildID' : ($this->childClass . 'ID');
$result = array();
if(!isset($componentName)) {
user_error('ComponentSet::getExtraData() passed a NULL component name', E_USER_ERROR);
}
if(!$componentName) return false;
if(!is_numeric($childID)) {
user_error('ComponentSet::getExtraData() passed a non-numeric child ID', E_USER_ERROR);
}
// @todo Optimize into a single query instead of one per extra field
$extraFields = $ownerObj->many_many_extraFields($componentName);
if($extraFields) {
foreach($extraFields as $fieldName => $dbFieldSpec) {
$query = DB::query("SELECT \"$fieldName\" FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$childID}");
$value = $query->value();
$result[$fieldName] = $value;
}
}
if(!$extraFields) return false;
return $result;
if($fieldName && !empty($extraFields[$fieldName])) {
$query = DB::query("SELECT $fieldName FROM {$this->tableName} WHERE $parentField = '{$this->ownerObj->ID}' AND $childField = '{$childID}'");
return $query->value();
} else {
$fields = array();
foreach($extraFields as $fieldName => $fieldSpec) {
$query = DB::query("SELECT $fieldName FROM {$this->tableName} WHERE $parentField = '{$this->ownerObj->ID}' AND $childField = '{$childID}'");
$fields[$fieldName] = $query->value();
}
return $fields;
}
}
/**

View File

@ -25,15 +25,21 @@ class Hierarchy extends DataObjectDecorator {
* @param string $titleEval PHP code to evaluate to start each child - this should include '<li>'
* @param string $extraArg Extra arguments that will be passed on to children, for if they overload this function.
* @param boolean $limitToMarked Display only marked children.
* @param string $childrenMethod The name of the method used to get children from each object
* @param boolean $rootCall Set to true for this first call, and then to false for calls inside the recursion. You should not change this.
* @return string
*/
public function getChildrenAsUL($attributes = "", $titleEval = '"<li>" . $child->Title', $extraArg = null, $limitToMarked = false, $rootCall = true) {
public function getChildrenAsUL($attributes = "", $titleEval = '"<li>" . $child->Title', $extraArg = null, $limitToMarked = false, $childrenMethod = "AllChildrenIncludingDeleted", $rootCall = true) {
if($limitToMarked && $rootCall) {
$this->markingFinished();
}
$children = $this->owner->AllChildrenIncludingDeleted($extraArg);
if($this->owner->hasMethod($childrenMethod)) {
$children = $this->owner->$childrenMethod($extraArg);
} else {
user_error(sprintf("Can't find the method '%s' on class '%s' for getting tree children",
$childrenMethod, get_class($this->owner)), E_USER_ERROR);
}
if($children) {
if($attributes) {
@ -46,7 +52,7 @@ class Hierarchy extends DataObjectDecorator {
if(!$limitToMarked || $child->isMarked()) {
$foundAChild = true;
$output .= eval("return $titleEval;") . "\n" .
$child->getChildrenAsUL("", $titleEval, $extraArg, $limitToMarked, false) . "</li>\n";
$child->getChildrenAsUL("", $titleEval, $extraArg, $limitToMarked, $childrenMethod, false) . "</li>\n";
}
}
@ -69,13 +75,13 @@ class Hierarchy extends DataObjectDecorator {
* @param int $minCount The minimum amount of nodes to mark.
* @return int The actual number of nodes marked.
*/
public function markPartialTree($minCount = 30, $context = null) {
public function markPartialTree($minCount = 30, $context = null, $childrenMethod = "AllChildrenIncludingDeleted") {
$this->markedNodes = array($this->owner->ID => $this->owner);
$this->owner->markUnexpanded();
// foreach can't handle an ever-growing $nodes list
while(list($id, $node) = each($this->markedNodes)) {
$this->markChildren($node, $context);
$this->markChildren($node, $context, $childrenMethod);
if($minCount && sizeof($this->markedNodes) >= $minCount) {
break;
@ -139,8 +145,14 @@ class Hierarchy extends DataObjectDecorator {
* Mark all children of the given node that match the marking filter.
* @param DataObject $node Parent node.
*/
public function markChildren($node, $context = null) {
$children = $node->AllChildrenIncludingDeleted($context);
public function markChildren($node, $context = null, $childrenMethod = "AllChildrenIncludingDeleted") {
if($node->hasMethod($childrenMethod)) {
$children = $node->$childrenMethod($context);
} else {
user_error(sprintf("Can't find the method '%s' on class '%s' for getting tree children",
$childrenMethod, get_class($this->owner)), E_USER_ERROR);
}
$node->markExpanded();
if($children) {
foreach($children as $child) {
@ -171,10 +183,10 @@ class Hierarchy extends DataObjectDecorator {
*/
public function markingClasses() {
$classes = '';
if(!$this->expanded) {
if(!$this->isExpanded()) {
$classes .= " unexpanded";
}
if(!$this->treeOpened) {
if(!$this->isTreeOpened()) {
$classes .= " closed";
}
return $classes;
@ -229,42 +241,42 @@ class Hierarchy extends DataObjectDecorator {
* True if this DataObject is marked.
* @var boolean
*/
protected $marked = false;
protected static $marked = array();
/**
* True if this DataObject is expanded.
* @var boolean
*/
protected $expanded = false;
protected static $expanded = array();
/**
* True if this DataObject is opened.
* @var boolean
*/
protected $treeOpened = false;
protected static $treeOpened = array();
/**
* Mark this DataObject as expanded.
*/
public function markExpanded() {
$this->marked = true;
$this->expanded = true;
self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
self::$expanded[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
}
/**
* Mark this DataObject as unexpanded.
*/
public function markUnexpanded() {
$this->marked = true;
$this->expanded = false;
self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
self::$expanded[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = false;
}
/**
* Mark this DataObject's tree as opened.
*/
public function markOpened() {
$this->marked = true;
$this->treeOpened = true;
self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
}
/**
@ -272,7 +284,9 @@ class Hierarchy extends DataObjectDecorator {
* @return boolean
*/
public function isMarked() {
return $this->marked;
$baseClass = ClassInfo::baseDataClass($this->owner->class);
$id = $this->owner->ID;
return isset(self::$marked[$baseClass][$id]) ? self::$marked[$baseClass][$id] : false;
}
/**
@ -280,14 +294,18 @@ class Hierarchy extends DataObjectDecorator {
* @return boolean
*/
public function isExpanded() {
return $this->expanded;
$baseClass = ClassInfo::baseDataClass($this->owner->class);
$id = $this->owner->ID;
return isset(self::$expanded[$baseClass][$id]) ? self::$expanded[$baseClass][$id] : false;
}
/**
* Check if this DataObject's tree is opened.
*/
public function isTreeOpened() {
return $this->treeOpened;
$baseClass = ClassInfo::baseDataClass($this->owner->class);
$id = $this->owner->ID;
return isset(self::$treeOpened[$baseClass][$id]) ? self::$treeOpened[$baseClass][$id] : false;
}
/**
@ -478,6 +496,15 @@ class Hierarchy extends DataObjectDecorator {
return $this->_cache_allChildrenIncludingDeleted;
}
/**
* Return all the children that this page had, including pages that were deleted
* from both stage & live.
*/
public function AllHistoricalChildren() {
return Versioned::get_including_deleted(ClassInfo::baseDataClass($this->owner->class),
"ParentID = " . (int)$this->owner->ID);
}
/**
* Return the number of children
* @return int

View File

@ -1222,6 +1222,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->fieldLabel('ClassName'),
$this->getClassDropdown()
),
new OptionsetField("ParentType", "Page location", array(
"root" => _t("SiteTree.PARENTTYPE_ROOT", "Top-level page"),
"subpage" => _t("SiteTree.PARENTTYPE_SUBPAGE", "Sub-page underneath a parent page (choose below)"),
)),
new TreeDropdownField("ParentID", $this->fieldLabel('ParentID'), 'SiteTree'),
new CheckboxField("ShowInMenus", $this->fieldLabel('ShowInMenus')),
new CheckboxField("ShowInSearch", $this->fieldLabel('ShowInSearch')),
/*, new TreeMultiselectField("MultipleParents", "Page appears within", "SiteTree")*/
@ -1318,6 +1325,8 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$labels['MetaKeywords'] = _t('SiteTree.METAKEYWORDS', "Keywords");
$labels['ExtraMeta'] = _t('SiteTree.METAEXTRA', "Custom Meta Tags");
$labels['ClassName'] = _t('SiteTree.PAGETYPE', "Page type", PR_MEDIUM, 'Classname of a page object');
$labels['ParentType'] = _t('SiteTree.PARENTTYPE', "Page location", PR_MEDIUM);
$labels['ParentID'] = _t('SiteTree.PARENTID', "Parent page", PR_MEDIUM);
$labels['ShowInMenus'] =_t('SiteTree.SHOWINMENUS', "Show in menus?");
$labels['ShowInSearch'] = _t('SiteTree.SHOWINSEARCH', "Show in search?");
$labels['ProvideComments'] = _t('SiteTree.ALLOWCOMMENTS', "Allow comments on this page?");
@ -1370,11 +1379,15 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($this->IsDeletedFromStage) {
if($this->can('CMSEdit')) {
// "restore"
$actions->push(new FormAction('revert',_t('CMSMain.RESTORE','Restore')));
// "delete from live"
$actions->push(new FormAction('deletefromlive',_t('CMSMain.DELETEFP','Delete from the published site')));
if($this->ExistsOnLive) {
// "restore"
$actions->push(new FormAction('revert',_t('CMSMain.RESTORE','Restore')));
// "delete from live"
$actions->push(new FormAction('deletefromlive',_t('CMSMain.DELETEFP','Delete from the published site')));
} else {
// "restore"
$actions->push(new FormAction('restore',_t('CMSMain.RESTORE','Restore')));
}
}
} else {
if($this->canEdit()) {
@ -1481,6 +1494,29 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->extend('onAfterRevertToLive');
}
/**
* Restore the content in the active copy of this SiteTree page to the stage site.
* @return The SiteTree object.
*/
function doRestoreToStage() {
// if no record can be found on draft stage (meaning it has been "deleted from draft" before),
// create an empty record
if(!DB::query("SELECT ID FROM SiteTree WHERE ID = $this->ID")->value()) {
DB::query("INSERT INTO SiteTree SET ID = $this->ID");
}
$oldStage = Versioned::current_stage();
Versioned::reading_stage('Stage');
$this->forceChange();
$this->writeWithoutVersion();
$result = DataObject::get_by_id($this->class, $this->ID);
Versioned::reading_stage($oldStage);
return $result;
}
/**
* Check if this page is new - that is, if it has yet to have been written
* to the database.
@ -1589,11 +1625,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// sort alphabetically, and put current on top
asort($result);
$currentPageTypeName = $result[$currentClass];
unset($result[$currentClass]);
$result = array_reverse($result);
$result[$currentClass] = $currentPageTypeName;
$result = array_reverse($result);
if($currentClass) {
$currentPageTypeName = $result[$currentClass];
unset($result[$currentClass]);
$result = array_reverse($result);
$result[$currentClass] = $currentPageTypeName;
$result = array_reverse($result);
}
return $result;
}
@ -1695,7 +1733,11 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/
function TreeTitle() {
if($this->IsDeletedFromStage) {
$tag ="del title=\"" . _t('SiteTree.REMOVEDFROMDRAFT', 'Removed from draft site') . "\"";
if($this->ExistsOnLive) {
$tag ="del title=\"" . _t('SiteTree.REMOVEDFROMDRAFT', 'Removed from draft site') . "\"";
} else {
$tag ="del class=\"deletedOnLive\" title=\"" . _t('SiteTree.DELETEDPAGE', 'Deleted page') . "\"";
}
} elseif($this->IsAddedToStage) {
$tag = "ins title=\"" . _t('SiteTree.ADDEDTODRAFT', 'Added to draft site') . "\"";
} elseif($this->IsModifiedOnStage) {
@ -1775,9 +1817,16 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($this->isNew()) return false;
$stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
$liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
return (!$stageVersion && $liveVersion);
// Return true for both completely deleted pages and for pages just deleted from stage.
return !$stageVersion;
}
/**
* Return true if this page exists on the live site
*/
function getExistsOnLive() {
return (bool)Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
}
/**
@ -1860,5 +1909,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return $entities;
}
function getParentType() {
return $this->ParentID == 0 ? 'root' : 'subpage';
}
}
?>

View File

@ -106,10 +106,10 @@ class Versioned extends DataObjectDecorator {
* Create a temporary table mapping each database record to its version on the given date.
* This is used by the versioning system to return database content on that date.
* @param string $baseTable The base table.
* @param string $date The date.
* @param string $date The date. If omitted, then the latest version of each page will be returned.
* @todo Ensure that this is DB abstracted
*/
protected function requireArchiveTempTable($baseTable, $date) {
protected static function requireArchiveTempTable($baseTable, $date = null) {
if(!isset(self::$createdArchiveTempTable[$baseTable])) {
self::$createdArchiveTempTable[$baseTable] = true;
@ -123,6 +123,7 @@ class Versioned extends DataObjectDecorator {
GROUP BY \"RecordID\"");
}
}
/**
* An array of DataObject extensions that may require versioning for extra tables
* The array value is a set of suffixes to form these table names, assuming a preceding '_'.
@ -724,6 +725,33 @@ class Versioned extends DataObjectDecorator {
return new $className($record);
}
/**
* Return the equivalent of a DataObject::get() call, querying the latest
* version of each page stored in the (class)_versions tables.
*
* In particular, this will query deleted records as well as active ones.
*/
static function get_including_deleted($class, $filter = "", $sort = "") {
$oldStage = Versioned::$reading_stage;
Versioned::$reading_stage = null;
$SNG = singleton($class);
// Build query
$query = $SNG->buildVersionSQL($filter, $sort);
$baseTable = ClassInfo::baseDataClass($class);
self::requireArchiveTempTable($baseTable);
$query->from["_Archive$baseTable"] = "INNER JOIN `_Archive$baseTable`
ON `_Archive$baseTable`.RecordID = `{$baseTable}_versions`.RecordID
AND `_Archive$baseTable`.Version = `{$baseTable}_versions`.Version";
// Process into a DataObjectSet
$result = $SNG->buildDataObjectSet($query->execute());
Versioned::$reading_stage = $oldStage;
return $result;
}
/**
* @return DataObject
*/

View File

@ -458,69 +458,85 @@ JS;
* this method.
*/
function getCustomFieldsFor($childData) {
if($this->detailFormFields instanceof Fieldset) {
if($this->detailFormFields instanceof FieldSet) {
return $this->detailFormFields;
} else {
$fieldsMethod = $this->detailFormFields;
if(!is_string($fieldsMethod)) {
$this->detailFormFields = 'getCMSFields';
$fieldsMethod = 'getCMSFields';
}
if(!$childData->hasMethod($fieldsMethod)) {
$fieldsMethod = 'getCMSFields';
}
$fields = $childData->$fieldsMethod();
}
if(!$this->relationAutoSetting) {
return $fields;
$fieldsMethod = $this->detailFormFields;
if(!is_string($fieldsMethod)) {
$this->detailFormFields = 'getCMSFields';
$fieldsMethod = 'getCMSFields';
}
$parentClass = DataObject::get_by_id($this->getParentClass(), $this->sourceID());
$manyManyExtraFields = $parentClass->many_many_extraFields($this->name);
if($manyManyExtraFields) {
foreach($manyManyExtraFields as $fieldName => $fieldSpec) {
$dbField = new Varchar('ctf[extraFields][' . $fieldName . ']');
$fields->addFieldToTab('Root.Main', $dbField->scaffoldFormField($fieldName));
}
if(!$childData->hasMethod($fieldsMethod)) {
$fieldsMethod = 'getCMSFields';
}
return $fields;
return $childData->$fieldsMethod();
}
function getFieldsFor($childData) {
// See if our parent class has any many_many relations by this source class
$parentClass = DataObject::get_by_id($this->getParentClass(), $this->sourceID());
if($this->sourceID()) {
$parentClass = DataObject::get_by_id($this->getParentClass(), $this->sourceID());
} else {
$parentClass = singleton($this->getParentClass());
}
$manyManyRelations = $parentClass->many_many();
$manyManyRelationName = null;
$manyManyComponentSet = null;
$hasManyRelations = $parentClass->has_many();
$hasManyRelationName = null;
$hasManyComponentSet = null;
if($manyManyRelations) foreach($manyManyRelations as $relation => $class) {
if($class == $this->sourceClass()) {
$manyManyRelationName = $relation;
}
}
if($hasManyRelations) foreach($hasManyRelations as $relation => $class) {
if($class == $this->sourceClass()) {
$hasManyRelationName = $relation;
}
}
// Add the relation value to related records
if(!$childData->ID && $this->getParentClass()) {
// make sure the relation-link is existing, even if we just add the sourceClass and didn't save it
$parentIDName = $this->getParentIdName( $this->getParentClass(), $this->sourceClass() );
$parentIDName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
$childData->$parentIDName = $this->sourceID();
}
$detailFields = $this->getCustomFieldsFor($childData);
// Loading of extra field values for editing an existing record
if($manyManyRelationName && $childData->ID) {
if($manyManyRelationName) {
$manyManyComponentSet = $parentClass->getManyManyComponents($manyManyRelationName);
$extraData = $manyManyComponentSet->getExtraData($manyManyRelationName, $childData->ID);
if($extraData) foreach($extraData as $fieldName => $fieldValue) {
$field = $detailFields->dataFieldByName('ctf[extraFields][' . $fieldName . ']');
$field->setValue($fieldValue);
$extraFieldsSpec = $parentClass->many_many_extraFields($this->name);
$extraData = null;
if($childData && $childData->ID) {
$extraData = $manyManyComponentSet->getExtraData($manyManyRelationName, $childData->ID);
}
if($extraFieldsSpec) foreach($extraFieldsSpec as $fieldName => $fieldSpec) {
// @todo Create the proper DBField type instead of hardcoding Varchar
$fieldObj = new Varchar($fieldName);
if(isset($extraData[$fieldName])) {
$fieldObj->setValue($extraData[$fieldName]);
}
$detailFields->addFieldToTab('Root.Main', $fieldObj->scaffoldFormField($fieldName));
}
}
if($hasManyRelationName && $childData->ID) {
$hasManyComponentSet = $parentClass->getComponents($hasManyRelationName);
}
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
@ -535,17 +551,23 @@ JS;
$detailFields->push(new HiddenField('ctf[ClassName]', '', $this->sourceClass()));
if($this->getParentClass()) {
$detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
if($manyManyRelationName && $this->relationAutoSetting) {
$detailFields->push(new HiddenField('ctf[manyManyRelation]', '', $manyManyRelationName));
$detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
}
if($hasManyRelationName && $this->relationAutoSetting) {
$detailFields->push(new HiddenField('ctf[hasManyRelation]', '', $hasManyRelationName));
}
if($manyManyRelationName || $hasManyRelationName) {
$detailFields->push(new HiddenField('ctf[sourceID]', '', $this->sourceID()));
}
$parentIdName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
if($parentIdName) {
// add relational fields
$detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
if($parentIdName) {
if($this->relationAutoSetting) {
// Hack for model admin: model admin will have included a dropdown for the relation itself
$detailFields->removeByName($parentIdName);
@ -614,6 +636,8 @@ JS;
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
* which in turn saves the record.
*
* This is for adding new item records. {@link ComplexTableField_ItemRequest::saveComplexTableField()}
*
* @see Form::ReferencedField
*/
function saveComplexTableField($data, $form, $params) {
@ -638,6 +662,14 @@ JS;
$componentSet->add($childData, $extraFields);
}
if(isset($data['ctf']['hasManyRelation'])) {
$parentRecord = DataObject::get_by_id($data['ctf']['parentClass'], (int) $data['ctf']['sourceID']);
$relationName = $data['ctf']['hasManyRelation'];
$componentSet = $parentRecord->getComponents($relationName);
$componentSet->add($childData);
}
$referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
$closeLink = sprintf(
@ -784,6 +816,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
* which in turn saves the record.
*
* This is for editing existing item records. {@link ComplexTableField::saveComplexTableField()}
*
* @see Form::ReferencedField
*/
function saveComplexTableField($data, $form, $request) {
@ -819,6 +853,7 @@ class ComplexTableField_ItemRequest extends RequestHandler {
'<a href="' . $this->Link() . '">"' . $dataObject->Title . '"</a>',
$closeLink
);
$form->sessionMessage($message, 'good');
Director::redirectBack();

View File

@ -95,6 +95,14 @@ class Form extends RequestHandler {
protected $messageType;
/**
* Should we redirect the user back down to the
* the form on validation errors rather then just the page
*
* @var bool
*/
protected $redirectToFormOnValidationError = false;
protected $security = true;
/**
@ -214,11 +222,16 @@ class Form extends RequestHandler {
return $response;
}
} else {
Director::redirectBack();
return;
if($this->getRedirectToFormOnValidationError()) {
if($pageURL = $request->getHeader('Referer')) {
return Director::redirect($pageURL . '#' . $this->FormName());
}
}
return Director::redirectBack();
}
}
// Protection against CSRF attacks
if($this->securityTokenEnabled()) {
$securityID = Session::get('SecurityID');
@ -279,6 +292,27 @@ class Form extends RequestHandler {
$this->transform(new ReadonlyTransformation());
}
/**
* Set whether the user should be redirected back down to the
* form on the page upon validation errors in the form or if
* they just need to redirect back to the page
*
* @param bool Redirect to the form
*/
public function setRedirectToFormOnValidationError($bool) {
$this->redirectToFormOnValidationError = $bool;
}
/**
* Get whether the user should be redirected back down to the
* form on the page upon validation errors
*
* @return bool
*/
public function getRedirectToFormOnValidationError() {
return $this->redirectToFormOnValidationError;
}
/**
* Add an error message to a field on this form. It will be saved into the session
* and used the next time this form is displayed.

View File

@ -579,23 +579,6 @@ class DataObjectTest extends SapphireTest {
// @todo test has_many and many_many relations
}
function testManyManyExtraFields() {
$player = $this->fixture->objFromFixture('DataObjectTest_Player', 'player1');
$team = $this->fixture->objFromFixture('DataObjectTest_Team', 'team1');
// Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
$teamExtraFields = $team->many_many_extraFields('Players');
$this->assertEquals($teamExtraFields, array(
'Position' => 'Varchar(100)'
));
// We'll have to go through the relation to get the extra fields on Player
$playerExtraFields = $player->many_many_extraFields('Teams');
$this->assertEquals($playerExtraFields, array(
'Position' => 'Varchar(100)'
));
}
function testPopulateDefaults() {
$obj = new DataObjectTest_WithDefaults();
$this->assertEquals(
@ -624,6 +607,23 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals($changedPage->ClassName, 'RedirectorPage');
}
function testManyManyExtraFields() {
$player = $this->fixture->objFromFixture('DataObjectTest_Player', 'player1');
$team = $this->fixture->objFromFixture('DataObjectTest_Team', 'team1');
// Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
$teamExtraFields = $team->many_many_extraFields('Players');
$this->assertEquals($teamExtraFields, array(
'Position' => 'Varchar(100)'
));
// We'll have to go through the relation to get the extra fields on Player
$playerExtraFields = $player->many_many_extraFields('Teams');
$this->assertEquals($playerExtraFields, array(
'Position' => 'Varchar(100)'
));
}
}
class DataObjectTest_Player extends Member implements TestOnly {

View File

@ -10,12 +10,17 @@ class ErrorPageTest extends FunctionalTest {
function test404ErrorPage() {
$page = $this->objFromFixture('ErrorPage', '404');
/* The page is an instance of ErrorPage */
$this->assertTrue($page instanceof ErrorPage, 'The page is an instance of ErrorPage');
$response = $this->get($page->URLSegment);
/* A standard error is shown */
$this->assertEquals($response->getBody(), 'The requested page couldn\'t be found.', 'A standard error is shown');
/* When the page is published, an error page with the theme is shown instead */
$page->publish('Stage', 'Live', false);
$response = $this->get($page->URLSegment);
/* We have body text from the error page */
/* There is body text from the error page */
$this->assertNotNull($response->getBody(), 'We have body text from the error page');
/* Status code of the HTTPResponse for error page is "404" */

View File

@ -66,9 +66,13 @@ if(class_exists('SiteTreeCMSWorkflow')) {
function testActionsDeletedFromStageRecord() {
$page = new Page();
$page->write();
$pageID = $page->ID;
$page->publish('Stage', 'Live');
$page->deleteFromStage('Stage');
// Get the live version of the page
$page = Versioned::get_one_by_stage("SiteTree", "Live", "`SiteTree`.ID = $pageID");
$author = $this->objFromFixture('Member', 'cmseditor');
$this->session()->inst_set('loggedInAs', $author->ID);

View File

@ -167,6 +167,45 @@ class SiteTreeTest extends SapphireTest {
$this->assertFalse($modifiedOnDraftPage->IsAddedToStage);
$this->assertTrue($modifiedOnDraftPage->IsModifiedOnStage);
}
/**
* Test that a page can be completely deleted and restored to the stage site
*/
function testRestoreToStage() {
$page = $this->objFromFixture('Page', 'about');
$pageID = $page->ID;
$page->delete();
$this->assertTrue(!DataObject::get_by_id("Page", $pageID));
$deletedPage = Versioned::get_latest_version('SiteTree', $pageID);
$resultPage = $deletedPage->doRestoreToStage();
$requeriedPage = DataObject::get_by_id("Page", $pageID);
$this->assertEquals($pageID, $resultPage->ID);
$this->assertEquals($pageID, $requeriedPage->ID);
$this->assertEquals('About Us', $requeriedPage->Title);
$this->assertEquals('Page', $requeriedPage->class);
$page2 = $this->objFromFixture('Page', 'staff');
$page2ID = $page2->ID;
$page2->doUnpublish();
$page2->delete();
// Check that if we restore while on the live site that the content still gets pushed to
// stage
Versioned::reading_stage('Live');
$deletedPage = Versioned::get_latest_version('SiteTree', $page2ID);
$deletedPage->doRestoreToStage();
$this->assertTrue(!Versioned::get_one_by_stage("Page", "Live", "`SiteTree`.ID = " . $page2ID));
Versioned::reading_stage('Stage');
$requeriedPage = DataObject::get_by_id("Page", $page2ID);
$this->assertEquals('Staff', $requeriedPage->Title);
$this->assertEquals('Page', $requeriedPage->class);
}
}
// We make these extend page since that's what all page types are expected to do

View File

@ -0,0 +1,246 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class ComplexTableFieldTest extends FunctionalTest {
static $fixture_file = 'sapphire/tests/forms/ComplexTableFieldTest.yml';
/**
* An instance of {@link Controller} used for
* running tests against.
*
* @var Controller object
*/
protected $controller;
/**
* An instance of {@link Form} that is taken
* from the test controller, used for testing.
*
* @var Form object
*/
protected $form;
function setUp() {
parent::setUp();
$this->controller = new ComplexTableFieldTest_Controller();
$this->manyManyForm = $this->controller->ManyManyForm();
}
function testCorrectNumberOfRowsInTable() {
$field = $this->manyManyForm->dataFieldByName('Players');
$parser = new CSSContentParser($field->FieldHolder());
/* There are 2 players (rows) in the table */
$this->assertEquals(count($parser->getBySelector('tbody tr')), 2, 'There are 2 players (rows) in the table');
/* There are 2 CTF items in the DataObjectSet */
$this->assertEquals($field->Items()->Count(), 2, 'There are 2 CTF items in the DataObjectSet');
}
function testDetailFormDisplaysWithCorrectFields() {
$field = $this->manyManyForm->dataFieldByName('Players');
$detailForm = $field->add();
$parser = new CSSContentParser($detailForm);
/* There is a field called "Name", which is a text input */
$this->assertEquals(count($parser->getBySelector('#Name input')), 1, 'There is a field called "Name", which is a text input');
/* There is a field called "Role" - this field is the extra field for $many_many_extraFields */
$this->assertEquals(count($parser->getBySelector('#Role input')), 1, 'There is a field called "Role" - this field is the extra field for $many_many_extraFields');
}
function testAddingManyManyNewPlayerWithExtraData() {
$team = DataObject::get_one('ComplexTableFieldTest_Team', "Name = 'The Awesome People'");
$this->post('ComplexTableFieldTest_Controller/ManyManyForm/field/Players/AddForm', array(
'Name' => 'Bobby Joe',
'ctf' => array(
'extraFields' => array(
'Role' => 'Goalie',
'Position' => 'Player',
'DateJoined' => '2008-10-10'
),
'ClassName' => 'ComplexTableFieldTest_Player',
'manyManyRelation' => 'Players',
'parentClass' => 'ComplexTableFieldTest_Team',
'sourceID' => $team->ID
)
));
/* Retrieve the new player record we created */
$newPlayer = DataObject::get_one('ComplexTableFieldTest_Player', "Name = 'Bobby Joe'");
/* A new ComplexTableFieldTest_Player record was created, Name = "Bobby Joe" */
$this->assertNotNull($newPlayer, 'A new ComplexTableFieldTest_Player record was created, Name = "Bobby Joe"');
/* Get the many-many related Teams to the new player that were automatically linked by CTF */
$teams = $newPlayer->getManyManyComponents('Teams');
/* Automatic many-many relation was set correctly on the new player */
$this->assertEquals($teams->Count(), 1, 'Automatic many-many relation was set correctly on the new player');
/* The extra fields have the correct value */
$extraFields = $teams->getExtraData('Teams', $team->ID);
/* There are 3 extra fields */
$this->assertEquals(count($extraFields), 3, 'There are 3 extra fields');
/* The three extra fields have the correct values */
$this->assertEquals($extraFields['Role'], 'Goalie', 'The extra field "Role" has the correct value');
$this->assertEquals($extraFields['Position'], 'Player', 'The extra field "Position" has the correct value');
$this->assertEquals($extraFields['DateJoined'], '2008-10-10', 'The extra field "DateJoined" has the correct value');
}
function testAddingHasManyData() {
$team = DataObject::get_one('ComplexTableFieldTest_Team', "Name = 'The Awesome People'");
$this->post('ComplexTableFieldTest_Controller/HasManyForm/field/Sponsors/AddForm', array(
'Name' => 'Jim Beam',
'ctf' => array(
'ClassName' => 'ComplexTableFieldTest_Sponsor',
'hasManyRelation' => 'Sponsors',
'parentClass' => 'ComplexTableFieldTest_Team',
'sourceID' => $team->ID
)
));
/* Retrieve the new sponsor record we created */
$newSponsor = DataObject::get_one('ComplexTableFieldTest_Sponsor', "Name = 'Jim Beam'");
/* A new ComplexTableFieldTest_Sponsor record was created, Name = "Jim Beam" */
$this->assertNotNull($newSponsor, 'A new ComplexTableFieldTest_Sponsor record was created, Name = "Jim Beam"');
/* Get the has-one related Team to the new sponsor that were automatically linked by CTF */
$teamID = $newSponsor->TeamID;
/* Automatic many-many relation was set correctly on the new player */
$this->assertTrue($teamID > 0, 'Automatic has-many/has-one relation was set correctly on the sponsor');
/* The other side of the relation works as well */
$team = DataObject::get_by_id('ComplexTableFieldTest_Team', $teamID);
/* Let's get the Sponsors component */
$sponsor = $team->getComponents('Sponsors')->First();
/* The sponsor is the same as the one we added */
$this->assertEquals($newSponsor->ID, $sponsor->ID, 'The sponsor is the same as the one we added');
}
}
class ComplexTableFieldTest_Controller extends Controller {
function Link($action = null) {
return "ComplexTableFieldTest_Controller/$action";
}
function ManyManyForm() {
$team = DataObject::get_one('ComplexTableFieldTest_Team', "Name = 'The Awesome People'");
$playersField = new ComplexTableField(
$this,
'Players',
'ComplexTableFieldTest_Player',
ComplexTableFieldTest_Player::$summary_fields,
'getCMSFields'
);
$playersField->setParentClass('ComplexTableFieldTest_Team');
$form = new Form(
$this,
'ManyManyForm',
new FieldSet(
new HiddenField('ID', '', $team->ID),
$playersField
),
new FieldSet(
new FormAction('doSubmit', 'Submit')
)
);
$form->disableSecurityToken();
return $form;
}
function HasManyForm() {
$team = DataObject::get_one('ComplexTableFieldTest_Team', "Name = 'The Awesome People'");
$sponsorsField = new ComplexTableField(
$this,
'Sponsors',
'ComplexTableFieldTest_Sponsor',
ComplexTableFieldTest_Sponsor::$summary_fields,
'getCMSFields'
);
$sponsorsField->setParentClass('ComplexTableFieldTest_Team');
$form = new Form(
$this,
'HasManyForm',
new FieldSet(
new HiddenField('ID', '', $team->ID),
$sponsorsField
),
new FieldSet(
new FormAction('doSubmit', 'Submit')
)
);
$form->disableSecurityToken();
return $form;
}
}
class ComplexTableFieldTest_Player extends DataObject implements TestOnly {
public static $db = array(
'Name' => 'Varchar(100)'
);
public static $many_many = array(
'Teams' => 'ComplexTableFieldTest_Team'
);
public static $many_many_extraFields = array(
'Teams' => array(
'Role' => 'Varchar(100)',
'Position' => "Enum('Admin,Player,Coach','Admin')",
'DateJoined' => 'Date'
)
);
}
class ComplexTableFieldTest_Team extends DataObject implements TestOnly {
public static $db = array(
'Name' => 'Varchar(100)'
);
public static $belongs_many_many = array(
'Players' => 'ComplexTableFieldTest_Player'
);
public static $has_many = array(
'Sponsors' => 'ComplexTableFieldTest_Sponsor'
);
}
class ComplexTableFieldTest_Sponsor extends DataObject implements TestOnly {
public static $db = array(
'Name' => 'Varchar(100)'
);
public static $has_one = array(
'Team' => 'ComplexTableFieldTest_Team'
);
}
?>

View File

@ -0,0 +1,15 @@
ComplexTableFieldTest_Player:
p1:
Name: Joe Bloggs
p2:
Name: Some Guy
ComplexTableFieldTest_Team:
t1:
Name: The Awesome People
t2:
Name: Incredible Four
ComplexTableFieldTest_Sponsor:
s1:
Name: Coca Cola
s2:
Name: Pepsi