diff --git a/dev/install/install.php5 b/dev/install/install.php5 index ff1b926a0..3b6cc4e2d 100644 --- a/dev/install/install.php5 +++ b/dev/install/install.php5 @@ -43,6 +43,7 @@ if($envFileExists) { } include_once('sapphire/core/Object.php'); +include_once('sapphire/view/TemplateGlobalProvider.php'); include_once('sapphire/i18n/i18n.php'); include_once('sapphire/dev/install/DatabaseConfigurationHelper.php'); include_once('sapphire/dev/install/DatabaseAdapterRegistry.php'); diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index 3de4eb5c0..f58e37108 100755 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -384,8 +384,11 @@ class GridField extends FormField { if($total > 0) { $rows = array(); foreach($list as $idx => $record) { + if(!$record->canView()) { + continue; + } $rowContent = ''; - foreach($columns as $column) { + foreach($this->getColumns() as $column) { $colContent = $this->getColumnContent($record, $column); // A return value of null means this columns should be skipped altogether. if($colContent === null) continue; @@ -409,8 +412,10 @@ class GridField extends FormField { $rows[] = $row; } $content['body'] = implode("\n", $rows); - - } else { //display a message when the grid field is empty + } + + // Display a message when the grid field is empty + if(!(isset($content['body']) && $content['body'])) { $content['body'] = $this->createTag( 'tr', array("class" => 'ss-gridfield-item ss-gridfield-no-items'), diff --git a/forms/gridfield/GridFieldDeleteAction.php b/forms/gridfield/GridFieldDeleteAction.php index f51ec62f8..f2837fed4 100644 --- a/forms/gridfield/GridFieldDeleteAction.php +++ b/forms/gridfield/GridFieldDeleteAction.php @@ -69,6 +69,9 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio * @return string - the HTML for the column */ public function getColumnContent($gridField, $record, $columnName) { + if(!$record->canDelete()) { + return; + } $field = Object::create('GridField_FormAction', $gridField, 'DeleteRecord'.$record->ID, @@ -96,6 +99,9 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio $id = $arguments['RecordID']; // Always deletes a record. Use GridFieldRelationDelete to detach it from the current relationship. $item = $gridField->getList()->byID($id); + if(!$item->canDelete()) { + throw new ValidationException(_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"),0); + } if(!$item) return; $item->delete(); } diff --git a/forms/gridfield/GridFieldEditAction.php b/forms/gridfield/GridFieldEditAction.php index af4dbb597..0425f4f51 100644 --- a/forms/gridfield/GridFieldEditAction.php +++ b/forms/gridfield/GridFieldEditAction.php @@ -72,6 +72,9 @@ class GridFieldEditAction implements GridField_ColumnProvider { * @return string - the HTML for the column */ public function getColumnContent($gridField, $record, $columnName) { + if(!$record->canEdit()){ + return; + } $data = new ArrayData(array( 'Link' => Controller::join_links($gridField->Link('item'), $record->ID, 'edit') )); diff --git a/forms/gridfield/GridFieldRelationAdd.php b/forms/gridfield/GridFieldRelationAdd.php index 5c4e99ccc..d6e4bf2f2 100755 --- a/forms/gridfield/GridFieldRelationAdd.php +++ b/forms/gridfield/GridFieldRelationAdd.php @@ -237,7 +237,7 @@ class GridFieldRelationAdd implements GridField_HTMLProvider, GridField_ActionPr return $this->placeholderText; } else { $labels = array(); - foreach($searchFields as $searchField) { + if($searchFields) foreach($searchFields as $searchField) { $label = singleton($dataClass)->fieldLabel($searchField); if($label) $labels[] = $label; } diff --git a/forms/gridfield/GridFieldTitle.php b/forms/gridfield/GridFieldTitle.php index 8eac6ea9f..31bceb52d 100644 --- a/forms/gridfield/GridFieldTitle.php +++ b/forms/gridfield/GridFieldTitle.php @@ -16,9 +16,20 @@ */ class GridFieldTitle implements GridField_HTMLProvider { + /** + * + * @var bool + */ protected $newEnabled = true; + + /** + * + * @var type + */ + protected $gridField = null; - function getHTMLFragments($gridField) { + public function getHTMLFragments( $gridField) { + $this->gridField = $gridField; return array( 'header' => $gridField->customise(array( 'NewLink' => Controller::join_links($gridField->Link('item'), 'new'), @@ -31,7 +42,14 @@ class GridFieldTitle implements GridField_HTMLProvider { * Returns whether or not the "add new" button will appear when rendering this DataGrid title * @return bool */ - function getNewEnabled() { + public function getNewEnabled() { + if($this->gridField) { + $model = singleton($this->gridField->getModelClass()); + if(!$model->canCreate()) { + return false; + } + } + return $this->newEnabled; } @@ -39,9 +57,7 @@ class GridFieldTitle implements GridField_HTMLProvider { * Enable or disable the "add new" button to add new DataGrid object instances * @param $enabled */ - function setNewEnabled($enabled) { + public function setNewEnabled($enabled) { $this->newEnabled = $enabled; } } - -?> \ No newline at end of file diff --git a/tests/forms/GridFieldTest.php b/tests/forms/GridFieldTest.php index 24d0543e3..5617bda17 100644 --- a/tests/forms/GridFieldTest.php +++ b/tests/forms/GridFieldTest.php @@ -417,6 +417,34 @@ class GridFieldTest extends SapphireTest { $this->setExpectedException('LogicException'); $field->FieldHolder(); } + + /** + * @covers GridField::FieldHolder + */ + public function testCanViewOnlyOddIDs() { + $this->logInWithPermission(); + $list = new ArrayList(array( + new GridFieldTest_Permissions(array("ID" => 1, "Email" => "ongi.schwimmer@example.org", 'Name' => 'Ongi Schwimmer')), + new GridFieldTest_Permissions(array("ID" => 2, "Email" => "klaus.lozenge@example.org", 'Name' => 'Klaus Lozenge')), + new GridFieldTest_Permissions(array("ID" => 3, "Email" => "otto.fischer@example.org", 'Name' => 'Otto Fischer')) + )); + + $config = new GridFieldConfig(); + $config->addComponent(new GridFieldDefaultColumns()); + $obj = new GridField('testfield', 'testfield', $list, $config); + $form = new Form(new Controller(), 'mockform', new FieldList(array($obj)), new FieldList()); + $content = new CSSContentParser($obj->FieldHolder()); + + $members = $content->getBySelector('.ss-gridfield-item tr'); + + $this->assertEquals(2, count($members)); + + $this->assertEquals((string)$members[0]->td[0], 'Ongi Schwimmer', 'First object Name should be Ongi Schwimmer'); + $this->assertEquals((string)$members[0]->td[1], 'ongi.schwimmer@example.org', 'First object Email should be ongi.schwimmer@example.org'); + + $this->assertEquals((string)$members[1]->td[0], 'Otto Fischer', 'Second object Name should be Otto Fischer'); + $this->assertEquals((string)$members[1]->td[1], 'otto.fischer@example.org', 'Second object Email should be otto.fischer@example.org'); + } } class GridFieldTest_Component implements GridField_ColumnProvider, GridField_ActionProvider, TestOnly{ @@ -473,7 +501,6 @@ class GridFieldTest_Player extends DataObject implements TestOnly { static $belongs_many_many = array('Teams' => 'GridFieldTest_Team'); } - class GridFieldTest_HTMLFragments implements GridField_HTMLProvider, TestOnly{ function __construct($fragments) { $this->fragments = $fragments; @@ -482,4 +509,22 @@ class GridFieldTest_HTMLFragments implements GridField_HTMLProvider, TestOnly{ function getHTMLFragments($gridField) { return $this->fragments; } +} + +class GridFieldTest_Permissions extends DataObject implements TestOnly { + public static $db = array( + 'Name' => 'Varchar', + 'Email' => 'Varchar', + ); + + public static $summary_fields = array( + 'Name', + 'Email' + ); + + public function canView($member = null) { + // Only records with odd numbers are viewable + if(!($this->ID % 2)){ return false; } + return true; + } } \ No newline at end of file diff --git a/tests/forms/gridfield/GridFieldActionTest.yml b/tests/forms/gridfield/GridFieldActionTest.yml new file mode 100644 index 000000000..20736a164 --- /dev/null +++ b/tests/forms/gridfield/GridFieldActionTest.yml @@ -0,0 +1,18 @@ +GridFieldAction_Delete_Team: + team1: + Name: Team 1 + City: Cologne + team2: + Name: Team 2 + City: Wellington + team3: + Name: Team 3 + City: Auckland + +GridFieldAction_Edit_Team: + team1: + Name: Team 1 + City: Cologne + team2: + Name: Team 2 + City: Wellington \ No newline at end of file diff --git a/tests/forms/gridfield/GridFieldDeleteActionTest.php b/tests/forms/gridfield/GridFieldDeleteActionTest.php new file mode 100644 index 000000000..750123207 --- /dev/null +++ b/tests/forms/gridfield/GridFieldDeleteActionTest.php @@ -0,0 +1,78 @@ +list = new DataList('GridFieldAction_Delete_Team'); + $config = GridFieldConfig::create()->addComponent(new GridFieldDeleteAction()); + $this->gridField = new GridField('testfield', 'testfield', $this->list, $config); + $this->form = new Form(new Controller(), 'mockform', new FieldList(array($this->gridField)), new FieldList()); + } + + public function testDontShowDeleteButtons() { + if(Member::currentUser()) { Member::currentUser()->logOut(); } + $content = new CSSContentParser($this->gridField->FieldHolder()); + // Check that there are content + $this->assertEquals(4, count($content->getBySelector('.ss-gridfield-item'))); + // Make sure that there are no delete buttons + $this->assertEmpty($content->getBySelector('.gridfield-button-delete'), 'Delete buttons should not show when not logged in.'); + } + + public function testShowDeleteButtonsWithAdminPermission() { + $this->logInWithPermission('ADMIN'); + $content = new CSSContentParser($this->gridField->FieldHolder()); + $deleteButtons = $content->getBySelector('.gridfield-button-delete'); + $this->assertEquals(3, count($deleteButtons), 'Delete buttons should show when logged in.'); + } + + public function testDeleteActionWithoutCorrectPermission() { + if(Member::currentUser()) { Member::currentUser()->logOut(); } + $this->setExpectedException('ValidationException'); + + $stateID = 'testGridStateActionField'; + Session::set($stateID, array('grid'=>'', 'actionName'=>'deleterecord','args'=>array('RecordID'=>1))); + $request = new SS_HTTPRequest('POST', 'url', array(), array('action_gridFieldAlterAction?StateID='.$stateID=>true)); + $this->gridField->gridFieldAlterAction(array('StateID'=>$stateID), $this->form, $request); + $this->assertEquals(3, $this->list->count(), 'User should\'t be able to delete records without correct permissions.'); + } + + public function testDeleteActionWithAdminPermission() { + $this->logInWithPermission('ADMIN'); + $stateID = 'testGridStateActionField'; + Session::set($stateID, array('grid'=>'', 'actionName'=>'deleterecord','args'=>array('RecordID'=>1))); + $request = new SS_HTTPRequest('POST', 'url', array(), array('action_gridFieldAlterAction?StateID='.$stateID=>true)); + $this->gridField->gridFieldAlterAction(array('StateID'=>$stateID), $this->form, $request); + $this->assertEquals(2, $this->list->count(), 'User should be able to delete records with ADMIN permission.'); + } +} + +class GridFieldAction_Delete_Team extends DataObject implements TestOnly { + static $db = array( + 'Name' => 'Varchar', + 'City' => 'Varchar' + ); + + public function canView($member = null) { + return true; + } + + public function canDelete($member = null) { + return parent::canDelete($member); + } +} \ No newline at end of file diff --git a/tests/forms/gridfield/GridFieldEditActionTest.php b/tests/forms/gridfield/GridFieldEditActionTest.php new file mode 100644 index 000000000..07ef784b4 --- /dev/null +++ b/tests/forms/gridfield/GridFieldEditActionTest.php @@ -0,0 +1,55 @@ +list = new DataList('GridFieldAction_Edit_Team'); + $config = GridFieldConfig::create()->addComponent(new GridFieldEditAction()); + $this->gridField = new GridField('testfield', 'testfield', $this->list, $config); + $this->form = new Form(new Controller(), 'mockform', new FieldList(array($this->gridField)), new FieldList()); + } + + public function testDontShowEditLinks() { + if(Member::currentUser()) { Member::currentUser()->logOut(); } + + $content = new CSSContentParser($this->gridField->FieldHolder()); + // Check that there are content + $this->assertEquals(3, count($content->getBySelector('.ss-gridfield-item'))); + // Make sure that there are no edit links + $this->assertEmpty($content->getBySelector('.edit-link'), 'Edit links should not show when not logged in.'); + } + + public function testShowEditLinksWithAdminPermission() { + $this->logInWithPermission('ADMIN'); + $content = new CSSContentParser($this->gridField->FieldHolder()); + $editLinks = $content->getBySelector('.edit-link'); + $this->assertEquals(2, count($editLinks), 'Edit links should show when logged in.'); + } +} + +class GridFieldAction_Edit_Team extends DataObject implements TestOnly { + static $db = array( + 'Name' => 'Varchar', + 'City' => 'Varchar' + ); + + public function canView($member = null) { + return true; + } +} \ No newline at end of file diff --git a/tests/forms/gridfield/GridFieldPopupFormsTest.php b/tests/forms/gridfield/GridFieldPopupFormsTest.php index b3dd04dad..178a6b335 100644 --- a/tests/forms/gridfield/GridFieldPopupFormsTest.php +++ b/tests/forms/gridfield/GridFieldPopupFormsTest.php @@ -5,11 +5,13 @@ class GridFieldPopupFormsTest extends FunctionalTest { protected $extraDataObjects = array( 'GridFieldPopupFormsTest_Person', - 'GridFieldPopupFormsTest_PeopleGroup' + 'GridFieldPopupFormsTest_PeopleGroup', + 'GridFieldPopupFormsTest_Category', ); function testAddForm() { + $this->logInWithPermission('ADMIN'); $group = DataList::create('GridFieldPopupFormsTest_PeopleGroup') ->filter('Name', 'My Group') ->First(); @@ -45,6 +47,7 @@ class GridFieldPopupFormsTest extends FunctionalTest { } function testEditForm() { + $this->logInWithPermission('ADMIN'); $group = DataList::create('GridFieldPopupFormsTest_PeopleGroup') ->filter('Name', 'My Group') ->First(); @@ -80,6 +83,47 @@ class GridFieldPopupFormsTest extends FunctionalTest { $firstperson = $group->People()->First(); $this->assertEquals($firstperson->Surname, 'Baggins'); } + + function testNestedEditForm() { + $this->logInWithPermission('ADMIN'); + + $group = $this->objFromFixture('GridFieldPopupFormsTest_PeopleGroup', 'group'); + $person = $group->People()->First(); + $category = $person->Categories()->First(); + + // Get first form (GridField managing PeopleGroup) + $response = $this->get('GridFieldPopupFormsTest_GroupController'); + $this->assertFalse($response->isError()); + $parser = new CSSContentParser($response->getBody()); + + $groupEditLink = $parser->getByXpath('//tr[contains(@class, "ss-gridfield-item") and contains(@data-id, "' . $group->ID . '")]//a'); + $this->assertEquals( + 'GridFieldPopupFormsTest_GroupController/Form/field/testfield/item/1/edit', + (string)$groupEditLink[0]['href'] + ); + + // Get second level form (GridField managing Person) + $response = $this->get((string)$groupEditLink[0]['href']); + $this->assertFalse($response->isError()); + $parser = new CSSContentParser($response->getBody()); + $personEditLink = $parser->getByXpath('//fieldset[@id="Form_ItemEditForm_People"]//tr[contains(@class, "ss-gridfield-item") and contains(@data-id, "' . $person->ID . '")]//a'); + $this->assertEquals( + 'GridFieldPopupFormsTest_GroupController/Form/field/testfield/item/1/ItemEditForm/field/People/item/1/edit', + (string)$personEditLink[0]['href'] + ); + + // Get third level form (GridField managing Category) + $response = $this->get((string)$personEditLink[0]['href']); + $this->assertFalse($response->isError()); + $parser = new CSSContentParser($response->getBody()); + $categoryEditLink = $parser->getByXpath('//fieldset[@id="Form_ItemEditForm_Categories"]//tr[contains(@class, "ss-gridfield-item") and contains(@data-id, "' . $category->ID . '")]//a'); + + // Get fourth level form (Category detail view) + $this->assertEquals( + 'GridFieldPopupFormsTest_GroupController/Form/field/testfield/item/1/ItemEditForm/field/People/item/1/ItemEditForm/field/Categories/item/1/edit', + (string)$categoryEditLink[0]['href'] + ); + } } class GridFieldPopupFormsTest_Person extends DataObject implements TestOnly { @@ -91,6 +135,22 @@ class GridFieldPopupFormsTest_Person extends DataObject implements TestOnly { static $has_one = array( 'Group' => 'GridFieldPopupFormsTest_PeopleGroup' ); + + static $many_many = array( + 'Categories' => 'GridFieldPopupFormsTest_Category' + ); + + function getCMSFields() { + $fields = parent::getCMSFields(); + // TODO No longer necessary once FormScaffolder uses GridField + $fields->replaceField('Categories', + Object::create('GridField', 'Categories', 'Categories', + $this->Categories(), + GridFieldConfig_RelationEditor::create() + ) + ); + return $fields; + } } class GridFieldPopupFormsTest_PeopleGroup extends DataObject implements TestOnly { @@ -101,6 +161,40 @@ class GridFieldPopupFormsTest_PeopleGroup extends DataObject implements TestOnly static $has_many = array( 'People' => 'GridFieldPopupFormsTest_Person' ); + + function getCMSFields() { + $fields = parent::getCMSFields(); + // TODO No longer necessary once FormScaffolder uses GridField + $fields->replaceField('People', + Object::create('GridField', 'People', 'People', + $this->People(), + GridFieldConfig_RelationEditor::create() + ) + ); + return $fields; + } +} + +class GridFieldPopupFormsTest_Category extends DataObject implements TestOnly { + static $db = array( + 'Name' => 'Varchar' + ); + + static $belongs_many_many = array( + 'People' => 'GridFieldPopupFormsTest_Person' + ); + + function getCMSFields() { + $fields = parent::getCMSFields(); + // TODO No longer necessary once FormScaffolder uses GridField + $fields->replaceField('People', + Object::create('GridField', 'People', 'People', + $this->People(), + GridFieldConfig_RelationEditor::create() + ) + ); + return $fields; + } } class GridFieldPopupFormsTest_Controller extends Controller implements TestOnly { @@ -118,4 +212,13 @@ class GridFieldPopupFormsTest_Controller extends Controller implements TestOnly } } -?> \ No newline at end of file +class GridFieldPopupFormsTest_GroupController extends Controller implements TestOnly { + protected $template = 'BlankPage'; + + function Form() { + $field = new GridField('testfield', 'testfield', DataList::create('GridFieldPopupFormsTest_PeopleGroup')); + $field->getConfig()->addComponent($gridFieldForm = new GridFieldPopupForms($this, 'Form')); + $field->getConfig()->addComponent(new GridFieldEditAction()); + return new Form($this, 'Form', new FieldList($field), new FieldList()); + } +} diff --git a/tests/forms/gridfield/GridFieldPopupFormsTest.yml b/tests/forms/gridfield/GridFieldPopupFormsTest.yml index 9090d706b..30bc00f2f 100644 --- a/tests/forms/gridfield/GridFieldPopupFormsTest.yml +++ b/tests/forms/gridfield/GridFieldPopupFormsTest.yml @@ -10,3 +10,8 @@ GridFieldPopupFormsTest_PeopleGroup: group: Name: My Group People: =>GridFieldPopupFormsTest_Person.joe,=>GridFieldPopupFormsTest_Person.jane + +GridFieldPopupFormsTest_Category: + category1: + Name: Category 1 + People: =>GridFieldPopupFormsTest_Person.joe,=>GridFieldPopupFormsTest_Person.jane \ No newline at end of file diff --git a/tests/forms/gridfield/GridFieldRelationAddTest.php b/tests/forms/gridfield/GridFieldRelationAddTest.php index 618fde08b..82d6fbc7e 100644 --- a/tests/forms/gridfield/GridFieldRelationAddTest.php +++ b/tests/forms/gridfield/GridFieldRelationAddTest.php @@ -37,6 +37,7 @@ class GridFieldRelationAddTest extends FunctionalTest { } function testAdd() { + $this->logInWithPermission('ADMIN'); $team1 = $this->objFromFixture('GridFieldTest_Team', 'team1'); $team2 = $this->objFromFixture('GridFieldTest_Team', 'team2'); diff --git a/tests/forms/gridfield/GridFieldTitleTest.php b/tests/forms/gridfield/GridFieldTitleTest.php index bf9015ee9..11ab670d2 100644 --- a/tests/forms/gridfield/GridFieldTitleTest.php +++ b/tests/forms/gridfield/GridFieldTitleTest.php @@ -3,6 +3,7 @@ class GridFieldTitleTest extends SapphireTest { public function testGridTitleAddNewEnabled() { + $this->logInWithPermission('ADMIN'); //construct a fake form field to render out the grid field within it $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldTitle()); @@ -17,6 +18,7 @@ class GridFieldTitleTest extends SapphireTest { } public function testGridTitleAddNewDisabled() { + $this->logInWithPermission('ADMIN'); //construct a fake form field to render out the grid field within it $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldTitle()); @@ -29,5 +31,16 @@ class GridFieldTitleTest extends SapphireTest { $html = $form->forTemplate(); $this->assertNotContains('data-icon="add"', $html,"HTML does not contain the 'add new' button"); } -} -?> \ No newline at end of file + + public function testGridTitleAddNewWithoutPermission() { + if(Member::currentUser()) { Member::currentUser()->logOut(); } + $config = new GridFieldConfig(); + $config->addComponent($titleField = new GridFieldTitle()); + $grid = new GridField('TestField', 'Test Field', new DataList('Company'),$config); + $fields = new FieldList(new TabSet("Root",$tabMain = new Tab('Main',$grid))); + $form = new Form(Controller::curr(), "TestForm", $fields, new FieldList()); + + $html = $form->forTemplate(); + $this->assertNotContains('data-icon="add"', $html, "HTML should not contain the 'add new' button"); + } +} \ No newline at end of file