diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 43356803c..21ec7ff06 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -473,8 +473,7 @@ class LeftAndMain extends Controller { /** * Return a list of appropriate templates for this class, with the given suffix */ - protected function getTemplatesWithSuffix($suffix) { - $templates = array(); + public function getTemplatesWithSuffix($suffix) { $classes = array_reverse(ClassInfo::ancestry($this->class)); foreach($classes as $class) { $template = $class . $suffix; diff --git a/admin/code/SecurityAdmin.php b/admin/code/SecurityAdmin.php index bbd651276..cc5070ca5 100755 --- a/admin/code/SecurityAdmin.php +++ b/admin/code/SecurityAdmin.php @@ -107,7 +107,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider { */ function RootForm() { $config = new GridFieldConfig_Base(25); - $config->addComponent(new GridFieldPopupForms($this, 'RootForm')); + $config->addComponent(new GridFieldPopupForms()); $config->addComponent(new GridFieldExporter()); $memberList = new GridField('Members', 'All members', DataList::create('Member'), $config); diff --git a/admin/css/screen.css b/admin/css/screen.css index 28330efb5..a48b6c107 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -109,8 +109,8 @@ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, .cms .ui-widget-header .ui-dialog-title { padding: 6px 0; text-shadow: #ced7dc 1px 1px 0; } .cms .ui-widget-header a.ui-dialog-titlebar-close { position: absolute; top: -8px; right: -15px; width: 30px; height: 30px; z-index: 100000; } .cms .ui-widget-header a.ui-state-hover { border-color: transparent; background: transparent; } -.cms .ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -42px no-repeat; } -.cms .ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -82px no-repeat; width: 30px; height: 30px; } +.cms .ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -252px no-repeat; } +.cms .ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -292px no-repeat; width: 30px; height: 30px; } .cms .ui-state-hover { cursor: pointer; } .cms .ui-widget input, .cms .ui-widget select, .cms .ui-widget textarea, .cms .ui-widget button { color: #444444; font-size: 12px; font-family: Arial, sans-serif; } @@ -383,7 +383,7 @@ form.member-profile-form #Groups .middleColumn .TreeDropdownField { width: 90%; form.member-profile-form #Permissions .optionset li { float: none; width: auto; } .cms .cms-content { border-right: 1px solid rgba(201, 205, 206, 0.8); -moz-box-shadow: 3px 0 4px rgba(0, 0, 0, 0.15); -webkit-box-shadow: 3px 0 4px rgba(0, 0, 0, 0.15); -o-box-shadow: 3px 0 4px rgba(0, 0, 0, 0.15); box-shadow: 3px 0 4px rgba(0, 0, 0, 0.15); -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; -ms-border-radius: 0; -khtml-border-radius: 0; border-radius: 0; } -.cms .cms-content-fields { /* always show a y scroll bar as popups like TreeDropdowns can trigger longer pages and the extra scroll bar doesn't fire our sizing bar */ overflow-y: auto; overflow-x: auto; background: none; -moz-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px; -webkit-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px; -o-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px; box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px; } +.cms .cms-content-fields { /* always show a y scroll bar as popups like TreeDropdowns can trigger longer pages and the extra scroll bar doesn't fire our sizing bar */ overflow-y: auto; overflow-x: auto; background: none; width: 100%; } /** -------------------------------------------- Panels -------------------------------------------- */ .cms-panel { overflow: hidden; } @@ -447,9 +447,18 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai /** -------------------------------------------- Step labels -------------------------------------------- */ .step-label > * { display: inline-block; vertical-align: top; } .step-label .flyout { height: 18px; font-size: 14px; font-weight: bold; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; -o-border-top-left-radius: 3px; -ms-border-top-left-radius: 3px; -khtml-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; -o-border-bottom-left-radius: 3px; -ms-border-bottom-left-radius: 3px; -khtml-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; background-color: #667980; padding: 4px 3px 4px 6px; text-align: center; text-shadow: none; color: #fff; } -.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -213px no-repeat; margin-right: 4px; } +.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -759px no-repeat; margin-right: 4px; } .step-label .title { height: 18px; padding: 4px; } +/** -------------------------------------------- Item Edit Form -------------------------------------------- */ +#Form_ItemEditForm .field { width: 100%; } +#Form_ItemEditForm .field .fieldgroup { height: 230px; float: left; } +#Form_ItemEditForm .field .fieldgroup .fieldgroup-field.first { position: relative; margin-left: 0px; } +#Form_ItemEditForm .field .fieldgroup .fieldgroup-field { clear: both; width: 500px; margin-left: 420px; } +#Form_ItemEditForm .field .fieldgroup .fieldgroup-field img { -moz-border-radius: 8px; -webkit-border-radius: 8px; -o-border-radius: 8px; -ms-border-radius: 8px; -khtml-border-radius: 8px; border-radius: 8px; border-width: 3px; border-style: dashed; border-color: #8c99a1; position: absolute; top: 8px; } +#Form_ItemEditForm .field .fieldgroup .fieldgroup-field label { font-weight: bold; float: left; padding-top: 8px; line-height: 16px; text-shadow: white 1px 1px 0; } +#Form_ItemEditForm .field .fieldgroup .fieldgroup-field span { float: left; padding-left: 8px; font-style: italic; color: #777777; } + /** This file defines the jstree base styling (see http://jstree.com), as well as any customizations (see bottom of file). The styles are usually added through jstree.js on DOM load, but we need it earlier in order to correctly display the uninitialized tree. */ .cms .jstree ul { display: block; margin: 0; padding: 0; background: none; list-style-type: none; } .cms .jstree li { display: block; margin: 0; padding: 0; list-style-type: none; display: block; min-height: 18px; line-height: 18px; white-space: nowrap; margin-left: 18px; min-width: 18px; } @@ -564,7 +573,7 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; } .cms-logo span { font-weight: bold; font-size: 14px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; o-text-overflow: ellipsis; } .cms-login-status { border-top: 1px solid #19435c; height: 24px; padding: 7px 4px 0 4px; overflow: hidden; line-height: 16px; font-size: 11px; } -.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 3px; background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -123px no-repeat; text-indent: -9999em; } +.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 3px; background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -669px no-repeat; text-indent: -9999em; } .cms-menu { z-index: 80; background: #c6d7df; border-right: 1px solid #8c99a1; width: 191px; /* 8x24 - 1 */ -moz-box-shadow: rgba(107, 120, 123, 0.5) 0 0 8px; -webkit-box-shadow: rgba(107, 120, 123, 0.5) 0 0 8px; -o-box-shadow: rgba(107, 120, 123, 0.5) 0 0 8px; box-shadow: rgba(107, 120, 123, 0.5) 0 0 8px; } .cms-menu a { text-decoration: none; } @@ -584,12 +593,12 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; } .cms-menu-list li a .icon { display: inline-block; float: left; margin: 4px 10px 0 4px; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); opacity: 0.7; } .cms-menu-list li a .text { display: inline-block; float: left; } .cms-menu-list li a .toggle-children { display: inline-block; float: right; width: 20px; height: 100%; cursor: pointer; } -.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -149px no-repeat; vertical-align: middle; } -.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -165px no-repeat; } +.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -695px no-repeat; vertical-align: middle; } +.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -711px no-repeat; } .cms-menu-list li ul { display: none; } .cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #1e5270; background-color: #338dc1; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #338dc1), color-stop(100%, #287099)); background-image: -webkit-linear-gradient(#338dc1, #287099); background-image: -moz-linear-gradient(#338dc1, #287099); background-image: -o-linear-gradient(#338dc1, #287099); background-image: -ms-linear-gradient(#338dc1, #287099); background-image: linear-gradient(#338dc1, #287099); } -.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -181px no-repeat; } -.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-s9a54b65fa5.png') 0 -197px no-repeat; } +.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -727px no-repeat; } +.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sa4e142f7f0.png') 0 -743px no-repeat; } .cms-menu-list li.current ul { border-top: none; display: block; } .cms-menu-list li.current li { background-color: #287099; } .cms-menu-list li.current li a { font-size: 11px; padding: 0 10px 0 40px; height: 32px; line-height: 32px; color: #e2f0f7; background: none; border-top: 1px solid #338dc1; border-bottom: 1px solid #1e5270; } diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index a0b59a91a..fbc4185e0 100644 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -828,7 +828,7 @@ form.member-profile-form { overflow-y: auto; overflow-x: auto; background: none; - @include box-shadow($color-shadow-dark 0 0 ($grid-x / 2)); + width:100%; } } @@ -973,6 +973,7 @@ form.member-profile-form { background-color: #92a5b2; padding: $grid-y/2 $grid-x/2 $grid-y/2 $grid-x*2; border-bottom: 2px solid #8399a7; + @include background-image(linear-gradient(#ced7dc, #92a5b2)); } @@ -1146,4 +1147,50 @@ body.cms-dialog { padding: 4px; } -} \ No newline at end of file +} + +/** -------------------------------------------- + * Item Edit Form + * -------------------------------------------- */ + +#Form_ItemEditForm { + .field { + width:100%; + .fieldgroup { + height:230px; + float:left; + .fieldgroup-field.first { + position:relative; + margin-left:0px; + } + .fieldgroup-field { + clear:both; + width:500px; + margin-left:420px; + img { + @include border-radius (8px); + border: { + width:3px; + style:dashed; + color:$color-menu-border; + } + position:absolute; + top:8px; + } + label { + font-weight:bold; + float:left; + padding-top:8px; + line-height:16px; + text-shadow: $color-text-light 1px 1px 0; + } + span { + float:left; + padding-left:8px; + font-style:italic; + color:lighten($color-text, 20%); + } + } + } + } +} diff --git a/admin/templates/CMSGridFieldPopupForms.ss b/admin/templates/CMSGridFieldPopupForms.ss deleted file mode 100644 index 1981a9b4b..000000000 --- a/admin/templates/CMSGridFieldPopupForms.ss +++ /dev/null @@ -1,4 +0,0 @@ -
- Back - $ItemEditForm -
\ No newline at end of file diff --git a/admin/templates/Includes/LeftAndMain_EditForm.ss b/admin/templates/Includes/LeftAndMain_EditForm.ss index a10576434..451a4430f 100644 --- a/admin/templates/Includes/LeftAndMain_EditForm.ss +++ b/admin/templates/Includes/LeftAndMain_EditForm.ss @@ -21,6 +21,13 @@ <% end_if %> + + <% if Backlink %> + + <% _t('Back', 'Back') %> + + <% end_if %> + @@ -28,7 +35,7 @@ $EditFormTools <% end_control %> -
+
<% if Message %>

$Message

<% else %> diff --git a/control/Controller.php b/control/Controller.php index ac1af911b..e53aaa7bd 100644 --- a/control/Controller.php +++ b/control/Controller.php @@ -298,6 +298,23 @@ class Controller extends RequestHandler { public function hasAction($action) { return parent::hasAction($action) || $this->hasActionTemplate($action); } + + /** + * Removes all the "action" part of the current URL and returns the result. + * If no action parameter is present, returns the full URL + * @static + * @return String + */ + public function removeAction($fullURL, $action = null) { + if (!$action) $action = $this->getAction(); //default to current action + $returnURL = $fullURL; + + if (($pos = strpos($fullURL, $action)) !== false) { + $returnURL = substr($fullURL,0,$pos); + } + + return $returnURL; + } /** * Returns TRUE if this controller has a template that is specifically designed to handle a specific action. diff --git a/css/GridField.css b/css/GridField.css index 52b7d26ba..1f92e5941 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -9,20 +9,22 @@ .cms table.ss-gridfield-table tbody td button { border: none; background: none; margin: 0 0 0 2px; padding: 0; width: auto; text-shadow: none; } .cms table.ss-gridfield-table tfoot { color: #1d2224; } .cms table.ss-gridfield-table tfoot tr td { background: #95a5ab; padding: .7em; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } -.cms table.ss-gridfield-table tr.sortable-header th { background: #7f9198; } +.cms table.ss-gridfield-table tr.title { -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; -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-table tr.title th { position: relative; background: #7f9198; border-top: 1px solid rgba(0, 0, 0, 0.1); padding: 5px; min-height: 40px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b1c0c5), color-stop(100%, #7f9198)); background-image: -webkit-linear-gradient(#b1c0c5, #7f9198); background-image: -moz-linear-gradient(#b1c0c5, #7f9198); background-image: -o-linear-gradient(#b1c0c5, #7f9198); background-image: -ms-linear-gradient(#b1c0c5, #7f9198); background-image: linear-gradient(#b1c0c5, #7f9198); -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; -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; text-shadow: rgba(0, 0, 0, 0.3) 0px -1px 0; } +.cms table.ss-gridfield-table tr.title th h2 { padding: 0px; font-size: 16px; color: #fff; margin: 0; display: inline; } +.cms table.ss-gridfield-table tr.title th .new { font-size: 14px; border-color: rgba(0, 0, 0, 0.1); float: right; } +.cms table.ss-gridfield-table tr.sortable-header { background: #bac8ce; } .cms table.ss-gridfield-table tr:hover { background: #FFFAD6 !important; } .cms table.ss-gridfield-table tr:first-child { background: transparent; } .cms table.ss-gridfield-table tr.ss-gridfield-even { background: #f0f4f7; } .cms table.ss-gridfield-table tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; } .cms table.ss-gridfield-table tr.even { background: #f0f4f7; } .cms table.ss-gridfield-table tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 5px; border-right: 1px solid rgba(0, 0, 0, 0.1); } -.cms table.ss-gridfield-table tr th.main:first-child { -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-table tr th.main:last-child { -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-table tr th div.fieldgroup, .cms table.ss-gridfield-table tr th div.fieldgroup-field { width: auto; } .cms table.ss-gridfield-table tr th div.fieldgroup { min-width: 200px; padding-right: 0; } -.cms table.ss-gridfield-table tr th.extra, .cms table.ss-gridfield-table tr th.action { background: #7f9198; padding: 0; cursor: default; } +.cms table.ss-gridfield-table tr th.extra, .cms table.ss-gridfield-table tr th.action { padding: 0; cursor: default; } .cms table.ss-gridfield-table tr th.extra button.ss-ui-button, .cms table.ss-gridfield-table tr th.extra button:hover.ss-ui-button, .cms table.ss-gridfield-table tr th.action button.ss-ui-button, .cms table.ss-gridfield-table tr th.action button:hover.ss-ui-button { margin-left: .9em; color: #222; } -.cms table.ss-gridfield-table tr th.main { border-top: 1px solid rgba(0, 0, 0, 0.1); color: #fff; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b1c0c5), color-stop(100%, #7f9198)); background-image: -webkit-linear-gradient(#b1c0c5, #7f9198); background-image: -moz-linear-gradient(#b1c0c5, #7f9198); background-image: -o-linear-gradient(#b1c0c5, #7f9198); background-image: -ms-linear-gradient(#b1c0c5, #7f9198); background-image: linear-gradient(#b1c0c5, #7f9198); } +.cms table.ss-gridfield-table tr th.main { border-top: 1px solid rgba(0, 0, 0, 0.1); color: #fff; background: #9cb0b9; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } .cms table.ss-gridfield-table tr th.main span { text-shadow: rgba(0, 0, 0, 0.3) 0px -1px 0; } .cms table.ss-gridfield-table tr th.extra { background: #bac8ce; padding: 5px; border-top: rgba(0, 0, 0, 0.3); } .cms table.ss-gridfield-table tr th.extra span { width: auto; display: inline; position: static; } diff --git a/filesystem/File.php b/filesystem/File.php index f7a8b4ef7..53473b44a 100644 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -119,6 +119,8 @@ class File extends DataObject { 'cab','arj','tar','zip','zipx','sit','sitx','gz','tgz','bz2','ace','arc','pkg','dmg','hqx','jar', 'xml','pdf', ); + + protected static $labelSeparator = ':'; /** * @var If this is true, then restrictions set in {@link $allowed_max_file_size} and @@ -255,23 +257,58 @@ class File extends DataObject { return $this->canEdit($member); } + /* + * Generate and return the preview image / file upload / replace field for this File + * @return FormField + */ + protected function getFilePreview() { + //file upload + $uploadField = new UploadField('UploadField','Upload Field'); + $uploadField->setConfig('previewMaxWidth', 40); + $uploadField->setConfig('previewMaxHeight', 30); + //$uploadField->setTemplate('FileEditUploadField'); + if ($this->ParentID) { + $parent = $this->Parent(); + if ($parent) { //set the parent that the Upload field should use for uploads + $uploadField->setFolderName($parent->getFilename()); + $uploadField->setRecord($parent); + //TODO: make the uploadField replace the existing file + } + } + + return $uploadField; + } + + /** + * Returns the fields to power the edit screen of files in the CMS + * @return FieldList + */ function getCMSFields() { $urlLink = "
"; $urlLink .= ""; $urlLink .= "{$this->RelativeLink()}"; $urlLink .= "
"; + //create the file attributes in a FieldGroup + $filePreview = new FieldGroup( + $this->getFilePreview(), + new ReadonlyField("FileType", _t('AssetTableField.TYPE','File type').self::$labelSeparator), + new ReadonlyField("Filename", _t('AssetTableField.FILENAME','File name').self::$labelSeparator), + new ReadonlyField("Size", _t('AssetTableField.SIZE','File size').self::$labelSeparator, $this->getSize()), + new DateField_Disabled("Created", _t('AssetTableField.CREATED','First uploaded').self::$labelSeparator), + new DateField_Disabled("LastEdited", _t('AssetTableField.LASTEDIT','Last changed').self::$labelSeparator) + ); + $filePreview->setTitle("File preview"); + $filePreview->setName("FilePreview"); + return new FieldList( new TabSet('Root', - new Tab('Main', + new Tab('Main', + $filePreview, new TextField("Title", _t('AssetTableField.TITLE','Title')), new TextField("Name", _t('AssetTableField.FILENAME','Filename')), - new LiteralField("AbsoluteURL", $urlLink), - new ReadonlyField("FileType", _t('AssetTableField.TYPE','Type')), - new ReadonlyField("Size", _t('AssetTableField.SIZE','Size'), $this->getSize()), - new DropdownField("OwnerID", _t('AssetTableField.OWNER','Owner'), Member::mapInCMSGroups()), - new DateField_Disabled("Created", _t('AssetTableField.CREATED','First uploaded')), - new DateField_Disabled("LastEdited", _t('AssetTableField.LASTEDIT','Last changed')) + new LiteralField("AbsoluteURL", $urlLink), //TODO: replace this is a proper preview + new DropdownField("OwnerID", _t('AssetTableField.OWNER','Owner'), Member::mapInCMSGroups()) ) ) ); diff --git a/forms/FieldGroup.php b/forms/FieldGroup.php index a0c7181a4..ec4ec989a 100644 --- a/forms/FieldGroup.php +++ b/forms/FieldGroup.php @@ -97,14 +97,21 @@ class FieldGroup extends CompositeField { $spaceZebra = isset($this->zebra) ? " fieldgroup-$this->zebra" : ''; $idAtt = isset($this->id) ? " id=\"{$this->id}\"" : ''; $content = "
"; - + + $count = 1; foreach($fs as $subfield) { $childZebra = (!isset($childZebra) || $childZebra == "odd") ? "even" : "odd"; if($subfield->hasMethod('setZebra')) { $subfield->setZebra($childZebra); } - - $content .= "
" . $subfield->{$this->subfieldParam}() . "
"; + + //label the first and last fields of each surrounding div + if ($count == 1) $firstLast = "first"; + elseif ($count == count($fs)) $firstLast = "last"; + else $firstLast = ''; + + $content .= "
" . $subfield->{$this->subfieldParam}() . "
"; + $count++; } $content .= "
"; diff --git a/forms/Form.php b/forms/Form.php index 5f251537c..5e7c21183 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -379,10 +379,9 @@ class Form extends RequestHandler { if($field = $this->checkFieldsForAction($field->FieldList(), $funcName)) { return $field; } - } elseif (!$field->hasMethod($funcName)) { - continue; + } elseif ($field->hasMethod($funcName)) { + return $field; } - return $field; } } diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index 22625f501..b2f574727 100755 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -80,8 +80,6 @@ class GridField extends FormField { */ public function __construct($name, $title = null, SS_List $dataList = null, GridFieldConfig $config = null) { parent::__construct($name, $title, null); - - FormField::__construct($name); if($dataList) { $this->setList($dataList); @@ -386,6 +384,7 @@ class GridField extends FormField { $item->augmentColumns($this, $columns); } } + return $columns; } @@ -404,8 +403,11 @@ class GridField extends FormField { } if(!empty($this->columnDispatch[$column])) { - $handler = $this->columnDispatch[$column]; - return $handler->getColumnContent($this, $record, $column); + $content = ""; + foreach($this->columnDispatch[$column] as $handler) { + $content .= $handler->getColumnContent($this, $record, $column); + } + return $content; } else { throw new InvalidArgumentException("Bad column '$column'"); } @@ -427,15 +429,18 @@ class GridField extends FormField { } if(!empty($this->columnDispatch[$column])) { - $handler = $this->columnDispatch[$column]; - $attrs = $handler->getColumnAttributes($this, $record, $column); - if(is_array($attrs)) { - return $attrs; - } elseif($attrs) { - throw new LogicException("Non-array response from " . get_class($handler) . "::getColumnAttributes()"); - } else { - return array(); + $attrs = array(); + + foreach($this->columnDispatch[$column] as $handler) { + $column_attrs = $handler->getColumnAttributes($this, $record, $column); + + if(is_array($column_attrs)) + $attrs = array_merge($attrs, $column_attrs); + elseif($column_attrs) + throw new LogicException("Non-array response from " . get_class($handler) . "::getColumnAttributes()"); } + + return $attrs; } else { throw new InvalidArgumentException("Bad column '$column'"); } @@ -456,13 +461,19 @@ class GridField extends FormField { } if(!empty($this->columnDispatch[$column])) { - $handler = $this->columnDispatch[$column]; - $metadata = $handler->getColumnMetadata($this, $column); - if(is_array($metadata)) { - return $metadata; - } elseif($metadata) { - throw new LogicException("Non-array response from " . get_class($handler) . "::getColumnMetadata()"); + $metadata = array(); + + foreach($this->columnDispatch[$column] as $handler) { + $column_metadata = $handler->getColumnMetadata($this, $column); + + if(is_array($column_metadata)) + $metadata = array_merge($metadata, $column_metadata); + else + throw new LogicException("Non-array response from " . get_class($handler) . "::getColumnMetadata()"); + } + + return $metadata; } throw new InvalidArgumentException("Bad column '$column'"); } @@ -489,10 +500,10 @@ class GridField extends FormField { if($item instanceof GridField_ColumnProvider) { $columns = $item->getColumnsHandled($this); foreach($columns as $column) { - $this->columnDispatch[$column] = $item; + $this->columnDispatch[$column][] = $item; } } - } + } } /** @@ -729,23 +740,18 @@ class GridField_Action extends FormAction { $actionData['StateID'] = $id; // And generate field - $attributes = array( - 'class' => ($this->extraClass() ? $this->extraClass() : '') . ' action-' . $this->actionName, - 'id' => $this->id(), - 'type' => 'submit', + $data = new ArrayData(array( + 'Class' => ($this->extraClass() ? $this->extraClass() : '') . ($this->isReadonly() ? ' disabled' : ''), + 'ID' => $this->id(), // Note: This field needs to be less than 65 chars, otherwise Suhosin security patch // will strip it from the requests - 'name' => 'action_gridFieldAlterAction'. '?' . http_build_query($actionData), - 'tabindex' => $this->getTabIndex(), - 'data-url' => $this->gridField->Link(), - ); + 'Name' => 'action_gridFieldAlterAction'. '?' . http_build_query($actionData), + 'Disabled' => $this->isReadonly(), + 'Label' => $this->buttonLabel, + 'DataURL' => $this->gridField->Link(), + )); - if($this->isReadonly()) { - $attributes['disabled'] = 'disabled'; - $attributes['class'] = $attributes['class'] . ' disabled'; - } - - return $this->createTag('button', $attributes, $this->buttonLabel); + return $data->renderWith('GridField_Action'); } /** diff --git a/forms/gridfield/GridFieldAction.php b/forms/gridfield/GridFieldAction.php index 3ae3f040f..6cfe77039 100644 --- a/forms/gridfield/GridFieldAction.php +++ b/forms/gridfield/GridFieldAction.php @@ -1,7 +1,10 @@ ''); } } @@ -47,7 +51,7 @@ class GridFieldAction_Edit implements GridField_ColumnProvider { * @return type */ public function getColumnsHandled($gridField) { - return array('EditAction'); + return array('Actions'); } /** @@ -57,7 +61,7 @@ class GridFieldAction_Edit implements GridField_ColumnProvider { * @return array */ public function getActions($gridField) { - return array('deleterecord'); + return array(); } /** @@ -68,7 +72,11 @@ class GridFieldAction_Edit implements GridField_ColumnProvider { * @return string - the HTML for the column */ public function getColumnContent($gridField, $record, $columnName) { - return sprintf('%s', Controller::join_links($gridField->Link('item'), $record->ID, 'edit'), _t('GridAction.Edit', 'edit')); + $data = new ArrayData(array( + 'Link' => Controller::join_links($gridField->Link('item'), $record->ID, 'edit') + )); + + return $data->renderWith('GridFieldAction_Edit'); } /** @@ -98,7 +106,8 @@ class GridFieldAction_Delete implements GridField_ColumnProvider, GridField_Acti * @param array $columns */ public function augmentColumns($gridField, &$columns) { - $columns[] = 'DeleteAction'; + if(!in_array('Actions', $columns)) + $columns[] = 'Actions'; } /** @@ -121,7 +130,7 @@ class GridFieldAction_Delete implements GridField_ColumnProvider, GridField_Acti * @return array */ public function getColumnMetadata($gridField, $columnName) { - if($columnName == 'DeleteAction') { + if($columnName == 'Actions') { return array('title' => ''); } } @@ -133,7 +142,7 @@ class GridFieldAction_Delete implements GridField_ColumnProvider, GridField_Acti * @return type */ public function getColumnsHandled($gridField) { - return array('DeleteAction'); + return array('Actions'); } /** @@ -175,12 +184,11 @@ class GridFieldAction_Delete implements GridField_ColumnProvider, GridField_Acti * @return void */ public function handleAction(GridField $gridField, $actionName, $arguments, $data) { - $id = $arguments['RecordID']; - $item = $gridField->getList()->byID($id); - if(!$item) return; - if($actionName == 'deleterecord') { - $item->delete(); + $id = $arguments['RecordID']; + $item = $gridField->getList()->byID($id); + if(!$item) return; + $item->delete(); } } } \ No newline at end of file diff --git a/forms/gridfield/GridFieldConfig.php b/forms/gridfield/GridFieldConfig.php index b45cde614..eae9ae5f2 100755 --- a/forms/gridfield/GridFieldConfig.php +++ b/forms/gridfield/GridFieldConfig.php @@ -87,15 +87,16 @@ class GridFieldConfig_Base extends GridFieldConfig { * @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 static function create($itemsPerPage=15){ + return new GridFieldConfig_Base($itemsPerPage=15); } /** * * @param int $itemsPerPage - How many items per page should show up */ - public function __construct($itemsPerPage=25) { + public function __construct($itemsPerPage=15) { + $this->addComponent(new GridFieldTitle()); $this->addComponent(new GridFieldSortableHeader()); $this->addComponent(new GridFieldFilter()); $this->addComponent(new GridFieldDefaultColumns()); @@ -142,5 +143,6 @@ class GridFieldConfig_ManyManyEditor extends GridFieldConfig { $this->addComponent(new GridFieldAction_Edit()); $this->addComponent(new GridFieldRelationDelete()); $this->addComponent(new GridFieldPaginator($itemsPerPage)); + $this->addComponent(new GridFieldPopupForms()); } } diff --git a/forms/gridfield/GridFieldPaginator.php b/forms/gridfield/GridFieldPaginator.php index 7b468413d..ff7b944d4 100755 --- a/forms/gridfield/GridFieldPaginator.php +++ b/forms/gridfield/GridFieldPaginator.php @@ -18,7 +18,7 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu * * @var int */ - protected $itemsPerPage = 25; + protected $itemsPerPage = 15; /** * Which template to use for rendering @@ -31,7 +31,7 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu * * @param int $itemsPerPage - How many items should be displayed per page */ - public function __construct($itemsPerPage=25) { + public function __construct($itemsPerPage=15) { $this->itemsPerPage = $itemsPerPage; } diff --git a/forms/gridfield/GridFieldPopupForms.php b/forms/gridfield/GridFieldPopupForms.php index 70399584b..168cc97f2 100755 --- a/forms/gridfield/GridFieldPopupForms.php +++ b/forms/gridfield/GridFieldPopupForms.php @@ -1,7 +1,10 @@ /field//item/ @@ -9,22 +12,18 @@ */ class GridFieldPopupForms implements GridField_URLHandler { + + /** * @var String */ - protected $template = 'GridFieldItemEditView'; + protected $template = 'GridFieldPopupForms'; - /** - * - * @var Controller - */ - protected $popupController; - /** * * @var string */ - protected $popupFormName; + protected $name; function getURLHandlers($gridField) { return array( @@ -41,12 +40,10 @@ class GridFieldPopupForms implements GridField_URLHandler { * 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 + * @param string $name The name of the edit form to place into the pop-up form */ - public function __construct($popupController, $popupFormName) { - $this->popupController = $popupController; - $this->popupFormName = $popupFormName; + public function __construct($name = 'DetailForm') { + $this->name = $name; } /** @@ -56,10 +53,22 @@ class GridFieldPopupForms implements GridField_URLHandler { * @return GridFieldPopupForm_ItemRequest */ public function handleItem($gridField, $request) { - $record = $gridField->getList()->byId($request->param("ID")); - $handler = new GridFieldPopupForm_ItemRequest($gridField, $this, $record, $this->popupController, $this->popupFormName); + $controller = $gridField->getForm()->Controller(); + + if(is_numeric($request->param('ID'))) { + $record = $gridField->getList()->byId($request->param("ID")); + } else { + $record = Object::create($gridField->getModelClass()); + } + + if(!$class = ClassInfo::exists(get_class($this) . "_ItemRequest")) { + $class = 'GridFieldPopupForm_ItemRequest'; + } + + $handler = Object::create($class, $gridField, $this, $record, $controller, $this->name); $handler->setTemplate($this->template); - return $handler; + + return $handler->handleRequest($request, $gridField); } /** @@ -75,6 +84,20 @@ class GridFieldPopupForms implements GridField_URLHandler { function getTemplate() { return $this->template; } + + /** + * @param String + */ + function setName($name) { + $this->name = $name; + } + + /** + * @return String + */ + function getName() { + return $this->name; + } } class GridFieldPopupForm_ItemRequest extends RequestHandler { @@ -137,15 +160,29 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler { } 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 ? $this->record->ID : 'new', $action); } function edit($request) { $controller = $this->popupController; - + $form = $this->ItemEditForm($this->gridField, $request); + + // TODO Coupling with CMS + if($controller instanceof LeftAndMain) { + $form->addExtraClass('cms-edit-form'); + $form->setTemplate($controller->getTemplatesWithSuffix('_EditForm')); + $form->addExtraClass('cms-content center ss-tabset ' . $controller->BaseCSSClasses()); + if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet'); + // TODO Link back to controller action (and edited root record) rather than index, + // which requires more URL knowledge than the current link to this field gives us. + // The current root record is held in session only, + // e.g. page/edit/show/6/ vs. page/edit/EditForm/field/MyGridField/.... + $form->Backlink = $controller->Link(); + } + $return = $this->customise(array( - 'Backlink' => $this->gridField->getForm()->Controller()->Link(), - 'ItemEditForm' => $this->ItemEditForm($this->gridField, $request), + 'Backlink' => $controller->Link(), + 'ItemEditForm' => $form, ))->renderWith($this->template); if($controller->isAjax()) { @@ -154,7 +191,8 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler { // If not requested by ajax, we need to render it within the controller context+template return $controller->customise(array( - $this->popupFormName => $return, + // TODO Allow customization + 'Content' => $return, )); } } @@ -163,35 +201,40 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler { * 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 + * @todo 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() { - $request = $this->popupController->getRequest(); $form = new Form( $this, 'ItemEditForm', // WARNING: The arguments passed here are a little arbitrary. This API will need cleanup $this->record->getCMSFields($this->popupController, $this->popupFormName), new FieldList( - $saveAction = new FormAction('doSave', _t('GridFieldDetailsForm.Save', 'Save')) + $saveAction = new FormAction('doSave', _t('GridFieldDetailsForm.Save', 'Save')), + $deleteAction = new FormAction('doDelete', _t('GridFieldDetailsForm.Delete', 'Delete')) ) ); - $saveAction->addExtraClass('ss-ui-action-constructive icon-accept'); + $saveAction->addExtraClass('ss-ui-action-constructive'); + $deleteAction->addExtraClass('ss-ui-action-destructive'); $form->loadDataFrom($this->record); return $form; } function doSave($data, $form) { + $new_record = $this->record->ID == 0; + try { $form->saveInto($this->record); $this->record->write(); + if($new_record) + $this->gridField->getList()->add($this->record); } catch(ValidationException $e) { $form->sessionMessage($e->getResult()->message(), 'bad'); - return Director::redirectBack(); + return Controller::curr()->redirectBack(); } // TODO Save this item into the given relationship @@ -207,6 +250,34 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler { return $this->popupController->redirectBack(); } + function doDelete($data, $form) { + try { + $toDelete = $this->record; + if (!$toDelete->canDelete()) { + throw new ValidationException(_t('GridFieldDetailsForm.DeletePermissionsFailure',"No delete permissions"),0); + } + + $toDelete->delete(); + } catch(ValidationException $e) { + $form->sessionMessage($e->getResult()->message(), 'bad'); + return Director::redirectBack(); + } + + $message = sprintf( + _t('ComplexTableField.SUCCESSEDIT2', 'Deleted %s %s'), + $this->record->singular_name(), + '"' . htmlspecialchars($this->record->Title, ENT_QUOTES) . '"' + ); + + $form->sessionMessage($message, 'good'); + + //when an item is deleted, redirect to the revelant admin section without the action parameter + $controller = Controller::curr(); + $noActionURL = $controller->removeAction($data['url']); + + return Director::redirect($noActionURL, 302); //redirect back to admin section + } + /** * @param String */ diff --git a/forms/gridfield/GridFieldTitle.php b/forms/gridfield/GridFieldTitle.php new file mode 100644 index 000000000..afb4480d6 --- /dev/null +++ b/forms/gridfield/GridFieldTitle.php @@ -0,0 +1,13 @@ + $gridField->customise(array( + 'NewLink' => Controller::join_links($gridField->Link('item'), 'new') + ))->renderWith('GridFieldTitle') + ); + } +} + +?> \ No newline at end of file diff --git a/javascript/GridField.js b/javascript/GridField.js index 8f046b7bb..f3d6a2ec7 100644 --- a/javascript/GridField.js +++ b/javascript/GridField.js @@ -112,7 +112,21 @@ ).css({'position':'relative','margin':'0 auto','width':'65%'}).appendTo(eleInput.closest('th')); } } - }); + }); + + $('fieldset.ss-gridfield .new-link').entwine({ + onclick: function(e) { + $(this).trigger('opennewview', $(this).prop('href')); + return false; + } + }); + + $('fieldset.ss-gridfield .edit-link').entwine({ + onclick: function(e) { + $(this).trigger('openeditview', $(this).prop('href')); + return false; + } + }); /** * Allows selection of one or more rows in the grid field. @@ -146,4 +160,4 @@ }); -}(jQuery)); \ No newline at end of file +}(jQuery)); diff --git a/model/HasManyList.php b/model/HasManyList.php index 6e53690f5..5be23c927 100644 --- a/model/HasManyList.php +++ b/model/HasManyList.php @@ -63,7 +63,7 @@ class HasManyList extends RelationList { * @param $itemID The ID of the item to be removed */ function removeByID($itemID) { - $item = $this->byID($item); + $item = $this->byID($itemID); return $this->remove($item); } diff --git a/model/Image.php b/model/Image.php index 7986da6c5..0761a0386 100644 --- a/model/Image.php +++ b/model/Image.php @@ -72,6 +72,21 @@ class Image extends File { parent::defineMethods(); } + + /* + * Generate and return the preview image / file upload / replace field for this File + * @return FormField + */ + protected function getFilePreview() { + $formattedImage = $this->getFormattedImage('AssetLibraryPreview'); + $thumbnail = $formattedImage ? $formattedImage->URL : ''; + + $previewField = new LiteralField("ImageFull", + "{$this->Name}\n" + ); + + return $previewField; + } function getCMSFields() { $fields = parent::getCMSFields(); @@ -81,19 +96,9 @@ class Image extends File { $urlLink .= "{$this->RelativeLink()}"; $urlLink .= "
"; - $big = $this->URL; - $formattedImage = $this->getFormattedImage('AssetLibraryPreview'); - $thumbnail = $formattedImage ? $formattedImage->URL : ''; - - // Hmm this required the translated string to be appended to BottomRoot to add this to the Main tab - $fields->addFieldToTab('Root.Main', - new ReadonlyField("Dimensions", _t('AssetTableField.DIM','Dimensions')) - ); - $fields->addFieldToTab('Root.Main', - new LiteralField("ImageFull", - "{$this->Name}" - ) - ); + //attach the addition file information for an image to the existing FieldGroup create in the parent class + $fileAttributes = $fields->FieldByName('Root.Main.FilePreview'); + $fileAttributes->push(new ReadonlyField("Dimensions", _t('AssetTableField.DIM','Dimensions').self::$labelSeparator)); return $fields; } diff --git a/scss/GridField.scss b/scss/GridField.scss index 3bb86bb12..bb006289f 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -86,11 +86,34 @@ $gf_border_radius: 7px; } } - tr { - &.sortable-header { + tr { + &.title { + @include border-top-radius($gf_border_radius); th { + position: relative; background: $gf_colour_gradient_dark; + border-top: 1px solid $gf_colour_border; + padding: 5px; + min-height: 40px; + @include background-image(linear-gradient($gf_colour_gradient_light, $gf_colour_gradient_dark)); + @include border-top-radius($gf_border_radius); + @include single-text-shadow($gf_colour_text_shadow, 0px, -1px, 0); + h2{ + padding: 0px; + font-size: 16px; + color:#fff; + margin:0; + display:inline; + } + .new{ + font-size: 14px; + border-color: $gf_colour_border; + float: right; + } } + } + &.sortable-header { + background: $gf_colour_subheader; } &:hover { background: #FFFAD6 !important; @@ -116,10 +139,10 @@ $gf_border_radius: 7px; padding: 5px; border-right: 1px solid $gf_colour_border; &.main:first-child{ - @include border-top-left-radius($gf_border_radius); + //@include border-top-left-radius($gf_border_radius); } &.main:last-child{ - @include border-top-right-radius($gf_border_radius); + //@include border-top-right-radius($gf_border_radius); } div { &.fieldgroup,&.fieldgroup-field { @@ -131,7 +154,6 @@ $gf_border_radius: 7px; } } &.extra,&.action { - background: $gf_colour_gradient_dark; padding: 0; cursor: default; button,button:hover { @@ -144,7 +166,8 @@ $gf_border_radius: 7px; &.main{ border-top: 1px solid $gf_colour_border; color:#fff; - @include background-image(linear-gradient($gf_colour_gradient_light, $gf_colour_gradient_dark)); + background: darken($gf_colour_subheader,10%); + border-bottom: 1px solid $gf_colour_border; span{ @include single-text-shadow($gf_colour_text_shadow, 0px, -1px, 0); } diff --git a/security/Group.php b/security/Group.php index e3bc0d34f..90ce0cd9d 100755 --- a/security/Group.php +++ b/security/Group.php @@ -63,7 +63,6 @@ class Group extends DataObject { Requirements::javascript(SAPPHIRE_DIR . '/javascript/PermissionCheckboxSetField.js'); $config = new GridFieldConfig_ManyManyEditor('FirstName', true, 20); - $config->addComponent(new GridFieldPopupForms(Controller::curr(), 'EditForm')); $config->addComponent(new GridFieldExporter()); $memberList = new GridField('Members','Members', $this->Members(), $config); diff --git a/templates/Includes/GridFieldAction_Edit.ss b/templates/Includes/GridFieldAction_Edit.ss new file mode 100644 index 000000000..0c9d6b18a --- /dev/null +++ b/templates/Includes/GridFieldAction_Edit.ss @@ -0,0 +1 @@ +edit \ No newline at end of file diff --git a/templates/Includes/GridFieldTitle.ss b/templates/Includes/GridFieldTitle.ss new file mode 100644 index 000000000..199d41d0a --- /dev/null +++ b/templates/Includes/GridFieldTitle.ss @@ -0,0 +1,3 @@ + +

$Title

Add new + \ No newline at end of file diff --git a/templates/Includes/GridField_Action.ss b/templates/Includes/GridField_Action.ss new file mode 100644 index 000000000..0d8b4787d --- /dev/null +++ b/templates/Includes/GridField_Action.ss @@ -0,0 +1 @@ + \ No newline at end of file