Merge remote-tracking branch 'origin/3'

Conflicts:
	core/Constants.php
	docs/en/05_Contributing/01_Code.md
	tests/model/SQLQueryTest.php
This commit is contained in:
Ingo Schommer 2015-04-09 16:26:16 +12:00
commit 72a284c9b8
168 changed files with 3438 additions and 1090 deletions

View File

@ -322,25 +322,28 @@ abstract class ModelAdmin extends LeftAndMain {
* @return Form * @return Form
*/ */
public function ImportForm() { public function ImportForm() {
$modelName = $this->modelClass; $modelSNG = singleton($this->modelClass);
$modelName = $modelSNG->i18n_singular_name();
// check if a import form should be generated // check if a import form should be generated
if(!$this->showImportForm || (is_array($this->showImportForm) && !in_array($modelName,$this->showImportForm))) { if(!$this->showImportForm ||
(is_array($this->showImportForm) && !in_array($this->modelClass, $this->showImportForm))
) {
return false; return false;
} }
$importers = $this->getModelImporters(); $importers = $this->getModelImporters();
if(!$importers || !isset($importers[$modelName])) return false; if(!$importers || !isset($importers[$this->modelClass])) return false;
if(!singleton($modelName)->canCreate(Member::currentUser())) return false; if(!$modelSNG->canCreate(Member::currentUser())) return false;
$fields = new FieldList( $fields = new FieldList(
new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $modelName), new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $this->modelClass),
new FileField('_CsvFile', false) new FileField('_CsvFile', false)
); );
// get HTML specification for each import (column names etc.) // get HTML specification for each import (column names etc.)
$importerClass = $importers[$modelName]; $importerClass = $importers[$this->modelClass];
$importer = new $importerClass($modelName); $importer = new $importerClass($this->modelClass);
$spec = $importer->getImportSpec(); $spec = $importer->getImportSpec();
$specFields = new ArrayList(); $specFields = new ArrayList();
foreach($spec['fields'] as $name => $desc) { foreach($spec['fields'] as $name => $desc) {

View File

@ -9,7 +9,7 @@
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
.cms .ss-ui-button { background-color: #e6e6e6; } .cms .ss-ui-button { background-color: #e6e6e6; }
.cms .ss-ui-button.ui-state-hover { background-color: #f3f3f3; } .cms .ss-ui-button.ui-state-hover { background-color: #f3f3f3; }
.cms .ss-ui-button.ss-ui-action-constructive { background-color: #1F9433; } .cms .ss-ui-button.ss-ui-action-constructive { background-color: #1f9433; }
.cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover { background-color: #23a93a; } .cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover { background-color: #23a93a; }
.cms .ss-ui-button.ss-gridfield-button-filter { background: #55a4d2 url(../../images/icons/filter-icons.png) no-repeat -14px 4px; } .cms .ss-ui-button.ss-gridfield-button-filter { background: #55a4d2 url(../../images/icons/filter-icons.png) no-repeat -14px 4px; }
@ -20,7 +20,7 @@
.ss-gridfield-button-filter.ss-ui-button.hover-alike { background-color: #338DC1; background-position: -16px 6px; filter: none; } .ss-gridfield-button-filter.ss-ui-button.hover-alike { background-color: #338DC1; background-position: -16px 6px; filter: none; }
.ss-gridfield-button-reset.ss-ui-button { background: #e6e6e6 url(../images/filter-icons.png) no-repeat 8px 5px; filter: none; } .ss-gridfield-button-reset.ss-ui-button { background: #e6e6e6 url(../images/filter-icons.png) no-repeat 8px 5px; filter: none; }
.ss-gridfield-button-reset.ss-ui-button.filtered:hover { background: #f00 url(../images/filter-icons.png) no-repeat 8px -17px; filter: none; } .ss-gridfield-button-reset.ss-ui-button.filtered:hover { background: red url(../images/filter-icons.png) no-repeat 8px -17px; filter: none; }
.ss-gridfield-button-reset.ss-ui-button.filtered:active { background: #e60000 url(../images/filter-icons.png) no-repeat 9px -16px; filter: none; } .ss-gridfield-button-reset.ss-ui-button.filtered:active { background: #e60000 url(../images/filter-icons.png) no-repeat 9px -16px; filter: none; }
.cms table.ss-gridfield-table tr td { border-right: 1px solid #9a9a9a; } .cms table.ss-gridfield-table tr td { border-right: 1px solid #9a9a9a; }
@ -54,77 +54,77 @@ fieldset.switch-states .switch input.state-name { margin-left: -20px; }
.cms-content-controls .preview-size-selector { display: none; } .cms-content-controls .preview-size-selector { display: none; }
/** Helper SCSS file for generating sprites for the interface. */ /** 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-image: url('../images/btn-icon-sf963a8adf3.png'); background-repeat: 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 0; } .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 -16px; } .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 -32px; } .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 -48px; } .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 -68px; } .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 -84px; } .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 -100px; } .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 -116px; } .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 -132px; } .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 -148px; } .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 -164px; } .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 -180px; } .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 -196px; } .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 -212px; } .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 -228px; } .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 -244px; } .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 -260px; } .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 -276px; } .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 -292px; } .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 -308px; } .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 -324px; } .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 -340px; } .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 -355px; } .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 -371px; } .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 -387px; } .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 -403px; } .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 -420px; } .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 -436px; } .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 -452px; } .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 -468px; } .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 -484px; } .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 -500px; } .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-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 -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 -548px; } .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 -564px; } .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 -580px; } .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 -596px; } .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 -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 -628px; } .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 -644px; } .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 -660px; } .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 -676px; } .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 -692px; } .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 -708px; } .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 -724px; } .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 -740px; } .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 -756px; } .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 -772px; } .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 { text-indent: -9999px; border: none; outline: none; }
.icon.icon-24 { width: 24px; height: 24px; background: url('../images/menu-icons/24x24-s0dc15c36f9.png'); } .icon.icon-24 { width: 24px; height: 24px; background: url('../images/menu-icons/24x24-s391afdd013.png'); }
.icon.icon-24.icon-assetadmin { background-position: 0 -216px; } .icon.icon-24.icon-assetadmin { background-position: 0 -120px; }
.icon.icon-24.icon-cmsmain { background-position: 0 -192px; } .icon.icon-24.icon-cmsmain { background-position: 0 -48px; }
.icon.icon-24.icon-cmspagescontroller { background-position: 0 -168px; } .icon.icon-24.icon-cmspagescontroller { background-position: 0 -216px; }
.icon.icon-24.icon-cmssettingscontroller { background-position: 0 -96px; } .icon.icon-24.icon-cmssettingscontroller { background-position: 0 0; }
.icon.icon-24.icon-securityadmin { background-position: 0 -24px; } .icon.icon-24.icon-securityadmin { background-position: 0 -24px; }
.icon.icon-24.icon-reportadmin { background-position: 0 -240px; } .icon.icon-24.icon-reportadmin { background-position: 0 -72px; }
.icon.icon-24.icon-commentadmin { background-position: 0 0; } .icon.icon-24.icon-commentadmin { background-position: 0 -192px; }
.icon.icon-24.icon-help { background-position: 0 -144px; } .icon.icon-24.icon-help { background-position: 0 -96px; }
.icon.icon-16 { width: 16px; height: 16px; background: url('../images/menu-icons/16x16-s3f4c846209.png'); } .icon.icon-16 { width: 16px; height: 16px; background: url('../images/menu-icons/16x16-sf5b94bb49b.png'); }
.icon.icon-16.icon-assetadmin { background-position: 0 -144px; } .icon.icon-16.icon-assetadmin { background-position: 0 -80px; }
.icon.icon-16.icon-cmsmain { background-position: 0 -128px; } .icon.icon-16.icon-cmsmain { background-position: 0 -16px; }
.icon.icon-16.icon-cmspagescontroller { background-position: 0 -112px; } .icon.icon-16.icon-cmspagescontroller { background-position: 0 -112px; }
.icon.icon-16.icon-cmssettingscontroller { background-position: 0 -64px; } .icon.icon-16.icon-cmssettingscontroller { background-position: 0 0; }
.icon.icon-16.icon-securityadmin { background-position: 0 -16px; } .icon.icon-16.icon-securityadmin { background-position: 0 -48px; }
.icon.icon-16.icon-reportadmin { background-position: 0 -160px; } .icon.icon-16.icon-reportadmin { background-position: 0 -32px; }
.icon.icon-16.icon-commentadmin { background-position: 0 0; } .icon.icon-16.icon-commentadmin { background-position: 0 -144px; }
.icon.icon-16.icon-help { background-position: 0 -96px; } .icon.icon-16.icon-help { background-position: 0 -64px; }
html { overflow: hidden; } html { overflow: hidden; }

View File

@ -9,7 +9,7 @@
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
.cms .ss-ui-button { background-color: #e6e6e6; } .cms .ss-ui-button { background-color: #e6e6e6; }
.cms .ss-ui-button.ui-state-hover { background-color: #f3f3f3; } .cms .ss-ui-button.ui-state-hover { background-color: #f3f3f3; }
.cms .ss-ui-button.ss-ui-action-constructive { background-color: #1F9433; } .cms .ss-ui-button.ss-ui-action-constructive { background-color: #1f9433; }
.cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover { background-color: #23a93a; } .cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover { background-color: #23a93a; }
.cms .ss-ui-button.ss-gridfield-button-filter { background: #55a4d2 url(../../images/icons/filter-icons.png) no-repeat -14px 4px; } .cms .ss-ui-button.ss-gridfield-button-filter { background: #55a4d2 url(../../images/icons/filter-icons.png) no-repeat -14px 4px; }
@ -20,7 +20,7 @@
.ss-gridfield-button-filter.ss-ui-button.hover-alike { background-color: #338DC1; background-position: -16px 6px; filter: none; } .ss-gridfield-button-filter.ss-ui-button.hover-alike { background-color: #338DC1; background-position: -16px 6px; filter: none; }
.ss-gridfield-button-reset.ss-ui-button { background: #e6e6e6 url(../images/filter-icons.png) no-repeat 8px 5px; filter: none; } .ss-gridfield-button-reset.ss-ui-button { background: #e6e6e6 url(../images/filter-icons.png) no-repeat 8px 5px; filter: none; }
.ss-gridfield-button-reset.ss-ui-button.filtered:hover { background: #f00 url(../images/filter-icons.png) no-repeat 8px -17px; filter: none; } .ss-gridfield-button-reset.ss-ui-button.filtered:hover { background: red url(../images/filter-icons.png) no-repeat 8px -17px; filter: none; }
.ss-gridfield-button-reset.ss-ui-button.filtered:active { background: #e60000 url(../images/filter-icons.png) no-repeat 9px -16px; filter: none; } .ss-gridfield-button-reset.ss-ui-button.filtered:active { background: #e60000 url(../images/filter-icons.png) no-repeat 9px -16px; filter: none; }
.cms table.ss-gridfield-table tr td { border-right: 1px solid #9a9a9a; } .cms table.ss-gridfield-table tr td { border-right: 1px solid #9a9a9a; }

View File

@ -127,10 +127,10 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif;
.ui-widget-header { background-color: #b0bec7; padding: 8px 8px 6px 8px; border-bottom: 2px solid #8399a7; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2RkZTNlNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzkyYTViMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dde3e7), color-stop(100%, #92a5b2)); background-image: -moz-linear-gradient(#dde3e7, #92a5b2); background-image: -webkit-linear-gradient(#dde3e7, #92a5b2); background-image: linear-gradient(#dde3e7, #92a5b2); border-bottom: 3px solid #5c7382; padding: 8px; -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; } .ui-widget-header { background-color: #b0bec7; padding: 8px 8px 6px 8px; border-bottom: 2px solid #8399a7; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2RkZTNlNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzkyYTViMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dde3e7), color-stop(100%, #92a5b2)); background-image: -moz-linear-gradient(#dde3e7, #92a5b2); background-image: -webkit-linear-gradient(#dde3e7, #92a5b2); background-image: linear-gradient(#dde3e7, #92a5b2); border-bottom: 3px solid #5c7382; padding: 8px; -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; }
.ui-widget-header .ui-dialog-title { padding: 6px 10px; text-shadow: #ced7dc 1px 1px 0; } .ui-widget-header .ui-dialog-title { padding: 6px 10px; 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-dialog-titlebar-close { position: absolute; top: -5px; right: -13px; 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 { border-color: transparent; background: transparent; }
.ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-s871d283813.png') 0 -356px no-repeat; } .ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -356px no-repeat; }
.ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-s871d283813.png') 0 -396px no-repeat; width: 30px; height: 30px; } .ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -396px no-repeat; width: 30px; height: 30px; }
.ui-state-hover { cursor: pointer; } .ui-state-hover { cursor: pointer; }
@ -634,10 +634,10 @@ form.member-profile-form #Permissions .optionset li { float: none; width: auto;
.cms .ui-dialog .ss-ui-dialog.ui-dialog-content { padding-top: 0px; } .cms .ui-dialog .ss-ui-dialog.ui-dialog-content { padding-top: 0px; }
.ui-dialog { background: url("../images/textures/bg_cms_main_content.png") repeat left top #F0F3F4; border: 3px solid #000 !important; border-radius: 8px; overflow: visible; padding: 0; } .ui-dialog { background: url("../images/textures/bg_cms_main_content.png") repeat left top #F0F3F4; background-clip: content-box; border: 1px solid #666 !important; -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; overflow: visible; padding: 0; -moz-box-shadow: 0px 0px 30px 10px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 0px 30px 10px rgba(0, 0, 0, 0.3); box-shadow: 0px 0px 30px 10px rgba(0, 0, 0, 0.3); }
.ui-dialog .ui-dialog-titlebar.ui-widget-header { font-size: 14px; padding: 0; border: none; background-color: transparent; background-image: url(../images/textures/cms_content_header.png); background-repeat: repeat; -moz-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; -webkit-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; } .ui-dialog .ui-dialog-titlebar.ui-widget-header { font-size: 14px; padding: 0; border: none; background-color: transparent; background-image: url(../images/textures/cms_content_header.png); background-repeat: repeat; -moz-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; -webkit-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; }
.ui-dialog .ui-dialog-titlebar.ui-widget-header .ui-dialog-title { position: absolute; } .ui-dialog .ui-dialog-titlebar.ui-widget-header .ui-dialog-title { position: absolute; }
.ui-dialog .ui-dialog-content { overflow: auto; } .ui-dialog .ui-dialog-content { -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; overflow: auto; }
.ui-dialog .ui-dialog-content.loading { background-image: url(../images/spinner.gif); background-position: 50% 50%; background-repeat: no-repeat; } .ui-dialog .ui-dialog-content.loading { background-image: url(../images/spinner.gif); background-position: 50% 50%; background-repeat: no-repeat; }
.ui-dialog .cms-dialog-content { background: url("../images/textures/bg_cms_main_content.png") repeat left top #F0F3F4; padding-bottom: 8px; padding-top: 0px; } .ui-dialog .cms-dialog-content { background: url("../images/textures/bg_cms_main_content.png") repeat left top #F0F3F4; padding-bottom: 8px; padding-top: 0px; }
.ui-dialog .cms-dialog-content .Actions { overflow: auto; margin: 8px 0; padding-bottom: 8px; float: right; } .ui-dialog .cms-dialog-content .Actions { overflow: auto; margin: 8px 0; padding-bottom: 8px; float: right; }
@ -663,7 +663,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai
.htmleditorfield-dialog .htmleditorfield-from-web button.add-url.ui-state-disabled, .htmleditorfield-dialog .htmleditorfield-from-web button.add-url.ui-state-disabled:hover, .htmleditorfield-dialog .htmleditorfield-from-web button.add-url.ui-state-disabled:active { opacity: 0.35; filter: Alpha(Opacity=35); } .htmleditorfield-dialog .htmleditorfield-from-web button.add-url.ui-state-disabled, .htmleditorfield-dialog .htmleditorfield-from-web button.add-url.ui-state-disabled:hover, .htmleditorfield-dialog .htmleditorfield-from-web button.add-url.ui-state-disabled:active { opacity: 0.35; filter: Alpha(Opacity=35); }
.htmleditorfield-dialog .htmleditorfield-from-web .loading button.add-url .ui-icon { background-image: url(../images/throbber.gif); background-position: 50% 50%; background-repeat: no-repeat; } .htmleditorfield-dialog .htmleditorfield-from-web .loading button.add-url .ui-icon { background-image: url(../images/throbber.gif); background-position: 50% 50%; background-repeat: no-repeat; }
.htmleditorfield-dialog .cms-content-header { padding: 0; width: 100%; height: 40px; } .htmleditorfield-dialog .cms-content-header { padding: 0; width: 100%; height: 40px; }
.htmleditorfield-dialog .cms-content-header h3 { padding: 0 8px; margin: 10px; } .htmleditorfield-dialog .cms-content-header h3 { padding: 3px 8px; margin: 10px; }
.htmleditorfield-dialog .ss-insert-media, .htmleditorfield-dialog .Actions, .htmleditorfield-dialog .ss-insert-link { padding: 8px 16px; } .htmleditorfield-dialog .ss-insert-media, .htmleditorfield-dialog .Actions, .htmleditorfield-dialog .ss-insert-link { padding: 8px 16px; }
.htmleditorfield-dialog .ss-insert-media .ui-tabs-panel, .htmleditorfield-dialog .Actions .ui-tabs-panel, .htmleditorfield-dialog .ss-insert-link .ui-tabs-panel { padding: 0; } .htmleditorfield-dialog .ss-insert-media .ui-tabs-panel, .htmleditorfield-dialog .Actions .ui-tabs-panel, .htmleditorfield-dialog .ss-insert-link .ui-tabs-panel { padding: 0; }
.htmleditorfield-dialog .details .file-url { display: block; width: 300px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; -o-text-overflow: ellipsis; } .htmleditorfield-dialog .details .file-url { display: block; width: 300px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; -o-text-overflow: ellipsis; }
@ -692,7 +692,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai
/** -------------------------------------------- Step labels -------------------------------------------- */ /** -------------------------------------------- Step labels -------------------------------------------- */
.step-label > * { display: inline-block; vertical-align: top; } .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 .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-s871d283813.png') 0 -862px no-repeat; margin-right: 4px; } .step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -862px no-repeat; margin-right: 4px; }
.step-label .title { height: 18px; padding: 4px; } .step-label .title { height: 18px; padding: 4px; }
/** -------------------------------------------- Item Edit Form -------------------------------------------- */ /** -------------------------------------------- Item Edit Form -------------------------------------------- */
@ -739,10 +739,10 @@ form.import-form label.left { width: 250px; }
/** -------------------------------------------- Buttons for FileUpload -------------------------------------------- */ /** -------------------------------------------- Buttons for FileUpload -------------------------------------------- */
.ss-uploadfield-item-edit-all .ui-button-text { padding-right: 0; } .ss-uploadfield-item-edit-all .ui-button-text { padding-right: 0; }
.toggle-details-icon { background: url('../images/sprites-32x32-s871d283813.png') 0 -830px no-repeat; } .toggle-details-icon { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -830px no-repeat; }
.ss-uploadfield-item-edit-all .toggle-details-icon { background: url('../images/sprites-32x32-s871d283813.png') 0 -798px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; } .ss-uploadfield-item-edit-all .toggle-details-icon { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -798px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; }
.toggle-details-icon.opened { background: url('../images/sprites-32x32-s871d283813.png') 0 -846px no-repeat; } .toggle-details-icon.opened { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -846px no-repeat; }
.ss-uploadfield-item-edit-all .toggle-details-icon.opened { background: url('../images/sprites-32x32-s871d283813.png') 0 -814px no-repeat; } .ss-uploadfield-item-edit-all .toggle-details-icon.opened { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -814px no-repeat; }
/** -------------------------------------------- Hide preview toggle link by default. May be shown in IE7 stylesheet and forced to show with js if needed -------------------------------------------- */ /** -------------------------------------------- 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; } .cms .Actions > .cms-preview-toggle-link, .cms .cms-navigator > .cms-preview-toggle-link { display: none; }
@ -759,7 +759,7 @@ form.import-form label.left { width: 250px; }
.cms .jstree li > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .jstree li > .jstree-icon { cursor: pointer; } .cms .jstree li > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .jstree li > .jstree-icon { cursor: pointer; }
.cms .jstree ins, .TreeDropdownField .treedropdownfield-panel .jstree ins { display: inline-block; text-decoration: none; width: 18px; height: 18px; margin: 0 0 0 0; padding: 0; float: left; } .cms .jstree ins, .TreeDropdownField .treedropdownfield-panel .jstree ins { display: inline-block; text-decoration: none; width: 18px; height: 18px; margin: 0 0 0 0; padding: 0; float: left; }
.cms .jstree a, .TreeDropdownField .treedropdownfield-panel .jstree a { display: inline-block; line-height: 16px; height: 16px; color: black; white-space: nowrap; text-decoration: none; padding: 1px 2px; margin: 0; border: 1px solid #fff; } .cms .jstree a, .TreeDropdownField .treedropdownfield-panel .jstree a { display: inline-block; line-height: 16px; height: 16px; color: black; white-space: nowrap; text-decoration: none; padding: 1px 2px; margin: 0; border: 1px solid #fff; }
.cms .jstree a:focus, .cms .jstree a:active, .cms .jstree a:hover, .TreeDropdownField .treedropdownfield-panel .jstree a:focus, .TreeDropdownField .treedropdownfield-panel .jstree a:active, .TreeDropdownField .treedropdownfield-panel .jstree a:hover { text-decoration: none; cursor: pointer; text-shadow: none; } .cms .jstree a:focus, .cms .jstree a:active, .cms .jstree a:hover, .TreeDropdownField .treedropdownfield-panel .jstree a:focus, .TreeDropdownField .treedropdownfield-panel .jstree a:active, .TreeDropdownField .treedropdownfield-panel .jstree a:hover { text-decoration: none; cursor: pointer; text-shadow: 1px 1px 1px white; }
.cms .jstree a > ins, .TreeDropdownField .treedropdownfield-panel .jstree a > ins { height: 16px; width: 16px; } .cms .jstree a > ins, .TreeDropdownField .treedropdownfield-panel .jstree a > ins { height: 16px; width: 16px; }
.cms .jstree a > ins.jstree-checkbox, .TreeDropdownField .treedropdownfield-panel .jstree a > ins.jstree-checkbox { height: 19px; } .cms .jstree a > ins.jstree-checkbox, .TreeDropdownField .treedropdownfield-panel .jstree a > ins.jstree-checkbox { height: 19px; }
.cms .jstree a > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .jstree a > .jstree-icon { margin-right: 3px; } .cms .jstree a > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .jstree a > .jstree-icon { margin-right: 3px; }
@ -772,8 +772,8 @@ form.import-form label.left { width: 250px; }
.cms .jstree .jstree-wholerow a, .cms .jstree .jstree-wholerow a:hover, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a:hover { margin: 0 !important; padding: 0 !important; text-indent: -9999px !important; width: 100%; border-right-width: 0px !important; border-left-width: 0px !important; } .cms .jstree .jstree-wholerow a, .cms .jstree .jstree-wholerow a:hover, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a:hover { margin: 0 !important; padding: 0 !important; text-indent: -9999px !important; width: 100%; border-right-width: 0px !important; border-left-width: 0px !important; }
.cms .jstree .jstree-wholerow ins, .cms .jstree .jstree-wholerow span, .cms .jstree .jstree-wholerow input, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ins, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow span, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow input { display: none !important; } .cms .jstree .jstree-wholerow ins, .cms .jstree .jstree-wholerow span, .cms .jstree .jstree-wholerow input, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ins, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow span, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow input { display: none !important; }
.cms .jstree .jstree-wholerow-span, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow-span { position: absolute; left: 0; margin: 0px; padding: 0; height: 18px; border-width: 0; padding: 0; z-index: 0; } .cms .jstree .jstree-wholerow-span, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow-span { position: absolute; left: 0; margin: 0px; padding: 0; height: 18px; border-width: 0; padding: 0; z-index: 0; }
.cms .jstree-apple.jstree-focused, .TreeDropdownField .treedropdownfield-panel .jstree-apple.jstree-focused { background: none; } .cms .jstree.jstree-focused, .TreeDropdownField .treedropdownfield-panel .jstree.jstree-focused { background: none; }
.cms .jstree-apple.jstree-focused .jstree-apple > ul, .TreeDropdownField .treedropdownfield-panel .jstree-apple.jstree-focused .jstree-apple > ul { background: none; } .cms .jstree.jstree-focused .jstree > ul, .TreeDropdownField .treedropdownfield-panel .jstree.jstree-focused .jstree > ul { background: none; }
.cms a > .jstree-icon, .TreeDropdownField .treedropdownfield-panel a > .jstree-icon { display: none; } .cms a > .jstree-icon, .TreeDropdownField .treedropdownfield-panel a > .jstree-icon { display: none; }
.cms .draggable a > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .draggable a > .jstree-icon { display: block; } .cms .draggable a > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .draggable a > .jstree-icon { display: block; }
.cms li.jstree-open > ul, .TreeDropdownField .treedropdownfield-panel li.jstree-open > ul { display: block; margin-left: -13px; } .cms li.jstree-open > ul, .TreeDropdownField .treedropdownfield-panel li.jstree-open > ul { display: block; margin-left: -13px; }
@ -814,38 +814,74 @@ form.import-form label.left { width: 250px; }
.cms #vakata-dragged .jstree-ok, .TreeDropdownField .treedropdownfield-panel #vakata-dragged .jstree-ok { background: green; } .cms #vakata-dragged .jstree-ok, .TreeDropdownField .treedropdownfield-panel #vakata-dragged .jstree-ok { background: green; }
.cms #vakata-dragged .jstree-invalid, .TreeDropdownField .treedropdownfield-panel #vakata-dragged .jstree-invalid { background: red; } .cms #vakata-dragged .jstree-invalid, .TreeDropdownField .treedropdownfield-panel #vakata-dragged .jstree-invalid { background: red; }
.jstree-apple li, .jstree-apple .jstree-apple ins { background: none; } .jstree li, .jstree .jstree ins { background: none; }
.jstree-apple .jstree-unchecked > a > .jstree-checkbox, .jstree-apple .jstree-checked > a > .jstree-checkbox, .jstree-apple .jstree-undetermined > a > .jstree-checkbox { margin-right: 3px; } .jstree .jstree-unchecked > a > .jstree-checkbox, .jstree .jstree-checked > a > .jstree-checkbox, .jstree .jstree-undetermined > a > .jstree-checkbox { margin-right: 3px; }
.tree-holder.jstree-apple, .cms-tree.jstree-apple { /* comment speech bubble - ccs3 only - source: http://nicolasgallagher.com/pure-css-speech-bubbles/demo/ */ } .tree-holder.jstree, .cms-tree.jstree { /* comment speech bubble - ccs3 only - source: http://nicolasgallagher.com/pure-css-speech-bubbles/demo/ */ }
.tree-holder.jstree-apple li, .cms-tree.jstree-apple li { padding: 0px; clear: left; } .tree-holder.jstree li, .cms-tree.jstree li { padding: 0px; clear: left; }
.tree-holder.jstree-apple li.Root strong, .cms-tree.jstree-apple li.Root strong { font-weight: bold; padding-left: 1px; } .tree-holder.jstree li.Root strong, .cms-tree.jstree li.Root strong { font-weight: bold; padding-left: 1px; }
.tree-holder.jstree-apple li.Root > a .jstree-icon, .cms-tree.jstree-apple li.Root > a .jstree-icon { background-position: -56px -36px; } .tree-holder.jstree li.Root > a .jstree-icon, .cms-tree.jstree li.Root > a .jstree-icon { background-position: -56px -36px; }
.tree-holder.jstree-apple li.status-deletedonlive .text, .cms-tree.jstree-apple li.status-deletedonlive .text { text-decoration: line-through; } .tree-holder.jstree li.status-deletedonlive .text, .cms-tree.jstree li.status-deletedonlive .text { text-decoration: line-through; }
.tree-holder.jstree-apple li.jstree-checked > a, .tree-holder.jstree-apple li.jstree-checked > a:link, .cms-tree.jstree-apple li.jstree-checked > a, .cms-tree.jstree-apple li.jstree-checked > a:link { background-color: #efe999; } .tree-holder.jstree li.jstree-checked > a, .tree-holder.jstree li.jstree-checked > a:link, .cms-tree.jstree li.jstree-checked > a, .cms-tree.jstree li.jstree-checked > a:link { background-color: #efe999; }
.tree-holder.jstree-apple li.disabled > a > .jstree-checkbox, .cms-tree.jstree-apple li.disabled > a > .jstree-checkbox { background-position: -57px -54px; } .tree-holder.jstree li.disabled > a > .jstree-checkbox, .cms-tree.jstree li.disabled > a > .jstree-checkbox { background-position: -57px -54px; }
.tree-holder.jstree-apple li.readonly, .cms-tree.jstree-apple li.readonly { color: #aaa; padding-left: 18px; } .tree-holder.jstree li.readonly, .cms-tree.jstree li.readonly { color: #aaa; padding-left: 18px; }
.tree-holder.jstree-apple li.readonly a, .tree-holder.jstree-apple li.readonly a:link, .cms-tree.jstree-apple li.readonly a, .cms-tree.jstree-apple li.readonly a:link { margin: 0; padding: 0; } .tree-holder.jstree li.readonly a, .tree-holder.jstree li.readonly a:link, .cms-tree.jstree li.readonly a, .cms-tree.jstree li.readonly a:link { margin: 0; padding: 0; }
.tree-holder.jstree-apple li.readonly .jstree-icon, .cms-tree.jstree-apple li.readonly .jstree-icon { display: none; } .tree-holder.jstree li.readonly .jstree-icon, .cms-tree.jstree li.readonly .jstree-icon { display: none; }
.tree-holder.jstree-apple a, .tree-holder.jstree-apple a:link, .cms-tree.jstree-apple a, .cms-tree.jstree-apple a:link { color: #0073C1; padding: 3px 6px 3px 3px; border: none; display: inline-block; margin-right: 5px; } .tree-holder.jstree a, .tree-holder.jstree a:link, .cms-tree.jstree a, .cms-tree.jstree a:link { color: #0073C1; padding: 3px 6px 3px 3px; border: none; display: inline-block; margin-right: 5px; }
.tree-holder.jstree-apple ins, .cms-tree.jstree-apple ins { background-color: transparent; background-image: url(../images/sitetree_ss_default_icons.png); } .tree-holder.jstree ins, .cms-tree.jstree ins { background-color: transparent; background-image: url(../images/sitetree_ss_default_icons.png); }
.tree-holder.jstree-apple span.badge, .cms-tree.jstree-apple span.badge { clear: both; text-transform: uppercase; display: inline-block; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; margin-top: -1px; -moz-border-radius: 2px / 2px; -webkit-border-radius: 2px 2px; border-radius: 2px / 2px; } .tree-holder.jstree span.badge, .cms-tree.jstree span.badge { clear: both; text-transform: uppercase; text-shadow: none; display: inline-block; position: relative; padding: 2px 3px 1px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-top: -1px; -moz-border-radius: 2px / 2px; -webkit-border-radius: 2px 2px; border-radius: 2px / 2px; }
.tree-holder.jstree-apple span.badge.status-modified, .tree-holder.jstree-apple span.badge.status-addedtodraft, .cms-tree.jstree-apple span.badge.status-modified, .cms-tree.jstree-apple span.badge.status-addedtodraft { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; } .tree-holder.jstree span.comment-count, .cms-tree.jstree span.comment-count { clear: both; position: relative; text-transform: uppercase; display: inline-block; overflow: visible; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; -moz-border-radius: 2px / 2px; -webkit-border-radius: 2px 2px; border-radius: 2px / 2px; color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.tree-holder.jstree-apple span.badge.status-deletedonlive, .tree-holder.jstree-apple span.badge.status-removedfromdraft, .cms-tree.jstree-apple span.badge.status-deletedonlive, .cms-tree.jstree-apple span.badge.status-removedfromdraft { color: #636363; border: 1px solid #E49393; background-color: #F2DADB; } .tree-holder.jstree span.comment-count:before, .cms-tree.jstree span.comment-count:before { content: ""; position: absolute; border-style: solid; display: block; width: 0; bottom: -4px; /* value = - border-top-width - border-bottom-width */ left: 3px; /* controls horizontal position */ border-width: 4px 4px 0; border-color: #C9B800 transparent; }
.tree-holder.jstree-apple span.badge.status-workflow-approval, .cms-tree.jstree-apple span.badge.status-workflow-approval { color: #56660C; border: 1px solid #7C8816; background-color: #DAE79A; } .tree-holder.jstree span.comment-count:after, .cms-tree.jstree span.comment-count:after { content: ""; position: absolute; border-style: solid; /* reduce the damage in FF3.0 */ display: block; width: 0; bottom: -3px; /* value = - border-top-width - border-bottom-width */ left: 4px; /* value = (:before left) + (:before border-left) - (:after border-left) */ border-width: 3px 3px 0; border-color: #FFF0BC transparent; }
.tree-holder.jstree-apple span.comment-count, .cms-tree.jstree-apple span.comment-count { clear: both; position: relative; text-transform: uppercase; display: inline-block; overflow: visible; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; -moz-border-radius: 2px / 2px; -webkit-border-radius: 2px 2px; border-radius: 2px / 2px; color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; } .tree-holder.jstree .jstree-hovered, .cms-tree.jstree .jstree-hovered { text-shadow: none; text-decoration: none; }
.tree-holder.jstree-apple span.comment-count:before, .cms-tree.jstree-apple span.comment-count:before { content: ""; position: absolute; border-style: solid; display: block; width: 0; bottom: -4px; /* value = - border-top-width - border-bottom-width */ left: 3px; /* controls horizontal position */ border-width: 4px 4px 0; border-color: #C9B800 transparent; } .tree-holder.jstree .jstree-closed > ins, .cms-tree.jstree .jstree-closed > ins { background-position: 0 0; }
.tree-holder.jstree-apple span.comment-count:after, .cms-tree.jstree-apple span.comment-count:after { content: ""; position: absolute; border-style: solid; /* reduce the damage in FF3.0 */ display: block; width: 0; bottom: -3px; /* value = - border-top-width - border-bottom-width */ left: 4px; /* value = (:before left) + (:before border-left) - (:after border-left) */ border-width: 3px 3px 0; border-color: #FFF0BC transparent; } .tree-holder.jstree .jstree-open > ins, .cms-tree.jstree .jstree-open > ins { background-position: -20px 0; }
.tree-holder.jstree-apple .jstree-hovered, .cms-tree.jstree-apple .jstree-hovered { text-shadow: none; text-decoration: none; }
.tree-holder.jstree-apple .jstree-closed > ins, .cms-tree.jstree-apple .jstree-closed > ins { background-position: 0 0; }
.tree-holder.jstree-apple .jstree-open > ins, .cms-tree.jstree-apple .jstree-open > ins { background-position: -20px 0; }
a .jstree-pageicon { float: left; margin-right: 4px; } /* ensure status is visible in sidebar */
#cms-content-tools-CMSMain .cms-tree.jstree li { min-width: 159px; }
#cms-content-tools-CMSMain .cms-tree.jstree a { overflow: hidden; display: block; position: relative; }
#cms-content-tools-CMSMain .cms-tree.jstree span.badge { position: absolute; top: 0; right: 0; padding: 7px 9px 6px 5px; margin: 0; max-width: 40%; -moz-transition: max-width 0.75s linear; -o-transition: max-width 0.75s linear; -webkit-transition: max-width 0.75s linear; transition: max-width 0.75s linear; }
#cms-content-tools-CMSMain .cms-tree.jstree span.badge:hover { max-width: 150px; }
a .jstree-pageicon { float: left; margin-right: 4px; position: relative; }
li.class-HomePage > a .jstree-pageicon { background-position: 0 -48px; } li.class-HomePage > a .jstree-pageicon { background-position: 0 -48px; }
li.class-RedirectorPage > a .jstree-pageicon { background-position: 0 -16px; } li.class-RedirectorPage > a .jstree-pageicon { background-position: 0 -16px; }
li.class-VirtualPage > a .jstree-pageicon { background-position: 0 -32px; } li.class-VirtualPage > a .jstree-pageicon { background-position: 0 -32px; }
li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; } li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
/* tree status icons and labels */
.cms-tree.jstree .status-modified > a .jstree-pageicon:before, .cms-tree.jstree .status-addedtodraft > a .jstree-pageicon:before, .cms-tree.jstree .status-deletedonlive > a .jstree-pageicon:before, .cms-tree.jstree .status-removedfromdraft > a .jstree-pageicon:before, .cms-tree.jstree .status-workflow-approval > a .jstree-pageicon:before { content: ""; display: block; width: 5px; height: 5px; position: absolute; bottom: 0; right: 0; background: #fce2d0; border: 1px solid #ff9344; border-radius: 100px; box-shadow: 0px 0px 0px 1px #fff; }
.jstree .status-modified > .jstree-hovered, .jstree .status-modified > .jstree-clicked, .cms-tree.jstree span.badge.status-modified, .cms-tree.jstree .status-modified > a .jstree-pageicon:before { background-color: #fce2d0; border-color: #ff7714; }
#cms-content-tools-CMSMain .cms-tree.jstree span.badge.status-modified { box-shadow: 0px 0px 6px 2px #fce2d0; }
.cms-tree.jstree span.badge.status-modified { color: #ff7714; }
.jstree .status-addedtodraft > .jstree-hovered, .jstree .status-addedtodraft > .jstree-clicked, .cms-tree.jstree span.badge.status-addedtodraft, .cms-tree.jstree .status-addedtodraft > a .jstree-pageicon:before { background-color: #f8f4d0; border-color: #e29a00; }
#cms-content-tools-CMSMain .cms-tree.jstree span.badge.status-addedtodraft { box-shadow: 0px 0px 6px 2px #f8f4d0; }
.cms-tree.jstree span.badge.status-addedtodraft { color: #e29a00; }
.jstree .status-deletedonlive > .jstree-hovered, .jstree .status-deletedonlive > .jstree-clicked, .cms-tree.jstree span.badge.status-deletedonlive, .cms-tree.jstree .status-deletedonlive > a .jstree-pageicon:before { background-color: #f9d6dd; border-color: #f0524f; }
#cms-content-tools-CMSMain .cms-tree.jstree span.badge.status-deletedonlive { box-shadow: 0px 0px 6px 2px #f9d6dd; }
.cms-tree.jstree span.badge.status-deletedonlive { color: #f0524f; }
.jstree .status-removedfromdraft > .jstree-hovered, .jstree .status-removedfromdraft > .jstree-clicked, .cms-tree.jstree span.badge.status-removedfromdraft, .cms-tree.jstree .status-removedfromdraft > a .jstree-pageicon:before { background-color: #f9d6dd; border-color: #f0524f; }
#cms-content-tools-CMSMain .cms-tree.jstree span.badge.status-removedfromdraft { box-shadow: 0px 0px 6px 2px #f9d6dd; }
.cms-tree.jstree span.badge.status-removedfromdraft { color: #f0524f; }
.jstree .status-workflow-approval > .jstree-hovered, .jstree .status-workflow-approval > .jstree-clicked, .cms-tree.jstree span.badge.status-workflow-approval, .cms-tree.jstree .status-workflow-approval > a .jstree-pageicon:before { background-color: #d3f2ff; border-color: #0097d7; }
#cms-content-tools-CMSMain .cms-tree.jstree span.badge.status-workflow-approval { box-shadow: 0px 0px 6px 2px #d3f2ff; }
.cms-tree.jstree span.badge.status-workflow-approval { color: #0097d7; }
.cms-tree { visibility: hidden; } .cms-tree { visibility: hidden; }
.cms-tree.multiple li > a > .jstree-icon { display: none; } .cms-tree.multiple li > a > .jstree-icon { display: none; }
.cms-tree.multiple li > a > .jstree-icon.jstree-checkbox { display: inline-block; } .cms-tree.multiple li > a > .jstree-icon.jstree-checkbox { display: inline-block; }
@ -866,7 +902,7 @@ li.class-ErrorPage > 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-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: 8px 0 9.6px; line-height: 16px; font-size: 11px; } .cms-login-status { border-top: 1px solid #19435c; padding: 8px 0 9.6px; 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-s871d283813.png') 0 -772px no-repeat; text-indent: -9999em; opacity: 0.9; } .cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 5px; background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -772px no-repeat; text-indent: -9999em; opacity: 0.9; }
.cms-login-status .logout-link:hover, .cms-login-status .logout-link:focus { opacity: 1; } .cms-login-status .logout-link:hover, .cms-login-status .logout-link:focus { opacity: 1; }
.cms-menu { z-index: 80; background: #b0bec7; width: 160px; -moz-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; -webkit-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; } .cms-menu { z-index: 80; background: #b0bec7; width: 160px; -moz-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; -webkit-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; }
@ -891,12 +927,12 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
.cms-menu-list li a .icon { display: block; position: absolute; top: 50%; margin-left: 4px; margin-top: -8px; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); opacity: 0.7; } .cms-menu-list li a .icon { display: block; position: absolute; top: 50%; margin-left: 4px; margin-top: -8px; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); opacity: 0.7; }
.cms-menu-list li a .text { display: block; margin-left: 30px; } .cms-menu-list li a .text { display: block; margin-left: 30px; }
.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 { 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-s871d283813.png') 0 -798px no-repeat; vertical-align: middle; } .cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -798px no-repeat; vertical-align: middle; }
.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-s871d283813.png') 0 -814px no-repeat; } .cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -814px no-repeat; }
.cms-menu-list li ul li a { border-top: 1px solid #b6c3cb; } .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+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+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: -moz-linear-gradient(#338dc1, #287099); background-image: -webkit-linear-gradient(#338dc1, #287099); background-image: linear-gradient(#338dc1, #287099); } .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+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+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: -moz-linear-gradient(#338dc1, #287099); background-image: -webkit-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-s871d283813.png') 0 -830px no-repeat; } .cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -830px no-repeat; }
.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-s871d283813.png') 0 -846px no-repeat; } .cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -846px no-repeat; }
.cms-menu-list li.current ul { border-top: none; display: block; } .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 { 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; } .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; }
@ -919,14 +955,14 @@ li.class-ErrorPage > 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.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, .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-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-s871d283813.png') 0 -898px no-repeat; } .cms-content-controls .icon-auto:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -898px no-repeat; }
.cms-content-controls .icon-desktop:before { background: url('../images/sprites-32x32-s871d283813.png') 0 -925px no-repeat; } .cms-content-controls .icon-desktop:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -925px no-repeat; }
.cms-content-controls .icon-tablet:before { background: url('../images/sprites-32x32-s871d283813.png') 0 -1087px no-repeat; } .cms-content-controls .icon-tablet:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -1087px no-repeat; }
.cms-content-controls .icon-mobile:before { background: url('../images/sprites-32x32-s871d283813.png') 0 -1006px no-repeat; } .cms-content-controls .icon-mobile:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -1006px no-repeat; }
.cms-content-controls .icon-split:before { background: url('../images/sprites-32x32-s871d283813.png') 0 -1060px no-repeat; } .cms-content-controls .icon-split:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -1060px no-repeat; }
.cms-content-controls .icon-edit:before { background: url('../images/sprites-32x32-s871d283813.png') 0 -979px no-repeat; } .cms-content-controls .icon-edit:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -979px no-repeat; }
.cms-content-controls .icon-preview:before { background: url('../images/sprites-32x32-s871d283813.png') 0 -1033px no-repeat; } .cms-content-controls .icon-preview:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -1033px no-repeat; }
.cms-content-controls .icon-window:before { background: url('../images/sprites-32x32-s871d283813.png') 0 -952px no-repeat; } .cms-content-controls .icon-window:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -952px no-repeat; }
.cms-content-controls .cms-navigator { width: 100%; } .cms-content-controls .cms-navigator { width: 100%; }
.cms-content-controls .preview-selector.dropdown a.chzn-single { text-indent: -200px; } .cms-content-controls .preview-selector.dropdown a.chzn-single { text-indent: -200px; }
.cms-content-controls .preview-selector { float: right; border-bottom: none; position: relative; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; margin: 3px 0 0 4px; padding: 0; height: 28px; } .cms-content-controls .preview-selector { float: right; border-bottom: none; position: relative; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; margin: 3px 0 0 4px; padding: 0; height: 28px; }
@ -1073,10 +1109,10 @@ visible. Added and removed with js in TabSet.js */ /***************************
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a { text-shadow: #fff 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 { text-shadow: #fff 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 { -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; outline: none; } .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 { -moz-box-shadow: none; -webkit-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: #fff 0 10px 10px; color: #005b98; } .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover { text-shadow: #fff 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-s871d283813.png') 0 -26px 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:after { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -26px 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-s871d283813.png') 0 0 no-repeat; } .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover:after { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 0 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-s871d283813.png') 0 -78px 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-s47450c5f5b.png') 0 -78px 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-s871d283813.png') 0 -52px 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-s47450c5f5b.png') 0 -52px 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 { 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, .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 h3 { font-size: 13px; }
@ -1158,28 +1194,28 @@ green tick icon as a background this is created using compass generated classes
/* Default CMS logo */ /* Default CMS logo */
.cms-logo a { background-image: url("../images/logo_small@2x.png"); background-size: 22px 22px; } .cms-logo a { background-image: url("../images/logo_small@2x.png"); background-size: 22px 22px; }
/* Logout button */ /* Logout button */
.cms-login-status .logout-link { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -184px; background-size: 30px auto; } .cms-login-status .logout-link { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -184px; background-size: 30px auto; }
.cms-content-controls .icon-auto:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -274px; background-size: 30px auto; } .cms-content-controls .icon-auto:before { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -274px; background-size: 30px auto; }
.cms-content-controls .icon-desktop:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -301px; background-size: 30px auto; } .cms-content-controls .icon-desktop:before { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -301px; background-size: 30px auto; }
.cms-content-controls .icon-tablet:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -436px; background-size: 30px auto; } .cms-content-controls .icon-tablet:before { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -436px; background-size: 30px auto; }
.cms-content-controls .icon-mobile:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -355px; background-size: 30px auto; } .cms-content-controls .icon-mobile:before { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -355px; background-size: 30px auto; }
.cms-content-controls .icon-split:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -409px; background-size: 30px auto; } .cms-content-controls .icon-split:before { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -409px; background-size: 30px auto; }
.cms-content-controls .icon-edit:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -328px; background-size: 30px auto; } .cms-content-controls .icon-edit:before { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -328px; background-size: 30px auto; }
.cms-content-controls .icon-preview:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -382px; background-size: 30px auto; } .cms-content-controls .icon-preview:before { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -382px; background-size: 30px auto; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:after { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -26px; background-size: 30px auto; } .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:after { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -26px; background-size: 30px auto; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover:after { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 0; background-size: 30px auto; } .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover:after { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 0; background-size: 30px auto; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:after { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -78px; background-size: 30px auto; } .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:after { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -78px; background-size: 30px auto; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:hover:after { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -52px; background-size: 30px auto; } .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:hover:after { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -52px; background-size: 30px auto; }
/* CMS menu */ /* CMS menu */
.cms-menu-list li a .toggle-children .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -210px; background-size: 30px auto; } .cms-menu-list li a .toggle-children .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -210px; background-size: 30px auto; }
.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -226px; background-size: 30px auto; } .cms-menu-list li a .toggle-children.opened .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -226px; background-size: 30px auto; }
.cms-menu-list li.current a .toggle-children .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -242px; background-size: 30px auto; } .cms-menu-list li.current a .toggle-children .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -242px; background-size: 30px auto; }
.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -258px; background-size: 30px auto; } .cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -258px; background-size: 30px auto; }
/* Sitetree */ /* Sitetree */
.tree-holder.jstree-apple ins, .cms-tree.jstree-apple ins { background-image: url(../images/sitetree_ss_default_icons@2x.png); background-size: 108px 72px; } .tree-holder.jstree-apple ins, .cms-tree.jstree-apple ins { background-image: url(../images/sitetree_ss_default_icons@2x.png); background-size: 108px 72px; }
/* UI widget "close" button */ /* UI widget "close" button */
.ui-widget-header a.ui-state-hover .ui-icon-closethick { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -104px; background-size: 30px auto; } .ui-widget-header a.ui-state-hover .ui-icon-closethick { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -104px; background-size: 30px auto; }
.ui-widget-header .ui-icon-closethick { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -144px; background-size: 30px auto; } .ui-widget-header .ui-icon-closethick { background-image: url('../images/sprites-32x32-2x-s6ccfbe50f9.png'); background-position: 0 -144px; background-size: 30px auto; }
/* Tab icons */ /* Tab icons */
.ui-tabs .ui-tabs-nav li.cms-tabset-icon.list a { background-image: url('../images/sprites-64x64-2x-se3e3f47b94.png'); background-position: 0 -150px; background-size: 40px auto; } .ui-tabs .ui-tabs-nav li.cms-tabset-icon.list a { background-image: url('../images/sprites-64x64-2x-se3e3f47b94.png'); background-position: 0 -150px; background-size: 40px auto; }
.ui-tabs .ui-tabs-nav li.cms-tabset-icon.tree a { background-image: url('../images/sprites-64x64-2x-se3e3f47b94.png'); background-position: 0 -250px; background-size: 40px auto; } .ui-tabs .ui-tabs-nav li.cms-tabset-icon.tree a { background-image: url('../images/sprites-64x64-2x-se3e3f47b94.png'); background-position: 0 -250px; background-size: 40px auto; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1158,7 +1158,7 @@ jQuery.noConflict();
* selection support. Rather than manually adding classes to selects we want * selection support. Rather than manually adding classes to selects we want
* styled, we style everything but the ones we tell it not to. * styled, we style everything but the ones we tell it not to.
* *
* For the CMS we also need to tell the parent div that his has a select so * For the CMS we also need to tell the parent div that it has a select so
* we can fix the height cropping. * we can fix the height cropping.
*/ */

View File

@ -0,0 +1,16 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/eo.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('eo', {
"LeftAndMain.CONFIRMUNSAVED": "Ĉu vi vere volas navigi for de ĉi tiu paĝo?\n\nAVERTO: Viaj ŝanĝoj ne estas konservitaj.\n\nPremu je Akcepti por daŭrigi, aŭ Nuligi por resti ĉe la aktuala paĝo.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "AVERTO: Viaj ŝanĝoj ne estas konservitaj.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Ĉu vi vere volas forigi %s grupojn?",
"ModelAdmin.SAVED": "Konservita",
"ModelAdmin.REALLYDELETE": "Ĉi vi vere volas forigi?",
"ModelAdmin.DELETED": "Forigita",
"ModelAdmin.VALIDATIONERROR": "Validiga eraro",
"LeftAndMain.PAGEWASDELETED": "Ĉi tiu paĝo estas forigita. Por redakti paĝon, elektu ĝin maldekstre."
});
}

View File

@ -0,0 +1,16 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/lt.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('lt', {
"LeftAndMain.CONFIRMUNSAVED": "Ar tikrai norite išeiti iš šio puslapio?\n\nDĖMESIO: Jūsų pakeitimai neišsaugoti.\n\nNorėdami tęsti, spauskite OK, jeigu norite likti, spauskite Cancel.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "DĖMESIO: Jūsų pakeitimai neišsaugoti.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Ar tikrai norite ištrinti %s grupes?",
"ModelAdmin.SAVED": "Išsaugota",
"ModelAdmin.REALLYDELETE": "Ar tikrai norite ištrinti?",
"ModelAdmin.DELETED": "Ištrinta",
"ModelAdmin.VALIDATIONERROR": "Tikrinimo klaida",
"LeftAndMain.PAGEWASDELETED": "Šis puslapis ištrintas. Norėdami redaguoti puslapį, pasirinkite jį kairėje."
});
}

View File

@ -0,0 +1,16 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/nb.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('nb', {
"LeftAndMain.CONFIRMUNSAVED": "Er du sikker på at du vil forlate denne siden?\n\nADVARSEL: Endringene din har ikke blitt lagret.\n\nTrykk OK for å fortsette eller Avbryt for å holde deg på samme side.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "ADVARSEL: Endringene dine har ikke blitt lagret.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Vil du virkelig slette %s grupper?",
"ModelAdmin.SAVED": "Lagret",
"ModelAdmin.REALLYDELETE": "Vil du virkelig slette?",
"ModelAdmin.DELETED": "Slettet",
"ModelAdmin.VALIDATIONERROR": "Valideringsfeil",
"LeftAndMain.PAGEWASDELETED": "Denne siden ble slettet. For å redigere en side, velg den fra listen til venstre."
});
}

View File

@ -10,7 +10,7 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
"ModelAdmin.SAVED": "Opgeslagen", "ModelAdmin.SAVED": "Opgeslagen",
"ModelAdmin.REALLYDELETE": "Weet u zeker dat u wilt verwijderen?", "ModelAdmin.REALLYDELETE": "Weet u zeker dat u wilt verwijderen?",
"ModelAdmin.DELETED": "Verwijderd", "ModelAdmin.DELETED": "Verwijderd",
"ModelAdmin.VALIDATIONERROR": "Validatie fout", "ModelAdmin.VALIDATIONERROR": "Validatiefout",
"LeftAndMain.PAGEWASDELETED": "Deze pagina is verwijderd. Om een pagina aan te passen, selecteer pagina aan de linkerkant." "LeftAndMain.PAGEWASDELETED": "Deze pagina is verwijderd. Om een pagina aan te passen, selecteer deze aan de linkerkant."
}); });
} }

View File

@ -0,0 +1,16 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/sl.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('sl', {
"LeftAndMain.CONFIRMUNSAVED": "Res želite zapusitit stran?\n\nOPOZORILO: spremembe niso bile shranjene\n\nKliknite OK za nadaljevanje ali Prekliči, da ostanete na trenutni strani.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "OPOZORILO: spremembe niso bile shranjene.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Izbrišem %s skupin?",
"ModelAdmin.SAVED": "Shranjeno",
"ModelAdmin.REALLYDELETE": "Izbrišem?",
"ModelAdmin.DELETED": "Izbrisano",
"ModelAdmin.VALIDATIONERROR": "Napaka pri preverjanju",
"LeftAndMain.PAGEWASDELETED": "Stran je bila izbrisana. Za urejanje izberite stran na levi."
});
}

View File

@ -0,0 +1,10 @@
{
"LeftAndMain.CONFIRMUNSAVED": "Är du säker på att du vill lämna denna sida?\n\nVARNING: Dina ändringar har inte sparats.\n\nTryck OK för att lämna sidan eller Avbryt för att stanna på aktuell sida.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "WARNING: Your changes have not been saved.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Vill du verkligen radera %s grupper?",
"ModelAdmin.SAVED": "Sparad",
"ModelAdmin.REALLYDELETE": "Vill du verkligen radera?",
"ModelAdmin.DELETED": "Raderad",
"ModelAdmin.VALIDATIONERROR": "Valideringsfel",
"LeftAndMain.PAGEWASDELETED": "Sidan raderades. För att redigera en sida, välj den från menyn till vänster."
}

View File

@ -0,0 +1,16 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/sv.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('sv', {
"LeftAndMain.CONFIRMUNSAVED": "Är du säker på att du vill lämna denna sida?\n\nVARNING: Dina ändringar har inte sparats.\n\nTryck OK för att lämna sidan eller Avbryt för att stanna på aktuell sida.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "WARNING: Your changes have not been saved.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Vill du verkligen radera %s grupper?",
"ModelAdmin.SAVED": "Sparad",
"ModelAdmin.REALLYDELETE": "Vill du verkligen radera?",
"ModelAdmin.DELETED": "Raderad",
"ModelAdmin.VALIDATIONERROR": "Valideringsfel",
"LeftAndMain.PAGEWASDELETED": "Sidan raderades. För att redigera en sida, välj den från menyn till vänster."
});
}

View File

@ -1313,10 +1313,12 @@ form.member-profile-form {
// most styles should be applied to .cms-dialog instead (which declares the content in the frame) // most styles should be applied to .cms-dialog instead (which declares the content in the frame)
.ui-dialog { .ui-dialog {
background: url("../images/textures/bg_cms_main_content.png") repeat left top #F0F3F4; background: url("../images/textures/bg_cms_main_content.png") repeat left top #F0F3F4;
border: 3px solid #000 !important; background-clip: content-box;
border-radius: $grid-y; border: 1px solid #666 !important;
@include border-radius($grid-y);
overflow: visible; overflow: visible;
padding: 0; padding: 0;
@include box-shadow(0px 0px 30px 10px rgba(0,0,0,.3));
// Titlebar for pop-up dialog. // Titlebar for pop-up dialog.
.ui-dialog-titlebar.ui-widget-header { .ui-dialog-titlebar.ui-widget-header {
@ -1335,6 +1337,7 @@ form.member-profile-form {
} }
.ui-dialog-content { .ui-dialog-content {
@include border-radius($grid-y);
overflow: auto; // TODO Replace with proper $.layout grid overflow: auto; // TODO Replace with proper $.layout grid
&.loading { &.loading {
@ -1470,7 +1473,7 @@ body.cms-dialog {
width:100%; width:100%;
height: 40px; height: 40px;
h3{ h3{
padding: 0 8px; padding: 3px 8px;
margin: 10px; margin: 10px;
} }
} }

View File

@ -68,13 +68,13 @@
&:hover { &:hover {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
text-shadow: none; text-shadow:1px 1px 1px white;
} }
> { > {
ins { ins {
height: 16px; height: 16px;
width: 16px; width: 16px;
&.jstree-checkbox{ &.jstree-checkbox {
height:19px; //Larger to help avoid accidental page loads when trying to click checkboxes height:19px; //Larger to help avoid accidental page loads when trying to click checkboxes
} }
} }
@ -111,7 +111,7 @@
background: transparent !important; background: transparent !important;
width: 100%; width: 100%;
} }
a, a:hover{ a, a:hover {
margin: 0 !important; margin: 0 !important;
padding: 0 !important; padding: 0 !important;
text-indent: -9999px !important; text-indent: -9999px !important;
@ -138,9 +138,9 @@
} }
// Custom styles // Custom styles
.jstree-apple.jstree-focused { .jstree.jstree-focused {
background: none; background: none;
.jstree-apple > ul { .jstree > ul {
background: none; background: none;
} }
} }
@ -182,7 +182,7 @@
} }
} }
.jstree-themeroller{ .jstree-themeroller {
a { a {
padding: 0 2px; padding: 0 2px;
} }
@ -276,7 +276,7 @@
min-width: 180px; min-width: 180px;
*width:180px; *width:180px;
} }
ul,li{ ul,li {
margin: 0; margin: 0;
padding: 0 ; padding: 0 ;
list-style-type: none; list-style-type: none;
@ -316,7 +316,7 @@
margin-top: 3px; margin-top: 3px;
margin-right: 5px; margin-right: 5px;
} }
&.vakata-hover > a{ &.vakata-hover > a {
padding: 1px 10px; padding: 1px 10px;
background: #3875d7; background: #3875d7;
@include background-image(linear-gradient(top, #3875d7 20%, #2a62bc 90%)); @include background-image(linear-gradient(top, #3875d7 20%, #2a62bc 90%));
@ -326,7 +326,7 @@
} }
} }
#vakata-contextmenu{ #vakata-contextmenu {
.right { .right {
right: 100%; right: 100%;
left: auto; left: auto;
@ -345,25 +345,25 @@
@include box-shadow(0 0 10px #CCC); @include box-shadow(0 0 10px #CCC);
&.col-2{ &.col-2{
width:180px * 2; // 2x the size of the original ul width:180px * 2; // 2x the size of the original ul
li{ li {
width:50%; width:50%;
} }
} }
&.col-3{ &.col-3{
width:180px * 3; // 3x the size of the original ul width:180px * 3; // 3x the size of the original ul
li{ li {
width:33%; width:33%;
} }
} }
li{ li {
min-width:180px; min-width:180px;
float:left; float:left;
a{ a {
@include hide-text-overflow; @include hide-text-overflow;
} }
} }
} }
li{ li {
&.vakata-separator { &.vakata-separator {
min-height: 0; min-height: 0;
height: 1px; height: 1px;
@ -409,8 +409,8 @@
} }
.jstree-apple { .jstree {
li, .jstree-apple ins { li, .jstree ins {
background:none; background:none;
} }
.jstree-unchecked, .jstree-checked, .jstree-undetermined { .jstree-unchecked, .jstree-checked, .jstree-undetermined {
@ -420,9 +420,9 @@
} }
} }
.tree-holder, .cms-tree{ .tree-holder, .cms-tree {
&.jstree-apple{ &.jstree {
li{ li {
padding: 0px; padding: 0px;
clear: left; clear: left;
&.Root { &.Root {
@ -440,8 +440,8 @@
text-decoration: line-through; text-decoration: line-through;
} }
} }
&.jstree-checked{ &.jstree-checked {
> a, > a:link{ > a, > a:link {
background-color: $color-cms-batchactions-menu-selected-background; background-color: $color-cms-batchactions-menu-selected-background;
} }
} }
@ -478,30 +478,15 @@
span.badge { span.badge {
clear: both; clear: both;
text-transform: uppercase; text-transform: uppercase;
text-shadow: none;
display: inline-block; display: inline-block;
padding: 0px 3px; position: relative;
padding: 2px 3px 1px;
font-size: 0.75em; font-size: 0.75em;
line-height: 1em; line-height: 1em;
margin-left: 3px; margin-left: 3px;
margin-right: 6px;
margin-top: -1px; margin-top: -1px;
@include border-radius(2px, 2px); @include border-radius(2px, 2px);
&.status-modified, &.status-addedtodraft {
color: #7E7470;
border: 1px solid #C9B800;
background-color: #FFF0BC;
}
&.status-deletedonlive, &.status-removedfromdraft {
color: #636363;
border: 1px solid #E49393;
background-color: #F2DADB;
}
&.status-workflow-approval {
color: #56660C;
border: 1px solid #7C8816;
background-color: #DAE79A;
}
} }
/* comment speech bubble - ccs3 only - source: http://nicolasgallagher.com/pure-css-speech-bubbles/demo/ */ /* comment speech bubble - ccs3 only - source: http://nicolasgallagher.com/pure-css-speech-bubbles/demo/ */
@ -559,10 +544,34 @@
} }
} }
/* ensure status is visible in sidebar */
#cms-content-tools-CMSMain .cms-tree.jstree {
li {
min-width: 159px;
}
a {
overflow: hidden;
display: block;
position: relative;
}
span.badge {
position: absolute;
top: 0;
right: 0;
padding: 7px 9px 6px 5px;
margin: 0;
max-width: 40%;
@include transition(max-width .75s linear);
}
span.badge:hover {
max-width: 150px;
}
}
a .jstree-pageicon { a .jstree-pageicon {
float: left; float: left;
margin-right: 4px; margin-right: 4px;
position: relative;
li.class-HomePage > &{ li.class-HomePage > &{
background-position: 0 -48px; background-position: 0 -48px;
} }
@ -577,6 +586,43 @@ a .jstree-pageicon {
} }
} }
/* tree status icons and labels */
%tree-status-icon-before {
content:"";
display: block;
width:5px;
height: 5px;
position: absolute;
bottom: 0;
right: 0;
background: #fce2d0;
border: 1px solid #ff9344;
border-radius: 100px;
box-shadow: 0px 0px 0px 1px #fff;
}
@mixin tree-status-icon($label, $color, $bgColor) {
.cms-tree.jstree .status-#{$label} > a .jstree-pageicon:before {
@extend %tree-status-icon-before;
}
.jstree .status-#{$label} > .jstree-hovered,
.jstree .status-#{$label} > .jstree-clicked,
.cms-tree.jstree span.badge.status-#{$label},
.cms-tree.jstree .status-#{$label} > a .jstree-pageicon:before {
background-color:$bgColor;
border-color:$color;
}
#cms-content-tools-CMSMain .cms-tree.jstree span.badge.status-#{$label} {
box-shadow: 0px 0px 6px 2px $bgColor;
}
.cms-tree.jstree span.badge.status-#{$label} {
color: $color;
}
}
@include tree-status-icon('modified', #ff7714, #fce2d0);
@include tree-status-icon('addedtodraft', #e29a00, #f8f4d0);
@include tree-status-icon('deletedonlive', #f0524f, #f9d6dd);
@include tree-status-icon('removedfromdraft', #f0524f, #f9d6dd);
@include tree-status-icon('workflow-approval', #0097d7, #d3f2ff);
.cms-tree { .cms-tree {
visibility: hidden; // enabled by JS to avoid layout glitches visibility: hidden; // enabled by JS to avoid layout glitches
@ -608,7 +654,7 @@ a .jstree-pageicon {
// Show the loading indicator on the page icon rather than the default // Show the loading indicator on the page icon rather than the default
// jstree icon (which is only used for its dragging handles) // jstree icon (which is only used for its dragging handles)
a.jstree-loading{ a.jstree-loading {
.jstree-icon { .jstree-icon {
background-image: none !important; background-image: none !important;
} }

View File

@ -38,8 +38,8 @@
& a.ui-dialog-titlebar-close { & a.ui-dialog-titlebar-close {
position: absolute; position: absolute;
top: -8px; top: -5px;
right: -15px; right: -13px;
width: 30px; width: 30px;
height: 30px; height: 30px;
z-index: 100000; z-index: 100000;

View File

@ -62,7 +62,7 @@ class JSONDataFormatter extends DataFormatter {
} }
if($this->relationDepth > 0) { if($this->relationDepth > 0) {
foreach($obj->has_one() as $relName => $relClass) { foreach($obj->hasOne() as $relName => $relClass) {
if(!singleton($relClass)->stat('api_access')) continue; if(!singleton($relClass)->stat('api_access')) continue;
// Field filtering // Field filtering
@ -82,7 +82,7 @@ class JSONDataFormatter extends DataFormatter {
)); ));
} }
foreach($obj->has_many() as $relName => $relClass) { foreach($obj->hasMany() as $relName => $relClass) {
if(!singleton($relClass)->stat('api_access')) continue; if(!singleton($relClass)->stat('api_access')) continue;
// Field filtering // Field filtering
@ -103,7 +103,7 @@ class JSONDataFormatter extends DataFormatter {
$serobj->$relName = $innerParts; $serobj->$relName = $innerParts;
} }
foreach($obj->many_many() as $relName => $relClass) { foreach($obj->manyMany() as $relName => $relClass) {
if(!singleton($relClass)->stat('api_access')) continue; if(!singleton($relClass)->stat('api_access')) continue;
// Field filtering // Field filtering

View File

@ -68,7 +68,7 @@ class XMLDataFormatter extends DataFormatter {
} }
if($this->relationDepth > 0) { if($this->relationDepth > 0) {
foreach($obj->has_one() as $relName => $relClass) { foreach($obj->hasOne() as $relName => $relClass) {
if(!singleton($relClass)->stat('api_access')) continue; if(!singleton($relClass)->stat('api_access')) continue;
// Field filtering // Field filtering
@ -85,7 +85,7 @@ class XMLDataFormatter extends DataFormatter {
. "\"></$relName>\n"; . "\"></$relName>\n";
} }
foreach($obj->has_many() as $relName => $relClass) { foreach($obj->hasMany() as $relName => $relClass) {
if(!singleton($relClass)->stat('api_access')) continue; if(!singleton($relClass)->stat('api_access')) continue;
// Field filtering // Field filtering
@ -103,7 +103,7 @@ class XMLDataFormatter extends DataFormatter {
$xml .= "</$relName>\n"; $xml .= "</$relName>\n";
} }
foreach($obj->many_many() as $relName => $relClass) { foreach($obj->manyMany() as $relName => $relClass) {
if(!singleton($relClass)->stat('api_access')) continue; if(!singleton($relClass)->stat('api_access')) continue;
// Field filtering // Field filtering

View File

@ -438,12 +438,21 @@ class Director implements TemplateGlobalProvider {
public static function absoluteURL($url, $relativeToSiteBase = false) { public static function absoluteURL($url, $relativeToSiteBase = false) {
if(!isset($_SERVER['REQUEST_URI'])) return false; if(!isset($_SERVER['REQUEST_URI'])) return false;
//a url of . or ./ is the same as an empty url
if ($url == '.' || $url == './') {
$url = '';
}
if(strpos($url,'/') === false && !$relativeToSiteBase) { if(strpos($url,'/') === false && !$relativeToSiteBase) {
$url = dirname($_SERVER['REQUEST_URI'] . 'x') . '/' . $url; //if there's no URL we want to force a trailing slash on the link
if (!$url) {
$url = '/';
}
$url = Controller::join_links(dirname($_SERVER['REQUEST_URI'] . 'x'), $url);
} }
if(substr($url,0,4) != "http") { if(substr($url,0,4) != "http") {
if($url[0] != "/") $url = Director::baseURL() . $url; if(strpos($url, '/') !== 0) $url = Director::baseURL() . $url;
// Sometimes baseURL() can return a full URL instead of just a path // Sometimes baseURL() can return a full URL instead of just a path
if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url; if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url;
} }
@ -802,14 +811,10 @@ class Director implements TemplateGlobalProvider {
* @param string $destURL - The URL to redirect to * @param string $destURL - The URL to redirect to
*/ */
protected static function force_redirect($destURL) { protected static function force_redirect($destURL) {
$response = new SS_HTTPResponse( $response = new SS_HTTPResponse();
"<h1>Your browser is not accepting header redirects</h1>". $response->redirect($destURL, 301);
"<p>Please <a href=\"$destURL\">click here</a>",
301
);
HTTP::add_cache_headers($response); HTTP::add_cache_headers($response);
$response->addHeader('Location', $destURL);
// TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block // TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
$response->output(); $response->output();

View File

@ -361,7 +361,8 @@ class HTTP {
if( if(
$body && $body &&
Director::is_https() && Director::is_https() &&
strstr($_SERVER["HTTP_USER_AGENT"], 'MSIE')==true && isset($_SERVER['HTTP_USER_AGENT']) &&
strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')==true &&
strstr($contentDisposition, 'attachment;')==true strstr($contentDisposition, 'attachment;')==true
) { ) {
// IE6-IE8 have problems saving files when https and no-cache are used // IE6-IE8 have problems saving files when https and no-cache are used

View File

@ -250,7 +250,7 @@ class Config {
* leak through to other instances. * leak through to other instances.
*/ */
public function __construct() { public function __construct() {
$this->cache = new Config_LRU(); $this->cache = new Config_MemCache();
} }
public function __clone() { public function __clone() {
@ -681,6 +681,7 @@ class Config {
/** /**
* @package framework * @package framework
* @subpackage core * @subpackage core
* @deprecated 3.2
*/ */
class Config_LRU { class Config_LRU {
const SIZE = 1000; const SIZE = 1000;
@ -692,6 +693,7 @@ class Config_LRU {
protected $c = 0; protected $c = 0;
public function __construct() { public function __construct() {
Deprecation::notice('3.2', 'Please use Config_MemCache instead', Deprecation::SCOPE_CLASS);
if (version_compare(PHP_VERSION, '5.3.7', '<')) { if (version_compare(PHP_VERSION, '5.3.7', '<')) {
// SplFixedArray causes seg faults before PHP 5.3.7 // SplFixedArray causes seg faults before PHP 5.3.7
$this->cache = array(); $this->cache = array();
@ -787,6 +789,69 @@ class Config_LRU {
} }
} }
/**
* @package framework
* @subpackage core
*/
class Config_MemCache {
protected $cache;
protected $i = 0;
protected $c = 0;
protected $tags = array();
public function __construct() {
$this->cache = array();
}
public function set($key, $val, $tags = array()) {
foreach($tags as $t) {
if(!isset($this->tags[$t])) {
$this->tags[$t] = array();
}
$this->tags[$t][$key] = true;
}
$this->cache[$key] = array($val, $tags);
}
private $hit = 0;
private $miss = 0;
public function stats() {
return $this->miss ? ($this->hit / $this->miss) : 0;
}
public function get($key) {
if(isset($this->cache[$key])) {
++$this->hit;
return $this->cache[$key][0];
}
++$this->miss;
return false;
}
public function clean($tag = null) {
if($tag) {
if(isset($this->tags[$tag])) {
foreach($this->tags[$tag] as $k => $dud) {
// Remove the key from everywhere else it is tagged
$ts = $this->cache[$k][1];
foreach($ts as $t) {
unset($this->tags[$t][$k]);
}
unset($this->cache[$k]);
}
unset($this->tags[$tag]);
}
} else {
$this->cache = array();
$this->tags = array();
}
}
}
/** /**
* @package framework * @package framework
* @subpackage core * @subpackage core

View File

@ -230,15 +230,32 @@ class Convert {
/** /**
* Converts an XML string to a PHP array * Converts an XML string to a PHP array
* See http://phpsecurity.readthedocs.org/en/latest/Injection-Attacks.html#xml-external-entity-injection
* *
* @uses recursiveXMLToArray() * @uses recursiveXMLToArray()
* @param string * @param string $val
* * @param boolean $disableDoctypes Disables the use of DOCTYPE, and will trigger an error if encountered.
* false by default.
* @param boolean $disableExternals Disables the loading of external entities. false by default.
* @return array * @return array
*/ */
public static function xml2array($val) { public static function xml2array($val, $disableDoctypes = false, $disableExternals = false) {
// Check doctype
if($disableDoctypes && preg_match('/\<\!DOCTYPE.+]\>/', $val)) {
throw new InvalidArgumentException('XML Doctype parsing disabled');
}
// Disable external entity loading
if($disableExternals) $oldVal = libxml_disable_entity_loader($disableExternals);
try {
$xml = new SimpleXMLElement($val); $xml = new SimpleXMLElement($val);
return self::recursiveXMLToArray($xml); $result = self::recursiveXMLToArray($xml);
} catch(Exception $ex) {
if($disableExternals) libxml_disable_entity_loader($oldVal);
throw $ex;
}
if($disableExternals) libxml_disable_entity_loader($oldVal);
return $result;
} }
/** /**

View File

@ -85,8 +85,6 @@ require_once 'control/injector/Injector.php';
// Initialise the dependency injector as soon as possible, as it is // Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code // subsequently used by some of the following code
$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator')); $injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
$injector->registerService(Config::inst());
Injector::set_inst($injector); Injector::set_inst($injector);
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@ -65,9 +65,10 @@ class PaginatedList extends SS_ListDecorator {
} }
/** /**
* Set the number of items displayed per page. * Set the number of items displayed per page. Set to zero to disable paging.
* *
* @param int $length * @param int $length
* @return $this
*/ */
public function setPageLength($length) { public function setPageLength($length) {
$this->pageLength = $length; $this->pageLength = $length;
@ -77,7 +78,8 @@ class PaginatedList extends SS_ListDecorator {
/** /**
* Sets the current page. * Sets the current page.
* *
* @param int $page * @param int $page Page index beginning with 1
* @return $this
*/ */
public function setCurrentPage($page) { public function setCurrentPage($page) {
$this->pageStart = ($page - 1) * $this->getPageLength(); $this->pageStart = ($page - 1) * $this->getPageLength();
@ -182,10 +184,11 @@ class PaginatedList extends SS_ListDecorator {
* @return IteratorIterator * @return IteratorIterator
*/ */
public function getIterator() { public function getIterator() {
if($this->limitItems) { $pageLength = $this->getPageLength();
if($this->limitItems && $pageLength) {
$tmptList = clone $this->list; $tmptList = clone $this->list;
return new IteratorIterator( return new IteratorIterator(
$tmptList->limit($this->getPageLength(), $this->getPageStart()) $tmptList->limit($pageLength, $this->getPageStart())
); );
} else { } else {
return new IteratorIterator($this->list); return new IteratorIterator($this->list);
@ -325,14 +328,20 @@ class PaginatedList extends SS_ListDecorator {
* @return int * @return int
*/ */
public function CurrentPage() { public function CurrentPage() {
return floor($this->getPageStart() / $this->getPageLength()) + 1; $pageLength = $this->getPageLength();
return $pageLength
? floor($this->getPageStart() / $pageLength) + 1
: 1;
} }
/** /**
* @return int * @return int
*/ */
public function TotalPages() { public function TotalPages() {
return ceil($this->getTotalItems() / $this->getPageLength()); $pageLength = $this->getPageLength();
return $pageLength
? ceil($this->getTotalItems() / $pageLength)
: min($this->getTotalItems(), 1);
} }
/** /**
@ -372,10 +381,13 @@ class PaginatedList extends SS_ListDecorator {
* @return int * @return int
*/ */
public function LastItem() { public function LastItem() {
if ($start = $this->getPageStart()) { $pageLength = $this->getPageLength();
return min($start + $this->getPageLength(), $this->getTotalItems()); if(!$pageLength) {
return $this->getTotalItems();
} elseif ($start = $this->getPageStart()) {
return min($start + $pageLength, $this->getTotalItems());
} else { } else {
return min($this->getPageLength(), $this->getTotalItems()); return min($pageLength, $this->getTotalItems());
} }
} }

View File

@ -101,7 +101,7 @@ class SS_ClassLoader {
* @return bool * @return bool
*/ */
public function classExists($class) { public function classExists($class) {
return class_exists($class, false) || $this->getItemPath($class); return class_exists($class, false) || interface_exists($class, false) || $this->getItemPath($class);
} }
} }

View File

@ -113,7 +113,7 @@ Used in side panels and action tabs
.cms table.ss-gridfield-table tr th input.ss-gridfield-sort:-ms-input-placeholder { font-style: italic; color: #ced5d7; } .cms table.ss-gridfield-table tr th input.ss-gridfield-sort:-ms-input-placeholder { font-style: italic; color: #ced5d7; }
.cms table.ss-gridfield-table tr th input.ss-gridfield-sort:placeholder { font-style: italic; color: #ced5d7; } .cms table.ss-gridfield-table tr th input.ss-gridfield-sort:placeholder { font-style: italic; color: #ced5d7; }
.cms table.ss-gridfield-table tr th input.ss-gridfield-sort:focus { -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } .cms table.ss-gridfield-table tr th input.ss-gridfield-sort:focus { -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }
.cms table.ss-gridfield-table tr th span.non-sortable { display: block; } .cms table.ss-gridfield-table tr th span.non-sortable { display: block; padding: 6px 8px; }
.cms table.ss-gridfield-table tr td { border-right: 1px solid rgba(0, 0, 0, 0.1); padding: 8px 8px; color: #666; } .cms table.ss-gridfield-table tr td { border-right: 1px solid rgba(0, 0, 0, 0.1); padding: 8px 8px; color: #666; }
.cms table.ss-gridfield-table tr td.bottom-all { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2IwYmVjNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzk4YWFiNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7), color-stop(100%, #98aab6)); background-image: -moz-linear-gradient(#b0bec7, #98aab6); background-image: -webkit-linear-gradient(#b0bec7, #98aab6); background-image: linear-gradient(#b0bec7, #98aab6); padding: 4px 12px; } .cms table.ss-gridfield-table tr td.bottom-all { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2IwYmVjNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzk4YWFiNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7), color-stop(100%, #98aab6)); background-image: -moz-linear-gradient(#b0bec7, #98aab6); background-image: -webkit-linear-gradient(#b0bec7, #98aab6); background-image: linear-gradient(#b0bec7, #98aab6); padding: 4px 12px; }
.cms table.ss-gridfield-table tr td.bottom-all .datagrid-footer-message { text-align: center; padding-top: 6px; color: white; } .cms table.ss-gridfield-table tr td.bottom-all .datagrid-footer-message { text-align: center; padding-top: 6px; color: white; }

View File

@ -11,6 +11,7 @@
Used in side panels and action tabs Used in side panels and action tabs
*/ */
.ss-uploadfield .clear { clear: both; } .ss-uploadfield .clear { clear: both; }
.ss-uploadfield .description { margin-left: 0; }
.ss-uploadfield .middleColumn { min-width: 510px; max-width: 600px; width: 100%; margin-left: 0; clear: both; padding: 0; background: #fff; border: 1px solid #b3b3b3; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VmZWZlZiIvPjxzdG9wIG9mZnNldD0iMTAlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIi8+PHN0b3Agb2Zmc2V0PSI5MCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlZmVmZWYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); } .ss-uploadfield .middleColumn { min-width: 510px; max-width: 600px; width: 100%; margin-left: 0; clear: both; padding: 0; background: #fff; border: 1px solid #b3b3b3; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VmZWZlZiIvPjxzdG9wIG9mZnNldD0iMTAlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIi8+PHN0b3Agb2Zmc2V0PSI5MCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlZmVmZWYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); }
.ss-uploadfield .ss-uploadfield-item { margin: 0; padding: 15px; overflow: auto; } .ss-uploadfield .ss-uploadfield-item { margin: 0; padding: 15px; overflow: auto; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview { height: 60px; line-height: 60px; width: 80px; text-align: center; font-weight: bold; float: left; overflow: hidden; } .ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview { height: 60px; line-height: 60px; width: 80px; text-align: center; font-weight: bold; float: left; overflow: hidden; }

View File

@ -220,9 +220,9 @@ abstract class BulkLoader extends ViewableData {
// using $$includerelations flag as false, so that it only contain $db fields // using $$includerelations flag as false, so that it only contain $db fields
$spec['fields'] = (array)singleton($this->objectClass)->fieldLabels(false); $spec['fields'] = (array)singleton($this->objectClass)->fieldLabels(false);
$has_ones = singleton($this->objectClass)->has_one(); $has_ones = singleton($this->objectClass)->hasOne();
$has_manys = singleton($this->objectClass)->has_many(); $has_manys = singleton($this->objectClass)->hasMany();
$many_manys = singleton($this->objectClass)->many_many(); $many_manys = singleton($this->objectClass)->manyMany();
$spec['relations'] = (array)$has_ones + (array)$has_manys + (array)$many_manys; $spec['relations'] = (array)$has_ones + (array)$has_manys + (array)$many_manys;

View File

@ -120,7 +120,7 @@ class CsvBulkLoader extends BulkLoader {
$relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record); $relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
} }
if(!$relationObj || !$relationObj->exists()) { if(!$relationObj || !$relationObj->exists()) {
$relationClass = $obj->has_one($relationName); $relationClass = $obj->hasOneComponent($relationName);
$relationObj = new $relationClass(); $relationObj = new $relationClass();
//write if we aren't previewing //write if we aren't previewing
if (!$preview) $relationObj->write(); if (!$preview) $relationObj->write();

View File

@ -110,7 +110,11 @@ class FixtureBlueprint {
// Populate overrides // Populate overrides
if($data) foreach($data as $fieldName => $fieldVal) { if($data) foreach($data as $fieldName => $fieldVal) {
// Defer relationship processing // Defer relationship processing
if($obj->many_many($fieldName) || $obj->has_many($fieldName) || $obj->has_one($fieldName)) { if(
$obj->manyManyComponent($fieldName)
|| $obj->hasManyComponent($fieldName)
|| $obj->hasOneComponent($fieldName)
) {
continue; continue;
} }
@ -127,7 +131,7 @@ class FixtureBlueprint {
// Populate all relations // Populate all relations
if($data) foreach($data as $fieldName => $fieldVal) { if($data) foreach($data as $fieldName => $fieldVal) {
if($obj->many_many($fieldName) || $obj->has_many($fieldName)) { if($obj->manyManyComponent($fieldName) || $obj->hasManyComponent($fieldName)) {
$obj->write(); $obj->write();
$parsedItems = array(); $parsedItems = array();
@ -165,15 +169,15 @@ class FixtureBlueprint {
$parsedItems[] = $this->parseValue($item, $fixtures); $parsedItems[] = $this->parseValue($item, $fixtures);
} }
if($obj->has_many($fieldName)) { if($obj->hasManyComponent($fieldName)) {
$obj->getComponents($fieldName)->setByIDList($parsedItems); $obj->getComponents($fieldName)->setByIDList($parsedItems);
} elseif($obj->many_many($fieldName)) { } elseif($obj->manyManyComponent($fieldName)) {
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems); $obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
} }
} }
} else { } else {
$hasOneField = preg_replace('/ID$/', '', $fieldName); $hasOneField = preg_replace('/ID$/', '', $fieldName);
if($className = $obj->has_one($hasOneField)) { if($className = $obj->hasOneComponent($hasOneField)) {
$obj->{$hasOneField.'ID'} = $this->parseValue($fieldVal, $fixtures, $fieldClass); $obj->{$hasOneField.'ID'} = $this->parseValue($fieldVal, $fixtures, $fieldClass);
// Inject class for polymorphic relation // Inject class for polymorphic relation
if($className === 'DataObject') { if($className === 'DataObject') {

View File

@ -12,7 +12,7 @@
* $this->get("your/url"); * $this->get("your/url");
* *
* // Submit a form on the page that you get in response * // Submit a form on the page that you get in response
* $this->submitForm("MyForm_ID", array("Email" => "invalid email ^&*&^")); * $this->submitForm("MyForm_ID", "action_dologin", array("Email" => "invalid email ^&*&^"));
* *
* // Validate the content that is returned * // Validate the content that is returned
* $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid.")); * $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid."));

View File

@ -4,7 +4,7 @@ SilverStripe CMS needs to be installed on a web server. Content authors and webs
to access a web-based GUI to do their day-to-day work. Website designers and developers require access to the files on to access a web-based GUI to do their day-to-day work. Website designers and developers require access to the files on
the server to update templates, website logic, and perform upgrades or maintenance. the server to update templates, website logic, and perform upgrades or maintenance.
Our web-based [PHP installer](/installation) can check if you meet the requirements listed below. Our web-based [PHP installer](installation/) can check if you meet the requirements listed below.
## Web server software requirements ## Web server software requirements

View File

@ -2,7 +2,7 @@
SilverStripe should be able to be installed on any Linux, Unix or *nix like OS as long as the correct server software is installed and configured (referred to a *nix in this document from herein). It is common that web hosting that you may use for your production SilverStripe application will be *nix based, here you may also want to use *nix locally to ensure how you develop locally mimics closely your production environment. SilverStripe should be able to be installed on any Linux, Unix or *nix like OS as long as the correct server software is installed and configured (referred to a *nix in this document from herein). It is common that web hosting that you may use for your production SilverStripe application will be *nix based, here you may also want to use *nix locally to ensure how you develop locally mimics closely your production environment.
Is important to ensure you check the [Server Requirements](/Getting_Started/Installation/Server_Requirements) list before acquiring and installing SilverStripe on your *nix server (locally or otherwise). Is important to ensure you check the [Server Requirements](/getting_started/server_requirements) list before acquiring and installing SilverStripe on your *nix server (locally or otherwise).
At a high level you will need a: At a high level you will need a:
* Web server e.g. Apache, Nginx * Web server e.g. Apache, Nginx
@ -12,7 +12,7 @@ At a high level you will need a:
##*nix installation guides on the web ##*nix installation guides on the web
There are a number of good step by step guides covering server setups and installing of SilverStripe on the various flavours of *nix systems. There are a number of good step by step guides covering server setups and installing of SilverStripe on the various flavours of *nix systems.
Note: Many of the following guides simply download SilverStripe as a zipped file. We recommend the use of [Composer](/Getting_Started/Composer/) once you get to the point of installing SilverStripe (though the choice is up to you). Always ensure you get the latest version if you are starting a new project. Note: Many of the following guides simply download SilverStripe as a zipped file. We recommend the use of [Composer](/getting_started/composer/) once you get to the point of installing SilverStripe (though the choice is up to you). Always ensure you get the latest version if you are starting a new project.
###Known (but not exhaustive) list ###Known (but not exhaustive) list
* [How To Install Silverstripe on Your VPS](https://www.digitalocean.com/community/tutorials/how-to-install-silverstripe-on-your-vps) * [How To Install Silverstripe on Your VPS](https://www.digitalocean.com/community/tutorials/how-to-install-silverstripe-on-your-vps)

View File

@ -5,7 +5,7 @@ done through [WampServer](http://www.wampserver.com/en/). This can be useful if
want a Microsoft Windows machine with a very similar environment. want a Microsoft Windows machine with a very similar environment.
Note: Installing on Microsoft's IIS webserver through Microsoft WebPI is likely to be easier, see Note: Installing on Microsoft's IIS webserver through Microsoft WebPI is likely to be easier, see
[installation-on-windows-pi](windows-pi). [Windows with Web Platform Installer](other_installation_options/windows_platform_installer).
## Install WAMP ## Install WAMP

View File

@ -73,11 +73,11 @@ every page on the site, if that's easier.
Please make sure all code inside `*.php` files is wrapped in classes. Due to the way `[api:ManifestBuilder]` Please make sure all code inside `*.php` files is wrapped in classes. Due to the way `[api:ManifestBuilder]`
includes all files with this extension, any **procedural code will be executed on every call**. The most common error here includes all files with this extension, any **procedural code will be executed on every call**. The most common error here
is putting a test.php/phpinfo.php file in the document root. See [datamodel](/topics/datamodel) and [controllers](/topics/controller) is putting a test.php/phpinfo.php file in the document root. See [datamodel](/developer_guides/data_model_and_orm) and [controllers](/developer_guides/controllers)
for ways how to structure your code. for ways how to structure your code.
Also, please check that you have PHP enabled on the webserver, and you're running PHP 5.1 or later. Also, please check that you have PHP enabled on the webserver, and you're running PHP 5.1 or later.
The web-based [SilverStripe installer](/installation) can help you with this. The web-based [SilverStripe installer](/getting_started/installation) can help you with this.
## I've got file permission problems during installation ## I've got file permission problems during installation

View File

@ -107,4 +107,4 @@ e.g. `/etc/nginx/sites-enabled/mysite`:
include /etc/nginx/silverstripe.conf; include /etc/nginx/silverstripe.conf;
} }
For more information on nginx configuration, please see the [nginx installation](nginx) page. For more information on nginx configuration, please see the [nginx installation](configure_nginx) page.

View File

@ -3,12 +3,12 @@
These instructions show you how to install SilverStripe on any web server. These instructions show you how to install SilverStripe on any web server.
The best way to install from the source code is to use [Composer](../composer). The best way to install from the source code is to use [Composer](../composer).
Check out our operating system specific guides for [Linux](linux_unix), Check out our operating system specific guides for [Linux](linux_unix),
[Windows Server](windows-pi) and [Mac OSX](mac-osx). [Windows Server](windows) and [Mac OSX](mac_osx).
## Installation Steps ## Installation Steps
* [Download](http://silverstripe.org/download) the installer package * [Download](http://silverstripe.org/download) the installer package
* Make sure the webserver has MySQL and PHP support. See [Server Requirements](server-requirements) for more information. * Make sure the webserver has MySQL and PHP support. See [Server Requirements](../server_requirements) for more information.
* Unpack the installer somewhere into your web-root. Usually the www folder or similar. Most downloads from SilverStripe * Unpack the installer somewhere into your web-root. Usually the www folder or similar. Most downloads from SilverStripe
are compressed tarballs. To extract these files you can either do them natively (Unix) or with 7-Zip (Windows) are compressed tarballs. To extract these files you can either do them natively (Unix) or with 7-Zip (Windows)
* Visit your sites domain or IP address in your web browser. * Visit your sites domain or IP address in your web browser.
@ -18,7 +18,7 @@ name' and the default login details. Follow the questions and select the *instal
## Issues? ## Issues?
If the above steps don't work for any reason have a read of the [Common Problems](common-problems) section. If the above steps don't work for any reason have a read of the [Common Problems](common_problems) section.
<div class="notice" markdown="1"> <div class="notice" markdown="1">
SilverStripe ships with default rewriting rules specific to your web server. Apart from SilverStripe ships with default rewriting rules specific to your web server. Apart from

View File

@ -3,7 +3,7 @@
Composer is a package management tool for PHP that lets you install and upgrade SilverStripe and its modules. Although installing Composer is one extra step, it will give you much more flexibility than just downloading the file from silverstripe.org. This is our recommended way of downloading SilverStripe and managing your code. Composer is a package management tool for PHP that lets you install and upgrade SilverStripe and its modules. Although installing Composer is one extra step, it will give you much more flexibility than just downloading the file from silverstripe.org. This is our recommended way of downloading SilverStripe and managing your code.
For more information about Composer, visit [its website](http://getcomposer.org/). For more information about Composer, visit [its website](http://getcomposer.org/).
We also have separate instructions for [installing modules with Composer](/topics/modules). We also have separate instructions for [installing modules with Composer](/developer_guides/extending/modules).
# Basic usage # Basic usage
@ -36,7 +36,7 @@ If you already have composer installed you can update it by running:
Composer updates regularly, so you should run this command fairly often. These instructions assume you are running the latest version. Composer updates regularly, so you should run this command fairly often. These instructions assume you are running the latest version.
## Installing Composer on Windows WAMP ## Installing Composer on Windows WAMP
For those that use WAMP as a development environment, [detailed information is available on installing using Composer.](/installation/windows-wamp#install-wamp) For those that use WAMP as a development environment, [detailed information is available on installing using Composer.](/getting_started/installation/windows)
## Create a new site ## Create a new site
@ -108,6 +108,70 @@ So, your deployment process, as it relates to Composer, should be as follows:
* Deploy your project code base, using the deployment tool of your choice. * Deploy your project code base, using the deployment tool of your choice.
* Run `composer install --no-dev -o` on your production version. * Run `composer install --no-dev -o` on your production version.
## Composer managed modules, Git and .gitignore
Modules and themes managed by composer should not be committed with your projects source code. For more details read [Should I commit the dependencies in my vendor directory?](https://getcomposer.org/doc/faqs/should-i-commit-the-dependencies-in-my-vendor-directory.md).
Since SilverStripe modules are installed in to thier own folder, you have to manage your [.gitignore](http://git-scm.com/docs/gitignore) to ensure they are ignored from your repository.
Here is the default SilverStripe [.gitignore](http://git-scm.com/docs/gitignore) with the forum module ignored
assets/*
_ss_environment.php
tools/phing-metadata
silverstripe-cache
.buildpath
.project
.settings
.idea
.DS_Store
vendor/
# Don't include the forum module, as this will be installed with composer
forum
In large projects it can get difficult to manage your [.gitignore](http://git-scm.com/docs/gitignore) and ensure it contains all composer managed modules and themes.
You can automate this with the [SSAutoGitIgnore](https://github.com/guru-digital/SSAutoGitIgnore/) package.
This package will maintain your [.gitignore](http://git-scm.com/docs/gitignore) and ensure it is kept up to date with your composer managed modules without affecting custom ignores. Once installed and setup, it will automatically run every time you install, remove or update modules using composer.
### Installing and enabling the SSAutoGitIgnore package
Include the package in your project by running this command
composer require gdmedia/ss-auto-git-ignore
Edit your composer.json and insert
"scripts": {
"post-update-cmd": "GDM\\SSAutoGitIgnore\\UpdateScript::Go"
}
This will instruct composer to run SSAutoGitIgnore after every update. SSAutoGitIgnore will then ensure composer managed models and themes are correctly added to your [.gitignore](http://git-scm.com/docs/gitignore).
For more information about SSAutoGitIgnore, see the [SSAutoGitIgnore home page](https://github.com/guru-digital/SSAutoGitIgnore/).
For more information about post-updated-cmd and scripts, read the ["Scripts" chapter of the Composer documentation](https://getcomposer.org/doc/articles/scripts.md).
Full example of composer.json with the SSAutoGitIgnore installed and enabled
{
"name": "silverstripe/installer",
"description": "The SilverStripe Framework Installer",
"require": {
"php": ">=5.3.2",
"silverstripe/cms": "3.0.*",
"silverstripe/framework": "3.0.*",
"silverstripe-themes/simple": "*",
"gdmedia/ss-auto-git-ignore": "*"
},
"require-dev": {
"silverstripe/compass": "*",
"silverstripe/docsviewer": "*"
},
"scripts": {
"post-update-cmd": "GDM\\SSAutoGitIgnore\\UpdateScript::Go"
},
"minimum-stability": "dev"
}
# Dev Environments for Contributing Code {#contributing} # Dev Environments for Contributing Code {#contributing}
So you want to contribute to SilverStripe? Fantastic! You can do this with composer too. So you want to contribute to SilverStripe? Fantastic! You can do this with composer too.
@ -139,7 +203,7 @@ and remove the `@stable` markers from the `silverstripe/cms` and `silverstripe/f
Another `composer update --dev` call will now fetch from the development branch instead. Another `composer update --dev` call will now fetch from the development branch instead.
Note that you can also convert an existing composer project with these steps. Note that you can also convert an existing composer project with these steps.
Please read the ["Contributing Code"](/misc/contributing/code) documentation to find out how to Please read the ["Contributing Code"](/contributing/code) documentation to find out how to
create forks and send pull requests. create forks and send pull requests.
# Advanced usage # Advanced usage
@ -290,9 +354,9 @@ which triggers their installation into the correct path.
### How do I convert an existing project to Composer? ### How do I convert an existing project to Composer?
The easiest way is to follow the [upgrading](/installation/upgrading) instructions Copy the `composer.json` file from a newer release, and adjust the
and switch to a newer release. Alternatively, copy the `composer.json` file from version settings in the "require" section to your needs. Then refer to
a newer release, and adjust the version settings in the "require" section to your needs. the [upgrading documentation](/upgrading).
You'll also need to update your webserver configuration You'll also need to update your webserver configuration
from there (`.htaccess` or `web.config` files), in order to prevent from there (`.htaccess` or `web.config` files), in order to prevent
web access to the composer-generated files. web access to the composer-generated files.

View File

@ -93,9 +93,9 @@ This is my `_ss_environment.php` file. I have it placed in `/var`, as each of th
define('SS_DEFAULT_ADMIN_USERNAME', '<email>'); define('SS_DEFAULT_ADMIN_USERNAME', '<email>');
define('SS_DEFAULT_ADMIN_PASSWORD', '<password>'); define('SS_DEFAULT_ADMIN_PASSWORD', '<password>');
// This causes errors to be written to the silverstripe.log file in the same directory as this file, so /var. // This causes errors to be written to the BASE_PATH/silverstripe.log file.
// Before PHP 5.3.0, you'll need to use dirname(__FILE__) instead of __DIR__ // Path must be relative to BASE_PATH
define('SS_ERROR_LOG', __DIR__ . '/silverstripe.log'); define('SS_ERROR_LOG', 'silverstripe.log');
// This is used by sake to know which directory points to which URL // This is used by sake to know which directory points to which URL
global $_FILE_TO_URL_MAPPING; global $_FILE_TO_URL_MAPPING;
@ -119,7 +119,7 @@ This is my `_ss_environment.php` file. I have it placed in `/var`, as each of th
| `SS_ENVIRONMENT_TYPE`| The environment type: dev, test or live.| | `SS_ENVIRONMENT_TYPE`| The environment type: dev, test or live.|
| `SS_DEFAULT_ADMIN_USERNAME`| The username of the default admin. This is a user with administrative privileges.| | `SS_DEFAULT_ADMIN_USERNAME`| The username of the default admin. This is a user with administrative privileges.|
| `SS_DEFAULT_ADMIN_PASSWORD`| The password of the default admin. This will not be stored in the database.| | `SS_DEFAULT_ADMIN_PASSWORD`| The password of the default admin. This will not be stored in the database.|
| `SS_USE_BASIC_AUTH`| Protect the site with basic auth (good for test sites).<br/>When using CGI/FastCGI with Apache, you will have to add the `RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]` rewrite rule to your `.htaccess` file| | `SS_USE_BASIC_AUTH`| Protect the site with basic auth (good for test sites).<br/>When using CGI/FastCGI with Apache, you will have to add the `RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]` rewrite rule to your `.htaccess` file|
| `SS_SEND_ALL_EMAILS_TO`| If you set this define, all emails will be redirected to this address.| | `SS_SEND_ALL_EMAILS_TO`| If you set this define, all emails will be redirected to this address.|
| `SS_SEND_ALL_EMAILS_FROM`| If you set this define, all emails will be send from this address.| | `SS_SEND_ALL_EMAILS_FROM`| If you set this define, all emails will be send from this address.|
| `SS_ERROR_LOG` | | | `SS_ERROR_LOG` | Relative path to the log file |

View File

@ -117,7 +117,7 @@ Example: `mysite/code/MyClass.php`
To help with namespacing common class names (like Database) it is recommended to use a prefix convention `SS_ClassName` but the filename will remain `ClassName.php`. To help with namespacing common class names (like Database) it is recommended to use a prefix convention `SS_ClassName` but the filename will remain `ClassName.php`.
See [directory-structure](/topics/directory-structure) for more information. See [directory structure](directory_structure) for more information.
## Coding Style ## Coding Style
@ -149,7 +149,7 @@ When a string is literal (contains no variable substitutions), the apostrophe or
When a literal string itself contains apostrophes, it is permitted to demarcate the string with quotation marks or "double quotes". When a literal string itself contains apostrophes, it is permitted to demarcate the string with quotation marks or "double quotes".
:::php :::php
$greeting = "She said 'hello'"; $greeting = "They said 'hello'";
This syntax is preferred over escaping apostrophes as it is much easier to read. This syntax is preferred over escaping apostrophes as it is much easier to read.
@ -458,5 +458,4 @@ which are licensed under BSD (see [license](http://framework.zend.com/license)).
## Related ## Related
* [Topics: CSS](/topics/css) * [Reference: CMS Architecture](/developer_guides/customising_the_admin_interface/cms_architecture)
* [Reference: CMS Architecture](/reference/cms-archirecture)

View File

@ -178,7 +178,7 @@ would create a new tab called "New Tab", and a single "Author" textfield inside.
</div> </div>
We have added two fields: A simple `[api:TextField]` and a `[api:DateField]`. We have added two fields: A simple `[api:TextField]` and a `[api:DateField]`.
There are many more fields available in the default installation, listed in ["form field types"](/developer_guides/forms/fields/common_subclasses). There are many more fields available in the default installation, listed in ["form field types"](/developer_guides/forms/field_types/common_subclasses).
:::php :::php
return $fields; return $fields;

View File

@ -9,7 +9,7 @@ This tutorial is deprecated, and has been replaced by Lessons 7, 8, 9, and 10 in
## Overview ## Overview
This tutorial explores the relationship and management of [DataObjects](/developer_guides/model/dataobject). It builds on the [second tutorial](/tutorials/extending_a_basic_site) where we learnt how to define This tutorial explores the relationship and management of [DataObjects](api:DataObject). It builds on the [second tutorial](/tutorials/extending_a_basic_site) where we learnt how to define
additional fields on our models, and attach images to them. additional fields on our models, and attach images to them.
## What are we working towards? ## What are we working towards?
@ -65,8 +65,9 @@ Let's create the `Student` and `Project` objects.
The relationships are defined through the `$has_one` The relationships are defined through the `$has_one`
and `$has_many` properties on the objects. and `$has_many` properties on the objects.
The array keys declares the name of the relationship, The array keys declares the name of the relationship,
the array values contain the class name (see the ["database structure"](/developer_guides/model/database_structure) the array values contain the class name
and ["datamodel"](/developer_guides/model/data_model_and_orm) topics for more information). (see the ["datamodel"](/developer_guides/model/data_model_and_orm)
topic for more information).
As you can see, only the `Project` model extends `Page`, As you can see, only the `Project` model extends `Page`,
while `Student` is a plain `DataObject` subclass. while `Student` is a plain `DataObject` subclass.

View File

@ -5,7 +5,7 @@ introduction: The tutorials below take a step by step look at how to build a Sil
<div class="alert" markdown="1"> <div class="alert" markdown="1">
These tutorials are deprecated, and have been replaced by the new [Lessons](http://silverstripe.org/learn/lessons) section. These tutorials are deprecated, and have been replaced by the new [Lessons](http://silverstripe.org/learn/lessons) section.
</div> </div>
[CHIDLREN]
## Video lessons ## Video lessons
These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites. These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites.

View File

@ -12,8 +12,8 @@ information.
All data tables in SilverStripe are defined as subclasses of [api:DataObject]. The [api:DataObject] class represents a All data tables in SilverStripe are defined as subclasses of [api:DataObject]. The [api:DataObject] class represents a
single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern) single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern)
design pattern. Database Columns are is defined as [Data Types](data_types_and_casting) in the static `$db` variable design pattern. Database Columns are defined as [Data Types](data_types_and_casting) in the static `$db` variable
along with any [relationships](../relations) defined as `$has_one`, `$has_many`, `$many_many` properties on the class. along with any [relationships](relations) defined as `$has_one`, `$has_many`, `$many_many` properties on the class.
Let's look at a simple example: Let's look at a simple example:
@ -222,7 +222,7 @@ Notice that we can step into the loop safely without having to check if `$player
// do something here // do something here
} }
See the [Lists](../lists) documentation for more information on dealing with [api:SS_List] instances. See the [Lists](lists) documentation for more information on dealing with [api:SS_List] instances.
## Returning a single DataObject ## Returning a single DataObject
@ -401,7 +401,7 @@ Remove both Sam and Sig..
'Surname' => 'Minnée', 'Surname' => 'Minnée',
)); ));
And removing Sig and Sam with that are either age 17 or 74. And removing Sig and Sam with that are either age 17 or 43.
:::php :::php
$players = Player::get()->exclude(array( $players = Player::get()->exclude(array(
@ -409,7 +409,7 @@ And removing Sig and Sam with that are either age 17 or 74.
'Age' => array(17, 43) 'Age' => array(17, 43)
)); ));
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '74)); // SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43'));
You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command. You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command.
@ -512,7 +512,7 @@ whenever a new object is created.
<div class="notice" markdown='1'> <div class="notice" markdown='1'>
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
[Data Types and Casting](data-types) for details. [Data Types and Casting](data_types_and_casting) for details.
</div> </div>
## Subclasses ## Subclasses
@ -548,7 +548,7 @@ The data for the following classes would be stored across the following tables:
- LastEdited: Datetime - LastEdited: Datetime
- Title: Varchar - Title: Varchar
- Content: Text - Content: Text
NewsArticle: NewsPage:
- ID: Int - ID: Int
- Summary: Text - Summary: Text
@ -558,7 +558,7 @@ Accessing the data is transparent to the developer.
$news = NewsPage::get(); $news = NewsPage::get();
foreach($news as $article) { foreach($news as $article) {
echo $news->Title; echo $article->Title;
} }
The way the ORM stores the data is this: The way the ORM stores the data is this:
@ -575,7 +575,7 @@ example above, NewsSection didn't have its own data, so an extra table would be
* In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record: * In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record:
record #2 in Page refers to the same object as record #2 in `[api:SiteTree]`. record #2 in Page refers to the same object as record #2 in `[api:SiteTree]`.
To retrieve a news article, SilverStripe joins the [api:SiteTree], [api:Page] and NewsArticle tables by their ID fields. To retrieve a news article, SilverStripe joins the [api:SiteTree], [api:Page] and NewsPage tables by their ID fields.
## Related Documentation ## Related Documentation

View File

@ -204,6 +204,27 @@ The relationship can also be navigated in [templates](../templates).
<% end_if %> <% end_if %>
<% end_with %> <% end_with %>
To specify multiple $many_manys between the same classes, use the dot notation to distinguish them like below:
:::php
<?php
class Category extends DataObject {
private static $many_many = array(
'Products' => 'Product',
'FeaturedProducts' => 'Product'
);
}
class Product extends DataObject {
private static $belongs_many_many = array(
'Categories' => 'Category.Products',
'FeaturedInCategories' => 'Category.FeaturedProducts'
);
}
## many_many or belongs_many_many? ## many_many or belongs_many_many?
If you're unsure about whether an object should take on `many_many` or `belongs_many_many`, the best way to think about it is that the object where the relationship will be edited (i.e. via checkboxes) should contain the `many_many`. For instance, in a `many_many` of Product => Categories, the `Product` should contain the `many_many`, because it is much more likely that the user will select Categories for a Product than vice-versa. If you're unsure about whether an object should take on `many_many` or `belongs_many_many`, the best way to think about it is that the object where the relationship will be edited (i.e. via checkboxes) should contain the `many_many`. For instance, in a `many_many` of Product => Categories, the `Product` should contain the `many_many`, because it is much more likely that the user will select Categories for a Product than vice-versa.

View File

@ -54,7 +54,7 @@ Example: Disallow creation of new players if the currently logged-in player is n
Triggered before executing *delete()* on an existing object. Triggered before executing *delete()* on an existing object.
Example: Checking for a specific [permission](/reference/permission) to delete this type of object. It checks if a Example: Checking for a specific [permission](permissions) to delete this type of object. It checks if a
member is logged in who belongs to a group containing the permission "PLAYER_DELETE". member is logged in who belongs to a group containing the permission "PLAYER_DELETE".
:::php :::php

View File

@ -103,7 +103,7 @@ Creates a map based on the first two columns of the query result.
## Related Documentation ## Related Documentation
* [Introduction to the Data Model and ORM](../data_model_and_orm) * [Introduction to the Data Model and ORM](data_model_and_orm)
## API Documentation ## API Documentation

View File

@ -5,7 +5,7 @@ summary: Add Indexes to your Data Model to optimize database queries.
It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint
to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that
represent each index. There's several supported notations: represent each index. There're several supported notations:
:::php :::php
<?php <?php
@ -19,7 +19,7 @@ represent each index. There's several supported notations:
); );
} }
The `<index-name>` can be an an arbitrary identifier in order to allow for more than one index on a specific database The `<index-name>` can be an arbitrary identifier in order to allow for more than one index on a specific database
column. The "advanced" notation supports more `<type>` notations. These vary between database drivers, but all of them column. The "advanced" notation supports more `<type>` notations. These vary between database drivers, but all of them
support the following: support the following:
@ -27,8 +27,8 @@ support the following:
* `unique`: Index plus uniqueness constraint on the value * `unique`: Index plus uniqueness constraint on the value
* `fulltext`: Fulltext content index * `fulltext`: Fulltext content index
In order to use more database specific or complex index notations, we also support raw SQL for as a value in the In order to use more database specific or complex index notations, we also support raw SQL as a value in the
`$indexes` definition. Keep in mind this will likely make your code less portable between databases. `$indexes` definition. Keep in mind that using raw SQL is likely to make your code less portable between DBMSs.
**mysite/code/MyTestObject.php** **mysite/code/MyTestObject.php**

View File

@ -1,8 +1,8 @@
# Dynamic Default Values # Dynamic Default Values
The [api:DataObject::$defaults] array allows you to specify simple static values to be the default value for when a The [api:DataObject::$defaults] array allows you to specify simple static values to be the default values when a
record is created, but in many situations default values needs to be dynamically calculated. In order to do this, the record is created, but in many situations default values need to be dynamically calculated. In order to do this, the
`[api:DataObject->populateDefaults()]` method will need to be overloaded. [api:DataObject->populateDefaults()] method will need to be overloaded.
This method is called whenever a new record is instantiated, and you must be sure to call the method on the parent This method is called whenever a new record is instantiated, and you must be sure to call the method on the parent
object! object!

View File

@ -2,7 +2,7 @@
The [api:SS_List] class is designed to return a flat list of records. The [api:SS_List] class is designed to return a flat list of records.
These lists can get quite long, and hard to present on a single list. These lists can get quite long, and hard to present on a single list.
[Pagination](/howto/pagination) is one way to solve this problem, [Pagination](/templates/how_tos/pagination) is one way to solve this problem,
by splitting up the list into multiple pages. by splitting up the list into multiple pages.
In this howto, we present an alternative to pagination: In this howto, we present an alternative to pagination:
@ -144,4 +144,5 @@ The final step is the render this into the template using the [api:GroupedList->
## Related ## Related
* [Howto: "Pagination"](/howto/pagination) * [Howto: "Pagination"](/templates/how_tos/pagination)

View File

@ -393,7 +393,7 @@ A page will normally contain some content and potentially a form of some kind. F
SilverStripe log-in form. If you are on such a page, the `$Form` variable will contain the HTML content of the form. SilverStripe log-in form. If you are on such a page, the `$Form` variable will contain the HTML content of the form.
Placing it just below `$Content` is a good default. Placing it just below `$Content` is a good default.
You can add your own forms by implementing new form instances (see the [Forms tutorial](../tutorials/forms)). You can add your own forms by implementing new form instances (see the [Forms tutorial](/tutorials/forms)).
## Related ## Related

View File

@ -22,7 +22,7 @@ Requiring assets from the template is restricted compared to the PHP API.
## PHP Requirements API ## PHP Requirements API
It is common practice to include most Requirements either in the *init()*-method of your [controller](../controller), or It is common practice to include most Requirements either in the *init()*-method of your [controller](../controllers/), or
as close to rendering as possible (e.g. in `[api:FormField]`. as close to rendering as possible (e.g. in `[api:FormField]`.
:::php :::php
@ -176,16 +176,18 @@ careful when messing with the order of requirements.
By default, SilverStripe includes all Javascript files at the bottom of the page body, unless there's another script By default, SilverStripe includes all Javascript files at the bottom of the page body, unless there's another script
already loaded, then, it's inserted before the first `<script>` tag. If this causes problems, it can be configured. already loaded, then, it's inserted before the first `<script>` tag. If this causes problems, it can be configured.
**mysite/_config/app.yml** :::php
Requirements::set_force_js_to_bottom(true);
:::yml
Requirements:
write_js_to_body: true
force_js_to_bottom: true
`Requirements.force_js_to_bottom`, will force SilverStripe to write the Javascript to the bottom of the page body, even `Requirements.force_js_to_bottom`, will force SilverStripe to write the Javascript to the bottom of the page body, even
if there is an earlier script tag. if there is an earlier script tag.
If the Javascript files are preferred to be placed in the `<head>` tag rather than in the `<body>` tag,
`Requirements.write_js_to_body` should be set to false.
:::php
Requirements::set_force_js_to_bottom(true);
## API Documentation ## API Documentation

View File

@ -46,9 +46,10 @@ your entire project for the appropriate `.ss` files located in `template` direct
It will each and prioritize templates in the following priority: It will each and prioritize templates in the following priority:
1. mysite (or other name given to site folder) 1. mysite (or other name given to site folder)
2. themes 2. module-specific themes (e.g. themes/simple_blog)
3. modules 3. themes (e.g. themes/simple)
4. framework. 4. modules (e.g. blog)
5. framework
<div class="warning"> <div class="warning">
Whenever you add or remove template files, rebuild the manifest by visiting `http://yoursite.com/?flush=1`. You can Whenever you add or remove template files, rebuild the manifest by visiting `http://yoursite.com/?flush=1`. You can

View File

@ -47,6 +47,30 @@ located within the `themes` directory.
![themes:basicfiles.gif](../../_images/basicfiles.gif) ![themes:basicfiles.gif](../../_images/basicfiles.gif)
Your theme can also be organised into split folders for each module it caters for.
```
themes
blackcandy
css
style.css
images
templates
Page.ss
Layout
Page.ss
Includes
blackcandy_blog
css
blog.css
images
templates
Layout
BlogHolder.ss
BlogEntry.ss
Includes
```
## Submitting your theme to SilverStripe ## Submitting your theme to SilverStripe
If you want to submit your theme to the SilverStripe directory then check If you want to submit your theme to the SilverStripe directory then check

View File

@ -31,4 +31,4 @@ top level menu with a nested second level using the `Menu` loop and a `Children`
## Related ## Related
* [Template Syntax](../syntax) * [Template Syntax](../syntax)
* [Common Variables](../command_variables) * [Common Variables](../common_variables)

View File

@ -88,6 +88,9 @@ when using custom lists.
$pages = new PaginatedList(Page::get(), $this->request); $pages = new PaginatedList(Page::get(), $this->request);
$pages->setPageLength(25); $pages->setPageLength(25);
If you set this limit to 0 it will disable paging entirely, effectively causing it to appear as a single page
list.
## Template Variables ## Template Variables
| Variable | Description | | Variable | Description |

View File

@ -39,7 +39,7 @@ routing.
</div> </div>
<div class="alert" markdown="1"> <div class="alert" markdown="1">
Make sure that after you have modified the `routes.yml` file, that you clear your SilverStripe caches using `flush=1`. Make sure that after you have modified the `routes.yml` file, that you clear your SilverStripe caches using `?flush=1`.
</div> </div>
**mysite/_config/routes.yml** **mysite/_config/routes.yml**
@ -70,7 +70,7 @@ Action methods can return one of four main things:
* an array. In this case the values in the array are available in the templates and the controller completes as usual by returning a [api:SS_HTTPResponse] with the body set to the current template. * an array. In this case the values in the array are available in the templates and the controller completes as usual by returning a [api:SS_HTTPResponse] with the body set to the current template.
* `HTML`. SilverStripe will wrap the `HTML` into a `SS_HTTPResponse` and set the status code to 200. * `HTML`. SilverStripe will wrap the `HTML` into a `SS_HTTPResponse` and set the status code to 200.
* an [api:SS_HTTPResponse] containing a manually defined `status code` and `body`. * an [api:SS_HTTPResponse] containing a manually defined `status code` and `body`.
* an [api:SS_HTTPResponse_Exception]. A special type of response which indicates a error. By returning the exception, the execution pipeline can adapt and display any error handlers. * an [api:SS_HTTPResponse_Exception]. A special type of response which indicates an error. By returning the exception, the execution pipeline can adapt and display any error handlers.
**mysite/code/controllers/TeamController.php** **mysite/code/controllers/TeamController.php**
@ -144,8 +144,8 @@ If a template of that name does not exist, then SilverStripe will fall back to t
Controller actions can use `renderWith` to override this template selection process as in the previous example with Controller actions can use `renderWith` to override this template selection process as in the previous example with
`htmlaction`. `MyCustomTemplate.ss` would be used rather than `TeamsController`. `htmlaction`. `MyCustomTemplate.ss` would be used rather than `TeamsController`.
For more information about templates, inheritance and how to rendering into views, See the For more information about templates, inheritance and how to render into views, See the
[Templates and Views](templates) documentation. [Templates and Views](../templates) documentation.
## Link ## Link

View File

@ -135,6 +135,14 @@ start parsing variables and the appropriate controller action AFTER the `//`).
## URL Handlers ## URL Handlers
<div class="alert" markdown="1">
You **must** use the **$url_handlers** static array described here if your URL
pattern does not use the Controller class's default pattern of
`$Action//$ID/$OtherID`. If you fail to do so, and your pattern has more than
2 parameters, your controller will throw the error "I can't handle sub-URLs of
a *class name* object" with HTTP status 404.
</div>
In the above example the URLs were configured using the [api:Director] rules in the **routes.yml** file. Alternatively In the above example the URLs were configured using the [api:Director] rules in the **routes.yml** file. Alternatively
you can specify these in your Controller class via the **$url_handlers** static array. This array is processed by the you can specify these in your Controller class via the **$url_handlers** static array. This array is processed by the
[api:RequestHandler] at runtime once the `Controller` has been matched. [api:RequestHandler] at runtime once the `Controller` has been matched.
@ -154,12 +162,42 @@ This is useful when you want to provide custom actions for the mapping of `teams
); );
private static $url_handlers = array( private static $url_handlers = array(
'staff/$ID/$Name' => 'payroll' 'staff/$ID/$Name' => 'payroll',
'coach/$ID/$Name' => 'payroll' 'coach/$ID/$Name' => 'payroll'
); );
The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules. The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules.
Now lets consider a more complex example from a real project, where using
**$url_handlers** is mandatory. In this example, the URLs are of the form
`http://example.org/feed/go/`, followed by 5 parameters. The PHP controller
class specifies the URL pattern in `$url_handlers`. Notice that it defines 5
parameters.
:::php
class FeedController extends ContentController {
private static $allowed_actions = array('go');
private static $url_handlers = array(
'go/$UserName/$AuthToken/$Timestamp/$OutputType/$DeleteMode' => 'go'
);
public function go() {
$this->validateUser(
$this->request->param('UserName'),
$this->request->param('AuthToken')
);
/* more processing goes here */
}
The YAML rule, in contrast, is simple. It needs to provide only enough
information for the framework to choose the desired controller.
:::yaml
Director:
rules:
'feed': 'FeedController'
## Links ## Links
* [api:Controller] API documentation * [api:Controller] API documentation

View File

@ -45,7 +45,7 @@ In practice, this looks like:
FormAction::create("doSayHello")->setTitle("Say hello") FormAction::create("doSayHello")->setTitle("Say hello")
); );
$required = new RequiredFields('Name') $required = new RequiredFields('Name');
$form = new Form($this, 'HelloForm', $fields, $actions, $required); $form = new Form($this, 'HelloForm', $fields, $actions, $required);

View File

@ -0,0 +1,407 @@
# UploadField
## Introduction
The UploadField will let you upload one or multiple files of all types, including images. But that's not all it does - it will also link the uploaded file(s) to an existing relation and let you edit the linked files as well. That makes it flexible enough to sometimes even replace the GridField, like for instance in creating and managing a simple gallery.
## Usage
The field can be used in three ways: To upload a single file into a `has_one` relationship,or allow multiple files into a `has_many` or `many_many` relationship, or to act as a stand
alone uploader into a folder with no underlying relation.
## Validation
Although images are uploaded and stored on the filesystem immediately after selection,the value (or values) of this field will not be written to any related record until the record is saved and successfully validated. However, any invalid records will still persist across form submissions until explicitly removed or replaced by the user.
Care should be taken as invalid files may remain within the filesystem until explicitly removed.
### Single fileupload
The following example adds an UploadField to a page for single fileupload, based on a has_one relation:
```php
class GalleryPage extends Page {
private static $has_one = array(
'SingleImage' => 'Image'
);
function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Upload',
$uploadField = new UploadField(
$name = 'SingleImage',
$title = 'Upload a single image'
)
);
return $fields;
}
}
```
The UploadField will auto-detect the relation based on it's `name` property, and save it into the GalleyPages' `SingleImageID` field. Setting the `setAllowedMaxFileNumber` to 1 will make sure that only one image can ever be uploaded and linked to the relation.
### Multiple fileupload
Enable multiple fileuploads by using a many_many (or has_many) relation. Again, the `UploadField` will detect the relation based on its $name property value:
```php
class GalleryPage extends Page {
private static $many_many = array(
'GalleryImages' => 'Image'
);
function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Upload',
$uploadField = new UploadField(
$name = 'GalleryImages',
$title = 'Upload one or more images (max 10 in total)'
)
);
$uploadField->setAllowedMaxFileNumber(10);
return $fields;
}
}
class GalleryPage_Controller extends Page_Controller {
}
```
```php
class GalleryImageExtension extends DataExtension {
private static $belongs_many_many = array('Galleries' => 'GalleryPage);
}
```
```yml
Image:
extensions:
- GalleryImageExtension
```
<div class="notice" markdown='1'>
In order to link both ends of the relationship together it's usually advisable to extend Image with the necessary $has_one, $belongs_to, $has_many or $belongs_many_many. In particular, a DataObject with $has_many Images will not work without this specified explicitly.
</div>
## Configuration
### Overview
The field can either be configured on an instance level with the various getProperty and setProperty functions, or globally by overriding the YAML defaults.
See the [Configuration Reference](uploadfield#configuration-reference) section for possible values.
Example: mysite/_config/uploadfield.yml
```yml
after: framework#uploadfield
---
UploadField:
defaultConfig:
canUpload: false
```
### Set a custom folder
This example will save all uploads in the `/assets/customfolder/` folder. If the folder doesn't exist, it will be created.
```php
$fields->addFieldToTab(
'Root.Upload',
$uploadField = new UploadField(
$name = 'GalleryImages',
$title = 'Please upload one or more images' )
);
$uploadField->setFolderName('customfolder');
```
### Limit the allowed filetypes
`AllowedExtensions` defaults to the `File.allowed_extensions` configuration setting, but can be overwritten for each UploadField:
```php
$uploadField->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
```
Entire groups of file extensions can be specified in order to quickly limit types to known file categories.
```php
$uploadField->setAllowedFileCategories('image', 'doc');
```
This will limit files to the following extensions: bmp gif jpg jpeg pcx tif png alpha als cel icon ico ps doc docx txt rtf xls xlsx pages ppt pptx pps csv html htm xhtml xml pdf.
`AllowedExtensions` can also be set globally via the [YAML configuration](/developer_guides/configuration/configuration/#configuration-yaml-syntax-and-rules), for example you may add the following into your mysite/_config/config.yml:
```yaml
File:
allowed_extensions:
- 7zip
- xzip
```
### Limit the maximum file size
`AllowedMaxFileSize` is by default set to the lower value of the 2 php.ini configurations: `upload_max_filesize` and `post_max_size`. The value is set as bytes.
NOTE: this only sets the configuration for your UploadField, this does NOT change your server upload settings, so if your server is set to only allow 1 MB and you set the UploadField to 2 MB, uploads will not work.
```php
$sizeMB = 2; // 2 MB
$size = $sizeMB * 1024 * 1024; // 2 MB in bytes
$this->getValidator()->setAllowedMaxFileSize($size);
```
You can also specify a default global maximum file size setting in your config for different file types. This is overridden when specifying the max allowed file size on the UploadField instance.
```yaml
Upload_Validator:
default_max_file_size:
'[image]': '1m'
'[doc]': '5m'
'jpeg': 2000
```
### Preview dimensions
Set the dimensions of the image preview. By default the max width is set to 80 and the max height is set to 60.
```php
$uploadField->setPreviewMaxWidth(100);
$uploadField->setPreviewMaxHeight(100);
```
### Disable attachment of existing files
This can force the user to upload a new file, rather than link to the already existing file library
```php
$uploadField->setCanAttachExisting(false);
```
### Disable uploading of new files
Alternatively, you can force the user to only specify already existing files in the file library
```php
$uploadField->setCanUpload(false);
```
### Automatic or manual upload
By default, the UploadField will try to automatically upload all selected files. Setting the `autoUpload` property to false, will present you with a list of selected files that you can then upload manually one by one:
```php
$uploadField->setAutoUpload(false);
```
### Change Detection
The CMS interface will automatically notify the form containing
an UploadField instance of changes, such as a new upload,
or the removal of an existing upload (through a `dirty` event).
The UI can then choose an appropriate response (e.g. highlighting the "save" button). If the UploadField doesn't save into a relation, there's technically no saveable change (the upload has already happened), which is why this feature can be disabled on demand.
```php
$uploadField->setConfig('changeDetection', false);
```
### Build a simple gallery
A gallery most times needs more then simple images. You might want to add a description, or maybe some settings to define a transition effect for each slide.
First create a [DataExtension](/developer_guides/extending/extensions) like this:
```php
class GalleryImage extends DataExtension {
private static $db = array(
'Description' => 'Text'
);
private static $belongs_many_many = array(
'GalleryPage' => 'GalleryPage'
);
}
```
Now register the DataExtension for the Image class in your mysite/_config/config.yml:
```yml
Image:
extensions:
- GalleryImage
```
<div class="notice" markdown='1'>
Note: Although you can subclass the Image class instead of using a DataExtension, this is not advisable. For instance: when using a subclass, the 'From files' button will only return files that were uploaded for that subclass, it won't recognize any other images!
</div>
### Edit uploaded images
By default the UploadField will let you edit the following fields: *Title, Filename, Owner and Folder*. The fileEditFields` configuration setting allows you you alter these settings. One way to go about this is create a `getCustomFields` function in your GalleryImage object like this:
```php
class GalleryImage extends DataExtension {
...
function getCustomFields() {
$fields = new FieldList();
$fields->push(new TextField('Title', 'Title'));
$fields->push(new TextareaField('Description', 'Description'));
return $fields;
}
}
```
Then, in your GalleryPage, tell the UploadField to use this function:
```php
$uploadField->setFileEditFields('getCustomFields');
```
In a similar fashion you can use 'setFileEditActions' to set the actions for the editform, or 'fileEditValidator' to determine the validator (e.g. RequiredFields).
### Configuration Reference
- `setAllowedMaxFileNumber`: (int) php validation of allowedMaxFileNumber only works when a db relation is available, set to null to allow unlimited if record has a has_one and allowedMaxFileNumber is null, it will be set to 1.
- `setAllowedFileExtensions`: (array) List of file extensions allowed.
- `setAllowedFileCategories`: (array|string) List of types of files allowed. May be any of 'image', 'audio', 'mov', 'zip', 'flash', or 'doc'.
- `setAutoUpload`: (boolean) Should the field automatically trigger an upload once a file is selected?
- `setCanAttachExisting`: (boolean|string) Can the user attach existing files from the library. String values are interpreted as permission codes.
- `setCanPreviewFolder`: (boolean|string) Can the user preview the folder files will be saved into? String values are interpreted as permission codes.
- `setCanUpload`: (boolean|string) Can the user upload new files, or just select from existing files. String values are interpreted as permission codes.
- `setDownloadTemplateName`: (string) javascript template used to display already uploaded files, see javascript/UploadField_downloadtemplate.js.
- `setFileEditFields`: (FieldList|string) FieldList $fields or string $name (of a method on File to provide a fields) for the EditForm (Example: 'getCMSFields').
- `setFileEditActions`: (FieldList|string) FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm (Example: 'getCMSActions').
- `setFileEditValidator`: (string) Validator (eg RequiredFields) or string $name (of a method on File to provide a Validator) for the EditForm (Example: 'getCMSValidator').
- `setOverwriteWarning`: (boolean) Show a warning when overwriting a file.
- `setPreviewMaxWidth`: (int).
- `setPreviewMaxHeight`: (int).
- `setTemplateFileButtons`: (string) Template name to use for the file buttons.
- `setTemplateFileEdit`: (string) Template name to use for the file edit form.
- `setUploadTemplateName`: (string) javascript template used to display uploading files, see javascript/UploadField_uploadtemplate.js.
- `setCanPreviewFolder`: (boolean|string) Is the upload folder visible to uploading users? String values are interpreted as permission codes.
Certain default values for the above can be configured using the YAML config system.
```yaml
UploadField:
defaultConfig:
autoUpload: true
allowedMaxFileNumber:
canUpload: true
canAttachExisting: 'CMS_ACCESS_AssetAdmin'
canPreviewFolder: true
previewMaxWidth: 80
previewMaxHeight: 60
uploadTemplateName: 'ss-uploadfield-uploadtemplate'
downloadTemplateName: 'ss-uploadfield-downloadtemplate'
overwriteWarning: true # Warning before overwriting existing file (only relevant when Upload: replaceFile is true)
```
The above settings can also be set on a per-instance basis by using `setConfig` with the appropriate key.
The `Upload_Validator` class has configuration options for setting the `default_max_file_size`.
```yaml
Upload_Validator:
default_max_file_size:
'[image]': '1m'
'[doc]': '5m'
'jpeg': 2000
```
You can specify the file extension or the app category (as specified in the `File` class) in square brackets. It supports setting the file size in bytes or using the syntax supported by `File::ini2bytes()`.
You can also configure the underlying `[api:Upload]` class, by using the YAML config system.
```yaml
Upload:
# Globally disables automatic renaming of files and displays a warning before overwriting an existing file
replaceFile: true
uploads_folder: 'Uploads'
```
## Using the UploadField in a frontend form
The UploadField can be used in a frontend form, given that sufficient attention is given to the permissions granted to non-authorised users.
By default Image::canDelete and Image::canEdit do not require admin privileges, so make sure you override the methods in your Image extension class.
For instance, to generate an upload form suitable for saving images into a user-defined gallery the below code could be used:
*In GalleryPage.php:*
```php
<?php
class GalleryPage extends Page {}
class GalleryPage_Controller extends Page_Controller {
private static $allowed_actions = array('Form');
public function Form() {
$fields = new FieldList(
new TextField('Title', 'Title', null, 255),
$field = new UploadField('Images', 'Upload Images')
);
$field->setCanAttachExisting(false); // Block access to SilverStripe assets library
$field->setCanPreviewFolder(false); // Don't show target filesystem folder on upload field
$field->relationAutoSetting = false; // Prevents the form thinking the GalleryPage is the underlying object
$actions = new FieldList(new FormAction('submit', 'Save Images'));
return new Form($this, 'Form', $fields, $actions, null);
}
public function submit($data, Form $form) {
$gallery = new Gallery();
$form->saveInto($gallery);
$gallery->write();
return $this;
}
}
```
*Gallery.php:*
```php
<?php
class Gallery extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)'
);
private static $many_many = array(
'Images' => 'Image'
);
}
```
*ImageExtension.php:*
```php
<?php
class ImageExtension extends DataExtension {
private static $belongs_many_many = array(
'Gallery' => 'Gallery'
);
function canEdit($member) {
// WARNING! This affects permissions on ALL images. Setting this incorrectly can restrict
// access to authorised users or unintentionally give access to unauthorised users if set incorrectly.
return Permission::check('CMS_ACCESS_AssetAdmin');
}
}
```
*mysite/_config/config.yml*
```yml
Image:
extensions:
- ImageExtension
```

View File

@ -1,6 +1,6 @@
# How to add a custom action to a GridField row # How to add a custom action to a GridField row
In a [GridField](/reference/grid-field) instance each table row can have a In a [GridField](../field_types/gridfield) instance each table row can have a
number of actions located the end of the row such as edit or delete actions. number of actions located the end of the row such as edit or delete actions.
Each action is represented as a instance of a specific class Each action is represented as a instance of a specific class
(e.g [api:GridFieldEditButton]) which has been added to the `GridFieldConfig` (e.g [api:GridFieldEditButton]) which has been added to the `GridFieldConfig`
@ -79,7 +79,7 @@ below:
While we're working on the code, to add this new action to the `GridField`, add While we're working on the code, to add this new action to the `GridField`, add
a new instance of the class to the [api:GridFieldConfig] object. The `GridField` a new instance of the class to the [api:GridFieldConfig] object. The `GridField`
[Reference](/reference/grid-field) documentation has more information about [Reference](../field_types/gridfield) documentation has more information about
manipulating the `GridFieldConfig` instance if required. manipulating the `GridFieldConfig` instance if required.
:::php :::php
@ -142,6 +142,6 @@ message to the user interface indicating a successful message.
## Related ## Related
* [GridField Reference](/reference/grid-field) * [GridField Reference](/developer_guides/forms/field_types/gridfield)
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin) * [ModelAdmin: A UI driven by GridField](/developer_guides/customising_the_admin_interface/modeladmin)
* [Tutorial 5: Dataobject Relationship Management](/tutorials/5-dataobject-relationship-management) * [Tutorial 5: Dataobject Relationship Management](/tutorials/dataobject_relationship_management)

View File

@ -16,7 +16,7 @@ throughout the site. Out of the box this includes selecting the current site the
<% with $SiteConfig %> <% with $SiteConfig %>
$Title $AnotherField $Title $AnotherField
<% end_loop %> <% end_with %>
To access variables in the PHP: To access variables in the PHP:
@ -61,10 +61,10 @@ Then activate the extension.
<div class="notice" markdown="1"> <div class="notice" markdown="1">
After adding the class and the YAML change, make sure to rebuild your database by visiting http://yoursite.com/dev/build. After adding the class and the YAML change, make sure to rebuild your database by visiting http://yoursite.com/dev/build.
You may also need to reload the screen with a `flush=1` i.e http://yoursite.com/admin/settings?flush=1. You may also need to reload the screen with a `?flush=1` i.e http://yoursite.com/admin/settings?flush=1.
</div> </div>
You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and what to You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and want to
provide the users a place to configure settings then the `SiteConfig` panel is the place to go it. provide the users a place to configure settings then the `SiteConfig` panel is the place to go it.
## API Documentation ## API Documentation

View File

@ -5,7 +5,7 @@ title: How to Publish a SilverStripe module
If you wish to submit your module to our public directory, you take responsibility for a certain level of code quality, If you wish to submit your module to our public directory, you take responsibility for a certain level of code quality,
adherence to conventions, writing documentation, and releasing updates. adherence to conventions, writing documentation, and releasing updates.
SilverStripe uses [Composer](../../getting_started/composer/) to manage module releases and dependencies between SilverStripe uses [Composer](../../../getting_started/composer/) to manage module releases and dependencies between
modules. If you plan on releasing your module to the public, ensure that you provide a `composer.json` file in the root modules. If you plan on releasing your module to the public, ensure that you provide a `composer.json` file in the root
of your module containing the meta-data about your module. of your module containing the meta-data about your module.

View File

@ -309,7 +309,7 @@ equal the class names they manage.
## Related Documentation ## Related Documentation
* [How to use a FixtureFactory](how_to/fixturefactories/) * [How to use a FixtureFactory](how_tos/fixturefactories/)
## API Documentation ## API Documentation

View File

@ -0,0 +1,29 @@
title: Testing Glossary
<dl>
<dt>Assertion<dd>A predicate statement that must be true when a test runs.
<dt>Behat<dd>A behaviour-driven testing library used with SilverStripe as a higher-level alternative to the `FunctionalTest` API, see <http://behat.org>.
<dt>Test Case<dd>The atomic class type in most unit test frameworks. New unit tests are created by inheriting from the base test case.
<dt>Test Suite<dd>Also known as a 'test group', a composite of test cases, used to collect individual unit tests into packages, allowing all tests to be run at once.
<dt>Fixture<dd>Usually refers to the runtime context of a unit test - the environment and data prerequisites that must be in place in order to run the test and expect a particular outcome. Most unit test frameworks provide methods that can be used to create fixtures for the duration of a test - `setUp` - and clean them up after the test is done - `tearDown`.
<dt>Refactoring<dd>A behavior preserving transformation of code. If you change the code, while keeping the actual functionality the same, it is refactoring. If you change the behavior or add new functionality it's not.
<dt>Smell<dd>A code smell is a symptom of a problem. Usually refers to code that is structured in a way that will lead to problems with maintenance or understanding.
<dt>Spike<dd>A limited and throwaway sketch of code or experiment to get a feel for how long it will take to implement a certain feature, or a possible direction for how that feature might work.
<dt>Test Double<dd>Also known as a 'Substitute'. A general term for a dummy object that replaces a real object with the same interface. Substituting objects is useful when a real object is difficult or impossible to incorporate into a unit test.
**Fake Object**: A substitute object that simply replaces a real object with the same interface, and returns a pre-determined (usually fixed) value from each method.
<dt>Mock Object<dd>A substitute object that mimics the same behavior as a real object (some people think of mocks as "crash test dummy" objects). Mocks differ from other kinds of substitute objects in that they must understand the context of each call to them, setting expectations of which, and what order, methods will be invoked and what parameters will be passed.
<dt>Test-Driven Development (TDD)<dd>A style of programming where tests for a new feature are constructed before any code is written. Code to implement the feature is then written with the aim of making the tests pass. Testing is used to understand the problem space and discover suitable APIs for performing specific actions.
<dt>Behavior Driven Development (BDD)<dd>An extension of the test-driven programming style, where tests are used primarily for describing the specification of how code should perform. In practice, there's little or no technical difference - it all comes down to language. In BDD, the usual terminology is changed to reflect this change of focus, so _Specification_ is used in place of _Test Case_, and _should_ is used in place of _expect_ and _assert_.
</dl>

View File

@ -12,7 +12,7 @@ response and modify the session within a test.
:::php :::php
<?php <?php
class HomePageTest extends SapphireTest { class HomePageTest extends FunctionalTest {
/** /**
* Test generation of the view * Test generation of the view
@ -24,7 +24,7 @@ response and modify the session within a test.
$this->assertEquals(200, $page->getStatusCode()); $this->assertEquals(200, $page->getStatusCode());
// We should see a login form // We should see a login form
$login = $this->submitForm("#LoginForm", null, array( $login = $this->submitForm("LoginFormID", null, array(
'Email' => 'test@test.com', 'Email' => 'test@test.com',
'Password' => 'wrongpassword' 'Password' => 'wrongpassword'
)); ));

View File

@ -12,18 +12,15 @@ SilverStripe uses [PHPUnit](http://www.phpunit.de) for unit tests, and the frame
process of creating and managing tests. process of creating and managing tests.
If you're more familiar with unit testing, but want a refresher of some of the concepts and terminology, you can browse If you're more familiar with unit testing, but want a refresher of some of the concepts and terminology, you can browse
the [Testing Glossary](glossary). To get started now, follow the installation instructions below, and check the [Testing Glossary](testing_glossary). To get started now, follow the installation instructions below.
[Troubleshooting](testing-guide-troubleshooting) in case you run into any problems.
If you are familiar with PHP coding but new to unit testing then check out Mark's presentation [Getting to Grips with SilverStripe Testing](http://www.slideshare.net/maetl/getting-to-grips-with-silverstripe-testing). If you are familiar with PHP coding but new to unit testing then check out Mark's presentation [Getting to Grips with SilverStripe Testing](http://www.slideshare.net/maetl/getting-to-grips-with-silverstripe-testing).
[Why Unit Test?](why-should-i-test) will give you reasons why you should be testing your code.
You should also read over [the PHPUnit manual](http://www.phpunit.de/manual/current/en/). It provides a lot of You should also read over [the PHPUnit manual](http://www.phpunit.de/manual/current/en/). It provides a lot of
fundamental concepts that we build on in this documentation. fundamental concepts that we build on in this documentation.
Unit tests are not included in the normal SilverStripe downloads so you need to install them through git repositories Unit tests are not included in the normal SilverStripe downloads so you need to install them through git repositories
([installation instructions](/installation/composer)). ([installation instructions](/getting_started/composer)).
## Install with Composer ## Install with Composer
@ -108,7 +105,7 @@ All command-line arguments are documented on
### Via the "sake" Wrapper on Command Line ### Via the "sake" Wrapper on Command Line
The [sake](/topics/commandline) executable that comes with SilverStripe can trigger a customized The [sake](/developer_guides/cli/) executable that comes with SilverStripe can trigger a customized
`[api:TestRunner]` class that handles the PHPUnit configuration and output formatting. `[api:TestRunner]` class that handles the PHPUnit configuration and output formatting.
While the custom test runner a handy tool, its also more limited than using `phpunit` directly, While the custom test runner a handy tool, its also more limited than using `phpunit` directly,
particularly around formatting test output. particularly around formatting test output.

View File

@ -24,7 +24,7 @@ Append the option and corresponding value to your URL in your browser's address
| URL Variable | | Values | | Description | | URL Variable | | Values | | Description |
| ------------ | | ------ | | ----------- | | ------------ | | ------ | | ----------- |
| isDev | | 1 | | Put the site into [development mode](/topics/debugging), enabling debugging messages to the browser on a live server. For security, you'll be asked to log in with an administrator log-in. Will persist for the current browser session. | | isDev | | 1 | | Put the site into [development mode](../), enabling debugging messages to the browser on a live server. For security, you'll be asked to log in with an administrator log-in. Will persist for the current browser session. |
| isTest | | 1 | | See above. | | isTest | | 1 | | See above. |
| debug | | 1 | | Show a collection of debugging information about the director / controller operation | | debug | | 1 | | Show a collection of debugging information about the director / controller operation |
| debug_request | | 1 | | Show all steps of the request from initial `[api:HTTPRequest]` to `[api:Controller]` to Template Rendering | | debug_request | | 1 | | Show all steps of the request from initial `[api:HTTPRequest]` to `[api:Controller]` to Template Rendering |

View File

@ -3,7 +3,7 @@ summary: Learn how to identify errors in your application and best practice for
# Debugging # Debugging
SilverStripe can be a large and complex framework to debug, but there are ways to make debugging less painful. In this SilverStripe can be a large and complex framework to debug, but there are ways to make debugging less painful. In this
guide we show the basics on defining the correct [Environment Type](environment_type) for your application and other guide we show the basics on defining the correct [Environment Type](environment_types) for your application and other
built-in helpers for dealing with application errors. built-in helpers for dealing with application errors.
[CHILDREN] [CHILDREN]

View File

@ -22,15 +22,15 @@ will invalidate the cache after a given amount of time has expired (default 10 m
Here are some more complex examples: Here are some more complex examples:
:::ss :::ss
<% cached 'database', LastEdited %> <% cached 'database', $LastEdited %>
<!-- that updates every time the record changes. --> <!-- that updates every time the record changes. -->
<% end_cached %> <% end_cached %>
<% cached 'loginblock', CurrentMember.ID %> <% cached 'loginblock', $CurrentMember.ID %>
<!-- cached unique to the user. i.e for user 2, they will see a different cache to user 1 --> <!-- cached unique to the user. i.e for user 2, they will see a different cache to user 1 -->
<% end_cached %> <% end_cached %>
<% cached 'loginblock', LastEdited, CurrentMember.isAdmin %> <% cached 'loginblock', $LastEdited, $CurrentMember.isAdmin %>
<!-- recached when block object changes, and if the user is admin --> <!-- recached when block object changes, and if the user is admin -->
<% end_cached %> <% end_cached %>
@ -53,13 +53,13 @@ user does not influence your template content, you can update this key as below;
Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
change. To do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates change. To do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates
on sets of [api:DataObject]s - the most useful for us being the `Max` aggregate. on sets of [api:DataObject]s - the most useful for us being the `max` aggregate.
For example, if we have a menu, we want that menu to update whenever _any_ page is edited, but would like to cache it For example, if we have a menu, we want that menu to update whenever _any_ page is edited, but would like to cache it
otherwise. By using aggregates, we do that like this: otherwise. By using aggregates, we do that like this:
:::ss :::ss
<% cached 'navigation', List(SiteTree).max(LastEdited), List(SiteTree).count() %> <% cached 'navigation', $List('SiteTree').max('LastEdited'), $List('SiteTree').count() %>
The cache for this will update whenever a page is added, removed or edited. The cache for this will update whenever a page is added, removed or edited.
@ -67,10 +67,10 @@ If we have a block that shows a list of categories, we can make sure the cache u
or edited or edited
:::ss :::ss
<% cached 'categorylist', List(Category).max(LastEdited), List(Category).count() %> <% cached 'categorylist', $List('Category').max('LastEdited'), $List('Category').count() %>
<div class="notice" markdown="1"> <div class="notice" markdown="1">
Note the use of both `.max(LastEdited)` and `.count()` - this takes care of both the case where an object has been Note the use of both `.max('LastEdited')` and `.count()` - this takes care of both the case where an object has been
edited since the cache was last built, and also when an object has been deleted since the cache was last built. edited since the cache was last built, and also when an object has been deleted since the cache was last built.
</div> </div>
@ -78,7 +78,7 @@ We can also calculate aggregates on relationships. A block that shows the curren
whenever the relationship `Member::$has_many = array('Favourites' => Favourite')` changes. whenever the relationship `Member::$has_many = array('Favourites' => Favourite')` changes.
:::ss :::ss
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %> <% cached 'favourites', $CurrentMember.ID, $CurrentMember.Favourites.max('LastEdited') %>
## Cache key calculated in controller ## Cache key calculated in controller
@ -100,7 +100,7 @@ extract that logic into the controller.
Then using that function in the cache key: Then using that function in the cache key:
:::ss :::ss
<% cached FavouriteCacheKey %> <% cached $FavouriteCacheKey %>
## Cache blocks and template changes ## Cache blocks and template changes
@ -118,7 +118,7 @@ data updates.
For instance, if we show some blog statistics, but are happy having them be slightly stale, we could do For instance, if we show some blog statistics, but are happy having them be slightly stale, we could do
:::ss :::ss
<% cached 'blogstatistics', Blog.ID %> <% cached 'blogstatistics', $Blog.ID %>
which will invalidate after the cache lifetime expires. If you need more control than that (cache lifetime is which will invalidate after the cache lifetime expires. If you need more control than that (cache lifetime is
@ -133,7 +133,7 @@ configurable only on a site-wide basis), you could add a special function to you
and then use it in the cache key and then use it in the cache key
:::ss :::ss
<% cached 'blogstatistics', Blog.ID, BlogStatisticsCounter %> <% cached 'blogstatistics', $Blog.ID, $BlogStatisticsCounter %>
## Cache block conditionals ## Cache block conditionals
@ -146,7 +146,7 @@ Following on from the previous example, you might wish to only cache slightly-st
heavy load: heavy load:
:::ss :::ss
<% cached 'blogstatistics', Blog.ID if HighLoad %> <% cached 'blogstatistics', $Blog.ID if $HighLoad %>
By adding a `HighLoad` function to your `Page_Controller`, you could enable or disable caching dynamically. By adding a `HighLoad` function to your `Page_Controller`, you could enable or disable caching dynamically.
@ -155,7 +155,7 @@ To cache the contents of a page for all anonymous users, but dynamically calcula
use something like: use something like:
:::ss :::ss
<% cached unless CurrentUser %> <% cached unless $CurrentUser %>
## Uncached ## Uncached
@ -178,10 +178,10 @@ portion dynamic, without having to include any member info in the page's cache k
An example: An example:
:::ss :::ss
<% cached LastEdited %> <% cached $LastEdited %>
Our wonderful site Our wonderful site
<% cached Member.ID %> <% cached $Member.ID %>
Welcome $Member.Name Welcome $Member.Name
<% end_cached %> <% end_cached %>
@ -196,7 +196,7 @@ Cache conditionals and the uncached tag also work in the same nested manner. Sin
could also write the last example as: could also write the last example as:
:::ss :::ss
<% cached LastEdited %> <% cached $LastEdited %>
Our wonderful site Our wonderful site
<% uncached %> <% uncached %>
@ -214,7 +214,7 @@ letting you know if you've done this. You can often get around this using aggreg
Failing example: Failing example:
:::ss :::ss
<% cached LastEdited %> <% cached $LastEdited %>
<% loop $Children %> <% loop $Children %>
<% cached LastEdited %> <% cached LastEdited %>
@ -227,9 +227,9 @@ Failing example:
Can be re-written as: Can be re-written as:
:::ss :::ss
<% cached LastEdited %> <% cached $LastEdited %>
<% cached AllChildren.max(LastEdited) %> <% cached $AllChildren.max('LastEdited') %>
<% loop $Children %> <% loop $Children %>
$Name $Name
<% end_loop %> <% end_loop %>

View File

@ -17,7 +17,7 @@ Flushing the various manifests is performed through a GET
parameter (`flush=1`). Since this action requires more server resources than normal requests, parameter (`flush=1`). Since this action requires more server resources than normal requests,
executing the action is limited to the following cases when performed via a web request: executing the action is limited to the following cases when performed via a web request:
* The [environment](/topics/environment-management) is in "dev mode" * The [environment](../getting_started/environment_management) is in "dev mode"
* A user is logged in with ADMIN permissions * A user is logged in with ADMIN permissions
* An error occurs during startup * An error occurs during startup

View File

@ -7,7 +7,7 @@ See our "[Release Process](/misc/release-process#security-releases) on how to re
## SQL Injection ## SQL Injection
The [coding-conventions](/misc/coding-conventions) help guard against SQL injection attacks but still require developer The [coding-conventions](/getting_started/coding_conventions) help guard against SQL injection attacks but still require developer
diligence: ensure that any variable you insert into a filter / sort / join clause is either parameterised, or has been diligence: ensure that any variable you insert into a filter / sort / join clause is either parameterised, or has been
escaped. escaped.
@ -95,8 +95,8 @@ result in *double escaping* and alters the actually saved data (e.g. by adding s
### Manual escaping ### Manual escaping
As a rule of thumb, whenever you're creating SQL queries (or just chunks of SQL) you should use parameterisation, As a rule of thumb, whenever you're creating SQL queries (or just chunks of SQL) you should use parameterisation,
but there may be cases where you need to take care of escaping yourself. See [coding-conventions](/misc/coding-conventions) but there may be cases where you need to take care of escaping yourself. See [coding-conventions](/getting_started/coding-conventions)
and [datamodel](/topics/datamodel) for ways to parameterise, cast, and convert your data. and [datamodel](/developer_guides/model) for ways to parameterise, cast, and convert your data.
* `SQLQuery` * `SQLQuery`
* `DB::query()` * `DB::query()`

View File

@ -216,7 +216,7 @@ $service->request('service.json', 'GET', null, null, $curlOptions);
## How to's ## How to's
* [Embed an RSS Feed](how_to/embed_rss) * [Embed an RSS Feed](how_tos/embed_rss)
## API Documentation ## API Documentation

View File

@ -11,7 +11,7 @@ The default output of a [api:SearchContext] is either a [api:SQLQuery] object fo
[api:DataObject] instance. [api:DataObject] instance.
<div class="notice" markdown="1"> <div class="notice" markdown="1">
[api:SearchContext] is mainly used by [ModelAdmin](../customising_the_cms/modeladmin). [api:SearchContext] is mainly used by [ModelAdmin](../customising_the_admin_interface/modeladmin).
</div> </div>
## Usage ## Usage

View File

@ -80,7 +80,7 @@ not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
### Language Names ### Language Names
SilverStripe comes with a built-in list of common languages, listed by locale and region. SilverStripe comes with a built-in list of common languages, listed by locale and region.
They can be accessed via the `i18n.common_languages` and `i18n.common_locales` [config setting](/topics/configuration). They can be accessed via the `i18n.common_languages` and `i18n.common_locales` [config setting](/developer_guides/configuration).
In order to add a value, add the following to your `config.yml`: In order to add a value, add the following to your `config.yml`:
@ -125,7 +125,7 @@ Date- and time related form fields support i18n ([api:DateField], [api:TimeField
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // sets typical 'de_DE' date format, shows as "23. Juni 1982" $field->setConfig('dateformat', 'dd. MMMM YYYY'); // sets typical 'de_DE' date format, shows as "23. Juni 1982"
Defaults can be applied globally for all field instances through the `DateField.default_config` Defaults can be applied globally for all field instances through the `DateField.default_config`
and `TimeField.default_config` [configuration arrays](/topics/configuration). and `TimeField.default_config` [configuration arrays](/developer_guides/configuration).
If no 'locale' default is set on the field, [api:i18n::get_locale()] will be used. If no 'locale' default is set on the field, [api:i18n::get_locale()] will be used.
**Important:** Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases, **Important:** Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases,
@ -408,7 +408,7 @@ The `ss.i18n` object contain a couple functions to help and replace dynamic vari
## Links ## Links
* [Help to translate](/misc/contribute/translation) - Instructions for online collaboration to translate core * [Help to translate](../../contributing/translations) - Instructions for online collaboration to translate core
* [Help to translate](/misc/translation-process) - Instructions for adding translation to your own modules * [Help to translate](../../contributing/translation_process) - Instructions for adding translation to your own modules
* [http://www.i18nguy.com/](http://www.i18nguy.com/) * [http://www.i18nguy.com/](http://www.i18nguy.com/)
* [balbus.tk i18n notes](http://www.balbus.tk/internationalize) * [balbus.tk i18n notes](http://www.balbus.tk/internationalize)

View File

@ -13,7 +13,7 @@ All files, images and folders in the 'assets' directory are stored in the databa
| `Title` | The optional, human-readable title of the file for display only (doesn't apply to folders). | | `Title` | The optional, human-readable title of the file for display only (doesn't apply to folders). |
| `Filename` | The path to the file/folder, relative to the webroot. For example 'assets/images/my-image.jpg', or 'assets/images/' for a folder. | | `Filename` | The path to the file/folder, relative to the webroot. For example 'assets/images/my-image.jpg', or 'assets/images/' for a folder. |
| `Content` | Typically unused, but handy for a textual representation of files. For example for fulltext indexing of PDF documents. | | `Content` | Typically unused, but handy for a textual representation of files. For example for fulltext indexing of PDF documents. |
| `ShowInSearch` | Whether the file should be shown in search results, defaults to '1'. See ["Tutorial 4 - Site Search"](/tutorials/4-site-search) for enabling search. | | `ShowInSearch` | Whether the file should be shown in search results, defaults to '1'. See ["Tutorial 4 - Site Search"](/tutorials/site_search) for enabling search. |
| `ParentID` | The ID of the parent Folder that this File/Folder is in. A ParentID of '0' indicates that the File/Folder is in the 'assets' directory. | | `ParentID` | The ID of the parent Folder that this File/Folder is in. A ParentID of '0' indicates that the File/Folder is in the 'assets' directory. |
| `OwnerID` | The ID of the Member that 'owns' the File/Folder (not related to filesystem permissions). | | `OwnerID` | The ID of the Member that 'owns' the File/Folder (not related to filesystem permissions). |
@ -37,4 +37,4 @@ You may also notice the 'Sync files' button (highlighted below). This button all
## Upload ## Upload
Files can be managed through a `FileField` or an `UploadField`. The `[api:FileField]` class provides a simple HTML input with a type of "file", whereas an `[api:UploadField]` provides a much more feature-rich field (including AJAX-based uploads, previews, relationship management and file data management). See [`Reference - UploadField`](/reference/uploadfield) for more information about how to use the `UploadField` class. Files can be managed through a `FileField` or an `UploadField`. The `[api:FileField]` class provides a simple HTML input with a type of "file", whereas an `[api:UploadField]` provides a much more feature-rich field (including AJAX-based uploads, previews, relationship management and file data management). See [`Reference - UploadField`](/developer_guides/forms/field_types/uploadfield) for more information about how to use the `UploadField` class.

View File

@ -10,7 +10,7 @@ It uses the framework's knowledge about the model to provide sensible defaults,
of lines of code, while still providing a solid base for customization. of lines of code, while still providing a solid base for customization.
<div class="info" markdown="1"> <div class="info" markdown="1">
The interface is mainly powered by the [api:GridField] class ([documentation](../forms/fields/gridfield)), which can The interface is mainly powered by the [api:GridField] class ([documentation](../forms/field_types/gridfield)), which can
also be used in other areas of your application. also be used in other areas of your application.
</div> </div>
@ -146,7 +146,7 @@ class (see [SearchContext](../search/searchcontext) docs for details).
## Displaying Results ## Displaying Results
The results are shown in a tabular listing, powered by the [GridField](../forms/fields/gridfield), more specifically The results are shown in a tabular listing, powered by the [GridField](../forms/field_types/gridfield), more specifically
the [api:GridFieldDataColumns] component. This component looks for a [api:DataObject::$summary_fields] static on your the [api:GridFieldDataColumns] component. This component looks for a [api:DataObject::$summary_fields] static on your
model class, where you can add or remove columns. To change the title, use [api:DataObject::$field_labels]. model class, where you can add or remove columns. To change the title, use [api:DataObject::$field_labels].
@ -320,9 +320,9 @@ To customize the exported columns, create a new method called `getExportFields`
## Related Documentation ## Related Documentation
* [GridField](../forms/fields/gridfield) * [GridField](../forms/field_types/gridfield)
* [Permissions](../security/permissions) * [Permissions](../security/permissions)
* [SeachContext](../search/seachcontext) * [SearchContext](../search/searchcontext)
## API Documentation ## API Documentation

View File

@ -66,7 +66,7 @@ Layout manager will automatically apply algorithms to the children of `.cms-cont
For detailed discussion on available algorithms refer to For detailed discussion on available algorithms refer to
[jLayout algorithms](https://github.com/bramstein/jlayout#layout-algorithms). [jLayout algorithms](https://github.com/bramstein/jlayout#layout-algorithms).
Our [Howto: Extend the CMS Interface](../howto/extend-cms-interface) has a practical example on how to add a bottom Our [Howto: Extend the CMS Interface](how_tos/extend_cms_interface) has a practical example on how to add a bottom
panel to the CMS UI. panel to the CMS UI.
### Methods ### Methods
@ -115,6 +115,6 @@ The parameters are as follows:
## Related ## Related
* [Reference: CMS Architecture](../reference/cms-architecture) * [Reference: CMS Architecture](cms_architecture)
* [Reference: Preview](../reference/preview) * [Reference: Preview](preview)
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface) * [Howto: Extend the CMS Interface](how_tos/extend_cms_interface)

View File

@ -69,7 +69,7 @@ Note how the configuration happens in different entwine namespaces
}(jQuery)); }(jQuery));
Load the file in the CMS via setting adding 'mysite/javascript/MyLeftAndMain.Preview.js' Load the file in the CMS via setting adding 'mysite/javascript/MyLeftAndMain.Preview.js'
to the `LeftAndMain.extra_requirements_javascript` [configuration value](/topics/configuration) to the `LeftAndMain.extra_requirements_javascript` [configuration value](../configuration)
:::yml :::yml
LeftAndMain: LeftAndMain:
@ -79,7 +79,7 @@ to the `LeftAndMain.extra_requirements_javascript` [configuration value](/topics
In order to find out which configuration values are available, the source code In order to find out which configuration values are available, the source code
is your best reference at the moment - have a look in `framework/admin/javascript/LeftAndMain.Preview.js`. is your best reference at the moment - have a look in `framework/admin/javascript/LeftAndMain.Preview.js`.
To understand how layouts are handled in the CMS UI, have a look at the To understand how layouts are handled in the CMS UI, have a look at the
[CMS Architecture](/reference/cms-architecture) guide. [CMS Architecture](cms_architecture) guide.
## Enabling preview ## Enabling preview
@ -146,7 +146,7 @@ You can find out current size by calling:
## Preview modes ## Preview modes
Preview modes map to the modes supported by the _threeColumnCompressor_ layout Preview modes map to the modes supported by the _threeColumnCompressor_ layout
algorithm, see [layout reference](../reference/layout) for more details. You algorithm, see [layout reference](cms_layout) for more details. You
can change modes by calling: can change modes by calling:
```js ```js
@ -184,4 +184,4 @@ previewable content is loaded.
## Related ## Related
* [Reference: Layout](../reference/layout) * [Reference: Layout](cms_layout)

View File

@ -13,7 +13,7 @@ feel familiar to you. This is just a quick run down to get you started
with some special conventions. with some special conventions.
For a more practical-oriented approach to CMS customizations, refer to the For a more practical-oriented approach to CMS customizations, refer to the
[Howto: Extend the CMS Interface](../howto/extend-cms-interface) which builds [Howto: Extend the CMS Interface](../how_tos/extend_cms_interface) which builds
## Markup and Style Conventions ## Markup and Style Conventions
@ -47,7 +47,7 @@ As there's a whole lot of CSS driving the CMS, we have certain best practives ar
* Use jQuery UI's built-in styles where possible, e.g. `ui-widget` for a generic container, or `ui-state-highlight` * Use jQuery UI's built-in styles where possible, e.g. `ui-widget` for a generic container, or `ui-state-highlight`
to highlight a specific component. See the [jQuery UI Theming API](http://jqueryui.com/docs/Theming/API) for a full list. to highlight a specific component. See the [jQuery UI Theming API](http://jqueryui.com/docs/Theming/API) for a full list.
See our [system requirements](../installation/server-requirements) for a list of supported browsers. See our [system requirements](/getting_started/server_requirements) for a list of supported browsers.
## Templates and Controllers ## Templates and Controllers
@ -89,7 +89,7 @@ The various panels and UI components within them are loosely coupled to the layo
attribute. The layout is triggered on the top element and cascades into children, with a `redraw` method defined on attribute. The layout is triggered on the top element and cascades into children, with a `redraw` method defined on
each panel and UI component that needs to update itself as a result of layouting. each panel and UI component that needs to update itself as a result of layouting.
Refer to [Layout reference](../reference/layout) for further information. Refer to [Layout reference](cms_layout) for further information.
## Forms ## Forms
@ -148,7 +148,7 @@ correctly configured form.
[jQuery.entwine](https://github.com/hafriedlander/jquery.entwine) is a thirdparty library [jQuery.entwine](https://github.com/hafriedlander/jquery.entwine) is a thirdparty library
which allows us to attach behaviour to DOM elements in a flexible and structured mannger. which allows us to attach behaviour to DOM elements in a flexible and structured mannger.
It replaces the `behaviour.js` library used in previous versions of the CMS interface. It replaces the `behaviour.js` library used in previous versions of the CMS interface.
See [Topics: JavaScript](../topics/javascript) for more information on how to use it. See [JavaScript Development](javascript_development) for more information on how to use it.
In the CMS interface, all entwine rules should be placed in the "ss" entwine namespace. In the CMS interface, all entwine rules should be placed in the "ss" entwine namespace.
If you want to call methods defined within these rules outside of entwine logic, If you want to call methods defined within these rules outside of entwine logic,
you have to use this namespace, e.g. `$('.cms-menu').entwine('ss').collapse()`. you have to use this namespace, e.g. `$('.cms-menu').entwine('ss').collapse()`.
@ -222,7 +222,7 @@ In order for this to work, the CMS templates declare certain sections as "PJAX f
through a `data-pjax-fragment` attribute. These names correlate to specific through a `data-pjax-fragment` attribute. These names correlate to specific
rendering logic in the PHP controllers, through the `[api:PjaxResponseNegotiator]` class. rendering logic in the PHP controllers, through the `[api:PjaxResponseNegotiator]` class.
Through a custom `X-Pjax` HTTP header, the client can declare which view he's expecting, Through a custom `X-Pjax` HTTP header, the client can declare which view they're expecting,
through identifiers like `CurrentForm` or `Content` (see `[api:LeftAndMain->getResponseNegotiator()]`). through identifiers like `CurrentForm` or `Content` (see `[api:LeftAndMain->getResponseNegotiator()]`).
These identifiers are passed to `loadPanel()` via the `pjax` data option. These identifiers are passed to `loadPanel()` via the `pjax` data option.
The HTTP response is a JSON object literal, with template replacements keyed by their Pjax fragment. The HTTP response is a JSON object literal, with template replacements keyed by their Pjax fragment.
@ -433,7 +433,7 @@ The CMS tree for viewing hierarchical structures (mostly pages) is powered
by the [jstree](http://jstree.com) library. It is configured through by the [jstree](http://jstree.com) library. It is configured through
`framework/admin/javascript/LeftAndMain.Tree.js`, as well as some `framework/admin/javascript/LeftAndMain.Tree.js`, as well as some
HTML5 metadata generated on its container (see the `data-hints` attribute). HTML5 metadata generated on its container (see the `data-hints` attribute).
For more information, see the [Howto: Customize the CMS tree](../howto/customize-cms-tree). For more information, see the [Howto: Customize the CMS tree](../how_tos/customize_cms_tree).
Note that a similar tree logic is also used for the Note that a similar tree logic is also used for the
form fields to select one or more entries from those hierarchies form fields to select one or more entries from those hierarchies
@ -538,8 +538,8 @@ through the `PjaxResponseNegotiator` class (see above).
## Related ## Related
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface) * [Howto: Extend the CMS Interface](how_tos/extend_cms_interface)
* [Howto: Customize the CMS tree](../howto/customize-cms-tree) * [Howto: Customize the CMS tree](how_tos/customize_cms_tree)
* [Reference: ModelAdmin](../reference/modeladmin) * [ModelAdmin API](api:ModelAdmin)
* [Reference: Layout](../reference/layout) * [Reference: Layout](cms_layout)
* [Topics: Rich Text Editing](../topics/rich-text-editing) * [Rich Text Editing](/developer_guides/forms/field_types/htmleditorfield)

View File

@ -14,7 +14,7 @@ This how-to will walk you through creation of a "Clean-up" button with two appea
* active: "Clean-up now" green constructive button if the actions can be performed * active: "Clean-up now" green constructive button if the actions can be performed
* netural: "Cleaned" default button if the action does not need to be done * netural: "Cleaned" default button if the action does not need to be done
The controller code that goes with this example is listed in [Extend CMS Interface](../reference/extend-cms-interface). The controller code that goes with this example is listed in [Extend CMS Interface](extend_cms_interface).
## Backend support ## ## Backend support ##
@ -156,4 +156,4 @@ cases.
## Summary ## ## Summary ##
The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS. The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS.
These alternating buttons can be used to give user the advantage of visual feedback upon his actions. These alternating buttons can be used to give user the advantage of visual feedback upon their actions.

View File

@ -27,4 +27,4 @@ more complex fields like `GridField`, `UploadField`
or `DropdownField` with the chosen.js behaviour applied. or `DropdownField` with the chosen.js behaviour applied.
Note: For more advanced help text we recommend using Note: For more advanced help text we recommend using
[Custom form field templates](/topics/forms#custom-form-field-templates); [Custom form field templates](../form_templates);

View File

@ -7,10 +7,10 @@ SilverStripe will automatically create a new `[api:CMSMenuItem]` for it
The most popular extension of LeftAndMain is a `[api:ModelAdmin]` class, so The most popular extension of LeftAndMain is a `[api:ModelAdmin]` class, so
for a more detailed introduction to creating new `ModelAdmin` interfaces, read for a more detailed introduction to creating new `ModelAdmin` interfaces, read
the [ModelAdmin reference](../reference/modeladmin). the [ModelAdmin reference](../modeladmin).
In this document we'll take the `ProductAdmin` class used in the In this document we'll take the `ProductAdmin` class used in the
[ModelAdmin reference](../reference/modeladmin#setup) and so how we can change [ModelAdmin reference](../modeladmin#setup) and so how we can change
the menu behaviour by using the `$menu_title` and `$menu_icon` statics to the menu behaviour by using the `$menu_title` and `$menu_icon` statics to
provide a custom title and icon. provide a custom title and icon.
@ -43,7 +43,7 @@ In order to localize the menu title in different languages, use the
the i18n text collection. the i18n text collection.
For more information on language and translations, please refer to the For more information on language and translations, please refer to the
[i18n](../reference/ii8n) docs. [i18n](../../ii8n) docs.
## Adding an external link to the menu ## Adding an external link to the menu
@ -85,7 +85,7 @@ button configuration.
To have the link appear, make sure you add the extension to the `LeftAndMain` To have the link appear, make sure you add the extension to the `LeftAndMain`
class. For more information about configuring extensions see the class. For more information about configuring extensions see the
[DataExtension reference](../reference/dataextension). [extensions reference](../extending/extensions).
:::php :::php
LeftAndMain::add_extension('CustomLeftAndMain') LeftAndMain::add_extension('CustomLeftAndMain')
@ -93,4 +93,4 @@ class. For more information about configuring extensions see the
## Related ## Related
* [How to extend the CMS interface](extend-cms-interface) * [How to extend the CMS interface](extend_cms_interface)

View File

@ -62,7 +62,7 @@ or across page types with common characteristics.
} }
} }
Now you just need to enable the extension in your [configuration file](/topics/configuration). Now you just need to enable the extension in your [configuration file](../../configuration).
// mysite/_config/config.yml // mysite/_config/config.yml
LeftAndMain: LeftAndMain:

View File

@ -55,7 +55,7 @@ with the CMS interface. Paste the following content into a new file called
.bookmarked-link.first {margin-top: 1em;} .bookmarked-link.first {margin-top: 1em;}
Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requirements_css` Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requirements_css`
[configuration value](/topics/configuration). [configuration value](../../configuration).
:::yml :::yml
LeftAndMain: LeftAndMain:
@ -85,7 +85,7 @@ and insert the following code.
} }
} }
Enable the extension in your [configuration file](/topics/configuration) Enable the extension in your [configuration file](../../configuration)
:::yml :::yml
SiteTree: SiteTree:
@ -114,7 +114,7 @@ Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension
} }
} }
Enable the extension in your [configuration file](/topics/configuration) Enable the extension in your [configuration file](../../configuration)
:::yml :::yml
LeftAndMain: LeftAndMain:
@ -191,11 +191,11 @@ Empty tabs will be automatically removed from the `FieldList` to prevent clutter
</div> </div>
New actions will need associated controller handlers to work. You can use a New actions will need associated controller handlers to work. You can use a
`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller) `LeftAndMainExtension` to provide one. Refer to [Controller documentation](../../controllers)
for instructions on setting up handlers. for instructions on setting up handlers.
To make the actions more user-friendly you can also use alternating buttons as To make the actions more user-friendly you can also use alternating buttons as
detailed in the [CMS Alternating Button](../reference/cms-alternating-button) detailed in the [CMS Alternating Button](cms_alternating_button)
how-to. how-to.
## Summary ## Summary
@ -207,7 +207,7 @@ blocks and concepts for more complex extensions as well.
## Related ## Related
* [Reference: CMS Architecture](../reference/cms-architecture) * [Reference: CMS Architecture](../cms_architecture)
* [Reference: Layout](../reference/layout) * [Reference: Layout](../cms_layout)
* [Topics: Rich Text Editing](../topics/rich-text-editing) * [Rich Text Editing](/developer_guides/forms/field_types/htmleditorfield)
* [CMS Alternating Button](../howto/cms-alternating-button) * [CMS Alternating Button](cms_alternating_button)

View File

@ -64,14 +64,14 @@ a `tests/` folder, unless tests are executed.
The `[api:SS_TemplateManifest]` class builds a manifest of all templates present in a directory, The `[api:SS_TemplateManifest]` class builds a manifest of all templates present in a directory,
in both modules and themes. Templates in `tests/` folders are automatically excluded. in both modules and themes. Templates in `tests/` folders are automatically excluded.
The chapter on [template inheritance](../templates/template-inheritance) provides more details The chapter on [template inheritance](../templates/template_inheritance) provides more details
on its operation. on its operation.
## Config Manifest ## Config Manifest
The `[api:SS_ConfigManifest]` loads builds a manifest of configuration items, The `[api:SS_ConfigManifest]` loads builds a manifest of configuration items,
for both PHP and YAML. It also takes care of ordering and merging configuration fragments. for both PHP and YAML. It also takes care of ordering and merging configuration fragments.
The chapter on [configuration](/topics/configuration) has more details. The chapter on [configuration](../configuration) has more details.
## Flushing ## Flushing

View File

@ -103,14 +103,14 @@ before handing control off to SilverStripe's own `main.php`.
## Routing and Request Handling ## Routing and Request Handling
The `main.php` script relies on `[api:Director]` to work out which [controller](../controllers) should handle this request. It parses the URL, matching it to one of a number of patterns, The `main.php` script relies on `[api:Director]` to work out which [controller](../controllers/) should handle this request. It parses the URL, matching it to one of a number of patterns,
and determines the controller, action and any argument to be used ([Routing](../controllers/routing)). and determines the controller, action and any argument to be used ([Routing](../controllers/routing)).
* Creates a `[api:SS_HTTPRequest]` object containing all request and environment information * Creates a `[api:SS_HTTPRequest]` object containing all request and environment information
* The [session](../cookies_and_sessions/sessions) holds an abstraction of PHP session * The [session](../cookies_and_sessions/sessions) holds an abstraction of PHP session
* Instantiates a [controller](../Controllers) object * Instantiates a [controller](../controllers/) object
* The `[api:Injector]` is first referenced, and asks the registered * The `[api:Injector]` is first referenced, and asks the registered
[RequestFilter](../controller/request_filters) [RequestFilter](../controllers/requestfilters)
to pre-process the request object (see below) to pre-process the request object (see below)
* The `Controller` executes the actual business logic and populates an `[api:SS_HTTPResponse]` * The `Controller` executes the actual business logic and populates an `[api:SS_HTTPResponse]`
* The `Controller` can optionally hand off control to further nested controllers * The `Controller` can optionally hand off control to further nested controllers
@ -125,7 +125,7 @@ The framework provides the ability to hook into the request both before and
after it is handled to allow binding custom logic. This can be used after it is handled to allow binding custom logic. This can be used
to transform or filter request data, instanciate helpers, execute global logic, to transform or filter request data, instanciate helpers, execute global logic,
or even short-circuit execution (e.g. to enforce custom authentication schemes). or even short-circuit execution (e.g. to enforce custom authentication schemes).
The ["Request Filters" documentation](../controller/request_filters) shows you how. The ["Request Filters" documentation](../controllers/requestfilters) shows you how.
## Flushing Manifests ## Flushing Manifests

View File

@ -35,14 +35,14 @@ Thanks to Rutger de Jong for reporting.
Severity: Moderate Severity: Moderate
Autologin tokens (remember me and reset password) are stored in the database as a plain text. Autologin tokens (remember me and reset password) are stored in the database as a plain text.
If attacker obtained the database he would be able to gain access to accounts that have requested a password change, or have "remember me" enabled. If attacker obtained the database they would be able to gain access to accounts that have requested a password change, or have "remember me" enabled.
### Security: Privilege escalation through profile form ### Security: Privilege escalation through profile form
Severity: Moderate Severity: Moderate
A logged-in CMS user can gain additional privileges by crafting a request A logged-in CMS user can gain additional privileges by crafting a request
to his/her profile form which resets another user's password. to their profile form which resets another user's password.
This method can potentially be used by CSRF attacks as well. This method can potentially be used by CSRF attacks as well.
Thanks to Nathaniel Carew (Sense of Security) for reporting. Thanks to Nathaniel Carew (Sense of Security) for reporting.

View File

@ -0,0 +1,71 @@
# 3.1.11
# Overview
This release resolves a high level security issue in the SiteTree class, as well as
the CMS controller classes which act on these objects during creation.
This release also resolves an issue affecting GridField on sites running in
an environment with Suhosin enabled.
## Upgrading
### SiteTree::canCreate Permissions
Any user code which overrides the `SiteTree::canCreate` method should be investigated to
ensure it continues to work correctly. In particular, a second parameter may now be passed
to this method in order to determine if page creation is allowed in any given context, whether
it be at the root level, or as a child of a parent page.
The creation of pages at the root level is now corrected to follow the rules specified
by the SiteConfig, which in turn has been updated to ensure only valid CMS users are
granted this permission (when applicable).
The creation of pages beneath parent pages will now inherit from the ability to edit
this parent page.
User code which is not updated, but relies on the old implementation of SiteTree::canCreate will
now assume creation at the top level.
For example see the below code as an example
E.g.
:::php
<?php
class SingletonPage extends Page {
public function canCreate($member) {
if(static::get()->count()) return false;
$context = func_num_args() > 1 ? func_get_arg(1) : array();
return parent::canCreate($member, $context);
}
}
For more information on the reason for this change please see the security announcement below.
## Security
* 2015-03-11 [3df41e1](https://github.com/silverstripe/silverstripe-cms/commit/3df41e1) Fix SiteTree / SiteConfig permissions (Damian Mooyman) - See announcement [ss-2015-008](http://www.silverstripe.org/software/download/security-releases/ss-2015-008-sitetree-creation-permission-vulnerability)
### Bugfixes
* 2015-03-09 [1770fab](https://github.com/silverstripe/sapphire/commit/1770fab) Fix gridfield generating invalid session keys (Damian Mooyman)
* 2015-03-05 [87adc44](https://github.com/silverstripe/sapphire/commit/87adc44) Fix serialised stateid exceeding request length (Damian Mooyman)
* 2015-03-04 [eb35f26](https://github.com/silverstripe/sapphire/commit/eb35f26) Corrected padding on non-sortable columns. (Sam Minnee)
* 2015-03-03 [6e0afd5](https://github.com/silverstripe/sapphire/commit/6e0afd5) Prevent unnecessary call to config system which doesn't exist yet (micmania1)
* 2015-03-03 [4709b90](https://github.com/silverstripe/sapphire/commit/4709b90) UploadField description alignment (Loz Calver)
* 2015-03-02 [f234301](https://github.com/silverstripe/sapphire/commit/f234301) DataQuery::applyRelation using incorrect foreign key (fixes #3954) (Loz Calver)
* 2015-03-02 [f9d493d](https://github.com/silverstripe/sapphire/commit/f9d493d) Fixes case insensitive search for postgres databases (Jean-Fabien Barrois)
* 2015-02-27 [4c5a07e](https://github.com/silverstripe/sapphire/commit/4c5a07e) Updated docs (Michael Strong)
* 2015-02-25 [3a7e24a](https://github.com/silverstripe/sapphire/commit/3a7e24a) Unable to access a list of all many_many_extraFields (Loz Calver)
* 2015-02-13 [998c055](https://github.com/silverstripe/sapphire/commit/998c055) Misleading error message in SSViewer (Loz Calver)
* 2015-02-10 [bbe2799](https://github.com/silverstripe/sapphire/commit/bbe2799) Use correct query when searching for items managed by a tree dropdown field #3173 (Jean-Fabien Barrois)
* 2015-01-13 [ab24ed3](https://github.com/silverstripe/sapphire/commit/ab24ed3) Use i18n_plural_name() instead of plural_name() (Elvinas L.)
* 2014-11-17 [a142ffd](https://github.com/silverstripe/silverstripe-cms/commit/a142ffd) VirtualPages use correct casting for 'virtual' database fields (Loz Calver)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.11)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.11)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.11)

View File

@ -0,0 +1,43 @@
# 3.1.12
# Overview
This security release resolves some XSS and an XML vulnerability in the Framework.
## Upgrading
If your code relies on `Convert::xml2array` there are some important things to consider with regards to
certain vulnerabilities. In this release additional options have been added to this method to assist
users in guarding against these risks, although each option has been turned off by default.
Please refer to http://phpsecurity.readthedocs.org/en/latest/Injection-Attacks.html#xml-external-entity-injection
on details of some of the specific reasons behind the need for these changes and how you can guard
against them in your code.
Specifically this method has these two new parameters:
* The `$disableDoctypes` parameter has been added to disallow parsing of XML content containing
a <!DOCTYPE > header, which may potentially contain unguarded or recursive entity definitions.
* The `$disableExternals` parameter allows XML parsing to ignore any externally referenced
dependency within the file, ensuring that injected XML is unable to invoke data from potentially
hazardous sources.
## Security
* 2015-03-20 [ee9bddb](https://github.com/silverstripe/sapphire/commit/ee9bddb) Fix SS-2015-010 (Damian Mooyman) - See announcement [ss-2015-010](http://www.silverstripe.org/software/download/security-releases/ss-2015-010-xss-in-directorforce-redirect)
* 2015-03-20 [7f983c2](https://github.com/silverstripe/sapphire/commit/7f983c2) Fix SS-2014-017 (Damian Mooyman) - See announcement [ss-2014-017](http://www.silverstripe.org/software/download/security-releases/ss-2014-017-xml-quadratic-blowup-attack)
* 2015-03-20 [604c328](https://github.com/silverstripe/sapphire/commit/604c328) Fixed XSS vulnerability relating to rewrite_hash (Christopher Pitt) - See announcements [ss-2014-015](http://www.silverstripe.org/software/download/security-releases/ss-2014-015-ie-requests-not-properly-behaving-with-rewritehashlinks), [ss-2015-009](http://www.silverstripe.org/software/download/security-releases/ss-2015-009-xss-in-rewritten-hash-links)
### Bugfixes
* 2015-03-18 [b34c236](https://github.com/silverstripe/sapphire/commit/b34c236) Fix joins on tables containing "select" being mistaken for sub-selects Fix PHPDoc on SQLQuery::addFrom and SQLQuery::setFrom Fixes #3965 (Damian Mooyman)
* 2015-03-11 [a61c08d](https://github.com/silverstripe/sapphire/commit/a61c08d) Security::$default_message_set Config value unusable (Loz Calver)
* 2015-03-10 [9651889](https://github.com/silverstripe/sapphire/commit/9651889) Fix yaml generation to conform to version 1.1, accepted by transifex (Damian Mooyman)
* 2015-02-25 [f5f41b2](https://github.com/silverstripe/sapphire/commit/f5f41b2) Ensuring custom CMS validator uses Object-&gt;hasMethod() to respect extension decorator pattern. (Patrick Nelson)
* 2015-01-13 [9da7e90](https://github.com/silverstripe/silverstripe-cms/commit/9da7e90) . Missing translation entity (Elvinas L.)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.12)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.12)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.12)

View File

@ -607,9 +607,7 @@ when using deprecated functionality (through the new `Deprecation` class).
* 2012-04-12 [e9dc610](https://github.com/silverstripe/sapphire/commit/e9dc610) API-CHANGE: new GridFieldFooter component (Julian Seidenberg) * 2012-04-12 [e9dc610](https://github.com/silverstripe/sapphire/commit/e9dc610) API-CHANGE: new GridFieldFooter component (Julian Seidenberg)
* 2012-04-10 [9888f98](https://github.com/silverstripe/silverstripe-cms/commit/9888f98) ENHANCMENT: Link pages in reports to cms edit (Andrew O'Neil) * 2012-04-10 [9888f98](https://github.com/silverstripe/silverstripe-cms/commit/9888f98) ENHANCMENT: Link pages in reports to cms edit (Andrew O'Neil)
* 2012-04-10 [1516934](https://github.com/silverstripe/silverstripe-cms/commit/1516934) Revert "BUGFIX: SSF-168 fixing rendering issue in Chrome, which displays extra control at the bottom of the window in a report that is of a certain length" (Julian Seidenberg) * 2012-04-10 [1516934](https://github.com/silverstripe/silverstripe-cms/commit/1516934) Revert "BUGFIX: SSF-168 fixing rendering issue in Chrome, which displays extra control at the bottom of the window in a report that is of a certain length" (Julian Seidenberg)
* 2012-04-06 [797d526](https://github.com/silverstripe/sapphire/commit/797d526) For png images with transparency, the imagesaveaplpha() needs to be set to true on the source image in order for * 2012-04-06 [797d526](https://github.com/silverstripe/sapphire/commit/797d526) For png images with transparency, the imagesaveaplpha() needs to be set to true on the source image in order for the alpha to be preserved when using the modifier methods. (jmwohl)
he alpha to be preserved when using the modifier methods. (jmwohl)
* 2012-04-05 [e76913f](https://github.com/silverstripe/sapphire/commit/e76913f) API-CHANGE: adding a default option of null to the $args argument in DataExtension::add_to_class. The args argument isn't used anywhere in the class and adding a third argument to every call to this function is tedious. (Julian Seidenberg) * 2012-04-05 [e76913f](https://github.com/silverstripe/sapphire/commit/e76913f) API-CHANGE: adding a default option of null to the $args argument in DataExtension::add_to_class. The args argument isn't used anywhere in the class and adding a third argument to every call to this function is tedious. (Julian Seidenberg)
* 2012-04-04 [5826b36](https://github.com/silverstripe/sapphire/commit/5826b36) ENHACEMENT: SSF-168 updated the font for titles on print stylesheets (Felipe Skroski) * 2012-04-04 [5826b36](https://github.com/silverstripe/sapphire/commit/5826b36) ENHACEMENT: SSF-168 updated the font for titles on print stylesheets (Felipe Skroski)
* 2012-04-04 [349a04d](https://github.com/silverstripe/silverstripe-cms/commit/349a04d) API-CHANGE: SSF-168 changing the API/code-conventions for excluding specific reports. get_reports method now returns an ArrayList instead of an array of SS_Reports. (Julian Seidenberg) * 2012-04-04 [349a04d](https://github.com/silverstripe/silverstripe-cms/commit/349a04d) API-CHANGE: SSF-168 changing the API/code-conventions for excluding specific reports. get_reports method now returns an ArrayList instead of an array of SS_Reports. (Julian Seidenberg)

View File

@ -22,7 +22,7 @@ Thanks to Rutger de Jong for reporting.
Severity: Moderate Severity: Moderate
A logged-in CMS user can gain additional privileges by crafting a request A logged-in CMS user can gain additional privileges by crafting a request
to his/her profile form which resets another user's password. to their profile form which resets another user's password.
This method can potentially be used by CSRF attacks as well. This method can potentially be used by CSRF attacks as well.
Thanks to Nathaniel Carew (Sense of Security) for reporting. Thanks to Nathaniel Carew (Sense of Security) for reporting.

View File

@ -25,7 +25,7 @@ API changes related to the below security patch:
Severity: Moderate Severity: Moderate
Autologin tokens (remember me and reset password) are stored in the database as a plain text. Autologin tokens (remember me and reset password) are stored in the database as a plain text.
If attacker obtained the database he would be able to gain access to accounts that have requested a password change, or have "remember me" enabled. If attacker obtained the database they would be able to gain access to accounts that have requested a password change, or have "remember me" enabled.
## Changelog ## Changelog

View File

@ -0,0 +1,28 @@
# 3.1.11-rc1
# Overview
This release contains fixes to resolve issues in 3.1.10 running in a Suhosin environment.
Failure to update may result in unpredictable behaviour of the GridField control in affected sites.
### Bugfixes
* 2015-03-09 [1770fab](https://github.com/silverstripe/sapphire/commit/1770fab) Fix gridfield generating invalid session keys (Damian Mooyman)
* 2015-03-05 [87adc44](https://github.com/silverstripe/sapphire/commit/87adc44) Fix serialised stateid exceeding request length (Damian Mooyman)
* 2015-03-04 [eb35f26](https://github.com/silverstripe/sapphire/commit/eb35f26) Corrected padding on non-sortable columns. (Sam Minnee)
* 2015-03-03 [6e0afd5](https://github.com/silverstripe/sapphire/commit/6e0afd5) Prevent unnecessary call to config system which doesn't exist yet (micmania1)
* 2015-03-03 [4709b90](https://github.com/silverstripe/sapphire/commit/4709b90) UploadField description alignment (Loz Calver)
* 2015-03-02 [f234301](https://github.com/silverstripe/sapphire/commit/f234301) DataQuery::applyRelation using incorrect foreign key (fixes #3954) (Loz Calver)
* 2015-03-02 [f9d493d](https://github.com/silverstripe/sapphire/commit/f9d493d) Fixes case insensitive search for postgres databases (Jean-Fabien Barrois)
* 2015-02-27 [4c5a07e](https://github.com/silverstripe/sapphire/commit/4c5a07e) Updated docs (Michael Strong)
* 2015-02-25 [3a7e24a](https://github.com/silverstripe/sapphire/commit/3a7e24a) Unable to access a list of all many_many_extraFields (Loz Calver)
* 2015-02-13 [998c055](https://github.com/silverstripe/sapphire/commit/998c055) Misleading error message in SSViewer (Loz Calver)
* 2015-02-10 [bbe2799](https://github.com/silverstripe/sapphire/commit/bbe2799) Use correct query when searching for items managed by a tree dropdown field #3173 (Jean-Fabien Barrois)
* 2015-01-13 [ab24ed3](https://github.com/silverstripe/sapphire/commit/ab24ed3) . Use i18n_plural_name() instead of plural_name() (Elvinas L.)
* 2014-11-17 [a142ffd](https://github.com/silverstripe/silverstripe-cms/commit/a142ffd) VirtualPages use correct casting for 'virtual' database fields (Loz Calver)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.11-rc1)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.11-rc1)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.11-rc1)

View File

@ -6,7 +6,7 @@ The SilverStripe core modules (`framework` and `cms`), as well as some of the mo
git version control. SilverStripe hosts its modules on [github.com/silverstripe](http://github.com/silverstripe) and [github.com/silverstripe-labs](http://github.com/silverstripe-labs). After [installing git](http://help.github.com/git-installation-redirect) and creating a [free github.com account](https://github.com/signup/free), you can "fork" a module, git version control. SilverStripe hosts its modules on [github.com/silverstripe](http://github.com/silverstripe) and [github.com/silverstripe-labs](http://github.com/silverstripe-labs). After [installing git](http://help.github.com/git-installation-redirect) and creating a [free github.com account](https://github.com/signup/free), you can "fork" a module,
which creates a copy that you can commit to (see github's [guide to "forking"](http://help.github.com/forking/)). which creates a copy that you can commit to (see github's [guide to "forking"](http://help.github.com/forking/)).
For other modules, our [module list on silverstripe.org](http://silverstripe.org/modules) lists the repository locations, typically using a version control system like "git" or "[subversion](subversion)". For other modules, our [add-ons site](http://addons.silverstripe.org/add-ons) lists the repository locations, typically using the version control system like "git".
<div class="hint" markdown="1"> <div class="hint" markdown="1">
Note: By supplying code to the SilverStripe core team in patches, tickets and pull requests, you agree to assign copyright of that code to SilverStripe Limited, on the condition that SilverStripe Limited releases that code under the BSD license. Note: By supplying code to the SilverStripe core team in patches, tickets and pull requests, you agree to assign copyright of that code to SilverStripe Limited, on the condition that SilverStripe Limited releases that code under the BSD license.
@ -18,7 +18,7 @@ We ask for this so that the ownership in the license is clear and unambiguous, a
_**NOTE:** The commands on this page assume that you are branching from `4.0`, at the time of writing this is the pre-release branch._ _**NOTE:** The commands on this page assume that you are branching from `4.0`, at the time of writing this is the pre-release branch._
1. Install the project through composer. The process is described in detail in "[Installation through Composer](../../installation/composer#contributing)". 1. Install the project through composer. The process is described in detail in "[Installation through Composer](../getting_started/composer#contributing)".
composer create-project --keep-vcs --dev silverstripe/installer ./my/website/folder 4.0.x-dev composer create-project --keep-vcs --dev silverstripe/installer ./my/website/folder 4.0.x-dev
@ -65,6 +65,42 @@ _**NOTE:** The commands on this page assume that you are branching from `4.0`, a
The core team is then responsible for reviewing patches and deciding if they will make it into core. If The core team is then responsible for reviewing patches and deciding if they will make it into core. If
there are any problems they will follow up with you, so please ensure they have a way to contact you! there are any problems they will follow up with you, so please ensure they have a way to contact you!
### The Pull Request Process
Once your pull request is issued, it's not the end of the road. A [core committer](/contributing/core_committers/) will most likely have some questions for you and may ask you to make some changes depending on discussions you have.
If you've been naughty and not adhered to the coding conventions, expect a few requests to make changes so your code is in-line.
If your change is particularly significant, it may be referred to the [mailing list](https://groups.google.com/forum/#!forum/silverstripe-dev) for further community discussion.
A core committer will also "label" your PR using the labels defined in GitHub, these are to correctly classify and help find your work at a later date.
#### GitHub Labels
The current GitHub labels are grouped into 5 sections:
1. Changes - These are designed to signal what kind of change they are and how they fit into the [Semantic Versioning](http://semver.org/) schema
2. Impact - What impact does this bug/issue/fix have, does it break a feature completely, is it just a side effect or is it trivial and not a bit problem (but a bit annoying)
3. Effort - How much effort is required to fix this issue?
4. Type - What aspect of the system the PR/issue covers
5. Feedback - Are we waiting on feedback, if so who from? Typically used for issues that are likely to take a while to have feedback given
| Label | Purpose |
| ----- | ------- |
| change/major | A change for the next major release (eg: 4.0) |
| change/minor | A change for the next minor release (eg: 3.x) |
| change/patch | A change for the next patch release (eg: 3.1.x) |
| impact/critical | Broken functionality for which no work around can be produced |
| impact/high | Broken functionality but can be mitigated by other non-core code changes |
| impact/medium | Unexpected behaviour but does not break functionality |
| impact/low | A nuisance but doesn't break any functionality (typos, etc) |
| effort/easy | Someone with limited SilverStripe experience could resolve |
| effort/medium | Someone with a good understanding of SilverStripe could resolve |
| effort/hard | Only an expert with SilverStripe could resolve |
| type/docs | A docs change |
| type/frontend | A change to front-end (CSS, HTML, etc) |
| feedback-required/core-team | Core team members need to give an in-depth consideration |
| feedback-required/author | This issue is awaiting feedback from the original author of the PR |
### Workflow Diagram ### ### Workflow Diagram ###
![Workflow diagram](http://www.silverstripe.org/assets/doc-silverstripe-org/collaboration-on-github.png) ![Workflow diagram](http://www.silverstripe.org/assets/doc-silverstripe-org/collaboration-on-github.png)
@ -75,7 +111,7 @@ If you aren't familiar with git and GitHub, try reading the ["GitHub bootcamp do
We also found the [free online git book](http://git-scm.com/book/) and the [git crash course](http://gitref.org/) useful. We also found the [free online git book](http://git-scm.com/book/) and the [git crash course](http://gitref.org/) useful.
If you're familiar with it, here's the short version of what you need to know. Once you fork and download the code: If you're familiar with it, here's the short version of what you need to know. Once you fork and download the code:
* **Don't develop on the master branch.** Always create a development branch specific to "the issue" you're working on (mostly on our [bugtracker](/misc/contributing/issues)). Name it by issue number and description. For example, if you're working on Issue #100, a `DataObject::get_one()` bugfix, your development branch should be called 100-dataobject-get-one. If you decide to work on another issue mid-stream, create a new branch for that issue--don't work on both in one branch. * **Don't develop on the master branch.** Always create a development branch specific to "the issue" you're working on (on our [GitHub repository's issues](https://github.com/silverstripe/silverstripe-framework/issues)). Name it by issue number and description. For example, if you're working on Issue #100, a `DataObject::get_one()` bugfix, your development branch should be called 100-dataobject-get-one. If you decide to work on another issue mid-stream, create a new branch for that issue--don't work on both in one branch.
* **Do not merge the upstream master** with your development branch; *rebase* your branch on top of the upstream master. * **Do not merge the upstream master** with your development branch; *rebase* your branch on top of the upstream master.
@ -86,7 +122,7 @@ If you're familiar with it, here's the short version of what you need to know. O
* **Choose the correct branch**: Assume the current release is 3.0.3, and 3.1.0 is in beta state. * **Choose the correct branch**: Assume the current release is 3.0.3, and 3.1.0 is in beta state.
Most pull requests should go against the `3.1.x-dev` *pre-release branch*, only critical bugfixes Most pull requests should go against the `3.1.x-dev` *pre-release branch*, only critical bugfixes
against the `3.0.x-dev` *release branch*. If you're changing an API or introducing a major feature, against the `3.0.x-dev` *release branch*. If you're changing an API or introducing a major feature,
the pull request should go against `master` (read more about our [release process](/misc/release-process)). Branches are periodically merged "upwards" (3.0 into 3.1, 3.1 into master). the pull request should go against `master` (read more about our [release process](release_process)). Branches are periodically merged "upwards" (3.0 into 3.1, 3.1 into master).
### Editing files directly on GitHub.com ### Editing files directly on GitHub.com
@ -96,11 +132,11 @@ After you have edited the file, GitHub will offer to create a pull request for y
## Check List ## Check List
* Adhere to our [coding conventions](/misc/coding-conventions) * Adhere to our [coding conventions](/getting_started/coding_conventions)
* If your patch is extensive, discuss it first on the [silverstripe-dev google group](https://groups.google.com/group/silverstripe-dev) (ideally before doing any serious coding) * If your patch is extensive, discuss it first on the [silverstripe-dev google group](https://groups.google.com/group/silverstripe-dev) (ideally before doing any serious coding)
* When working on existing tickets, provide status updates through ticket comments * When working on existing tickets, provide status updates through ticket comments
* Check your patches against the "master" branch, as well as the latest release branch * Check your patches against the "master" branch, as well as the latest release branch
* Write [unit tests](/topics/testing) * Write [unit tests](../developer_guides/testing/unit_testing)
* Write [Behat integration tests](https://github.com/silverstripe-labs/silverstripe-behat-extension) for any interface changes * Write [Behat integration tests](https://github.com/silverstripe-labs/silverstripe-behat-extension) for any interface changes
* Describe specifics on how to test the effects of the patch * Describe specifics on how to test the effects of the patch
* It's better to submit multiple patches with separate bits of functionality than a big patch containing lots of * It's better to submit multiple patches with separate bits of functionality than a big patch containing lots of
@ -110,7 +146,7 @@ changes
[API documentation](http://api.silverstripe.org/3.1/) for good examples. [API documentation](http://api.silverstripe.org/3.1/) for good examples.
* Check and update documentation on [doc.silverstripe.org](http://doc.silverstripe.org). Check for any references to functionality deprecated or extended through your patch. Documentation changes should be included in the patch. * Check and update documentation on [doc.silverstripe.org](http://doc.silverstripe.org). Check for any references to functionality deprecated or extended through your patch. Documentation changes should be included in the patch.
* If you get stuck, please post to the [forum](http://silverstripe.org/forum) or for deeper core problems, to the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev) * If you get stuck, please post to the [forum](http://silverstripe.org/forum) or for deeper core problems, to the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
* When working with the CMS, please read the ["CMS Architecture Guide"](/reference/cms-architecture) first * When working with the CMS, please read the ["CMS Architecture Guide"](cms_architecture) first
## Commit Messages ## Commit Messages

View File

@ -9,7 +9,7 @@ only have time for a partial translation or quick review work - our system accom
same language. same language.
The content for UI elements (button labels, field titles) and instruction texts shown in the CMS and elsewhere is The content for UI elements (button labels, field titles) and instruction texts shown in the CMS and elsewhere is
stored in the PHP code for a module (see [i18n](/topics/i18n)). All content can be extracted as a "language file", and stored in the PHP code for a module (see [i18n](../developer_guides/i18n)). All content can be extracted as a "language file", and
uploaded to an online translation editor interface. SilverStripe is already translated in over 60 languages, and we're uploaded to an online translation editor interface. SilverStripe is already translated in over 60 languages, and we're
relying on native speakers to keep these up to date, and of course add new languages. relying on native speakers to keep these up to date, and of course add new languages.
@ -83,13 +83,13 @@ dropdown which automatically includes all found translations (based on the files
### I've found a piece of untranslatable text ### I've found a piece of untranslatable text
It is entirely possible that we missed certain strings in preparing Silverstripe for translation-support. If you're It is entirely possible that we missed certain strings in preparing Silverstripe for translation-support. If you're
technically minded, please read [i18n](/topics/i18n) on how to make it translatable. Otherwise just post your findings technically minded, please read [i18n](../developer_guides/i18n) on how to make it translatable. Otherwise just post your findings
to the forum. to the forum.
### How do I add my own module? ### How do I add my own module?
Once you've built a translation-enabled module, you can run the "textcollector" on your local installation for this Once you've built a translation-enabled module, you can run the "textcollector" on your local installation for this
specific module (see [i18n](/topics/i18n)). This should find all calls to `_t()` in php and template files, and generate specific module (see [i18n](../developer_guides/i18n)). This should find all calls to `_t()` in php and template files, and generate
a new lang file with the default locale (path: <mymodule>/lang/en.yml). Upload this file to the online translation a new lang file with the default locale (path: <mymodule>/lang/en.yml). Upload this file to the online translation
tool, and wait for your translators to do their magic! tool, and wait for your translators to do their magic!
@ -119,7 +119,7 @@ translators.
### I'm seeing lots of duplicated translations, what should I do? ### I'm seeing lots of duplicated translations, what should I do?
For now, please translate all duplications - sometimes they might be intentional, but mostly the developer just didn't For now, please translate all duplications - sometimes they might be intentional, but mostly the developer just didn't
know his phrase was already translated. Please contact us about any duplicates that might be worth merging. know their phrase was already translated. Please contact us about any duplicates that might be worth merging.
### What happened to translate.silverstripe.org? ### What happened to translate.silverstripe.org?
@ -128,7 +128,7 @@ This was a custom-built online translation tool serving us well for a couple of
were migrated. Unfortunately, the ownership of individual translations couldn't be migrated. were migrated. Unfortunately, the ownership of individual translations couldn't be migrated.
As the new tool doesn't support the PHP format used in SilverStripe 2.x, this means that we no longer have a working As the new tool doesn't support the PHP format used in SilverStripe 2.x, this means that we no longer have a working
translation tool for PHP files. Please edit the PHP files directly and [send us pull requests](/misc/contributing). translation tool for PHP files. Please edit the PHP files directly and [send us pull requests](/contributing).
This also applies for any modules staying compatible with SilverStripe 2.x. This also applies for any modules staying compatible with SilverStripe 2.x.
@ -140,7 +140,7 @@ board if you have specific comments on a translation.
## Related ## Related
* [i18n](/developer_guids/i18n): Developer-level documentation of Silverstripe's i18n capabilities * [i18n](/developer_guides/i18n): Developer-level documentation of Silverstripe's i18n capabilities
* [translation-process](translation-process): Information about managing translations for the core team and/or module maintainers. * [Translation Process](translation_process): Information about managing translations for the core team and/or module maintainers.
* [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations * [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations
* ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin * ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin

View File

@ -4,14 +4,14 @@ summary: Implement SilverStripe's internationalization system in your own module
# Implementing Internationalization # Implementing Internationalization
To find out about how to assist with translating SilverStripe from a users point of view, see the To find out about how to assist with translating SilverStripe from a users point of view, see the
[Contributing Translations page](translation). [Contributing Translations page](/contributing/translations).
## Set up your own module for localization ## Set up your own module for localization
### Collecting translatable text ### Collecting translatable text
As a first step, you can automatically collect all translatable text in your module through the `i18nTextCollector` As a first step, you can automatically collect all translatable text in your module through the `i18nTextCollector`
task. See [i18n](/topics/i18n#collecting-text) for more details. task. See [i18n](../developer_guides/i18n#collecting-text) for more details.
### Import master files ### Import master files
@ -127,7 +127,7 @@ files back into the JS files SilverStripe can actually read. This requires an in
# Related # Related
* [i18n](/topics/i18n): Developer-level documentation of Silverstripe's i18n capabilities * [i18n](/developer_guides/i18n/): Developer-level documentation of Silverstripe's i18n capabilities
* [contributing/translation](contributing/translation): Information for translators looking to contribute translations of the SilverStripe UI. * [Contributing Translations](/contributing/translations): Information for translators looking to contribute translations of the SilverStripe UI.
* [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations * [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations
* ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin * ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin

View File

@ -24,7 +24,7 @@ With great power comes great responsibility, so we have agreed on certain expect
* Treat issues according to our [issue guidelines](issues_and_bugs) * Treat issues according to our [issue guidelines](issues_and_bugs)
* Don't commit directly to core, raise pull requests instead (except trivial fixes) * Don't commit directly to core, raise pull requests instead (except trivial fixes)
* Only merge code you have tested and fully understand. If in doubt, ask for a second opinion. * Only merge code you have tested and fully understand. If in doubt, ask for a second opinion.
* Ensure contributions have appropriate [test coverage](/topics/testing), are documented, and pass our [coding conventions](/getting_started/coding-conventions) * Ensure contributions have appropriate [test coverage](../developer_guides/testing), are documented, and pass our [coding conventions](/getting_started/coding_conventions)
* Keep the codebase "releasable" at all times (check our [release process](release_process)) * Keep the codebase "releasable" at all times (check our [release process](release_process))
* API changes and non-trivial features should not be merged into release branches. * API changes and non-trivial features should not be merged into release branches.
* API changes on master should not be merged until they have the buy-in of at least two core committers (or better, through the [core mailing list](https://groups.google.com/forum/#!forum/silverstripe-dev)) * API changes on master should not be merged until they have the buy-in of at least two core committers (or better, through the [core mailing list](https://groups.google.com/forum/#!forum/silverstripe-dev))

View File

@ -830,7 +830,7 @@ class File extends DataObject {
'js' => _t('File.JsType', 'Javascript file'), 'js' => _t('File.JsType', 'Javascript file'),
'css' => _t('File.CssType', 'CSS file'), 'css' => _t('File.CssType', 'CSS file'),
'html' => _t('File.HtmlType', 'HTML file'), 'html' => _t('File.HtmlType', 'HTML file'),
'htm' => _t('File.HtlType', 'HTML file') 'htm' => _t('File.HtmlType', 'HTML file')
); );
$ext = $this->getExtension(); $ext = $this->getExtension();

View File

@ -43,14 +43,14 @@ class ImagickBackend extends Imagick implements Image_Backend {
/** /**
* set_default_quality * set_default_quality
* *
* @deprecated 3.2 Use the "IMagickBackend.default_quality" config setting instead * @deprecated 3.2 Use the "ImagickBackend.default_quality" config setting instead
* @param int $quality * @param int $quality
* @return void * @return void
*/ */
public static function set_default_quality($quality) { public static function set_default_quality($quality) {
Deprecation::notice('3.2', 'Use the "IMagickBackend.default_quality" config setting instead'); Deprecation::notice('3.2', 'Use the "ImagickBackend.default_quality" config setting instead');
if(is_numeric($quality) && (int) $quality >= 0 && (int) $quality <= 100) { if(is_numeric($quality) && (int) $quality >= 0 && (int) $quality <= 100) {
config::inst()->update('IMagickBackend', 'default_quality', (int) $quality); Config::inst()->update('ImagickBackend', 'default_quality', (int) $quality);
} }
} }

View File

@ -305,6 +305,16 @@ class Upload extends Controller {
*/ */
class Upload_Validator { class Upload_Validator {
/**
* Contains a list of the max file sizes shared by
* all upload fields. This is then duplicated into the
* "allowedMaxFileSize" instance property on construct.
*
* @config
* @var array
*/
private static $default_max_file_size = array();
/** /**
* Information about the temporary file produced * Information about the temporary file produced
* by the PHP-runtime. * by the PHP-runtime.
@ -360,22 +370,46 @@ class Upload_Validator {
* @return int Filesize in bytes * @return int Filesize in bytes
*/ */
public function getAllowedMaxFileSize($ext = null) { public function getAllowedMaxFileSize($ext = null) {
// Check if there is any defined instance max file sizes
if (empty($this->allowedMaxFileSize)) {
// Set default max file sizes if there isn't
$fileSize = Config::inst()->get('Upload_Validator', 'default_max_file_size');
if (isset($fileSize)) {
$this->setAllowedMaxFileSize($fileSize);
} else {
// When no default is present, use maximum set by PHP
$maxUpload = File::ini2bytes(ini_get('upload_max_filesize'));
$maxPost = File::ini2bytes(ini_get('post_max_size'));
$this->setAllowedMaxFileSize(min($maxUpload, $maxPost));
}
}
$ext = strtolower($ext); $ext = strtolower($ext);
if(isset($ext) && isset($this->allowedMaxFileSize[$ext])) { if ($ext) {
if (isset($this->allowedMaxFileSize[$ext])) {
return $this->allowedMaxFileSize[$ext]; return $this->allowedMaxFileSize[$ext];
}
$category = File::get_app_category($ext);
if ($category && isset($this->allowedMaxFileSize['[' . $category . ']'])) {
return $this->allowedMaxFileSize['[' . $category . ']'];
}
return false;
} else { } else {
return (isset($this->allowedMaxFileSize['*'])) ? $this->allowedMaxFileSize['*'] : false; return (isset($this->allowedMaxFileSize['*'])) ? $this->allowedMaxFileSize['*'] : false;
} }
} }
/** /**
* Set filesize maximums (in bytes). * Set filesize maximums (in bytes or INI format).
* Automatically converts extensions to lowercase * Automatically converts extensions to lowercase
* for easier matching. * for easier matching.
* *
* Example: * Example:
* <code> * <code>
* array('*' => 200, 'jpg' => 1000) * array('*' => 200, 'jpg' => 1000, '[doc]' => '5m')
* </code> * </code>
* *
* @param array|int $rules * @param array|int $rules
@ -384,7 +418,22 @@ class Upload_Validator {
if(is_array($rules) && count($rules)) { if(is_array($rules) && count($rules)) {
// make sure all extensions are lowercase // make sure all extensions are lowercase
$rules = array_change_key_case($rules, CASE_LOWER); $rules = array_change_key_case($rules, CASE_LOWER);
$this->allowedMaxFileSize = $rules; $finalRules = array();
$tmpSize = 0;
foreach ($rules as $rule => $value) {
if (is_numeric($value)) {
$tmpSize = $value;
} else {
$tmpSize = File::ini2bytes($value);
}
$finalRules[$rule] = (int)$tmpSize;
}
$this->allowedMaxFileSize = $finalRules;
} elseif(is_string($rules)) {
$this->allowedMaxFileSize['*'] = File::ini2bytes($rules);
} elseif((int) $rules > 0) { } elseif((int) $rules > 0) {
$this->allowedMaxFileSize['*'] = (int)$rules; $this->allowedMaxFileSize['*'] = (int)$rules;
} }

View File

@ -109,7 +109,7 @@ class FileField extends FormField {
if($this->relationAutoSetting) { if($this->relationAutoSetting) {
// assume that the file is connected via a has-one // assume that the file is connected via a has-one
$hasOnes = $record->has_one($this->name); $hasOnes = $record->hasOne($this->name);
// try to create a file matching the relation // try to create a file matching the relation
$file = (is_string($hasOnes)) ? Object::create($hasOnes) : new $fileClass(); $file = (is_string($hasOnes)) ? Object::create($hasOnes) : new $fileClass();
} else if($record instanceof File) { } else if($record instanceof File) {

View File

@ -93,8 +93,8 @@ class FormScaffolder extends Object {
} }
// add has_one relation fields // add has_one relation fields
if($this->obj->has_one()) { if($this->obj->hasOne()) {
foreach($this->obj->has_one() as $relationship => $component) { foreach($this->obj->hasOne() as $relationship => $component) {
if($this->restrictFields && !in_array($relationship, $this->restrictFields)) continue; if($this->restrictFields && !in_array($relationship, $this->restrictFields)) continue;
$fieldName = $component === 'DataObject' $fieldName = $component === 'DataObject'
? $relationship // Polymorphic has_one field is composite, so don't refer to ID subfield ? $relationship // Polymorphic has_one field is composite, so don't refer to ID subfield
@ -118,10 +118,10 @@ class FormScaffolder extends Object {
// only add relational fields if an ID is present // only add relational fields if an ID is present
if($this->obj->ID) { if($this->obj->ID) {
// add has_many relation fields // add has_many relation fields
if($this->obj->has_many() if($this->obj->hasMany()
&& ($this->includeRelations === true || isset($this->includeRelations['has_many']))) { && ($this->includeRelations === true || isset($this->includeRelations['has_many']))) {
foreach($this->obj->has_many() as $relationship => $component) { foreach($this->obj->hasMany() as $relationship => $component) {
if($this->tabbed) { if($this->tabbed) {
$relationTab = $fields->findOrMakeTab( $relationTab = $fields->findOrMakeTab(
"Root.$relationship", "Root.$relationship",
@ -145,10 +145,10 @@ class FormScaffolder extends Object {
} }
} }
if($this->obj->many_many() if($this->obj->manyMany()
&& ($this->includeRelations === true || isset($this->includeRelations['many_many']))) { && ($this->includeRelations === true || isset($this->includeRelations['many_many']))) {
foreach($this->obj->many_many() as $relationship => $component) { foreach($this->obj->manyMany() as $relationship => $component) {
if($this->tabbed) { if($this->tabbed) {
$relationTab = $fields->findOrMakeTab( $relationTab = $fields->findOrMakeTab(
"Root.$relationship", "Root.$relationship",

View File

@ -88,5 +88,12 @@ class GroupedDropdownField extends DropdownField {
return 'groupeddropdown dropdown'; return 'groupeddropdown dropdown';
} }
/**
* @todo Implement DropdownField::validate() with group validation support
*/
public function validate(Validator $validator) {
return true;
}
} }

View File

@ -453,7 +453,6 @@ class TreeDropdownField extends FormField {
$this->labelField $this->labelField
)); ));
} }
$res = DataObject::get($this->sourceObject)->filterAny($filters); $res = DataObject::get($this->sourceObject)->filterAny($filters);
} }
@ -463,18 +462,18 @@ class TreeDropdownField extends FormField {
if ($row->ParentID) $parents[$row->ParentID] = true; if ($row->ParentID) $parents[$row->ParentID] = true;
$this->searchIds[$row->ID] = true; $this->searchIds[$row->ID] = true;
} }
$sourceObject = $this->sourceObject;
while (!empty($parents)) { while (!empty($parents)) {
$idsClause = DB::placeholders($parents); $items = $sourceObject::get()
$res = DB::prepared_query( ->filter("ID",array_keys($parents));
"SELECT \"ParentID\", \"ID\" FROM \"{$this->sourceObject}\" WHERE \"ID\" in ($idsClause)",
array_keys($parents)
);
$parents = array(); $parents = array();
foreach($res as $row) { foreach($items as $item) {
if ($row['ParentID']) $parents[$row['ParentID']] = true; if ($item->ParentID) $parents[$item->ParentID] = true;
$this->searchIds[$row['ID']] = true; $this->searchIds[$item->ID] = true;
$this->searchExpanded[$row['ID']] = true; $this->searchExpanded[$item->ID] = true;
} }
} }
} }

View File

@ -502,7 +502,7 @@ class UploadField extends FileField {
if($relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) { if($relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
// has_many or many_many // has_many or many_many
$relation->setByIDList($idList); $relation->setByIDList($idList);
} elseif($record->has_one($fieldname)) { } elseif($record->hasOneComponent($fieldname)) {
// has_one // has_one
$record->{"{$fieldname}ID"} = $idList ? reset($idList) : 0; $record->{"{$fieldname}ID"} = $idList ? reset($idList) : 0;
} }
@ -590,7 +590,7 @@ class UploadField extends FileField {
if(empty($allowedMaxFileNumber)) { if(empty($allowedMaxFileNumber)) {
$record = $this->getRecord(); $record = $this->getRecord();
$name = $this->getName(); $name = $this->getName();
if($record && $record->has_one($name)) { if($record && $record->hasOneComponent($name)) {
return 1; // Default for has_one return 1; // Default for has_one
} else { } else {
return null; // Default for has_many and many_many return null; // Default for has_many and many_many

View File

@ -841,7 +841,8 @@ class GridField_FormAction extends FormAction {
'args' => $this->args, 'args' => $this->args,
); );
$id = md5(serialize($state)); // Ensure $id doesn't contain only numeric characters
$id = 'gf_'.substr(md5(serialize($state)), 0, 8);
Session::set($id, $state); Session::set($id, $state);
$actionData['StateID'] = $id; $actionData['StateID'] = $id;

View File

@ -95,7 +95,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
// if no validator has been set on the GridField and the record has a // if no validator has been set on the GridField and the record has a
// CMS validator, use that. // CMS validator, use that.
if(!$this->getValidator() && method_exists($record, 'getCMSValidator')) { if(!$this->getValidator() && (method_exists($record, 'getCMSValidator') || $record instanceof Object && $record->hasMethod('getCMSValidator'))) {
$this->setValidator($record->getCMSValidator()); $this->setValidator($record->getCMSValidator());
} }
@ -188,6 +188,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
*/ */
public function setItemEditFormCallback(Closure $cb) { public function setItemEditFormCallback(Closure $cb) {
$this->itemEditFormCallback = $cb; $this->itemEditFormCallback = $cb;
return $this;
} }
/** /**

View File

@ -155,8 +155,10 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
$fileData .= "\n"; $fileData .= "\n";
} }
if ($item->hasMethod('destroy')) {
$item->destroy(); $item->destroy();
} }
}
return $fileData; return $fileData;
} }

View File

@ -137,6 +137,12 @@
onclick: function(e){ onclick: function(e){
var filterState='show'; //filterstate should equal current state. var filterState='show'; //filterstate should equal current state.
// If the button is disabled, do nothing.
if (this.button('option', 'disabled')) {
e.preventDefault();
return;
}
if(this.hasClass('ss-gridfield-button-close') || !(this.closest('.ss-gridfield').hasClass('show-filter'))){ if(this.hasClass('ss-gridfield-button-close') || !(this.closest('.ss-gridfield').hasClass('show-filter'))){
filterState='hidden'; filterState='hidden';
} }
@ -146,6 +152,34 @@
} }
}); });
/**
* Don't allow users to submit empty values in grid field auto complete inputs.
*/
$('.ss-gridfield .add-existing-autocompleter').entwine({
onbuttoncreate: function () {
var self = this;
this.toggleDisabled();
this.find('input[type="text"]').on('keyup', function () {
self.toggleDisabled();
});
},
onunmatch: function () {
this.find('input[type="text"]').off('keyup');
},
toggleDisabled: function () {
var $button = this.find('.ss-ui-button'),
$input = this.find('input[type="text"]'),
inputHasValue = $input.val() !== '',
buttonDisabled = $button.is(':disabled');
if ((inputHasValue && buttonDisabled) || (!inputHasValue && !buttonDisabled)) {
$button.button("option", "disabled", !buttonDisabled);
}
}
});
// Covers both tabular delete button, and the button on the detail form // Covers both tabular delete button, and the button on the detail form
$('.ss-gridfield .col-buttons .action.gridfield-button-delete, .cms-edit-form .Actions button.action.action-delete').entwine({ $('.ss-gridfield .col-buttons .action.gridfield-button-delete, .cms-edit-form .Actions button.action.action-delete').entwine({
onclick: function(e){ onclick: function(e){

41
javascript/lang/src/sv.js Normal file
View File

@ -0,0 +1,41 @@
{
"VALIDATOR.FIELDREQUIRED": "Var god fyll i \"%s\", det är obligatoriskt.",
"HASMANYFILEFIELD.UPLOADING": "Laddar upp... %s",
"TABLEFIELD.DELETECONFIRMMESSAGE": "Vill du verkligen radera detta?",
"LOADING": "laddar...",
"UNIQUEFIELD.SUGGESTED": "Ändrade värde till '%s' : %s",
"UNIQUEFIELD.ENTERNEWVALUE": "Du måste fylla i ett nytt värde för detta fält",
"UNIQUEFIELD.CANNOTLEAVEEMPTY": "Detta fält kan inte lämnas tomt",
"RESTRICTEDTEXTFIELD.CHARCANTBEUSED": "Tecknet '%s' kan inte användas i detta fält",
"UPDATEURL.CONFIRM": "Vill du att URL:en ändras till:\n\n%s/\n\nKlicka OK för att ändra URL:en, klicka Avbryt för att lämna den som:\n\n%s",
"UPDATEURL.CONFIRMURLCHANGED": "URL:en har ändrats till\n'%s'",
"FILEIFRAMEFIELD.DELETEFILE": "Radera fil",
"FILEIFRAMEFIELD.UNATTACHFILE": "Avlänka fil",
"FILEIFRAMEFIELD.DELETEIMAGE": "Radera bild",
"FILEIFRAMEFIELD.CONFIRMDELETE": "Vill du verkligen radera denna fil?",
"LeftAndMain.IncompatBrowserWarning": "Din webbläsare är inte kompatibel med detta CMS. Var god använd Internet Explorer 7+, Google Chrome 10+ eller Mozilla Firefox 3.5+.",
"GRIDFIELD.ERRORINTRANSACTION": "Ett fel uppstod när data hämtades från servern.\nVar god försök igen senare.",
"HtmlEditorField.SelectAnchor": "Välj ett ankare",
"UploadField.ConfirmDelete": "Är du säker på att du vill radera denna fil från servern?",
"UploadField.PHP_MAXFILESIZE": "Filen överskrider upload_max_filesize (php-ini-direktiv)",
"UploadField.HTML_MAXFILESIZE": "Filen överskrider MAX_FILE_SIZE (HTML form-direktiv)",
"UploadField.ONLYPARTIALUPLOADED": "Filen laddas bara upp delvis",
"UploadField.NOFILEUPLOADED": "Ingen fil laddades upp",
"UploadField.NOTMPFOLDER": "Tillfällig mapp saknas",
"UploadField.WRITEFAILED": "Kunde inte skriva filen",
"UploadField.STOPEDBYEXTENSION": "Uppladdning stoppades av otillåten filtyp",
"UploadField.TOOLARGE": "Filen är för stor",
"UploadField.TOOSMALL": "Filen är för liten",
"UploadField.INVALIDEXTENSION": "Filtypen tillåts inte",
"UploadField.MAXNUMBEROFFILESSIMPLE": "Maximalt antal filer överstiget",
"UploadField.UPLOADEDBYTES": "Antalet uppladdade bytes överstiger filstorleken",
"UploadField.EMPTYRESULT": "Tomt uppladdningsresultat",
"UploadField.LOADING": "Laddar ...",
"UploadField.Editing": "Redigerar ...",
"UploadField.Uploaded": "Uppladdad",
"UploadField.OVERWRITEWARNING": "Fil med samma namn existerar redan",
"TreeDropdownField.ENTERTOSEARCH": "Tryck Enter för att söka",
"TreeDropdownField.OpenLink": "Öppna",
"TreeDropdownField.FieldTitle": "Välj",
"TreeDropdownField.SearchFieldTitle": "Välj eller Sök"
}

47
javascript/lang/sv.js Normal file
View File

@ -0,0 +1,47 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/sv.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('sv', {
"VALIDATOR.FIELDREQUIRED": "Var god fyll i \"%s\", det är obligatoriskt.",
"HASMANYFILEFIELD.UPLOADING": "Laddar upp... %s",
"TABLEFIELD.DELETECONFIRMMESSAGE": "Vill du verkligen radera detta?",
"LOADING": "laddar...",
"UNIQUEFIELD.SUGGESTED": "Ändrade värde till '%s' : %s",
"UNIQUEFIELD.ENTERNEWVALUE": "Du måste fylla i ett nytt värde för detta fält",
"UNIQUEFIELD.CANNOTLEAVEEMPTY": "Detta fält kan inte lämnas tomt",
"RESTRICTEDTEXTFIELD.CHARCANTBEUSED": "Tecknet '%s' kan inte användas i detta fält",
"UPDATEURL.CONFIRM": "Vill du att URL:en ändras till:\n\n%s/\n\nKlicka OK för att ändra URL:en, klicka Avbryt för att lämna den som:\n\n%s",
"UPDATEURL.CONFIRMURLCHANGED": "URL:en har ändrats till\n'%s'",
"FILEIFRAMEFIELD.DELETEFILE": "Radera fil",
"FILEIFRAMEFIELD.UNATTACHFILE": "Avlänka fil",
"FILEIFRAMEFIELD.DELETEIMAGE": "Radera bild",
"FILEIFRAMEFIELD.CONFIRMDELETE": "Vill du verkligen radera denna fil?",
"LeftAndMain.IncompatBrowserWarning": "Din webbläsare är inte kompatibel med detta CMS. Var god använd Internet Explorer 7+, Google Chrome 10+ eller Mozilla Firefox 3.5+.",
"GRIDFIELD.ERRORINTRANSACTION": "Ett fel uppstod när data hämtades från servern.\nVar god försök igen senare.",
"HtmlEditorField.SelectAnchor": "Välj ett ankare",
"UploadField.ConfirmDelete": "Är du säker på att du vill radera denna fil från servern?",
"UploadField.PHP_MAXFILESIZE": "Filen överskrider upload_max_filesize (php-ini-direktiv)",
"UploadField.HTML_MAXFILESIZE": "Filen överskrider MAX_FILE_SIZE (HTML form-direktiv)",
"UploadField.ONLYPARTIALUPLOADED": "Filen laddas bara upp delvis",
"UploadField.NOFILEUPLOADED": "Ingen fil laddades upp",
"UploadField.NOTMPFOLDER": "Tillfällig mapp saknas",
"UploadField.WRITEFAILED": "Kunde inte skriva filen",
"UploadField.STOPEDBYEXTENSION": "Uppladdning stoppades av otillåten filtyp",
"UploadField.TOOLARGE": "Filen är för stor",
"UploadField.TOOSMALL": "Filen är för liten",
"UploadField.INVALIDEXTENSION": "Filtypen tillåts inte",
"UploadField.MAXNUMBEROFFILESSIMPLE": "Maximalt antal filer överstiget",
"UploadField.UPLOADEDBYTES": "Antalet uppladdade bytes överstiger filstorleken",
"UploadField.EMPTYRESULT": "Tomt uppladdningsresultat",
"UploadField.LOADING": "Laddar ...",
"UploadField.Editing": "Redigerar ...",
"UploadField.Uploaded": "Uppladdad",
"UploadField.OVERWRITEWARNING": "Fil med samma namn existerar redan",
"TreeDropdownField.ENTERTOSEARCH": "Tryck Enter för att söka",
"TreeDropdownField.OpenLink": "Öppna",
"TreeDropdownField.FieldTitle": "Välj",
"TreeDropdownField.SearchFieldTitle": "Välj eller Sök"
});
}

View File

@ -235,7 +235,7 @@ lt:
SINGULARNAME: Grupė SINGULARNAME: Grupė
Sort: 'Rūšiavimo tvarka' Sort: 'Rūšiavimo tvarka'
has_many_Permissions: Leidimai has_many_Permissions: Leidimai
many_many_Members: Nariai many_many_Members: Vartotojai
GroupImportForm: GroupImportForm:
Help1: '<p>Importuoti vieną ar kelias grupes <em>CSV</em> formatu (kableliu atskirtos reikšmės). <small><a href="#" class="toggle-advanced">Rodyti detalesnį aprašymą</a></small></p>' Help1: '<p>Importuoti vieną ar kelias grupes <em>CSV</em> formatu (kableliu atskirtos reikšmės). <small><a href="#" class="toggle-advanced">Rodyti detalesnį aprašymą</a></small></p>'
Help2: "<div class=\"advanced\">\n<h4>Detalesnis aprašymas</h4>\n<ul>\n<li>Galimi stulpeliai: <em>%s</em></li>\n<li>Esamos grupės yra surišamos su jų unikalia <em>Code</em> reikšme ir atnaujinamos duomenimis iš importuojamos bylos</li>\n<li>Grupių hierarchija gali būti sukurta naudojant <em>ParentCode</em> stulpelį.</li>\n<li>Leidimų gali būti priskirti naudojant <em>PermissionCode</em> stulpelį. Esami leidimai nebus pakeisti.</li>\n</ul>\n</div>" Help2: "<div class=\"advanced\">\n<h4>Detalesnis aprašymas</h4>\n<ul>\n<li>Galimi stulpeliai: <em>%s</em></li>\n<li>Esamos grupės yra surišamos su jų unikalia <em>Code</em> reikšme ir atnaujinamos duomenimis iš importuojamos bylos</li>\n<li>Grupių hierarchija gali būti sukurta naudojant <em>ParentCode</em> stulpelį.</li>\n<li>Leidimų gali būti priskirti naudojant <em>PermissionCode</em> stulpelį. Esami leidimai nebus pakeisti.</li>\n</ul>\n</div>"
@ -350,15 +350,15 @@ lt:
NEWPASSWORD: 'Naujas slaptažodis' NEWPASSWORD: 'Naujas slaptažodis'
NoPassword: 'Šis vartotojas neturi slaptažodžio.' NoPassword: 'Šis vartotojas neturi slaptažodžio.'
PASSWORD: Slaptažodis PASSWORD: Slaptažodis
PLURALNAME: Nariai PLURALNAME: Vartotojai
REMEMBERME: 'Prisiminti jungiantis kitą kartą?' REMEMBERME: 'Prisiminti jungiantis kitą kartą?'
SINGULARNAME: Narys SINGULARNAME: Vartotojas
SUBJECTPASSWORDCHANGED: 'Jūsų slaptažodis pakeistas' SUBJECTPASSWORDCHANGED: 'Jūsų slaptažodis pakeistas'
SUBJECTPASSWORDRESET: 'Slaptažodžio atstatymo nuoroda' SUBJECTPASSWORDRESET: 'Slaptažodžio atstatymo nuoroda'
SURNAME: Pavardė SURNAME: Pavardė
TIMEFORMAT: 'Laiko formatas' TIMEFORMAT: 'Laiko formatas'
VALIDATIONMEMBEREXISTS: 'Tokį e. paštą jau naudoja kitas narys.' VALIDATIONMEMBEREXISTS: 'Vartotojas šiuo el. pašto adresu %s jau egzistuoja.'
ValidationIdentifierFailed: 'Nepavyko perrašyti nario #{id} su tuo pačiu atpažinimo kodu ({name} = {value}))' ValidationIdentifierFailed: 'Nepavyko atnaujinti vartotojo #{id} duomenų su atpažinimo kodu ({name} = {value})'
WELCOMEBACK: 'Sveiki, {firstname}' WELCOMEBACK: 'Sveiki, {firstname}'
YOUROLDPASSWORD: 'Jūsų senas slaptažodis' YOUROLDPASSWORD: 'Jūsų senas slaptažodis'
belongs_many_many_Groups: Grupės belongs_many_many_Groups: Grupės
@ -488,7 +488,7 @@ lt:
GROUPNAME: 'Grupės pavadinimas' GROUPNAME: 'Grupės pavadinimas'
IMPORTGROUPS: 'Importuoti grupes' IMPORTGROUPS: 'Importuoti grupes'
IMPORTUSERS: 'Importuoti vartotojus' IMPORTUSERS: 'Importuoti vartotojus'
MEMBERS: Nariai MEMBERS: Vartotojai
MENUTITLE: Saugumas MENUTITLE: Saugumas
MemberListCaution: 'Dėmesio: Pašalinus vartotojus iš šio sąrašo, jie bus pašalinti ir iš visų grupių, bei duomenų bazės.' MemberListCaution: 'Dėmesio: Pašalinus vartotojus iš šio sąrašo, jie bus pašalinti ir iš visų grupių, bei duomenų bazės.'
NEWGROUP: 'Nauja grupė' NEWGROUP: 'Nauja grupė'

View File

@ -1,6 +1,7 @@
sv: sv:
AssetAdmin: AssetAdmin:
NEWFOLDER: Ny mapp NEWFOLDER: Ny mapp
SHOWALLOWEDEXTS: 'Visa tillåtna filtyper'
AssetTableField: AssetTableField:
CREATED: 'Först uppladdad' CREATED: 'Först uppladdad'
DIM: Dimensioner DIM: Dimensioner
@ -67,6 +68,8 @@ sv:
ACCESSALLINTERFACES: 'Tillgång till alla CMS-sektioner' ACCESSALLINTERFACES: 'Tillgång till alla CMS-sektioner'
ACCESSALLINTERFACESHELP: 'Ersätter mer specifika behörighetsinställningar.' ACCESSALLINTERFACESHELP: 'Ersätter mer specifika behörighetsinställningar.'
SAVE: Spara SAVE: Spara
CMSPageHistoryController_versions_ss:
PREVIEW: 'Förhandsgranska sida'
CMSProfileController: CMSProfileController:
MENUTITLE: 'Min Profil' MENUTITLE: 'Min Profil'
ChangePasswordEmail_ss: ChangePasswordEmail_ss:
@ -87,6 +90,8 @@ sv:
FOURTH: fjärde FOURTH: fjärde
SECOND: andra SECOND: andra
THIRD: tredje THIRD: tredje
CurrencyField:
CURRENCYSYMBOL: $
DataObject: DataObject:
PLURALNAME: 'Dataobjekt' PLURALNAME: 'Dataobjekt'
SINGULARNAME: 'Dataobjekt' SINGULARNAME: 'Dataobjekt'
@ -96,9 +101,12 @@ sv:
HOUR: timme HOUR: timme
HOURS: timmar HOURS: timmar
LessThanMinuteAgo: 'mindre än en minut' LessThanMinuteAgo: 'mindre än en minut'
MIN: minut
MINS: minuter
MONTH: månad MONTH: månad
MONTHS: månader MONTHS: månader
SEC: sek SEC: sekund
SECS: sekunder
TIMEDIFFAGO: '{difference} sen' TIMEDIFFAGO: '{difference} sen'
TIMEDIFFIN: 'om {difference}' TIMEDIFFIN: 'om {difference}'
YEAR: år YEAR: år
@ -121,37 +129,39 @@ sv:
Enum: Enum:
ANY: Vilken som helst ANY: Vilken som helst
File: File:
AviType: 'AVI videofil' AviType: 'AVI-videofil'
Content: Innehåll Content: Innehåll
CssType: 'CSS fil' CssType: 'CSS-fil'
DmgType: 'Apple skivavbild' DmgType: 'Apple-skivavbild'
DocType: 'Word dokument' DocType: 'Word-dokument'
Filename: Filnamn Filename: Filnamn
GifType: 'GIF bild - bra för diagram' GifType: 'GIF-bild - bra för diagram'
GzType: 'GZIP packad fil' GzType: 'GZIP-packad fil'
HtlType: 'HTML fil' HtlType: 'HTML-fil'
HtmlType: 'HTML fil' HtmlType: 'HTML-fil'
INVALIDEXTENSION: 'Filändelsen tillåts inte (tillåtna: {extensions})' INVALIDEXTENSION: 'Filändelsen tillåts inte (tillåtna: {extensions})'
INVALIDEXTENSIONSHORT: 'Filändelsen tillåts inte' INVALIDEXTENSIONSHORT: 'Filändelsen tillåts inte'
IcoType: 'Icon bild' IcoType: 'Ikonbild'
JpgType: 'JPEG bild - bra för fotografier' JpgType: 'JPEG-bild - bra för fotografier'
JsType: 'Javascript fil' JsType: 'Javascript-fil'
Mp3Type: 'MP3 ljudfil' Mp3Type: 'MP3-ljudfil'
MpgType: 'MPEG videofil' MpgType: 'MPEG-videofil'
NOFILESIZE: 'Filstorleken är noll bytes' NOFILESIZE: 'Filstorleken är noll bytes'
NOVALIDUPLOAD: 'Filen är inte giltig för uppladdning' NOVALIDUPLOAD: 'Filen är inte giltig för uppladdning'
Name: Namn Name: Namn
PLURALNAME: Filer PLURALNAME: Filer
PdfType: 'Adobe Acrobat PDF fil' PdfType: 'Adobe Acrobat PDF-fil'
PngType: 'PNG bild - bra allmänt format' PngType: 'PNG-bild - bra allmänt format'
SINGULARNAME: Fil SINGULARNAME: Fil
TOOLARGE: 'Filen är för stor, max {size} tillåts' TOOLARGE: 'Filen är för stor, max {size} tillåts'
TOOLARGESHORT: 'Filstorlek överskriden {size}' TOOLARGESHORT: 'Filstorlek överskriden {size}'
TiffType: 'Tiff bildformat' TiffType: 'Tiff bildformat'
Title: Titel Title: Titel
WavType: 'WAV ljudfil' WavType: 'WAV-ljudfil'
XlsType: 'Excel kalkylblad' XlsType: 'Excel kalkylblad'
ZipType: 'ZIP packad fil' ZipType: 'ZIP-packad fil'
Filesystem:
SYNCRESULTS: 'Synkning komplett: {createdcount} artiklar skapades, {deletedcount} artiklar raderades'
Folder: Folder:
PLURALNAME: Mappar PLURALNAME: Mappar
SINGULARNAME: Mapp SINGULARNAME: Mapp
@ -161,14 +171,19 @@ sv:
TEXT2: 'Återställningslänk för lösenord' TEXT2: 'Återställningslänk för lösenord'
TEXT3: för TEXT3: för
Form: Form:
CSRF_FAILED_MESSAGE: "Ett tekniskt fel uppstod. Var god klicka på bakåt-knappen,\n⇥⇥⇥⇥⇥ladda om webbläsaren och försök igen."
FIELDISREQUIRED: '{name} är obligatoriskt'
SubmitBtnLabel: Kör SubmitBtnLabel: Kör
VALIDATIONCREDITNUMBER: 'Kontrollera att du angav kortnummret {number} rätt' VALIDATIONCREDITNUMBER: 'Kontrollera att du angav kortnummret {number} rätt'
VALIDATIONNOTUNIQUE: 'Det angivna värdet är inte unikt' VALIDATIONNOTUNIQUE: 'Det angivna värdet är inte unikt'
VALIDATIONPASSWORDSDONTMATCH: 'Lösenorden stämmer inte överrens ' VALIDATIONPASSWORDSDONTMATCH: 'Lösenorden stämmer inte överrens '
VALIDATIONPASSWORDSNOTEMPTY: 'Lösenordfältet får inte vara tomt' VALIDATIONPASSWORDSNOTEMPTY: 'Lösenordfältet får inte vara tomt'
VALIDATIONSTRONGPASSWORD: 'Lösenord måste innehålla minst en siffra och en bokstav.' VALIDATIONSTRONGPASSWORD: 'Lösenord måste innehålla minst en siffra och en bokstav.'
VALIDATOR: Validator
VALIDCURRENCY: 'Var vänlig ange en korrekt valuta' VALIDCURRENCY: 'Var vänlig ange en korrekt valuta'
CSRF_EXPIRED_MESSAGE: 'Din session har upphört. Var god och skicka in formuläret på nytt.'
FormField: FormField:
Example: 't.ex. %s'
NONE: ingen NONE: ingen
GridAction: GridAction:
DELETE_DESCRIPTION: Radera DELETE_DESCRIPTION: Radera
@ -191,6 +206,7 @@ sv:
ResetFilter: Rensa ResetFilter: Rensa
GridFieldAction_Delete: GridFieldAction_Delete:
DeletePermissionsFailure: 'Rättighet för att radera saknas' DeletePermissionsFailure: 'Rättighet för att radera saknas'
EditPermissionsFailure: 'Rättigheter för avlänkning saknas'
GridFieldDetailForm: GridFieldDetailForm:
CancelBtn: Avbryt CancelBtn: Avbryt
Create: Skapa Create: Skapa
@ -198,6 +214,7 @@ sv:
DeletePermissionsFailure: 'Rättighet för att radera saknas' DeletePermissionsFailure: 'Rättighet för att radera saknas'
Deleted: 'Raderade %s %s' Deleted: 'Raderade %s %s'
Save: Spara Save: Spara
Saved: 'Sparade {name} {link}'
GridFieldEditButton_ss: GridFieldEditButton_ss:
EDIT: Redigera EDIT: Redigera
GridFieldItemEditView: GridFieldItemEditView:
@ -209,9 +226,11 @@ sv:
DefaultGroupTitleContentAuthors: 'Författare' DefaultGroupTitleContentAuthors: 'Författare'
Description: Beskrivning Description: Beskrivning
GroupReminder: 'Om du väljer en förälder till gruppen så kommer gruppen ärva alla förälderns roller' GroupReminder: 'Om du väljer en förälder till gruppen så kommer gruppen ärva alla förälderns roller'
HierarchyPermsError: 'Den överordnade gruppen "%s" kan inte ges priviligerad tillgång (adminrättigheter krävs)'
Locked: 'Låst?' Locked: 'Låst?'
NoRoles: 'Inga roller fun' NoRoles: 'Inga roller fun'
PLURALNAME: Grupper PLURALNAME: Grupper
Parent: 'Överordnad grupp'
RolesAddEditLink: 'Hantera roller' RolesAddEditLink: 'Hantera roller'
SINGULARNAME: Grupp SINGULARNAME: Grupp
Sort: 'Sorteringsordning' Sort: 'Sorteringsordning'
@ -250,6 +269,7 @@ sv:
FindInFolder: 'Hitta i mapp' FindInFolder: 'Hitta i mapp'
IMAGEALT: 'Alternativ text (alt)' IMAGEALT: 'Alternativ text (alt)'
IMAGEALTTEXT: 'Alternativ text (alt) - visas om bilden inte kan visas' IMAGEALTTEXT: 'Alternativ text (alt) - visas om bilden inte kan visas'
IMAGEALTTEXTDESC: 'Visas för skärmläsare eller om bilden inte kan visas'
IMAGEDIMENSIONS: Dimensioner IMAGEDIMENSIONS: Dimensioner
IMAGEHEIGHTPX: Höjd IMAGEHEIGHTPX: Höjd
IMAGETITLE: 'Titel text (tooltip) - för ytterligare information om bilden' IMAGETITLE: 'Titel text (tooltip) - för ytterligare information om bilden'
@ -319,8 +339,10 @@ sv:
EMAIL: E-post EMAIL: E-post
EMPTYNEWPASSWORD: 'Det nya lösenordet kan inte vara tomt, vänligen försök igen' EMPTYNEWPASSWORD: 'Det nya lösenordet kan inte vara tomt, vänligen försök igen'
ENTEREMAIL: 'Ange en e-postadress för att få en återställningslänk för lösenordet.' ENTEREMAIL: 'Ange en e-postadress för att få en återställningslänk för lösenordet.'
ERRORLOCKEDOUT2: 'Ditt konto har tillfälligt stängs av på grund av för många misslyckade inloggningsförsök. Försök igen om {count} minuter.'
ERRORNEWPASSWORD: 'Du har angett ditt nya lösenord annorlunda, försök igen' ERRORNEWPASSWORD: 'Du har angett ditt nya lösenord annorlunda, försök igen'
ERRORPASSWORDNOTMATCH: 'Ditt nuvarande lösenord matchar inte, var god försök igen' ERRORPASSWORDNOTMATCH: 'Ditt nuvarande lösenord matchar inte, var god försök igen'
ERRORWRONGCRED: 'Antingen e-postadressen eller lösenordet är fel. Försök igen.'
FIRSTNAME: 'Förnamn' FIRSTNAME: 'Förnamn'
INTERFACELANG: 'Gränssnittsspråk' INTERFACELANG: 'Gränssnittsspråk'
INVALIDNEWPASSWORD: 'Vi kunde inte godkänna det lösenordet: {password}' INVALIDNEWPASSWORD: 'Vi kunde inte godkänna det lösenordet: {password}'
@ -412,6 +434,10 @@ sv:
Pagination: Pagination:
Page: Sida Page: Sida
View: Visa View: Visa
PasswordValidator:
LOWCHARSTRENGTH: 'Var god och stärk ditt lösenord genom att lägga till något av följande tecken: %s'
PREVPASSWORD: 'Du har redan använt samma lösenord tidigare, var god och välj ett nytt lösenord'
TOOSHORT: 'Lösenordet är för kort, det måste innehålla %s eller fler tecken.'
Permission: Permission:
AdminGroup: Administratör AdminGroup: Administratör
CMS_ACCESS_CATEGORY: 'CMS-åtkomst' CMS_ACCESS_CATEGORY: 'CMS-åtkomst'
@ -431,6 +457,7 @@ sv:
Title: Rollnamn Title: Rollnamn
PermissionRoleCode: PermissionRoleCode:
PLURALNAME: 'Koder för rollrättigheter' PLURALNAME: 'Koder för rollrättigheter'
PermsError: 'Koden "%s" kan inte ges privilegierad tillgång (adminrättigheter krävs)'
SINGULARNAME: 'Kodför rollrättigheter' SINGULARNAME: 'Kodför rollrättigheter'
Permissions: Permissions:
PERMISSIONS_CATEGORY: 'Roller och åtkomsträttigheter' PERMISSIONS_CATEGORY: 'Roller och åtkomsträttigheter'
@ -446,6 +473,7 @@ sv:
ERRORPASSWORDPERMISSION: 'Du måste vara inloggad för att kunna ändra ditt lösenord!' ERRORPASSWORDPERMISSION: 'Du måste vara inloggad för att kunna ändra ditt lösenord!'
LOGGEDOUT: 'Du har blivit utloggad. Om du vill logga in igen anger du dina uppgifter nedan.' LOGGEDOUT: 'Du har blivit utloggad. Om du vill logga in igen anger du dina uppgifter nedan.'
LOGIN: 'Logga in' LOGIN: 'Logga in'
LOSTPASSWORDHEADER: 'Bortglömt lösenord'
NOTEPAGESECURED: 'Den här sidan är låst. Fyll i dina uppgifter nedan så skickar vi dig vidare.' NOTEPAGESECURED: 'Den här sidan är låst. Fyll i dina uppgifter nedan så skickar vi dig vidare.'
NOTERESETLINKINVALID: '<p>Återställningslänk för lösenord är felaktig eller för gammal.</p><p>Du kan begära en ny <a href="{link1}">här</a> eller ändra ditt lösenord när du <a href="{link2}">loggat in</a>.</p>' NOTERESETLINKINVALID: '<p>Återställningslänk för lösenord är felaktig eller för gammal.</p><p>Du kan begära en ny <a href="{link1}">här</a> eller ändra ditt lösenord när du <a href="{link2}">loggat in</a>.</p>'
NOTERESETPASSWORD: 'Ange din e-postadress så skickar vi en länk med vilken du kan återställa ditt lösenord' NOTERESETPASSWORD: 'Ange din e-postadress så skickar vi en länk med vilken du kan återställa ditt lösenord'
@ -474,8 +502,21 @@ sv:
FileFieldLabel: 'CSV-fil <small>(Tillåtna filtyper: *.csv)</small>' FileFieldLabel: 'CSV-fil <small>(Tillåtna filtyper: *.csv)</small>'
SilverStripeNavigator: SilverStripeNavigator:
Auto: Auto Auto: Auto
ChangeViewMode: 'Ändra visningsläge'
Desktop: Stationär
DualWindowView: 'Delat fönster'
Edit: Ändra Edit: Ändra
EditView: 'Redigeringsläge'
Mobile: Mobil
PreviewState: 'Förhandsgranskningsstatus'
PreviewView: 'Förhandsgranskningsläge'
Responsive: Responsiv
SplitView: 'Uppdelat läge'
Tablet: Platta
ViewDeviceWidth: 'Välj bredd på förhandsgranskning'
Width: bredd Width: bredd
SiteTree:
TABMAIN: Allmän
TableListField: TableListField:
CSVEXPORT: 'Exportera till CSV' CSVEXPORT: 'Exportera till CSV'
Print: Skriv ut Print: Skriv ut

View File

@ -78,7 +78,7 @@ class DataDifferencer extends ViewableData {
$fields = array_keys($this->toRecord->toMap()); $fields = array_keys($this->toRecord->toMap());
} }
$hasOnes = array_merge($this->fromRecord->has_one(), $this->toRecord->has_one()); $hasOnes = array_merge($this->fromRecord->hasOne(), $this->toRecord->hasOne());
// Loop through properties // Loop through properties
foreach($fields as $field) { foreach($fields as $field) {

View File

@ -547,10 +547,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// DO NOT copy has_many relations, because copying the relation would result in us changing the has_one // DO NOT copy has_many relations, because copying the relation would result in us changing the has_one
// relation on the other side of this relation to point at the copy and no longer the original (being a // relation on the other side of this relation to point at the copy and no longer the original (being a
// has_one, it can only point at one thing at a time). So, all relations except has_many can and are copied // has_one, it can only point at one thing at a time). So, all relations except has_many can and are copied
if ($sourceObject->has_one()) foreach($sourceObject->has_one() as $name => $type) { if ($sourceObject->hasOne()) foreach($sourceObject->hasOne() as $name => $type) {
$this->duplicateRelations($sourceObject, $destinationObject, $name); $this->duplicateRelations($sourceObject, $destinationObject, $name);
} }
if ($sourceObject->many_many()) foreach($sourceObject->many_many() as $name => $type) { if ($sourceObject->manyMany()) foreach($sourceObject->manyMany() as $name => $type) {
//many_many include belongs_many_many //many_many include belongs_many_many
$this->duplicateRelations($sourceObject, $destinationObject, $name); $this->duplicateRelations($sourceObject, $destinationObject, $name);
} }
@ -666,24 +666,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if($this->class == 'DataObject') return; if($this->class == 'DataObject') return;
// Set up accessors for joined items // Set up accessors for joined items
if($manyMany = $this->many_many()) { if($manyMany = $this->manyMany()) {
foreach($manyMany as $relationship => $class) { foreach($manyMany as $relationship => $class) {
$this->addWrapperMethod($relationship, 'getManyManyComponents'); $this->addWrapperMethod($relationship, 'getManyManyComponents');
} }
} }
if($hasMany = $this->has_many()) { if($hasMany = $this->hasMany()) {
foreach($hasMany as $relationship => $class) { foreach($hasMany as $relationship => $class) {
$this->addWrapperMethod($relationship, 'getComponents'); $this->addWrapperMethod($relationship, 'getComponents');
} }
} }
if($hasOne = $this->has_one()) { if($hasOne = $this->hasOne()) {
foreach($hasOne as $relationship => $class) { foreach($hasOne as $relationship => $class) {
$this->addWrapperMethod($relationship, 'getComponent'); $this->addWrapperMethod($relationship, 'getComponent');
} }
} }
if($belongsTo = $this->belongs_to()) foreach(array_keys($belongsTo) as $relationship) { if($belongsTo = $this->belongsTo()) foreach(array_keys($belongsTo) as $relationship) {
$this->addWrapperMethod($relationship, 'getComponent'); $this->addWrapperMethod($relationship, 'getComponent');
} }
} }
@ -972,7 +972,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// merge relations // merge relations
if($includeRelations) { if($includeRelations) {
if($manyMany = $this->many_many()) { if($manyMany = $this->manyMany()) {
foreach($manyMany as $relationship => $class) { foreach($manyMany as $relationship => $class) {
$leftComponents = $leftObj->getManyManyComponents($relationship); $leftComponents = $leftObj->getManyManyComponents($relationship);
$rightComponents = $rightObj->getManyManyComponents($relationship); $rightComponents = $rightObj->getManyManyComponents($relationship);
@ -983,7 +983,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
} }
if($hasMany = $this->has_many()) { if($hasMany = $this->hasMany()) {
foreach($hasMany as $relationship => $class) { foreach($hasMany as $relationship => $class) {
$leftComponents = $leftObj->getComponents($relationship); $leftComponents = $leftObj->getComponents($relationship);
$rightComponents = $rightObj->getComponents($relationship); $rightComponents = $rightObj->getComponents($relationship);
@ -995,7 +995,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
if($hasOne = $this->has_one()) { if($hasOne = $this->hasOne()) {
foreach($hasOne as $relationship => $class) { foreach($hasOne as $relationship => $class) {
$leftComponent = $leftObj->getComponent($relationship); $leftComponent = $leftObj->getComponent($relationship);
$rightComponent = $rightObj->getComponent($relationship); $rightComponent = $rightObj->getComponent($relationship);
@ -1130,7 +1130,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$this->$fieldName = $fieldValue; $this->$fieldName = $fieldValue;
} }
// Set many-many defaults with an array of ids // Set many-many defaults with an array of ids
if(is_array($fieldValue) && $this->many_many($fieldName)) { if(is_array($fieldValue) && $this->manyManyComponent($fieldName)) {
$manyManyJoin = $this->$fieldName(); $manyManyJoin = $this->$fieldName();
$manyManyJoin->setByIdList($fieldValue); $manyManyJoin->setByIdList($fieldValue);
} }
@ -1497,7 +1497,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $this->components[$componentName]; return $this->components[$componentName];
} }
if($class = $this->has_one($componentName)) { if($class = $this->hasOneComponent($componentName)) {
$joinField = $componentName . 'ID'; $joinField = $componentName . 'ID';
$joinID = $this->getField($joinField); $joinID = $this->getField($joinField);
@ -1514,7 +1514,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(empty($component)) { if(empty($component)) {
$component = $this->model->$class->newObject(); $component = $this->model->$class->newObject();
} }
} elseif($class = $this->belongs_to($componentName)) { } elseif($class = $this->belongsToComponent($componentName)) {
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to', $polymorphic); $joinField = $this->getRemoteJoinField($componentName, 'belongs_to', $polymorphic);
$joinID = $this->ID; $joinID = $this->ID;
@ -1553,18 +1553,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Returns a one-to-many relation as a HasManyList * Returns a one-to-many relation as a HasManyList
* *
* @param string $componentName Name of the component * @param string $componentName Name of the component
* @param string $filter A filter to be inserted into the WHERE clause * @param string|null $filter Deprecated. A filter to be inserted into the WHERE clause
* @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, the static * @param string|null|array $sort Deprecated. A sort expression to be inserted into the ORDER BY clause. If omitted,
* field $default_sort on the component class will be used. * the static field $default_sort on the component class will be used.
* @param string $join Deprecated, use leftJoin($table, $joinClause) instead * @param string $join Deprecated, use leftJoin($table, $joinClause) instead
* @param string|array $limit A limit expression to be inserted into the LIMIT clause * @param string|null|array $limit Deprecated. A limit expression to be inserted into the LIMIT clause
* *
* @return HasManyList The components of the one-to-many relationship. * @return HasManyList The components of the one-to-many relationship.
*/ */
public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null) { public function getComponents($componentName, $filter = null, $sort = null, $join = null, $limit = null) {
$result = null; $result = null;
if(!$componentClass = $this->has_many($componentName)) { if(!$componentClass = $this->hasManyComponent($componentName)) {
user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName'" user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName'"
. " on class '$this->class'", E_USER_ERROR); . " on class '$this->class'", E_USER_ERROR);
} }
@ -1575,6 +1575,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
); );
} }
if($filter !== null || $sort !== null || $limit !== null) {
Deprecation::notice('3.2', 'The $filter, $sort and $limit parameters for DataObject::getComponents()
have been deprecated. Please manipluate the returned list directly.', Deprecation::SCOPE_GLOBAL);
}
// If we haven't been written yet, we can't save these relations, so use a list that handles this case // If we haven't been written yet, we can't save these relations, so use a list that handles this case
if(!$this->ID) { if(!$this->ID) {
if(!isset($this->unsavedRelations[$componentName])) { if(!isset($this->unsavedRelations[$componentName])) {
@ -1647,7 +1652,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/ */
public function getRemoteJoinField($component, $type = 'has_many', &$polymorphic = false) { public function getRemoteJoinField($component, $type = 'has_many', &$polymorphic = false) {
// Extract relation from current object // Extract relation from current object
$remoteClass = $this->$type($component, false); if($type === 'has_many') {
$remoteClass = $this->hasManyComponent($component, false);
} else {
$remoteClass = $this->belongsToComponent($component, false);
}
if(empty($remoteClass)) { if(empty($remoteClass)) {
throw new Exception("Unknown $type component '$component' on class '$this->class'"); throw new Exception("Unknown $type component '$component' on class '$this->class'");
} }
@ -1716,8 +1726,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* *
* @todo Implement query-params * @todo Implement query-params
*/ */
public function getManyManyComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") { public function getManyManyComponents($componentName, $filter = null, $sort = null, $join = null, $limit = null) {
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName); list($parentClass, $componentClass, $parentField, $componentField, $table)
= $this->manyManyComponent($componentName);
if($filter !== null || $sort !== null || $join !== null || $limit !== null) {
Deprecation::notice('3.2', 'The $filter, $sort, $join and $limit parameters for
DataObject::getManyManyComponents() have been deprecated.
Please manipluate the returned list directly.', Deprecation::SCOPE_GLOBAL);
}
// If we haven't been written yet, we can't save these relations, so use a list that handles this case // If we haven't been written yet, we can't save these relations, so use a list that handles this case
if(!$this->ID) { if(!$this->ID) {
@ -1730,7 +1747,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$result = ManyManyList::create( $result = ManyManyList::create(
$componentClass, $table, $componentField, $parentField, $componentClass, $table, $componentField, $parentField,
$this->many_many_extraFields($componentName) $this->manyManyExtraFieldsForComponent($componentName)
); );
if($this->model) $result->setDataModel($this->model); if($this->model) $result->setDataModel($this->model);
@ -1743,64 +1760,91 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
->limit($limit); ->limit($limit);
} }
/**
* @deprecated 4.0 Method has been replaced by hasOne() and hasOneComponent()
* @param string $component
* @return array|null
*/
public function has_one($component = null) {
if($component) {
Deprecation::notice('3.2', 'Please use hasOneComponent() instead');
return $this->hasOneComponent($component);
}
Deprecation::notice('3.2', 'Please use hasOne() instead');
return $this->hasOne();
}
/** /**
* Return the class of a one-to-one component. If $component is null, return all of the one-to-one components and * Return the class of a one-to-one component. If $component is null, return all of the one-to-one components and
* their classes. If the selected has_one is a polymorphic field then 'DataObject' will be returned for the type. * their classes. If the selected has_one is a polymorphic field then 'DataObject' will be returned for the type.
* *
* @param string $component Name of component * @param string $component Deprecated - Name of component
* * @return string|array The class of the one-to-one component, or an array of all one-to-one components and
* @return string|array The class of the one-to-one component, or an array of all one-to-one components and their * their classes.
* classes.
*/ */
public function has_one($component = null) { public function hasOne($component = null) {
$classes = ClassInfo::ancestry($this);
foreach($classes as $class) {
// Wait until after we reach DataObject
if(in_array($class, array('Object', 'ViewableData', 'DataObject'))) continue;
if($component) { if($component) {
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED); Deprecation::notice(
'3.2',
'Please use DataObject::hasOneComponent() instead of passing a component name to hasOne()',
Deprecation::SCOPE_GLOBAL
);
return $this->hasOneComponent($component);
}
if(isset($hasOne[$component])) { return (array)Config::inst()->get($this->class, 'has_one', Config::INHERITED);
return $hasOne[$component];
} }
} else {
$newItems = (array)Config::inst()->get($class, 'has_one', Config::UNINHERITED); /**
// Validate the data * Return data for a specific has_one component.
foreach($newItems as $k => $v) { * @param string $component
if(!is_string($k) || is_numeric($k) || !is_string($v)) { * @return string|null
user_error("$class::\$has_one has a bad entry: " */
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a" public function hasOneComponent($component) {
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR); $hasOnes = (array)Config::inst()->get($this->class, 'has_one', Config::INHERITED);
if(isset($hasOnes[$component])) {
return $hasOnes[$component];
} }
} }
$items = isset($items) ? array_merge($newItems, (array)$items) : $newItems;
/**
* @deprecated 4.0 Method has been replaced by belongsTo() and belongsToComponent()
* @param string $component
* @param bool $classOnly
* @return array|null
*/
public function belongs_to($component = null, $classOnly = true) {
if($component) {
Deprecation::notice('3.2', 'Please use belongsToComponent() instead');
return $this->belongsToComponent($component, $classOnly);
} }
}
return isset($items) ? $items : null; Deprecation::notice('3.2', 'Please use belongsTo() instead');
return $this->belongsTo(null, $classOnly);
} }
/** /**
* Returns the class of a remote belongs_to relationship. If no component is specified a map of all components and * Returns the class of a remote belongs_to relationship. If no component is specified a map of all components and
* their class name will be returned. * their class name will be returned.
* *
* @param string $component * @param string $component - Name of component
* @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have * @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
* the field data stripped off. It defaults to TRUE. * the field data stripped off. It defaults to TRUE.
* @return string|array * @return string|array
*/ */
public function belongs_to($component = null, $classOnly = true) { public function belongsTo($component = null, $classOnly = true) {
$belongsTo = $this->config()->belongs_to;
if($component) { if($component) {
if($belongsTo && array_key_exists($component, $belongsTo)) { Deprecation::notice(
$belongsTo = $belongsTo[$component]; '3.2',
} else { 'Please use DataObject::belongsToComponent() instead of passing a component name to belongsTo()',
return false; Deprecation::SCOPE_GLOBAL
} );
return $this->belongsToComponent($component, $classOnly);
} }
$belongsTo = (array)Config::inst()->get($this->class, 'belongs_to', Config::INHERITED);
if($belongsTo && $classOnly) { if($belongsTo && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $belongsTo); return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
} else { } else {
@ -1808,9 +1852,28 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
} }
/**
* Return data for a specific belongs_to component.
* @param string $component
* @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
* the field data stripped off. It defaults to TRUE.
* @return string|false
*/
public function belongsToComponent($component, $classOnly = true) {
$belongsTo = (array)Config::inst()->get($this->class, 'belongs_to', Config::INHERITED);
if($belongsTo && array_key_exists($component, $belongsTo)) {
$belongsTo = $belongsTo[$component];
} else {
return false;
}
return ($classOnly) ? preg_replace('/(.+)?\..+/', '$1', $belongsTo) : $belongsTo;
}
/** /**
* Return all of the database fields defined in self::$db and all the parent classes. * Return all of the database fields defined in self::$db and all the parent classes.
* Doesn't include any fields specified by self::$has_one. Use $this->has_one() to get these fields * Doesn't include any fields specified by self::$has_one. Use $this->hasOne() to get these fields
* *
* @param string $fieldName Limit the output to a specific field name * @param string $fieldName Limit the output to a specific field name
* @return array The database fields * @return array The database fields
@ -1837,15 +1900,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $dbItems[$fieldName]; return $dbItems[$fieldName];
} }
} else { } else {
// Validate the data
foreach($dbItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
user_error("$class::\$db has a bad entry: "
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
. " property name, and the map value should be the property type.", E_USER_ERROR);
}
}
$items = isset($items) ? array_merge((array) $items, $dbItems) : $dbItems; $items = isset($items) ? array_merge((array) $items, $dbItems) : $dbItems;
} }
} }
@ -1853,26 +1907,42 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $items; return $items;
} }
/**
* @deprecated 4.0 Method has been replaced by hasMany() and hasManyComponent()
* @param string $component
* @param bool $classOnly
* @return array|null
*/
public function has_many($component = null, $classOnly = true) {
if($component) {
Deprecation::notice('3.2', 'Please use hasManyComponent() instead');
return $this->hasManyComponent($component, $classOnly);
}
Deprecation::notice('3.2', 'Please use hasMany() instead');
return $this->hasMany(null, $classOnly);
}
/** /**
* Gets the class of a one-to-many relationship. If no $component is specified then an array of all the one-to-many * Gets the class of a one-to-many relationship. If no $component is specified then an array of all the one-to-many
* relationships and their classes will be returned. * relationships and their classes will be returned.
* *
* @param string $component Name of component * @param string $component Deprecated - Name of component
* @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have * @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
* the field data stripped off. It defaults to TRUE. * the field data stripped off. It defaults to TRUE.
* @return string|array * @return string|array|false
*/ */
public function has_many($component = null, $classOnly = true) { public function hasMany($component = null, $classOnly = true) {
$hasMany = $this->config()->has_many;
if($component) { if($component) {
if($hasMany && array_key_exists($component, $hasMany)) { Deprecation::notice(
$hasMany = $hasMany[$component]; '3.2',
} else { 'Please use DataObject::hasManyComponent() instead of passing a component name to hasMany()',
return false; Deprecation::SCOPE_GLOBAL
} );
return $this->hasManyComponent($component, $classOnly);
} }
$hasMany = (array)Config::inst()->get($this->class, 'has_many', Config::INHERITED);
if($hasMany && $classOnly) { if($hasMany && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $hasMany); return preg_replace('/(.+)?\..+/', '$1', $hasMany);
} else { } else {
@ -1880,92 +1950,123 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
} }
/**
* Return data for a specific has_many component.
* @param string $component
* @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
* the field data stripped off. It defaults to TRUE.
* @return string|false
*/
public function hasManyComponent($component, $classOnly = true) {
$hasMany = (array)Config::inst()->get($this->class, 'has_many', Config::INHERITED);
if($hasMany && array_key_exists($component, $hasMany)) {
$hasMany = $hasMany[$component];
} else {
return false;
}
return ($classOnly) ? preg_replace('/(.+)?\..+/', '$1', $hasMany) : $hasMany;
}
/**
* @deprecated 4.0 Method has been replaced by manyManyExtraFields() and
* manyManyExtraFieldsForComponent()
* @param string $component
* @return array
*/
public function many_many_extraFields($component = null) {
if($component) {
Deprecation::notice('3.2', 'Please use manyManyExtraFieldsForComponent() instead');
return $this->manyManyExtraFieldsForComponent($component);
}
Deprecation::notice('3.2', 'Please use manyManyExtraFields() instead');
return $this->manyManyExtraFields();
}
/** /**
* Return the many-to-many extra fields specification. * Return the many-to-many extra fields specification.
* *
* If you don't specify a component name, it returns all * If you don't specify a component name, it returns all
* extra fields for all components available. * extra fields for all components available.
* *
* @param string $component Name of component * @param string $component Deprecated - Name of component
* @return array * @return array|null
*/ */
public function many_many_extraFields($component = null) { public function manyManyExtraFields($component = null) {
$classes = ClassInfo::ancestry($this);
foreach($classes as $class) {
if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
$relationName = null;
// Find extra fields for one component
if($component) { if($component) {
$SNG_class = singleton($class); Deprecation::notice(
$extraFields = $SNG_class->stat('many_many_extraFields'); '3.2',
'Please use DataObject::manyManyExtraFieldsForComponent() instead of passing a component name
to manyManyExtraFields()',
Deprecation::SCOPE_GLOBAL
);
return $this->manyManyExtraFieldsForComponent($component);
}
// Extra fields are immediately available on this class return Config::inst()->get($this->class, 'many_many_extraFields', Config::INHERITED);
}
/**
* Return the many-to-many extra fields specification for a specific component.
* @param string $component
* @return array|null
*/
public function manyManyExtraFieldsForComponent($component) {
// Get all many_many_extraFields defined in this class or parent classes
$extraFields = (array)Config::inst()->get($this->class, 'many_many_extraFields', Config::INHERITED);
// Extra fields are immediately available
if(isset($extraFields[$component])) { if(isset($extraFields[$component])) {
return $extraFields[$component]; return $extraFields[$component];
} }
$manyMany = $SNG_class->stat('many_many'); // Check this class' belongs_many_manys to see if any of their reverse associations contain extra fields
$manyMany = (array)Config::inst()->get($this->class, 'belongs_many_many', Config::INHERITED);
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null; $candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) { if($candidate) {
$SNG_candidate = singleton($candidate); $relationName = null;
$candidateManyMany = $SNG_candidate->stat('belongs_many_many'); // Extract class and relation name from dot-notation
if(strpos($candidate, '.') !== false) {
// Find the relation given the class list($candidate, $relationName) = explode('.', $candidate, 2);
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
$relationName = $relation;
break;
}
} }
if($relationName) { // If we've not already found the relation name from dot notation, we need to find a relation that points
$extraFields = $SNG_candidate->stat('many_many_extraFields'); // back to this class. As there's no dot-notation, there can only be one relation pointing to this class,
if(isset($extraFields[$relationName])) { // so it's safe to assume that it's the correct one
return $extraFields[$relationName]; if(!$relationName) {
} $candidateManyManys = (array)Config::inst()->get($candidate, 'many_many', Config::UNINHERITED);
}
}
$manyMany = $SNG_class->stat('belongs_many_many'); foreach($candidateManyManys as $relation => $relatedClass) {
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null; if($relatedClass === $this->class) {
if($candidate) {
$SNG_candidate = singleton($candidate);
$candidateManyMany = $SNG_candidate->stat('many_many');
// Find the relation given the class
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
$relationName = $relation; $relationName = $relation;
} }
} }
}
$extraFields = $SNG_candidate->stat('many_many_extraFields'); // If we've found a matching relation on the target class, see if we can find extra fields for it
$extraFields = (array)Config::inst()->get($candidate, 'many_many_extraFields', Config::UNINHERITED);
if(isset($extraFields[$relationName])) { if(isset($extraFields[$relationName])) {
return $extraFields[$relationName]; return $extraFields[$relationName];
} }
} }
} else { return isset($items) ? $items : null;
// Find all the extra fields for all components
$newItems = eval("return (array){$class}::\$many_many_extraFields;");
foreach($newItems as $k => $v) {
if(!is_array($v)) {
user_error(
"$class::\$many_many_extraFields has a bad entry: "
. var_export($k, true) . " => " . var_export($v, true)
. ". Each many_many_extraFields entry should map to a field specification array.",
E_USER_ERROR
);
}
} }
return isset($items) ? array_merge($newItems, $items) : $newItems; /**
} * @deprecated 4.0 Method has been renamed to manyMany()
* @param string $component
* @return array|null
*/
public function many_many($component = null) {
if($component) {
Deprecation::notice('3.2', 'Please use manyManyComponent() instead');
return $this->manyManyComponent($component);
} }
Deprecation::notice('3.2', 'Please use manyMany() instead');
return $this->manyMany();
} }
/** /**
@ -1973,20 +2074,44 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* The return value is an array of (parentclass, childclass). If $component is null, then all many-many * The return value is an array of (parentclass, childclass). If $component is null, then all many-many
* components are returned. * components are returned.
* *
* @param string $component Name of component * @see DataObject::manyManyComponent()
* * @param string $component Deprecated - Name of component
* @return array An array of (parentclass, childclass), or an array of all many-many components * @return array|null An array of (parentclass, childclass), or an array of all many-many components
*/ */
public function many_many($component = null) { public function manyMany($component = null) {
$classes = ClassInfo::ancestry($this);
foreach($classes as $class) {
// Wait until after we reach DataObject
if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
if($component) { if($component) {
Deprecation::notice(
'3.2',
'Please use DataObject::manyManyComponent() instead of passing a component name to manyMany()',
Deprecation::SCOPE_GLOBAL
);
return $this->manyManyComponent($component);
}
$manyManys = (array)Config::inst()->get($this->class, 'many_many', Config::INHERITED);
$belongsManyManys = (array)Config::inst()->get($this->class, 'belongs_many_many', Config::INHERITED);
$items = array_merge($manyManys, $belongsManyManys);
return $items;
}
/**
* Return information about a specific many_many component. Returns a numeric array of:
* array(
* <classname>, The class that relation is defined in e.g. "Product"
* <candidateName>, The target class of the relation e.g. "Category"
* <parentField>, The field name pointing to <classname>'s table e.g. "ProductID"
* <childField>, The field name pointing to <candidatename>'s table e.g. "CategoryID"
* <joinTable> The join table between the two classes e.g. "Product_Categories"
* )
* @param string $component The component name
* @return array|null
*/
public function manyManyComponent($component) {
$classes = $this->getClassAncestry();
foreach($classes as $class) {
$manyMany = Config::inst()->get($class, 'many_many', Config::UNINHERITED); $manyMany = Config::inst()->get($class, 'many_many', Config::UNINHERITED);
// Try many_many // Check if the component is defined in many_many on this class
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null; $candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) { if($candidate) {
$parentField = $class . "ID"; $parentField = $class . "ID";
@ -1994,55 +2119,48 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return array($class, $candidate, $parentField, $childField, "{$class}_$component"); return array($class, $candidate, $parentField, $childField, "{$class}_$component");
} }
// Try belongs_many_many // Check if the component is defined in belongs_many_many on this class
$belongsManyMany = Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED); $belongsManyMany = Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
$candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null; $candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null;
if($candidate) { if($candidate) {
// Extract class and relation name from dot-notation
if(strpos($candidate, '.') !== false) {
list($candidate, $relationName) = explode('.', $candidate, 2);
}
$childField = $candidate . "ID"; $childField = $candidate . "ID";
// We need to find the inverse component name // We need to find the inverse component name
$otherManyMany = Config::inst()->get($candidate, 'many_many', Config::UNINHERITED); $otherManyMany = Config::inst()->get($candidate, 'many_many', Config::UNINHERITED);
if(!$otherManyMany) { if(!$otherManyMany) {
user_error("Inverse component of $candidate not found ({$this->class})", E_USER_ERROR); throw new LogicException("Inverse component of $candidate not found ({$this->class})");
} }
// If we've got a relation name (extracted from dot-notation), we can already work out
// the join table and candidate class name...
if(isset($relationName) && isset($otherManyMany[$relationName])) {
$candidateClass = $otherManyMany[$relationName];
$joinTable = "{$candidate}_{$relationName}";
} else {
// ... otherwise, we need to loop over the many_manys and find a relation that
// matches up to this class
foreach($otherManyMany as $inverseComponentName => $candidateClass) { foreach($otherManyMany as $inverseComponentName => $candidateClass) {
if($candidateClass == $class || is_subclass_of($class, $candidateClass)) { if($candidateClass == $class || is_subclass_of($class, $candidateClass)) {
$joinTable = "{$candidate}_{$inverseComponentName}";
break;
}
}
}
// If we could work out the join table, we've got all the info we need
if(isset($joinTable)) {
$parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID"; $parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID";
return array($class, $candidate, $parentField, $childField, $joinTable);
return array($class, $candidate, $parentField, $childField,
"{$candidate}_$inverseComponentName");
}
}
user_error("Orphaned \$belongs_many_many value for $this->class.$component", E_USER_ERROR);
}
} else {
$newItems = (array)Config::inst()->get($class, 'many_many', Config::UNINHERITED);
// Validate the data
foreach($newItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
user_error("$class::\$many_many has a bad entry: "
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
}
}
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
$newItems = (array)Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
// Validate the data
foreach($newItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
user_error("$class::\$belongs_many_many has a bad entry: "
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
}
} }
$items = isset($items) ? array_merge($newItems, $items) : $newItems; throw new LogicException("Orphaned \$belongs_many_many value for $this->class.$component");
} }
} }
return isset($items) ? $items : null;
} }
/** /**
@ -2534,7 +2652,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return ( return (
array_key_exists($field, $this->record) array_key_exists($field, $this->record)
|| $this->db($field) || $this->db($field)
|| (substr($field,-2) == 'ID') && $this->has_one(substr($field,0, -2)) || (substr($field,-2) == 'ID') && $this->hasOneComponent(substr($field,0, -2))
|| $this->hasMethod("get{$field}") || $this->hasMethod("get{$field}")
); );
} }
@ -2623,7 +2741,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
if(Permission::checkMember($member, "ADMIN")) return true; if(Permission::checkMember($member, "ADMIN")) return true;
if($this->many_many('Can' . $perm)) { if($this->manyManyComponent('Can' . $perm)) {
if($this->ParentID && $this->SecurityType == 'Inherit') { if($this->ParentID && $this->SecurityType == 'Inherit') {
if(!($p = $this->Parent)) { if(!($p = $this->Parent)) {
return false; return false;
@ -2807,12 +2925,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $obj; return $obj;
// Special case for has_one relationships // Special case for has_one relationships
} else if(preg_match('/ID$/', $fieldName) && $this->has_one(substr($fieldName,0,-2))) { } else if(preg_match('/ID$/', $fieldName) && $this->hasOneComponent(substr($fieldName,0,-2))) {
$val = $this->$fieldName; $val = $this->$fieldName;
return DBField::create_field('ForeignKey', $val, $fieldName, $this); return DBField::create_field('ForeignKey', $val, $fieldName, $this);
// has_one for polymorphic relations do not end in ID // has_one for polymorphic relations do not end in ID
} else if(($type = $this->has_one($fieldName)) && ($type === 'DataObject')) { } else if(($type = $this->hasOneComponent($fieldName)) && ($type === 'DataObject')) {
$val = $this->$fieldName(); $val = $this->$fieldName();
return DBField::create_field('PolymorphicForeignKey', $val, $fieldName, $this); return DBField::create_field('PolymorphicForeignKey', $val, $fieldName, $this);
@ -2910,16 +3028,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return String * @return String
*/ */
public function getReverseAssociation($className) { public function getReverseAssociation($className) {
if (is_array($this->many_many())) { if (is_array($this->manyMany())) {
$many_many = array_flip($this->many_many()); $many_many = array_flip($this->manyMany());
if (array_key_exists($className, $many_many)) return $many_many[$className]; if (array_key_exists($className, $many_many)) return $many_many[$className];
} }
if (is_array($this->has_many())) { if (is_array($this->hasMany())) {
$has_many = array_flip($this->has_many()); $has_many = array_flip($this->hasMany());
if (array_key_exists($className, $has_many)) return $has_many[$className]; if (array_key_exists($className, $has_many)) return $has_many[$className];
} }
if (is_array($this->has_one())) { if (is_array($this->hasOne())) {
$has_one = array_flip($this->has_one()); $has_one = array_flip($this->hasOne());
if (array_key_exists($className, $has_one)) return $has_one[$className]; if (array_key_exists($className, $has_one)) return $has_one[$className];
} }
@ -3191,6 +3309,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$indexes = $this->databaseIndexes(); $indexes = $this->databaseIndexes();
// Validate relationship configuration
$this->validateModelDefinitions();
if($fields) { if($fields) {
$hasAutoIncPK = ($this->class == ClassInfo::baseDataClass($this->class)); $hasAutoIncPK = ($this->class == ClassInfo::baseDataClass($this->class));
DB::require_table($this->class, $fields, $indexes, $hasAutoIncPK, $this->stat('create_table_options'), DB::require_table($this->class, $fields, $indexes, $hasAutoIncPK, $this->stat('create_table_options'),
@ -3227,6 +3348,42 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$this->extend('augmentDatabase', $dummy); $this->extend('augmentDatabase', $dummy);
} }
/**
* Validate that the configured relations for this class use the correct syntaxes
* @throws LogicException
*/
protected function validateModelDefinitions() {
$modelDefinitions = array(
'db' => Config::inst()->get($this->class, 'db', Config::UNINHERITED),
'has_one' => Config::inst()->get($this->class, 'has_one', Config::UNINHERITED),
'has_many' => Config::inst()->get($this->class, 'has_many', Config::UNINHERITED),
'belongs_to' => Config::inst()->get($this->class, 'belongs_to', Config::UNINHERITED),
'many_many' => Config::inst()->get($this->class, 'many_many', Config::UNINHERITED),
'belongs_many_many' => Config::inst()->get($this->class, 'belongs_many_many', Config::UNINHERITED),
'many_many_extraFields' => Config::inst()->get($this->class, 'many_many_extraFields', Config::UNINHERITED)
);
foreach($modelDefinitions as $defType => $relations) {
if( ! $relations) continue;
foreach($relations as $k => $v) {
if($defType === 'many_many_extraFields') {
if(!is_array($v)) {
throw new LogicException("$this->class::\$many_many_extraFields has a bad entry: "
. var_export($k, true) . " => " . var_export($v, true)
. ". Each many_many_extraFields entry should map to a field specification array.");
}
} else {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
throw new LogicException("$this->class::$defType has a bad entry: "
. var_export($k, true). " => " . var_export($v, true) . ". Each map key should be a
relationship name, and the map value should be the data class to join to.");
}
}
}
}
}
/** /**
* Add default records to database. This function is called whenever the * Add default records to database. This function is called whenever the
* database is built, after the database tables have all been created. Overload * database is built, after the database tables have all been created. Overload
@ -3783,7 +3940,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/ */
public function hasValue($field, $arguments = null, $cache = true) { public function hasValue($field, $arguments = null, $cache = true) {
// has_one fields should not use dbObject to check if a value is given // has_one fields should not use dbObject to check if a value is given
if(!$this->has_one($field) && ($obj = $this->dbObject($field))) { if(!$this->hasOneComponent($field) && ($obj = $this->dbObject($field))) {
return $obj->exists(); return $obj->exists();
} else { } else {
return parent::hasValue($field, $arguments, $cache); return parent::hasValue($field, $arguments, $cache);

View File

@ -645,10 +645,9 @@ class DataQuery {
foreach($relation as $rel) { foreach($relation as $rel) {
$model = singleton($modelClass); $model = singleton($modelClass);
if ($component = $model->has_one($rel)) { if ($component = $model->hasOneComponent($rel)) {
if(!$this->query->isJoinedTo($component)) { if(!$this->query->isJoinedTo($component)) {
$has_one = array_flip($model->has_one()); $foreignKey = $rel;
$foreignKey = $has_one[$component];
$realModelClass = ClassInfo::table_for_object_field($modelClass, "{$foreignKey}ID"); $realModelClass = ClassInfo::table_for_object_field($modelClass, "{$foreignKey}ID");
$this->query->addLeftJoin($component, $this->query->addLeftJoin($component,
"\"$component\".\"ID\" = \"{$realModelClass}\".\"{$foreignKey}ID\""); "\"$component\".\"ID\" = \"{$realModelClass}\".\"{$foreignKey}ID\"");
@ -669,7 +668,7 @@ class DataQuery {
} }
$modelClass = $component; $modelClass = $component;
} elseif ($component = $model->has_many($rel)) { } elseif ($component = $model->hasManyComponent($rel)) {
if(!$this->query->isJoinedTo($component)) { if(!$this->query->isJoinedTo($component)) {
$ancestry = $model->getClassAncestry(); $ancestry = $model->getClassAncestry();
$foreignKey = $model->getRemoteJoinField($rel); $foreignKey = $model->getRemoteJoinField($rel);
@ -691,7 +690,7 @@ class DataQuery {
} }
$modelClass = $component; $modelClass = $component;
} elseif ($component = $model->many_many($rel)) { } elseif ($component = $model->manyManyComponent($rel)) {
list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component; list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
$parentBaseClass = ClassInfo::baseDataClass($parentClass); $parentBaseClass = ClassInfo::baseDataClass($parentClass);
$componentBaseClass = ClassInfo::baseDataClass($componentClass); $componentBaseClass = ClassInfo::baseDataClass($componentClass);

View File

@ -28,7 +28,7 @@ class ForeignKey extends Int {
public function scaffoldFormField($title = null, $params = null) { public function scaffoldFormField($title = null, $params = null) {
$relationName = substr($this->name,0,-2); $relationName = substr($this->name,0,-2);
$hasOneClass = $this->object->has_one($relationName); $hasOneClass = $this->object->hasOneComponent($relationName);
if($hasOneClass && singleton($hasOneClass) instanceof Image) { if($hasOneClass && singleton($hasOneClass) instanceof Image) {
$field = new UploadField($relationName, $title); $field = new UploadField($relationName, $title);

View File

@ -17,6 +17,7 @@ abstract class StringField extends DBField {
*/ */
private static $casting = array( private static $casting = array(
"LimitCharacters" => "Text", "LimitCharacters" => "Text",
"LimitCharactersToClosestWord" => "Text",
'LimitWordCount' => 'Text', 'LimitWordCount' => 'Text',
'LimitWordCountXML' => 'HTMLText', 'LimitWordCountXML' => 'HTMLText',
"LowerCase" => "Text", "LowerCase" => "Text",
@ -116,7 +117,6 @@ abstract class StringField extends DBField {
*/ */
public function LimitCharacters($limit = 20, $add = '...') { public function LimitCharacters($limit = 20, $add = '...') {
$value = trim($this->value); $value = trim($this->value);
if($this->stat('escape_type') == 'xml') { if($this->stat('escape_type') == 'xml') {
$value = strip_tags($value); $value = strip_tags($value);
$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8'); $value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
@ -126,10 +126,39 @@ abstract class StringField extends DBField {
} else { } else {
$value = (mb_strlen($value) > $limit) ? mb_substr($value, 0, $limit) . $add : $value; $value = (mb_strlen($value) > $limit) ? mb_substr($value, 0, $limit) . $add : $value;
} }
return $value; return $value;
} }
/**
* Limit this field's content by a number of characters and truncate
* the field to the closest complete word. All HTML tags are stripped
* from the field.
*
* @param int $limit Number of characters to limit by
* @param string $add Ellipsis to add to the end of truncated string
* @return string
*/
public function LimitCharactersToClosestWord($limit = 20, $add = '...') {
// Strip HTML tags if they exist in the field
$this->value = strip_tags($this->value);
// Determine if value exceeds limit before limiting characters
$exceedsLimit = mb_strlen($this->value) > $limit;
// Limit to character limit
$value = $this->LimitCharacters($limit, '');
// If value exceeds limit, strip punctuation off the end to the last space and apply ellipsis
if($exceedsLimit) {
$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
$value = rtrim(mb_substr($value, 0, mb_strrpos($value, " ")), "/[\.,-\/#!$%\^&\*;:{}=\-_`~()]\s") . $add;
$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
}
return $value;
}
/** /**
* Limit this field's content by a number of words. * Limit this field's content by a number of words.

View File

@ -271,8 +271,13 @@ abstract class SQLConditionalExpression extends SQLExpression {
$filter = "(" . implode(") AND (", $join['filter']) . ")"; $filter = "(" . implode(") AND (", $join['filter']) . ")";
} }
$table = strpos(strtoupper($join['table']), 'SELECT') ? $join['table'] : "\"" . $join['table'] . "\""; // Ensure tables are quoted, unless the table is actually a sub-select
$aliasClause = ($alias != $join['table']) ? " AS \"$alias\"" : ""; $table = preg_match('/\bSELECT\b/i', $join['table'])
? $join['table']
: "\"{$join['table']}\"";
$aliasClause = ($alias != $join['table'])
? " AS \"{$alias}\""
: "";
$joins[$alias] = strtoupper($join['type']) . " JOIN " . $table . "$aliasClause ON $filter"; $joins[$alias] = strtoupper($join['type']) . " JOIN " . $table . "$aliasClause ON $filter";
if(!empty($join['parameters'])) { if(!empty($join['parameters'])) {
$parameters = array_merge($parameters, $join['parameters']); $parameters = array_merge($parameters, $join['parameters']);

View File

@ -580,6 +580,7 @@ $gf_grid_x: 16px;
span.non-sortable { span.non-sortable {
display:block; display:block;
padding: 6px 8px;
} }
} }

View File

@ -10,6 +10,10 @@
clear: both; clear: both;
} }
.description {
margin-left: 0;
}
.middleColumn { .middleColumn {
// TODO .middleColumn styling should probably be theme specific (eg cms ui will look different than blackcandy) // TODO .middleColumn styling should probably be theme specific (eg cms ui will look different than blackcandy)
// so we should move this style into the cms and black candy files // so we should move this style into the cms and black candy files

View File

@ -228,7 +228,7 @@ abstract class SearchFilter extends Object {
* @return DataQuery * @return DataQuery
*/ */
protected function applyMany(DataQuery $query) { protected function applyMany(DataQuery $query) {
throw new InvalidArgumentException(get_class($this) . "can't be used to filter by a list of items."); throw new InvalidArgumentException(get_class($this) . " can't be used to filter by a list of items.");
} }
/** /**
@ -264,7 +264,7 @@ abstract class SearchFilter extends Object {
* @return DataQuery * @return DataQuery
*/ */
protected function excludeMany(DataQuery $query) { protected function excludeMany(DataQuery $query) {
throw new InvalidArgumentException(get_class($this) . "can't be used to filter by a list of items."); throw new InvalidArgumentException(get_class($this) . " can't be used to filter by a list of items.");
} }
/** /**

View File

@ -50,9 +50,19 @@ class BasicAuth {
$isRunningTests = (class_exists('SapphireTest', false) && SapphireTest::is_running_test()); $isRunningTests = (class_exists('SapphireTest', false) && SapphireTest::is_running_test());
if(!Security::database_is_ready() || (Director::is_cli() && !$isRunningTests)) return true; if(!Security::database_is_ready() || (Director::is_cli() && !$isRunningTests)) return true;
/*
* Enable HTTP Basic authentication workaround for PHP running in CGI mode with Apache
* Depending on server configuration the auth header may be in HTTP_AUTHORIZATION or
* REDIRECT_HTTP_AUTHORIZATION
*
* The follow rewrite rule must be in the sites .htaccess file to enable this workaround
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
*/
$authHeader = (isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] :
(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : null));
$matches = array(); $matches = array();
if (isset($_SERVER['HTTP_AUTHORIZATION']) && if ($authHeader &&
preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) { preg_match('/Basic\s+(.*)$/i', $authHeader, $matches)) {
list($name, $password) = explode(':', base64_decode($matches[1])); list($name, $password) = explode(':', base64_decode($matches[1]));
$_SERVER['PHP_AUTH_USER'] = strip_tags($name); $_SERVER['PHP_AUTH_USER'] = strip_tags($name);
$_SERVER['PHP_AUTH_PW'] = strip_tags($password); $_SERVER['PHP_AUTH_PW'] = strip_tags($password);

View File

@ -120,6 +120,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
'Salt', 'Salt',
'NumVisit' 'NumVisit'
); );
/** /**
* @config * @config
* @var Array See {@link set_title_columns()} * @var Array See {@link set_title_columns()}
@ -513,7 +514,13 @@ class Member extends DataObject implements TemplateGlobalProvider {
// Don't bother trying this multiple times // Don't bother trying this multiple times
self::$_already_tried_to_auto_log_in = true; self::$_already_tried_to_auto_log_in = true;
if(strpos(Cookie::get('alc_enc'), ':') && !Session::get("loggedInAs")) { if(strpos(Cookie::get('alc_enc'), ':') === false
|| Session::get("loggedInAs")
|| !Security::database_is_ready()
) {
return;
}
list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2); list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
$member = DataObject::get_by_id("Member", $uid); $member = DataObject::get_by_id("Member", $uid);
@ -539,13 +546,14 @@ class Member extends DataObject implements TemplateGlobalProvider {
$hash = $member->encryptWithUserSettings($token); $hash = $member->encryptWithUserSettings($token);
$member->RememberLoginToken = $hash; $member->RememberLoginToken = $hash;
Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true); Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true);
$member->NumVisit++;
$member->write(); $member->write();
// Audit logging hook // Audit logging hook
$member->extend('memberAutoLoggedIn'); $member->extend('memberAutoLoggedIn');
} }
} }
}
/** /**
* Logs this member out. * Logs this member out.
@ -1442,8 +1450,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
if(!($member && $member->exists())) return false; if(!($member && $member->exists())) return false;
// If the requesting member is not an admin, but has access to manage members, // If the requesting member is not an admin, but has access to manage members,
// he still can't edit other members with ADMIN permission. // they still can't edit other members with ADMIN permission.
// This is a bit weak, strictly speaking he shouldn't be allowed to // This is a bit weak, strictly speaking they shouldn't be allowed to
// perform any action that could change the password on a member // perform any action that could change the password on a member
// with "higher" permissions than himself, but thats hard to determine. // with "higher" permissions than himself, but thats hard to determine.
if(!Permission::checkMember($member, 'ADMIN') && Permission::checkMember($this, 'ADMIN')) return false; if(!Permission::checkMember($member, 'ADMIN') && Permission::checkMember($this, 'ADMIN')) return false;

View File

@ -227,7 +227,7 @@ JS;
return $this->controller->redirect(Director::absoluteBaseURL() . Security::config()->default_login_dest); return $this->controller->redirect(Director::absoluteBaseURL() . Security::config()->default_login_dest);
} }
// Redirect the user to the page where he came from // Redirect the user to the page where they came from
$member = Member::currentUser(); $member = Member::currentUser();
if($member) { if($member) {
$firstname = Convert::raw2xml($member->FirstName); $firstname = Convert::raw2xml($member->FirstName);

View File

@ -269,7 +269,7 @@ class PermissionCheckboxSetField extends FormField {
$permission->delete(); $permission->delete();
} }
if($fieldname && $record && ($record->has_many($fieldname) || $record->many_many($fieldname))) { if($fieldname && $record && ($record->hasManyComponent($fieldname) || $record->manyManyComponent($fieldname))) {
if(!$record->ID) $record->write(); // We need a record ID to write permissions if(!$record->ID) $record->write(); // We need a record ID to write permissions

View File

@ -97,9 +97,10 @@ class Security extends Controller implements TemplateGlobalProvider {
/** /**
* Default message set used in permission failures. * Default message set used in permission failures.
* *
* @config
* @var array|string * @var array|string
*/ */
private static $default_message_set = ''; private static $default_message_set;
/** /**
* Random secure token, can be used as a crypto key internally. * Random secure token, can be used as a crypto key internally.
@ -198,9 +199,6 @@ class Security extends Controller implements TemplateGlobalProvider {
* If you pass an array, you can use the * If you pass an array, you can use the
* following keys: * following keys:
* - default: The default message * - default: The default message
* - logInAgain: The message to show
* if the user has just
* logged out and the
* - alreadyLoggedIn: The message to * - alreadyLoggedIn: The message to
* show if the user * show if the user
* is already logged * is already logged
@ -231,8 +229,8 @@ class Security extends Controller implements TemplateGlobalProvider {
} else { } else {
// Prepare the messageSet provided // Prepare the messageSet provided
if(!$messageSet) { if(!$messageSet) {
if(self::$default_message_set) { if($configMessageSet = static::config()->get('default_message_set')) {
$messageSet = self::$default_message_set; $messageSet = $configMessageSet;
} else { } else {
$messageSet = array( $messageSet = array(
'default' => _t( 'default' => _t(
@ -246,11 +244,6 @@ class Security extends Controller implements TemplateGlobalProvider {
. "can access that page, you can log in again below.", . "can access that page, you can log in again below.",
"%s will be replaced with a link to log in." "%s will be replaced with a link to log in."
),
'logInAgain' => _t(
'Security.LOGGEDOUT',
"You have been logged out. If you would like to log in again, enter "
. "your credentials below."
) )
); );
} }
@ -420,6 +413,20 @@ class Security extends Controller implements TemplateGlobalProvider {
if($result instanceof SS_HTTPResponse) return $result; if($result instanceof SS_HTTPResponse) return $result;
} }
} }
// If arriving on the login page already logged in, with no security error, and a ReturnURL then redirect
// back. The login message check is neccesary to prevent infinite loops where BackURL links to
// an action that triggers Security::permissionFailure.
// This step is necessary in cases such as automatic redirection where a user is authenticated
// upon landing on an SSL secured site and is automatically logged in, or some other case
// where the user has permissions to continue but is not given the option.
if($this->request->requestVar('BackURL')
&& !$this->getLoginMessage()
&& ($member = Member::currentUser())
&& $member->exists()
) {
return $this->redirectBack();
}
} }
/** /**
@ -530,6 +537,7 @@ class Security extends Controller implements TemplateGlobalProvider {
// Finally, customise the controller to add any form messages and the form. // Finally, customise the controller to add any form messages and the form.
$customisedController = $controller->customise(array( $customisedController = $controller->customise(array(
"Content" => $message,
"Message" => $message, "Message" => $message,
"MessageType" => $messageType, "MessageType" => $messageType,
"Form" => $content, "Form" => $content,
@ -579,7 +587,8 @@ class Security extends Controller implements TemplateGlobalProvider {
* @return Form Returns the lost password form * @return Form Returns the lost password form
*/ */
public function LostPasswordForm() { public function LostPasswordForm() {
return MemberLoginForm::create( $this, return MemberLoginForm::create(
$this,
'LostPasswordForm', 'LostPasswordForm',
new FieldList( new FieldList(
new EmailField('Email', _t('Member.EMAIL', 'Email')) new EmailField('Email', _t('Member.EMAIL', 'Email'))

View File

@ -97,6 +97,16 @@ class DirectorTest extends SapphireTest {
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/"; $_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
Config::inst()->update('Director', 'alternate_base_url', '/mysite/'); Config::inst()->update('Director', 'alternate_base_url', '/mysite/');
//test empty URL
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL(''));
//test absolute - /
$this->assertEquals("$rootURL/", Director::absoluteURL('/'));
//test relative
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL('./'));
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL('.'));
// Test already absolute url // Test already absolute url
$this->assertEquals($rootURL, Director::absoluteURL($rootURL)); $this->assertEquals($rootURL, Director::absoluteURL($rootURL));
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, true)); $this->assertEquals($rootURL, Director::absoluteURL($rootURL, true));
@ -135,8 +145,10 @@ class DirectorTest extends SapphireTest {
// absolute base URLs - you should end them in a / // absolute base URLs - you should end them in a /
Config::inst()->update('Director', 'alternate_base_url', 'http://www.example.org/'); Config::inst()->update('Director', 'alternate_base_url', 'http://www.example.org/');
$_SERVER['REQUEST_URI'] = "http://www.example.org/";
$this->assertEquals('http://www.example.org/', Director::baseURL()); $this->assertEquals('http://www.example.org/', Director::baseURL());
$this->assertEquals('http://www.example.org/', Director::absoluteBaseURL()); $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
$this->assertEquals('http://www.example.org/', Director::absoluteURL(''));
$this->assertEquals('http://www.example.org/subfolder/test', Director::absoluteURL('subfolder/test')); $this->assertEquals('http://www.example.org/subfolder/test', Director::absoluteURL('subfolder/test'));
// Setting it to false restores functionality // Setting it to false restores functionality

View File

@ -160,6 +160,26 @@ class HTTPTest extends FunctionalTest {
*/ */
public function testAbsoluteURLsAttributes() { public function testAbsoluteURLsAttributes() {
$this->withBaseURL('http://www.silverstripe.org/', function($test){ $this->withBaseURL('http://www.silverstripe.org/', function($test){
//empty links
$test->assertEquals(
'<a href="http://www.silverstripe.org/">test</a>',
HTTP::absoluteURLs('<a href="">test</a>')
);
$test->assertEquals(
'<a href="http://www.silverstripe.org/">test</a>',
HTTP::absoluteURLs('<a href="/">test</a>')
);
//relative
$test->assertEquals(
'<a href="http://www.silverstripe.org/">test</a>',
HTTP::absoluteURLs('<a href="./">test</a>')
);
$test->assertEquals(
'<a href="http://www.silverstripe.org/">test</a>',
HTTP::absoluteURLs('<a href=".">test</a>')
);
// links // links
$test->assertEquals( $test->assertEquals(

View File

@ -6,6 +6,14 @@
*/ */
class ClassInfoTest extends SapphireTest { class ClassInfoTest extends SapphireTest {
protected $extraDataObjects = array(
'ClassInfoTest_BaseClass',
'ClassInfoTest_ChildClass',
'ClassInfoTest_GrandChildClass',
'ClassInfoTest_BaseDataClass',
'ClassInfoTest_NoFields',
);
public function testExists() { public function testExists() {
$this->assertTrue(ClassInfo::exists('Object')); $this->assertTrue(ClassInfo::exists('Object'));
$this->assertTrue(ClassInfo::exists('ClassInfoTest')); $this->assertTrue(ClassInfo::exists('ClassInfoTest'));
@ -146,7 +154,7 @@ class ClassInfoTest extends SapphireTest {
* @subpackage tests * @subpackage tests
*/ */
class ClassInfoTest_BaseClass extends DataObject { class ClassInfoTest_BaseClass extends DataObject implements TestOnly {
} }
@ -173,7 +181,7 @@ class ClassInfoTest_GrandChildClass extends ClassInfoTest_ChildClass {
* @subpackage tests * @subpackage tests
*/ */
class ClassInfoTest_BaseDataClass extends DataObject { class ClassInfoTest_BaseDataClass extends DataObject implements TestOnly {
private static $db = array( private static $db = array(
'Title' => 'Varchar' 'Title' => 'Varchar'

View File

@ -254,54 +254,92 @@ class ConfigTest extends SapphireTest {
$this->markTestIncomplete(); $this->markTestIncomplete();
} }
public function testLRUDiscarding() { public function testCacheCleaning() {
$cache = new ConfigTest_Config_LRU(); $cache = new ConfigTest_Config_MemCache();
for ($i = 0; $i < 1000; $i++) $cache->set($i, $i);
$this->assertEquals(1000, count($cache->cache));
$cache->clean();
$this->assertEquals(0, count($cache->cache), 'Clean clears all items');
$this->assertFalse($cache->get(1), 'Clean clears all items');
$cache->set(1, 1, array('Foo'));
$this->assertEquals(1, count($cache->cache));
$this->assertEquals(1, count($cache->tags));
$cache->clean('Foo');
$this->assertEquals(0, count($cache->tags), 'Clean items with matching tag');
$this->assertFalse($cache->get(1), 'Clean items with matching tag');
$cache->set(1, 1, array('Foo', 'Bar'));
$this->assertEquals(2, count($cache->tags));
$this->assertEquals(1, count($cache->cache));
$cache->clean('Bar');
$this->assertEquals(1, count($cache->tags));
$this->assertEquals(0, count($cache->cache), 'Clean items with any single matching tag');
$this->assertFalse($cache->get(1), 'Clean items with any single matching tag');
}
public function testLRUDiscarding() {
$depSettings = Deprecation::dump_settings();
Deprecation::restore_settings(array(
'level' => false,
'version' => false,
'moduleVersions' => false,
));
$cache = new ConfigTest_Config_LRU();
for ($i = 0; $i < Config_LRU::SIZE*2; $i++) $cache->set($i, $i); for ($i = 0; $i < Config_LRU::SIZE*2; $i++) $cache->set($i, $i);
$this->assertEquals( $this->assertEquals(
Config_LRU::SIZE, count($cache->indexing), Config_LRU::SIZE, count($cache->indexing),
'Homogenous usage gives exact discarding' 'Homogenous usage gives exact discarding'
); );
$cache = new ConfigTest_Config_LRU(); $cache = new ConfigTest_Config_LRU();
for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set($i, $i); for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set($i, $i);
for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set(-1, -1); for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set(-1, -1);
$this->assertLessThan( $this->assertLessThan(
Config_LRU::SIZE, count($cache->indexing), Config_LRU::SIZE, count($cache->indexing),
'Heterogenous usage gives sufficient discarding' 'Heterogenous usage gives sufficient discarding'
); );
Deprecation::restore_settings($depSettings);
} }
public function testLRUCleaning() { public function testLRUCleaning() {
$depSettings = Deprecation::dump_settings();
Deprecation::restore_settings(array(
'level' => false,
'version' => false,
'moduleVersions' => false,
));
$cache = new ConfigTest_Config_LRU(); $cache = new ConfigTest_Config_LRU();
for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set($i, $i); for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set($i, $i);
$this->assertEquals(Config_LRU::SIZE, count($cache->indexing)); $this->assertEquals(Config_LRU::SIZE, count($cache->indexing));
$cache->clean(); $cache->clean();
$this->assertEquals(0, count($cache->indexing), 'Clean clears all items'); $this->assertEquals(0, count($cache->indexing), 'Clean clears all items');
$this->assertFalse($cache->get(1), 'Clean clears all items'); $this->assertFalse($cache->get(1), 'Clean clears all items');
$cache->set(1, 1, array('Foo')); $cache->set(1, 1, array('Foo'));
$this->assertEquals(1, count($cache->indexing)); $this->assertEquals(1, count($cache->indexing));
$cache->clean('Foo'); $cache->clean('Foo');
$this->assertEquals(0, count($cache->indexing), 'Clean items with matching tag'); $this->assertEquals(0, count($cache->indexing), 'Clean items with matching tag');
$this->assertFalse($cache->get(1), 'Clean items with matching tag'); $this->assertFalse($cache->get(1), 'Clean items with matching tag');
$cache->set(1, 1, array('Foo', 'Bar')); $cache->set(1, 1, array('Foo', 'Bar'));
$this->assertEquals(1, count($cache->indexing)); $this->assertEquals(1, count($cache->indexing));
$cache->clean('Bar'); $cache->clean('Bar');
$this->assertEquals(0, count($cache->indexing), 'Clean items with any single matching tag'); $this->assertEquals(0, count($cache->indexing), 'Clean items with any single matching tag');
$this->assertFalse($cache->get(1), 'Clean items with any single matching tag'); $this->assertFalse($cache->get(1), 'Clean items with any single matching tag');
Deprecation::restore_settings($depSettings);
} }
} }
class ConfigTest_Config_LRU extends Config_LRU implements TestOnly { class ConfigTest_Config_LRU extends Config_LRU implements TestOnly {
public $cache; public $cache;
public $indexing; public $indexing;
}
class ConfigTest_Config_MemCache extends Config_MemCache implements TestOnly {
public $cache;
public $tags;
} }

View File

@ -280,4 +280,55 @@ PHP
Convert::raw2json($value) Convert::raw2json($value)
); );
} }
public function testXML2Array() {
// Ensure an XML file at risk of entity expansion can be avoided safely
$inputXML = <<<XML
<?xml version="1.0"?>
<!DOCTYPE results [<!ENTITY long "SOME_SUPER_LONG_STRING">]>
<results>
<result>Now include &long; lots of times to expand the in-memory size of this XML structure</result>
<result>&long;&long;&long;</result>
</results>
XML
;
try {
Convert::xml2array($inputXML, true);
} catch(Exception $ex) {}
$this->assertTrue(
isset($ex)
&& $ex instanceof InvalidArgumentException
&& $ex->getMessage() === 'XML Doctype parsing disabled'
);
// Test without doctype validation
$expected = array(
'result' => array(
"Now include SOME_SUPER_LONG_STRING lots of times to expand the in-memory size of this XML structure",
array(
'long' => array(
array(
'long' => 'SOME_SUPER_LONG_STRING'
),
array(
'long' => 'SOME_SUPER_LONG_STRING'
),
array(
'long' => 'SOME_SUPER_LONG_STRING'
)
)
)
)
);
$result = Convert::xml2array($inputXML, false, true);
$this->assertEquals(
$expected,
$result
);
$result = Convert::xml2array($inputXML, false, false);
$this->assertEquals(
$expected,
$result
);
}
} }

View File

@ -28,7 +28,7 @@ class FixtureBlueprintTest extends SapphireTest {
$obj = $blueprint->createObject( $obj = $blueprint->createObject(
'one', 'one',
array( array(
'ManyMany' => 'ManyManyRelation' =>
array( array(
array( array(
"=>FixtureFactoryTest_DataObjectRelation.relation1" => array(), "=>FixtureFactoryTest_DataObjectRelation.relation1" => array(),
@ -48,18 +48,18 @@ class FixtureBlueprintTest extends SapphireTest {
) )
); );
$this->assertEquals(2, $obj->ManyMany()->Count()); $this->assertEquals(2, $obj->ManyManyRelation()->Count());
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID)); $this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation1->ID));
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID)); $this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation2->ID));
$this->assertEquals( $this->assertEquals(
array('Label' => 'This is a label for relation 1'), array('Label' => 'This is a label for relation 1'),
$obj->ManyMany()->getExtraData('ManyMany', $relation1->ID) $obj->ManyManyRelation()->getExtraData('ManyManyRelation', $relation1->ID)
); );
$this->assertEquals( $this->assertEquals(
array('Label' => 'This is a label for relation 2'), array('Label' => 'This is a label for relation 2'),
$obj->ManyMany()->getExtraData('ManyMany', $relation2->ID) $obj->ManyManyRelation()->getExtraData('ManyManyRelation', $relation2->ID)
); );
} }
@ -92,7 +92,7 @@ class FixtureBlueprintTest extends SapphireTest {
$obj = $blueprint->createObject( $obj = $blueprint->createObject(
'one', 'one',
array( array(
'ManyMany' => 'ManyManyRelation' =>
'=>FixtureFactoryTest_DataObjectRelation.relation1,' . '=>FixtureFactoryTest_DataObjectRelation.relation1,' .
'=>FixtureFactoryTest_DataObjectRelation.relation2' '=>FixtureFactoryTest_DataObjectRelation.relation2'
), ),
@ -104,9 +104,9 @@ class FixtureBlueprintTest extends SapphireTest {
) )
); );
$this->assertEquals(2, $obj->ManyMany()->Count()); $this->assertEquals(2, $obj->ManyManyRelation()->Count());
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID)); $this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation1->ID));
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID)); $this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation2->ID));
} }
/** /**
@ -119,7 +119,7 @@ class FixtureBlueprintTest extends SapphireTest {
$obj = $blueprint->createObject( $obj = $blueprint->createObject(
'one', 'one',
array( array(
'ManyMany' => '=>UnknownClass.relation1' 'ManyManyRelation' => '=>UnknownClass.relation1'
), ),
array( array(
'FixtureFactoryTest_DataObjectRelation' => array( 'FixtureFactoryTest_DataObjectRelation' => array(
@ -139,7 +139,7 @@ class FixtureBlueprintTest extends SapphireTest {
$obj = $blueprint->createObject( $obj = $blueprint->createObject(
'one', 'one',
array( array(
'ManyMany' => '=>FixtureFactoryTest_DataObjectRelation.unknown_identifier' 'ManyManyRelation' => '=>FixtureFactoryTest_DataObjectRelation.unknown_identifier'
), ),
array( array(
'FixtureFactoryTest_DataObjectRelation' => array( 'FixtureFactoryTest_DataObjectRelation' => array(
@ -163,7 +163,7 @@ class FixtureBlueprintTest extends SapphireTest {
$obj = $blueprint->createObject( $obj = $blueprint->createObject(
'one', 'one',
array( array(
'ManyMany' => 'FixtureFactoryTest_DataObjectRelation.relation1' 'ManyManyRelation' => 'FixtureFactoryTest_DataObjectRelation.relation1'
), ),
array( array(
'FixtureFactoryTest_DataObjectRelation' => array( 'FixtureFactoryTest_DataObjectRelation' => array(

View File

@ -163,11 +163,11 @@ class FixtureFactoryTest_DataObject extends DataObject implements TestOnly {
); );
private static $many_many = array( private static $many_many = array(
"ManyMany" => "FixtureFactoryTest_DataObjectRelation" "ManyManyRelation" => "FixtureFactoryTest_DataObjectRelation"
); );
private static $many_many_extraFields = array( private static $many_many_extraFields = array(
"ManyMany" => array( "ManyManyRelation" => array(
"Label" => "Varchar" "Label" => "Varchar"
) )
); );

View File

@ -84,15 +84,100 @@ class UploadTest extends SapphireTest {
'error' => UPLOAD_ERR_OK, 'error' => UPLOAD_ERR_OK,
); );
$v = new UploadTest_Validator();
$v->setAllowedMaxFileSize(array('txt' => 10));
// test upload into default folder // test upload into default folder
$u1 = new Upload(); $u1 = new Upload();
$v = new UploadTest_Validator();
$v->setAllowedMaxFileSize(array('txt' => 10));
$u1->setValidator($v); $u1->setValidator($v);
$result = $u1->load($tmpFile); $result = $u1->load($tmpFile);
$this->assertFalse($result, 'Load failed because size was too big'); $this->assertFalse($result, 'Load failed because size was too big');
$v->setAllowedMaxFileSize(array('[doc]' => 10));
$u1->setValidator($v);
$result = $u1->load($tmpFile);
$this->assertFalse($result, 'Load failed because size was too big');
$v->setAllowedMaxFileSize(array('txt' => 200000));
$u1->setValidator($v);
$result = $u1->load($tmpFile);
$this->assertTrue($result, 'Load failed with setting max file size');
// check max file size set by app category
$tmpFileName = 'UploadTest-testUpload.jpg';
$tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName;
file_put_contents($tmpFilePath, $tmpFileContent . $tmpFileContent);
$tmpFile = array(
'name' => $tmpFileName,
'type' => 'image/jpeg',
'size' => filesize($tmpFilePath),
'tmp_name' => $tmpFilePath,
'extension' => 'jpg',
'error' => UPLOAD_ERR_OK,
);
$v->setAllowedMaxFileSize(array('[image]' => '40k'));
$u1->setValidator($v);
$result = $u1->load($tmpFile);
$this->assertTrue($result, 'Load failed with setting max file size');
$v->setAllowedMaxFileSize(array('[image]' => '1k'));
$u1->setValidator($v);
$result = $u1->load($tmpFile);
$this->assertFalse($result, 'Load failed because size was too big');
$v->setAllowedMaxFileSize(array('[image]' => 1000));
$u1->setValidator($v);
$result = $u1->load($tmpFile);
$this->assertFalse($result, 'Load failed because size was too big');
}
public function testGetAllowedMaxFileSize() {
Config::nest();
// Check the max file size uses the config values
$configMaxFileSizes = array(
'[image]' => '1k',
'txt' => 1000
);
Config::inst()->update('Upload_Validator', 'default_max_file_size', $configMaxFileSizes);
$v = new UploadTest_Validator();
$retrievedSize = $v->getAllowedMaxFileSize('[image]');
$this->assertEquals(1024, $retrievedSize, 'Max file size check on default values failed (config category set check)');
$retrievedSize = $v->getAllowedMaxFileSize('txt');
$this->assertEquals(1000, $retrievedSize, 'Max file size check on default values failed (config extension set check)');
// Check instance values for max file size
$maxFileSizes = array(
'[doc]' => 2000,
'txt' => '4k'
);
$v = new UploadTest_Validator();
$v->setAllowedMaxFileSize($maxFileSizes);
$retrievedSize = $v->getAllowedMaxFileSize('[doc]');
$this->assertEquals(2000, $retrievedSize, 'Max file size check on instance values failed (instance category set check)');
// Check that the instance values overwrote the default values
// ie. The max file size will not exist for [image]
$retrievedSize = $v->getAllowedMaxFileSize('[image]');
$this->assertFalse($retrievedSize, 'Max file size check on instance values failed (config overridden check)');
// Check a category that has not been set before
$retrievedSize = $v->getAllowedMaxFileSize('[zip]');
$this->assertFalse($retrievedSize, 'Max file size check on instance values failed (category not set check)');
// Check a file extension that has not been set before
$retrievedSize = $v->getAllowedMaxFileSize('mp3');
$this->assertFalse($retrievedSize, 'Max file size check on instance values failed (extension not set check)');
$retrievedSize = $v->getAllowedMaxFileSize('txt');
$this->assertEquals(4096, $retrievedSize, 'Max file size check on instance values failed (instance extension set check)');
Config::unnest();
} }
public function testAllowedSizeOnFileWithNoExtension() { public function testAllowedSizeOnFileWithNoExtension() {

View File

@ -0,0 +1,107 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class TreeDropdownFieldTest extends SapphireTest {
protected static $fixture_file = 'TreeDropdownFieldTest.yml';
public function testTreeSearch(){
$field = new TreeDropdownField('TestTree', 'Test tree', 'Folder');
// case insensitive search against keyword 'sub' for folders
$request = new SS_HTTPRequest('GET','url',array('search'=>'sub'));
$tree = $field->tree($request);
$folder1 = $this->objFromFixture('Folder','folder1');
$folder1Subfolder1 = $this->objFromFixture('Folder','folder1-subfolder1');
$parser = new CSSContentParser($tree);
$cssPath = 'ul.tree li#selector-TestTree-'.$folder1->ID.' li#selector-TestTree-'.$folder1Subfolder1->ID.' a span.item';
$firstResult = $parser->getBySelector($cssPath);
$this->assertEquals(
(string)$firstResult[0],
$folder1Subfolder1->Name,
$folder1Subfolder1->Name.' is found, nested under '.$folder1->Name
);
$subfolder = $this->objFromFixture('Folder','subfolder');
$cssPath = 'ul.tree li#selector-TestTree-'.$subfolder->ID.' a span.item';
$secondResult = $parser->getBySelector($cssPath);
$this->assertEquals(
(string)$secondResult[0],
$subfolder->Name,
$subfolder->Name.' is found at root level'
);
// other folders which don't contain the keyword 'sub' are not returned in search results
$folder2 = $this->objFromFixture('Folder','folder2');
$cssPath = 'ul.tree li#selector-TestTree-'.$folder2->ID.' a span.item';
$noResult = $parser->getBySelector($cssPath);
$this->assertEquals(
$noResult,
array(),
$folder2.' is not found'
);
$field = new TreeDropdownField('TestTree', 'Test tree', 'File');
// case insensitive search against keyword 'sub' for files
$request = new SS_HTTPRequest('GET','url',array('search'=>'sub'));
$tree = $field->tree($request);
$parser = new CSSContentParser($tree);
// Even if we used File as the source object, folders are still returned because Folder is a File
$cssPath = 'ul.tree li#selector-TestTree-'.$folder1->ID.' li#selector-TestTree-'.$folder1Subfolder1->ID.' a span.item';
$firstResult = $parser->getBySelector($cssPath);
$this->assertEquals(
(string)$firstResult[0],
$folder1Subfolder1->Name,
$folder1Subfolder1->Name.' is found, nested under '.$folder1->Name
);
// Looking for two files with 'sub' in their name, both under the same folder
$file1 = $this->objFromFixture('File','subfolderfile1');
$file2 = $this->objFromFixture('File','subfolderfile2');
$cssPath = 'ul.tree li#selector-TestTree-'.$subfolder->ID.' li#selector-TestTree-'.$file1->ID.' a';
$firstResult = $parser->getBySelector($cssPath);
$this->assertGreaterThan(
0,
count($firstResult),
$file1->Name.' with ID '.$file1->ID.' is in search results'
);
$this->assertEquals(
(string)$firstResult[0],
$file1->Name,
$file1->Name.' is found nested under '.$subfolder->Name
);
$cssPath = 'ul.tree li#selector-TestTree-'.$subfolder->ID.' li#selector-TestTree-'.$file2->ID.' a';
$secondResult = $parser->getBySelector($cssPath);
$this->assertGreaterThan(
0,
count($secondResult),
$file2->Name.' with ID '.$file2->ID.' is in search results'
);
$this->assertEquals(
(string)$secondResult[0],
$file2->Name,
$file2->Name.' is found nested under '.$subfolder->Name
);
// other files which don't include 'sub' are not returned in search results
$file3 = $this->objFromFixture('File','asdf');
$cssPath = 'ul.tree li#selector-TestTree-'.$file3->ID;
$noResult = $parser->getBySelector($cssPath);
$this->assertEquals(
$noResult,
array(),
$file3->Name.' is not found'
);
}
}

View File

@ -0,0 +1,25 @@
Folder:
subfolder:
Name: FileTest-subfolder
folder1:
Name: FileTest-folder1
folder2:
Name: FileTest-folder2
folder1-subfolder1:
Name: FileTest-folder1-subfolder1
ParentID: =>Folder.folder1
File:
asdf:
Filename: assets/FileTest.txt
subfolderfile1:
Filename: assets/FileTest-subfolder/TestFile1InSubfolder.txt
Name: TestFile1InSubfolder
ParentID: =>Folder.subfolder
subfolderfile2:
Filename: assets/FileTest-subfolder/TestFile2InSubfolder.txt
Name: TestFile2InSubfolder
ParentID: =>Folder.subfolder
file1-folder1:
Filename: assets/FileTest-folder1/File1.txt
Name: File1.txt
ParentID: =>Folder.folder1

View File

@ -32,7 +32,7 @@ class GridFieldEditButtonTest extends SapphireTest {
// Check that there are content // Check that there are content
$this->assertEquals(3, count($content->getBySelector('.ss-gridfield-item'))); $this->assertEquals(3, count($content->getBySelector('.ss-gridfield-item')));
// Make sure that there are edit links, even though the user doesn't have "edit" permissions // Make sure that there are edit links, even though the user doesn't have "edit" permissions
// (he can still view the records) // (they can still view the records)
$this->assertEquals(2, count($content->getBySelector('.edit-link')), $this->assertEquals(2, count($content->getBySelector('.edit-link')),
'Edit links should show when not logged in.'); 'Edit links should show when not logged in.');
} }

View File

@ -37,7 +37,7 @@ class CompositeDBFieldTest extends SapphireTest {
} }
} }
class CompositeDBFieldTest_DataObject extends DataObject { class CompositeDBFieldTest_DataObject extends DataObject implements TestOnly {
private static $db = array( private static $db = array(
'Title' => 'Text', 'Title' => 'Text',
'MyMoney' => 'Money', 'MyMoney' => 'Money',

View File

@ -19,7 +19,12 @@ class DataListTest extends SapphireTest {
'DataObjectTest_ValidatedObject', 'DataObjectTest_ValidatedObject',
'DataObjectTest_Player', 'DataObjectTest_Player',
'DataObjectTest_TeamComment', 'DataObjectTest_TeamComment',
'DataObjectTest_ExtendedTeamComment',
'DataObjectTest_EquipmentCompany',
'DataObjectTest_SubEquipmentCompany',
'DataObjectTest\NamespacedClass', 'DataObjectTest\NamespacedClass',
'DataObjectTest_Company',
'DataObjectTest_Fan',
); );
public function testFilterDataObjectByCreatedDate() { public function testFilterDataObjectByCreatedDate() {

View File

@ -22,8 +22,12 @@ class DataObjectLazyLoadingTest extends SapphireTest {
'DataObjectTest_ValidatedObject', 'DataObjectTest_ValidatedObject',
'DataObjectTest_Player', 'DataObjectTest_Player',
'DataObjectTest_TeamComment', 'DataObjectTest_TeamComment',
'DataObjectTest_EquipmentCompany',
'DataObjectTest_SubEquipmentCompany',
'VersionedTest_DataObject', 'VersionedTest_DataObject',
'VersionedTest_Subclass' 'VersionedTest_Subclass',
'VersionedLazy_DataObject',
'VersionedLazySub_DataObject',
); );
public function testQueriedColumnsID() { public function testQueriedColumnsID() {
@ -403,7 +407,7 @@ class DataObjectLazyLoadingTest extends SapphireTest {
/** Additional classes for versioned lazy loading testing */ /** Additional classes for versioned lazy loading testing */
class VersionedLazy_DataObject extends DataObject { class VersionedLazy_DataObject extends DataObject implements TestOnly {
private static $db = array( private static $db = array(
"PageName" => "Varchar" "PageName" => "Varchar"
); );

View File

@ -17,9 +17,15 @@ class DataObjectTest extends SapphireTest {
'DataObjectTest_ValidatedObject', 'DataObjectTest_ValidatedObject',
'DataObjectTest_Player', 'DataObjectTest_Player',
'DataObjectTest_TeamComment', 'DataObjectTest_TeamComment',
'DataObjectTest_EquipmentCompany',
'DataObjectTest_SubEquipmentCompany',
'DataObjectTest\NamespacedClass', 'DataObjectTest\NamespacedClass',
'DataObjectTest\RelationClass', 'DataObjectTest\RelationClass',
'DataObjectTest_ExtendedTeamComment' 'DataObjectTest_ExtendedTeamComment',
'DataObjectTest_Company',
'DataObjectTest_Staff',
'DataObjectTest_CEO',
'DataObjectTest_Fan',
); );
public function testDb() { public function testDb() {
@ -1056,6 +1062,86 @@ class DataObjectTest extends SapphireTest {
); );
} }
protected function makeAccessible($object, $method) {
$reflectionMethod = new ReflectionMethod($object, $method);
$reflectionMethod->setAccessible(true);
return $reflectionMethod;
}
public function testValidateModelDefinitionsFailsWithArray() {
Config::nest();
$object = new DataObjectTest_Team;
$method = $this->makeAccessible($object, 'validateModelDefinitions');
Config::inst()->update('DataObjectTest_Team', 'has_one', array('NotValid' => array('NoArraysAllowed')));
$this->setExpectedException('LogicException');
try {
$method->invoke($object);
} catch(Exception $e) {
Config::unnest(); // Catch the exception so we can unnest config before failing the test
throw $e;
}
}
public function testValidateModelDefinitionsFailsWithIntKey() {
Config::nest();
$object = new DataObjectTest_Team;
$method = $this->makeAccessible($object, 'validateModelDefinitions');
Config::inst()->update('DataObjectTest_Team', 'has_many', array(12 => 'DataObjectTest_Player'));
$this->setExpectedException('LogicException');
try {
$method->invoke($object);
} catch(Exception $e) {
Config::unnest(); // Catch the exception so we can unnest config before failing the test
throw $e;
}
}
public function testValidateModelDefinitionsFailsWithIntValue() {
Config::nest();
$object = new DataObjectTest_Team;
$method = $this->makeAccessible($object, 'validateModelDefinitions');
Config::inst()->update('DataObjectTest_Team', 'many_many', array('Players' => 12));
$this->setExpectedException('LogicException');
try {
$method->invoke($object);
} catch(Exception $e) {
Config::unnest(); // Catch the exception so we can unnest config before failing the test
throw $e;
}
}
/**
* many_many_extraFields is allowed to have an array value, so shouldn't throw an exception
*/
public function testValidateModelDefinitionsPassesWithExtraFields() {
Config::nest();
$object = new DataObjectTest_Team;
$method = $this->makeAccessible($object, 'validateModelDefinitions');
Config::inst()->update('DataObjectTest_Team', 'many_many_extraFields',
array('Relations' => array('Price' => 'Int')));
try {
$method->invoke($object);
} catch(Exception $e) {
Config::unnest();
$this->fail('Exception should not be thrown');
throw $e;
}
Config::unnest();
}
public function testNewClassInstance() { public function testNewClassInstance() {
$dataObject = $this->objFromFixture('DataObjectTest_Team', 'team1'); $dataObject = $this->objFromFixture('DataObjectTest_Team', 'team1');
$changedDO = $dataObject->newClassInstance('DataObjectTest_SubTeam'); $changedDO = $dataObject->newClassInstance('DataObjectTest_SubTeam');
@ -1074,18 +1160,85 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals($changedDO->ClassName, 'DataObjectTest_SubTeam'); $this->assertEquals($changedDO->ClassName, 'DataObjectTest_SubTeam');
} }
public function testMultipleManyManyWithSameClass() {
$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
$sponsors = $team->Sponsors();
$equipmentSuppliers = $team->EquipmentSuppliers();
// Check that DataObject::many_many() works as expected
list($class, $targetClass, $parentField, $childField, $joinTable) = $team->manyManyComponent('Sponsors');
$this->assertEquals('DataObjectTest_Team', $class,
'DataObject::many_many() didn\'t find the correct base class');
$this->assertEquals('DataObjectTest_EquipmentCompany', $targetClass,
'DataObject::many_many() didn\'t find the correct target class for the relation');
$this->assertEquals('DataObjectTest_EquipmentCompany_SponsoredTeams', $joinTable,
'DataObject::many_many() didn\'t find the correct relation table');
// Check that ManyManyList still works
$this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
$this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
// Check everything works when no relation is present
$teamWithoutSponsor = $this->objFromFixture('DataObjectTest_Team', 'team3');
$this->assertInstanceOf('ManyManyList', $teamWithoutSponsor->Sponsors());
$this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
// Check many_many_extraFields still works
$equipmentCompany = $this->objFromFixture('DataObjectTest_EquipmentCompany', 'equipmentcompany1');
$equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
$sponsoredTeams = $equipmentCompany->SponsoredTeams();
$this->assertEquals(1000, $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
'Data from many_many_extraFields was not stored/extracted correctly');
// Check subclasses correctly inherit multiple many_manys
$subTeam = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
$this->assertEquals(2, $subTeam->Sponsors()->count(),
'Child class did not inherit multiple many_manys');
$this->assertEquals(1, $subTeam->EquipmentSuppliers()->count(),
'Child class did not inherit multiple many_manys');
// Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
$team2 = $this->objFromFixture('DataObjectTest_Team', 'team2');
$this->assertEquals(2, $team2->Sponsors()->count(),
'Child class did not inherit multiple belongs_many_manys');
// Check many_many_extraFields also works from the belongs_many_many side
$sponsors = $team2->Sponsors();
$sponsors->add($equipmentCompany, array('SponsorFee' => 750));
$this->assertEquals(750, $sponsors->byID($equipmentCompany->ID)->SponsorFee,
'Data from many_many_extraFields was not stored/extracted correctly');
$subEquipmentCompany = $this->objFromFixture('DataObjectTest_SubEquipmentCompany', 'subequipmentcompany1');
$subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
$this->assertEquals(1200, $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
'Data from inherited many_many_extraFields was not stored/extracted correctly');
}
public function testManyManyExtraFields() { public function testManyManyExtraFields() {
$player = $this->objFromFixture('DataObjectTest_Player', 'player1'); $player = $this->objFromFixture('DataObjectTest_Player', 'player1');
$team = $this->objFromFixture('DataObjectTest_Team', 'team1'); $team = $this->objFromFixture('DataObjectTest_Team', 'team1');
// Get all extra fields
$teamExtraFields = $team->manyManyExtraFields();
$this->assertEquals(array(
'Players' => array('Position' => 'Varchar(100)')
), $teamExtraFields);
// Ensure fields from parent classes are included
$subTeam = singleton('DataObjectTest_SubTeam');
$teamExtraFields = $subTeam->manyManyExtraFields();
$this->assertEquals(array(
'Players' => array('Position' => 'Varchar(100)'),
'FormerPlayers' => array('Position' => 'Varchar(100)')
), $teamExtraFields);
// Extra fields are immediately available on the Team class (defined in $many_many_extraFields) // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
$teamExtraFields = $team->many_many_extraFields('Players'); $teamExtraFields = $team->manyManyExtraFieldsForComponent('Players');
$this->assertEquals($teamExtraFields, array( $this->assertEquals($teamExtraFields, array(
'Position' => 'Varchar(100)' 'Position' => 'Varchar(100)'
)); ));
// We'll have to go through the relation to get the extra fields on Player // We'll have to go through the relation to get the extra fields on Player
$playerExtraFields = $player->many_many_extraFields('Teams'); $playerExtraFields = $player->manyManyExtraFieldsForComponent('Teams');
$this->assertEquals($playerExtraFields, array( $this->assertEquals($playerExtraFields, array(
'Position' => 'Varchar(100)' 'Position' => 'Varchar(100)'
)); ));
@ -1115,7 +1268,7 @@ class DataObjectTest extends SapphireTest {
// Check that ordering a many-many relation by an aggregate column doesn't fail // Check that ordering a many-many relation by an aggregate column doesn't fail
$player = $this->objFromFixture('DataObjectTest_Player', 'player2'); $player = $this->objFromFixture('DataObjectTest_Player', 'player2');
$player->Teams("", "count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC"); $player->Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
} }
/** /**
@ -1268,13 +1421,13 @@ class DataObjectTest extends SapphireTest {
'CurrentStaff' => 'DataObjectTest_Staff', 'CurrentStaff' => 'DataObjectTest_Staff',
'PreviousStaff' => 'DataObjectTest_Staff' 'PreviousStaff' => 'DataObjectTest_Staff'
), ),
$company->has_many(), $company->hasMany(),
'has_many strips field name data by default.' 'has_many strips field name data by default.'
); );
$this->assertEquals ( $this->assertEquals (
'DataObjectTest_Staff', 'DataObjectTest_Staff',
$company->has_many('CurrentStaff'), $company->hasManyComponent('CurrentStaff'),
'has_many strips field name data by default on single relationships.' 'has_many strips field name data by default on single relationships.'
); );
@ -1283,13 +1436,13 @@ class DataObjectTest extends SapphireTest {
'CurrentStaff' => 'DataObjectTest_Staff.CurrentCompany', 'CurrentStaff' => 'DataObjectTest_Staff.CurrentCompany',
'PreviousStaff' => 'DataObjectTest_Staff.PreviousCompany' 'PreviousStaff' => 'DataObjectTest_Staff.PreviousCompany'
), ),
$company->has_many(null, false), $company->hasMany(null, false),
'has_many returns field name data when $classOnly is false.' 'has_many returns field name data when $classOnly is false.'
); );
$this->assertEquals ( $this->assertEquals (
'DataObjectTest_Staff.CurrentCompany', 'DataObjectTest_Staff.CurrentCompany',
$company->has_many('CurrentStaff', false), $company->hasManyComponent('CurrentStaff', false),
'has_many returns field name data on single records when $classOnly is false.' 'has_many returns field name data on single records when $classOnly is false.'
); );
} }
@ -1545,6 +1698,11 @@ class DataObjectTest_Team extends DataObject implements TestOnly {
) )
); );
private static $belongs_many_many = array(
'Sponsors' => 'DataObjectTest_EquipmentCompany.SponsoredTeams',
'EquipmentSuppliers' => 'DataObjectTest_EquipmentCompany.EquipmentCustomers'
);
private static $summary_fields = array( private static $summary_fields = array(
'Title' => 'Custom Title', 'Title' => 'Custom Title',
'Title.UpperCase' => 'Title', 'Title.UpperCase' => 'Title',
@ -1606,6 +1764,16 @@ class DataObjectTest_SubTeam extends DataObjectTest_Team implements TestOnly {
private static $has_one = array( private static $has_one = array(
"ParentTeam" => 'DataObjectTest_Team', "ParentTeam" => 'DataObjectTest_Team',
); );
private static $many_many = array(
'FormerPlayers' => 'DataObjectTest_Player'
);
private static $many_many_extraFields = array(
'FormerPlayers' => array(
'Position' => 'Varchar(100)'
)
);
} }
class OtherSubclassWithSameField extends DataObjectTest_Team implements TestOnly { class OtherSubclassWithSameField extends DataObjectTest_Team implements TestOnly {
private static $db = array( private static $db = array(
@ -1652,7 +1820,7 @@ class DataObjectTest_ValidatedObject extends DataObject implements TestOnly {
} }
} }
class DataObjectTest_Company extends DataObject { class DataObjectTest_Company extends DataObject implements TestOnly {
private static $db = array( private static $db = array(
'Name' => 'Varchar' 'Name' => 'Varchar'
@ -1670,7 +1838,26 @@ class DataObjectTest_Company extends DataObject {
); );
} }
class DataObjectTest_Staff extends DataObject { class DataObjectTest_EquipmentCompany extends DataObjectTest_Company implements TestOnly {
private static $many_many = array(
'SponsoredTeams' => 'DataObjectTest_Team',
'EquipmentCustomers' => 'DataObjectTest_Team'
);
private static $many_many_extraFields = array(
'SponsoredTeams' => array(
'SponsorFee' => 'Int'
)
);
}
class DataObjectTest_SubEquipmentCompany extends DataObjectTest_EquipmentCompany implements TestOnly {
private static $db = array(
'SubclassDatabaseField' => 'Varchar'
);
}
class DataObjectTest_Staff extends DataObject implements TestOnly {
private static $has_one = array ( private static $has_one = array (
'CurrentCompany' => 'DataObjectTest_Company', 'CurrentCompany' => 'DataObjectTest_Company',
'PreviousCompany' => 'DataObjectTest_Company' 'PreviousCompany' => 'DataObjectTest_Company'
@ -1685,7 +1872,7 @@ class DataObjectTest_CEO extends DataObjectTest_Staff {
); );
} }
class DataObjectTest_TeamComment extends DataObject { class DataObjectTest_TeamComment extends DataObject implements TestOnly {
private static $db = array( private static $db = array(
'Name' => 'Varchar', 'Name' => 'Varchar',
'Comment' => 'Text' 'Comment' => 'Text'
@ -1697,7 +1884,7 @@ class DataObjectTest_TeamComment extends DataObject {
} }
class DataObjectTest_Fan extends DataObject { class DataObjectTest_Fan extends DataObject implements TestOnly {
private static $db = array( private static $db = array(
'Name' => 'Varchar(255)' 'Name' => 'Varchar(255)'

View File

@ -1,8 +1,20 @@
DataObjectTest_EquipmentCompany:
equipmentcompany1:
Name: Company corp
equipmentcompany2:
Name: 'Team co.'
DataObjectTest_SubEquipmentCompany:
subequipmentcompany1:
Name: John Smith and co
DataObjectTest_Team: DataObjectTest_Team:
team1: team1:
Title: Team 1 Title: Team 1
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany2
team2: team2:
Title: Team 2 Title: Team 2
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany2,=>DataObjectTest_SubEquipmentCompany.subequipmentcompany1
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
team3: team3:
Title: Team 3 Title: Team 3
DataObjectTest_Player: DataObjectTest_Player:
@ -26,6 +38,8 @@ DataObjectTest_SubTeam:
SubclassDatabaseField: Subclassed 1 SubclassDatabaseField: Subclassed 1
ExtendedDatabaseField: Extended 1 ExtendedDatabaseField: Extended 1
ParentTeam: =>DataObjectTest_Team.team1 ParentTeam: =>DataObjectTest_Team.team1
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1
subteam2_with_player_relation: subteam2_with_player_relation:
Title: Subteam 2 Title: Subteam 2
SubclassDatabaseField: Subclassed 2 SubclassDatabaseField: Subclassed 2

View File

@ -49,6 +49,19 @@ class DataQueryTest extends SapphireTest {
$dq->sql($parameters)); $dq->sql($parameters));
} }
public function testApplyRelation() {
// Test applyRelation with two has_ones pointing to the same class
$dq = new DataQuery('DataQueryTest_B');
$dq->applyRelation('TestC');
$this->assertTrue($dq->query()->isJoinedTo('DataQueryTest_C'));
$this->assertContains('"DataQueryTest_C"."ID" = "DataQueryTest_B"."TestCID"', $dq->sql());
$dq = new DataQuery('DataQueryTest_B');
$dq->applyRelation('TestCTwo');
$this->assertTrue($dq->query()->isJoinedTo('DataQueryTest_C'));
$this->assertContains('"DataQueryTest_C"."ID" = "DataQueryTest_B"."TestCTwoID"', $dq->sql());
}
public function testApplyReplationDeepInheretence() { public function testApplyReplationDeepInheretence() {
$newDQ = new DataQuery('DataQueryTest_E'); $newDQ = new DataQuery('DataQueryTest_E');
//apply a relation to a relation from an ancestor class //apply a relation to a relation from an ancestor class
@ -257,6 +270,7 @@ class DataQueryTest_B extends DataObject implements TestOnly {
private static $has_one = array( private static $has_one = array(
'TestC' => 'DataQueryTest_C', 'TestC' => 'DataQueryTest_C',
'TestCTwo' => 'DataQueryTest_C',
); );
} }
@ -273,7 +287,8 @@ class DataQueryTest_C extends DataObject implements TestOnly {
private static $has_many = array( private static $has_many = array(
'TestAs' => 'DataQueryTest_A', 'TestAs' => 'DataQueryTest_A',
'TestBs' => 'DataQueryTest_B', 'TestBs' => 'DataQueryTest_B.TestC',
'TestBsTwo' => 'DataQueryTest_B.TestCTwo',
); );
private static $many_many = array( private static $many_many = array(

View File

@ -9,6 +9,7 @@ class HasManyListTest extends SapphireTest {
'DataObjectTest_Team', 'DataObjectTest_Team',
'DataObjectTest_SubTeam', 'DataObjectTest_SubTeam',
'DataObjectTest_Player', 'DataObjectTest_Player',
'DataObjectTest_TeamComment',
); );
public function testRelationshipEmptyOnNewRecords() { public function testRelationshipEmptyOnNewRecords() {

View File

@ -64,6 +64,10 @@ class PaginatedListTest extends SapphireTest {
$this->assertEquals(10, $list->CurrentPage()); $this->assertEquals(10, $list->CurrentPage());
$this->assertEquals(90, $list->getPageStart()); $this->assertEquals(90, $list->getPageStart());
// Test disabled paging
$list->setPageLength(0);
$this->assertEquals(1, $list->CurrentPage());
} }
public function testGetIterator() { public function testGetIterator() {
@ -93,6 +97,20 @@ class PaginatedListTest extends SapphireTest {
$list->setCurrentPage(999); $list->setCurrentPage(999);
$this->assertDOSEquals(array(), $list->getIterator()); $this->assertDOSEquals(array(), $list->getIterator());
// Test disabled paging
$list->setPageLength(0);
$list->setCurrentPage(1);
$this->assertDOSEquals(
array(
array('Num' => 1),
array('Num' => 2),
array('Num' => 3),
array('Num' => 4),
array('Num' => 5)
), $list->getIterator()
);
// Test with dataobjectset
$players = DataObjectTest_Player::get(); $players = DataObjectTest_Player::get();
$list = new PaginatedList($players); $list = new PaginatedList($players);
$list->setPageLength(1); $list->setPageLength(1);
@ -127,6 +145,13 @@ class PaginatedListTest extends SapphireTest {
array('PageNum' => 4), array('PageNum' => 4),
); );
$this->assertDOSEquals($expectLimited, $list->Pages(3)); $this->assertDOSEquals($expectLimited, $list->Pages(3));
// Disable paging
$list->setPageLength(0);
$expectAll = array(
array('PageNum' => 1, 'CurrentBool' => true),
);
$this->assertDOSEquals($expectAll, $list->Pages());
} }
public function testPaginationSummary() { public function testPaginationSummary() {
@ -148,6 +173,13 @@ class PaginatedListTest extends SapphireTest {
array('PageNum' => 25), array('PageNum' => 25),
); );
$this->assertDOSEquals($expect, $list->PaginationSummary(4)); $this->assertDOSEquals($expect, $list->PaginationSummary(4));
// Disable paging
$list->setPageLength(0);
$expect = array(
array('PageNum' => 1, 'CurrentBool' => true)
);
$this->assertDOSEquals($expect, $list->PaginationSummary(4));
} }
public function testLimitItems() { public function testLimitItems() {
@ -170,6 +202,10 @@ class PaginatedListTest extends SapphireTest {
$this->assertEquals(2, $list->CurrentPage()); $this->assertEquals(2, $list->CurrentPage());
$list->setPageStart(40); $list->setPageStart(40);
$this->assertEquals(5, $list->CurrentPage()); $this->assertEquals(5, $list->CurrentPage());
// Disable paging
$list->setPageLength(0);
$this->assertEquals(1, $list->CurrentPage());
} }
public function testTotalPages() { public function testTotalPages() {
@ -183,6 +219,13 @@ class PaginatedListTest extends SapphireTest {
$list->setTotalItems(5); $list->setTotalItems(5);
$this->assertEquals(5, $list->TotalPages()); $this->assertEquals(5, $list->TotalPages());
// Disable paging
$list->setPageLength(0);
$this->assertEquals(1, $list->TotalPages());
$list->setTotalItems(0);
$this->assertEquals(0, $list->TotalPages());
} }
public function testMoreThanOnePage() { public function testMoreThanOnePage() {
@ -194,6 +237,10 @@ class PaginatedListTest extends SapphireTest {
$list->setTotalItems(2); $list->setTotalItems(2);
$this->assertTrue($list->MoreThanOnePage()); $this->assertTrue($list->MoreThanOnePage());
// Disable paging
$list->setPageLength(0);
$this->assertFalse($list->MoreThanOnePage());
} }
public function testNotFirstPage() { public function testNotFirstPage() {
@ -230,6 +277,10 @@ class PaginatedListTest extends SapphireTest {
$this->assertEquals(20, $list->LastItem()); $this->assertEquals(20, $list->LastItem());
$list->setCurrentPage(3); $list->setCurrentPage(3);
$this->assertEquals(25, $list->LastItem()); $this->assertEquals(25, $list->LastItem());
// Disable paging
$list->setPageLength(0);
$this->assertEquals(25, $list->LastItem());
} }
public function testFirstLink() { public function testFirstLink() {
@ -242,6 +293,10 @@ class PaginatedListTest extends SapphireTest {
$list->setPageLength(10); $list->setPageLength(10);
$list->setTotalItems(100); $list->setTotalItems(100);
$this->assertContains('start=90', $list->LastLink()); $this->assertContains('start=90', $list->LastLink());
// Disable paging
$list->setPageLength(0);
$this->assertContains('start=0', $list->LastLink());
} }
public function testNextLink() { public function testNextLink() {
@ -257,6 +312,11 @@ class PaginatedListTest extends SapphireTest {
$this->assertContains('start=40', $list->NextLink()); $this->assertContains('start=40', $list->NextLink());
$list->setCurrentPage(5); $list->setCurrentPage(5);
$this->assertNull($list->NextLink()); $this->assertNull($list->NextLink());
// Disable paging
$list->setCurrentPage(1);
$list->setPageLength(0);
$this->assertNull($list->NextLink());
} }
public function testPrevLink() { public function testPrevLink() {
@ -270,6 +330,10 @@ class PaginatedListTest extends SapphireTest {
$this->assertContains('start=10', $list->PrevLink()); $this->assertContains('start=10', $list->PrevLink());
$list->setCurrentPage(5); $list->setCurrentPage(5);
$this->assertContains('start=30', $list->PrevLink()); $this->assertContains('start=30', $list->PrevLink());
// Disable paging
$list->setPageLength(0);
$this->assertNull($list->PrevLink());
} }
} }

View File

@ -19,7 +19,7 @@ class PolymorphicHasManyListTest extends SapphireTest {
'DataObjectTest_Team', 'DataObjectTest_Team',
'DataObjectTest_SubTeam', 'DataObjectTest_SubTeam',
'DataObjectTest_Player', 'DataObjectTest_Player',
'DataObjectTest_Fan' 'DataObjectTest_Fan',
); );
public function testRelationshipEmptyOnNewRecords() { public function testRelationshipEmptyOnNewRecords() {

View File

@ -21,6 +21,35 @@ class TextTest extends SapphireTest {
} }
} }
/**
* Test {@link Text->LimitCharactersToClosestWord()}
*/
public function testLimitCharactersToClosestWord() {
$cases = array(
/* Standard words limited, ellipsis added if truncated */
'Lorem ipsum dolor sit amet' => 'Lorem ipsum dolor sit...',
/* Complete words less than the character limit don't get truncated, ellipsis not added */
'Lorem ipsum' => 'Lorem ipsum',
'Lorem' => 'Lorem',
'' => '', // No words produces nothing!
/* HTML tags get stripped out, leaving the raw text */
'<p>Lorem ipsum dolor sit amet</p>' => 'Lorem ipsum dolor sit...',
'<p><span>Lorem ipsum dolor sit amet</span></p>' => 'Lorem ipsum dolor sit...',
'<p>Lorem ipsum</p>' => 'Lorem ipsum',
/* HTML entities are treated as a single character */
'Lorem &amp; ipsum dolor sit amet' => 'Lorem &amp; ipsum dolor...'
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = new Text('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitCharactersToClosestWord(24));
}
}
/** /**
* Test {@link Text->LimitWordCount()} * Test {@link Text->LimitWordCount()}
*/ */

View File

@ -46,7 +46,7 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
$this->assertEquals($group->Permissions()->Count(), 0, 'The tested group has no permissions'); $this->assertEquals($group->Permissions()->Count(), 0, 'The tested group has no permissions');
$this->assertEquals($untouchable->Permissions()->Count(), 1, 'The other group has one permission'); $this->assertEquals($untouchable->Permissions()->Count(), 1, 'The other group has one permission');
$this->assertEquals($untouchable->Permissions("\"Code\"='ADMIN'")->Count(), 1, $this->assertEquals($untouchable->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
'The other group has ADMIN permission'); 'The other group has ADMIN permission');
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount, 'There are no orphaned permissions'); $this->assertEquals(DataObject::get('Permission')->Count(), $baseCount, 'There are no orphaned permissions');
@ -62,14 +62,14 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
$untouchable->flushCache(); $untouchable->flushCache();
$this->assertEquals($group->Permissions()->Count(), 2, $this->assertEquals($group->Permissions()->Count(), 2,
'The tested group has two permissions permission'); 'The tested group has two permissions permission');
$this->assertEquals($group->Permissions("\"Code\"='ADMIN'")->Count(), 1, $this->assertEquals($group->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
'The tested group has ADMIN permission'); 'The tested group has ADMIN permission');
$this->assertEquals($group->Permissions("\"Code\"='NON-ADMIN'")->Count(), 1, $this->assertEquals($group->Permissions()->where("\"Code\"='NON-ADMIN'")->Count(), 1,
'The tested group has CMS_ACCESS_AssetAdmin permission'); 'The tested group has CMS_ACCESS_AssetAdmin permission');
$this->assertEquals($untouchable->Permissions()->Count(), 1, $this->assertEquals($untouchable->Permissions()->Count(), 1,
'The other group has one permission'); 'The other group has one permission');
$this->assertEquals($untouchable->Permissions("\"Code\"='ADMIN'")->Count(), 1, $this->assertEquals($untouchable->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
'The other group has ADMIN permission'); 'The other group has ADMIN permission');
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+2, $this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+2,
@ -85,12 +85,12 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
$untouchable->flushCache(); $untouchable->flushCache();
$this->assertEquals($group->Permissions()->Count(), 1, $this->assertEquals($group->Permissions()->Count(), 1,
'The tested group has 1 permission'); 'The tested group has 1 permission');
$this->assertEquals($group->Permissions("\"Code\"='ADMIN'")->Count(), 1, $this->assertEquals($group->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
'The tested group has ADMIN permission'); 'The tested group has ADMIN permission');
$this->assertEquals($untouchable->Permissions()->Count(), 1, $this->assertEquals($untouchable->Permissions()->Count(), 1,
'The other group has one permission'); 'The other group has one permission');
$this->assertEquals($untouchable->Permissions("\"Code\"='ADMIN'")->Count(), 1, $this->assertEquals($untouchable->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
'The other group has ADMIN permission'); 'The other group has ADMIN permission');
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+1, $this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+1,

View File

@ -74,6 +74,104 @@ class SecurityTest extends FunctionalTest {
$this->autoFollowRedirection = true; $this->autoFollowRedirection = true;
} }
public function testPermissionFailureSetsCorrectFormMessages() {
Config::nest();
// Controller that doesn't attempt redirections
$controller = new SecurityTest_NullController();
$controller->response = new SS_HTTPResponse();
Security::permissionFailure($controller, array('default' => 'Oops, not allowed'));
$this->assertEquals('Oops, not allowed', Session::get('Security.Message.message'));
// Test that config values are used correctly
Config::inst()->update('Security', 'default_message_set', 'stringvalue');
Security::permissionFailure($controller);
$this->assertEquals('stringvalue', Session::get('Security.Message.message'),
'Default permission failure message value was not present');
Config::inst()->remove('Security', 'default_message_set');
Config::inst()->update('Security', 'default_message_set', array('default' => 'arrayvalue'));
Security::permissionFailure($controller);
$this->assertEquals('arrayvalue', Session::get('Security.Message.message'),
'Default permission failure message value was not present');
// Test that non-default messages work.
// NOTE: we inspect the response body here as the session message has already
// been fetched and output as part of it, so has been removed from the session
$this->logInWithPermission('EDITOR');
Config::inst()->update('Security', 'default_message_set',
array('default' => 'default', 'alreadyLoggedIn' => 'You are already logged in!'));
Security::permissionFailure($controller);
$this->assertContains('You are already logged in!', $controller->response->getBody(),
'Custom permission failure message was ignored');
Security::permissionFailure($controller,
array('default' => 'default', 'alreadyLoggedIn' => 'One-off failure message'));
$this->assertContains('One-off failure message', $controller->response->getBody(),
"Message set passed to Security::permissionFailure() didn't override Config values");
Config::unnest();
}
/**
* Follow all redirects recursively
*
* @param string $url
* @param int $limit Max number of requests
* @return SS_HTTPResponse
*/
protected function getRecursive($url, $limit = 10) {
$this->cssParser = null;
$response = $this->mainSession->get($url);
while(--$limit > 0 && $response instanceof SS_HTTPResponse && $response->getHeader('Location')) {
$response = $this->mainSession->followRedirection();
}
return $response;
}
public function testAutomaticRedirectionOnLogin() {
// BackURL with permission error (not authenticated) should not redirect
if($member = Member::currentUser()) $member->logOut();
$response = $this->getRecursive('SecurityTest_SecuredController');
$this->assertContains(Convert::raw2xml("That page is secured."), $response->getBody());
$this->assertContains('<input type="submit" name="action_dologin"', $response->getBody());
// Non-logged in user should not be redirected, but instead shown the login form
// No message/context is available as the user has not attempted to view the secured controller
$response = $this->getRecursive('Security/login?BackURL=SecurityTest_SecuredController/');
$this->assertNotContains(Convert::raw2xml("That page is secured."), $response->getBody());
$this->assertNotContains(Convert::raw2xml("You don't have access to this page"), $response->getBody());
$this->assertContains('<input type="submit" name="action_dologin"', $response->getBody());
// BackURL with permission error (wrong permissions) should not redirect
$this->logInAs('grouplessmember');
$response = $this->getRecursive('SecurityTest_SecuredController');
$this->assertContains(Convert::raw2xml("You don't have access to this page"), $response->getBody());
$this->assertContains(
'<input type="submit" name="action_logout" value="Log in as someone else"',
$response->getBody()
);
// Directly accessing this page should attempt to follow the BackURL, but stop when it encounters the error
$response = $this->getRecursive('Security/login?BackURL=SecurityTest_SecuredController/');
$this->assertContains(Convert::raw2xml("You don't have access to this page"), $response->getBody());
$this->assertContains(
'<input type="submit" name="action_logout" value="Log in as someone else"',
$response->getBody()
);
// Check correctly logged in admin doesn't generate the same errors
$this->logInAs('admin');
$response = $this->getRecursive('SecurityTest_SecuredController');
$this->assertContains(Convert::raw2xml("Success"), $response->getBody());
// Directly accessing this page should attempt to follow the BackURL and succeed
$response = $this->getRecursive('Security/login?BackURL=SecurityTest_SecuredController/');
$this->assertContains(Convert::raw2xml("Success"), $response->getBody());
}
public function testLogInAsSomeoneElse() { public function testLogInAsSomeoneElse() {
$member = DataObject::get_one('Member'); $member = DataObject::get_one('Member');
@ -517,3 +615,11 @@ class SecurityTest_SecuredController extends Controller implements TestOnly {
return 'Success'; return 'Success';
} }
} }
class SecurityTest_NullController extends Controller implements TestOnly {
public function redirect($url, $code = 302) {
// NOOP
}
}

View File

@ -49,7 +49,7 @@ class YamlFixtureTest extends SapphireTest {
$factory->getId("YamlFixtureTest_DataObject", "testobject1") $factory->getId("YamlFixtureTest_DataObject", "testobject1")
); );
$this->assertTrue( $this->assertTrue(
$object1->ManyMany()->Count() == 2, $object1->ManyManyRelation()->Count() == 2,
"Should be two items in this relationship" "Should be two items in this relationship"
); );
$this->assertGreaterThan(0, $factory->getId("YamlFixtureTest_DataObject", "testobject2")); $this->assertGreaterThan(0, $factory->getId("YamlFixtureTest_DataObject", "testobject2"));
@ -58,7 +58,7 @@ class YamlFixtureTest extends SapphireTest {
$factory->getId("YamlFixtureTest_DataObject", "testobject2") $factory->getId("YamlFixtureTest_DataObject", "testobject2")
); );
$this->assertTrue( $this->assertTrue(
$object2->ManyMany()->Count() == 1, $object2->ManyManyRelation()->Count() == 1,
"Should be one item in this relationship" "Should be one item in this relationship"
); );
} }
@ -79,7 +79,7 @@ class YamlFixtureTest_DataObject extends DataObject implements TestOnly {
"Name" => "Varchar" "Name" => "Varchar"
); );
private static $many_many = array( private static $many_many = array(
"ManyMany" => "YamlFixtureTest_DataObjectRelation" "ManyManyRelation" => "YamlFixtureTest_DataObjectRelation"
); );
} }

View File

@ -6,7 +6,7 @@ YamlFixtureTest_DataObjectRelation:
YamlFixtureTest_DataObject: YamlFixtureTest_DataObject:
testobject1: testobject1:
Name: TestObject1 Name: TestObject1
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1,=>YamlFixtureTest_DataObjectRelation.relation2 ManyManyRelation: =>YamlFixtureTest_DataObjectRelation.relation1,=>YamlFixtureTest_DataObjectRelation.relation2
testobject2: testobject2:
Name: TestObject2 Name: TestObject2
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1 ManyManyRelation: =>YamlFixtureTest_DataObjectRelation.relation1

View File

@ -1,6 +1,11 @@
<?php <?php
class SSViewerTest extends SapphireTest { class SSViewerTest extends SapphireTest {
protected $extraDataObjects = array(
'SSViewerTest_Object',
);
public function setUp() { public function setUp() {
parent::setUp(); parent::setUp();
Config::inst()->update('SSViewer', 'source_file_comments', false); Config::inst()->update('SSViewer', 'source_file_comments', false);
@ -1112,6 +1117,8 @@ after')
$orig = Config::inst()->get('SSViewer', 'rewrite_hash_links'); $orig = Config::inst()->get('SSViewer', 'rewrite_hash_links');
Config::inst()->update('SSViewer', 'rewrite_hash_links', true); Config::inst()->update('SSViewer', 'rewrite_hash_links', true);
$_SERVER['REQUEST_URI'] = 'http://path/to/file?foo"onclick="alert(\'xss\')""';
// Emulate SSViewer::process() // Emulate SSViewer::process()
$base = Convert::raw2att($_SERVER['REQUEST_URI']); $base = Convert::raw2att($_SERVER['REQUEST_URI']);
@ -1122,6 +1129,8 @@ after')
<html> <html>
<head><% base_tag %></head> <head><% base_tag %></head>
<body> <body>
<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
$ExternalInsertedLink
<a class="inline" href="#anchor">InlineLink</a> <a class="inline" href="#anchor">InlineLink</a>
$InsertedLink $InsertedLink
<svg><use xlink:href="#sprite"></use></svg> <svg><use xlink:href="#sprite"></use></svg>
@ -1130,15 +1139,24 @@ after')
$tmpl = new SSViewer($tmplFile); $tmpl = new SSViewer($tmplFile);
$obj = new ViewableData(); $obj = new ViewableData();
$obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>'; $obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>';
$obj->ExternalInsertedLink = '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>';
$result = $tmpl->process($obj); $result = $tmpl->process($obj);
$this->assertContains( $this->assertContains(
'<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>', '<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
$result $result
); );
$this->assertContains(
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
$result
);
$this->assertContains( $this->assertContains(
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>', '<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
$result $result
); );
$this->assertContains(
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
$result
);
$this->assertContains( $this->assertContains(
'<svg><use xlink:href="#sprite"></use></svg>', '<svg><use xlink:href="#sprite"></use></svg>',
$result, $result,
@ -1171,7 +1189,7 @@ after')
$obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>'; $obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>';
$result = $tmpl->process($obj); $result = $tmpl->process($obj);
$this->assertContains( $this->assertContains(
'<a class="inserted" href="<?php echo strip_tags(', '<a class="inserted" href="<?php echo Convert::raw2att(',
$result $result
); );
// TODO Fix inline links in PHP mode // TODO Fix inline links in PHP mode
@ -1509,7 +1527,7 @@ class SSViewerTest_Controller extends Controller {
} }
class SSViewerTest_Object extends DataObject { class SSViewerTest_Object extends DataObject implements TestOnly {
public $number = null; public $number = null;

View File

@ -156,6 +156,25 @@ class ViewableDataTest extends SapphireTest {
$this->assertEquals($uncastedData, $castedData->getValue(), 'Casted and uncasted strings are not equal.'); $this->assertEquals($uncastedData, $castedData->getValue(), 'Casted and uncasted strings are not equal.');
} }
public function testCaching() {
$objCached = new ViewableDataTest_Cached();
$objNotCached = new ViewableDataTest_NotCached();
$objCached->Test = 'AAA';
$objNotCached->Test = 'AAA';
$this->assertEquals('AAA', $objCached->obj('Test', null, true, true));
$this->assertEquals('AAA', $objNotCached->obj('Test', null, true, true));
$objCached->Test = 'BBB';
$objNotCached->Test = 'BBB';
// Cached data must be always the same
$this->assertEquals('AAA', $objCached->obj('Test', null, true, true));
$this->assertEquals('BBB', $objNotCached->obj('Test', null, true, true));
}
} }
/**#@+ /**#@+
@ -253,4 +272,17 @@ class ViewableDataTest_NoCastingInformation extends ViewableData {
} }
} }
class ViewableDataTest_Cached extends ViewableData {
public $Test;
}
class ViewableDataTest_NotCached extends ViewableData {
public $Test;
protected function objCacheGet($key) {
// Disable caching
return null;
}
}
/**#@-*/ /**#@-*/

View File

@ -10,7 +10,7 @@ jQuery(function($){
nextText: 'Neste&raquo;', nextText: 'Neste&raquo;',
currentText: 'I dag', currentText: 'I dag',
monthNames: ['januar','februar','mars','april','mai','juni','juli','august','september','oktober','november','desember'], monthNames: ['januar','februar','mars','april','mai','juni','juli','august','september','oktober','november','desember'],
monthNamesShort: ['jan.','feb.','mar.','apr.','mai','juni','juli','aug.','sep.','okt.','nov.','des.'], monthNamesShort: ['jan.','feb.','mars','apr.','mai','juni','juli','aug.','sep.','okt.','nov.','des.'],
dayNames: ['søndag','mandag','tirsdag','onsdag','torsdag','fredag','lørdag'], dayNames: ['søndag','mandag','tirsdag','onsdag','torsdag','fredag','lørdag'],
dayNamesShort: ['søn','man','tir','ons','tor','fre','lør'], dayNamesShort: ['søn','man','tir','ons','tor','fre','lør'],
dayNamesMin: ['Sø','Ma','Ti','On','To','Fr','Lø'], dayNamesMin: ['Sø','Ma','Ti','On','To','Fr','Lø'],

View File

@ -4675,7 +4675,7 @@ class SSTemplateParser extends Parser implements TemplateParser {
$text = preg_replace( $text = preg_replace(
'/(<a[^>]+href *= *)"#/i', '/(<a[^>]+href *= *)"#/i',
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' . '\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") . ' Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "") .
\'#', \'#',
$text $text
); );

View File

@ -1129,7 +1129,7 @@ class SSTemplateParser extends Parser implements TemplateParser {
$text = preg_replace( $text = preg_replace(
'/(<a[^>]+href *= *)"#/i', '/(<a[^>]+href *= *)"#/i',
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' . '\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") . ' Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "") .
\'#', \'#',
$text $text
); );

View File

@ -1115,9 +1115,9 @@ class SSViewer implements Flushable {
if($this->rewriteHashlinks && $rewrite) { if($this->rewriteHashlinks && $rewrite) {
if(strpos($output, '<base') !== false) { if(strpos($output, '<base') !== false) {
if($rewrite === 'php') { if($rewrite === 'php') {
$thisURLRelativeToBase = "<?php echo strip_tags(\$_SERVER['REQUEST_URI']); ?>"; $thisURLRelativeToBase = "<?php echo Convert::raw2att(\$_SERVER['REQUEST_URI']); ?>";
} else { } else {
$thisURLRelativeToBase = strip_tags($_SERVER['REQUEST_URI']); $thisURLRelativeToBase = Convert::raw2att($_SERVER['REQUEST_URI']);
} }
$output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output); $output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);

View File

@ -346,6 +346,38 @@ class ViewableData extends Object implements IteratorAggregate {
); );
} }
/**
* Generate the cache name for a field
*
* @param string $fieldName Name of field
* @param array $arguments List of optional arguments given
*/
protected function objCacheName($fieldName, $arguments) {
return $arguments
? $fieldName . ":" . implode(',', $arguments)
: $fieldName;
}
/**
* Get a cached value from the field cache
*
* @param string $key Cache key
* @return mixed
*/
protected function objCacheGet($key) {
if(isset($this->objCache[$key])) return $this->objCache[$key];
}
/**
* Store a value in the field cache
*
* @param string $key Cache key
* @param mixed $value
*/
protected function objCacheSet($key, $value) {
$this->objCache[$key] = $value;
}
/** /**
* Get the value of a field on this object, automatically inserting the value into any available casting objects * Get the value of a field on this object, automatically inserting the value into any available casting objects
* that have been specified. * that have been specified.
@ -354,12 +386,14 @@ class ViewableData extends Object implements IteratorAggregate {
* @param array $arguments * @param array $arguments
* @param bool $forceReturnedObject if TRUE, the value will ALWAYS be casted to an object before being returned, * @param bool $forceReturnedObject if TRUE, the value will ALWAYS be casted to an object before being returned,
* even if there is no explicit casting information * even if there is no explicit casting information
* @param bool $cache Cache this object
* @param string $cacheName a custom cache name * @param string $cacheName a custom cache name
*/ */
public function obj($fieldName, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) { public function obj($fieldName, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
if(!$cacheName) $cacheName = $arguments ? $fieldName . implode(',', $arguments) : $fieldName; if(!$cacheName && $cache) $cacheName = $this->objCacheName($fieldName, $arguments);
if(!isset($this->objCache[$cacheName])) { $value = $cache ? $this->objCacheGet($cacheName) : null;
if(!isset($value)) {
// HACK: Don't call the deprecated FormField::Name() method // HACK: Don't call the deprecated FormField::Name() method
$methodIsAllowed = true; $methodIsAllowed = true;
if($this instanceof FormField && $fieldName == 'Name') $methodIsAllowed = false; if($this instanceof FormField && $fieldName == 'Name') $methodIsAllowed = false;
@ -381,9 +415,7 @@ class ViewableData extends Object implements IteratorAggregate {
$value = $valueObject; $value = $valueObject;
} }
if($cache) $this->objCache[$cacheName] = $value; if($cache) $this->objCacheSet($cacheName, $value);
} else {
$value = $this->objCache[$cacheName];
} }
if(!is_object($value) && $forceReturnedObject) { if(!is_object($value) && $forceReturnedObject) {
@ -669,6 +701,13 @@ class ViewableData_Debugger extends ViewableData {
parent::__construct(); parent::__construct();
} }
/**
* @return string The rendered debugger
*/
public function __toString() {
return $this->forTemplate();
}
/** /**
* Return debugging information, as XHTML. If a field name is passed, it will show debugging information on that * Return debugging information, as XHTML. If a field name is passed, it will show debugging information on that
* field, otherwise it will show information on all methods and fields. * field, otherwise it will show information on all methods and fields.