(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@60231 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-08-09 06:29:50 +00:00
parent 0362276b67
commit 4ec93162a0
9 changed files with 91 additions and 22 deletions

View File

@ -1061,7 +1061,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* Add the scaffold-generated relation fields to the given field set * Add the scaffold-generated relation fields to the given field set
*/ */
protected function addScaffoldRelationFields($fieldSet) { protected function addScaffoldRelationFields($fieldSet) {
if ($this->has_many() || $this->_many_many()) { if ($this->has_many() || $this->many_many()) {
$oldFields = $fieldSet; $oldFields = $fieldSet;
$fieldSet = new FieldSet( $fieldSet = new FieldSet(
new TabSet("Root", new Tab("Main")) new TabSet("Root", new Tab("Main"))
@ -1070,22 +1070,42 @@ class DataObject extends ViewableData implements DataObjectInterface {
$fieldSet->addFieldToTab("Root.Main", $field); $fieldSet->addFieldToTab("Root.Main", $field);
} }
} }
if($this->has_many()) { if($this->has_many()) {
// Add each relation as a separate tab // Add each relation as a separate tab
foreach($this->has_many() as $relationship => $component) { foreach($this->has_many() as $relationship => $component) {
$relationshipFields = singleton($component)->summary_fields(); $relationshipFields = singleton($component)->summaryFields();
$foreignKey = $this->getComponentJoinField($relationship); $foreignKey = $this->getComponentJoinField($relationship);
$fieldSet->addFieldToTab("Root.$relationship", new ComplexTableField($this, $relationship, $component, $relationshipFields, "getCMSFields", "$foreignKey = $this->ID")); $ctf = new ComplexTableField(
$this,
$relationship,
$component,
$relationshipFields,
"getCMSFields",
"$foreignKey = $this->ID"
);
$ctf->setPermissions(TableListField::permissions_for_object($component));
$fieldSet->addFieldToTab("Root.$relationship", $ctf);
} }
} }
if ($this->many_many()) { if ($this->many_many()) {
foreach($this->many_many() as $relationship => $component) { foreach($this->many_many() as $relationship => $component) {
$relationshipFields = singleton($component)->summary_fields(); $relationshipFields = singleton($component)->summaryFields();
$filterWhere = $this->getManyManyFilter($relationship, $component); $filterWhere = $this->getManyManyFilter($relationship, $component);
$filterJoin = $this->getManyManyJoin($relationship, $component); $filterJoin = $this->getManyManyJoin($relationship, $component);
$tableField = new ComplexTableField($this, $relationship, $component, $relationshipFields, "getCMSFields", $filterWhere, '', $filterJoin); $ctf = new ComplexTableField(
$tableField->popupClass = "ScaffoldingComplexTableField_Popup"; $this,
$fieldSet->addFieldToTab("Root.$relationship", $tableField); $relationship,
$component,
$relationshipFields,
"getCMSFields",
$filterWhere,
'',
$filterJoin
);
$ctf->setPermissions(TableListField::permissions_for_object($component));
$ctf->popupClass = "ScaffoldingComplexTableField_Popup";
$fieldSet->addFieldToTab("Root.$relationship", $ctf);
} }
} }
return $fieldSet; return $fieldSet;
@ -1319,21 +1339,28 @@ class DataObject extends ViewableData implements DataObjectInterface {
* based on default {@link FormField} mapping in {@link DBField::scaffoldFormField()} * based on default {@link FormField} mapping in {@link DBField::scaffoldFormField()}
* *
* @uses {@link DBField::scaffoldFormField()} * @uses {@link DBField::scaffoldFormField()}
* @param array $fieldClasses Optional mapping of fieldnames to subclasses of {@link DBField}
* @return FieldSet * @return FieldSet
*/ */
public function scaffoldFormFields() { public function scaffoldFormFields($fieldClasses = null) {
$fields = new FieldSet(); $fields = new FieldSet();
$fields->push(new HeaderField($this->singular_name())); $fields->push(new HeaderField($this->singular_name()));
foreach($this->db() as $fieldName => $fieldType) { foreach($this->db() as $fieldName => $fieldType) {
// @todo Pass localized title // @todo Pass localized title
// commented out, to be less of a pain in the ass // commented out, to be less of a pain in the ass
//$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField()); //$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField());
$fields->push($this->dbObject($fieldName)->scaffoldFormField()); if(isset($fieldClasses[$fieldName])) {
$fieldClass = $fieldClasses[$fieldName];
$fieldObject = new $fieldClass($fieldName);
} else {
$fieldObject = $this->dbObject($fieldName)->scaffoldFormField();
}
$fields->push($fieldObject);
} }
foreach($this->has_one() as $relationship => $component) { foreach($this->has_one() as $relationship => $component) {
$model = singleton($component); $model = singleton($component);
$records = DataObject::get($component); $records = DataObject::get($component);
$collect = ($model->hasMethod('customSelectOption')) ? 'customSelectOption' : current($model->summary_fields()); $collect = ($model->hasMethod('customSelectOption')) ? 'customSelectOption' : current($model->summaryFields());
$options = $records ? $records->filter_map('ID', $collect) : array(); $options = $records ? $records->filter_map('ID', $collect) : array();
$fields->push(new DropdownField($relationship.'ID', $relationship, $options)); $fields->push(new DropdownField($relationship.'ID', $relationship, $options));
} }
@ -2227,7 +2254,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
public function searchable_fields() { public function searchable_fields() {
$fields = $this->stat('searchable_fields'); $fields = $this->stat('searchable_fields');
if (!$fields) { if (!$fields) {
$fields = array_fill_keys(array_keys($this->summary_fields()), 'TextField'); $fields = array_fill_keys(array_keys($this->summaryFields()), 'TextField');
} }
return $fields; return $fields;
} }
@ -2239,10 +2266,18 @@ class DataObject extends ViewableData implements DataObjectInterface {
* *
* @return array * @return array
*/ */
public function summary_fields() { public function summaryFields() {
$fields = $this->stat('summary_fields'); $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) { if (!$fields) {
$fields = array(); $fields = array();
// try to scaffold a couple of usual suspects
if ($this->hasField('Name')) $fields['Name'] = 'Name'; if ($this->hasField('Name')) $fields['Name'] = 'Name';
if ($this->hasField('Title')) $fields['Title'] = 'Title'; if ($this->hasField('Title')) $fields['Title'] = 'Title';
if ($this->hasField('Description')) $fields['Description'] = 'Description'; if ($this->hasField('Description')) $fields['Description'] = 'Description';

View File

@ -66,7 +66,7 @@ class HTMLText extends Text {
} }
public function scaffoldFormField($title = null) { public function scaffoldFormField($title = null) {
return new HTMLEditorField($this->name, $title); return new HtmlEditorField($this->name, $title);
} }
public function scaffoldSearchField($title = null) { public function scaffoldSearchField($title = null) {

View File

@ -12,6 +12,7 @@ class Varchar extends DBField {
$this->size = $size ? $size : 50; $this->size = $size ? $size : 50;
parent::__construct($name); parent::__construct($name);
} }
function requireField() { function requireField() {
DB::requireField($this->tableName, $this->name, "varchar($this->size) character set utf8 collate utf8_general_ci"); DB::requireField($this->tableName, $this->name, "varchar($this->size) character set utf8 collate utf8_general_ci");
} }

View File

@ -561,12 +561,12 @@ interface DebugReporter {
/** /**
* Render HTML markup for the header/top segment of debug report. * Render HTML markup for the header/top segment of debug report.
*/ */
abstract function writeHeader(); function writeHeader();
/** /**
* Render HTML markup for the footer and closing tags of debug report. * Render HTML markup for the footer and closing tags of debug report.
*/ */
abstract function writeFooter(); function writeFooter();
} }
@ -576,7 +576,7 @@ interface DebugReporter {
*/ */
class SapphireDebugReporter implements DebugReporter { class SapphireDebugReporter implements DebugReporter {
function writeHeader() { public function writeHeader() {
echo '<!DOCTYPE html><html><head><title>'. $_SERVER['REQUEST_METHOD'] . ' ' .$_SERVER['REQUEST_URI'] .'</title>'; echo '<!DOCTYPE html><html><head><title>'. $_SERVER['REQUEST_METHOD'] . ' ' .$_SERVER['REQUEST_URI'] .'</title>';
echo '<style type="text/css">'; echo '<style type="text/css">';
echo 'body { background-color:#eee; margin:0; padding:0; font-family:Helvetica,Arial,sans-serif; }'; echo 'body { background-color:#eee; margin:0; padding:0; font-family:Helvetica,Arial,sans-serif; }';
@ -595,7 +595,7 @@ class SapphireDebugReporter implements DebugReporter {
echo '<div class="header"><img src="'. Director::absoluteBaseURL() .'cms/images/mainmenu/logo.gif" width="26" height="23"></div>'; echo '<div class="header"><img src="'. Director::absoluteBaseURL() .'cms/images/mainmenu/logo.gif" width="26" height="23"></div>';
} }
function writeFooter() { public function writeFooter() {
echo "</body></html>"; echo "</body></html>";
} }

View File

@ -47,7 +47,7 @@ class TestRunner extends Controller {
function init() { function init() {
parent::init(); parent::init();
if (!self::$default_reporter) self::set_reporter('DebugView'); if (!self::$default_reporter) self::set_reporter('SapphireDebugReporter');
} }
/** /**

View File

@ -896,7 +896,7 @@ class ScaffoldingComplexTableField_Popup extends Form {
protected $dataObject; protected $dataObject;
public static $allowed_actions = array( public static $allowed_actions = array(
'filter', 'record', 'httpSubmission', 'handleAction' 'filter', 'record', 'httpSubmission', 'handleAction', 'handleField'
); );
function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) { function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {

View File

@ -291,6 +291,8 @@ JS
$SQL_limit = ($this->pageSize) ? "{$this->pageSize}" : "0"; $SQL_limit = ($this->pageSize) ? "{$this->pageSize}" : "0";
if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) { if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
$SQL_start = (isset($_REQUEST['ctf'][$this->Name()]['start'])) ? intval($_REQUEST['ctf'][$this->Name()]['start']) : "0"; $SQL_start = (isset($_REQUEST['ctf'][$this->Name()]['start'])) ? intval($_REQUEST['ctf'][$this->Name()]['start']) : "0";
} else {
$SQL_start = 0;
} }
if(isset($this->customSourceItems)) { if(isset($this->customSourceItems)) {
if($this->customSourceItems) { if($this->customSourceItems) {
@ -929,6 +931,29 @@ JS
} }
return $idField->Value(); return $idField->Value();
} }
/**
* Helper method to determine permissions for a scaffolded
* TableListField (or subclasses) - currently used in {@link ModelAdmin} and {@link DataObject->scaffoldFormFields()}.
* Returns true for each permission that doesn't have an explicit getter.
*
* @todo Temporary method, implement directly in FormField subclasses with object-level permissions.
*
* @param string $class
* @param numeric $id
* @return array
*/
public static function permissions_for_object($class, $id = null) {
$permissions = array();
$obj = ($id) ? DataObject::get_by_id($class, $id) : singleton($class);
if(!$obj->hasMethod('canView') || $obj->canView()) $permissions[] = 'show';
if(!$obj->hasMethod('canEdit') || $obj->canEdit()) $permissions[] = 'edit';
if(!$obj->hasMethod('canDelete') || $obj->canDelete()) $permissions[] = 'delete';
if(!$obj->hasMethod('canCreate') || $obj->canCreate()) $permissions[] = 'add';
return $permissions;
}
@ -1075,6 +1100,7 @@ class TableListField_Item extends ViewableData {
function IsReadOnly() { function IsReadOnly() {
return $this->parent->Can('delete'); return $this->parent->Can('delete');
} }
} }
?> ?>

View File

@ -17,6 +17,9 @@ ComplexTableField.prototype = {
rules['#'+this.id+' table.data tbody td'] = {onclick: this.openPopup.bind(this)}; rules['#'+this.id+' table.data tbody td'] = {onclick: this.openPopup.bind(this)};
Behaviour.register(rules); Behaviour.register(rules);
// HACK If already in a popup, we can't allow add (doesn't save existing relation correctly)
if(window != top) $$('#'+this.id+' table.data a.addlink').each(function(el) {Element.hide(el);});
}, },
/** /**
@ -66,6 +69,10 @@ ComplexTableField.prototype = {
* @param href, table Optional dom object (use for external triggering without an event) * @param href, table Optional dom object (use for external triggering without an event)
*/ */
openPopup: function(e, _popupLink, _table) { openPopup: function(e, _popupLink, _table) {
// If already in a popup, simply open the link instead
// of opening a nested lightwindow
if(window != top) return true;
var el,type; var el,type;
var popupLink = ""; var popupLink = "";
if(_popupLink) { if(_popupLink) {

View File

@ -19,15 +19,15 @@ class SearchContextTest extends SapphireTest {
function testSummaryIncludesDefaultFieldsIfNotDefined() { function testSummaryIncludesDefaultFieldsIfNotDefined() {
$person = singleton('SearchContextTest_Person'); $person = singleton('SearchContextTest_Person');
$this->assertContains('Name', $person->summary_fields()); $this->assertContains('Name', $person->summaryFields());
$book = singleton('SearchContextTest_Book'); $book = singleton('SearchContextTest_Book');
$this->assertContains('Title', $book->summary_fields()); $this->assertContains('Title', $book->summaryFields());
} }
function testAccessDefinedSummaryFields() { function testAccessDefinedSummaryFields() {
$company = singleton('SearchContextTest_Company'); $company = singleton('SearchContextTest_Company');
$this->assertContains('Industry', $company->summary_fields()); $this->assertContains('Industry', $company->summaryFields());
} }
function testPartialMatchUsedByDefaultWhenNotExplicitlySet() { function testPartialMatchUsedByDefaultWhenNotExplicitlySet() {