Merge pull request #172 from silverstripe-scienceninjas/story/ssf-104

API CHANGE Security admin supports adding, removing and searching for members by relations via gridfield
This commit is contained in:
Sam Minnée 2012-01-29 22:12:05 -08:00
commit a4f1fbaac5
15 changed files with 577 additions and 108 deletions

14
admin/code/SecurityAdmin.php Normal file → Executable file
View File

@ -35,11 +35,10 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
public function init() { public function init() {
parent::init(); parent::init();
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/SecurityAdmin.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/SecurityAdmin.js');
} }
function getEditForm($id = null) { public function getEditForm($id = null, $fields = null) {
// TODO Duplicate record fetching (see parent implementation) // TODO Duplicate record fetching (see parent implementation)
if(!$id) $id = $this->currentPageID(); if(!$id) $id = $this->currentPageID();
$form = parent::getEditForm($id); $form = parent::getEditForm($id);
@ -107,12 +106,9 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
* @return FieldList * @return FieldList
*/ */
function RootForm() { function RootForm() {
$memberList = new MemberTableField( $config = new GridFieldConfig_Base(25);
$this, $config->addComponent(new GridFieldPopupForms($this, 'RootForm'));
"Members" $memberList = new GridField('Members', 'All members', DataList::create('Member'), $config);
);
// unset 'inlineadd' permission, we don't want inline addition
$memberList->setPermissions(array('edit', 'delete', 'add'));
$fields = new FieldList( $fields = new FieldList(
$root = new TabSet( $root = new TabSet(
@ -132,7 +128,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
new LiteralField( new LiteralField(
'GroupImportFormIframe', 'GroupImportFormIframe',
sprintf( sprintf(
'<iframe src="%s" id="GroupImportFormIframe" width="100%%" height="400px" border="0"></iframe>', '<iframe src="%s" id="GroupImportFormIframe" width="100%" height="400px" border="0"></iframe>',
$this->Link('groupimport') $this->Link('groupimport')
) )
) )

View File

@ -1,4 +1,5 @@
/** Core styles for the basic GridField form field without any specific style. @package sapphire @subpackage scss @todo Add radial gradient to default delete button state @todo Create SASS mixin-function to simply swap the from/to, to to/from colours in grsdient mixins? */ /** Core styles for the basic GridField form field without any specific style. @package sapphire @subpackage scss @todo Add radial gradient to default delete button state @todo Create SASS mixin-function to simply swap the from/to, to to/from colours in grsdient mixins? */
.cms fieldset.ss-gridfield > div { margin-bottom: 35px; }
.cms table.ss-gridfield.field { box-shadow: none; padding: 0; margin: 20px 0 0 0; border-collapse: separate; border-bottom: 0 none; } .cms table.ss-gridfield.field { box-shadow: none; padding: 0; margin: 20px 0 0 0; border-collapse: separate; border-bottom: 0 none; }
.cms table.ss-gridfield.field thead { color: #1d2224; background: transparent; } .cms table.ss-gridfield.field thead { color: #1d2224; background: transparent; }
.cms table.ss-gridfield.field tbody { background: #FFF; } .cms table.ss-gridfield.field tbody { background: #FFF; }
@ -11,7 +12,7 @@
.cms table.ss-gridfield.field tr:first-child { background: transparent; } .cms table.ss-gridfield.field tr:first-child { background: transparent; }
.cms table.ss-gridfield.field tr.ss-gridfield-even { background: #f2f9fd; } .cms table.ss-gridfield.field tr.ss-gridfield-even { background: #f2f9fd; }
.cms table.ss-gridfield.field tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; } .cms table.ss-gridfield.field tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; }
.cms table.ss-gridfield.field tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 0; border-right: 1px solid #85959C; height: 20px; /* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */ } .cms table.ss-gridfield.field tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 0; border-right: 1px solid #85959C; height: 20px; white-space: nowrap; /* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */ }
.cms table.ss-gridfield.field tr th span { display: block; position: relative; left: 20px; top: -7px; width: 100%; } .cms table.ss-gridfield.field tr th span { display: block; position: relative; left: 20px; top: -7px; width: 100%; }
.cms table.ss-gridfield.field tr th div.fieldgroup, .cms table.ss-gridfield.field tr th div.fieldgroup-field { width: auto; } .cms table.ss-gridfield.field tr th div.fieldgroup, .cms table.ss-gridfield.field tr th div.fieldgroup-field { width: auto; }
.cms table.ss-gridfield.field tr th div.fieldgroup { min-width: 200px; } .cms table.ss-gridfield.field tr th div.fieldgroup { min-width: 200px; }
@ -23,7 +24,7 @@
.cms table.ss-gridfield.field tr th.first { -moz-border-radius-topleft: 7px; -webkit-border-top-left-radius: 7px; -o-border-top-left-radius: 7px; -ms-border-top-left-radius: 7px; -khtml-border-top-left-radius: 7px; border-top-left-radius: 7px; } .cms table.ss-gridfield.field tr th.first { -moz-border-radius-topleft: 7px; -webkit-border-top-left-radius: 7px; -o-border-top-left-radius: 7px; -ms-border-top-left-radius: 7px; -khtml-border-top-left-radius: 7px; border-top-left-radius: 7px; }
.cms table.ss-gridfield.field tr th.last { -moz-border-radius-topright: 7px; -webkit-border-top-right-radius: 7px; -o-border-top-right-radius: 7px; -ms-border-top-right-radius: 7px; -khtml-border-top-right-radius: 7px; border-top-right-radius: 7px; } .cms table.ss-gridfield.field tr th.last { -moz-border-radius-topright: 7px; -webkit-border-top-right-radius: 7px; -o-border-top-right-radius: 7px; -ms-border-top-right-radius: 7px; -khtml-border-top-right-radius: 7px; border-top-right-radius: 7px; }
.cms table.ss-gridfield.field tr th button, .cms table.ss-gridfield.field tr th button:hover { font-size: 12px; margin-left: -0.9em; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .cms table.ss-gridfield.field tr th button, .cms table.ss-gridfield.field tr th button:hover { font-size: 12px; margin-left: -0.9em; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.cms table.ss-gridfield.field tr th button.ss-gridfield-sort, .cms table.ss-gridfield.field tr th button:hover.ss-gridfield-sort { text-align: left; padding: 0; color: #FFF; width: 95%; background: transparent; border: 0 none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; text-shadow: none; } .cms table.ss-gridfield.field tr th button.ss-gridfield-sort, .cms table.ss-gridfield.field tr th button:hover.ss-gridfield-sort { text-align: left; padding: 0; color: #FFF; min-width: 100px; background: transparent; border: 0 none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; text-shadow: none; }
.cms table.ss-gridfield.field tr th button:hover { color: #CCC !important; /* Not sure why IE think it needs this */ } .cms table.ss-gridfield.field tr th button:hover { color: #CCC !important; /* Not sure why IE think it needs this */ }
.cms table.ss-gridfield.field tr th.extra button.ss-ui-button { padding: .3em; line-height: 1; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; position: relative; top: -24px; border: #b1c0c5 solid 10px; border-bottom-width: 0; } .cms table.ss-gridfield.field tr th.extra button.ss-ui-button { padding: .3em; line-height: 1; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; position: relative; top: -24px; border: #b1c0c5 solid 10px; border-bottom-width: 0; }
.cms table.ss-gridfield.field tr th input.ss-gridfield-sort { position: relative; top: -24px; padding: 2px; width: 65%; margin: 0 auto; border: #b1c0c5 solid 10px; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .cms table.ss-gridfield.field tr th input.ss-gridfield-sort { position: relative; top: -24px; padding: 2px; width: 65%; margin: 0 auto; border: #b1c0c5 solid 10px; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }

View File

@ -405,7 +405,7 @@ class Folder extends File {
$config->addComponent(new GridFieldPaginator(10)); $config->addComponent(new GridFieldPaginator(10));
$config->addComponent(new GridFieldAction_Delete()); $config->addComponent(new GridFieldAction_Delete());
$config->addComponent(new GridFieldAction_Edit()); $config->addComponent(new GridFieldAction_Edit());
$config->addComponent($gridFieldForm = new GridFieldPopupForms()); $config->addComponent($gridFieldForm = new GridFieldPopupForms(Controller::curr(), 'EditForm'));
$gridFieldForm->setTemplate('CMSGridFieldPopupForms'); $gridFieldForm->setTemplate('CMSGridFieldPopupForms');
$files = DataList::create('File')->filter('ParentID', $this->ID)->exclude('ClassName', 'Folder'); $files = DataList::create('File')->filter('ParentID', $this->ID)->exclude('ClassName', 'Folder');
$gridField = new GridField('File','Files', $files, $config); $gridField = new GridField('File','Files', $files, $config);

View File

@ -511,7 +511,11 @@ class GridField extends FormField {
$actionName = $stateChange['actionName']; $actionName = $stateChange['actionName'];
$args = isset($stateChange['args']) ? $stateChange['args'] : array(); $args = isset($stateChange['args']) ? $stateChange['args'] : array();
$grid->handleAction($actionName, $args, $data); $html = $grid->handleAction($actionName, $args, $data);
if($html) {
return $html;
}
switch($request->getHeader('X-Get-Fragment')) { switch($request->getHeader('X-Get-Fragment')) {
case 'CurrentField': case 'CurrentField':
@ -539,13 +543,13 @@ class GridField extends FormField {
*/ */
public function handleAction($actionName, $args, $data) { public function handleAction($actionName, $args, $data) {
$actionName = strtolower($actionName); $actionName = strtolower($actionName);
foreach($this->components as $item) { foreach($this->components as $component) {
if(!($item instanceof GridField_ActionProvider)) { if(!($component instanceof GridField_ActionProvider)) {
continue; continue;
} }
if(in_array($actionName, array_map('strtolower', $item->getActions($this)))) { if(in_array($actionName, array_map('strtolower', $component->getActions($this)))) {
return $item->handleAction($this, $actionName, $args, $data); return $component->handleAction($this, $actionName, $args, $data);
} }
} }
throw new InvalidArgumentException("Can't handle action '$actionName'"); throw new InvalidArgumentException("Can't handle action '$actionName'");
@ -564,8 +568,6 @@ class GridField extends FormField {
$this->request = $request; $this->request = $request;
$this->setModel($model); $this->setModel($model);
///
foreach($this->components as $component) { foreach($this->components as $component) {
if(!($component instanceof GridField_URLHandler)) { if(!($component instanceof GridField_URLHandler)) {
continue; continue;

105
forms/gridfield/GridFieldConfig.php Normal file → Executable file
View File

@ -19,24 +19,6 @@ class GridFieldConfig {
*/ */
protected $components = null; protected $components = null;
/**
*
* @var int
*/
protected $checkboxes = null;
/**
*
* @var array
*/
protected $affectors = array();
/**
*
* @var array
*/
protected $decorators = array();
/** /**
* *
*/ */
@ -59,30 +41,69 @@ class GridFieldConfig {
} }
return $this->components; return $this->components;
} }
}
public function setCheckboxes($row=0){
$this->checkboxes = $row; class GridFieldConfig_Base extends GridFieldConfig {
return $this;
/**
*
* @param int $itemsPerPage - How many items per page should show up per page
* @return GridFieldConfig_Base
*/
public static function create($itemsPerPage=25){
return new GridFieldConfig_Base($itemsPerPage=25);
} }
public function getCheckboxes() { /**
return $this->checkboxes; *
} * @param int $itemsPerPage - How many items per page should show up
*/
public function addAffector(GridState_Affector $affector) { public function __construct($itemsPerPage=25) {
$this->affectors[] = $affector; $this->addComponent(new GridFieldSortableHeader());
return $this; $this->addComponent(new GridFieldDefaultColumns());
} $this->addComponent(new GridFieldAction_Edit());
$this->addComponent(new GridFieldPaginator($itemsPerPage));
public function getAffectors() { }
return $this->affectors; }
}
/**
public function addDecorator($decorator) { * This GridFieldConfig bundles a common set of componentes used for displaying
$this->decorators[] = $decorator; * a gridfield with:
} *
* - Relation adding
public function getDecorators() { * - Sortable header
return $this->decorators; * - Default columns
* - Edit links on every item
* - Action for removing relationship
* - Paginator
*
*/
class GridFieldConfig_ManyManyEditor extends GridFieldConfig {
/**
*
* @param string $fieldToSearch - Which field on the object should be searched for
* @param bool $autoSuggest - Show a jquery.ui.autosuggest dropdown field
* @param int $itemsPerPage - How many items per page should show up
* @return GridFieldConfig_ManyManyEditor
*/
public static function create($fieldToSearch, $autoSuggest=true, $itemsPerPage=25){
return new GridFieldConfig_ManyManyEditor($fieldToSearch, $autoSuggest=true, $itemsPerPage=25);
}
/**
*
* @param string $fieldToSearch - Which field on the object should be searched for
* @param bool $autoSuggest - Show a jquery.ui.autosuggest dropdown field
* @param int $itemsPerPage - How many items per page should show up
*/
public function __construct($fieldToSearch, $autoSuggest=true, $itemsPerPage=25) {
$this->addComponent(new GridFieldFilter());
$this->addComponent(new GridFieldRelationAdd($fieldToSearch, $autoSuggest));
$this->addComponent(new GridFieldSortableHeader());
$this->addComponent(new GridFieldDefaultColumns());
$this->addComponent(new GridFieldAction_Edit());
$this->addComponent(new GridFieldRelationDelete());
$this->addComponent(new GridFieldPaginator($itemsPerPage));
} }
} }

View File

@ -9,12 +9,6 @@
*/ */
class GridFieldFilter implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider { class GridFieldFilter implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
/**
*
* @var string
*/
public static $location = 'head';
/** /**
* *
* @param GridField $gridField * @param GridField $gridField

67
forms/gridfield/GridFieldPaginator.php Normal file → Executable file
View File

@ -1,35 +1,57 @@
<?php <?php
/** /**
* GridFieldPaginator decorates the GridFieldPresenter with the possibility to * GridFieldPaginator paginates the gridfields list and adds controlls to the
* paginate the GridField. * bottom of the gridfield.
*
* @see GridField
* *
* @package sapphire * @package sapphire
* @subpackage fields-relational * @subpackage fields-relational
*/ */
class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider { class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
/**
*
* @var int
*/
protected $currentPage = 1; protected $currentPage = 1;
/**
*
* @var int
*/
protected $itemsPerPage = 25; protected $itemsPerPage = 25;
/** /**
* Which template to use for rendering * Which template to use for rendering
* *
* @var string $itemClass * @var string
*/ */
protected $itemClass = 'GridFieldPaginator_Row'; protected $itemClass = 'GridFieldPaginator_Row';
/**
*
* @param int $itemsPerPage - How many items should be displayed per page
*/
public function __construct($itemsPerPage=25) { public function __construct($itemsPerPage=25) {
$this->itemsPerPage = $itemsPerPage; $this->itemsPerPage = $itemsPerPage;
} }
/**
*
* @param GridField $gridField
* @return array
*/
public function getActions($gridField) { public function getActions($gridField) {
return array('paginate'); return array('paginate');
} }
/**
*
* @param GridField $gridField
* @param string $actionName
* @param string $arguments
* @param array $data
* @return void
*/
public function handleAction(GridField $gridField, $actionName, $arguments, $data) { public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
if($actionName !== 'paginate') { if($actionName !== 'paginate') {
return; return;
@ -38,17 +60,12 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
$this->currentPage = $state->currentPage = (int)$arguments; $this->currentPage = $state->currentPage = (int)$arguments;
} }
/** Duck check to see if list support methods we need to paginate */ /**
protected function getListPaginatable(SS_List $list) { *
// If no list yet, not paginatable * @param GridField $gridField
if (!$list) return false; * @param SS_List $dataList
// Check for methods we use * @return SS_List
if(!method_exists($list, 'getRange')) return false; */
if(!method_exists($list, 'limit')) return false;
// Default it true
return true;
}
public function getManipulatedData(GridField $gridField, SS_List $dataList) { public function getManipulatedData(GridField $gridField, SS_List $dataList) {
if(!$this->getListPaginatable($dataList)) { if(!$this->getListPaginatable($dataList)) {
return $dataList; return $dataList;
@ -60,6 +77,11 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
return $dataList->getRange((int)$startRow, (int)$this->itemsPerPage); return $dataList->getRange((int)$startRow, (int)$this->itemsPerPage);
} }
/**
*
* @param GridField $gridField
* @return array
*/
public function getHTMLFragments($gridField) { public function getHTMLFragments($gridField) {
Requirements::javascript(SAPPHIRE_DIR.'/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js'); Requirements::javascript(SAPPHIRE_DIR.'/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(SAPPHIRE_DIR.'/javascript/GridField.js'); Requirements::javascript(SAPPHIRE_DIR.'/javascript/GridField.js');
@ -87,4 +109,15 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
'footer' => $forTemplate->renderWith('GridFieldPaginator_Row', array('Colspan'=>count($gridField->getColumns()))), 'footer' => $forTemplate->renderWith('GridFieldPaginator_Row', array('Colspan'=>count($gridField->getColumns()))),
); );
} }
/** Duck check to see if list support methods we need to paginate */
protected function getListPaginatable(SS_List $list) {
// If no list yet, not paginatable
if (!$list) return false;
// Check for methods we use
if(!method_exists($list, 'getRange')) return false;
if(!method_exists($list, 'limit')) return false;
// Default it true
return true;
}
} }

106
forms/gridfield/GridFieldPopupForms.php Normal file → Executable file
View File

@ -13,16 +13,51 @@ class GridFieldPopupForms implements GridField_URLHandler {
* @var String * @var String
*/ */
protected $template = 'GridFieldItemEditView'; protected $template = 'GridFieldItemEditView';
/**
*
* @var Controller
*/
protected $popupController;
/**
*
* @var string
*/
protected $popupFormName;
function getURLHandlers($gridField) { function getURLHandlers($gridField) {
return array( return array(
'item/$ID' => 'handleItem', 'item/$ID' => 'handleItem',
'autocomplete' => 'handleAutocomplete',
); );
} }
function handleItem($gridField, $request) { /**
* Create a popup component. The two arguments will specify how the popup form's HTML and
* behaviour is created. The given controller will be customised, putting the edit form into the
* template with the given name.
*
* The arguments are experimental API's to support partial content to be passed back to whatever
* controller who wants to display the getCMSFields
*
* @param Controller $popupController The controller object that will be used to render the pop-up forms
* @param string $popupFormName The name of the edit form to place into the pop-up form
*/
public function __construct($popupController, $popupFormName) {
$this->popupController = $popupController;
$this->popupFormName = $popupFormName;
}
/**
*
* @param type $gridField
* @param type $request
* @return GridFieldPopupForm_ItemRequest
*/
public function handleItem($gridField, $request) {
$record = $gridField->getList()->byId($request->param("ID")); $record = $gridField->getList()->byId($request->param("ID"));
$handler = new GridFieldPopupForm_ItemRequest($gridField, $this, $record); $handler = new GridFieldPopupForm_ItemRequest($gridField, $this, $record, $this->popupController, $this->popupFormName);
$handler->setTemplate($this->template); $handler->setTemplate($this->template);
return $handler; return $handler;
} }
@ -44,12 +79,36 @@ class GridFieldPopupForms implements GridField_URLHandler {
class GridFieldPopupForm_ItemRequest extends RequestHandler { class GridFieldPopupForm_ItemRequest extends RequestHandler {
/**
*
* @var GridField
*/
protected $gridField; protected $gridField;
/**
*
* @var GridField_URLHandler
*/
protected $component; protected $component;
/**
*
* @var DataObject
*/
protected $record; protected $record;
/**
*
* @var Controller
*/
protected $popupController;
/**
*
* @var string
*/
protected $popupFormName;
/** /**
* @var String * @var String
*/ */
@ -57,45 +116,66 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler {
static $url_handlers = array( static $url_handlers = array(
'$Action!' => '$Action', '$Action!' => '$Action',
'' => 'index', '' => 'edit',
); );
function __construct($gridField, $component, $record) { /**
*
* @param GridFIeld $gridField
* @param GridField_URLHandler $component
* @param DataObject $record
* @param Controller $popupController
* @param string $popupFormName
*/
public function __construct($gridField, $component, $record, $popupController, $popupFormName) {
$this->gridField = $gridField; $this->gridField = $gridField;
$this->component = $gridField; $this->component = $gridField;
$this->record = $record; $this->record = $record;
$this->popupController = $popupController;
$this->popupFormName = $popupFormName;
parent::__construct(); parent::__construct();
} }
function Link($action = null) { public function Link($action = null) {
return Controller::join_links($this->gridField->Link('item'), $this->record->ID, $action); return Controller::join_links($this->gridField->Link('item'), $this->record->ID, $action);
} }
function edit($request) { function edit($request) {
$controller = $this->gridField->getForm()->Controller(); $controller = $this->popupController;
$return = $this->customise(array( $return = $this->customise(array(
'Backlink' => $controller->Link(), 'Backlink' => $this->gridField->getForm()->Controller()->Link(),
'ItemEditForm' => $this->ItemEditForm($this->gridField, $request), 'ItemEditForm' => $this->ItemEditForm($this->gridField, $request),
))->renderWith($this->template); ))->renderWith($this->template);
if($controller->isAjax()) { if($controller->isAjax()) {
return $return; return $return;
} else { } else {
// If not requested by ajax, we need to render it within the controller context+template // If not requested by ajax, we need to render it within the controller context+template
return $controller->customise(array( return $controller->customise(array(
$this->gridField->getForm()->Name() => $return, $this->popupFormName => $return,
)); ));
} }
} }
/**
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
* popupFormName, however this is an experimental API and may change.
*
* In the future, we will probably need to come up with a tigher object representing a partially
* complete controller with gaps for extra functionality. This, for example, would be a better way
* of letting Security/login put its log-in form inside a UI specified elsewhere.
*
* @return Form
*/
function ItemEditForm() { function ItemEditForm() {
$request = $this->gridField->getForm()->Controller()->getRequest(); $request = $this->popupController->getRequest();
$form = new Form( $form = new Form(
$this, $this,
'ItemEditForm', 'ItemEditForm',
$this->record->getCMSFields(), // WARNING: The arguments passed here are a little arbitrary. This API will need cleanup
$this->record->getCMSFields($this->popupController, $this->popupFormName),
new FieldList( new FieldList(
$saveAction = new FormAction('doSave', _t('GridFieldDetailsForm.Save', 'Save')) $saveAction = new FormAction('doSave', _t('GridFieldDetailsForm.Save', 'Save'))
) )
@ -124,7 +204,7 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler {
$form->sessionMessage($message, 'good'); $form->sessionMessage($message, 'good');
return $this->gridField->getForm()->Controller()->redirectBack(); return $this->popupController->redirectBack();
} }
/** /**

View File

@ -0,0 +1,198 @@
<?php
/**
* A GridFieldRelationAdd is responsible for adding objects to another objects
* has_many and many_many relation. It will not attach duplicate objects.
*
* It augments a GridField with fields above the gridfield to search and add
* objects to whatever the SS_List passed into the gridfield.
*
* If the object is set to use autosuggestion it will include jQuery UI
* autosuggestion field that searches for current objects that isn't already
* attached to the list.
*/
class GridFieldRelationAdd implements GridField_HTMLProvider, GridField_ActionProvider, GridField_DataManipulator, GridField_URLHandler {
/**
* Which template to use for rendering
*
* @var string $itemClass
*/
protected $itemClass = 'GridFieldRelationAdd';
/**
* Which column that should be used for doing a StartsWith search
*
* @var string
*/
protected $fieldToSearch = '';
/**
* Use the jQuery.ui.autosuggestion plugin
*
* @var bool
*/
protected $useAutoSuggestion = true;
/**
*
* @param string $fieldToSearch which field on the object in the list should be search
* @param bool $autoSuggestion - if you would like to use the javascript autosuggest feature
*/
public function __construct($fieldToSearch, $autoSuggestion=true) {
$this->fieldToSearch = $fieldToSearch;
$this->useAutoSuggestion = $autoSuggestion;
}
/**
*
* @param GridField $gridField
* @return string - HTML
*/
public function getHTMLFragments($gridField) {
$searchState = $gridField->State->GridFieldSearchRelation;
if($this->useAutoSuggestion){
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/jquery-ui.js');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/GridFieldSearch.js");
}
$forTemplate = new ArrayData(array());
$forTemplate->Fields = new ArrayList();
$value = $this->findSingleEntry($gridField, $this->fieldToSearch, $searchState, $gridField->getList()->dataClass);
$searchField = new TextField('gridfield_relationsearch', 'Auto Suggest Search field', $value);
// Apparently the data-* needs to be double qouted for the jQuery.meta data plugin
//
$searchField->setAttribute('data-search-url', '\''.Controller::join_links($gridField->Link('search').'\''));
$findAction = new GridField_Action($gridField, 'gridfield_relationfind', 'Find', 'find', 'find');
$addAction = new GridField_Action($gridField, 'gridfield_relationadd', 'Add Relation', 'addto', 'addto');
// If an object is not found, disable the action
if(!is_int($gridField->State->GridFieldAddRelation)) {
$addAction->setReadonly(true);
}
$forTemplate->Fields->push($searchField);
$forTemplate->Fields->push($findAction);
$forTemplate->Fields->push($addAction);
return array('before' => $forTemplate->renderWith($this->itemClass));
}
/**
*
* @param GridField $gridField
* @return array
*/
public function getActions($gridField) {
return array('addto', 'find');
}
/**
* Manipulate the state to either add a new relation, or doing a small search
*
* @param GridField $gridField
* @param string $actionName
* @param string $arguments
* @param string $data
* @return string
*/
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
switch($actionName) {
case 'addto':
if(isset($data['relationID']) && $data['relationID']){
$gridField->State->GridFieldAddRelation = $data['relationID'];
}
$gridField->State->GridFieldSearchRelation = '';
break;
case 'find' && isset($data['autosuggest_search']):
$gridField->State->GridFieldSearchRelation = $data['autosuggest_search'];
break;
}
}
/**
* If an object ID is set, add the object to the list
*
* @param GridField $gridField
* @param SS_List $dataList
* @return SS_List
*/
public function getManipulatedData(GridField $gridField, SS_List $dataList) {
if(!$gridField->State->GridFieldAddRelation) {
return $dataList;
}
$objectID = Convert::raw2sql($gridField->State->GridFieldAddRelation);
if($objectID) {
$object = DataObject::get_by_id($dataList->dataclass(), $objectID);
if($object) {
$dataList->add($object);
}
}
$gridField->State->GridFieldAddRelation = null;
return $dataList;
}
/**
*
* @param GridField $gridField
* @return array
*/
public function getURLHandlers($gridField) {
return array(
'search/$ID' => 'doSearch',
);
}
/**
* Returns a json array of a search results that can be used by for example Jquery.ui.autosuggestion
*
* @param GridField $gridField
* @param SS_HTTPRequest $request
*/
public function doSearch($gridField, $request) {
$allList = DataList::create($gridField->getList()->dataClass());
$results = $allList->subtract($gridField->getList())->filter($this->fieldToSearch.':StartsWith',$request->param('ID'));
$results->sort($this->fieldToSearch, 'ASC');
$json = array();
foreach($results as $result) {
$json[$result->ID] = $result->{$this->fieldToSearch};
}
return Convert::array2json($json);
}
/**
* This will provide a StartsWith search that only returns a value if we are
* matching ONE object only. We wouldn't want to attach used any object to
* the list.
*
* @param GridField $gridField
* @param string $field
* @param string $searchTerm
* @param string $dataclass
* @return string
*/
protected function findSingleEntry($gridField, $field, $searchTerm, $dataclass) {
$fullList = DataList::create($dataclass);
$searchTerm = Convert::raw2sql($searchTerm);
if(!$searchTerm) {
return;
}
$existingList = clone $gridField->getList();
$searchResults = $fullList->subtract($existingList->limit(0))->filter($field.':StartsWith', $searchTerm);
// If more than one, skip
if($searchResults->count() != 1) {
return '';
}
$gridField->State->GridFieldAddRelation = $searchResults->first()->ID;
return $searchResults->first()->$field;
}
}

View File

@ -0,0 +1,99 @@
<?php
/**
* GridFieldRelationDelete
*
*/
class GridFieldRelationDelete implements GridField_ColumnProvider, GridField_ActionProvider {
/**
* Add a column 'UnlinkRelation'
*
* @param type $gridField
* @param array $columns
*/
public function augmentColumns($gridField, &$columns) {
$columns[] = 'UnlinkRelation';
}
/**
* Return any special attributes that will be used for FormField::createTag()
*
* @param GridField $gridField
* @param DataObject $record
* @param string $columnName
* @return array
*/
public function getColumnAttributes($gridField, $record, $columnName) {
return array();
}
/**
* Don't add an title
*
* @param GridField $gridField
* @param string $columnName
* @return array
*/
public function getColumnMetadata($gridField, $columnName) {
if($columnName == 'UnlinkRelation') {
return array('title' => '');
}
}
/**
* Which columns are handled by this component
*
* @param type $gridField
* @return type
*/
public function getColumnsHandled($gridField) {
return array('UnlinkRelation');
}
/**
* Which GridField actions are this component handling
*
* @param GridField $gridField
* @return array
*/
public function getActions($gridField) {
return array('unlinkrelation');
}
/**
*
* @param GridField $gridField
* @param DataObject $record
* @param string $columnName
* @return string - the HTML for the column
*/
public function getColumnContent($gridField, $record, $columnName) {
$field = new GridField_Action(
$gridField,
'UnlinkRelation'.$record->ID,
_t('GridAction.UnlinkRelation', "Unlink"),
"unlinkrelation",
array('RecordID' => $record->ID)
);
$output = $field->Field();
return $output;
}
/**
* Handle the actions and apply any changes to the GridField
*
* @param GridField $gridField
* @param string $actionName
* @param mixed $arguments
* @param array $data - form data
* @return void
*/
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
$id = $arguments['RecordID'];
$item = $gridField->getList()->byID($id);
if(!$item) return;
if($actionName == 'unlinkrelation') {
$gridField->getList()->remove($item);
}
}
}

View File

@ -0,0 +1,36 @@
jQuery(function($){
$(document).delegate("#gridfield_relationsearch", "focus", function (event) {
$(this).autocomplete({
source: function(request, response){
var searchField = $(this.element);
var form = $(this.element).closest("form");
// Due to some very weird behaviout of jquery.metadata, the url have to be double quoted
var suggestionUrl = $(searchField).attr('data-search-url').substr(1,$(searchField).attr('data-search-url').length-2);
$.ajax({
headers: {
"X-Get-Fragment" : 'Partial'
},
type: "GET",
url: suggestionUrl+'/'+request.term,
data: form.serialize()+'&'+escape(searchField.attr('name'))+'='+escape(searchField.val()),
success: function(data) {
response( $.map(JSON.parse(data), function( name, id ) {
return { label: name, value: name, id: id }
}));
},
error: function(e) {
alert(ss.i18n._t('GRIDFIELD.ERRORINTRANSACTION', 'An error occured while fetching data from the server\n Please try again later.'));
}
});
},
select: function(event, ui) {
$(this).closest("fieldset.ss-gridfield").find("#action_gridfield_relationfind").replaceWith(
'<input type="hidden" name="relationID" value="'+ui.item.id+'" id="relationID"/>'
);
$(this).closest("fieldset.ss-gridfield").find("#action_gridfield_relationadd").removeAttr('disabled');
}
});
});
});

View File

@ -45,6 +45,9 @@ $gf_border_radius: 7px;
} }
.cms { .cms {
fieldset.ss-gridfield>div {
margin-bottom: 35px;
}
table.ss-gridfield.field { table.ss-gridfield.field {
box-shadow: none; box-shadow: none;
padding: 0; padding: 0;
@ -108,6 +111,7 @@ $gf_border_radius: 7px;
padding: 0; padding: 0;
border-right: 1px solid #85959C; border-right: 1px solid #85959C;
height: 20px; height: 20px;
white-space: nowrap;
/* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */ /* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */
span { span {
@ -164,7 +168,7 @@ $gf_border_radius: 7px;
text-align: left; text-align: left;
padding: 0; padding: 0;
color: #FFF; color: #FFF;
width: 95%; min-width: 100px;
background: transparent; background: transparent;
border: 0 none; border: 0 none;
@include box-shadow-none; @include box-shadow-none;

18
security/Group.php Normal file → Executable file
View File

@ -62,17 +62,18 @@ class Group extends DataObject {
public function getCMSFields() { public function getCMSFields() {
Requirements::javascript(SAPPHIRE_DIR . '/javascript/PermissionCheckboxSetField.js'); Requirements::javascript(SAPPHIRE_DIR . '/javascript/PermissionCheckboxSetField.js');
$config = new GridFieldConfig_ManyManyEditor('FirstName', false, 20);
$config->addComponent(new GridFieldPopupForms(Controller::curr(), 'EditForm'));
$memberList = new GridField('Members','Members', $this->Members(), $config);
// @todo Implement permission checking on GridField
//$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
//$memberList->setPopupCaption(_t('SecurityAdmin.VIEWUSER', 'View User'));
$fields = new FieldList( $fields = new FieldList(
new TabSet("Root", new TabSet("Root",
new Tab('Members', _t('SecurityAdmin.MEMBERS', 'Members'), new Tab('Members', _t('SecurityAdmin.MEMBERS', 'Members'),
new TextField("Title", $this->fieldLabel('Title')), new TextField("Title", $this->fieldLabel('Title')),
$memberList = new MemberTableField( $memberList
(Controller::has_curr()) ? Controller::curr() : new Controller(),
"Members",
$this,
null,
false
)
), ),
$permissionsTab = new Tab('Permissions', _t('SecurityAdmin.PERMISSIONS', 'Permissions'), $permissionsTab = new Tab('Permissions', _t('SecurityAdmin.PERMISSIONS', 'Permissions'),
@ -152,9 +153,6 @@ class Group extends DataObject {
$rolesField->setDisabledItems($inheritedRoles->column('ID')); $rolesField->setDisabledItems($inheritedRoles->column('ID'));
} }
$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
$memberList->setPopupCaption(_t('SecurityAdmin.VIEWUSER', 'View User'));
$fields->push($idField = new HiddenField("ID")); $fields->push($idField = new HiddenField("ID"));
$this->extend('updateCMSFields', $fields); $this->extend('updateCMSFields', $fields);

View File

@ -0,0 +1,3 @@
<a href="$Backlink"> Go back the way you came!</a>
$ItemEditForm

View File

@ -0,0 +1,4 @@
<div><% control Fields %>
<span>$Field</span>
<% end_control %>
</div>