ENHANCEMENT Allow auto setting a many-to-many relation on ComplexTableField in the similar fashion of auto setting the foreign key for a one-to-many or one-to-many relation.

ENHANCEMENT Added DataObject::many_many_extraFields() for getting the many_many_extraFields fields for a component name on both sides of a many-to-many relation.
MINOR Added test for many_many_extraFields in DataObjectTest



git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@71613 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sean Harvey 2009-02-10 06:04:36 +00:00
parent bf07ed2d87
commit fa4c42d642
3 changed files with 176 additions and 25 deletions

View File

@ -1342,6 +1342,90 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
return isset($items) ? $items : null;
}
/**
* Return the many-to-many extra fields specification.
*
* If you don't specify a component name, it returns all
* extra fields for all components available.
*
* @param string $component Name of component
* @return array
*/
public function many_many_extraFields($component = null) {
$classes = ClassInfo::ancestry($this);
foreach($classes as $class) {
if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
// Find extra fields for one component
if($component) {
$SNG_class = singleton($class);
$extraFields = $SNG_class->stat('many_many_extraFields');
// Extra fields are immediately available on this class
if(isset($extraFields[$component])) {
return $extraFields[$component];
}
$manyMany = $SNG_class->stat('many_many');
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) {
$SNG_candidate = singleton($candidate);
$candidateManyMany = $SNG_candidate->stat('belongs_many_many');
// Find the relation given the class
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
$relationName = $relation;
}
}
$extraFields = $SNG_candidate->stat('many_many_extraFields');
if(isset($extraFields[$relationName])) {
return $extraFields[$relationName];
}
}
$manyMany = $SNG_class->stat('belongs_many_many');
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) {
$SNG_candidate = singleton($candidate);
$candidateManyMany = $SNG_candidate->stat('many_many');
// Find the relation given the class
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
$relationName = $relation;
}
}
$extraFields = $SNG_candidate->stat('many_many_extraFields');
if(isset($extraFields[$relationName])) {
return $extraFields[$relationName];
}
}
} else {
// Find all the extra fields for all components
$newItems = eval("return (array){$class}::\$many_many_extraFields;");
foreach($newItems as $k => $v) {
if(!is_array($v)) {
user_error(
"$class::\$many_many_extraFields has a bad entry: "
. var_export($k, true) . " => " . var_export($v, true)
. ". Each many_many_extraFields entry should map to a field specification array.",
E_USER_ERROR
);
}
}
return isset($items) ? array_merge($newItems, $items) : $newItems;
}
}
}
/**
* Return information about a many-to-many component.
* The return value is an array of (parentclass, childclass). If $component is null, then all many-many

View File

@ -453,61 +453,87 @@ JS;
* this method.
*/
function getCustomFieldsFor($childData) {
// If the fieldset is passed, use it
if(is_a($this->detailFormFields,"Fieldset")) {
if($this->detailFormFields instanceof Fieldset) {
return $this->detailFormFields;
// Else use the formfields returned from the object via a string method call.
} else {
if(!is_string($this->detailFormFields)) $this->detailFormFields = "getCMSFields";
$functioncall = $this->detailFormFields;
if(!$childData->hasMethod($functioncall)) $functioncall = "getCMSFields";
$fieldsMethod = $this->detailFormFields;
if(!is_string($fieldsMethod)) {
$this->detailFormFields = 'getCMSFields';
$fieldsMethod = 'getCMSFields';
}
return $childData->$functioncall();
if(!$childData->hasMethod($fieldsMethod)) {
$fieldsMethod = 'getCMSFields';
}
$fields = $childData->$fieldsMethod();
}
if(!$this->relationAutoSetting) {
return $fields;
}
$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));
}
}
return $fields;
}
function getFieldsFor($childData) {
// See if our parent class has any many_many relations by this source class
$SNG_parentClass = singleton($this->getParentClass());
$manyManyRelations = $SNG_parentClass->many_many();
$manyManyRelationName = null;
if($manyManyRelations) foreach($manyManyRelations as $relation => $class) {
if($class == $this->sourceClass()) {
$manyManyRelationName = $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() );
$childData->$parentIDName = $this->sourceID();
}
$detailFields = $this->getCustomFieldsFor($childData);
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
$detailFields->removeByName('ID');
// only add childID if we're not adding a record
if($childData->ID) {
$detailFields->push(new HiddenField("ctf[childID]","",$childData->ID));
$detailFields->push(new HiddenField('ctf[childID]', '', $childData->ID));
}
// add a namespaced ID instead thats "converted" by saveComplexTableField()
$detailFields->push(new HiddenField("ctf[ClassName]","",$this->sourceClass()));
$detailFields->push(new HiddenField('ctf[ClassName]', '', $this->sourceClass()));
if($this->getParentClass()) {
$parentIdName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
/*
if(!$parentIdName) {
user_error("ComplexTableField::DetailForm() Cannot automatically
determine 'has-one'-relationship to parent class " . $this->ctf->getParentClass() . ",
please use setParentClass() to set it manually",
E_USER_WARNING);
return;
if($manyManyRelationName && $this->relationAutoSetting) {
$detailFields->push(new HiddenField('ctf[manyManyRelation]', '', $manyManyRelationName));
$detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
$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( $this->relationAutoSetting ) {
$detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
if($this->relationAutoSetting) {
// Hack for model admin: model admin will have included a dropdown for the relation itself
$detailFields->removeByName($parentIdName);
$detailFields->push(new HiddenField("$parentIdName"," ",$this->sourceID()));
$detailFields->push(new HiddenField($parentIdName, '', $this->sourceID()));
}
}
}
@ -579,17 +605,35 @@ JS;
$childData = new $className();
$form->saveInto($childData);
$childData->write();
// Save the many many relationship if it's available
if(isset($data['ctf']['manyManyRelation'])) {
$parentRecord = DataObject::get_by_id($data['ctf']['parentClass'], (int) $data['ctf']['sourceID']);
$relationName = $data['ctf']['manyManyRelation'];
$extraFields = array();
if(isset($data['ctf']['extraFields'])) {
foreach($data['ctf']['extraFields'] as $field => $value) {
$extraFields[$field] = $value;
}
}
$componentSet = $parentRecord->getManyManyComponents($relationName);
$componentSet->add($childData, $extraFields);
}
$closeLink = sprintf(
'<small><a href="' . $_SERVER['HTTP_REFERER'] . '" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
_t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
);
$message = sprintf(
_t('ComplexTableField.SUCCESSADD', 'Added %s %s %s'),
$childData->singular_name(),
'<a href="' . $this->Link() . '/item/' . $childData->ID . '/edit">' . $childData->Title . '</a>',
$closeLink
);
$form->sessionMessage($message, 'good');
Director::redirectBack();

View File

@ -587,6 +587,23 @@ class DataObjectTest extends SapphireTest {
// @todo test has_one relations
// @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)'
));
}
}
@ -617,6 +634,12 @@ class DataObjectTest_Team extends DataObject implements TestOnly {
'Players' => 'DataObjectTest_Player'
);
static $many_many_extraFields = array(
'Players' => array(
'Position' => 'Varchar(100)'
)
);
function getDynamicField() {
return 'dynamicfield';
}