Merge remote-tracking branch 'origin/3.1'

Conflicts:
	.travis.yml
	dev/DevelopmentAdmin.php
	email/Mailer.php
	tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php
This commit is contained in:
Ingo Schommer 2012-12-14 11:07:56 +01:00
commit 5579032716
117 changed files with 2905 additions and 1485 deletions

View File

@ -6,6 +6,7 @@ php:
env:
- TESTDB=MYSQL
- TESTDB=PGSQL
- TESTDB=SQLITE
matrix:
exclude:
@ -13,6 +14,15 @@ matrix:
env: TESTDB=PGSQL
- php: 5.4
env: TESTDB=SQLITE
include:
- php: 5.4
env:
- PHPCS=1
allow_failures:
- env: TESTDB=PGSQL
- php: 5.4
env:
- PHPCS=1
before_script:
- pear install pear/PHP_CodeSniffer
@ -21,17 +31,13 @@ before_script:
- cd ~/builds/ss
script:
- phpunit -c phpunit.xml.dist
- phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml -np framework
- phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml -np framework
- sh -c "if [ '$PHPCS' != '1' ]; then phpunit -c phpunit.xml.dist; else phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml -np framework && phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml -np framework; fi"
branches:
except:
- 2.1
- 2.2
- 2.3
- 2.4
- post-2.4
- translation-staging
notifications:

View File

@ -34,18 +34,6 @@ define('MCE_ROOT', FRAMEWORK_DIR . '/thirdparty/tinymce/');
ShortcodeParser::get('default')->register('file_link', array('File', 'link_shortcode_handler'));
ShortcodeParser::get('default')->register('embed', array('Oembed', 'handle_shortcode'));
/**
* The secret key that needs to be sent along with pings to /Email_BounceHandler
*
* Change this to something different for increase security (you can
* override it in mysite/_config.php to ease upgrades).
* For more information see:
* {@link http://doc.silverstripe.org/doku.php?id=email_bouncehandler}
*/
if(!defined('EMAIL_BOUNCEHANDLER_KEY')) {
define('EMAIL_BOUNCEHANDLER_KEY', '1aaaf8fb60ea253dbf6efa71baaacbb3');
}
// Zend_Cache temp directory setting
$_ENV['TMPDIR'] = TEMP_FOLDER; // for *nix
$_ENV['TMP'] = TEMP_FOLDER; // for Windows

View File

@ -296,6 +296,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
array(
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Layout.js',
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.js',
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.ActionTabSet.js',
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js',
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js',
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js',

View File

@ -52,8 +52,96 @@ fieldset.switch-states .switch .slide-button { display: none; }
/* Hide size controls in IE - they won't work as intended */
.cms-content-controls .preview-size-selector { display: none; }
/** Helper SCSS file for generating sprites for the interface. */
.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s5a3074ba2a.png') no-repeat; }
.ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept { background-position: 0 -96px; }
.ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled { background-position: 0 -80px; }
.ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add { background-position: 0 0; }
.ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia { background-position: 0 -208px; }
.ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled { background-position: 0 -32px; }
.ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage { background-position: 0 -144px; }
.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -500px; }
.ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left { background-position: 0 -356px; }
.ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -340px; }
.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -372px; }
.ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled { background-position: 0 -16px; }
.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -724px; }
.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -516px; }
.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -740px; }
.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -676px; }
.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -708px; }
.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -772px; }
.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -484px; }
.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -756px; }
.ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -452px; }
.ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -564px; }
.ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross { background-position: 0 -276px; }
.ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline { background-position: 0 -128px; }
.ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled { background-position: 0 -192px; }
.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -468px; }
.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -307px; }
.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -291px; }
.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -548px; }
.ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv { background-position: 0 -48px; }
.ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -420px; }
.ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -580px; }
.ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print { background-position: 0 -260px; }
.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -532px; }
.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -628px; }
.ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled { background-position: 0 -644px; }
.ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -388px; }
.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -436px; }
.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -612px; }
.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -692px; }
.ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil { background-position: 0 -228px; }
.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -596px; }
.ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition { background-position: 0 -244px; }
.ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -660px; }
.ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview { background-position: 0 -64px; }
.ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled { background-position: 0 -160px; }
.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -324px; }
.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -404px; }
.ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish { background-position: 0 -112px; }
.ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background-position: 0 -176px; }
.icon { text-indent: -9999px; border: none; outline: none; }
.icon.icon-24 { width: 24px; height: 24px; background: url('../images/menu-icons/24x24-sedfac01ed1.png'); }
.icon.icon-24.icon-assetadmin { background-position: 0 -120px; }
.icon.icon-24.icon-cmsmain { background-position: 0 -48px; }
.icon.icon-24.icon-cmspagescontroller { background-position: 0 -192px; }
.icon.icon-24.icon-cmssettingscontroller { background-position: 0 0; }
.icon.icon-24.icon-securityadmin { background-position: 0 -24px; }
.icon.icon-24.icon-reportadmin { background-position: 0 -72px; }
.icon.icon-24.icon-commentadmin { background-position: 0 -168px; }
.icon.icon-24.icon-help { background-position: 0 -96px; }
.icon.icon-16 { width: 16px; height: 16px; background: url('../images/menu-icons/16x16-sb173d358c2.png'); }
.icon.icon-16.icon-assetadmin { background-position: 0 -80px; }
.icon.icon-16.icon-cmsmain { background-position: 0 -16px; }
.icon.icon-16.icon-cmspagescontroller { background-position: 0 -112px; }
.icon.icon-16.icon-cmssettingscontroller { background-position: 0 0; }
.icon.icon-16.icon-securityadmin { background-position: 0 -48px; }
.icon.icon-16.icon-reportadmin { background-position: 0 -32px; }
.icon.icon-16.icon-commentadmin { background-position: 0 -128px; }
.icon.icon-16.icon-help { background-position: 0 -64px; }
html { overflow: hidden; }
.cms-content-toolbar { padding-bottom: 5px; }
.cms-menu-list li { list-style-type: none; width: 100%; float: left; margin: 0px; padding: 0px; }
/* Site tree
------------------------- */
.cms-tree-view-modes div { float: left; }
.cms-tree-view-modes span { float: left; padding-top: 5px; }
.cms-panel-content .cms-tree li { width: 200px; overflow: hidden; float: left; display: inline; }
.jstree li a .ui-icon { text-indent: 0px !important; }
/* Forms and files area
-----------------------------*/
.field input.text, .field textarea, .field .TreeDropdownField { width: 94%; }
select { padding: 10px 0; height: 30px; }
@ -76,6 +164,8 @@ select { padding: 10px 0; height: 30px; }
.jstree li a .ui-icon { text-indent: 0px !important; }
/* Gridfield
------------------------- */
.cms table.ss-gridfield-table tbody td { width: auto; }
.cms table.ss-gridfield-table tr th.extra span input { height: 23px; }
@ -100,12 +190,14 @@ table.ss-gridfield-table tr.title th h2 { float: left; }
table.ss-gridfield-table tr.ss-gridfield-item.odd { background: white; }
table.ss-gridfield-table tr.ss-gridfield-item.even { background: #F0F4F7; }
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form { overflow: hidden; }
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form input { width: 160px; }
.cms .ss-gridfield table.ss-gridfield-table tbody td.col-listChildrenLink { width: 16px; }
.cms .ss-gridfield table.ss-gridfield-table tbody td.col-listChildrenLink .list-children-link { background: transparent url(../images/sitetree_ss_default_icons.png) no-repeat 4px -4px; display: block; }
.ss-ui-button.ss-gridfield-button-filter { border: none !important; }
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form { overflow: hidden; }
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form input { width: 160px; }
.cms-content-header h2 { float: left; }
.cms-content-header h2 .section-icon { display: none; }
.cms-content-header .cms-content-header-tabs { position: absolute; right: 0; }
@ -118,3 +210,15 @@ table.ss-gridfield-table tr.ss-gridfield-item.even { background: #F0F4F7; }
.cms-panel-content-collapsed { position: relative; width: 40px; }
.cms-panel-content-collapsed h2.cms-panel-header, .cms-panel-content-collapsed h3.cms-panel-header { zoom: 1; position: absolute; top: 10px; right: 10px; writing-mode: tb-rl; float: right; z-index: 5000; }
.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset { width: 190px; }
.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav a.ui-tabs-anchor { background: transparent url(../images/sprites-32x32/arrow_down_lighter.png) no-repeat right top; }
.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav a.ui-tabs-anchor:hover { background: transparent url(../images/sprites-32x32/arrow_down_darker.png) no-repeat right top; }
.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav .ui-state-active a.ui-tabs-anchor { background: transparent url(../images/sprites-32x32/arrow_up_lighter.png) no-repeat right top; }
.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav .ui-state-active a.ui-tabs-anchor:hover { background: transparent url(../images/sprites-32x32/arrow_up_darker.png) no-repeat right top; }
.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button { width: 190px; /* Width 100% not calculating by ie7 */ }
/* Tempory fix as jquery loads too slow to add icons */
button.ui-button-text-icon-primary { padding-left: 30px !important; }
button.ui-button-text-icon-primary span.ui-button-icon-primary { position: absolute !important; top: 5px !important; left: 8px !important; }
button.ui-button-text-icon-primary .ui-button-text { margin-left: 0 !important; }

View File

@ -32,9 +32,12 @@ If more variables exist in the future, consider creating a variables file.*/
/** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
/** ----------------------------- Custom mixins ------------------------------ */
/*Mixin used to generate slightly smaller text and forms
Used in side panels and action tabs
*/
/** ----------------------------- Sprite images ----------------------------- */
/** Helper SCSS file for generating sprites for the interface. */
.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s37c6548b54.png') no-repeat; }
.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s5a3074ba2a.png') no-repeat; }
.ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept { background-position: 0 -96px; }
.ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled { background-position: 0 -80px; }
@ -42,46 +45,47 @@ If more variables exist in the future, consider creating a variables file.*/
.ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia { background-position: 0 -208px; }
.ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled { background-position: 0 -32px; }
.ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage { background-position: 0 -144px; }
.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -484px; }
.ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left { background-position: 0 -340px; }
.ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -324px; }
.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -356px; }
.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -500px; }
.ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left { background-position: 0 -356px; }
.ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -340px; }
.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -372px; }
.ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled { background-position: 0 -16px; }
.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -708px; }
.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -500px; }
.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -724px; }
.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -660px; }
.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -692px; }
.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -756px; }
.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -468px; }
.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -740px; }
.ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -436px; }
.ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -548px; }
.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -724px; }
.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -516px; }
.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -740px; }
.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -676px; }
.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -708px; }
.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -772px; }
.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -484px; }
.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -756px; }
.ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -452px; }
.ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -564px; }
.ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross { background-position: 0 -276px; }
.ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline { background-position: 0 -128px; }
.ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled { background-position: 0 -192px; }
.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -452px; }
.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -291px; }
.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -532px; }
.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -468px; }
.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -307px; }
.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -291px; }
.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -548px; }
.ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv { background-position: 0 -48px; }
.ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -404px; }
.ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -564px; }
.ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -420px; }
.ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -580px; }
.ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print { background-position: 0 -260px; }
.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -516px; }
.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -612px; }
.ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled { background-position: 0 -628px; }
.ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -372px; }
.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -420px; }
.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -596px; }
.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -676px; }
.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -532px; }
.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -628px; }
.ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled { background-position: 0 -644px; }
.ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -388px; }
.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -436px; }
.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -612px; }
.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -692px; }
.ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil { background-position: 0 -228px; }
.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -580px; }
.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -596px; }
.ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition { background-position: 0 -244px; }
.ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -644px; }
.ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -660px; }
.ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview { background-position: 0 -64px; }
.ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled { background-position: 0 -160px; }
.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -308px; }
.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -388px; }
.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -324px; }
.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -404px; }
.ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish { background-position: 0 -112px; }
.ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background-position: 0 -176px; }
@ -125,8 +129,8 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif;
.ui-widget-header .ui-dialog-title { padding: 6px 0; text-shadow: #ced7dc 1px 1px 0; }
.ui-widget-header a.ui-dialog-titlebar-close { position: absolute; top: -8px; right: -15px; width: 30px; height: 30px; z-index: 100000; }
.ui-widget-header a.ui-state-hover { border-color: transparent; background: transparent; }
.ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -216px no-repeat; }
.ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -318px no-repeat; width: 30px; height: 30px; }
.ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -216px no-repeat; }
.ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -318px no-repeat; width: 30px; height: 30px; }
.ui-state-hover { cursor: pointer; }
@ -149,18 +153,18 @@ form.nostyle input.text, form.nostyle textarea, form.nostyle select, form.nostyl
.field:last-child { border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.field:after { content: "\0020"; display: block; height: 0; clear: both; overflow: hidden; visibility: hidden; }
.field.nolabel .middleColumn { margin-left: 0; }
.field.nolabel .help { margin-left: 0; }
.field.nolabel .description { margin-left: 0; }
.field.checkbox label.right { margin: 4px 0 0 0; display: inline; font-style: normal; color: #444444; clear: none; }
.field label.left { float: left; display: block; width: 176px; padding: 8px 8px 8px 0; line-height: 16px; font-weight: bold; text-shadow: 1px 1px 0 white; }
.field label.right { cursor: pointer; clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; }
.field .middleColumn { margin-left: 184px; }
.field span.readonly { padding-top: 8px; line-height: 16px; display: block; }
.field .fieldgroup .fieldgroup-field.last { /* This is used on page/settings/visibility */ padding-bottom: 8px; /* replicates li item spacing */ }
.field .help { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; }
.field.help label.right { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; }
.field .description { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; }
.field.checkbox .description, .field.ss-gridfield .description { margin-left: 0; }
.field input.text, .field textarea, .field select, .field .TreeDropdownField { margin-left: 10px; width: 100%; max-width: 512px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
.field input.text.help, .field textarea.help, .field select.help, .field .TreeDropdownField.help { margin: 0; }
.field input.text .help, .field textarea .help, .field select .help, .field .TreeDropdownField .help { max-width: 512px; }
.field input.text.description, .field textarea.description, .field select.description, .field .TreeDropdownField.description { margin: 0; }
.field input.text .description, .field textarea .description, .field select .description, .field .TreeDropdownField .description { max-width: 512px; }
.field input.text, .field textarea, .field .TreeDropdownField { background: #fff; border: 1px solid #b3b3b3; padding: 7px 7px; line-height: 16px; margin: 0; outline: none; -moz-transition: 0.2s box-shadow ease-in; -webkit-transition: 0.2s box-shadow ease-in; -o-transition: 0.2s box-shadow ease-in; transition: 0.2s box-shadow ease-in; -moz-transition: 0.2s border ease-in; -webkit-transition: 0.2s border ease-in; -o-transition: 0.2s border ease-in; transition: 0.2s border ease-in; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VhZWFlYSIvPjxzdG9wIG9mZnNldD0iMTAlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g'); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #eaeaea), color-stop(10%, #ffffff)); background-image: -webkit-linear-gradient(#eaeaea, #ffffff 10%); background-image: -moz-linear-gradient(#eaeaea, #ffffff 10%); background-image: -o-linear-gradient(#eaeaea, #ffffff 10%); background-image: linear-gradient(#eaeaea, #ffffff 10%); }
.field input.text:focus, .field textarea:focus, .field .TreeDropdownField:focus { border: 1px solid #9a9a9a; border-top-color: gray; -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) inset; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) inset; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) inset; }
.field input[disabled], .field input.disabled, .field textarea[disabled], .field textarea.disabled, .field select[disabled], .field select.disabled { color: #777777; background: #efefef; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2JjYmNiYyIvPjxzdG9wIG9mZnNldD0iMTAlIiBzdG9wLWNvbG9yPSIjZWZlZmVmIi8+PHN0b3Agb2Zmc2V0PSI5MCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNiY2JjYmMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #bcbcbc), color-stop(10%, #efefef), color-stop(90%, #ffffff), color-stop(100%, #bcbcbc)); background-image: -webkit-linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); background-image: -moz-linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); background-image: -o-linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); background-image: linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); border: 1px solid #b3b3b3; }
@ -192,21 +196,29 @@ form.small .field input.text, form.small .field textarea, form.small .field sele
.field.remove-splitter { border-bottom: none; box-shadow: none; }
/** ---------------------------------------------------- Buttons ---------------------------------------------------- */
.cms .button-no-style button, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: none; border: none; color: #0073c1; display: block; font-weight: normal; margin: 0; outline: none; padding-left: 10px; padding-right: 10px; text-align: left; text-shadow: none; white-space: normal; }
.cms .button-no-style button.ss-ui-action-destructive, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; }
.cms .button-no-style button span, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; }
.cms .button-no-style button:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; background: none; border: none; }
.cms .button-no-style button.loading, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.loading { background: transparent url(../../images/network-save.gif) no-repeat 8px center; }
.cms .button-no-style button.loading .ui-button-text, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.loading .ui-button-text { padding-left: 20px; }
.cms .Actions > *, .cms .cms-actions-row > * { display: block; float: left; margin-right: 8px; }
.cms .Actions > *:last-child, .cms .cms-actions-row > *:last-child { margin-right: 0; }
.cms .Actions { min-height: 30px; overflow: visible; padding: 8px 12px; }
.cms .Actions { min-height: 30px; overflow: auto; padding: 8px 12px; }
.cms .south .Actions, .cms .ui-tabs-panel .Actions, .cms .ui-tabs-panel iframe .Actions { padding: 0; }
.cms input.loading, .cms button.loading, .cms input.ui-state-default.loading, .cms .ui-widget-content input.ui-state-default.loading, .cms .ui-widget-header input.ui-state-default.loading { color: #525252; border-color: #d5d3d3; cursor: default; }
.cms input.loading .ui-icon, .cms button.loading .ui-icon, .cms input.ui-state-default.loading .ui-icon, .cms .ui-widget-content input.ui-state-default.loading .ui-icon, .cms .ui-widget-header input.ui-state-default.loading .ui-icon { background: transparent url(../../images/network-save.gif) no-repeat 0 0; }
.cms input.loading.ss-ui-action-constructive .ui-icon, .cms button.loading.ss-ui-action-constructive .ui-icon { background: transparent url(../../images/network-save-constructive.gif) no-repeat 0 0; }
.cms .ss-ui-button { margin-top: 0px; font-weight: bold; text-decoration: none; line-height: 16px; color: #393939; border: 1px solid #c0c0c2; border-bottom: 1px solid #a6a6a9; cursor: pointer; background-color: #e6e6e6; white-space: nowrap; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background: -webkit-linear-gradient(#ffffff, #d9d9d9); background: -moz-linear-gradient(#ffffff, #d9d9d9); background: -o-linear-gradient(#ffffff, #d9d9d9); background: linear-gradient(#ffffff, #d9d9d9); text-shadow: white 0 1px 1px; /* constructive */ /* destructive */ }
.cms .ss-ui-button { font-size: 12px; margin-top: 0px; padding: 5px 10px; font-weight: bold; text-decoration: none; line-height: 16px; color: #393939; border: 1px solid #c0c0c2; border-bottom: 1px solid #a6a6a9; cursor: pointer; background-color: #e6e6e6; white-space: nowrap; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background: -webkit-linear-gradient(#ffffff, #d9d9d9); background: -moz-linear-gradient(#ffffff, #d9d9d9); background: -o-linear-gradient(#ffffff, #d9d9d9); background: linear-gradient(#ffffff, #d9d9d9); text-shadow: white 0 1px 1px; /* constructive */ /* destructive */ }
.cms .ss-ui-button .ui-icon, .cms .ss-ui-button .ui-button-text { display: inline-block; line-height: 16px; padding: 0; }
.cms .ss-ui-button .ui-icon { width: 16px; padding: 0 2px; position: relative; left: -2px; margin-top: 0; top: 0; height: 16px; float: left; }
.cms .ss-ui-button.ui-state-hover, .cms .ss-ui-button:hover { text-decoration: none; background-color: white; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e6e6e6)); background: -webkit-linear-gradient(#ffffff, #e6e6e6); background: -moz-linear-gradient(#ffffff, #e6e6e6); background: -o-linear-gradient(#ffffff, #e6e6e6); background: linear-gradient(#ffffff, #e6e6e6); -webkit-box-shadow: 0 0 5px #b3b3b3; -moz-box-shadow: 0 0 5px #b3b3b3; box-shadow: 0 0 5px #b3b3b3; }
.cms .ss-ui-button:active, .cms .ss-ui-button:focus, .cms .ss-ui-button.ui-state-active, .cms .ss-ui-button.ui-state-focus { border: 1px solid #b3b3b3; background-color: white; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e6e6e6)); background: -webkit-linear-gradient(#ffffff, #e6e6e6); background: -moz-linear-gradient(#ffffff, #e6e6e6); background: -o-linear-gradient(#ffffff, #e6e6e6); background: linear-gradient(#ffffff, #e6e6e6); -webkit-box-shadow: 0 0 5px #b3b3b3 inset; -moz-box-shadow: 0 0 5px #b3b3b3 inset; box-shadow: 0 0 5px #b3b3b3 inset; }
.cms .ss-ui-button.ss-ui-action-constructive { text-shadow: none; font-weight: bold; color: white; border-color: #1f9433; border-bottom-color: #166a24; background-color: #1f9433; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzkzYmU0MiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzFmOTQzMyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #93be42), color-stop(100%, #1f9433)); background: -webkit-linear-gradient(#93be42, #1f9433); background: -moz-linear-gradient(#93be42, #1f9433); background: -o-linear-gradient(#93be42, #1f9433); background: linear-gradient(#93be42, #1f9433); text-shadow: #1c872f 0 -1px -1px; }
.cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover, .cms .ss-ui-button.ss-ui-action-constructive:hover { border-color: #166a24; background-color: #1f9433; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2E0Y2EzYSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzIzYTkzYSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a4ca3a), color-stop(100%, #23a93a)); background: -webkit-linear-gradient(#a4ca3a, #23a93a); background: -moz-linear-gradient(#a4ca3a, #23a93a); background: -o-linear-gradient(#a4ca3a, #23a93a); background: linear-gradient(#a4ca3a, #23a93a); }
.cms .ss-ui-button.ss-ui-action-constructive:active, .cms .ss-ui-button.ss-ui-action-constructive:focus, .cms .ss-ui-button.ss-ui-action-constructive.ui-state-active, .cms .ss-ui-button.ss-ui-action-constructive.ui-state-focus { background-color: #1d8c30; -webkit-box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); -moz-box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); }
.cms .ss-ui-button.ss-ui-action-destructive { color: red; background-color: #e6e6e6; }
.cms .ss-ui-button.ss-ui-button-small .ui-button-text { padding: 2px 2px; font-size: 10px; }
.cms .ss-ui-button.ss-ui-button-small .ui-button-text { font-size: 10px; }
.cms .ss-ui-button.ui-state-highlight { background-color: #e6e6e6; border: 1px solid #708284; }
.cms .ss-ui-button.ss-ui-action-minor { background: none; border: 0; color: #393939; text-decoration: underline; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.cms .ss-ui-button.ss-ui-action-minor:hover { text-decoration: none; color: #1f1f1f; }
@ -230,7 +242,7 @@ form.small .field input.text, form.small .field textarea, form.small .field sele
.ss-toggle .ui-accordion-content .field:last-child { margin-bottom: 0; }
.ss-toggle .ui-accordion-content .field .middleColumn { margin-left: 0; }
.ss-toggle .ui-accordion-content .field label { float: none; margin-left: 0; }
.ss-toggle .ui-accordion-content .field .help { margin-left: 0; }
.ss-toggle .ui-accordion-content .field .description { margin-left: 0; }
/** ---------------------------------------------------- Checkbox Field ---------------------------------------------------- */
.field.checkbox { padding-left: 184px; margin-bottom: 8px; }
@ -338,9 +350,7 @@ body.cms { overflow: hidden; }
.cms strong { font-weight: bold; }
/** -------------------------------------------- Helpers -------------------------------------------- */
.cms-helper-hide-actions .Actions { display: none; }
.hide { display: none; }
.hide, .cms-helper-hide-actions .Actions { display: none; }
/** -------------------------------------------- Panels Styles -------------------------------------------- */
.cms-container { height: 100%; /*background: $tab-panel-texture-background;*/ background: #eceff1; }
@ -363,7 +373,7 @@ body.cms { overflow: hidden; }
/** -------------------------------------------- Tabs -------------------------------------------- */
.ui-tabs { padding: 0; background: none; }
.ui-tabs .ui-tabs { position: static; }
.ui-tabs .ui-tabs-panel { padding: 8px 0; background: transparent; }
.ui-tabs .ui-tabs-panel { padding: 8px 0; background: transparent; border: 0; }
.ui-tabs .ui-tabs-panel.cms-edit-form { padding: 0; }
.ui-tabs .ui-widget-header { border: 0; background: none; }
.ui-tabs .ui-tabs-nav { float: right; margin: 0 0 -1px 0; padding: 0 12px 0 0; border-bottom: none; }
@ -388,6 +398,7 @@ body.cms { overflow: hidden; }
.ui-tabs .ui-tabs-nav li.cms-tabset-icon.gallery.ui-state-active a { background: url('../images/sprites-64x64-s88957ee578.png') 0 -54px no-repeat; }
.ui-tabs .ui-tabs-nav li.cms-tabset-icon.edit.ui-state-active a { background: url('../images/sprites-64x64-s88957ee578.png') 0 -404px no-repeat; }
.ui-tabs .ui-tabs-nav li.cms-tabset-icon.search.ui-state-active a { background: url('../images/sprites-64x64-s88957ee578.png') 0 -104px no-repeat; }
.ui-tabs .cms-edit-form, .ui-tabs .cms-content-fields { /*not sure if .cms-content-fields effects other areas*/ }
.ui-tabs .cms-edit-form .cms-panel-padded, .ui-tabs .cms-content-fields .cms-panel-padded { /* Has padded area inside it */ padding: 0; margin: 0; }
.ui-tabs .cms-edit-form .ui-tabs-panel, .ui-tabs .cms-edit-form .ss-gridfield, .ui-tabs .cms-content-fields .ui-tabs-panel, .ui-tabs .cms-content-fields .ss-gridfield { margin: 12px; padding: 0 0 12px; }
.ui-tabs .cms-edit-form .ui-tabs-panel .ss-gridfield, .ui-tabs .cms-edit-form .ss-gridfield .ss-gridfield, .ui-tabs .cms-content-fields .ui-tabs-panel .ss-gridfield, .ui-tabs .cms-content-fields .ss-gridfield .ss-gridfield { /* Files area & inside second level tabs */ padding: 0; /* should be zero ideally */ margin: 0 0 12px; }
@ -466,7 +477,7 @@ p.message { margin-bottom: 12px; }
#PageType ul li .description { font-style: italic; }
/** -------------------------------------------- Content toolbar -------------------------------------------- */
.cms-content-toolbar { min-height: 29px; display: block; margin: 0 0 15px 0; border-bottom: 1px solid #d0d3d5; -webkit-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -moz-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -o-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); *zoom: 1; /* smaller treedropdown */ }
.cms-content-toolbar { min-height: 29px; display: block; margin: 0 0 15px 0; padding-bottom: 9px; border-bottom: 1px solid #d0d3d5; -webkit-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -moz-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -o-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); *zoom: 1; /* smaller treedropdown */ }
.cms-content-toolbar:after { content: "\0020"; display: block; height: 0; clear: both; overflow: hidden; visibility: hidden; }
.cms-content-toolbar .cms-tree-view-modes { float: right; padding-top: 5px; }
.cms-content-toolbar .cms-tree-view-modes * { display: inline-block; }
@ -544,6 +555,9 @@ form.member-profile-form #Groups .middleColumn { margin-left: 0; width: 100%; }
form.member-profile-form #Groups .middleColumn .TreeDropdownField { width: 90%; max-width: 90%; }
form.member-profile-form #Permissions .optionset li { float: none; width: auto; }
.memberdatetimeoptionset .description { font-style: normal; }
.memberdatetimeoptionset .toggle { font-size: 11px; }
.cms .cms-content { border-right: 1px solid #BBB; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eceff1; width: 800px; z-index: 40; }
.cms .cms-content-fields { overflow-y: auto; overflow-x: auto; background: none; width: 100%; }
.cms .cms-content-fields #Root_Main .confirmedpassword { border-bottom: none; box-shadow: none; }
@ -649,7 +663,7 @@ 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; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-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-se93fc83bf9.png') 0 -256px no-repeat; margin-right: 4px; }
.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -256px no-repeat; margin-right: 4px; }
.step-label .title { height: 18px; padding: 4px; }
/** -------------------------------------------- Item Edit Form -------------------------------------------- */
@ -669,9 +683,7 @@ form.small .cms-file-info-data .field .middleColumn { margin-left: 120px; }
/** -------------------------------------------- Users Members Admin -------------------------------------------- */
.members_grid span button#action_gridfield_relationfind { display: none; }
.members_grid p button#action_export { margin-top: 16px; }
.members_grid p button#action_export span.btn-icon-download-csv { height: 17px; }
.members_grid p button#action_export .ui-button-text { padding-left: 26px; }
/** Import forms */
form.import-form ul { list-style: disc; }
@ -695,10 +707,10 @@ form.import-form label.left { width: 250px; }
/** -------------------------------------------- Buttons for FileUpload -------------------------------------------- */
.ss-uploadfield-item-edit-all .ui-button-text { padding-right: 0; }
.toggle-details-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -433px no-repeat; }
.ss-uploadfield-item-edit-all .toggle-details-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -375px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; }
.toggle-details-icon.opened { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -1121px no-repeat; }
.ss-uploadfield-item-edit-all .toggle-details-icon.opened { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -359px no-repeat; }
.toggle-details-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -433px no-repeat; }
.ss-uploadfield-item-edit-all .toggle-details-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -375px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; }
.toggle-details-icon.opened { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1121px no-repeat; }
.ss-uploadfield-item-edit-all .toggle-details-icon.opened { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -359px no-repeat; }
/** -------------------------------------------- Hide preview toggle link by default. May be shown in IE7 stylesheet and forced to show with js if needed -------------------------------------------- */
.cms .Actions > .cms-preview-toggle-link, .cms .cms-navigator > .cms-preview-toggle-link { display: none; }
@ -813,7 +825,7 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; }
.cms-logo span { font-weight: bold; font-size: 12px; line-height: 16px; padding: 2px 0; margin-left: 30px; }
.cms-login-status { border-top: 1px solid #19435c; padding: 12px 0 17px; line-height: 16px; font-size: 11px; }
.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 5px; background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -292px no-repeat; text-indent: -9999em; }
.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 5px; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -292px no-repeat; text-indent: -9999em; }
.cms-menu { z-index: 80; background: #b0bec7; width: 160px; -webkit-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; -moz-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; }
.cms-menu a { text-decoration: none; }
@ -837,12 +849,12 @@ li.class-ErrorPage > a 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-se93fc83bf9.png') 0 -375px no-repeat; vertical-align: middle; }
.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -359px no-repeat; }
.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -375px no-repeat; vertical-align: middle; }
.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -359px no-repeat; }
.cms-menu-list li ul li a { border-top: 1px solid #b6c3cb; }
.cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #236184; background-color: #338dc1; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzMzOGRjMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzI4NzA5OSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; 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: linear-gradient(#338dc1, #287099); }
.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -433px no-repeat; }
.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -1121px no-repeat; }
.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -433px no-repeat; }
.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1121px 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 #2f81b1; border-bottom: 1px solid #1e5270; }
@ -851,44 +863,7 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; }
.cms-menu-list li.current li.current { background: #2e7ead; border-top: 1px solid #2e7ead; border-top: none; }
.cms-menu-list li.current li.current a { font-weight: bold; color: white; }
.cms-menu-list li.current li.first a { border-top: none; }
.cms-menu-list li ul.collapse { display: none; /* // To specific - was overriding collapsed-flyout styles
#Menu-CMSPagesController {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
#Menu-CMSPageAddController {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
#Menu-AssetAdmin {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
#Menu-CMSFileAddController {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
*/ }
.cms-menu-list li ul.collapse { display: none; }
.cms-menu-list li ul.collapse li a { background-image: none; font-size: 11px; padding: 0 10px 0 40px; height: 32px; line-height: 32px; }
.cms-menu-list li ul.collapsed-flyout { display: block; }
.cms-menu-list li ul.collapsed-flyout li a { font-size: 11px; padding: 0 10px 0 16px; height: 32px; line-height: 32px; }
@ -902,14 +877,14 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; }
.cms-content-controls.cms-preview-controls { z-index: 1; background: #eceff1; height: 30px; /* should be set in js Layout to match page actions */ padding: 12px 12px; }
.cms-content-controls .icon-view, .cms-content-controls .preview-selector.dropdown a.chzn-single { white-space: nowrap; }
.cms-content-controls .icon-view:before, .cms-content-controls .preview-selector.dropdown a.chzn-single:before { display: inline-block; float: left; content: ''; width: 23px; height: 17px; overflow: hidden; }
.cms-content-controls .icon-auto:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -108px no-repeat; }
.cms-content-controls .icon-desktop:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -81px no-repeat; }
.cms-content-controls .icon-tablet:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -162px no-repeat; }
.cms-content-controls .icon-mobile:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -189px no-repeat; }
.cms-content-controls .icon-split:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -135px no-repeat; }
.cms-content-controls .icon-edit:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -27px no-repeat; }
.cms-content-controls .icon-preview:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 0 no-repeat; }
.cms-content-controls .icon-window:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -54px no-repeat; }
.cms-content-controls .icon-auto:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -108px no-repeat; }
.cms-content-controls .icon-desktop:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -81px no-repeat; }
.cms-content-controls .icon-tablet:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -162px no-repeat; }
.cms-content-controls .icon-mobile:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -189px no-repeat; }
.cms-content-controls .icon-split:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -135px no-repeat; }
.cms-content-controls .icon-edit:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -27px no-repeat; }
.cms-content-controls .icon-preview:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 0 no-repeat; }
.cms-content-controls .icon-window:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -54px no-repeat; }
.cms-content-controls .cms-navigator { width: 100%; }
.cms-content-controls .preview-selector.dropdown a.chzn-single { text-indent: -200px; }
.cms-content-controls .preview-selector { float: right; border-bottom: none; position: relative; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; margin: 3px 0 0 4px; padding: 0; height: 28px; }
@ -946,7 +921,7 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; }
/* Styling for the preview screen sizes */
.cms-preview { background-color: #eceff1; height: 100%; width: 100%; }
.cms-preview .preview-note { color: #CDD7DC; display: block; font-size: 22px; font-weight: bold; height: 82px; margin-top: -50px; margin-left: -150px; /* half of width */ position: absolute; text-align: center; text-shadow: 0 1px 0 #fff; top: 50%; width: 300px; }
.cms-preview .preview-note { color: #CDD7DC; display: block; font-size: 22px; font-weight: bold; height: 82px; margin-top: -50px; margin-left: -150px; /* half of width */ position: absolute; text-align: center; text-shadow: 0 1px 0 #fff; top: 50%; left: 50%; width: 300px; }
.cms-preview .preview-note span { background: url('../images/sprites-64x64-s88957ee578.png') 0 0 no-repeat; display: block; height: 41px; margin: 0 auto 20px; width: 50px; }
.cms-preview .preview-scroll { height: 100%; overflow: auto; position: relative; width: 100%; }
.cms-preview .preview-scroll .preview-device-outer { height: 100%; width: 100%; }
@ -967,12 +942,141 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; }
.cms-preview.tabletLandscape .preview-scroll .preview-device-outer .preview-device-inner { -webkit-transition: all 0.3s ease-out; -webkit-transition-delay: 1s; -moz-transition: all 0.3s ease-out 1s; -o-transition: all 0.3s ease-out 1s; transition: all 0.3s ease-out 1s; width: 1039px; }
.cms-preview.desktop .preview-scroll .preview-device-outer { -webkit-transition: all 0.3s ease-out; -webkit-transition-delay: 1s; -moz-transition: all 0.3s ease-out 1s; -o-transition: all 0.3s ease-out 1s; transition: all 0.3s ease-out 1s; height: 800px; margin: 0 auto; width: 1024px; }
/********************************************
* Defines the styles for .ss-ui-action-tabset:
* * Site tree action tabs (to perform actions on the site tree)
* * Actions menu (Edit page actions)
*
* Reliant on TabSet.js to apply and remove some classes.
*
* Note: This is a special use case of tabs, so the default tab
* styling should not apply
*
**********************************************/
.cms .ss-ui-action-tabset { float: left; position: relative; /*
Styles for the tab-nav of the site tree implementation
of ss-ui-action-tabset
*/ /* position a checkbox & icon within a tab */ /* Styles for the cms-actions in tree view, to use more limited space.
Title hidden in tree view, until hover/active state added. Active is applied
to the first tab within the template, so there should always be one title
visible. Added and removed with js in TabSet.js */ /****************************************************************
Styles for the actions-menu implementation
of ss-ui-action-tabset
****************************************************************/ }
.cms .ss-ui-action-tabset.multi { /* Style the tab panels */ }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav { -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; overflow: hidden; *zoom: 1; border: 1px solid #b3b3b3; float: left; overflow: visible; padding: 0; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav:active { outline: none; box-shadow: none; -webkit-box-shadow: none; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eaeaea; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; overflow: visible; min-width: 110px; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li:active { outline: none; box-shadow: none; -webkit-box-shadow: none; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; background: #f8f8f8; border-bottom: none !important; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a:active, .cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a span:active { outline: none; box-shadow: none; -webkit-box-shadow: none; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.first { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; border-left: none; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.last { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; border-right: none; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link { color: #444444; display: inline-block; font-weight: bold; line-height: 16px; padding: 5px 10px; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link .ui-no-icon { display: inline-block; float: left; height: 16px; padding: 0 2px; width: 16px; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link .title { display: inline-block; line-height: 18px; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link.view-mode-batchactions-wrapper .title { margin-left: 22px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel { /* Restyle for smaller area*/ background: #f8f8f8 !important; border: 1px solid #b3b3b3; border-top: none; clear: both; display: block; float: left; margin: 0; padding: 10px; padding-top: 15px; position: absolute; top: 30px; width: 202px; z-index: 1; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h3, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h4, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h5 { font-weight: bold; line-height: 16px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h3 { font-size: 13px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .ui-widget-content { background: none; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field .middleColumn { margin: 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field input.text, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field select, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field.checkbox { padding: 0 8px 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field.checkbox input { margin: 2px 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-content-fields { overflow: visible; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .chzn-container-single { width: 100% !important; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-content-actions, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-edit-form { width: 100%; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .parent-mode { padding-top: 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel.first { left: 0; width: 203px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .ui-icon { padding-right: 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .tab-nav-link, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .ss-ui-button { font-size: 12px; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel #PageType ul { padding: 0; }
.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel #PageType ul li { padding: 4px 5px; }
.cms .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav, .cms .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.first { -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; }
.cms .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; }
.cms .ss-ui-action-tabset .batch-check, .cms .ss-ui-action-tabset .ui-icon { display: inline-block; float: left; margin-left: -2px; padding-right: 6px; }
.cms .ss-ui-action-tabset .batch-check { margin: 6px 0px 5px 9px; position: absolute; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar { min-width: 176px; /* for when the scrollbar is present & find dropdown open */ }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li { width: auto; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; overflow: hidden; padding-right: 0; width: 30px; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link.active { -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; width: 110px; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.first, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.last, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.first, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab { padding: 10px 6px; width: 162px; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .field { max-width: 160px; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .ui-icon { padding-right: 0; }
.cms .ss-ui-action-tabset .cms-tree-view-sidebar .last .ui-tabs-panel.ss-ui-action-tab { left: auto; right: 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset { margin-top: 2px; /* Style the panel for actions-menu */ /* Re-align last tab */ }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav { margin: 0; float: left; /* needed for ie but doesnt effect other browsers */ }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li { background: none; border: none; border-bottom: none !important; display: inline; padding: 0; /* Make arrow point in up when nav open */ }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a { text-shadow: white 0 1px 1px; color: #0073c1; font-size: 13px; font-weight: normal; line-height: 24px; padding: 0 25px 0 10px; /* Arrow */ }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover { text-shadow: white 0 10px 10px; color: #005b98; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; border-bottom: 0; content: ""; display: inline-block; height: 16px; margin-left: 6px; width: 16px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ clear: both; display: block; background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; margin: 0; margin-top: 2px; max-width: 250px; padding: 8px 0 2px; position: absolute; z-index: 1; min-width: 190px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h3, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h4, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h3 { font-size: 13px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .ui-widget-content { background: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field .middleColumn { margin: 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field input.text, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field select, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-content-fields { overflow: visible; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .chzn-container-single { width: 100% !important; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-content-actions, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-edit-form { width: 100%; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .parent-mode { padding-top: 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #e6e7e8; margin-bottom: 8px; padding: 0 20px 0 0; margin-right: 10px; margin-left: 10px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; white-space: nowrap; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button { width: 100%; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; background-color: #e0e5e8; outline: none; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .last .ui-tabs-panel.ss-ui-action-tab { left: auto; right: -1px; }
.cms .cms-content-actions .Actions { overflow: visible; }
.ModelAdmin .cms-content-fields { overflow: hidden; }
.ModelAdmin .cms-content-fields .cms-edit-form { overflow-y: auto; overflow-x: hidden; }
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form .resetformaction { margin-right: 0px; }
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content #Form_ImportForm { overflow: hidden; }
.SecurityAdmin .permissioncheckboxset .optionset li, .SecurityAdmin .permissioncheckboxsetfield_readonly .optionset li { float: none; width: auto; }
.permissioncheckboxset h5, .permissioncheckboxsetfield_readonly h5 { margin: 0; }
.permissioncheckboxset .optionset, .permissioncheckboxsetfield_readonly .optionset { overflow: auto; }
.permissioncheckboxset .optionset li, .permissioncheckboxsetfield_readonly .optionset li { float: none; width: auto; clear: both; }
/* For user permissions the readonly checkboxes are set as display none and are replaced with a <span> that has a
green tick icon as a background this is created using compass generated classes and hardcoded in the php */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
admin/images/btn-icon/disk.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

View File

@ -0,0 +1,239 @@
/**
* File: LeftAndMain.ActionTabset.js
*
* Contains rules for .ss-ui-action-tabset, used for:
* * Site tree action tabs (to perform actions on the site tree)
* * Actions menu (Edit page actions)
*
*/
(function($){
$.entwine('ss', function($) {
/**
* Generic rules for all ss-ui-action-tabsets
* * ActionMenus
* * SiteTree ActionTabs
*/
$('.ss-tabset.ss-ui-action-tabset').entwine({
// Ignore tab state so it will not be reopened on form submission.
IgnoreTabState: true,
onadd: function() {
// Make sure the .ss-tabset is already initialised to apply our modifications on top.
this._super();
//Set actionTabs to allow closing and be closed by default
this.tabs({'collapsible': true, 'active': false});
},
onremove: function() {
// Remove all bound events.
// This guards against an edge case where the click handlers are not unbound
// because the panel is still open when the ajax edit form reloads.
var frame = $('.cms').find('iframe');
frame.each(function(index, iframe){
$(iframe).contents().off('click.ss-ui-action-tabset');
});
$(document).off('click.ss-ui-action-tabset');
this._super();
},
/**
* Deal with available vertical space
*/
'ontabsbeforeactivate': function(event, ui) {
this.riseUp(event, ui);
},
/**
* Handle opening and closing tabs
*/
onclick: function(event, ui) {
this.attachCloseHandler(event, ui);
},
/**
* Generic function to close open tabs. Stores event in a handler,
* and removes the bound event once activated.
*
* Note: Should be called by a click event attached to 'this'
*/
attachCloseHandler: function(event, ui) {
var that = this, frame = $('.cms').find('iframe'), closeHandler;
// Create a handler for the click event so we can close tabs
// and easily remove the event once done
closeHandler = function(event) {
var panel, frame;
panel = $(event.target).closest('.ss-ui-action-tabset .ui-tabs-panel');
// If anything except the ui-nav button or panel is clicked,
// close panel and remove handler. We can't close if click was
// within panel, as it might've caused a button action,
// and we need to show its loading indicator.
if (!$(event.target).closest(that).length && !panel.length) {
that.tabs('option', 'active', false); // close tabs
// remove click event from objects it is bound to (iframe's and document)
frame = $('.cms').find('iframe');
frame.each(function(index, iframe){
$(iframe).contents().off('click.ss-ui-action-tabset', closeHandler);
});
$(document).off('click.ss-ui-action-tabset', closeHandler);
}
};
// Bind click event to document, and use closeHandler to handle the event
$(document).on('click.ss-ui-action-tabset', closeHandler);
// Make sure iframe click also closes tab
// iframe needs a special case, else the click event will not register here
if(frame.length > 0){
frame.each(function(index, iframe) {
$(iframe).contents().on('click.ss-ui-action-tabset', closeHandler);
});
}
},
/**
* Function riseUp checks to see if a tab should be opened upwards
* (based on space concerns). If true, the rise-up class is applied
* and a new position is calculated and applied to the element.
*
* Note: Should be called by a tabsbeforeactivate event
*/
riseUp: function(event, ui) {
var elHeight, trigger, endOfWindow, elPos, activePanel, activeTab, topPosition, containerSouth, padding;
// Get the numbers needed to calculate positions
elHeight = $(this).find('.ui-tabs-panel').outerHeight();
trigger = $(this).find('.ui-tabs-nav').outerHeight();
endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger;
elPos = $(this).find('.ui-tabs-nav').offset().top;
activePanel = ui.newPanel;
activeTab = ui.newTab;
if (elPos + elHeight >= endOfWindow && elPos - elHeight > 0){
this.addClass('rise-up');
if (activeTab.position() !== null){
topPosition = -activePanel.outerHeight();
containerSouth = activePanel.parents('.south');
if (containerSouth){
// If container is the southern panel, make tab appear from the top of the container
padding = activeTab.offset().top - containerSouth.offset().top;
topPosition = topPosition-padding;
}
$(activePanel).css('top',topPosition+"px");
}
} else {
// else remove the rise-up class and set top to 0
this.removeClass('rise-up');
if (activeTab.position() !== null){
$(activePanel).css('top','0px');
}
}
return false;
}
});
/**
* ActionMenus
* * Specific rules for ActionMenus, used for edit page actions
*/
$('.cms-content-actions .ss-tabset.ss-ui-action-tabset').entwine({
/**
* Make necessary adjustments before tab is activated
*/
'ontabsbeforeactivate': function(event, ui) {
this._super(event, ui);
//Set the position of the opening tab (if it exists)
if($(ui.newPanel).length > 0){
$(ui.newPanel).css('left', ui.newTab.position().left+"px");
}
}
});
/**
* SiteTree ActionTabs
* Specific rules for site tree action tabs. Applies to tabs
* within the expanded content area, and within the sidebar
*/
$('.cms-actions-row.ss-tabset.ss-ui-action-tabset').entwine({
/**
* Make necessary adjustments before tab is activated
*/
'ontabsbeforeactivate': function(event, ui) {
this._super(event, ui);
// Remove tabset open classes (Last gets a unique class
// in the bigger sitetree. Remove this if we have it)
$(this).closest('.ss-ui-action-tabset')
.removeClass('tabset-open tabset-open-last');
}
});
/**
* SiteTree ActionTabs: expanded
* * Specific rules for siteTree actions within the expanded content area.
*/
$('.cms-content-fields .ss-tabset.ss-ui-action-tabset').entwine({
/**
* Make necessary adjustments before tab is activated
*/
'ontabsbeforeactivate': function(event, ui) {
this._super(event, ui);
if($( ui.newPanel).length > 0){
if($(ui.newTab).hasClass("last")){
// Align open tab to the right (because opened tab is last)
$(ui.newPanel).css({'left': 'auto', 'right': '0px'});
// Last needs to be styled differently when open, so apply a unique class
$(ui.newPanel).parent().addClass('tabset-open-last');
}else{
// Assign position to tabpanel based on position of relivent active tab item
$(ui.newPanel).css('left', ui.newTab.position().left+"px");
// If this is the first tab, make sure the position doesn't include border
// (hard set position to 0 ), and add the tab-set open class
if($(ui.newTab).hasClass("first")){
$(ui.newPanel).css('left',"0px");
$(ui.newPanel).parent().addClass('tabset-open');
}
}
}
}
});
/**
* SiteTree ActionTabs: sidebar
* * Specific rules for when the site tree actions panel appears in
* * the side-bar
*/
$('.cms-tree-view-sidebar .cms-actions-row.ss-tabset.ss-ui-action-tabset').entwine({
// If actions panel is within the sidebar, apply active class
// to help animate open/close on hover
'from .ui-tabs-nav li': {
onhover: function(e) {
$(e.target).parent().find('li .active').removeClass('active');
$(e.target).find('a').addClass('active');
}
},
/**
* Make necessary adjustments before tab is activated
*/
'ontabsbeforeactivate': function(event, ui) {
this._super(event, ui);
// Reset position of tabs, else anyone going between the large
// and the small sitetree will see broken tabs
// Apply styles with .css, to avoid overriding currently applied styles
$(ui.newPanel).css({'left': 'auto', 'right': 'auto'});
if($(ui.newPanel).length > 0){
$(ui.newPanel).parent().addClass('tabset-open');
}
}
});
});
}(jQuery));

View File

@ -15,9 +15,8 @@
// Force initialization of certain UI elements to avoid layout glitches
this.find('.cms-tabset').redrawTabs();
this.find('.ss-ui-tabs-nav').redraw();
this._super();
},
redraw: function() {

View File

@ -205,15 +205,19 @@
});
/**
* Hide tabs when only one is available
* Hide tabs when only one is available.
* Special case is actiontabs - tabs between buttons, where we want to have
* extra options hidden within a tab (even if only one) by default.
*/
$('.cms-edit-form .ss-tabset').entwine({
onmatch: function() {
if (!this.hasClass('ss-ui-action-tabset')) {
var tabs = this.find("> ul:first");
if(tabs.children("li").length == 1) {
tabs.hide().parent().addClass("ss-tabset-tabshidden");
}
}
this._super();
},

View File

@ -1,27 +1,35 @@
(function($) {
$.entwine('ss', function($) {
/**
* Takes form fields with a title attribute, extracts it, and displays
* it as inline help text below the field.
* Converts an inline field description into a tooltip
* which is shown on hover over any part of the field container,
* as well as when focusing into an input element within the field container.
*
* Note that some fields don't have distinct focusable
* input fields (e.g. GridField), and aren't compatible
* with showing tooltips.
*/
$(".cms form .field .middleColumn > [title]").entwine({
$(".cms .field.cms-description-tooltip").entwine({
onmatch: function() {
var title = this.prop("title");
var field = this.closest(".field");
if(title && title.length && field.has('.help').length == 0) {
var span = $("<span></span>", {
"class": "help",
"text": title
});
field.append(span);
this.removeProp("title");
var descriptionEl = this.find('.description'), inputEl, tooltipEl;
if(descriptionEl.length) {
this
// TODO Remove title setting, shouldn't be necessary
.attr('title', descriptionEl.text())
.tooltip({content: descriptionEl.html()});
descriptionEl.remove();
}
this._super();
}
});
$(".cms .field.cms-description-tooltip :input").entwine({
onfocusin: function(e) {
this.closest('.field').tooltip('open');
},
onfocusout: function(e) {
this.closest('.field').tooltip('close');
}
});
});
}(jQuery));

View File

@ -440,7 +440,7 @@ jQuery.noConflict();
* Can be hooked into an ajax 'success' callback.
*/
handleAjaxResponse: function(data, status, xhr) {
var self = this, url, activeTabs, guessFragment;
var self = this, url, selectedTabs, guessFragment;
// Pseudo-redirects via X-ControllerURL might return empty data, in which
// case we'll ignore the response
@ -571,19 +571,22 @@ jQuery.noConflict();
saveTabState: function() {
if(typeof(window.sessionStorage)=="undefined" || window.sessionStorage == null) return;
var activeTabs = [], url = this._tabStateUrl();
var selectedTabs = [], url = this._tabStateUrl();
this.find('.cms-tabset,.ss-tabset').each(function(i, el) {
var id = $(el).attr('id');
if(!id) return; // we need a unique reference
if(!$(el).data('tabs')) return; // don't act on uninit'ed controls
if($(el).data('ignoreTabState')) return; // allow opt-out
activeTabs.push({id:id, active:$(el).tabs('option', 'active')});
// Allow opt-out via data element or entwine property.
if($(el).data('ignoreTabState') || $(el).getIgnoreTabState()) return;
selectedTabs.push({id:id, selected:$(el).tabs('option', 'selected')});
});
if(activeTabs) {
if(selectedTabs) {
var tabsUrl = 'tabs-' + url;
try {
window.sessionStorage.setItem(tabsUrl, JSON.stringify(activeTabs));
window.sessionStorage.setItem(tabsUrl, JSON.stringify(selectedTabs));
} catch(err) {
if (err.code === DOMException.QUOTA_EXCEEDED_ERR && window.sessionStorage.length === 0) {
// If this fails we ignore the error as the only issue is that it
@ -606,12 +609,12 @@ jQuery.noConflict();
var self = this, url = this._tabStateUrl(),
data = window.sessionStorage.getItem('tabs-' + url),
activeTabs = data ? JSON.parse(data) : false;
if(activeTabs) {
$.each(activeTabs, function(i, activeTab) {
var el = self.find('#' + activeTab.id);
selectedTabs = data ? JSON.parse(data) : false;
if(selectedTabs) {
$.each(selectedTabs, function(i, selectedTab) {
var el = self.find('#' + selectedTab.id);
if(!el.data('tabs')) return; // don't act on uninit'ed controls
el.tabs('option', 'active', activeTab.active);
el.tabs('select', selectedTab.selected);
});
}
},
@ -925,25 +928,6 @@ jQuery.noConflict();
});
/**
* Simple toggle link, which points to a DOm element by its ID selector
* in the href attribute (which doubles as an anchor link to that element).
*/
$('.cms .cms-help-toggle').entwine({
onmatch: function() {
this._super();
$(this.attr('href')).hide();
},
onunmatch: function() {
this._super();
},
onclick: function(e) {
$(this.attr('href')).toggle();
e.preventDefault();
}
});
/**
* Allows to lazy load a panel, by leaving it empty
* and declaring a URL to load its content via a 'url' HTML5 data attribute.
@ -1025,7 +1009,7 @@ jQuery.noConflict();
activate: function(e, ui) {
// Usability: Hide actions for "readonly" tabs (which don't contain any editable fields)
var actions = $(this).closest('form').find('.Actions');
if($(ui.tab).closest('li').hasClass('readonly')) {
if($(ui.newTab).closest('li').hasClass('readonly')) {
actions.fadeOut();
} else {
actions.show();

View File

@ -0,0 +1,20 @@
(function($) {
$.entwine('ss', function($){
$('.memberdatetimeoptionset').entwine({
onmatch: function() {
this.find('.description .toggle-content').hide();
this._super();
}
});
$('.memberdatetimeoptionset .toggle').entwine({
onclick: function(e) {
jQuery(this).closest('.description').find('.toggle-content').toggle();
return false;
}
});
});
}(jQuery));

View File

@ -83,17 +83,17 @@
}
// Create missing elements.
if (this.options.alternate.text) {
this.buttonElement.append(
"<span class='ui-button-text-alternate ui-button-text'>" + this.options.alternate.text + "</span>"
);
}
if (this.options.alternate.icon) {
this.buttonElement.append(
"<span class='ui-button-icon-alternate ui-button-icon-primary ui-icon btn-icon-"
+ this.options.alternate.icon + "'></span>"
);
}
if (this.options.alternate.text) {
this.buttonElement.append(
"<span class='ui-button-text-alternate ui-button-text'>" + this.options.alternate.text + "</span>"
);
}
this._refreshAlternate();
},

View File

@ -1,11 +1,14 @@
.SecurityAdmin {
// Same rules in .member-profile-form
.permissioncheckboxset, .permissioncheckboxsetfield_readonly {
.permissioncheckboxset, .permissioncheckboxsetfield_readonly {
h5 {
margin: 0;
}
.optionset {
overflow: auto;
li {
float: none;
width: auto;
}
clear: both;
}
}
}

324
admin/scss/_actionTabs.scss Normal file
View File

@ -0,0 +1,324 @@
/********************************************
* Defines the styles for .ss-ui-action-tabset:
* * Site tree action tabs (to perform actions on the site tree)
* * Actions menu (Edit page actions)
*
* Reliant on TabSet.js to apply and remove some classes.
*
* Note: This is a special use case of tabs, so the default tab
* styling should not apply
*
**********************************************/
$border: 1px solid darken(#D9D9D9, 15%);
.cms {
.ss-ui-action-tabset{
float:left;
position:relative;
/*
Styles for the tab-nav of the site tree implementation
of ss-ui-action-tabset
*/
&.multi{
ul.ui-tabs-nav{
@include border-radius(3px);
@include clearfix;
border:$border;
float:left;
overflow:visible;
padding:0;
&:active{
outline:none;
box-shadow:none;
-webkit-box-shadow: none;
}
li{
@include background-image(linear-gradient(top, #f8f8f8, #D9D9D9));
@include border-radius(0);
background: #eaeaea;
border: none;
border-right:1px solid #eee;
border-left: $border;
margin:0;
overflow: visible;
min-width: 110px; //To make label width more uniform, but allow growth if needed
&:active{
outline:none;
box-shadow:none;
-webkit-box-shadow: none;
}
&.ui-state-active{
@include border-bottom-left-radius(0px);
@include border-bottom-right-radius(0px);
background:#f8f8f8;
border-bottom:none !important; //jquery-ui style has important on it
a {
@include border-bottom-left-radius(0px);
@include border-bottom-right-radius(0px);
&:active, span:active{
outline:none;
box-shadow:none;
-webkit-box-shadow: none;
}
}
}
&.first{
@include border-top-left-radius(3px);
@include border-bottom-left-radius(3px);
border-left:none;
}
&.last{
@include border-top-right-radius(3px);
@include border-bottom-right-radius(3px);
border-right:none;
}
a.tab-nav-link{
color:$color-text;
display:inline-block;
font-weight:bold;
line-height:16px;
padding: 5px 10px;
.ui-no-icon { //for links that don't have icons (ie the batch actions field)
display: inline-block;
float: left;
height: 16px;
padding: 0 2px;
width: 16px;
}
.title{
display:inline-block;
line-height: 18px;
}
&.view-mode-batchactions-wrapper .title {
margin-left: 22px;
}
}
}
}
/* Style the tab panels */
.ss-ui-action-tab.ui-tabs-panel{
@include tightSpacing;
background:#f8f8f8 !important; //Because ie7 doesn't understand what the 'C' in CSS stands for
border:$border;
border-top:none;
clear:both;
display:block;
float:left;
margin:0;
padding:10px;
padding-top:15px;
position:absolute;
top:30px;
width:202px; //Width is approx the size of two tab nav panels.
z-index:1;
&.first {
left: 0;
width: 203px; //Width is approx the size of two tab nav panels with 1px border.
}
.ui-icon {
padding-right: 0;
}
.tab-nav-link, .ss-ui-button {
font-size: 12px;
}
#PageType ul{
padding:0;
li{
padding:4px 5px;
}
}
}
}
// Classes applied by javascript
&.tabset-open {
ul.ui-tabs-nav,
ul.ui-tabs-nav li.first {
@include border-bottom-left-radius(0);
}
}
&.tabset-open-last {
ul.ui-tabs-nav li.last {
@include border-bottom-right-radius(0);
}
}
/* position a checkbox & icon within a tab */
.batch-check, .ui-icon {
display: inline-block;
float:left;
margin-left: -2px;
padding-right: 6px;
}
.batch-check {
margin: 6px 0px 5px 9px;
position: absolute;
}
/* Styles for the cms-actions in tree view, to use more limited space.
Title hidden in tree view, until hover/active state added. Active is applied
to the first tab within the template, so there should always be one title
visible. Added and removed with js in TabSet.js */
.cms-tree-view-sidebar{
min-width: 176px; /* for when the scrollbar is present & find dropdown open */
.ss-ui-action-tabset{
ul.ui-tabs-nav{
>li{
width: auto;
a.tab-nav-link{
@include box-sizing(border-box);
@include duration(0.5s);
overflow:hidden;
padding-right:0;
width:30px;
&.active{
@include duration(0.5s);
width:110px;
}
}
}
}
&.tabset-open, &.tabset-open-last {
ul.ui-tabs-nav,
ul.ui-tabs-nav li.first,
ul.ui-tabs-nav li.last {
@include border-bottom-right-radius(0);
@include border-bottom-left-radius(0);
}
}
}
.ui-tabs .ui-tabs-panel.ss-ui-action-tab {
padding:10px 6px;
width:162px;
.field {
max-width:160px;
}
.ui-icon {
padding-right: 0;
}
}
.last .ui-tabs-panel.ss-ui-action-tab {
left:auto;
right:0;
}
}
/****************************************************************
Styles for the actions-menu implementation
of ss-ui-action-tabset
****************************************************************/
&.action-menus.ss-tabset {
margin-top: 2px;
//Style the tabs naivgation
ul.ui-tabs-nav{
margin: 0;
float: left; /* needed for ie but doesnt effect other browsers */
li{
background: none;
border: none;
border-bottom: none !important; //over-ride jquery-ui style (which also has important)
display: inline;
padding: 0;
&:hover, &:active{
@include box-shadow(none);
outline:none;
}
a{
@include text-shadow(#fff 0 1px 1px);
color: $color-text-blue-link;
font-size: 13px;
font-weight: normal;
line-height: 24px;
padding:0 25px 0 10px;
&:hover, &:active{
@include box-shadow(none);
outline:none;
}
&:hover{
@include text-shadow(#fff 0 10px 10px);
color: darken($color-text-blue-link,8%);
}
/* Arrow */
&:after {
background: sprite($sprites32, arrow_down_lighter) no-repeat;
border-bottom: 0;
content: "";
display: inline-block;
height: 16px;
margin-left: 6px;
width: 16px;
}
&:hover:after {
background: sprite($sprites32, arrow_down_darker) no-repeat;
}
}
/* Make arrow point in up when nav open */
&.ui-state-active a {
&:after {
background: sprite($sprites32, arrow_up_lighter) no-repeat;
}
&:hover:after {
background: sprite($sprites32, arrow_up_darker) no-repeat;
}
}
}
}
/* Style the panel for actions-menu */
.ui-tabs-panel{
@include clearfix;
@include border-top-radius(3px);
@include border-bottom-radius(0);
@include tightSpacing;
@extend .button-no-style;
clear:both;
display:block;
background-color: $tab-panel-texture-color;
border:1px solid #ccc;
border-bottom:1px solid $tab-panel-texture-color;
margin:0;
margin-top:2px;
max-width:250px;
padding: 8px 0 2px;
position:absolute;
z-index:1;
min-width: 190px;
//Styles for the information displayed in popup above the main action buttons
.cms-sitetree-information {
border-bottom: 1px solid lighten($color-light-separator, 8%);
margin-bottom: 8px;
padding: 0 20px 0 0;
margin-right: 10px;
margin-left: 10px;
p.meta-info {
color: #999;
font-size: 11px;
line-height: 16px;
margin-bottom: 8px;
white-space: nowrap;
}
}
button.ss-ui-button{
width: 100%;
&:hover, &:focus, &:active{
@include box-shadow(none);
background-color: darken($tab-panel-texture-color,4%);
outline:none;
}
}
}
/* Re-align last tab */
.last .ui-tabs-panel.ss-ui-action-tab{
left:auto;
right:-1px;
}
}
}
.cms-content-actions .Actions{
overflow:visible; //for testing (changed in another branch)
}
}

View File

@ -42,7 +42,7 @@ form.nostyle {
.middleColumn {
margin-left: 0;
}
.help {
.description {
margin-left: 0;
}
}
@ -90,21 +90,16 @@ form.nostyle {
// Additional help text to clarify the field intent,
// displayed alongside the field (rather than in a tooltip)
.help {
.description {
clear: both;
color: lighten($color-text, 20%);
display: block;
font-style: italic;
margin: $grid-y/2 0 0 $grid-x*23;
}
&.help {
label.right {
clear: both;
color: lighten($color-text, 20%);
display: block;
font-style: italic;
margin: $grid-y/2 0 0 $grid-x*23;
margin: $grid-y/2 0 0 $grid-x*23; // left align with .middleColumn
}
&.checkbox .description, &.ss-gridfield .description {
margin-left: 0;
}
input.text,
@ -115,10 +110,10 @@ form.nostyle {
width: 100%;
max-width: $grid-x * 64;
@include box-sizing(border-box);
&.help {
&.description {
margin:0; //overrides help class adding left margin to the textarea input.
}
.help {
.description {
max-width: $grid-x * 64;
}
}
@ -285,6 +280,44 @@ form.small .field, .field.small {
* ---------------------------------------------------- */
.cms {
.button-no-style{
button{
@include border-radius(0);
background: none;
border: none;
color: $color-text-blue-link;
display: block;
font-weight:normal;
margin:0;
outline:none;
padding-left:10px;
padding-right:10px;
text-align: left;
text-shadow: none;
white-space:normal;
&.ss-ui-action-destructive{
color: darken($color-error,25%);
}
span{
padding-left:0;
padding-right:0;
}
&:hover, &:focus, &:active{
@include box-shadow(none);
outline:none;
background:none;
border:none;
}
&.loading {
background: transparent url(../../images/network-save.gif) no-repeat $grid-x center;
.ui-button-text {
padding-left: 16px /* icon */ + ($grid-x/2);
}
}
}
}
.Actions, .cms-actions-row {
> * {
@ -300,7 +333,7 @@ form.small .field, .field.small {
.Actions {
min-height: 30px;
overflow: visible;
overflow: auto;
padding: $grid-x $grid-y * 1.5;
}
.south .Actions, .ui-tabs-panel .Actions, .ui-tabs-panel iframe .Actions {
@ -328,7 +361,9 @@ form.small .field, .field.small {
}
.ss-ui-button {
font-size: 12px;
margin-top:0px;
padding: 5px 10px;
font-weight: bold;
text-decoration: none;
line-height: $grid-y * 2;
@ -339,6 +374,22 @@ form.small .field, .field.small {
background-color: $color-button-generic;
white-space: nowrap;
.ui-icon, .ui-button-text {
display: inline-block;
line-height: $grid-x*2;
padding: 0;
}
.ui-icon {
width: 16px;
padding: 0 2px;
position: relative;
left: -2px;
margin-top: 0;
top: 0;
height: 16px;
float: left;
}
@include background(
linear-gradient(color-stops(
lighten($color-button-generic, 10%),
@ -412,7 +463,6 @@ form.small .field, .field.small {
&.ss-ui-button-small {
.ui-button-text {
padding: ($grid-y/4) ($grid-x/4);
font-size: $font-base-size - 2;
}
}
@ -530,7 +580,7 @@ form.small .field, .field.small {
float: none;
margin-left: 0;
}
.help {
.description {
margin-left: 0;
}
}
@ -753,4 +803,3 @@ fieldset.switch-states{
}
//old web-kit browser fix
@-webkit-keyframes bugfix { from { position: relative; } to { position: relative; } }

View File

@ -319,44 +319,6 @@
line-height: 32px;
}
}
/* // To specific - was overriding collapsed-flyout styles
#Menu-CMSPagesController {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
#Menu-CMSPageAddController {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
#Menu-AssetAdmin {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
#Menu-CMSFileAddController {
a {
background-image:none;
font-size: 11px;
padding: 0 10px 0 40px;
height: 32px;
line-height: 32px;
}
}
*/
}
/* Style applied to the menu flyout only when the collapsed setting */

View File

@ -108,7 +108,167 @@
transition: $properties;
}
@mixin duration($time, $webkit:true){
@if($webkit){
-webkit-transition-duration: $time;
}
-moz-transition-duration: $time;
-o-transition-duration: $time;
transition-duration: $time;
}
/*Mixin used to generate slightly smaller text and forms
Used in side panels and action tabs
*/
@mixin tightSpacing{
h3,h4,h5 {
font-weight: bold;
line-height: $grid-y * 2;
}
h3 {
font-size: $font-base-size + 1;
}
h4 {
font-size: $font-base-size;
margin:5px 0;
}
.ui-widget-content {
background: none;
}
.field {
/*
* Fields are more compressed in some areas compared to the
* main content editing window so the below alters the internal
* spacing of the fields so we can move that spacing to between
* the form fields rather than padding
*/
border-bottom:none;
@include box-shadow(none);
label {
float: none;
width: auto;
font-size: 12px;
padding: 0 $grid-x 4px 0;
&.extra-details{
overflow:hidden;
margin-top:10px;
display: block;
color: lighten($color-text, 35%);
font-style:italic;
font-weight:normal;
font-size:1em;
float:left;
@include text-shadow(none);
&.fill{
&:before{
color:#fff;
content: '?';
font-size:12px;
@include box-sizing('border-box');
padding-left:3px;
padding-right:3px;
display:block;
float:left;
@include text-shadow(none);
@include border-radius(50px);
background-color:lighten($color-text, 45%);
width:15px;
height:15px;
margin-right:5px;
margin-bottom:5px;
}
}
}
}
.middleColumn {
margin: 0;
}
input.text,
select,
textarea {
padding: 5px;
font-size: 11px;
}
&.checkbox {
padding: 0 8px 0;
input {
margin: 2px 0;
}
}
}
.fieldgroup {
.fieldgroup-field {
padding: 0;
.field {
margin: 0;
padding: 0;
}
}
}
/* Restyle for smaller area*/
.cms-content-fields{
overflow:visible;
}
.chzn-container-single{
width:100% !important;
.chzn-single{
padding: 0 0 0 5px;
float:none;
}
}
.cms-content-actions, .cms-preview-controls{
padding:0;
height:auto;
border:none;
@include box-shadow(none);
}
.cms-edit-form{
width:100%;
}
.CompositeField{
margin:0;
padding:0;
float:none;
}
.parent-mode{
padding-top:0;
}
.treedropdown, .SelectionGroup li.selected div.field{
margin:10px 0 0 0;
//@include box-shadow(inset 0 1px 0 #fff, 0 1px 1px rgba(0,0,0,0.1));
.treedropdownfield-title{
position:absolute;
z-index:2;
padding:5px;
}
.treedropdownfield-panel{
margin-top:11px;
}
.treedropdownfield-toggle-panel-link{
background:none;
border-left:none;
padding:5px 3px;
.ui-icon{
float:right;
opacity:0.7;
}
}
}
.cms-add-form ul.SelectionGroup{
padding-left:0;
padding-right:0;
overflow:visible;
border-bottom:none;
}
}

View File

@ -226,6 +226,7 @@
text-align: center;
text-shadow: 0 1px 0 #fff;
top: 50%;
left: 50%;
width: 300px;
span {
background: sprite($sprites64, preview) no-repeat;

View File

@ -54,16 +54,18 @@ body.cms {
* Helpers
* -------------------------------------------- */
.cms-helper-hide-actions {
.Actions {
display: none;
}
}
.hide {
display: none;
}
.cms-helper-hide-actions {
.Actions {
@extend .hide;
}
}
/** --------------------------------------------
* Panels Styles
* -------------------------------------------- */
@ -159,6 +161,7 @@ body.cms {
.ui-tabs-panel {
padding: $grid-x 0;
background: transparent; // default it's white
border: 0; // suppress default borders
&.cms-edit-form {
padding: 0;
}
@ -247,7 +250,7 @@ body.cms {
}
}
.cms-edit-form, .cms-content-fields {
.cms-edit-form, .cms-content-fields { /*not sure if .cms-content-fields effects other areas*/
.cms-panel-padded { /* Has padded area inside it */
padding: 0;
margin: 0;
@ -539,6 +542,7 @@ p.message {
overflow: auto;
}
}
#PageType {
ul {
padding-left: 20px;
@ -606,6 +610,7 @@ p.message {
min-height: 29px;
display: block;
margin: 0 0 15px 0;
padding-bottom: 9px;
@include doubleborder(bottom, $color-light-separator, $box-shadow-shine);
@include legacy-pie-clearfix();
@ -993,6 +998,15 @@ form.member-profile-form {
}
}
.memberdatetimeoptionset {
.description {
font-style: normal;
}
.toggle {
font-size: $font-base-size - 1;
}
}
.cms {
.cms-content {
border-right: 1px solid #BBB;
@ -1575,13 +1589,9 @@ form.small {
display:none; //hides find button - redundant functionality
}
p button#action_export {
margin-top:$grid-y*2;
span.btn-icon-download-csv {
height:17px; //exact height of icon
}
.ui-button-text {
padding-left:26px; //to accomodate wider export icon
}
}
}

View File

@ -45,16 +45,16 @@
z-index: 100000;
}
& a.ui-state-hover {
a.ui-state-hover {
border-color: transparent;
background: transparent;
& .ui-icon-closethick {
.ui-icon-closethick {
background: sprite($sprites32, dialog-close-over) no-repeat;
}
}
& .ui-icon-closethick {
.ui-icon-closethick {
background: sprite($sprites32, dialog-close) no-repeat;
width: 30px;
height: 30px;

View File

@ -1,116 +0,0 @@
/**
* This file defines CMS-specific customizations to the jQuery UI theme.
* Every rule should contain ONLY overwritten jQuery UI rules (with 'ui-' prefix).
*
* This file should be fairly short, as we're using our own custom jQuery UI theme already.
* TODO Add theme reference
*
* Use _style.scss to add more generic style information,
* and read the jQuery UI theming API: http://jqueryui.com/docs/Theming/API
*/
<<<<<<< HEAD
.cms {
.ui-tabs {
padding: 0;
background: none;
.ui-widget-header {
border: 0;
background: none;
}
.ui-tabs-nav {
margin: 0;
padding: 0;
li {
top: 0;
border-bottom: 0 !important;
a {
padding: 0 15px;
}
}
&.ui-state-active {
border-color: $color-medium-separator;
}
}
=======
.ui-widget-content,
.ui-widget {
color: $color-text;
font-size: $font-base-size;
font-family: $font-family;
border: 0;
}
.ui-widget-header {
background-color: darken($color-widget-bg, 20%);
padding: 8px 8px 6px 8px;
border-bottom: 2px solid darken($color-widget-bg, 35%);
@include background-image(
linear-gradient(darken($color-widget-bg, 5%), darken($color-widget-bg, 30%))
);
border-bottom: 3px solid darken($color-widget-bg, 50%);
padding: 8px;
@include border-radius(0);
>>>>>>> ENHANCEMENT Tab style consolidation and design consistency
& .ui-dialog-title {
padding: 6px 0;
text-shadow: lighten($color-base, 10%) 1px 1px 0;
}
& a.ui-dialog-titlebar-close {
position: absolute;
top: -8px;
right: -15px;
width: 30px;
height: 30px;
z-index: 100000;
}
& a.ui-state-hover {
border-color: transparent;
background: transparent;
& .ui-icon-closethick {
background: sprite($sprites32, dialog-close-over) no-repeat;
}
}
& .ui-icon-closethick {
background: sprite($sprites32, dialog-close) no-repeat;
width: 30px;
height: 30px;
}
}
.ui-state-hover {
cursor: pointer;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
color: $color-text;
font-size: $font-base-size;
font-family: $font-family;
}
.ui-accordion {
.ui-accordion-header {
border-color: $color-button-generic-border;
margin-bottom: 0;
}
.ui-accordion-content {
border: 1px solid $color-button-generic-border;
border-top: none;
}
}

View File

@ -1,10 +1,58 @@
@import 'themes/default';
@import 'ieShared';
@import "compass/utilities/sprites/sprite-img";
@import "sprites.scss";
html {
overflow: hidden;
}
//add line below the cms-content-toolbar
.cms-content-toolbar {
padding-bottom:5px;
}
.cms-menu-list{
li{
list-style-type: none;
width: 100%;
float: left;
margin: 0px;
padding: 0px;
}
}
/* Site tree
------------------------- */
//fix for the tree view modes not displaying inline
.cms-tree-view-modes {
div {
float:left;
}
span {
float:left;
padding-top:5px;
}
}
.cms-panel-content .cms-tree{
li{
width:200px;
overflow:hidden;
float:left;
display:inline;
}
}
// fix jstree themeroller plugin bug: tree disappear in IE7
.jstree li a .ui-icon {
text-indent: 0px !important;
}
/* Forms and files area
-----------------------------*/
.field {
input.text,
textarea,
@ -41,7 +89,6 @@ select {
float:left;
}
//fix for the tree view modes not displaying inline
.cms-tree-view-modes {
div {
@ -84,6 +131,9 @@ select {
text-indent: 0px !important;
}
/* Gridfield
------------------------- */
.cms table.ss-gridfield-table {
tbody td {
// Overrule width: 100% setting to trigger "shrink fit"
@ -104,8 +154,6 @@ select {
margin: -1px -5px;
}
//fix for edit and delete icons
.cms .ss-gridfield table.ss-gridfield-table tbody {
td {
@ -171,16 +219,6 @@ table.ss-gridfield-table {
}
}
//fix for model admin filter styling
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content {
.cms-search-form {
overflow:hidden;
input {
width:160px;
}
}
}
//fix for view children arrow in pages list view
.cms .ss-gridfield table.ss-gridfield-table tbody {
td {
@ -194,6 +232,25 @@ table.ss-gridfield-table {
}
}
.ss-ui-button{
&.ss-gridfield-button-filter{
border:none !important;
}
}
//fix for model admin filter styling
.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content {
.cms-search-form {
overflow:hidden;
input {
width:160px;
}
}
}
// CMS Content header & tab fix
.cms-content-header {
h2 {
@ -222,4 +279,41 @@ table.ss-gridfield-table {
.cms .Actions > .cms-preview-toggle-link{
display:block;
}
@include IEVerticalPanelText;
//IE7 can't use before and after. Compromise
.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset {
width: 190px;
ul.ui-tabs-nav {
a.ui-tabs-anchor{
background: transparent url(../images/sprites-32x32/arrow_down_lighter.png) no-repeat right top;
&:hover {
background: transparent url(../images/sprites-32x32/arrow_down_darker.png) no-repeat right top;
}
}
.ui-state-active a.ui-tabs-anchor {
background: transparent url(../images/sprites-32x32/arrow_up_lighter.png) no-repeat right top;
&:hover {
background: transparent url(../images/sprites-32x32/arrow_up_darker.png) no-repeat right top;
}
}
}
.ui-tabs-panel button.ss-ui-button {
width: 190px; /* Width 100% not calculating by ie7 */
}
}
/* Tempory fix as jquery loads too slow to add icons */
button.ui-button-text-icon-primary {
padding-left: 30px !important;
span.ui-button-icon-primary {
position: absolute !important;
top: 5px !important;
left: 8px !important;
}
.ui-button-text {
margin-left: 0 !important;
}
}

View File

@ -55,5 +55,6 @@ $experimental-support-for-svg: true;
@import "tree.scss";
@import "menu.scss";
@import "preview.scss";
@import "actionTabs.scss";
@import "ModelAdmin.scss";
@import "SecurityAdmin.scss";

View File

@ -362,6 +362,7 @@ class Injector {
// EXCEPT when there's already an existing instance at this id.
// if so, we need to instantiate and replace immediately
if (isset($this->serviceCache[$id])) {
$this->updateSpecConstructor($spec);
$this->instantiate($spec, $id);
}
}
@ -404,6 +405,20 @@ class Injector {
}
}
/**
* Update a class specification to convert constructor configuration information if needed
*
* We do this as a separate process to avoid unneeded calls to convertServiceProperty
*
* @param array $spec
* The class specification to update
*/
protected function updateSpecConstructor(&$spec) {
if (isset($spec['constructor'])) {
$spec['constructor'] = $this->convertServiceProperty($spec['constructor']);
}
}
/**
* Recursively convert a value into its proper representation with service references
* resolved to actual objects
@ -468,7 +483,7 @@ class Injector {
$constructorParams = $spec['constructor'];
}
$object = $this->objectCreator->create($this, $class, $constructorParams);
$object = $this->objectCreator->create($class, $constructorParams);
// figure out if we have a specific id set or not. In some cases, we might be instantiating objects
// that we don't manage directly; we don't want to store these in the service cache below
@ -735,10 +750,17 @@ class Injector {
if (($type && $type == 'prototype') || !$asSingleton) {
if ($spec && $constructorArgs) {
$spec['constructor'] = $constructorArgs;
} else {
// convert any _configured_ constructor args.
// we don't call this for get() calls where someone passes in
// constructor args, otherwise we end up calling convertServiceParams
// way too often
$this->updateSpecConstructor($spec);
}
return $this->instantiate($spec, $serviceName, !$type ? 'prototype' : $type);
} else {
if (!isset($this->serviceCache[$serviceName])) {
$this->updateSpecConstructor($spec);
$this->instantiate($spec, $serviceName);
}
return $this->serviceCache[$serviceName];
@ -750,6 +772,7 @@ class Injector {
$this->load(array($name => $config));
if (isset($this->specs[$name])) {
$spec = $this->specs[$name];
$this->updateSpecConstructor($spec);
return $this->instantiate($spec, $name);
}
}
@ -816,10 +839,10 @@ class InjectionCreator {
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create(Injector $injector, $class, $params = array()) {
public function create($class, $params = array()) {
$reflector = new ReflectionClass($class);
if (count($params)) {
return $reflector->newInstanceArgs($injector->convertServiceProperty($params));
return $reflector->newInstanceArgs($params);
}
return $reflector->newInstance();
}
@ -833,10 +856,10 @@ class SilverStripeInjectionCreator {
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create(Injector $injector, $class, $params = array()) {
public function create($class, $params = array()) {
$class = Object::getCustomClass($class);
$reflector = new ReflectionClass($class);
return $reflector->newInstanceArgs($injector->convertServiceProperty($params));
return $reflector->newInstanceArgs($params);
}
}

View File

@ -7,6 +7,9 @@
/** ----------------------------------------------- Typography. ------------------------------------------------ */
/** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
/*Mixin used to generate slightly smaller text and forms
Used in side panels and action tabs
*/
.ss-uploadfield-view-allowed-extensions { padding-top: 20px; clear: both; max-width: 750px; display: block; }
#AssetUploadField { border-bottom: 0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; padding: 12px; }

View File

@ -8,27 +8,28 @@
/** ----------------------------------------------- Typography. ------------------------------------------------ */
/** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
/*Mixin used to generate slightly smaller text and forms
Used in side panels and action tabs
*/
.cms .ss-gridfield > div { margin-bottom: 36px; }
.cms .ss-gridfield > div.addNewGridFieldButton { margin-bottom: 12px; }
.cms .ss-gridfield > div.addNewGridFieldButton:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
*:first-child .cms .ss-gridfield > div.addNewGridFieldButton { zoom: 1; }
.cms .ss-gridfield > div.addNewGridFieldButton { margin-bottom: 0; }
.cms .ss-gridfield > div.addNewGridFieldButton .action { margin-bottom: 12px; }
.cms .ss-gridfield[data-selectable] tr.ui-selected, .cms .ss-gridfield[data-selectable] tr.ui-selecting { background: #FFFAD6 !important; }
.cms .ss-gridfield[data-selectable] td { cursor: pointer; }
.cms .ss-gridfield span button#action_gridfield_relationfind { display: none; }
.cms .ss-gridfield p button#action_export { margin-top: 12px; }
.cms .ss-gridfield p button#action_export span.btn-icon-download-csv { height: 17px; }
.cms .ss-gridfield p button#action_export .ui-button-text { padding-left: 26px; }
.cms .ss-gridfield .right { float: right; }
.cms .ss-gridfield .right > * { float: right; margin-left: 5px; font-size: 14.4px; }
.cms .ss-gridfield .right > * { float: right; margin-left: 8px; }
.cms .ss-gridfield .right .pagination-records-number { font-size: 1.0em; padding: 6px 3px 6px 0; color: white; text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2); font-weight: normal; }
.cms .ss-gridfield .left { float: left; }
.cms .ss-gridfield .left > * { margin-right: 5px; float: left; font-size: 14.4px; }
.cms .ss-gridfield .left > * { margin-right: 8px; float: left; font-size: 14.4px; }
.cms .ss-gridfield .ss-gridfield-buttonrow { font-size: 14.4px; }
.cms .ss-gridfield .grid-levelup { text-indent: -9999em; margin-bottom: 6px; }
.cms .ss-gridfield .grid-levelup a.list-parent-link { background: transparent url(../images/gridfield-level-up.png) no-repeat 0 0; display: block; }
.cms .ss-gridfield .add-existing-autocompleter { width: 500px; }
.cms .ss-gridfield .add-existing-autocompleter input.relation-search { width: 380px; }
.cms .ss-gridfield .grid-print-button { display: inline-block; }
.cms .ss-gridfield .grid-csv-button { display: inline-block; }
.cms .ss-gridfield .add-existing-autocompleter span { display: -moz-inline-stack; display: inline-block; vertical-align: top; *vertical-align: auto; zoom: 1; *display: inline; }
.cms .ss-gridfield .add-existing-autocompleter input.relation-search { width: 270px; margin-bottom: 12px; }
.cms .ss-gridfield .grid-csv-button, .cms .ss-gridfield .grid-print-button { margin-bottom: 12px; display: -moz-inline-stack; display: inline-block; vertical-align: middle; *vertical-align: auto; zoom: 1; *display: inline; }
.cms table.ss-gridfield-table { display: table; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; padding: 0; border-collapse: separate; border-bottom: 0 none; width: 100%; margin-bottom: 12px; }
.cms table.ss-gridfield-table thead { color: #323e46; background: transparent; }
.cms table.ss-gridfield-table thead tr.filter-header .fieldgroup { max-width: 512px; }
@ -41,7 +42,7 @@
.cms table.ss-gridfield-table tbody td.col-buttons { width: auto; padding: 0 8px; text-align: right; white-space: nowrap; }
.cms table.ss-gridfield-table tbody td.col-listChildrenLink { width: 16px; border-right: none; text-indent: -9999em; padding: 0; }
.cms table.ss-gridfield-table tbody td.col-listChildrenLink .list-children-link { background: transparent url(../images/sitetree_ss_default_icons.png) no-repeat 3px -4px; display: block; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.item { color: #1556b2; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.item { color: #0073c1; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge { clear: both; text-transform: uppercase; display: inline-block; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 10px; margin-right: 6px; margin-top: -1px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.modified { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.addedtodraft { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
@ -53,7 +54,7 @@
.cms table.ss-gridfield-table tbody td button.ui-state-active { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }
.cms table.ss-gridfield-table tbody td button.gridfield-button-delete { width: 20px; margin: 0; }
.cms table.ss-gridfield-table tbody td button.gridfield-button-delete span.btn-icon-decline { left: 2px; }
.cms table.ss-gridfield-table tbody td a.view-link, .cms table.ss-gridfield-table tbody td a.edit-link { display: inline-block; float: left; width: 20px; height: 20px; text-indent: 9999em; overflow: hidden; vertical-align: middle; }
.cms table.ss-gridfield-table tbody td a.view-link, .cms table.ss-gridfield-table tbody td a.edit-link { display: inline-block; width: 20px; height: 20px; text-indent: 9999em; overflow: hidden; vertical-align: middle; }
.cms table.ss-gridfield-table tbody td a.view-link { background: url(../admin/images/btn-icon/magnifier.png) no-repeat 0 1px; }
.cms table.ss-gridfield-table tbody td a.edit-link { background: url(../admin/images/btn-icon/document--pencil.png) no-repeat 2px 0px; }
.cms table.ss-gridfield-table tfoot { color: #323e46; }

View File

@ -7,6 +7,9 @@
/** ----------------------------------------------- Typography. ------------------------------------------------ */
/** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
/*Mixin used to generate slightly smaller text and forms
Used in side panels and action tabs
*/
.ss-uploadfield .clear { clear: both; }
.ss-insert-media .ss-uploadfield { margin-top: 20px; }
.ss-insert-media .ss-uploadfield h4 { float: left; }

View File

@ -25,7 +25,8 @@ class DevelopmentAdmin extends Controller {
'viewmodel',
'build',
'reset',
'viewcode'
'viewcode',
'generatesecuretoken',
);
public function init() {
@ -171,6 +172,25 @@ class DevelopmentAdmin extends Controller {
}
}
/**
* Generate a secure token which can be used as a crypto key.
* Returns the token and suggests PHP configuration to set it.
*/
public function generatesecuretoken() {
$generator = Injector::inst()->create('RandomGenerator');
$token = $generator->randomToken('sha1');
echo <<<TXT
Token: $token
Please add this to your mysite/_config.php with the following code:
Config::inst()->update('Security', 'token', '$token');
TXT;
}
public function reset() {
$link = BASE_URL.'/dev/tests/startsession';

View File

@ -349,6 +349,9 @@ class TestRunner extends Controller {
* See {@link setdb()} for an alternative approach which just sets a database
* name, and is used for more advanced use cases like interacting with test databases
* directly during functional tests.
*
* Requires PHP's mycrypt extension in order to set the database name
* as an encrypted cookie.
*/
public function startsession() {
if(!Director::isLive()) {
@ -420,7 +423,7 @@ HTML;
}
/**
* Set an alternative database name in the current browser session.
* Set an alternative database name in the current browser session as a cookie.
* Useful for functional testing libraries like behat to create a "clean slate".
* Does not actually create the database, that's usually handled
* by {@link SapphireTest::create_temp_db()}.
@ -432,33 +435,33 @@ HTML;
*
* See {@link startsession()} for a different approach which actually creates
* the DB and loads a fixture file instead.
*
* Requires PHP's mycrypt extension in order to set the database name
* as an encrypted cookie.
*/
public function setdb() {
if(Director::isLive()) {
return $this->permissionFailure("dev/tests/setdb can only be used on dev and test sites");
return $this->httpError(403, "dev/tests/setdb can only be used on dev and test sites");
}
if(!isset($_GET['database'])) {
return $this->permissionFailure("dev/tests/setdb must be used with a 'database' parameter");
return $this->httpError(400, "dev/tests/setdb must be used with a 'database' parameter");
}
$database_name = $_GET['database'];
$name = $_GET['database'];
$prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_';
$pattern = strtolower(sprintf('#^%stmpdb\d{7}#', $prefix));
if(!preg_match($pattern, $database_name)) {
return $this->permissionFailure("Invalid database name format");
if($name && !preg_match($pattern, $name)) {
return $this->httpError(400, "Invalid database name format");
}
DB::set_alternative_database_name($database_name);
DB::set_alternative_database_name($name);
if($name) {
return "<p>Set database session to '$name'.</p>";
} else {
return "<p>Unset database session.</p>";
}
return "<p>Set database session to '$database_name'. Time to start testing; where would you like to start?</p>
<ul>
<li><a id=\"home-link\" href=\"" .Director::baseURL() . "\">Homepage - published site</a></li>
<li><a id=\"draft-link\" href=\"" .Director::baseURL() . "?stage=Stage\">Homepage - draft site</a></li>
<li><a id=\"admin-link\" href=\"" .Director::baseURL() . "admin/\">CMS Admin</a></li>
<li><a id=\"endsession-link\" href=\"" .Director::baseURL() . "dev/tests/endsession\">
End your test session</a></li>
</ul>";
}
public function emptydb() {

View File

@ -0,0 +1,12 @@
# 3.0.4
## Overview
* Changed `dev/tests/setdb` and `dev/tests/startsession` from session to cookie storage.
## Upgrading
* If you are using `dev/tests/setdb` and `dev/tests/startsession`,
you'll need to configure a secure token in order to encrypt the cookie value:
Simply run `sake dev/generatesecuretoken` and add the resulting code to your `mysite/_config.php`.
Note that this functionality now requires the PHP `mcrypt` extension.

View File

@ -4,6 +4,48 @@
## Upgrading
### Grouped CMS Buttons
The CMS buttons are now grouped, in order to hide minor actions by default and declutter the interface.
This required changing the form field structure from a simple `FieldList`
to a `FieldList` which contains a `CompositeField` for all "major actions",
and a `TabSet` with a single tab for all "minor actions".
If you have previously added, removed or altered built-in CMS actions in any way,
you'll need to adjust your code.
:::php
class MyPage extends Page {
function getCMSActions() {
$actions = parent::getCMSActions();
// Inserting a new toplevel action (old)
$actions->push(new FormAction('MyAction'));
// Inserting a new toplevel action (new)
$actions->insertAfter(new FormAction('MyAction'), 'MajorActions');
// Removing an action, both toplevel and nested (no change required)
$actions->removeByName('action_unpublish');
// Inserting a new minor action (new)
$actions->addFieldToTab(
'Root.ActionMenus.MoreOptions',
new FormAction('MyMinorAction')
);
// Finding a toplevel action (no change required)
$match = $actions->dataFieldByName('action_save');
// Finding a nested action (new)
$match = $actions->fieldByName('ActionMenus.MoreOptions')
->fieldByName('action_MyMinorAction');
return $actions;
}
}
### Other
* `TableListField`, `ComplexTableField`, `TableField`, `HasOneComplexTableField`, `HasManyComplexTableField` and `ManyManyComplexTableField` have been removed from the core and placed into a module called "legacytablefields" located at https://github.com/silverstripe-labs/legacytablefields
* `prototype.js` and `behaviour.js` have been removed from the core, they are no longer used. If you have custom code relying on these two libraries, please update your code to include the files yourself
* `Object::has_extension()` and `Object::add_extension()` deprecated in favour of using late static binding, please use `{class}::has_extension()` and `{class}::add_extension()` instead, where {class} is the class name of your DataObject class.
@ -14,6 +56,35 @@
* Removed `Member_ProfileForm`, use `CMSProfileController` instead
* `SiteTree::$nested_urls` enabled by default. To disable, call `SiteTree::disable_nested_urls()`.
* Removed CMS permission checks from `File->canEdit()` and `File->canDelete()`. If you have unsecured controllers relying on these permissions, please override them through a `DataExtension`.
* Moved email bounce handling to new ["emailbouncehandler" module](https://github.com/silverstripe-labs/silverstripe-emailbouncehandler),
including `Email_BounceHandler` and `Email_BounceRecord` classes,
as well as the `Member->Bounced` property.
* Deprecated global email methods `htmlEmail()` and `plaintextEmail`, as well as various email helper methods like `encodeMultipart()`. Use the `Email` API, or the `Mailer` class where applicable.
* Removed non-functional `$inlineImages` option for sending emails * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object
* Removed non-functional `$inlineImages` option for sending emails
* Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object
to populate the list instead (see [API docs](api:SelectionGroup)).
* `FormField->setDescription()` now renders in a `<span class="description">` by default, rather than a `title` attribute * Removed `Form->Name()`: Use getName()
* Removed `FormField->setContainerFieldSet()`: Use setContainerFieldList()
* Removed `FormField->rootFieldSet()`: Use rootFieldList()
* Removed `Group::map()`: Use DataList::("Group")->map()
* Removed `Member->generateAutologinHash()`: Tokens are no longer saved directly into the database in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token
* Removed `Member->sendInfo()`: use Member_ChangePasswordEmail or Member_ForgotPasswordEmail directly
* Removed `SQLMap::map()`: Use DataList::("Member")->map()
* Removed `SQLMap::mapInGroups()`: Use Member::map_in_groups()
* Removed `PasswordEncryptor::register()/unregister()`: Use config system instead
* Methods on DataList and ArrayList that used to both modify the existing list & return a new version now just return a new version. Make sure you change statements like `$list->filter(...)` to $`list = $list->filter(...)` for these methods:
- `ArrayList#reverse`
- `ArrayList#sort`
- `ArrayList#filter`
- `ArrayList#exclude`
- `DataList#where`
- `DataList#limit`
- `DataList#sort`
- `DataList#addFilter`
- `DataList#applyFilterContext`
- `DataList#innerJoin`
- `DataList#leftJoin`
- `DataList#find`
- `DataList#byIDs`
- `DataList#reverse`
* `DataList#dataQuery` has been changed to return a clone of the query, and so can't be used to modify the list's query directly. Use `DataList#alterDataQuery` instead to modify dataQuery in a safe manner.

View File

@ -0,0 +1,30 @@
# How to Show Help Text on CMS Form Fields
Sometimes you need to express more context for a form field
than is suitable for its `<label>` element.
The CMS provides you with an easy way to transform
form field attributes into either help text
shown alongside the field, or a tooltip which shows on demand.
The `FormField->setDescription()` method will add a `<span class="description">`
at the last position within the field, and expects unescaped HTML content.
:::php
TextField::create('MyText', 'My Text Label')
->setDescription('More <strong>detailed</strong> help');
To show the help text as a tooltip instead of inline,
add a `.cms-description-tooltip` class.
:::php
TextField::create('MyText', 'My Text Label')
->setDescription('More <strong>detailed</strong> help')
->addExtraClass('cms-help-tooltip');
Tooltips are only supported
for native, focusable input elements, which excludes
more complex fields like `GridField`, `UploadField`
or `DropdownField` with the chosen.js behaviour applied.
Note: For more advanced help text we recommend using
[Custom form field templates](/topics/forms#custom-form-field-templates);

View File

@ -22,8 +22,8 @@ We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class).
Copy the template markup of the base implementation at `framework/admin/templates/LeftAndMain.ss` into
`mysite/templates/LeftAndMain.ss`. It will automatically be picked up by the CMS logic. Add a new section after
the `$Content` tag:
`mysite/templates/LeftAndMain.ss`. It will automatically be picked up by the CMS logic. Add a new section after the
`$Content` tag:
:::ss
...
@ -125,6 +125,55 @@ and replace it with the following:
<% end_loop %>
</ul>
## Extending the CMS actions
CMS actions follow a principle similar to the CMS fields: they are built in the backend with the help of `FormFields`
and `FormActions`, and the frontend is responsible for applying a consistent styling.
The following conventions apply:
* New actions can be added by redefining `getCMSActions`, or adding an extension with `updateCMSActions`.
* It is required the actions are contained in a `FieldSet` (`getCMSActions` returns this already).
* Standalone buttons are created by adding a top-level `FormAction` (no such button is added by default).
* Button groups are created by adding a top-level `CompositeField` with `FormActions` in it.
* A `MajorActions` button group is already provided as a default.
* Drop ups with additional actions that appear as links are created via a `TabSet` and `Tabs` with `FormActions` inside.
* A `ActionMenus.MoreOptions` tab is already provided as a default and contains some minor actions.
* You can override the actions completely by providing your own `getAllCMSFields`.
Let's walk through a couple of examples of adding new CMS actions in `getCMSActions`.
First of all we can add a regular standalone button anywhere in the set. Here we are inserting it in the front of all
other actions. We could also add a button group (`CompositeField`) in a similar fashion.
:::php
$fields->unshift(FormAction::create('normal', 'Normal button'));
We can affect the existing button group by manipulating the `CompositeField` already present in the `FieldList`.
:::php
$fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button'));
Another option is adding actions into the drop-up - best place for placing infrequently used minor actions.
:::php
$fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action'));
We can also easily create new drop-up menus by defining new tabs within the `TabSet`.
:::php
$fields->addFieldToTab('ActionMenus.MyDropUp', FormAction::create('minor', 'Minor action in a new drop-up'));
<div class="hint" markdown='1'>
Empty tabs will be automatically removed from the `FieldList` to prevent clutter.
</div>
New actions will need associated controller handlers to work. You can use a `LeftAndMainExtension` to provide one. Refer
to [Controller documentation](../topics/controller) for instructions on setting up handlers.
To make the actions more user-friendly you can also use alternating buttons as detailed in the [CMS Alternating
Button](../reference/cms-alternating-button) how-to.
## Summary
In a few lines of code, we've customized the look and feel of the CMS.
@ -136,3 +185,4 @@ blocks and concepts for more complex extensions as well.
* [Reference: CMS Architecture](../reference/cms-architecture)
* [Reference: Layout](../reference/layout)
* [Topics: Rich Text Editing](../topics/rich-text-editing)
* [CMS Alternating Button](../reference/cms-alternating-button)

View File

@ -12,6 +12,7 @@ the language and functions which are used in the guides.
* [PHPUnit Configuration](phpunit-configuration). How to setup your testing environment with PHPUnit
* [Extend the CMS Interface](extend-cms-interface).
* [How to customize CMS Tree](customize-cms-tree).
* [How to Show Help Text on CMS Form Fields](cms-formfield-help-text).
* [Cache control](cache-control). Override the default PHP cache-control settings.
* [Howto customize the CMS menu](customize-cms-menu).
* [How to create a navigation menu](navigation-menu). Create primary navigation for your website.

View File

@ -14,7 +14,6 @@ It is where most documentation should live, and is the natural "second step" aft
* [Emails](email): Configuring and sending emails
* [Environment management](environment-management): Sharing configuration details (e.g. database login, passwords) with multiple websites via a `_ss_environment.php` file
* [Error Handling](error-handling): Error messages and filesystem logs
* [Extending the CMS](extending-the-cms): Introduction to changing the default CMS editor
* [Files and Images](files): File and Image management in the database and how to manipulate images
* [Form Validation](form-validation): Built-in validation on form fields, and how to extend it
* [Forms](forms): Create your own form, add fields and create your own form template using the existing `Form` class

View File

@ -16,8 +16,6 @@ if(isset($_SERVER['SERVER_NAME'])) {
*/
define('X_MAILER', 'SilverStripe Mailer - version 2006.06.21');
}
// Note: The constant 'BOUNCE_EMAIL' should be defined as a valid email address for where bounces should be returned
// to.
/**
* Class to support sending emails.
@ -113,11 +111,6 @@ class Email extends ViewableData {
*/
protected $template_data = null;
/**
* @param string $bounceHandlerURL
*/
protected $bounceHandlerURL = null;
/**
* @param sring $admin_email_address The default administrator email address.
* This will be set in the config on a site-by-site basis
@ -151,7 +144,11 @@ class Email extends ViewableData {
if($body != null) $this->body = $body;
if($cc != null) $this->cc = $cc;
if($bcc != null) $this->bcc = $bcc;
if($bounceHandlerURL != null) $this->setBounceHandlerURL($bounceHandlerURL);
if($bounceHandlerURL != null) {
Deprecation::notice('3.1', 'Use "emailbouncehandler" module');
}
parent::__construct();
}
@ -163,12 +160,8 @@ class Email extends ViewableData {
);
}
public function setBounceHandlerURL( $bounceHandlerURL ) {
if($bounceHandlerURL) {
$this->bounceHandlerURL = $bounceHandlerURL;
} else {
$this->bounceHandlerURL = $_SERVER['HTTP_HOST'] . Director::baseURL() . 'Email_BounceHandler';
}
public function setBounceHandlerURL($bounceHandlerURL) {
Deprecation::notice('3.1', 'Use "emailbouncehandler" module');
}
public function attachFile($filename, $attachedFilename = null, $mimetype = null) {
@ -388,12 +381,8 @@ class Email extends ViewableData {
if(empty($this->from)) $this->from = Email::getAdminEmail();
$this->setBounceHandlerURL($this->bounceHandlerURL);
$headers = $this->customHeaders;
$headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL;
if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;
if(project()) $headers['X-SilverStripeSite'] = project();
@ -449,12 +438,8 @@ class Email extends ViewableData {
if(empty($this->from)) $this->from = Email::getAdminEmail();
$this->setBounceHandlerURL( $this->bounceHandlerURL );
$headers = $this->customHeaders;
$headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL;
if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;
if(project()) $headers['X-SilverStripeSite'] = project();
@ -620,167 +605,3 @@ class Email extends ViewableData {
}
}
}
/**
* Base class that email bounce handlers extend
* @package framework
* @subpackage email
*/
class Email_BounceHandler extends Controller {
static $allowed_actions = array(
'index'
);
public function init() {
BasicAuth::protect_entire_site(false);
parent::init();
}
public function index() {
$subclasses = ClassInfo::subclassesFor( $this->class );
unset($subclasses[$this->class]);
if( $subclasses ) {
$subclass = array_pop( $subclasses );
$task = new $subclass();
$task->index();
return;
}
// Check if access key exists
if( !isset($_REQUEST['Key']) ) {
echo 'Error: Access validation failed. No "Key" specified.';
return;
}
// Check against access key defined in framework/_config.php
if( $_REQUEST['Key'] != EMAIL_BOUNCEHANDLER_KEY) {
echo 'Error: Access validation failed. Invalid "Key" specified.';
return;
}
if( !$_REQUEST['Email'] ) {
echo "No email address";
return;
}
$this->recordBounce( $_REQUEST['Email'], $_REQUEST['Date'], $_REQUEST['Time'], $_REQUEST['Message'] );
}
private function recordBounce( $email, $date = null, $time = null, $error = null ) {
if(preg_match('/<(.*)>/', $email, $parts)) $email = $parts[1];
$SQL_email = Convert::raw2sql($email);
$SQL_bounceTime = Convert::raw2sql("$date $time");
$duplicateBounce = DataObject::get_one("Email_BounceRecord",
"\"BounceEmail\" = '$SQL_email' AND (\"BounceTime\"+INTERVAL 1 MINUTE) > '$SQL_bounceTime'");
if(!$duplicateBounce) {
$record = new Email_BounceRecord();
$member = DataObject::get_one( 'Member', "\"Email\"='$SQL_email'" );
if( $member ) {
$record->MemberID = $member->ID;
// If the SilverStripeMessageID (taken from the X-SilverStripeMessageID header embedded in the email)
// is sent, then log this bounce in a Newsletter_SentRecipient record so it will show up on the 'Sent
// Status Report' tab of the Newsletter
if( isset($_REQUEST['SilverStripeMessageID'])) {
// Note: was sent out with: $project . '.' . $messageID;
$message_id_parts = explode('.', $_REQUEST['SilverStripeMessageID']);
// Note: was encoded with: base64_encode( $newsletter->ID . '_' . date( 'd-m-Y H:i:s' ) );
$newsletter_id_date_parts = explode ('_', base64_decode($message_id_parts[1]) );
// Escape just in case
$SQL_memberID = Convert::raw2sql($member->ID);
$SQL_newsletterID = Convert::raw2sql($newsletter_id_date_parts[0]);
// Log the bounce
$oldNewsletterSentRecipient = DataObject::get_one("Newsletter_SentRecipient",
"\"MemberID\" = '$SQL_memberID' AND \"ParentID\" = '$SQL_newsletterID'"
. " AND \"Email\" = '$SQL_email'");
// Update the Newsletter_SentRecipient record if it exists
if($oldNewsletterSentRecipient) {
$oldNewsletterSentRecipient->Result = 'Bounced';
$oldNewsletterSentRecipient->write();
} else {
// For some reason it didn't exist, create a new record
$newNewsletterSentRecipient = new Newsletter_SentRecipient();
$newNewsletterSentRecipient->Email = $SQL_email;
$newNewsletterSentRecipient->MemberID = $member->ID;
$newNewsletterSentRecipient->Result = 'Bounced';
$newNewsletterSentRecipient->ParentID = $newsletter_id_date_parts[0];
$newNewsletterSentRecipient->write();
}
// Now we are going to Blacklist this member so that email will not be sent to them in the future.
// Note: Sending can be re-enabled by going to 'Mailing List' 'Bounced' tab and unchecking the box
// under 'Blacklisted'
$member->setBlacklistedEmail(TRUE);
echo '<p><b>Member: '.$member->FirstName.' '.$member->Surname
.' <'.$member->Email.'> was added to the Email Blacklist!</b></p>';
}
}
if( !$date )
$date = date( 'd-m-Y' );
/*else
$date = date( 'd-m-Y', strtotime( $date ) );*/
if( !$time )
$time = date( 'H:i:s' );
/*else
$time = date( 'H:i:s', strtotime( $time ) );*/
$record->BounceEmail = $email;
$record->BounceTime = $date . ' ' . $time;
$record->BounceMessage = $error;
$record->write();
echo "Handled bounced email to address: $email";
} else {
echo 'Sorry, this bounce report has already been logged, not logging this duplicate bounce.';
}
}
}
/**
* Database record for recording a bounced email
* @package framework
* @subpackage email
*/
class Email_BounceRecord extends DataObject {
static $db = array(
'BounceEmail' => 'Varchar',
'BounceTime' => 'SS_Datetime',
'BounceMessage' => 'Varchar'
);
static $has_one = array(
'Member' => 'Member'
);
static $has_many = array();
static $many_many = array();
static $defaults = array();
static $singular_name = 'Email Bounce Record';
/**
* a record of Email_BounceRecord can't be created manually. Instead, it should be
* created though system.
*/
public function canCreate($member = null) {
return false;
}
}

View File

@ -53,7 +53,7 @@ class Mailer {
} else {
$messageParts[] = $this->encodeFileForEmail($file);
}
}
}
// We further wrap all of this into another multipart block
@ -233,12 +233,12 @@ class Mailer {
}
return $result;
}
}
/**
* @todo Make visibility protected in 3.2
*/
function encodeMultipart($parts, $contentType, $headers = false) {
function encodeMultipart($parts, $contentType, $headers = false) {
$separator = "----=_NextPart_" . preg_replace('/[^0-9]/', '', rand() * 10000000000);
$headers["MIME-Version"] = "1.0";
@ -261,20 +261,20 @@ class Mailer {
$separator . implode("\n".$separator, $parts) . "\n" . trim($separator) . "--";
return array($body, $headers);
}
}
/**
* @todo Make visibility protected in 3.2
*/
function processHeaders($headers, $body = false) {
function processHeaders($headers, $body = false) {
$res = '';
if(is_array($headers)) while(list($k, $v) = each($headers))
$res .= "$k: $v\n";
if($body) $res .= "\n$body";
return $res;
}
}
/**
/**
* Encode the contents of a file for emailing, including headers
*
* $file can be an array, in which case it expects these members:
@ -316,7 +316,7 @@ class Mailer {
*
* @todo Make visibility protected in 3.2
*/
function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") {
function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") {
if(!$file) {
user_error("encodeFileForEmail: not passed a filename and/or data", E_USER_WARNING);
return;
@ -352,7 +352,7 @@ class Mailer {
$headers = "Content-type: $mimeType;\n\tname=\"$base\"\n".
"Content-Transfer-Encoding: $encoding\n".
"Content-Disposition: $disposition;\n\tfilename=\"$base\"\n" ;
"Content-Disposition: $disposition;\n\tfilename=\"$base\"\n";
if ( isset($file['contentLocation']) ) $headers .= 'Content-Location: ' . $file['contentLocation'] . "\n" ;
@ -360,12 +360,12 @@ class Mailer {
// Return completed packet
return $headers . $file['contents'];
}
}
/**
* @todo Make visibility protected in 3.2
*/
function QuotedPrintable_encode($quotprint) {
function QuotedPrintable_encode($quotprint) {
$quotprint = (string)str_replace('\r\n',chr(13).chr(10),$quotprint);
$quotprint = (string)str_replace('\n', chr(13).chr(10),$quotprint);
$quotprint = (string)preg_replace("~([\x01-\x1F\x3D\x7F-\xFF])~e", "sprintf('=%02X', ord('\\1'))", $quotprint);
@ -375,12 +375,12 @@ class Mailer {
$quotprint = (string)str_replace('=0D',"\n",$quotprint);
$quotprint = (string)str_replace('=0A',"\n",$quotprint);
return (string) $quotprint;
}
}
/**
* @todo Make visibility protected in 3.2
*/
function validEmailAddr($emailAddress) {
function validEmailAddr($emailAddress) {
$emailAddress = trim($emailAddress);
$angBrack = strpos($emailAddress, '<');
@ -393,7 +393,7 @@ class Mailer {
}
return $emailAddress;
}
}
}
/**

View File

@ -41,12 +41,6 @@ class CheckboxField extends FormField {
return $field;
}
public function performDisabledTransformation() {
$clone = clone $this;
$clone->setDisabled(true);
return $clone;
}
}
/**

View File

@ -276,10 +276,8 @@ class CheckboxSetField extends OptionsetField {
}
}
$title = ($this->title) ? $this->title : '';
$field = new ReadonlyField($this->name, $title, $values);
$field->setForm($this->form);
$field = $this->castedCopy('ReadonlyField');
$field->setValue($values);
return $field;
}

View File

@ -265,6 +265,8 @@ class CompositeField extends FormField {
$clone->children = $newChildren;
$clone->readonly = true;
$clone->addExtraClass($this->extraClass());
$clone->setDescription($this->getDescription());
return $clone;
}
@ -285,6 +287,11 @@ class CompositeField extends FormField {
$clone->children = $newChildren;
$clone->readonly = true;
$clone->addExtraClass($this->extraClass());
$clone->setDescription($this->getDescription());
foreach($this->attributes as $k => $v) {
$clone->setAttribute($k, $v);
}
return $clone;
}

View File

@ -122,6 +122,11 @@ class ConfirmedPasswordField extends FormField {
foreach($this->children as $field) {
$field->setDisabled($this->isDisabled());
$field->setReadonly($this->isReadonly());
if(count($this->attributes)) {
foreach($this->attributes as $name => $value) {
$field->setAttribute($name, $value);
}
}
$content .= $field->FieldHolder();
}
@ -315,10 +320,10 @@ class ConfirmedPasswordField extends FormField {
* Makes a pretty readonly field with some stars in it
*/
public function performReadonlyTransformation() {
$stars = '*****';
$field = $this->castedCopy('ReadonlyField')
->setTitle($this->title ? $this->title : _t('Member.PASSWORD'))
->setValue('*****');
$field = new ReadonlyField($this->name, $this->title ? $this->title : _t('Member.PASSWORD'), $stars);
$field->setForm($this->form);
return $field;
}
}

View File

@ -40,9 +40,7 @@ class CurrencyField extends TextField {
* Create a new class for this field
*/
public function performReadonlyTransformation() {
$field = new CurrencyField_Readonly($this->name, $this->title, $this->value);
$field -> addExtraClass($this->extraClass());
return $field;
return $this->castedCopy('CurrencyField_Readonly');
}
public function validate($validator) {

View File

@ -279,13 +279,25 @@ class DateField extends TextField {
}
public function performReadonlyTransformation() {
$field = new DateField_Disabled($this->name, $this->title, $this->dataValue());
$field->setForm($this->form);
$field = $this->castedCopy('DateField_Disabled');
$field->setValue($this->dataValue());
$field->readonly = true;
return $field;
}
public function castedCopy($class) {
$copy = new $class($this->name);
if($copy->hasMethod('setConfig')) {
$config = $this->getConfig();
foreach($config as $k => $v) {
$copy->setConfig($k, $v);
}
}
return parent::castedCopy($copy);
}
/**
* Validate an array with expected keys 'day', 'month' and 'year.
* Used because Zend_Date::isDate() doesn't provide this.

View File

@ -237,6 +237,15 @@ class DatetimeField extends FormField {
return $this->dateField->getLocale();
}
public function setDescription($description) {
parent::setDescription($description);
$this->dateField->setDescription($description);
$this->timeField->setDescription($description);
return $this;
}
/**
* Note: Use {@link getDateField()} and {@link getTimeField()}
* to set field-specific config options.
@ -274,11 +283,30 @@ class DatetimeField extends FormField {
}
public function performReadonlyTransformation() {
$field = new DatetimeField_Readonly($this->name, $this->title, $this->dataValue());
$field->setForm($this->form);
$field = $this->castedCopy('DatetimeField_Readonly');
$field->setValue($this->dataValue());
$dateFieldConfig = $this->getDateField()->getConfig();
if($dateFieldConfig) {
foreach($dateFieldConfig as $k => $v) {
$field->getDateField()->setConfig($k, $v);
}
}
$timeFieldConfig = $this->getTimeField()->getConfig();
if($timeFieldConfig) {
foreach($timeFieldConfig as $k => $v) {
$field->getTimeField()->setConfig($k, $v);
}
}
return $field;
}
public function __clone() {
$this->dateField = clone $this->dateField;
$this->timeField = clone $this->timeField;
}
}
/**

View File

@ -239,10 +239,10 @@ class DropdownField extends FormField {
}
public function performReadonlyTransformation() {
$field = new LookupField($this->name, $this->title, $this->getSource());
$field->setValue($this->value);
$field->setForm($this->form);
$field = $this->castedCopy('LookupField');
$field->setSource($this->getSource());
$field->setReadonly(true);
return $field;
}
}

View File

@ -864,14 +864,6 @@ class Form extends RequestHandler {
return $this;
}
/**
* @return string
*/
public function Name() {
Deprecation::notice('3.0', 'Use getName() instead.');
return $this->getName();
}
/**
* Get the name of the form.
* @return string

View File

@ -356,7 +356,6 @@ class FormField extends RequestHandler {
'class' => $this->extraClass(),
'id' => $this->ID(),
'disabled' => $this->isDisabled(),
'title' => $this->getDescription(),
);
return array_merge($attrs, $this->attributes);
@ -710,10 +709,9 @@ class FormField extends RequestHandler {
* Returns a readonly version of this field
*/
public function performReadonlyTransformation() {
$field = new ReadonlyField($this->name, $this->title, $this->value);
$field->addExtraClass($this->extraClass());
$field->setForm($this->form);
return $field;
$copy = $this->castedCopy('ReadonlyField');
$copy->setReadonly(true);
return $copy;
}
/**
@ -724,14 +722,15 @@ class FormField extends RequestHandler {
* @return FormField
*/
public function performDisabledTransformation() {
$clone = clone $this;
$disabledClassName = $clone->class . '_Disabled';
$disabledClassName = $this->class . '_Disabled';
if(ClassInfo::exists($disabledClassName)) {
return new $disabledClassName($this->name, $this->title, $this->value);
$clone = $this->castedCopy($disabledClassName);
} else {
$clone = clone $this;
$clone->setDisabled(true);
return $clone;
}
return $clone;
}
public function transform(FormTransformation $trans) {
@ -775,7 +774,8 @@ class FormField extends RequestHandler {
/**
* Describe this field, provide help text for it.
* By default, renders as a "title" attribute on the form field.
* By default, renders as a <span class="description">
* underneath the form field.
*
* @return string Description
*/
@ -814,11 +814,6 @@ class FormField extends RequestHandler {
}
}
public function setContainerFieldSet($list) {
Deprecation::notice('3.0', 'Use setContainerFieldList() instead.');
return $this->setContainerFieldList($list);
}
/**
* Set the FieldList that contains this field.
*
@ -830,14 +825,47 @@ class FormField extends RequestHandler {
return $this;
}
public function rootFieldSet() {
Deprecation::notice('3.0', 'Use rootFieldList() instead.');
return $this->rootFieldList();
}
public function rootFieldList() {
if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList();
else user_error("rootFieldList() called on $this->class object without a containerFieldList", E_USER_ERROR);
}
/**
* Returns another instance of this field, but "cast" to a different class.
* The logic tries to retain all of the instance properties,
* and may be overloaded by subclasses to set additional ones.
*
* Assumes the standard FormField parameter signature with
* its name as the only mandatory argument. Mainly geared towards
* creating *_Readonly or *_Disabled subclasses of the same type,
* or casting to a {@link ReadonlyField}.
*
* Does not copy custom field templates, since they probably won't apply to
* the new instance.
*
* @param String $classOrCopy Class name for copy, or existing copy instance to update
* @return FormField
*/
public function castedCopy($classOrCopy) {
$field = (is_object($classOrCopy)) ? $classOrCopy : new $classOrCopy($this->name);
$field
->setValue($this->value) // get value directly from property, avoid any conversions
->setForm($this->form)
->setTitle($this->Title())
->setLeftTitle($this->LeftTitle())
->setRightTitle($this->RightTitle())
->addExtraClass($this->extraClass())
->setDescription($this->getDescription());
// Only include built-in attributes, ignore anything
// set through getAttributes(), since those might change important characteristics
// of the field, e.g. its "type" attribute.
foreach($this->attributes as $k => $v) {
$field->setAttribute($k, $v);
}
$field->dontEscape = $this->dontEscape;
return $field;
}
}

View File

@ -204,9 +204,9 @@ class HtmlEditorField extends TextareaField {
* @return HtmlEditorField_Readonly
*/
public function performReadonlyTransformation() {
$field = new HtmlEditorField_Readonly($this->name, $this->title, $this->value);
$field->setForm($this->form);
$field = $this->castedCopy('HtmlEditorField_Readonly');
$field->dontEscape = true;
return $field;
}

View File

@ -24,7 +24,7 @@ class InlineFormAction extends FormField {
}
public function performReadonlyTransformation() {
return new InlineFormAction_ReadOnly( $this->name, $this->title );
return $this->castedCopy('InlineFormAction_ReadOnly');
}
public function Field($properties = array()) {

View File

@ -32,7 +32,7 @@ class LookupField extends DropdownField {
// Don't check if string arguments are matching against the source,
// as they might be generated HTML diff views instead of the actual values
if($this->value && !$mapped) {
if($this->value && !is_array($this->value) && !$mapped) {
$mapped = array(trim($this->value));
$values = array();
}

View File

@ -6,6 +6,8 @@
class MemberDatetimeOptionsetField extends OptionsetField {
public function Field($properties = array()) {
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/MemberDatetimeOptionsetField.js');
$options = '';
$odd = 0;
$source = $this->getSource();
@ -39,7 +41,11 @@ class MemberDatetimeOptionsetField extends OptionsetField {
$itemID, $this->name, $checked)
. sprintf('<label for="%s_custom">%s:</label>',
$itemID, _t('MemberDatetimeOptionsetField.Custom', 'Custom'))
. sprintf("<input class=\"customFormat\" name=\"%s_custom\" value=\"%s\" />\n", $this->name, $value)
. sprintf(
"<input class=\"customFormat cms-help cms-help-tooltip\" name=\"%s_custom\" value=\"%s\" />\n",
$this->name,
$value
)
. sprintf("<input type=\"hidden\" class=\"formatValidationURL\" value=\"%s\" />",
$this->Link() . '/validate');
$options .= ($value) ? sprintf(
@ -47,15 +53,6 @@ class MemberDatetimeOptionsetField extends OptionsetField {
_t('MemberDatetimeOptionsetField.Preview', 'Preview'),
Zend_Date::now()->toString($value)
) : '';
$options .= sprintf(
'<a class="cms-help-toggle" href="#%s">%s</a>',
$this->id() . '_Help',
_t('MemberDatetimeOptionsetField.TOGGLEHELP', 'Toggle formatting help')
);
$options .= "<div id=\"" . $this->id() . "_Help\">";
$options .= $this->getFormattingHelpText();
$options .= "</div>";
$options .= "</li>\n";
$id = $this->id();
return "<ul id=\"$id\" class=\"optionset {$this->extraClass()}\">\n$options</ul>\n";
@ -64,42 +61,46 @@ class MemberDatetimeOptionsetField extends OptionsetField {
/**
* @todo Put this text into a template?
*/
public function getFormattingHelpText() {
$output = '<ul>';
$output .= '<li>YYYY = ' . _t('MemberDatetimeOptionsetField.FOURDIGITYEAR', 'Four-digit year',
40, 'Help text describing what "YYYY" means in ISO date formatting') . '</li>';
$output .= '<li>YY = ' . _t('MemberDatetimeOptionsetField.TWODIGITYEAR', 'Two-digit year',
40, 'Help text describing what "YY" means in ISO date formatting') . '</li>';
$output .= '<li>MMMM = ' . _t('MemberDatetimeOptionsetField.FULLNAMEMONTH', 'Full name of month (e.g. June)',
40, 'Help text describing what "MMMM" means in ISO date formatting') . '</li>';
$output .= '<li>MMM = ' . _t('MemberDatetimeOptionsetField.SHORTMONTH', 'Short name of month (e.g. Jun)',
40, 'Help text letting describing what "MMM" means in ISO date formatting') . '</li>';
$output .= '<li>MM = ' . _t('MemberDatetimeOptionsetField.TWODIGITMONTH', 'Two-digit month (01=January, etc.)',
40, 'Help text describing what "MM" means in ISO date formatting') . '</li>';
$output .= '<li>M = ' . _t('MemberDatetimeOptionsetField.MONTHNOLEADING', 'Month digit without leading zero',
40, 'Help text describing what "M" means in ISO date formatting') . '</li>';
$output .= '<li>dd = ' . _t('MemberDatetimeOptionsetField.TWODIGITDAY', 'Two-digit day of month',
40, 'Help text describing what "dd" means in ISO date formatting') . '</li>';
$output .= '<li>d = ' . _t('MemberDatetimeOptionsetField.DAYNOLEADING', 'Day of month without leading zero',
40, 'Help text describing what "d" means in ISO date formatting') . '</li>';
$output .= '<li>hh = ' . _t('MemberDatetimeOptionsetField.TWODIGITHOUR', 'Two digits of hour (00 through 23)',
40, 'Help text describing what "hh" means in ISO date formatting') . '</li>';
$output .= '<li>h = ' . _t('MemberDatetimeOptionsetField.HOURNOLEADING', 'Hour without leading zero',
40, 'Help text describing what "h" means in ISO date formatting') . '</li>';
$output .= '<li>mm = ' . _t('MemberDatetimeOptionsetField.TWODIGITMINUTE',
public function getDescription() {
$output =
'<a href="#" class="toggle">'
. _t('MemberDatetimeOptionsetField.Toggle', 'Show formatting help')
. '</a>'
. '<ul class="toggle-content">'
. '<li>YYYY = ' . _t('MemberDatetimeOptionsetField.FOURDIGITYEAR', 'Four-digit year',
40, 'Help text describing what "YYYY" means in ISO date formatting') . '</li>'
. '<li>YY = ' . _t('MemberDatetimeOptionsetField.TWODIGITYEAR', 'Two-digit year',
40, 'Help text describing what "YY" means in ISO date formatting') . '</li>'
. '<li>MMMM = ' . _t('MemberDatetimeOptionsetField.FULLNAMEMONTH', 'Full name of month (e.g. June)',
40, 'Help text describing what "MMMM" means in ISO date formatting') . '</li>'
. '<li>MMM = ' . _t('MemberDatetimeOptionsetField.SHORTMONTH', 'Short name of month (e.g. Jun)',
40, 'Help text letting describing what "MMM" means in ISO date formatting') . '</li>'
. '<li>MM = ' . _t('MemberDatetimeOptionsetField.TWODIGITMONTH', 'Two-digit month (01=January, etc.)',
40, 'Help text describing what "MM" means in ISO date formatting') . '</li>'
. '<li>M = ' . _t('MemberDatetimeOptionsetField.MONTHNOLEADING', 'Month digit without leading zero',
40, 'Help text describing what "M" means in ISO date formatting') . '</li>'
. '<li>dd = ' . _t('MemberDatetimeOptionsetField.TWODIGITDAY', 'Two-digit day of month',
40, 'Help text describing what "dd" means in ISO date formatting') . '</li>'
. '<li>d = ' . _t('MemberDatetimeOptionsetField.DAYNOLEADING', 'Day of month without leading zero',
40, 'Help text describing what "d" means in ISO date formatting') . '</li>'
. '<li>hh = ' . _t('MemberDatetimeOptionsetField.TWODIGITHOUR', 'Two digits of hour (00 through 23)',
40, 'Help text describing what "hh" means in ISO date formatting') . '</li>'
. '<li>h = ' . _t('MemberDatetimeOptionsetField.HOURNOLEADING', 'Hour without leading zero',
40, 'Help text describing what "h" means in ISO date formatting') . '</li>'
. '<li>mm = ' . _t('MemberDatetimeOptionsetField.TWODIGITMINUTE',
'Two digits of minute (00 through 59)',
40, 'Help text describing what "mm" means in ISO date formatting') . '</li>';
$output .= '<li>m = ' . _t('MemberDatetimeOptionsetField.MINUTENOLEADING', 'Minute without leading zero',
40, 'Help text describing what "m" means in ISO date formatting') . '</li>';
$output .= '<li>ss = ' . _t('MemberDatetimeOptionsetField.TWODIGITSECOND',
40, 'Help text describing what "mm" means in ISO date formatting') . '</li>'
. '<li>m = ' . _t('MemberDatetimeOptionsetField.MINUTENOLEADING', 'Minute without leading zero',
40, 'Help text describing what "m" means in ISO date formatting') . '</li>'
. '<li>ss = ' . _t('MemberDatetimeOptionsetField.TWODIGITSECOND',
'Two digits of second (00 through 59)',
40, 'Help text describing what "ss" means in ISO date formatting') . '</li>';
$output .= '<li>s = ' . _t('MemberDatetimeOptionsetField.DIGITSDECFRACTIONSECOND',
40, 'Help text describing what "ss" means in ISO date formatting') . '</li>'
. '<li>s = ' . _t('MemberDatetimeOptionsetField.DIGITSDECFRACTIONSECOND',
'One or more digits representing a decimal fraction of a second',
40, 'Help text describing what "s" means in ISO date formatting') . '</li>';
$output .= '<li>a = ' . _t('MemberDatetimeOptionsetField.AMORPM', 'AM (Ante meridiem) or PM (Post meridiem)',
40, 'Help text describing what "a" means in ISO date formatting') . '</li>';
$output .= '</ul>';
40, 'Help text describing what "s" means in ISO date formatting') . '</li>'
. '<li>a = ' . _t('MemberDatetimeOptionsetField.AMORPM', 'AM (Ante meridiem) or PM (Post meridiem)',
40, 'Help text describing what "a" means in ISO date formatting') . '</li>'
. '</ul>';
return $output;
}

View File

@ -92,9 +92,8 @@ class OptionsetField extends DropdownField {
public function performReadonlyTransformation() {
// Source and values are DataObject sets.
$items = $this->getSource();
$field = new LookupField($this->name, $this->title ? $this->title : '', $items, $this->value);
$field->setForm($this->form);
$field = $this->castedCopy('LookupField');
$field->setValue($this->getSource());
$field->setReadonly(true);
return $field;

View File

@ -31,11 +31,9 @@ class PasswordField extends TextField {
* Makes a pretty readonly field with some stars in it
*/
public function performReadonlyTransformation() {
$stars = '*****';
$field = $this->castedCopy('ReadonlyField');
$field->setValue('*****');
$field = new ReadonlyField($this->name, $this->title ? $this->title : '', $stars);
$field->setForm($this->form);
$field->setReadonly(true);
return $field;
}

View File

@ -54,6 +54,9 @@ class PhoneNumberField extends FormField {
$field->push(new NumericField( $this->name.'[Extension]', 'ext', $extension, 6));
}
$description = $this->getDescription();
if($description) $fields->getChildren()->First()->setDescription($description);
foreach($fields as $field) {
$field->setDisabled($this->isDisabled());
$field->setReadonly($this->isReadonly());

View File

@ -44,20 +44,6 @@ class TextareaField extends FormField {
);
}
/**
* Performs a disabled transformation on this field. You shouldn't be able to
* copy from this field, and it should not send any data when you submit the
* form it's attached to.
*
* The element shouldn't be both disabled and readonly at the same time.
*/
public function performDisabledTransformation() {
$clone = clone $this;
$clone->setDisabled(true);
$clone->setReadonly(false);
return $clone;
}
public function Type() {
return parent::Type() . ($this->readonly ? ' readonly' : '');
}

View File

@ -191,7 +191,19 @@ class TimeField extends TextField {
* Creates a new readonly field specified below
*/
public function performReadonlyTransformation() {
return new TimeField_Readonly($this->name, $this->title, $this->dataValue(), $this->getConfig('timeformat'));
return $this->castedCopy('TimeField_Readonly');
}
public function castedCopy($class) {
$copy = parent::castedCopy($class);
if($copy->hasMethod('setConfig')) {
$config = $this->getConfig();
foreach($config as $k => $v) {
$copy->setConfig($k, $v);
}
}
return $copy;
}
}

View File

@ -292,6 +292,48 @@ class TreeDropdownField extends FormField {
return true;
}
/**
* @param String $field
*/
public function setLabelField($field) {
$this->labelField = $field;
}
/**
* @return String
*/
public function getLabelField() {
return $this->labelField;
}
/**
* @param String $field
*/
public function setKeyField($field) {
$this->keyField = $field;
}
/**
* @return String
*/
public function getKeyField() {
return $this->keyField;
}
/**
* @param String $field
*/
public function setSourceObject($class) {
$this->sourceObject = $class;
}
/**
* @return String
*/
public function getSourceObject() {
return $this->sourceObject;
}
/**
* Populate $this->searchIds with the IDs of the pages matching the searched parameter and their parents.
* Reverse-constructs the tree starting from the leaves. Initially taken from CMSSiteTreeFilter, but modified
@ -342,9 +384,14 @@ class TreeDropdownField extends FormField {
* Changes this field to the readonly field.
*/
public function performReadonlyTransformation() {
return new TreeDropdownField_Readonly($this->name, $this->title, $this->sourceObject, $this->keyField,
$this->labelField);
$copy = $this->castedCopy('TreeDropdownField_Readonly');
$copy->setKeyField($this->keyField);
$copy->setLabelField($this->labelField);
$copy->setSourceObject($this->sourceObject);
return $copy;
}
}
/**

View File

@ -175,14 +175,12 @@ class TreeMultiselectField extends TreeDropdownField {
* Changes this field to the readonly field.
*/
public function performReadonlyTransformation() {
$field = new TreeMultiselectField_Readonly($this->name, $this->title, $this->sourceObject,
$this->keyField, $this->labelField);
$copy = $this->castedCopy('TreeMultiselectField_Readonly');
$copy->setKeyField($this->keyField);
$copy->setLabelField($this->labelField);
$copy->setSourceObject($this->sourceObject);
$field->addExtraClass($this->extraClass());
$field->setForm($this->form);
$field->setValue($this->value);
return $field;
return $copy;
}
}

View File

@ -397,6 +397,13 @@ class GridField extends FormField {
'cellspacing' => '0'
);
if($this->getDescription()) {
$content['after'] .= FormField::create_tag(
'span',
array('class' => 'description'),
$this->getDescription()
);
}
return
FormField::create_tag('fieldset', $attrs,

View File

@ -883,7 +883,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
//get the uploaded file ID when this event triggers, signaling the upload has compeleted successfully
editFieldIDs.push($(this).data('id'));
});
var uploadedFiles = $('.ss-uploadfield-files').children('.ss-uploadfield-item');
var uploadedFiles = form.find('.ss-uploadfield-files').children('.ss-uploadfield-item');
uploadedFiles.each(function(){
var uploadedID = $(this).data('fileid');
if ($.inArray(uploadedID, editFieldIDs) == -1) {

View File

@ -1,9 +1,11 @@
(function($){
$.entwine('ss', function($){
/**
* Lightweight wrapper around jQuery UI tabs.
* Lightweight wrapper around jQuery UI tabs for generic tab set-up
*/
$('.ss-tabset').entwine({
IgnoreTabState: false,
onadd: function() {
// Can't name redraw() as it clashes with other CMS entwine classes
this.redrawTabs();

View File

@ -2,6 +2,17 @@
/**
* A list object that wraps around an array of objects or arrays.
*
* Note that (like DataLists), the implementations of the methods from SS_Filterable, SS_Sortable and
* SS_Limitable return a new instance of ArrayList, rather than modifying the existing instance.
*
* For easy reference, methods that operate in this way are:
*
* - limit
* - reverse
* - sort
* - filter
* - exclude
*
* @package framework
* @subpackage model
*/
@ -119,7 +130,9 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
* @return ArrayList
*/
public function limit($length, $offset = 0) {
return new ArrayList(array_slice($this->items, $offset, $length));
$list = clone $this;
$list->items = array_slice($this->items, $offset, $length);
return $list;
}
/**
@ -309,8 +322,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
* @return ArrayList
*/
public function reverse() {
// TODO 3.1: This currently mutates existing array
$list = /* clone */ $this;
$list = clone $this;
$list->items = array_reverse($this->items);
return $list;
@ -376,8 +388,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
$multisortArgs[] = &$sortDirection[$column];
}
// TODO 3.1: This currently mutates existing array
$list = /* clone */ $this;
$list = clone $this;
// As the last argument we pass in a reference to the items that all the sorting will be applied upon
$multisortArgs[] = &$list->items;
call_user_func_array('array_multisort', $multisortArgs);
@ -440,8 +451,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
}
}
// TODO 3.1: This currently mutates existing array
$list = /* clone */ $this;
$list = clone $this;
$list->items = $itemsToKeep;
return $list;
}
@ -504,8 +514,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
}
}
// TODO 3.1: This currently mutates existing array
$list = /* clone */ $this;
$list = clone $this;
$list->items = $itemsToKeep;
return $list;
}

View File

@ -60,19 +60,89 @@ class DB {
}
/**
* Set an alternative database to use for this browser session.
* This is useful when using testing systems other than SapphireTest; for example, Windmill.
* Set an alternative database in a browser cookie,
* with the cookie lifetime set to the browser session.
* This is useful for integration testing on temporary databases.
*
* There is a strict naming convention for temporary databases to avoid abuse:
* <prefix> (default: 'ss_') + tmpdb + <7 digits>
* As an additional security measure, temporary databases will
* be ignored in "live" mode.
*
* Note that the database will be set on the next request.
* Set it to null to revert to the main database.
*/
public static function set_alternative_database_name($dbname) {
Session::set("alternativeDatabaseName", $dbname);
public static function set_alternative_database_name($name = null) {
if($name) {
if(!self::valid_alternative_database_name($name)) {
throw new InvalidArgumentException(sprintf(
'Invalid alternative database name: "%s"',
$name
));
}
$key = Config::inst()->get('Security', 'token');
if(!$key) {
throw new LogicException('"Security.token" not found, run "sake dev/generatesecuretoken"');
}
if(!function_exists('mcrypt_encrypt')) {
throw new LogicException('DB::set_alternative_database_name() requires the mcrypt PHP extension');
}
$key = md5($key); // Ensure key is correct length for chosen cypher
$ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB);
$iv = mcrypt_create_iv($ivSize);
$encrypted = mcrypt_encrypt(
MCRYPT_RIJNDAEL_256, $key, $name, MCRYPT_MODE_CFB, $iv
);
// Set to browser session lifetime, and restricted to HTTP access only
Cookie::set("alternativeDatabaseName", base64_encode($encrypted), 0, null, null, false, true);
Cookie::set("alternativeDatabaseNameIv", base64_encode($iv), 0, null, null, false, true);
} else {
Cookie::set("alternativeDatabaseName", null, 0, null, null, false, true);
Cookie::set("alternativeDatabaseNameIv", null, 0, null, null, false, true);
}
}
/**
* Get the name of the database in use
*/
public static function get_alternative_database_name() {
return Session::get("alternativeDatabaseName");
$name = Cookie::get("alternativeDatabaseName");
$iv = Cookie::get("alternativeDatabaseNameIv");
if($name) {
$key = Config::inst()->get('Security', 'token');
if(!$key) {
throw new LogicException('"Security.token" not found, run "sake dev/generatesecuretoken"');
}
if(!function_exists('mcrypt_encrypt')) {
throw new LogicException('DB::set_alternative_database_name() requires the mcrypt PHP extension');
}
$key = md5($key); // Ensure key is correct length for chosen cypher
$decrypted = mcrypt_decrypt(
MCRYPT_RIJNDAEL_256, $key, base64_decode($name), MCRYPT_MODE_CFB, base64_decode($iv)
);
return (self::valid_alternative_database_name($decrypted)) ? $decrypted : false;
} else {
return false;
}
}
/**
* Determines if the name is valid, as a security
* measure against setting arbitrary databases.
*
* @param String $name
* @return Boolean
*/
public static function valid_alternative_database_name($name) {
if(Director::isLive()) return false;
$prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_';
$pattern = strtolower(sprintf('/^%stmpdb\d{7}$/', $prefix));
return (bool)preg_match($pattern, $name);
}
/**
@ -84,7 +154,7 @@ class DB {
*/
public static function connect($databaseConfig) {
// This is used by TestRunner::startsession() to test up a test session using an alt
if($name = Session::get('alternativeDatabaseName')) {
if($name = self::get_alternative_database_name()) {
$databaseConfig['database'] = $name;
}

View File

@ -3,21 +3,21 @@
* Implements a "lazy loading" DataObjectSet.
* Uses {@link DataQuery} to do the actual query generation.
*
* todo 3.1: In 3.0 the below is not currently true for backwards compatible reasons, but code should not rely on
* current behaviour.
* DataLists are _immutable_ as far as the query they represent is concerned. When you call a method that
* alters the query, a new DataList instance is returned, rather than modifying the existing instance
*
* DataLists have two sets of methods.
* When you add or remove an element to the list the query remains the same, but because you have modified
* the underlying data the contents of the list changes. These are some of those methods:
*
* 1). Selection methods (SS_Filterable, SS_Sortable, SS_Limitable) change the way the list is built, but does not
* alter underlying data. There are no external affects from selection methods once this list instance is
* destructed.
* - add
* - addMany
* - remove
* - removeMany
* - removeByID
* - removeByFilter
* - removeAll
*
* 2). Mutation methods change the underlying data. The change persists into the underlying data storage layer.
*
* DataLists are _immutable_ as far as selection methods go - they all return new instances of DataList, rather
* than change the current list.
*
* DataLists are _mutable_ as far as mutation methods go - they all act on the existing DataList instance.
* Subclasses of DataList may add other methods that have the same effect.
*
* @package framework
* @subpackage model
@ -85,17 +85,13 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
/**
* Return a copy of the internal {@link DataQuery} object
*
* todo 3.1: In 3.0 the below is not currently true for backwards compatible reasons, but code should not rely on
* this
*
* Because the returned value is a copy, modifying it won't affect this list's contents. If
* you want to alter the data query directly, use the alterDataQuery method
*
* @return DataQuery
*/
public function dataQuery() {
// TODO 3.1: This method potentially mutates self
return /* clone */ $this->dataQuery;
return clone $this->dataQuery;
}
/**
@ -122,7 +118,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
if ($this->inAlterDataQueryCall) {
$list = $this;
$res = $callback($list->dataQuery, $list);
$res = call_user_func($callback, $list->dataQuery, $list);
if ($res) $list->dataQuery = $res;
return $list;
@ -132,7 +128,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
$list->inAlterDataQueryCall = true;
try {
$res = $callback($list->dataQuery, $list);
$res = call_user_func($callback, $list->dataQuery, $list);
if ($res) $list->dataQuery = $res;
}
catch (Exception $e) {
@ -145,39 +141,6 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
}
}
/**
* In 3.0.0 some methods in DataList mutate their list. We don't want to change that in the 3.0.x
* line, but we don't want people relying on it either. This does the same as alterDataQuery, but
* _does_ mutate the existing list.
*
* todo 3.1: All methods that call this need to call alterDataQuery instead
*/
protected function alterDataQuery_30($callback) {
Deprecation::notice('3.1', 'DataList will become immutable in 3.1');
if ($this->inAlterDataQueryCall) {
$res = $callback($this->dataQuery, $this);
if ($res) $this->dataQuery = $res;
return $this;
}
else {
$this->inAlterDataQueryCall = true;
try {
$res = $callback($this->dataQuery, $this);
if ($res) $this->dataQuery = $res;
}
catch (Exception $e) {
$this->inAlterDataQueryCall = false;
throw $e;
}
$this->inAlterDataQueryCall = false;
return $this;
}
}
/**
* Return a new DataList instance with the underlying {@link DataQuery} object changed
*
@ -190,6 +153,21 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
return $clone;
}
public function setDataQueryParam($keyOrArray, $val = null) {
$clone = clone $this;
if(is_array($keyOrArray)) {
foreach($keyOrArray as $key => $val) {
$clone->dataQuery->setQueryParam($key, $val);
}
}
else {
$clone->dataQuery->setQueryParam($keyOrArray, $val);
}
return $clone;
}
/**
* Returns the SQL query that will be used to get this DataList's records. Good for debugging. :-)
*
@ -206,7 +184,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
* @return DataList
*/
public function where($filter) {
return $this->alterDataQuery_30(function($query) use ($filter){
return $this->alterDataQuery(function($query) use ($filter){
$query->where($filter);
});
}
@ -243,7 +221,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
if(!$limit && !$offset) {
return $this;
}
return $this->alterDataQuery_30(function($query) use ($limit, $offset){
return $this->alterDataQuery(function($query) use ($limit, $offset){
$query->limit($limit, $offset);
});
}
@ -281,7 +259,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
$sort = func_get_arg(0);
}
return $this->alterDataQuery_30(function($query, $list) use ($sort, $col, $dir){
return $this->alterDataQuery(function($query, $list) use ($sort, $col, $dir){
if ($col) {
// sort('Name','Desc')
@ -346,25 +324,24 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
throw new InvalidArgumentException('Incorrect number of arguments passed to filter()');
}
// TODO 3.1: Once addFilter doesn't mutate self, this results in a double clone
$clone = clone $this;
$clone->addFilter($filters);
return $clone;
return $this->addFilter($filters);
}
/**
* Return a new instance of the list with an added filter
*/
public function addFilter($filterArray) {
$list = $this;
foreach($filterArray as $field => $value) {
$fieldArgs = explode(':', $field);
$field = array_shift($fieldArgs);
$filterType = array_shift($fieldArgs);
$modifiers = $fieldArgs;
$this->applyFilterContext($field, $filterType, $modifiers, $value);
$list = $list->applyFilterContext($field, $filterType, $modifiers, $value);
}
return $this;
return $list;
}
/**
@ -485,7 +462,6 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
* @todo Deprecated SearchContexts and pull their functionality into the core of the ORM
*/
private function applyFilterContext($field, $comparisators, $modifiers, $value) {
$t = singleton($this->dataClass())->dbObject($field);
if($comparisators) {
$className = "{$comparisators}Filter";
} else {
@ -496,7 +472,8 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
array_unshift($modifiers, $comparisators);
}
$t = new $className($field, $value, $modifiers);
$t->apply($this->dataQuery());
return $this->alterDataQuery(array($t, 'apply'));
}
/**
@ -581,7 +558,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
* @return DataList
*/
public function innerJoin($table, $onClause, $alias = null) {
return $this->alterDataQuery_30(function($query) use ($table, $onClause, $alias){
return $this->alterDataQuery(function($query) use ($table, $onClause, $alias){
$query->innerJoin($table, $onClause, $alias);
});
}
@ -595,7 +572,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
* @return DataList
*/
public function leftJoin($table, $onClause, $alias = null) {
return $this->alterDataQuery_30(function($query) use ($table, $onClause, $alias){
return $this->alterDataQuery(function($query) use ($table, $onClause, $alias){
$query->leftJoin($table, $onClause, $alias);
});
}
@ -810,9 +787,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
$SQL_col = sprintf('"%s"', Convert::raw2sql($key));
}
// todo 3.1: In 3.1 where won't be mutating, so this can be on $this directly
$clone = clone $this;
return $clone->where("$SQL_col = '" . Convert::raw2sql($value) . "'")->First();
return $this->where("$SQL_col = '" . Convert::raw2sql($value) . "'")->First();
}
/**
@ -836,9 +811,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
public function byIDs(array $ids) {
$ids = array_map('intval', $ids); // sanitize
$baseClass = ClassInfo::baseDataClass($this->dataClass);
$this->where("\"$baseClass\".\"ID\" IN (" . implode(',', $ids) .")");
return $this;
return $this->where("\"$baseClass\".\"ID\" IN (" . implode(',', $ids) .")");
}
/**
@ -849,10 +822,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
*/
public function byID($id) {
$baseClass = ClassInfo::baseDataClass($this->dataClass);
// todo 3.1: In 3.1 where won't be mutating, so this can be on $this directly
$clone = clone $this;
return $clone->where("\"$baseClass\".\"ID\" = " . (int)$id)->First();
return $this->where("\"$baseClass\".\"ID\" = " . (int)$id)->First();
}
/**
@ -1008,8 +978,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
*/
public function remove($item) {
// By default, we remove an item from a DataList by deleting it.
if($item instanceof $this->dataClass) $item->delete();
$this->removeByID($item->ID);
}
/**
@ -1028,7 +997,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
* @return DataList
*/
public function reverse() {
return $this->alterDataQuery_30(function($query){
return $this->alterDataQuery(function($query){
$query->reverseSort();
});
}

View File

@ -2722,9 +2722,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if($limit && strpos($limit, ',') !== false) {
$limitArguments = explode(',', $limit);
$result->limit($limitArguments[1],$limitArguments[0]);
$result = $result->limit($limitArguments[1],$limitArguments[0]);
} elseif($limit) {
$result->limit($limit);
$result = $result->limit($limit);
}
if($join) $result = $result->join($join);

View File

@ -193,7 +193,7 @@ class DataQuery {
}
if ($joinTable) {
$query->addLeftJoin($tableClass, "\"$tableClass\".\"ID\" = \"$baseClass\".\"ID\"") ;
$query->addLeftJoin($tableClass, "\"$tableClass\".\"ID\" = \"$baseClass\".\"ID\"", $tableClass, 10) ;
}
}

View File

@ -19,8 +19,9 @@ interface SS_Filterable {
public function canFilterBy($by);
/**
* Filter the list to include items with these charactaristics
* Return a new instance of this list that only includes items with these charactaristics
*
* @return SS_Filterable
* @example $list = $list->filter('Name', 'bob'); // only bob in the list
* @example $list = $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list
* @example $list = $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the age 21
@ -31,8 +32,9 @@ interface SS_Filterable {
public function filter();
/**
* Exclude the list to not contain items with these charactaristics
* Return a new instance of this list that excludes any items with these charactaristics
*
* @return SS_Filterable
* @example $list = $list->exclude('Name', 'bob'); // exclude bob from list
* @example $list = $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list
* @example $list = $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21

View File

@ -21,14 +21,16 @@ class HasManyList extends RelationList {
$this->foreignKey = $foreignKey;
}
protected function foreignIDFilter() {
protected function foreignIDFilter($id = null) {
if ($id === null) $id = $this->getForeignID();
// Apply relation filter
if(is_array($this->foreignID)) {
if(is_array($id)) {
return "\"$this->foreignKey\" IN ('" .
implode("', '", array_map('Convert::raw2sql', $this->foreignID)) . "')";
} else if($this->foreignID !== null){
implode("', '", array_map('Convert::raw2sql', $id)) . "')";
} else if($id !== null){
return "\"$this->foreignKey\" = '" .
Convert::raw2sql($this->foreignID) . "'";
Convert::raw2sql($id) . "'";
}
}
@ -44,18 +46,20 @@ class HasManyList extends RelationList {
user_error("HasManyList::add() expecting a $this->dataClass object, or ID value", E_USER_ERROR);
}
$foreignID = $this->getForeignID();
// Validate foreignID
if(!$this->foreignID) {
if(!$foreignID) {
user_error("ManyManyList::add() can't be called until a foreign ID is set", E_USER_WARNING);
return;
}
if(is_array($this->foreignID)) {
if(is_array($foreignID)) {
user_error("ManyManyList::add() can't be called on a list linked to mulitple foreign IDs", E_USER_WARNING);
return;
}
$fk = $this->foreignKey;
$item->$fk = $this->foreignID;
$item->$fk = $foreignID;
$item->write();
}

View File

@ -591,12 +591,13 @@ class Hierarchy extends DataExtension {
$id = $this->owner->ID;
$children = DataObject::get($baseClass)
->where("\"{$baseClass}\".\"ParentID\" = $id AND \"{$baseClass}\".\"ID\" != $id");
if(!$showAll) $children = $children->where('"ShowInMenus" = 1');
->where("\"{$baseClass}\".\"ParentID\" = $id AND \"{$baseClass}\".\"ID\" != $id")
->setDataQueryParam(array(
'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage',
'Versioned.stage' => 'Live'
));
// Query the live site
$children->dataQuery()->setQueryParam('Versioned.mode', $onlyDeletedFromStage ? 'stage_unique' : 'stage');
$children->dataQuery()->setQueryParam('Versioned.stage', 'Live');
if(!$showAll) $children = $children->where('"ShowInMenus" = 1');
return $children;
}

View File

@ -11,11 +11,11 @@
interface SS_Limitable {
/**
* Returns a filtered version of this where no more than $limit records are included.
* Returns a new instance of this list where no more than $limit records are included.
* If $offset is specified, then that many records at the beginning of the list will be skipped.
* This matches the behaviour of the SQL LIMIT clause.
*
* @return SS_List
* @return SS_Limitable
*/
public function limit($limit, $offset = 0);

View File

@ -9,7 +9,7 @@ class ManyManyList extends RelationList {
protected $localKey;
protected $foreignKey, $foreignID;
protected $foreignKey;
protected $extraFields;
@ -48,19 +48,35 @@ class ManyManyList extends RelationList {
}
/**
* Return a filter expression for the foreign ID.
* Return a filter expression for when getting the contents of the relationship for some foreign ID
* @return string
*/
protected function foreignIDFilter() {
protected function foreignIDFilter($id = null) {
if ($id === null) $id = $this->getForeignID();
// Apply relation filter
if(is_array($this->foreignID)) {
if(is_array($id)) {
return "\"$this->joinTable\".\"$this->foreignKey\" IN ('" .
implode("', '", array_map('Convert::raw2sql', $this->foreignID)) . "')";
} else if($this->foreignID !== null){
implode("', '", array_map('Convert::raw2sql', $id)) . "')";
} else if($id !== null){
return "\"$this->joinTable\".\"$this->foreignKey\" = '" .
Convert::raw2sql($this->foreignID) . "'";
Convert::raw2sql($id) . "'";
}
}
/**
* Return a filter expression for the join table when writing to the join table
*
* When writing (add, remove, removeByID), we need to filter the join table to just the relevant
* entries. However some subclasses of ManyManyList (Member_GroupSet) modify foreignIDFilter to
* include additional calculated entries, so we need different filters when reading and when writing
*
* @return string
*/
protected function foreignIDWriteFilter($id = null) {
return $this->foreignIDFilter($id);
}
/**
* Add an item to this many_many relationship
* Does so by adding an entry to the joinTable.
@ -74,21 +90,24 @@ class ManyManyList extends RelationList {
E_USER_ERROR);
}
$foreignIDs = $this->getForeignID();
$foreignFilter = $this->foreignIDWriteFilter();
// Validate foreignID
if(!$this->foreignID) {
if(!$foreignIDs) {
throw new Exception("ManyManyList::add() can't be called until a foreign ID is set", E_USER_WARNING);
}
if($filter = $this->foreignIDFilter()) {
if($foreignFilter) {
$query = new SQLQuery("*", array("\"$this->joinTable\""));
$query->setWhere($filter);
$query->setWhere($foreignFilter);
$hasExisting = ($query->count() > 0);
} else {
$hasExisting = false;
}
// Insert or update
foreach((array)$this->foreignID as $foreignID) {
foreach((array)$foreignIDs as $foreignID) {
$manipulation = array();
if($hasExisting) {
$manipulation[$this->joinTable]['command'] = 'update';
@ -135,7 +154,7 @@ class ManyManyList extends RelationList {
$query = new SQLQuery("*", array("\"$this->joinTable\""));
$query->setDelete(true);
if($filter = $this->foreignIDFilter()) {
if($filter = $this->foreignIDWriteFilter($this->getForeignID())) {
$query->setWhere($filter);
} else {
user_error("Can't call ManyManyList::remove() until a foreign ID is set", E_USER_WARNING);
@ -177,7 +196,7 @@ class ManyManyList extends RelationList {
if($this->extraFields) {
foreach($this->extraFields as $fieldName => $dbFieldSpec) {
$query = new SQLQuery("\"$fieldName\"", array("\"$this->joinTable\""));
if($filter = $this->foreignIDFilter()) {
if($filter = $this->foreignIDWriteFilter($this->getForeignID())) {
$query->setWhere($filter);
} else {
user_error("Can't call ManyManyList::getExtraData() until a foreign ID is set", E_USER_WARNING);

View File

@ -7,33 +7,9 @@
* @todo Is this additional class really necessary?
*/
abstract class RelationList extends DataList {
protected $foreignID;
/**
* Set the ID of the record that this ManyManyList is linking *from*.
*
* This is the mutatable version of this function, and will be protected only
* from 3.1. Use forForeignID instead
*
* @param $id A single ID, or an array of IDs
*/
public function setForeignID($id) {
// If already filtered on foreign ID, remove that first
if($this->foreignID !== null) {
$oldFilter = $this->foreignIDFilter();
try {
$this->dataQuery->removeFilterOn($oldFilter);
}
catch(InvalidArgumentException $e) { /* NOP */ }
}
// Turn a 1-element array into a simple value
if(is_array($id) && sizeof($id) == 1) $id = reset($id);
$this->foreignID = $id;
$this->dataQuery->where($this->foreignIDFilter());
return $this;
public function getForeignID() {
return $this->dataQuery->getQueryParam('Foreign.ID');
}
/**
@ -41,10 +17,34 @@ abstract class RelationList extends DataList {
* @param $id An ID or an array of IDs.
*/
public function forForeignID($id) {
return $this->alterDataQuery_30(function($query, $list) use ($id){
$list->setForeignID($id);
});
// Turn a 1-element array into a simple value
if(is_array($id) && sizeof($id) == 1) $id = reset($id);
// Calculate the new filter
$filter = $this->foreignIDFilter($id);
$list = $this->alterDataQuery(function($query, $list) use ($id, $filter){
// Check if there is an existing filter, remove if there is
$currentFilter = $query->getQueryParam('Foreign.Filter');
if($currentFilter) {
try {
$query->removeFilterOn($currentFilter);
}
catch (Exception $e) { /* NOP */ }
}
abstract protected function foreignIDFilter();
// Add the new filter
$query->setQueryParam('Foreign.ID', $id);
$query->setQueryParam('Foreign.Filter', $filter);
$query->where($filter);
});
return $list;
}
/**
* Returns a where clause that filters the members of this relationship to just the related items
* @param $id (optional) An ID or an array of IDs - if not provided, will use the current ids as per getForeignID
*/
abstract protected function foreignIDFilter($id = null);
}

View File

@ -276,18 +276,28 @@ class SQLQuery {
* Add a LEFT JOIN criteria to the FROM clause.
*
* @param string $table Unquoted table name
* @param string $onPredicate The "ON" SQL fragment in a "LEFT JOIN ... AS ... ON ..." statement,
* Needs to be valid (quoted) SQL.
* @param string $onPredicate The "ON" SQL fragment in a "LEFT JOIN ... AS ... ON ..." statement, Needs to be valid
* (quoted) SQL.
* @param string $tableAlias Optional alias which makes it easier to identify and replace joins later on
* @param int $order A numerical index to control the order that joins are added to the query; lower order values
* will cause the query to appear first. The default is 20, and joins created automatically by the
* ORM have a value of 10.
* @return SQLQuery
*/
public function addLeftJoin($table, $onPredicate, $tableAlias = null) {
if(!$tableAlias) $tableAlias = $table;
$this->from[$tableAlias] = array('type' => 'LEFT', 'table' => $table, 'filter' => array($onPredicate));
public function addLeftJoin($table, $onPredicate, $tableAlias = '', $order = 20) {
if(!$tableAlias) {
$tableAlias = $table;
}
$this->from[$tableAlias] = array(
'type' => 'LEFT',
'table' => $table,
'filter' => array($onPredicate),
'order' => $order
);
return $this;
}
public function leftjoin($table, $onPredicate, $tableAlias = null) {
public function leftjoin($table, $onPredicate, $tableAlias = null, $order = 20) {
Deprecation::notice('3.0', 'Please use addLeftJoin() instead!');
$this->addLeftJoin($table, $onPredicate, $tableAlias);
}
@ -296,20 +306,28 @@ class SQLQuery {
* Add an INNER JOIN criteria to the FROM clause.
*
* @param string $table Unquoted table name
* @param string $onPredicate The "ON" SQL fragment in an "INNER JOIN ... AS ... ON ..." statement.
* Needs to be valid (quoted) SQL.
* @param string $onPredicate The "ON" SQL fragment in an "INNER JOIN ... AS ... ON ..." statement. Needs to be
* valid (quoted) SQL.
* @param string $tableAlias Optional alias which makes it easier to identify and replace joins later on
* @param int $order A numerical index to control the order that joins are added to the query; lower order values
* will cause the query to appear first. The default is 20, and joins created automatically by the
* ORM have a value of 10.
* @return SQLQuery
*/
public function addInnerJoin($table, $onPredicate, $tableAlias = null) {
public function addInnerJoin($table, $onPredicate, $tableAlias = null, $order = 20) {
if(!$tableAlias) $tableAlias = $table;
$this->from[$tableAlias] = array('type' => 'INNER', 'table' => $table, 'filter' => array($onPredicate));
$this->from[$tableAlias] = array(
'type' => 'INNER',
'table' => $table,
'filter' => array($onPredicate),
'order' => $order
);
return $this;
}
public function innerjoin($table, $onPredicate, $tableAlias = null) {
public function innerjoin($table, $onPredicate, $tableAlias = null, $order = 20) {
Deprecation::notice('3.0', 'Please use addInnerJoin() instead!');
return $this->addInnerJoin($table, $onPredicate, $tableAlias);
return $this->addInnerJoin($table, $onPredicate, $tableAlias, $order);
}
/**
@ -869,12 +887,13 @@ class SQLQuery {
// TODO: Don't require this internal-state manipulate-and-preserve - let sqlQueryToString() handle the new
// syntax
$origFrom = $this->from;
// Sort the joins
$this->from = $this->getOrderedJoins($this->from);
// Build from clauses
foreach($this->from as $alias => $join) {
// $join can be something like this array structure
// array('type' => 'inner', 'table' => 'SiteTree', 'filter' => array("SiteTree.ID = 1",
// "Status = 'approved'"))
// "Status = 'approved'", 'order' => 20))
if(is_array($join)) {
if(is_string($join['filter'])) $filter = $join['filter'];
else if(sizeof($join['filter']) == 1) $filter = $join['filter'][0];
@ -895,10 +914,9 @@ class SQLQuery {
$this->from = $origFrom;
// The query was most likely just created and then exectued.
if($sql === 'SELECT *') {
if(trim($sql) === 'SELECT * FROM') {
return '';
}
return $sql;
}
@ -1080,5 +1098,80 @@ class SQLQuery {
return $query;
}
/**
* Ensure that framework "auto-generated" table JOINs are first in the finalised SQL query.
* This prevents issues where developer-initiated JOINs attempt to JOIN using relations that haven't actually
* yet been scaffolded by the framework. Demonstrated by PostGres in errors like:
*"...ERROR: missing FROM-clause..."
*
* @param $from array - in the format of $this->select
* @return array - and reorderded list of selects
*/
protected function getOrderedJoins($from) {
// shift the first FROM table out from so we only deal with the JOINs
$baseFrom = array_shift($from);
$this->mergesort($from, function($firstJoin, $secondJoin) {
if($firstJoin['order'] == $secondJoin['order']) {
return 0;
}
return ($firstJoin['order'] < $secondJoin['order']) ? -1 : 1;
});
// Put the first FROM table back into the results
array_unshift($from, $baseFrom);
return $from;
}
/**
* Since uasort don't preserve the order of an array if the comparison is equal
* we have to resort to a merge sort. It's quick and stable: O(n*log(n)).
*
* @see http://stackoverflow.com/q/4353739/139301
*
* @param array &$array - the array to sort
* @param callable $cmpFunction - the function to use for comparison
*/
protected function mergesort(&$array, $cmpFunction = 'strcmp') {
// Arrays of size < 2 require no action.
if (count($array) < 2) {
return;
}
// Split the array in half
$halfway = count($array) / 2;
$array1 = array_slice($array, 0, $halfway);
$array2 = array_slice($array, $halfway);
// Recurse to sort the two halves
$this->mergesort($array1, $cmpFunction);
$this->mergesort($array2, $cmpFunction);
// If all of $array1 is <= all of $array2, just append them.
if(call_user_func($cmpFunction, end($array1), reset($array2)) < 1) {
$array = array_merge($array1, $array2);
return;
}
// Merge the two sorted arrays into a single sorted array
$array = array();
$val1 = reset($array1);
$val2 = reset($array2);
do {
if (call_user_func($cmpFunction, $val1, $val2) < 1) {
$array[key($array1)] = $val1;
$val1 = next($array1);
} else {
$array[key($array2)] = $val2;
$val2 = next($array2);
}
} while($val1 && $val2);
// Merge the remainder
while($val1) {
$array[key($array1)] = $val1;
$val1 = next($array1);
}
while($val2) {
$array[key($array2)] = $val2;
$val2 = next($array2);
}
return;
}
}

View File

@ -19,9 +19,10 @@ interface SS_Sortable {
public function canSortBy($by);
/**
* Sorts this list by one or more fields. You can either pass in a single
* Return a new instance of this list that is sorted by one or more fields. You can either pass in a single
* field name and direction, or a map of field names to sort directions.
*
* @return SS_Sortable
* @example $list = $list->sort('Name'); // default ASC sorting
* @example $list = $list->sort('Name DESC'); // DESC sorting
* @example $list = $list->sort('Name', 'ASC');
@ -31,11 +32,10 @@ interface SS_Sortable {
/**
* Reverses the list based on reversing the current sort.
* Return a new instance of this list based on reversing the current sort.
*
* @return SS_Sortable
* @example $list = $list->reverse();
*
* @return array
*/
public function reverse();
}

View File

@ -239,25 +239,14 @@ class UnsavedRelationList extends ArrayList {
return $list->column('ID');
}
/**
* Set the ID of the record that this RelationList is linking.
*
* Adds the
*
* @param $id A single ID, or an array of IDs
*/
public function setForeignID($id) {
$class = singleton($this->baseClass);
$class->ID = 1;
return $class->{$this->relationName}()->setForeignID($id);
}
/**
* Returns a copy of this list with the relationship linked to the given foreign ID.
* @param $id An ID or an array of IDs.
*/
public function forForeignID($id) {
return $this->setForeignID($id);
$class = singleton($this->baseClass);
$class->ID = 1;
return $class->{$this->relationName}()->forForeignID($id);
}
/**

View File

@ -1003,10 +1003,10 @@ class Versioned extends DataExtension {
$containerClass = 'DataList') {
$result = DataObject::get($class, $filter, $sort, $join, $limit, $containerClass);
$dq = $result->dataQuery();
$dq->setQueryParam('Versioned.mode', 'stage');
$dq->setQueryParam('Versioned.stage', $stage);
return $result;
return $result->setDataQueryParam(array(
'Versioned.mode' => 'stage',
'Versioned.stage' => $stage
));
}
public function deleteFromStage($stage) {
@ -1049,8 +1049,10 @@ class Versioned extends DataExtension {
*/
public static function get_latest_version($class, $id) {
$baseClass = ClassInfo::baseDataClass($class);
$list = DataList::create($baseClass)->where("\"$baseClass\".\"RecordID\" = $id");
$list->dataQuery()->setQueryParam("Versioned.mode", "latest_versions");
$list = DataList::create($baseClass)
->where("\"$baseClass\".\"RecordID\" = $id")
->setDataQueryParam("Versioned.mode", "latest_versions");
return $list->First();
}
@ -1077,8 +1079,11 @@ class Versioned extends DataExtension {
* In particular, this will query deleted records as well as active ones.
*/
public static function get_including_deleted($class, $filter = "", $sort = "") {
$list = DataList::create($class)->where($filter)->sort($sort);
$list->dataQuery()->setQueryParam("Versioned.mode", "latest_versions");
$list = DataList::create($class)
->where($filter)
->sort($sort)
->setDataQueryParam("Versioned.mode", "latest_versions");
return $list;
}
@ -1093,8 +1098,9 @@ class Versioned extends DataExtension {
$baseClass = ClassInfo::baseDataClass($class);
$list = DataList::create($baseClass)
->where("\"$baseClass\".\"RecordID\" = $id")
->where("\"$baseClass\".\"Version\" = " . (int)$version);
$list->dataQuery()->setQueryParam('Versioned.mode', 'all_versions');
->where("\"$baseClass\".\"Version\" = " . (int)$version)
->setDataQueryParam("Versioned.mode", 'all_versions');
return $list->First();
}
@ -1104,8 +1110,10 @@ class Versioned extends DataExtension {
*/
public static function get_all_versions($class, $id) {
$baseClass = ClassInfo::baseDataClass($class);
$list = DataList::create($class)->where("\"$baseClass\".\"RecordID\" = $id");
$list->dataQuery()->setQueryParam('Versioned.mode', 'all_versions');
$list = DataList::create($class)
->where("\"$baseClass\".\"RecordID\" = $id")
->setDataQueryParam('Versioned.mode', 'all_versions');
return $list;
}

View File

@ -197,58 +197,68 @@ class Date extends DBField {
}
/**
* Returns the number of seconds/minutes/hours/days or months since the timestamp
* Returns the number of seconds/minutes/hours/days or months since the timestamp.
*
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute".
* @return String
*/
public function Ago() {
public function Ago($includeSeconds = true) {
if($this->value) {
if(strtotime($this->value) == time() || time() > strtotime($this->value)) {
$time = SS_Datetime::now()->Format('U');
if(strtotime($this->value) == $time || $time > strtotime($this->value)) {
return _t(
'Date.TIMEDIFFAGO',
"{difference} ago",
'Natural language time difference, e.g. 2 hours ago',
array('difference' => $this->TimeDiff())
array('difference' => $this->TimeDiff($includeSeconds))
);
} else {
return _t(
'Date.TIMEDIFFIN',
"in {difference}",
'Natural language time difference, e.g. in 2 hours',
array('difference' => $this->TimeDiff())
array('difference' => $this->TimeDiff($includeSeconds))
);
}
}
}
public function TimeDiff() {
/**
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute".
* @return String
*/
public function TimeDiff($includeSeconds = true) {
if(!$this->value) return false;
if($this->value) {
$ago = abs(time() - strtotime($this->value));
$time = SS_Datetime::now()->Format('U');
$ago = abs($time - strtotime($this->value));
if($ago < 60) {
if($ago < 60 && $includeSeconds) {
$span = $ago;
return ($span != 1) ? "{$span} "._t("Date.SECS", " secs") : "{$span} "._t("Date.SEC", " sec");
}
if($ago < 3600) {
$result = ($span != 1) ? "{$span} "._t("Date.SECS", "secs") : "{$span} "._t("Date.SEC", "sec");
} elseif($ago < 60) {
$result = _t('Date.LessThanMinuteAgo', 'less than a minute');
} elseif($ago < 3600) {
$span = round($ago/60);
return ($span != 1) ? "{$span} "._t("Date.MINS", " mins") : "{$span} "._t("Date.MIN", " min");
}
if($ago < 86400) {
$result = ($span != 1) ? "{$span} "._t("Date.MINS", "mins") : "{$span} "._t("Date.MIN", "min");
} elseif($ago < 86400) {
$span = round($ago/3600);
return ($span != 1) ? "{$span} "._t("Date.HOURS", " hours") : "{$span} "._t("Date.HOUR", " hour");
}
if($ago < 86400*30) {
$result = ($span != 1) ? "{$span} "._t("Date.HOURS", "hours") : "{$span} "._t("Date.HOUR", "hour");
} elseif($ago < 86400*30) {
$span = round($ago/86400);
return ($span != 1) ? "{$span} "._t("Date.DAYS", " days") : "{$span} "._t("Date.DAY", " day");
}
if($ago < 86400*365) {
$result = ($span != 1) ? "{$span} "._t("Date.DAYS", "days") : "{$span} "._t("Date.DAY", "day");
} elseif($ago < 86400*365) {
$span = round($ago/86400/30);
return ($span != 1) ? "{$span} "._t("Date.MONTHS", " months") : "{$span} "._t("Date.MONTH", " month");
}
if($ago > 86400*365) {
$result = ($span != 1) ? "{$span} "._t("Date.MONTHS", "months") : "{$span} "._t("Date.MONTH", "month");
} elseif($ago > 86400*365) {
$span = round($ago/86400/365);
return ($span != 1) ? "{$span} "._t("Date.YEARS", " years") : "{$span} "._t("Date.YEAR", " year");
}
$result = ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year");
}
// Replace duplicate spaces, backwards compat with existing translations
$result = preg_replace('/\s+/', ' ', $result);
return $result;
}
/**

View File

@ -41,8 +41,10 @@ $gf_grid_x: 16px;
& > div {
margin-bottom: $gf_grid_y*3;
&.addNewGridFieldButton{
margin-bottom: 0;
.action {
margin-bottom:$gf_grid_y;
@include clearfix;
}
}
}
@ -61,24 +63,18 @@ $gf_grid_x: 16px;
}
p button#action_export {
margin-top:$gf_grid_y;
span.btn-icon-download-csv {
height:17px; //exact height of icon
}
.ui-button-text {
padding-left:26px; //to accomodate wider export icon
}
}
.right {
float:right;
& > * {
float: right;
margin-left:5px;
font-size: $gf_grid_y*1.2;
margin-left:$gf_grid_x/2;
}
.pagination-records-number
{
.pagination-records-number {
font-size: 1.0em;
padding: 6px 3px 6px 0;
color: $color-text-light;
@ -89,11 +85,15 @@ $gf_grid_x: 16px;
.left {
float:left;
& > * {
margin-right:5px;
margin-right:$gf_grid_x/2;
float: left;
font-size: $gf_grid_y*1.2;
}
}
.ss-gridfield-buttonrow {
font-size: $gf_grid_y*1.2;
}
}
.ss-gridfield {
@ -106,16 +106,18 @@ $gf_grid_x: 16px;
margin-bottom: 6px;
}
.add-existing-autocompleter {
span {
@include inline-block(top);
}
input.relation-search {
width: 380px;
width: 270px;
margin-bottom: $gf_grid_y;
}
width: 500px;
}
.grid-print-button{
display: inline-block;
}
.grid-csv-button{
display: inline-block;
.grid-csv-button, .grid-print-button {
margin-bottom: $gf_grid_y;
@include inline-block();
}
}
table.ss-gridfield-table {
@ -252,7 +254,6 @@ $gf_grid_x: 16px;
}
a.view-link, a.edit-link {
display:inline-block;
float: left;
width:20px;
height:20px; //min height to fit the edit icon
text-indent:9999em;

View File

@ -153,7 +153,7 @@ class SearchContext extends Object {
$filter->setModel($this->modelClass);
$filter->setValue($value);
if(! $filter->isEmpty()) {
$filter->apply($query->dataQuery());
$query = $query->alterDataQuery(array($filter, 'apply'));
}
}
}

View File

@ -95,8 +95,8 @@ class Group extends DataObject {
if($this->ID) {
$group = $this;
$config = new GridFieldConfig_RelationEditor();
$config->addComponents(new GridFieldExportButton('before'));
$config->addComponents(new GridFieldPrintButton('before'));
$config->addComponents(new GridFieldExportButton('after'));
$config->addComponents(new GridFieldPrintButton('after'));
$config->getComponentByType('GridFieldAddExistingAutocompleter')
->setResultsFormat('$Title ($Email)')->setSearchFields(array('FirstName', 'Surname', 'Email'));
$config->getComponentByType('GridFieldDetailForm')
@ -236,7 +236,9 @@ class Group extends DataObject {
// Remove the default foreign key filter in prep for re-applying a filter containing all children groups.
// Filters are conjunctive in DataQuery by default, so this filter would otherwise overrule any less specific
// ones.
$result->dataQuery()->removeFilterOn('Group_Members');
$result = $result->alterDataQuery(function($query){
$query->removeFilterOn('Group_Members');
});
// Now set all children groups as a new foreign key
$groups = Group::get()->byIDs($this->collateFamilyIDs());
$result = $result->forForeignID($groups->column('ID'))->where($filter)->sort($sort)->limit($limit);
@ -252,17 +254,6 @@ class Group extends DataObject {
return $this->getManyManyComponents('Members');
}
public static function map($filter = "", $sort = "", $blank="") {
Deprecation::notice('3.0', 'Use DataList::("Group")->map()');
$list = Group::get()->where($filter)->sort($sort);
$map = $list->map();
if($blank) $map->unshift(0, $blank);
return $map;
}
/**
* Return a set of this record's "family" of IDs - the IDs of
* this record and all its descendants.

View File

@ -15,7 +15,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
'RememberLoginToken' => 'Varchar(160)', // Note: this currently holds a hash, not a token.
'NumVisit' => 'Int',
'LastVisited' => 'SS_Datetime',
'Bounced' => 'Boolean', // Note: This does not seem to be used anywhere.
'AutoLoginHash' => 'Varchar(160)',
'AutoLoginExpired' => 'SS_Datetime',
// This is an arbitrary code pointing to a PasswordEncryptor instance,
@ -473,23 +472,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
return $token;
}
/**
* @deprecated 3.0
*/
public function generateAutologinHash($lifetime = 2) {
Deprecation::notice('3.0',
'Member::generateAutologinHash is deprecated - tokens are no longer saved directly into the database '.
'in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token '.
'instead.',
Deprecation::SCOPE_METHOD);
user_error(
'Member::generateAutologinHash is deprecated - tokens are no longer saved directly into the database '.
'in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token '.
'instead.',
E_USER_ERROR);
}
/**
* Check the token against the member.
*
@ -527,35 +509,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
return $member;
}
/**
* Send signup, change password or forgot password informations to an user
*
* @param string $type Information type to send ("signup", "changePassword" or "forgotPassword")
* @param array $data Additional data to pass to the email (can be used in the template)
*/
public function sendInfo($type = 'signup', $data = null) {
Deprecation::notice('3.0',
'Please use Member_ChangePasswordEmail or Member_ForgotPasswordEmail directly instead');
switch($type) {
case "changePassword":
$e = Member_ChangePasswordEmail::create();
break;
case "forgotPassword":
$e = Member_ForgotPasswordEmail::create();
break;
}
if(is_array($data)) {
foreach($data as $key => $value)
$e->$key = $value;
}
$e->populateTemplate($this);
$e->setTo($this->Email);
$e->send();
}
/**
* Returns the fields for the member form - used in the registration/profile module.
* It should return fields that are editable by the admin and the logged-in user.
@ -584,7 +537,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
$fields->removeByName('RememberLoginToken');
$fields->removeByName('NumVisit');
$fields->removeByName('LastVisited');
$fields->removeByName('Bounced');
$fields->removeByName('AutoLoginHash');
$fields->removeByName('AutoLoginExpired');
$fields->removeByName('PasswordEncryption');
@ -1013,42 +965,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
return $this->getManyManyComponents('Groups');
}
/**
* Get member SQLMap
*
* @param string $filter Filter for the SQL statement (WHERE clause)
* @param string $sort Sorting function (ORDER clause)
* @param string $blank Shift a blank member in the items
* @return SQLMap Returns an SQLMap that returns all Member data.
*
* @todo Improve documentation of this function! (Markus)
*/
public static function map($filter = "", $sort = "", $blank="") {
Deprecation::notice('3.0', 'Use DataList::("Member")->map()');
$list = Member::get()->where($filter)->sort($sort);
$map = $list->map();
if($blank) $map->unshift(0, $blank);
return $map;
}
/**
* Get a member SQLMap of members in specific groups
*
* If no $groups is passed, all members will be returned
*
* @param mixed $groups - takes a SS_List, an array or a single Group.ID
* @return SQLMap Returns an SQLMap that returns all Member data.
* @see map()
*/
public static function mapInGroups($groups = null) {
Deprecation::notice('3.0', 'Use Member::map_in_groups() instead');
return self::map_in_groups();
}
/**
* Get a member SQLMap of members in specific groups
*
@ -1197,7 +1113,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
i18n::get_existing_translations()
));
$mainFields->removeByName('Bounced');
$mainFields->removeByName('RememberLoginToken');
$mainFields->removeByName('AutoLoginHash');
$mainFields->removeByName('AutoLoginExpired');
@ -1505,14 +1420,12 @@ class Member_GroupSet extends ManyManyList {
/**
* Link this group set to a specific member.
*/
public function setForeignID($id) {
// Turn a 1-element array into a simple value
if(is_array($id) && sizeof($id) == 1) $id = reset($id);
$this->foreignID = $id;
public function foreignIDFilter($id = null) {
if ($id === null) $id = $this->getForeignID();
// Find directly applied groups
$manymanyFilter = $this->foreignIDFilter();
$groupIDs = DB::query('SELECT "GroupID" FROM "Group_Members" WHERE ' . $manymanyFilter)->column();
$manyManyFilter = parent::foreignIDFilter($id);
$groupIDs = DB::query('SELECT "GroupID" FROM "Group_Members" WHERE ' . $manyManyFilter)->column();
// Get all ancestors
$allGroupIDs = array();
@ -1523,8 +1436,16 @@ class Member_GroupSet extends ManyManyList {
}
// Add a filter to this DataList
if($allGroupIDs) $this->byIDs($allGroupIDs);
else $this->byIDs(array(0));
if($allGroupIDs) {
return "\"Group\".\"ID\" IN (" . implode(',', $allGroupIDs) .")";
}
else {
return "\"Group\".\"ID\" = 0";
}
}
public function foreignIDWriteFilter($id = null) {
return parent::foreignIDFilter($id);
}
}

View File

@ -25,29 +25,6 @@ abstract class PasswordEncryptor {
return Config::inst()->get('PasswordEncryptor', 'encryptors');
}
/**
* Add a new encryptor implementation.
*
* Note: Due to portability concerns, its not advisable to
* override an existing $code mapping with different behaviour.
*
* @param String $code This value will be stored stored in the
* {@link Member->PasswordEncryption} property.
* @param String $class Classname of a {@link PasswordEncryptor} subclass
*/
public static function register($code, $class) {
Deprecation::notice('3.0', 'Use the Config system to register Password encryptors');
self::$encryptors[$code] = $class;
}
/**
* @param String $code Unique lookup.
*/
public static function unregister($code) {
Deprecation::notice('3.0', 'Use the Config system to unregister Password encryptors');
if(isset(self::$encryptors[$code])) unset(self::$encryptors[$code]);
}
/**
* @param String $algorithm
* @return PasswordEncryptor

View File

@ -83,6 +83,14 @@ class Security extends Controller {
*/
protected static $default_message_set = '';
/**
* Random secure token, can be used as a crypto key internally.
* Generate one through 'sake dev/generatesecuretoken'.
*
* @var String
*/
public static $token;
/**
* Get location of word list file
*/

View File

@ -1,4 +1,4 @@
<div class="addNewGridFieldButton">
<div class="addNewGridFieldButton ss-gridfield-buttonrow">
<div class="left">$LeftFragment</div>
<div class="right">$RightFragment</div>
</div>

View File

@ -49,16 +49,17 @@
<% _t('UploadField.ATTACHFILE', 'Attach a file') %>
<% end_if %>
</b></label>
<label class="ss-uploadfield-fromcomputer ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Upload from your computer') %>" data-icon="drive-upload">
<label class="ss-uploadfield-fromcomputer ss-ui-button ui-corner-all" data-icon="drive-upload">
<% _t('UploadField.FROMCOMPUTER', 'From your computer') %>
<input id="$id" name="$getName" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="$configString" type="file"<% if $multiple %> multiple="multiple"<% end_if %> />
</label>
<button class="ss-uploadfield-fromfiles ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Select from files') %>" data-icon="network-cloud"><% _t('UploadField.FROMFILES', 'From files') %></button>
<button class="ss-uploadfield-fromfiles ss-ui-button ui-corner-all" data-icon="network-cloud"><% _t('UploadField.FROMFILES', 'From files') %></button>
<% if not $autoUpload %>
<button class="ss-uploadfield-startall ss-ui-button ui-corner-all" title="<% _t('UploadField.STARTALLINFO', 'Start all uploads') %>" data-icon="navigation"><% _t('UploadField.STARTALL', 'Start all') %></button>
<button class="ss-uploadfield-startall ss-ui-button ui-corner-all" data-icon="navigation"><% _t('UploadField.STARTALL', 'Start all') %></button>
<% end_if %>
<div class="clear"><!-- --></div>
</div>
<div class="clear"><!-- --></div>
</div>
<% end_if %>
<% if Description %><span class="description">$Description</span><% end_if %>

View File

@ -2,4 +2,5 @@
$Field
<label class="right" for="$ID">$Title</label>
<% if Message %><span class="message $MessageType">$Message</span><% end_if %>
<% if Description %><span class="description">$Description</span><% end_if %>
</div>

View File

@ -1,4 +1,4 @@
<ul id="$ID" class="$extraClass"<% if Description %> title="$Description"<% end_if %>>
<ul id="$ID" class="$extraClass">
<% if Options.Count %>
<% loop Options %>
<li class="$Class">

View File

@ -12,4 +12,6 @@
$FieldHolder
<% end_if %>
<% end_loop %>
<% if Description %><span class="description">$Description</span><% end_if %>
</$Tag>

View File

@ -1,7 +1,5 @@
<% if isReadonly %>
<span id="$ID"
<% if extraClass %>class="$extraClass"<% end_if %>
<% if $Description %>title="$Description"<% end_if %>>
<span id="$ID"<% if extraClass %> class="$extraClass"<% end_if %>>
$Value
</span>
<% else %>

View File

@ -5,4 +5,5 @@
</div>
<% if RightTitle %><label class="right" for="$ID">$RightTitle</label><% end_if %>
<% if Message %><span class="message $MessageType">$Message</span><% end_if %>
<% if Description %><span class="description">$Description</span><% end_if %>
</div>

View File

@ -1,4 +1,4 @@
<ul id="$ID" class="$extraClass"<% if Description %> title="$Description"<% end_if %>>
<ul id="$ID" class="$extraClass">
<% loop Options %>
<li class="$Class">
<input id="$ID" class="radio" name="$Name" type="radio" value="$Value"<% if isChecked %> checked<% end_if %><% if isDisabled %> disabled<% end_if %> />

View File

@ -0,0 +1,13 @@
@database-defaults
Feature: My Profile
As a CMS user
I want to be able to change personal settings
In order to streamline my CMS experience
@javascript
Scenario: I can see date formatting help
Given I am logged in with "ADMIN" permissions
# Only tests this specific field and admin UI because its got built-in tooltips
When I go to "/admin/myprofile"
And I follow "Show formatting help"
Then I should see "Four-digit year"

View File

@ -122,6 +122,7 @@ class CheckboxFieldTest extends SapphireTest {
// Test 1: a checked checkbox goes to "Yes"
$field1 = new CheckboxField('IsChecked', 'Checked');
$field1->setValue('on');
$copy = $field1->performReadonlyTransformation();
$this->assertEquals(_t('CheckboxField.YES', 'Yes'),
trim(strip_tags($field1->performReadonlyTransformation()->Field())));

View File

@ -442,7 +442,7 @@ class InjectorTest extends SapphireTest {
public function testCustomObjectCreator() {
$injector = new Injector();
$injector->setObjectCreator(new SSObjectCreator());
$injector->setObjectCreator(new SSObjectCreator($injector));
$config = array(
'OriginalRequirementsBackend',
'DummyRequirements' => array(
@ -485,9 +485,65 @@ class InjectorTest extends SapphireTest {
$again = $injector->get('NeedsBothCirculars');
$this->assertEquals($again->var, 'One');
}
public function testConvertServicePropertyOnCreate() {
// make sure convert service property is not called on direct calls to create, only on configured
// declarations to avoid un-needed function calls
$injector = new Injector();
$item = $injector->create('ConstructableObject', '%$TestObject');
$this->assertEquals('%$TestObject', $item->property);
// do it again but have test object configured as a constructor dependency
$injector = new Injector();
$config = array(
'ConstructableObject' => array(
'constructor' => array(
'%$TestObject'
)
)
);
$injector->load($config);
$item = $injector->get('ConstructableObject');
$this->assertTrue($item->property instanceof TestObject);
// and with a configured object defining TestObject to be something else!
$injector = new Injector(array('locator' => 'InjectorTestConfigLocator'));
$config = array(
'ConstructableObject' => array(
'constructor' => array(
'%$TestObject'
)
),
);
$injector->load($config);
$item = $injector->get('ConstructableObject');
$this->assertTrue($item->property instanceof ConstructableObject);
$this->assertInstanceOf('OtherTestObject', $item->property->property);
}
}
class TestObject {
class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly {
public function locateConfigFor($name) {
if ($name == 'TestObject') {
return array('class' => 'ConstructableObject', 'constructor' => array('%$OtherTestObject'));
}
return parent::locateConfigFor($name);
}
}
class ConstructableObject implements TestOnly {
public $property;
public function __construct($prop) {
$this->property = $prop;
}
}
class TestObject implements TestOnly {
public $sampleService;
@ -497,7 +553,7 @@ class TestObject {
}
class OtherTestObject {
class OtherTestObject implements TestOnly {
private $sampleService;
@ -511,13 +567,13 @@ class OtherTestObject {
}
class CircularOne {
class CircularOne implements TestOnly {
public $circularTwo;
}
class CircularTwo {
class CircularTwo implements TestOnly {
public $circularOne;
@ -528,7 +584,7 @@ class CircularTwo {
}
}
class NeedsBothCirculars {
class NeedsBothCirculars implements TestOnly{
public $circularOne;
public $circularTwo;
@ -536,15 +592,15 @@ class NeedsBothCirculars {
}
class MyParentClass {
class MyParentClass implements TestOnly {
public $one;
}
class MyChildClass extends MyParentClass {
class MyChildClass extends MyParentClass implements TestOnly {
}
class DummyRequirements {
class DummyRequirements implements TestOnly {
public $backend;
@ -558,15 +614,15 @@ class DummyRequirements {
}
class OriginalRequirementsBackend {
class OriginalRequirementsBackend implements TestOnly {
}
class NewRequirementsBackend {
class NewRequirementsBackend implements TestOnly {
}
class TestStaticInjections {
class TestStaticInjections implements TestOnly {
public $backend;
static $dependencies = array(
@ -582,13 +638,19 @@ class TestStaticInjections {
* @see https://github.com/silverstripe/sapphire
*/
class SSObjectCreator extends InjectionCreator {
private $injector;
public function create(Injector $injector, $class, $params = array()) {
public function __construct($injector) {
$this->injector = $injector;
}
public function create($class, $params = array()) {
if (strpos($class, '(') === false) {
return parent::create($injector, $class, $params);
return parent::create($class, $params);
} else {
list($class, $params) = self::parse_class_spec($class);
return parent::create($injector, $class, $params);
$params = $this->injector->convertServiceProperty($params);
return parent::create($class, $params);
}
}

View File

@ -229,14 +229,14 @@ class ArrayListTest extends SapphireTest {
));
}
public function testSortSimpleDefualtIsSortedASC() {
public function testSortSimpleDefaultIsSortedASC() {
$list = new ArrayList(array(
array('Name' => 'Steve'),
(object) array('Name' => 'Bob'),
array('Name' => 'John')
));
$list->sort('Name');
$list = $list->sort('Name');
$this->assertEquals($list->toArray(), array(
(object) array('Name' => 'Bob'),
array('Name' => 'John'),
@ -250,7 +250,8 @@ class ArrayListTest extends SapphireTest {
(object) array('Name' => 'Bob'),
array('Name' => 'John')
));
$list->sort('Name','asc');
$list = $list->sort('Name','asc');
$this->assertEquals($list->toArray(), array(
(object) array('Name' => 'Bob'),
array('Name' => 'John'),
@ -265,7 +266,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'John')
));
$list->sort('Name', 'DESC');
$list = $list->sort('Name', 'DESC');
$this->assertEquals($list->toArray(), array(
array('Name' => 'Steve'),
array('Name' => 'John'),
@ -280,8 +281,8 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'Steve')
));
$list->sort('Name', 'ASC');
$list->reverse();
$list = $list->sort('Name', 'ASC');
$list = $list->reverse();
$this->assertEquals($list->toArray(), array(
array('Name' => 'Steve'),
@ -297,11 +298,11 @@ class ArrayListTest extends SapphireTest {
(object) array('Name'=>'Object3', 'F1'=>5, 'F2'=>2, 'F3'=>2),
));
$list->sort('F3', 'ASC');
$list = $list->sort('F3', 'ASC');
$this->assertEquals($list->first()->Name, 'Object3', 'Object3 should be first in the list');
$this->assertEquals($list->last()->Name, 'Object2', 'Object2 should be last in the list');
$list->sort('F3', 'DESC');
$list = $list->sort('F3', 'DESC');
$this->assertEquals($list->first()->Name, 'Object2', 'Object2 should be first in the list');
$this->assertEquals($list->last()->Name, 'Object3', 'Object3 should be last in the list');
}
@ -313,11 +314,11 @@ class ArrayListTest extends SapphireTest {
(object) array('ID'=>2, 'Name'=>'Aron', 'Importance'=>1),
));
$list->sort(array('Name'=>'ASC', 'Importance'=>'ASC'));
$list = $list->sort(array('Name'=>'ASC', 'Importance'=>'ASC'));
$this->assertEquals($list->first()->ID, 2, 'Aron.2 should be first in the list');
$this->assertEquals($list->last()->ID, 3, 'Bert.3 should be last in the list');
$list->sort(array('Name'=>'ASC', 'Importance'=>'DESC'));
$list = $list->sort(array('Name'=>'ASC', 'Importance'=>'DESC'));
$this->assertEquals($list->first()->ID, 1, 'Aron.2 should be first in the list');
$this->assertEquals($list->last()->ID, 3, 'Bert.3 should be last in the list');
}
@ -331,7 +332,7 @@ class ArrayListTest extends SapphireTest {
(object) array('Name' => 'Bob'),
array('Name' => 'John')
));
$list->filter('Name','Bob');
$list = $list->filter('Name','Bob');
$this->assertEquals(array((object)array('Name'=>'Bob')), $list->toArray(), 'List should only contain Bob');
}
@ -349,7 +350,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'Steve'),
array('Name' => 'John')
);
$list->filter('Name',array('Steve','John'));
$list = $list->filter('Name',array('Steve','John'));
$this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and John');
}
@ -362,7 +363,7 @@ class ArrayListTest extends SapphireTest {
(object) array('Name' => 'Steve', 'ID' => 2),
array('Name' => 'John', 'ID' => 2)
));
$list->filter(array('Name'=>'Clair'));
$list = $list->filter(array('Name'=>'Clair'));
$this->assertEquals(array(), $list->toArray(), 'List should be empty');
}
@ -375,7 +376,7 @@ class ArrayListTest extends SapphireTest {
(object) array('Name' => 'Steve', 'ID' => 2),
array('Name' => 'John', 'ID' => 2)
));
$list->filter(array('Name'=>'Steve', 'ID'=>2));
$list = $list->filter(array('Name'=>'Steve', 'ID'=>2));
$this->assertEquals(array((object)array('Name'=>'Steve', 'ID'=>2)), $list->toArray(),
'List should only contain object Steve');
}
@ -389,7 +390,7 @@ class ArrayListTest extends SapphireTest {
(object) array('Name' => 'Steve', 'ID' => 2),
array('Name' => 'John', 'ID' => 2)
));
$list->filter(array('Name'=>'Steve', 'ID'=>4));
$list = $list->filter(array('Name'=>'Steve', 'ID'=>4));
$this->assertEquals(array(), $list->toArray(), 'List should be empty');
}
@ -404,7 +405,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'Steve', 'ID' => 3, 'Age'=>43)
));
$list->filter(array('Name'=>'Steve','Age'=>array(21, 43)));
$list = $list->filter(array('Name'=>'Steve','Age'=>array(21, 43)));
$expected = array(
array('Name' => 'Steve', 'ID' => 1, 'Age'=>21),
@ -426,7 +427,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'Steve', 'ID' => 3, 'Age'=>43)
));
$list->filter(array('Name'=>array('Steve','Clair'),'Age'=>array(21, 43)));
$list = $list->filter(array('Name'=>array('Steve','Clair'),'Age'=>array(21, 43)));
$expected = array(
array('Name' => 'Steve', 'ID' => 1, 'Age'=>21),
@ -448,7 +449,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'John')
));
$list->exclude('Name', 'Bob');
$list = $list->exclude('Name', 'Bob');
$expected = array(
array('Name' => 'Steve'),
array('Name' => 'John')
@ -467,7 +468,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'John')
));
$list->exclude('Name', 'Clair');
$list = $list->exclude('Name', 'Clair');
$expected = array(
array('Name' => 'Steve'),
array('Name' => 'Bob'),
@ -485,7 +486,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'Bob'),
array('Name' => 'John')
));
$list->exclude('Name', array('Steve','John'));
$list = $list->exclude('Name', array('Steve','John'));
$expected = array(array('Name' => 'Bob'));
$this->assertEquals(1, $list->count());
$this->assertEquals($expected, $list->toArray(), 'List should only contain Bob');
@ -501,7 +502,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'John', 'Age' => 21)
));
$list->exclude(array('Name' => 'Bob', 'Age' => 21));
$list = $list->exclude(array('Name' => 'Bob', 'Age' => 21));
$expected = array(
array('Name' => 'Bob', 'Age' => 32),
@ -527,7 +528,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'phil', 'Age' => 16)
));
$list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16)));
$list = $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16)));
$expected = array(
array('Name' => 'phil', 'Age' => 11),
array('Name' => 'bob', 'Age' => 12),
@ -553,7 +554,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'phil', 'Age' => 16)
));
$list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'Bananas'=>true));
$list = $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'Bananas'=>true));
$expected = array(
array('Name' => 'bob', 'Age' => 10),
array('Name' => 'phil', 'Age' => 11),
@ -584,7 +585,7 @@ class ArrayListTest extends SapphireTest {
array('Name' => 'clair','Age' => 16, 'HasBananas'=>true)
));
$list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'HasBananas'=>true));
$list = $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'HasBananas'=>true));
$expected = array(
array('Name' => 'bob', 'Age' => 10, 'HasBananas'=>false),
array('Name' => 'phil','Age' => 11, 'HasBananas'=>true),

36
tests/model/DBTest.php Normal file
View File

@ -0,0 +1,36 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class DBTest extends SapphireTest {
protected $origEnvType;
function setUp() {
$this->origEnvType = Director::get_environment_type();
Director::set_environment_type('dev');
parent::setUp();
}
function tearDown() {
Director::set_environment_type($this->origEnvType);
parent::tearDown();
}
function testValidAlternativeDatabaseName() {
$this->assertTrue(DB::valid_alternative_database_name('ss_tmpdb1234567'));
$this->assertFalse(DB::valid_alternative_database_name('ss_tmpdb12345678'));
$this->assertFalse(DB::valid_alternative_database_name('tmpdb1234567'));
$this->assertFalse(DB::valid_alternative_database_name('random'));
$this->assertFalse(DB::valid_alternative_database_name(''));
$origEnvType = Director::get_environment_type();
Director::set_environment_type('live');
$this->assertFalse(DB::valid_alternative_database_name('ss_tmpdb1234567'));
Director::set_environment_type($origEnvType);
}
}

View File

@ -79,9 +79,14 @@ class DataListTest extends SapphireTest {
public function testInnerJoin() {
$db = DB::getConn();
$list = DataObjectTest_TeamComment::get();
$list->innerJoin('DataObjectTest_Team', '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
'Team');
$list = $list->innerJoin(
'DataObjectTest_Team',
'"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
'Team'
);
$expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", "DataObjectTest_TeamComment"."Created",'
. ' "DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Name",'
. ' "DataObjectTest_TeamComment"."Comment", "DataObjectTest_TeamComment"."TeamID",'
@ -89,14 +94,20 @@ class DataListTest extends SapphireTest {
. ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '.$db->prepStringForDB('DataObjectTest_TeamComment')
. ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" INNER JOIN "DataObjectTest_Team" AS "Team"'
. ' ON "DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"';
$this->assertEquals($expected, $list->sql());
}
public function testLeftJoin() {
$db = DB::getConn();
$list = DataObjectTest_TeamComment::get();
$list->leftJoin('DataObjectTest_Team', '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
'Team');
$list = $list->leftJoin(
'DataObjectTest_Team',
'"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
'Team'
);
$expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", "DataObjectTest_TeamComment"."Created",'
. ' "DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Name",'
. ' "DataObjectTest_TeamComment"."Comment", "DataObjectTest_TeamComment"."TeamID",'
@ -104,14 +115,16 @@ class DataListTest extends SapphireTest {
. ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '.$db->prepStringForDB('DataObjectTest_TeamComment')
. ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" LEFT JOIN "DataObjectTest_Team" AS "Team"'
. ' ON "DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"';
$this->assertEquals($expected, $list->sql());
// Test with namespaces (with non-sensical join, but good enough for testing)
$list = DataObjectTest_TeamComment::get();
$list->leftJoin(
$list = $list->leftJoin(
'DataObjectTest\NamespacedClass',
'"DataObjectTest\NamespacedClass"."ID" = "DataObjectTest_TeamComment"."ID"'
);
$expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
. '"DataObjectTest_TeamComment"."Created", '
. '"DataObjectTest_TeamComment"."LastEdited", '
@ -126,6 +139,7 @@ class DataListTest extends SapphireTest {
. 'LEFT JOIN "DataObjectTest\NamespacedClass" ON '
. '"DataObjectTest\NamespacedClass"."ID" = "DataObjectTest_TeamComment"."ID"';
$this->assertEquals($expected, $list->sql(), 'Retains backslashes in namespaced classes');
}
public function testToNestedArray() {

View File

@ -1,6 +1,13 @@
<?php
class DataQueryTest extends SapphireTest {
protected $extraDataObjects = array(
'DataQueryTest_A',
'DataQueryTest_B',
'DataQueryTest_D',
);
/**
* Test the leftJoin() and innerJoin method of the DataQuery object
*/
@ -33,6 +40,12 @@ class DataQueryTest extends SapphireTest {
'DataQuery::applyRelation should return the name of the related object.');
}
public function testRelationOrderWithCustomJoin() {
$dataQuery = new DataQuery('DataQueryTest_B');
$dataQuery->innerJoin('DataQueryTest_D', '"DataQueryTest_D"."RelationID" = "DataQueryTest_B"."ID"');
$dataQuery->execute();
}
public function testDisjunctiveGroup() {
$dq = new DataQuery('DataQueryTest_A');
@ -126,6 +139,7 @@ class DataQueryTest_B extends DataQueryTest_A {
}
class DataQueryTest_C extends DataObject implements TestOnly {
public static $has_one = array(
'TestA' => 'DataQueryTest_A',
'TestB' => 'DataQueryTest_B',
@ -141,3 +155,10 @@ class DataQueryTest_C extends DataObject implements TestOnly {
'ManyTestBs' => 'DataQueryTest_B',
);
}
class DataQueryTest_D extends DataObject implements TestOnly {
public static $has_one = array(
'Relation' => 'DataQueryTest_B',
);
}

View File

@ -160,4 +160,46 @@ class DateTest extends SapphireTest {
$this->assertEquals('03 Apr 3000', $date->Format('d M Y'));
}
public function testAgoInPast() {
SS_Datetime::set_mock_now('2000-12-31 12:00:00');
$this->assertEquals(
'10 years ago',
DBField::create_field('Date', '1990-12-31')->Ago(),
'Exact past match on years'
);
$this->assertEquals(
'10 years ago',
DBField::create_field('Date', '1990-12-30')->Ago(),
'Approximate past match on years'
);
$this->assertEquals(
'1 year ago',
DBField::create_field('Date', '1999-12-30')->Ago(),
'Approximate past match in singular'
);
SS_Datetime::clear_mock_now();
}
public function testAgoInFuture() {
SS_Datetime::set_mock_now('2000-12-31 00:00:00');
$this->assertEquals(
'in 100 years',
DBField::create_field('Date', '2100-12-31')->Ago(),
'Exact past match on years'
);
$this->assertEquals(
'in 1 day',
DBField::create_field('Date', '2001-01-01')->Ago(),
'Approximate past match on minutes'
);
SS_Datetime::clear_mock_now();
}
}

View File

@ -89,4 +89,64 @@ class SS_DatetimeTest extends SapphireTest {
$this->assertEquals('2001-12-31%2022:10:59', $date->URLDateTime());
}
public function testAgoInPast() {
SS_Datetime::set_mock_now('2000-12-31 12:00:00');
$this->assertEquals(
'10 years ago',
DBField::create_field('SS_Datetime', '1990-12-31 12:00:00')->Ago(),
'Exact past match on years'
);
$this->assertEquals(
'10 years ago',
DBField::create_field('SS_Datetime', '1990-12-30 12:00:00')->Ago(),
'Approximate past match on years'
);
$this->assertEquals(
'1 year ago',
DBField::create_field('SS_Datetime', '1999-12-30 12:00:12')->Ago(),
'Approximate past match in singular'
);
$this->assertEquals(
'50 mins ago',
DBField::create_field('SS_Datetime', '2000-12-31 11:10:11')->Ago(),
'Approximate past match on minutes'
);
$this->assertEquals(
'59 secs ago',
DBField::create_field('SS_Datetime', '2000-12-31 11:59:01')->Ago(),
'Approximate past match on seconds'
);
$this->assertEquals(
'less than a minute ago',
DBField::create_field('SS_Datetime', '2000-12-31 11:59:01')->Ago(false),
'Approximate past match on seconds with $includeSeconds=false'
);
SS_Datetime::clear_mock_now();
}
public function testAgoInFuture() {
SS_Datetime::set_mock_now('2000-12-31 00:00:00');
$this->assertEquals(
'in 100 years',
DBField::create_field('SS_Datetime', '2100-12-31 12:00:00')->Ago(),
'Exact past match on years'
);
$this->assertEquals(
'in 1 hour',
DBField::create_field('SS_Datetime', '2000-12-31 1:01:05')->Ago(),
'Approximate past match on minutes'
);
SS_Datetime::clear_mock_now();
}
}

View File

@ -293,6 +293,7 @@ class SQLQueryTest extends SapphireTest {
);
}
public function testSetWhereAny() {
$query = new SQLQuery();
$query->setFrom('MyTable');
@ -376,4 +377,17 @@ class SQLQueryTest_DO extends DataObject implements TestOnly {
);
}
class SQLQueryTestBase extends DataObject implements TestOnly {
static $db = array(
"Title" => "Varchar",
);
}
class SQLQueryTestChild extends SQLQueryTestBase {
static $db = array(
"Name" => "Varchar",
);
static $has_one = array(
);
}

View File

@ -25,30 +25,6 @@ class GroupTest extends FunctionalTest {
$this->assertNull($g3->Code, 'Default title doesnt trigger attribute setting');
}
/**
* Test the Group::map() function
*/
public function testGroupMap() {
// 2.4 only
$originalDeprecation = Deprecation::dump_settings();
Deprecation::notification_version('2.4');
/* Group::map() returns an SQLMap object implementing iterator. You can use foreach to get ID-Title pairs. */
// We will iterate over the map and build mapOuput to more easily call assertions on the result.
$map = Group::map();
$mapOutput = $map->toArray();
$group1 = $this->objFromFixture('Group', 'group1');
$group2 = $this->objFromFixture('Group', 'group2');
/* We have added 2 groups to our fixture. They should both appear in $mapOutput. */
$this->assertEquals($mapOutput[$group1->ID], $group1->Title);
$this->assertEquals($mapOutput[$group2->ID], $group2->Title);
Deprecation::restore_settings($originalDeprecation);
}
public function testMemberGroupRelationForm() {
Session::set('loggedInAs', $this->idFromFixture('GroupTest_Member', 'admin'));

View File

@ -4,6 +4,14 @@
BUILD_DIR=$1
# Environment info
echo "# Environment info"
echo " - `php --version`"
echo " - `mysql --version`"
echo " - `pg_config --version`"
echo " - SQLite3 `sqlite3 -version`"
echo ""
# Fetch all dependencies
# TODO Replace with different composer.json variations