Merge remote-tracking branch 'origin/3'
Conflicts: core/Constants.php docs/en/05_Contributing/01_Code.md tests/model/SQLQueryTest.php
@ -322,25 +322,28 @@ abstract class ModelAdmin extends LeftAndMain {
|
||||
* @return Form
|
||||
*/
|
||||
public function ImportForm() {
|
||||
$modelName = $this->modelClass;
|
||||
$modelSNG = singleton($this->modelClass);
|
||||
$modelName = $modelSNG->i18n_singular_name();
|
||||
// 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;
|
||||
}
|
||||
|
||||
$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(
|
||||
new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $modelName),
|
||||
new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $this->modelClass),
|
||||
new FileField('_CsvFile', false)
|
||||
);
|
||||
|
||||
// get HTML specification for each import (column names etc.)
|
||||
$importerClass = $importers[$modelName];
|
||||
$importer = new $importerClass($modelName);
|
||||
$importerClass = $importers[$this->modelClass];
|
||||
$importer = new $importerClass($this->modelClass);
|
||||
$spec = $importer->getImportSpec();
|
||||
$specFields = new ArrayList();
|
||||
foreach($spec['fields'] as $name => $desc) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
|
||||
.cms .ss-ui-button { background-color: #e6e6e6; }
|
||||
.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-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-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; }
|
||||
|
||||
.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; }
|
||||
|
||||
/** 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_disabled, .ui-widget-content .btn-icon-accept_disabled { background-position: 0 -16px; }
|
||||
.ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add { background-position: 0 -32px; }
|
||||
.ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia { background-position: 0 -48px; }
|
||||
.ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled { background-position: 0 -68px; }
|
||||
.ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage { background-position: 0 -84px; }
|
||||
.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -100px; }
|
||||
.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-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -132px; }
|
||||
.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -148px; }
|
||||
.ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled { background-position: 0 -164px; }
|
||||
.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -180px; }
|
||||
.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -196px; }
|
||||
.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -212px; }
|
||||
.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -228px; }
|
||||
.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -244px; }
|
||||
.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -260px; }
|
||||
.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -276px; }
|
||||
.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -292px; }
|
||||
.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_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -324px; }
|
||||
.ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross { background-position: 0 -340px; }
|
||||
.ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline { background-position: 0 -355px; }
|
||||
.ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled { background-position: 0 -371px; }
|
||||
.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -387px; }
|
||||
.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -403px; }
|
||||
.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -420px; }
|
||||
.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -436px; }
|
||||
.ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv { background-position: 0 -452px; }
|
||||
.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_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -484px; }
|
||||
.ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print { background-position: 0 -500px; }
|
||||
.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -516px; }
|
||||
.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -532px; }
|
||||
.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-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -564px; }
|
||||
.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -580px; }
|
||||
.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -596px; }
|
||||
.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -612px; }
|
||||
.ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil { background-position: 0 -628px; }
|
||||
.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -644px; }
|
||||
.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_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -676px; }
|
||||
.ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview { background-position: 0 -692px; }
|
||||
.ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled { background-position: 0 -708px; }
|
||||
.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -724px; }
|
||||
.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -740px; }
|
||||
.ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish { background-position: 0 -756px; }
|
||||
.ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background-position: 0 -772px; }
|
||||
.ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept { background-position: 0 -96px; }
|
||||
.ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled { background-position: 0 -80px; }
|
||||
.ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add { background-position: 0 0; }
|
||||
.ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia { background-position: 0 -208px; }
|
||||
.ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled { background-position: 0 -32px; }
|
||||
.ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage { background-position: 0 -144px; }
|
||||
.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -500px; }
|
||||
.ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left { background-position: 0 -356px; }
|
||||
.ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -340px; }
|
||||
.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -372px; }
|
||||
.ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled { background-position: 0 -16px; }
|
||||
.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -724px; }
|
||||
.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -516px; }
|
||||
.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -740px; }
|
||||
.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -676px; }
|
||||
.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -708px; }
|
||||
.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -772px; }
|
||||
.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -484px; }
|
||||
.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -756px; }
|
||||
.ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -452px; }
|
||||
.ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -564px; }
|
||||
.ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross { background-position: 0 -276px; }
|
||||
.ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline { background-position: 0 -128px; }
|
||||
.ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled { background-position: 0 -192px; }
|
||||
.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -468px; }
|
||||
.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -307px; }
|
||||
.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -291px; }
|
||||
.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -548px; }
|
||||
.ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv { background-position: 0 -48px; }
|
||||
.ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -420px; }
|
||||
.ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -580px; }
|
||||
.ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print { background-position: 0 -260px; }
|
||||
.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -532px; }
|
||||
.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -628px; }
|
||||
.ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled { background-position: 0 -644px; }
|
||||
.ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -388px; }
|
||||
.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -436px; }
|
||||
.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -612px; }
|
||||
.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -692px; }
|
||||
.ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil { background-position: 0 -228px; }
|
||||
.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -596px; }
|
||||
.ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition { background-position: 0 -244px; }
|
||||
.ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -660px; }
|
||||
.ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview { background-position: 0 -64px; }
|
||||
.ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled { background-position: 0 -160px; }
|
||||
.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -324px; }
|
||||
.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -404px; }
|
||||
.ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish { background-position: 0 -112px; }
|
||||
.ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background-position: 0 -176px; }
|
||||
|
||||
.icon { text-indent: -9999px; border: none; outline: none; }
|
||||
.icon.icon-24 { width: 24px; height: 24px; background: url('../images/menu-icons/24x24-s0dc15c36f9.png'); }
|
||||
.icon.icon-24.icon-assetadmin { background-position: 0 -216px; }
|
||||
.icon.icon-24.icon-cmsmain { background-position: 0 -192px; }
|
||||
.icon.icon-24.icon-cmspagescontroller { background-position: 0 -168px; }
|
||||
.icon.icon-24.icon-cmssettingscontroller { background-position: 0 -96px; }
|
||||
.icon.icon-24 { width: 24px; height: 24px; background: url('../images/menu-icons/24x24-s391afdd013.png'); }
|
||||
.icon.icon-24.icon-assetadmin { background-position: 0 -120px; }
|
||||
.icon.icon-24.icon-cmsmain { background-position: 0 -48px; }
|
||||
.icon.icon-24.icon-cmspagescontroller { background-position: 0 -216px; }
|
||||
.icon.icon-24.icon-cmssettingscontroller { background-position: 0 0; }
|
||||
.icon.icon-24.icon-securityadmin { background-position: 0 -24px; }
|
||||
.icon.icon-24.icon-reportadmin { background-position: 0 -240px; }
|
||||
.icon.icon-24.icon-commentadmin { background-position: 0 0; }
|
||||
.icon.icon-24.icon-help { background-position: 0 -144px; }
|
||||
.icon.icon-16 { width: 16px; height: 16px; background: url('../images/menu-icons/16x16-s3f4c846209.png'); }
|
||||
.icon.icon-16.icon-assetadmin { background-position: 0 -144px; }
|
||||
.icon.icon-16.icon-cmsmain { background-position: 0 -128px; }
|
||||
.icon.icon-24.icon-reportadmin { background-position: 0 -72px; }
|
||||
.icon.icon-24.icon-commentadmin { background-position: 0 -192px; }
|
||||
.icon.icon-24.icon-help { background-position: 0 -96px; }
|
||||
.icon.icon-16 { width: 16px; height: 16px; background: url('../images/menu-icons/16x16-sf5b94bb49b.png'); }
|
||||
.icon.icon-16.icon-assetadmin { background-position: 0 -80px; }
|
||||
.icon.icon-16.icon-cmsmain { background-position: 0 -16px; }
|
||||
.icon.icon-16.icon-cmspagescontroller { background-position: 0 -112px; }
|
||||
.icon.icon-16.icon-cmssettingscontroller { background-position: 0 -64px; }
|
||||
.icon.icon-16.icon-securityadmin { background-position: 0 -16px; }
|
||||
.icon.icon-16.icon-reportadmin { background-position: 0 -160px; }
|
||||
.icon.icon-16.icon-commentadmin { background-position: 0 0; }
|
||||
.icon.icon-16.icon-help { background-position: 0 -96px; }
|
||||
.icon.icon-16.icon-cmssettingscontroller { background-position: 0 0; }
|
||||
.icon.icon-16.icon-securityadmin { background-position: 0 -48px; }
|
||||
.icon.icon-16.icon-reportadmin { background-position: 0 -32px; }
|
||||
.icon.icon-16.icon-commentadmin { background-position: 0 -144px; }
|
||||
.icon.icon-16.icon-help { background-position: 0 -64px; }
|
||||
|
||||
html { overflow: hidden; }
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
/** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */
|
||||
.cms .ss-ui-button { background-color: #e6e6e6; }
|
||||
.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-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-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; }
|
||||
|
||||
.cms table.ss-gridfield-table tr td { border-right: 1px solid #9a9a9a; }
|
||||
|
@ -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 .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 .ui-icon-closethick { background: url('../images/sprites-32x32-s871d283813.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 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-s47450c5f5b.png') 0 -396px no-repeat; width: 30px; height: 30px; }
|
||||
|
||||
.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; }
|
||||
|
||||
.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 .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 .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; }
|
||||
@ -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 .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 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 .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; }
|
||||
@ -692,7 +692,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai
|
||||
/** -------------------------------------------- Step labels -------------------------------------------- */
|
||||
.step-label > * { display: inline-block; vertical-align: top; }
|
||||
.step-label .flyout { height: 18px; font-size: 14px; font-weight: bold; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; background-color: #667980; padding: 4px 3px 4px 6px; text-align: center; text-shadow: none; color: #fff; }
|
||||
.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-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; }
|
||||
|
||||
/** -------------------------------------------- Item Edit Form -------------------------------------------- */
|
||||
@ -739,10 +739,10 @@ form.import-form label.left { width: 250px; }
|
||||
/** -------------------------------------------- Buttons for FileUpload -------------------------------------------- */
|
||||
.ss-uploadfield-item-edit-all .ui-button-text { padding-right: 0; }
|
||||
|
||||
.toggle-details-icon { background: url('../images/sprites-32x32-s871d283813.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; }
|
||||
.toggle-details-icon.opened { background: url('../images/sprites-32x32-s871d283813.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; }
|
||||
.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-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-s47450c5f5b.png') 0 -846px 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 -------------------------------------------- */
|
||||
.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 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: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.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; }
|
||||
@ -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 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-apple.jstree-focused, .TreeDropdownField .treedropdownfield-panel .jstree-apple.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, .TreeDropdownField .treedropdownfield-panel .jstree.jstree-focused { 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 .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; }
|
||||
@ -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-invalid, .TreeDropdownField .treedropdownfield-panel #vakata-dragged .jstree-invalid { background: red; }
|
||||
|
||||
.jstree-apple li, .jstree-apple .jstree-apple 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 li, .jstree .jstree ins { background: none; }
|
||||
.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-apple li, .cms-tree.jstree-apple 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-apple li.Root > a .jstree-icon, .cms-tree.jstree-apple 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-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-apple li.disabled > a > .jstree-checkbox, .cms-tree.jstree-apple 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-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-apple li.readonly .jstree-icon, .cms-tree.jstree-apple 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-apple ins, .cms-tree.jstree-apple 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-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-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-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-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-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-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-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; }
|
||||
.tree-holder.jstree, .cms-tree.jstree { /* comment speech bubble - ccs3 only - source: http://nicolasgallagher.com/pure-css-speech-bubbles/demo/ */ }
|
||||
.tree-holder.jstree li, .cms-tree.jstree li { padding: 0px; clear: left; }
|
||||
.tree-holder.jstree li.Root strong, .cms-tree.jstree li.Root strong { font-weight: bold; padding-left: 1px; }
|
||||
.tree-holder.jstree li.Root > a .jstree-icon, .cms-tree.jstree li.Root > a .jstree-icon { background-position: -56px -36px; }
|
||||
.tree-holder.jstree li.status-deletedonlive .text, .cms-tree.jstree li.status-deletedonlive .text { text-decoration: line-through; }
|
||||
.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 li.disabled > a > .jstree-checkbox, .cms-tree.jstree li.disabled > a > .jstree-checkbox { background-position: -57px -54px; }
|
||||
.tree-holder.jstree li.readonly, .cms-tree.jstree li.readonly { color: #aaa; padding-left: 18px; }
|
||||
.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 li.readonly .jstree-icon, .cms-tree.jstree li.readonly .jstree-icon { display: none; }
|
||||
.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 ins, .cms-tree.jstree ins { background-color: transparent; background-image: url(../images/sitetree_ss_default_icons.png); }
|
||||
.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 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 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 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 .jstree-hovered, .cms-tree.jstree .jstree-hovered { text-shadow: none; text-decoration: none; }
|
||||
.tree-holder.jstree .jstree-closed > ins, .cms-tree.jstree .jstree-closed > ins { background-position: 0 0; }
|
||||
.tree-holder.jstree .jstree-open > ins, .cms-tree.jstree .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-RedirectorPage > a .jstree-pageicon { background-position: 0 -16px; }
|
||||
li.class-VirtualPage > a .jstree-pageicon { background-position: 0 -32px; }
|
||||
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.multiple li > a > .jstree-icon { display: none; }
|
||||
.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-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-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 .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 .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.opened .toggle-children-icon { background: url('../images/sprites-32x32-s871d283813.png') 0 -814px no-repeat; }
|
||||
.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-s47450c5f5b.png') 0 -814px no-repeat; }
|
||||
.cms-menu-list li ul li a { border-top: 1px solid #b6c3cb; }
|
||||
.cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #236184; background-color: #338DC1; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+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.opened .toggle-children-icon { background: url('../images/sprites-32x32-s871d283813.png') 0 -846px 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-s47450c5f5b.png') 0 -846px no-repeat; }
|
||||
.cms-menu-list li.current ul { border-top: none; display: block; }
|
||||
.cms-menu-list li.current li { background-color: #287099; }
|
||||
.cms-menu-list li.current li a { font-size: 11px; padding: 0 10px 0 40px; height: 32px; line-height: 32px; color: #e2f0f7; background: none; border-top: 1px solid #2f81b1; border-bottom: 1px solid #1e5270; }
|
||||
@ -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 .icon-view, .cms-content-controls .preview-selector.dropdown a.chzn-single { white-space: nowrap; }
|
||||
.cms-content-controls .icon-view:before, .cms-content-controls .preview-selector.dropdown a.chzn-single:before { display: inline-block; float: left; content: ''; width: 23px; height: 17px; overflow: hidden; }
|
||||
.cms-content-controls .icon-auto:before { background: url('../images/sprites-32x32-s871d283813.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-tablet:before { background: url('../images/sprites-32x32-s871d283813.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-split:before { background: url('../images/sprites-32x32-s871d283813.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-preview:before { background: url('../images/sprites-32x32-s871d283813.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-auto:before { background: url('../images/sprites-32x32-s47450c5f5b.png') 0 -898px 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-s47450c5f5b.png') 0 -1087px 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-s47450c5f5b.png') 0 -1060px 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-s47450c5f5b.png') 0 -1033px 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 .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; }
|
||||
@ -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: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: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: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.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: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 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-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-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-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 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; }
|
||||
@ -1158,28 +1194,28 @@ green tick icon as a background this is created using compass generated classes
|
||||
/* Default CMS logo */
|
||||
.cms-logo a { background-image: url("../images/logo_small@2x.png"); background-size: 22px 22px; }
|
||||
/* 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-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-desktop:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.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-mobile:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.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-edit:before { background-image: url('../images/sprites-32x32-2x-sa271d435b9.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 .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: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.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:hover:after { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -52px; 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-s6ccfbe50f9.png'); background-position: 0 -274px; 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-s6ccfbe50f9.png'); background-position: 0 -436px; 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-s6ccfbe50f9.png'); background-position: 0 -409px; 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-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-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-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-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-s6ccfbe50f9.png'); background-position: 0 -52px; background-size: 30px auto; }
|
||||
/* 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.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.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.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 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-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-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-s6ccfbe50f9.png'); background-position: 0 -258px; background-size: 30px auto; }
|
||||
/* 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; }
|
||||
/* 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 .ui-icon-closethick { background-image: url('../images/sprites-32x32-2x-sa271d435b9.png'); background-position: 0 -144px; 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-s6ccfbe50f9.png'); background-position: 0 -144px; background-size: 30px auto; }
|
||||
/* 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.tree a { background-image: url('../images/sprites-64x64-2x-se3e3f47b94.png'); background-position: 0 -250px; background-size: 40px auto; }
|
||||
|
BIN
admin/images/sprites-32x32-2x-s6ccfbe50f9.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
admin/images/sprites-32x32-s47450c5f5b.png
Normal file
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.7 KiB |
@ -1158,7 +1158,7 @@ jQuery.noConflict();
|
||||
* selection support. Rather than manually adding classes to selects we want
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
16
admin/javascript/lang/eo.js
Normal 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."
|
||||
});
|
||||
}
|
16
admin/javascript/lang/lt.js
Normal 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."
|
||||
});
|
||||
}
|
16
admin/javascript/lang/nb.js
Normal 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."
|
||||
});
|
||||
}
|
@ -10,7 +10,7 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
||||
"ModelAdmin.SAVED": "Opgeslagen",
|
||||
"ModelAdmin.REALLYDELETE": "Weet u zeker dat u wilt verwijderen?",
|
||||
"ModelAdmin.DELETED": "Verwijderd",
|
||||
"ModelAdmin.VALIDATIONERROR": "Validatie fout",
|
||||
"LeftAndMain.PAGEWASDELETED": "Deze pagina is verwijderd. Om een pagina aan te passen, selecteer pagina aan de linkerkant."
|
||||
"ModelAdmin.VALIDATIONERROR": "Validatiefout",
|
||||
"LeftAndMain.PAGEWASDELETED": "Deze pagina is verwijderd. Om een pagina aan te passen, selecteer deze aan de linkerkant."
|
||||
});
|
||||
}
|
16
admin/javascript/lang/sl.js
Normal 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."
|
||||
});
|
||||
}
|
10
admin/javascript/lang/src/sv.js
Normal 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."
|
||||
}
|
16
admin/javascript/lang/sv.js
Normal 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."
|
||||
});
|
||||
}
|
@ -1313,10 +1313,12 @@ form.member-profile-form {
|
||||
// most styles should be applied to .cms-dialog instead (which declares the content in the frame)
|
||||
.ui-dialog {
|
||||
background: url("../images/textures/bg_cms_main_content.png") repeat left top #F0F3F4;
|
||||
border: 3px solid #000 !important;
|
||||
border-radius: $grid-y;
|
||||
background-clip: content-box;
|
||||
border: 1px solid #666 !important;
|
||||
@include border-radius($grid-y);
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
@include box-shadow(0px 0px 30px 10px rgba(0,0,0,.3));
|
||||
|
||||
// Titlebar for pop-up dialog.
|
||||
.ui-dialog-titlebar.ui-widget-header {
|
||||
@ -1335,6 +1337,7 @@ form.member-profile-form {
|
||||
}
|
||||
|
||||
.ui-dialog-content {
|
||||
@include border-radius($grid-y);
|
||||
overflow: auto; // TODO Replace with proper $.layout grid
|
||||
|
||||
&.loading {
|
||||
@ -1470,7 +1473,7 @@ body.cms-dialog {
|
||||
width:100%;
|
||||
height: 40px;
|
||||
h3{
|
||||
padding: 0 8px;
|
||||
padding: 3px 8px;
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
@ -68,13 +68,13 @@
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
text-shadow: none;
|
||||
text-shadow:1px 1px 1px white;
|
||||
}
|
||||
> {
|
||||
ins {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
&.jstree-checkbox{
|
||||
&.jstree-checkbox {
|
||||
height:19px; //Larger to help avoid accidental page loads when trying to click checkboxes
|
||||
}
|
||||
}
|
||||
@ -111,7 +111,7 @@
|
||||
background: transparent !important;
|
||||
width: 100%;
|
||||
}
|
||||
a, a:hover{
|
||||
a, a:hover {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
text-indent: -9999px !important;
|
||||
@ -138,9 +138,9 @@
|
||||
}
|
||||
|
||||
// Custom styles
|
||||
.jstree-apple.jstree-focused {
|
||||
.jstree.jstree-focused {
|
||||
background: none;
|
||||
.jstree-apple > ul {
|
||||
.jstree > ul {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
@ -182,7 +182,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.jstree-themeroller{
|
||||
.jstree-themeroller {
|
||||
a {
|
||||
padding: 0 2px;
|
||||
}
|
||||
@ -276,7 +276,7 @@
|
||||
min-width: 180px;
|
||||
*width:180px;
|
||||
}
|
||||
ul,li{
|
||||
ul,li {
|
||||
margin: 0;
|
||||
padding: 0 ;
|
||||
list-style-type: none;
|
||||
@ -316,7 +316,7 @@
|
||||
margin-top: 3px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
&.vakata-hover > a{
|
||||
&.vakata-hover > a {
|
||||
padding: 1px 10px;
|
||||
background: #3875d7;
|
||||
@include background-image(linear-gradient(top, #3875d7 20%, #2a62bc 90%));
|
||||
@ -326,7 +326,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
#vakata-contextmenu{
|
||||
#vakata-contextmenu {
|
||||
.right {
|
||||
right: 100%;
|
||||
left: auto;
|
||||
@ -345,25 +345,25 @@
|
||||
@include box-shadow(0 0 10px #CCC);
|
||||
&.col-2{
|
||||
width:180px * 2; // 2x the size of the original ul
|
||||
li{
|
||||
li {
|
||||
width:50%;
|
||||
}
|
||||
}
|
||||
&.col-3{
|
||||
width:180px * 3; // 3x the size of the original ul
|
||||
li{
|
||||
li {
|
||||
width:33%;
|
||||
}
|
||||
}
|
||||
li{
|
||||
li {
|
||||
min-width:180px;
|
||||
float:left;
|
||||
a{
|
||||
a {
|
||||
@include hide-text-overflow;
|
||||
}
|
||||
}
|
||||
}
|
||||
li{
|
||||
li {
|
||||
&.vakata-separator {
|
||||
min-height: 0;
|
||||
height: 1px;
|
||||
@ -409,8 +409,8 @@
|
||||
|
||||
}
|
||||
|
||||
.jstree-apple {
|
||||
li, .jstree-apple ins {
|
||||
.jstree {
|
||||
li, .jstree ins {
|
||||
background:none;
|
||||
}
|
||||
.jstree-unchecked, .jstree-checked, .jstree-undetermined {
|
||||
@ -420,9 +420,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tree-holder, .cms-tree{
|
||||
&.jstree-apple{
|
||||
li{
|
||||
.tree-holder, .cms-tree {
|
||||
&.jstree {
|
||||
li {
|
||||
padding: 0px;
|
||||
clear: left;
|
||||
&.Root {
|
||||
@ -440,8 +440,8 @@
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
&.jstree-checked{
|
||||
> a, > a:link{
|
||||
&.jstree-checked {
|
||||
> a, > a:link {
|
||||
background-color: $color-cms-batchactions-menu-selected-background;
|
||||
}
|
||||
}
|
||||
@ -478,30 +478,15 @@
|
||||
span.badge {
|
||||
clear: both;
|
||||
text-transform: uppercase;
|
||||
text-shadow: none;
|
||||
display: inline-block;
|
||||
padding: 0px 3px;
|
||||
position: relative;
|
||||
padding: 2px 3px 1px;
|
||||
font-size: 0.75em;
|
||||
line-height: 1em;
|
||||
margin-left: 3px;
|
||||
margin-right: 6px;
|
||||
margin-top: -1px;
|
||||
@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/ */
|
||||
@ -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 {
|
||||
float: left;
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
li.class-HomePage > &{
|
||||
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 {
|
||||
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
|
||||
// jstree icon (which is only used for its dragging handles)
|
||||
a.jstree-loading{
|
||||
a.jstree-loading {
|
||||
.jstree-icon {
|
||||
background-image: none !important;
|
||||
}
|
||||
|
@ -38,8 +38,8 @@
|
||||
|
||||
& a.ui-dialog-titlebar-close {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -15px;
|
||||
top: -5px;
|
||||
right: -13px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
z-index: 100000;
|
||||
|
@ -62,7 +62,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
}
|
||||
|
||||
if($this->relationDepth > 0) {
|
||||
foreach($obj->has_one() as $relName => $relClass) {
|
||||
foreach($obj->hasOne() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// 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;
|
||||
|
||||
// Field filtering
|
||||
@ -103,7 +103,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
$serobj->$relName = $innerParts;
|
||||
}
|
||||
|
||||
foreach($obj->many_many() as $relName => $relClass) {
|
||||
foreach($obj->manyMany() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
|
@ -68,7 +68,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
}
|
||||
|
||||
if($this->relationDepth > 0) {
|
||||
foreach($obj->has_one() as $relName => $relClass) {
|
||||
foreach($obj->hasOne() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
@ -85,7 +85,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
. "\"></$relName>\n";
|
||||
}
|
||||
|
||||
foreach($obj->has_many() as $relName => $relClass) {
|
||||
foreach($obj->hasMany() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
@ -103,7 +103,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
$xml .= "</$relName>\n";
|
||||
}
|
||||
|
||||
foreach($obj->many_many() as $relName => $relClass) {
|
||||
foreach($obj->manyMany() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
|
@ -438,12 +438,21 @@ class Director implements TemplateGlobalProvider {
|
||||
public static function absoluteURL($url, $relativeToSiteBase = 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) {
|
||||
$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($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
|
||||
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
|
||||
*/
|
||||
protected static function force_redirect($destURL) {
|
||||
$response = new SS_HTTPResponse(
|
||||
"<h1>Your browser is not accepting header redirects</h1>".
|
||||
"<p>Please <a href=\"$destURL\">click here</a>",
|
||||
301
|
||||
);
|
||||
$response = new SS_HTTPResponse();
|
||||
$response->redirect($destURL, 301);
|
||||
|
||||
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
|
||||
$response->output();
|
||||
|
@ -361,7 +361,8 @@ class HTTP {
|
||||
if(
|
||||
$body &&
|
||||
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
|
||||
) {
|
||||
// IE6-IE8 have problems saving files when https and no-cache are used
|
||||
|
@ -250,7 +250,7 @@ class Config {
|
||||
* leak through to other instances.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->cache = new Config_LRU();
|
||||
$this->cache = new Config_MemCache();
|
||||
}
|
||||
|
||||
public function __clone() {
|
||||
@ -681,6 +681,7 @@ class Config {
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage core
|
||||
* @deprecated 3.2
|
||||
*/
|
||||
class Config_LRU {
|
||||
const SIZE = 1000;
|
||||
@ -692,6 +693,7 @@ class Config_LRU {
|
||||
protected $c = 0;
|
||||
|
||||
public function __construct() {
|
||||
Deprecation::notice('3.2', 'Please use Config_MemCache instead', Deprecation::SCOPE_CLASS);
|
||||
if (version_compare(PHP_VERSION, '5.3.7', '<')) {
|
||||
// SplFixedArray causes seg faults before PHP 5.3.7
|
||||
$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
|
||||
* @subpackage core
|
||||
|
@ -230,15 +230,32 @@ class Convert {
|
||||
|
||||
/**
|
||||
* Converts an XML string to a PHP array
|
||||
* See http://phpsecurity.readthedocs.org/en/latest/Injection-Attacks.html#xml-external-entity-injection
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,8 +85,6 @@ require_once 'control/injector/Injector.php';
|
||||
// Initialise the dependency injector as soon as possible, as it is
|
||||
// subsequently used by some of the following code
|
||||
$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
|
||||
$injector->registerService(Config::inst());
|
||||
|
||||
Injector::set_inst($injector);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
* @return $this
|
||||
*/
|
||||
public function setPageLength($length) {
|
||||
$this->pageLength = $length;
|
||||
@ -77,7 +78,8 @@ class PaginatedList extends SS_ListDecorator {
|
||||
/**
|
||||
* Sets the current page.
|
||||
*
|
||||
* @param int $page
|
||||
* @param int $page Page index beginning with 1
|
||||
* @return $this
|
||||
*/
|
||||
public function setCurrentPage($page) {
|
||||
$this->pageStart = ($page - 1) * $this->getPageLength();
|
||||
@ -182,10 +184,11 @@ class PaginatedList extends SS_ListDecorator {
|
||||
* @return IteratorIterator
|
||||
*/
|
||||
public function getIterator() {
|
||||
if($this->limitItems) {
|
||||
$pageLength = $this->getPageLength();
|
||||
if($this->limitItems && $pageLength) {
|
||||
$tmptList = clone $this->list;
|
||||
return new IteratorIterator(
|
||||
$tmptList->limit($this->getPageLength(), $this->getPageStart())
|
||||
$tmptList->limit($pageLength, $this->getPageStart())
|
||||
);
|
||||
} else {
|
||||
return new IteratorIterator($this->list);
|
||||
@ -325,14 +328,20 @@ class PaginatedList extends SS_ListDecorator {
|
||||
* @return int
|
||||
*/
|
||||
public function CurrentPage() {
|
||||
return floor($this->getPageStart() / $this->getPageLength()) + 1;
|
||||
$pageLength = $this->getPageLength();
|
||||
return $pageLength
|
||||
? floor($this->getPageStart() / $pageLength) + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function LastItem() {
|
||||
if ($start = $this->getPageStart()) {
|
||||
return min($start + $this->getPageLength(), $this->getTotalItems());
|
||||
$pageLength = $this->getPageLength();
|
||||
if(!$pageLength) {
|
||||
return $this->getTotalItems();
|
||||
} elseif ($start = $this->getPageStart()) {
|
||||
return min($start + $pageLength, $this->getTotalItems());
|
||||
} else {
|
||||
return min($this->getPageLength(), $this->getTotalItems());
|
||||
return min($pageLength, $this->getTotalItems());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ class SS_ClassLoader {
|
||||
* @return bool
|
||||
*/
|
||||
public function classExists($class) {
|
||||
return class_exists($class, false) || $this->getItemPath($class);
|
||||
return class_exists($class, false) || interface_exists($class, false) || $this->getItemPath($class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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: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 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.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; }
|
||||
|
@ -11,6 +11,7 @@
|
||||
Used in side panels and action tabs
|
||||
*/
|
||||
.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 .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; }
|
||||
|
@ -220,9 +220,9 @@ abstract class BulkLoader extends ViewableData {
|
||||
// using $$includerelations flag as false, so that it only contain $db fields
|
||||
$spec['fields'] = (array)singleton($this->objectClass)->fieldLabels(false);
|
||||
|
||||
$has_ones = singleton($this->objectClass)->has_one();
|
||||
$has_manys = singleton($this->objectClass)->has_many();
|
||||
$many_manys = singleton($this->objectClass)->many_many();
|
||||
$has_ones = singleton($this->objectClass)->hasOne();
|
||||
$has_manys = singleton($this->objectClass)->hasMany();
|
||||
$many_manys = singleton($this->objectClass)->manyMany();
|
||||
|
||||
$spec['relations'] = (array)$has_ones + (array)$has_manys + (array)$many_manys;
|
||||
|
||||
|
@ -120,7 +120,7 @@ class CsvBulkLoader extends BulkLoader {
|
||||
$relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
|
||||
}
|
||||
if(!$relationObj || !$relationObj->exists()) {
|
||||
$relationClass = $obj->has_one($relationName);
|
||||
$relationClass = $obj->hasOneComponent($relationName);
|
||||
$relationObj = new $relationClass();
|
||||
//write if we aren't previewing
|
||||
if (!$preview) $relationObj->write();
|
||||
|
@ -110,7 +110,11 @@ class FixtureBlueprint {
|
||||
// Populate overrides
|
||||
if($data) foreach($data as $fieldName => $fieldVal) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -127,7 +131,7 @@ class FixtureBlueprint {
|
||||
|
||||
// Populate all relations
|
||||
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();
|
||||
|
||||
$parsedItems = array();
|
||||
@ -165,15 +169,15 @@ class FixtureBlueprint {
|
||||
$parsedItems[] = $this->parseValue($item, $fixtures);
|
||||
}
|
||||
|
||||
if($obj->has_many($fieldName)) {
|
||||
if($obj->hasManyComponent($fieldName)) {
|
||||
$obj->getComponents($fieldName)->setByIDList($parsedItems);
|
||||
} elseif($obj->many_many($fieldName)) {
|
||||
} elseif($obj->manyManyComponent($fieldName)) {
|
||||
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$hasOneField = preg_replace('/ID$/', '', $fieldName);
|
||||
if($className = $obj->has_one($hasOneField)) {
|
||||
if($className = $obj->hasOneComponent($hasOneField)) {
|
||||
$obj->{$hasOneField.'ID'} = $this->parseValue($fieldVal, $fixtures, $fieldClass);
|
||||
// Inject class for polymorphic relation
|
||||
if($className === 'DataObject') {
|
||||
|
@ -12,7 +12,7 @@
|
||||
* $this->get("your/url");
|
||||
*
|
||||
* // 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
|
||||
* $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid."));
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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:
|
||||
* Web server e.g. Apache, Nginx
|
||||
@ -12,7 +12,7 @@ At a high level you will need a:
|
||||
##*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.
|
||||
|
||||
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
|
||||
* [How To Install Silverstripe on Your VPS](https://www.digitalocean.com/community/tutorials/how-to-install-silverstripe-on-your-vps)
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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]`
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -107,4 +107,4 @@ e.g. `/etc/nginx/sites-enabled/mysite`:
|
||||
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.
|
||||
|
@ -3,12 +3,12 @@
|
||||
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).
|
||||
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
|
||||
|
||||
* [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
|
||||
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.
|
||||
@ -18,7 +18,7 @@ name' and the default login details. Follow the questions and select the *instal
|
||||
|
||||
## 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">
|
||||
SilverStripe ships with default rewriting rules specific to your web server. Apart from
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
@ -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.
|
||||
* 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}
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
# Advanced usage
|
||||
@ -290,9 +354,9 @@ which triggers their installation into the correct path.
|
||||
|
||||
### How do I convert an existing project to Composer?
|
||||
|
||||
The easiest way is to follow the [upgrading](/installation/upgrading) instructions
|
||||
and switch to a newer release. Alternatively, copy the `composer.json` file from
|
||||
a newer release, and adjust the version settings in the "require" section to your needs.
|
||||
Copy the `composer.json` file from a newer release, and adjust the
|
||||
version settings in the "require" section to your needs. Then refer to
|
||||
the [upgrading documentation](/upgrading).
|
||||
You'll also need to update your webserver configuration
|
||||
from there (`.htaccess` or `web.config` files), in order to prevent
|
||||
web access to the composer-generated files.
|
||||
|
@ -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_PASSWORD', '<password>');
|
||||
|
||||
// This causes errors to be written to the silverstripe.log file in the same directory as this file, so /var.
|
||||
// Before PHP 5.3.0, you'll need to use dirname(__FILE__) instead of __DIR__
|
||||
define('SS_ERROR_LOG', __DIR__ . '/silverstripe.log');
|
||||
// This causes errors to be written to the BASE_PATH/silverstripe.log file.
|
||||
// Path must be relative to BASE_PATH
|
||||
define('SS_ERROR_LOG', 'silverstripe.log');
|
||||
|
||||
// This is used by sake to know which directory points to which URL
|
||||
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_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_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_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 |
|
||||
|
@ -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`.
|
||||
|
||||
See [directory-structure](/topics/directory-structure) for more information.
|
||||
See [directory structure](directory_structure) for more information.
|
||||
|
||||
## 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".
|
||||
|
||||
:::php
|
||||
$greeting = "She said 'hello'";
|
||||
$greeting = "They said 'hello'";
|
||||
|
||||
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
|
||||
|
||||
* [Topics: CSS](/topics/css)
|
||||
* [Reference: CMS Architecture](/reference/cms-archirecture)
|
||||
* [Reference: CMS Architecture](/developer_guides/customising_the_admin_interface/cms_architecture)
|
||||
|
@ -178,7 +178,7 @@ would create a new tab called "New Tab", and a single "Author" textfield inside.
|
||||
</div>
|
||||
|
||||
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
|
||||
return $fields;
|
||||
|
@ -9,7 +9,7 @@ This tutorial is deprecated, and has been replaced by Lessons 7, 8, 9, and 10 in
|
||||
|
||||
## 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.
|
||||
|
||||
## 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`
|
||||
and `$has_many` properties on the objects.
|
||||
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)
|
||||
and ["datamodel"](/developer_guides/model/data_model_and_orm) topics for more information).
|
||||
the array values contain the class name
|
||||
(see the ["datamodel"](/developer_guides/model/data_model_and_orm)
|
||||
topic for more information).
|
||||
|
||||
As you can see, only the `Project` model extends `Page`,
|
||||
while `Student` is a plain `DataObject` subclass.
|
||||
|
@ -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">
|
||||
These tutorials are deprecated, and have been replaced by the new [Lessons](http://silverstripe.org/learn/lessons) section.
|
||||
</div>
|
||||
[CHIDLREN]
|
||||
|
||||
## Video lessons
|
||||
These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites.
|
||||
|
||||
|
@ -12,8 +12,8 @@ information.
|
||||
|
||||
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)
|
||||
design pattern. Database Columns are is 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.
|
||||
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.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -401,7 +401,7 @@ Remove both Sam and Sig..
|
||||
'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
|
||||
$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)
|
||||
));
|
||||
|
||||
// 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.
|
||||
|
||||
@ -512,7 +512,7 @@ whenever a new object is created.
|
||||
|
||||
<div class="notice" markdown='1'>
|
||||
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>
|
||||
|
||||
## Subclasses
|
||||
@ -548,7 +548,7 @@ The data for the following classes would be stored across the following tables:
|
||||
- LastEdited: Datetime
|
||||
- Title: Varchar
|
||||
- Content: Text
|
||||
NewsArticle:
|
||||
NewsPage:
|
||||
- ID: Int
|
||||
- Summary: Text
|
||||
|
||||
@ -558,7 +558,7 @@ Accessing the data is transparent to the developer.
|
||||
$news = NewsPage::get();
|
||||
|
||||
foreach($news as $article) {
|
||||
echo $news->Title;
|
||||
echo $article->Title;
|
||||
}
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
|
@ -204,6 +204,27 @@ The relationship can also be navigated in [templates](../templates).
|
||||
<% end_if %>
|
||||
<% 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?
|
||||
|
||||
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.
|
||||
|
@ -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.
|
||||
|
||||
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".
|
||||
|
||||
:::php
|
||||
|
@ -103,7 +103,7 @@ Creates a map based on the first two columns of the query result.
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
@ -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
|
||||
support the following:
|
||||
|
||||
@ -27,8 +27,8 @@ support the following:
|
||||
* `unique`: Index plus uniqueness constraint on the value
|
||||
* `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
|
||||
`$indexes` definition. Keep in mind this will likely make your code less portable between databases.
|
||||
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 that using raw SQL is likely to make your code less portable between DBMSs.
|
||||
|
||||
**mysite/code/MyTestObject.php**
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Dynamic Default Values
|
||||
|
||||
The [api:DataObject::$defaults] array allows you to specify simple static values to be the default value for when a
|
||||
record is created, but in many situations default values needs to be dynamically calculated. In order to do this, the
|
||||
`[api:DataObject->populateDefaults()]` method will need to be overloaded.
|
||||
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 need to be dynamically calculated. In order to do this, the
|
||||
[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
|
||||
object!
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
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.
|
||||
[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.
|
||||
|
||||
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
|
||||
|
||||
* [Howto: "Pagination"](/howto/pagination)
|
||||
* [Howto: "Pagination"](/templates/how_tos/pagination)
|
||||
|
@ -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.
|
||||
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
|
||||
|
@ -22,7 +22,7 @@ Requiring assets from the template is restricted compared to the PHP 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]`.
|
||||
|
||||
:::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
|
||||
already loaded, then, it's inserted before the first `<script>` tag. If this causes problems, it can be configured.
|
||||
|
||||
**mysite/_config/app.yml**
|
||||
|
||||
:::yml
|
||||
Requirements:
|
||||
write_js_to_body: true
|
||||
force_js_to_bottom: true
|
||||
:::php
|
||||
Requirements::set_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
|
||||
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
|
||||
|
||||
|
@ -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:
|
||||
|
||||
1. mysite (or other name given to site folder)
|
||||
2. themes
|
||||
3. modules
|
||||
4. framework.
|
||||
2. module-specific themes (e.g. themes/simple_blog)
|
||||
3. themes (e.g. themes/simple)
|
||||
4. modules (e.g. blog)
|
||||
5. framework
|
||||
|
||||
<div class="warning">
|
||||
Whenever you add or remove template files, rebuild the manifest by visiting `http://yoursite.com/?flush=1`. You can
|
||||
|
@ -47,6 +47,30 @@ located within the `themes` directory.
|
||||
|
||||
![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
|
||||
|
||||
If you want to submit your theme to the SilverStripe directory then check
|
||||
|
@ -31,4 +31,4 @@ top level menu with a nested second level using the `Menu` loop and a `Children`
|
||||
## Related
|
||||
|
||||
* [Template Syntax](../syntax)
|
||||
* [Common Variables](../command_variables)
|
||||
* [Common Variables](../common_variables)
|
@ -88,6 +88,9 @@ when using custom lists.
|
||||
$pages = new PaginatedList(Page::get(), $this->request);
|
||||
$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
|
||||
|
||||
| Variable | Description |
|
||||
|
@ -39,7 +39,7 @@ routing.
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
**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.
|
||||
* `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_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**
|
||||
|
||||
@ -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
|
||||
`htmlaction`. `MyCustomTemplate.ss` would be used rather than `TeamsController`.
|
||||
|
||||
For more information about templates, inheritance and how to rendering into views, See the
|
||||
[Templates and Views](templates) documentation.
|
||||
For more information about templates, inheritance and how to render into views, See the
|
||||
[Templates and Views](../templates) documentation.
|
||||
|
||||
## Link
|
||||
|
||||
|
@ -135,6 +135,14 @@ start parsing variables and the appropriate controller action AFTER the `//`).
|
||||
|
||||
## 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
|
||||
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.
|
||||
@ -154,12 +162,42 @@ This is useful when you want to provide custom actions for the mapping of `teams
|
||||
);
|
||||
|
||||
private static $url_handlers = array(
|
||||
'staff/$ID/$Name' => 'payroll'
|
||||
'staff/$ID/$Name' => 'payroll',
|
||||
'coach/$ID/$Name' => 'payroll'
|
||||
);
|
||||
|
||||
The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules.
|
||||
|
||||
Now let’s 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
|
||||
|
||||
* [api:Controller] API documentation
|
||||
|
@ -45,7 +45,7 @@ In practice, this looks like:
|
||||
FormAction::create("doSayHello")->setTitle("Say hello")
|
||||
);
|
||||
|
||||
$required = new RequiredFields('Name')
|
||||
$required = new RequiredFields('Name');
|
||||
|
||||
$form = new Form($this, 'HelloForm', $fields, $actions, $required);
|
||||
|
||||
|
@ -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
|
||||
```
|
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
Each action is represented as a instance of a specific class
|
||||
(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
|
||||
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.
|
||||
|
||||
:::php
|
||||
@ -142,6 +142,6 @@ message to the user interface indicating a successful message.
|
||||
|
||||
## Related
|
||||
|
||||
* [GridField Reference](/reference/grid-field)
|
||||
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin)
|
||||
* [Tutorial 5: Dataobject Relationship Management](/tutorials/5-dataobject-relationship-management)
|
||||
* [GridField Reference](/developer_guides/forms/field_types/gridfield)
|
||||
* [ModelAdmin: A UI driven by GridField](/developer_guides/customising_the_admin_interface/modeladmin)
|
||||
* [Tutorial 5: Dataobject Relationship Management](/tutorials/dataobject_relationship_management)
|
||||
|
@ -16,7 +16,7 @@ throughout the site. Out of the box this includes selecting the current site the
|
||||
|
||||
<% with $SiteConfig %>
|
||||
$Title $AnotherField
|
||||
<% end_loop %>
|
||||
<% end_with %>
|
||||
|
||||
To access variables in the PHP:
|
||||
|
||||
@ -61,10 +61,10 @@ Then activate the extension.
|
||||
|
||||
<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.
|
||||
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>
|
||||
|
||||
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.
|
||||
|
||||
## API Documentation
|
||||
|
@ -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,
|
||||
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
|
||||
of your module containing the meta-data about your module.
|
||||
|
||||
|
@ -309,7 +309,7 @@ equal the class names they manage.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
* [How to use a FixtureFactory](how_to/fixturefactories/)
|
||||
* [How to use a FixtureFactory](how_tos/fixturefactories/)
|
||||
|
||||
## API Documentation
|
||||
|
||||
|
@ -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>
|
@ -12,7 +12,7 @@ response and modify the session within a test.
|
||||
:::php
|
||||
<?php
|
||||
|
||||
class HomePageTest extends SapphireTest {
|
||||
class HomePageTest extends FunctionalTest {
|
||||
|
||||
/**
|
||||
* Test generation of the view
|
||||
@ -24,7 +24,7 @@ response and modify the session within a test.
|
||||
$this->assertEquals(200, $page->getStatusCode());
|
||||
|
||||
// We should see a login form
|
||||
$login = $this->submitForm("#LoginForm", null, array(
|
||||
$login = $this->submitForm("LoginFormID", null, array(
|
||||
'Email' => 'test@test.com',
|
||||
'Password' => 'wrongpassword'
|
||||
));
|
||||
|
@ -12,18 +12,15 @@ SilverStripe uses [PHPUnit](http://www.phpunit.de) for unit tests, and the frame
|
||||
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
|
||||
the [Testing Glossary](glossary). To get started now, follow the installation instructions below, and check
|
||||
[Troubleshooting](testing-guide-troubleshooting) in case you run into any problems.
|
||||
the [Testing Glossary](testing_glossary). To get started now, follow the installation instructions below.
|
||||
|
||||
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
|
||||
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
|
||||
([installation instructions](/installation/composer)).
|
||||
([installation instructions](/getting_started/composer)).
|
||||
|
||||
## Install with Composer
|
||||
|
||||
@ -108,7 +105,7 @@ All command-line arguments are documented on
|
||||
|
||||
### 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.
|
||||
While the custom test runner a handy tool, its also more limited than using `phpunit` directly,
|
||||
particularly around formatting test output.
|
||||
|
@ -24,7 +24,7 @@ Append the option and corresponding value to your URL in your browser's address
|
||||
|
||||
| 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. |
|
||||
| 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 |
|
||||
|
@ -3,7 +3,7 @@ summary: Learn how to identify errors in your application and best practice for
|
||||
# Debugging
|
||||
|
||||
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.
|
||||
|
||||
[CHILDREN]
|
||||
|
@ -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:
|
||||
|
||||
:::ss
|
||||
<% cached 'database', LastEdited %>
|
||||
<% cached 'database', $LastEdited %>
|
||||
<!-- that updates every time the record changes. -->
|
||||
<% 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 -->
|
||||
<% end_cached %>
|
||||
|
||||
<% cached 'loginblock', LastEdited, CurrentMember.isAdmin %>
|
||||
<% cached 'loginblock', $LastEdited, $CurrentMember.isAdmin %>
|
||||
<!-- recached when block object changes, and if the user is admin -->
|
||||
<% 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
|
||||
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
|
||||
otherwise. By using aggregates, we do that like this:
|
||||
|
||||
:::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.
|
||||
|
||||
@ -67,10 +67,10 @@ If we have a block that shows a list of categories, we can make sure the cache u
|
||||
or edited
|
||||
|
||||
:::ss
|
||||
<% cached 'categorylist', List(Category).max(LastEdited), List(Category).count() %>
|
||||
<% cached 'categorylist', $List('Category').max('LastEdited'), $List('Category').count() %>
|
||||
|
||||
<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.
|
||||
</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.
|
||||
|
||||
:::ss
|
||||
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %>
|
||||
<% cached 'favourites', $CurrentMember.ID, $CurrentMember.Favourites.max('LastEdited') %>
|
||||
|
||||
## Cache key calculated in controller
|
||||
|
||||
@ -100,7 +100,7 @@ extract that logic into the controller.
|
||||
Then using that function in the cache key:
|
||||
|
||||
:::ss
|
||||
<% cached FavouriteCacheKey %>
|
||||
<% cached $FavouriteCacheKey %>
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
:::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
|
||||
@ -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
|
||||
|
||||
:::ss
|
||||
<% cached 'blogstatistics', Blog.ID, BlogStatisticsCounter %>
|
||||
<% cached 'blogstatistics', $Blog.ID, $BlogStatisticsCounter %>
|
||||
|
||||
|
||||
## Cache block conditionals
|
||||
@ -146,7 +146,7 @@ Following on from the previous example, you might wish to only cache slightly-st
|
||||
heavy load:
|
||||
|
||||
:::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.
|
||||
@ -155,7 +155,7 @@ To cache the contents of a page for all anonymous users, but dynamically calcula
|
||||
use something like:
|
||||
|
||||
:::ss
|
||||
<% cached unless CurrentUser %>
|
||||
<% cached unless $CurrentUser %>
|
||||
|
||||
## Uncached
|
||||
|
||||
@ -178,10 +178,10 @@ portion dynamic, without having to include any member info in the page's cache k
|
||||
An example:
|
||||
|
||||
:::ss
|
||||
<% cached LastEdited %>
|
||||
<% cached $LastEdited %>
|
||||
Our wonderful site
|
||||
|
||||
<% cached Member.ID %>
|
||||
<% cached $Member.ID %>
|
||||
Welcome $Member.Name
|
||||
<% 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:
|
||||
|
||||
:::ss
|
||||
<% cached LastEdited %>
|
||||
<% cached $LastEdited %>
|
||||
Our wonderful site
|
||||
|
||||
<% uncached %>
|
||||
@ -214,7 +214,7 @@ letting you know if you've done this. You can often get around this using aggreg
|
||||
Failing example:
|
||||
|
||||
:::ss
|
||||
<% cached LastEdited %>
|
||||
<% cached $LastEdited %>
|
||||
|
||||
<% loop $Children %>
|
||||
<% cached LastEdited %>
|
||||
@ -227,9 +227,9 @@ Failing example:
|
||||
Can be re-written as:
|
||||
|
||||
:::ss
|
||||
<% cached LastEdited %>
|
||||
<% cached $LastEdited %>
|
||||
|
||||
<% cached AllChildren.max(LastEdited) %>
|
||||
<% cached $AllChildren.max('LastEdited') %>
|
||||
<% loop $Children %>
|
||||
$Name
|
||||
<% end_loop %>
|
||||
|
@ -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,
|
||||
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
|
||||
* An error occurs during startup
|
||||
|
||||
|
@ -7,7 +7,7 @@ See our "[Release Process](/misc/release-process#security-releases) on how to re
|
||||
|
||||
## 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
|
||||
escaped.
|
||||
|
||||
@ -95,8 +95,8 @@ result in *double escaping* and alters the actually saved data (e.g. by adding s
|
||||
### Manual escaping
|
||||
|
||||
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)
|
||||
and [datamodel](/topics/datamodel) for ways to parameterise, cast, and convert your data.
|
||||
but there may be cases where you need to take care of escaping yourself. See [coding-conventions](/getting_started/coding-conventions)
|
||||
and [datamodel](/developer_guides/model) for ways to parameterise, cast, and convert your data.
|
||||
|
||||
* `SQLQuery`
|
||||
* `DB::query()`
|
||||
|
@ -216,7 +216,7 @@ $service->request('service.json', 'GET', null, null, $curlOptions);
|
||||
|
||||
## How to's
|
||||
|
||||
* [Embed an RSS Feed](how_to/embed_rss)
|
||||
* [Embed an RSS Feed](how_tos/embed_rss)
|
||||
|
||||
## API Documentation
|
||||
|
||||
|
@ -11,7 +11,7 @@ The default output of a [api:SearchContext] is either a [api:SQLQuery] object fo
|
||||
[api:DataObject] instance.
|
||||
|
||||
<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>
|
||||
|
||||
## Usage
|
||||
|
@ -80,7 +80,7 @@ not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
|
||||
### Language Names
|
||||
|
||||
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`:
|
||||
|
||||
@ -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"
|
||||
|
||||
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.
|
||||
|
||||
**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
|
||||
|
||||
* [Help to translate](/misc/contribute/translation) - 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/translations) - Instructions for online collaboration to translate core
|
||||
* [Help to translate](../../contributing/translation_process) - Instructions for adding translation to your own modules
|
||||
* [http://www.i18nguy.com/](http://www.i18nguy.com/)
|
||||
* [balbus.tk i18n notes](http://www.balbus.tk/internationalize)
|
||||
|
@ -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). |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `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
|
||||
|
||||
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.
|
@ -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.
|
||||
|
||||
<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.
|
||||
</div>
|
||||
|
||||
@ -146,7 +146,7 @@ class (see [SearchContext](../search/searchcontext) docs for details).
|
||||
|
||||
## 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
|
||||
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
|
||||
|
||||
* [GridField](../forms/fields/gridfield)
|
||||
* [GridField](../forms/field_types/gridfield)
|
||||
* [Permissions](../security/permissions)
|
||||
* [SeachContext](../search/seachcontext)
|
||||
* [SearchContext](../search/searchcontext)
|
||||
|
||||
## API Documentation
|
||||
|
||||
|
@ -66,7 +66,7 @@ Layout manager will automatically apply algorithms to the children of `.cms-cont
|
||||
For detailed discussion on available algorithms refer to
|
||||
[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.
|
||||
|
||||
### Methods
|
||||
@ -115,6 +115,6 @@ The parameters are as follows:
|
||||
|
||||
## Related
|
||||
|
||||
* [Reference: CMS Architecture](../reference/cms-architecture)
|
||||
* [Reference: Preview](../reference/preview)
|
||||
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)
|
||||
* [Reference: CMS Architecture](cms_architecture)
|
||||
* [Reference: Preview](preview)
|
||||
* [Howto: Extend the CMS Interface](how_tos/extend_cms_interface)
|
||||
|
@ -69,7 +69,7 @@ Note how the configuration happens in different entwine namespaces
|
||||
}(jQuery));
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
[CMS Architecture](/reference/cms-architecture) guide.
|
||||
[CMS Architecture](cms_architecture) guide.
|
||||
|
||||
## Enabling preview
|
||||
|
||||
@ -146,7 +146,7 @@ You can find out current size by calling:
|
||||
## Preview modes
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
@ -184,4 +184,4 @@ previewable content is loaded.
|
||||
|
||||
## Related
|
||||
|
||||
* [Reference: Layout](../reference/layout)
|
||||
* [Reference: Layout](cms_layout)
|
||||
|
@ -13,7 +13,7 @@ feel familiar to you. This is just a quick run down to get you started
|
||||
with some special conventions.
|
||||
|
||||
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
|
||||
@ -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`
|
||||
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
|
||||
|
||||
@ -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
|
||||
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
|
||||
|
||||
@ -148,7 +148,7 @@ correctly configured form.
|
||||
[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.
|
||||
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.
|
||||
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()`.
|
||||
@ -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
|
||||
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()]`).
|
||||
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.
|
||||
@ -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
|
||||
`framework/admin/javascript/LeftAndMain.Tree.js`, as well as some
|
||||
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
|
||||
form fields to select one or more entries from those hierarchies
|
||||
@ -538,8 +538,8 @@ through the `PjaxResponseNegotiator` class (see above).
|
||||
|
||||
## Related
|
||||
|
||||
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)
|
||||
* [Howto: Customize the CMS tree](../howto/customize-cms-tree)
|
||||
* [Reference: ModelAdmin](../reference/modeladmin)
|
||||
* [Reference: Layout](../reference/layout)
|
||||
* [Topics: Rich Text Editing](../topics/rich-text-editing)
|
||||
* [Howto: Extend the CMS Interface](how_tos/extend_cms_interface)
|
||||
* [Howto: Customize the CMS tree](how_tos/customize_cms_tree)
|
||||
* [ModelAdmin API](api:ModelAdmin)
|
||||
* [Reference: Layout](cms_layout)
|
||||
* [Rich Text Editing](/developer_guides/forms/field_types/htmleditorfield)
|
||||
|
@ -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
|
||||
* 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 ##
|
||||
|
||||
@ -156,4 +156,4 @@ cases.
|
||||
## Summary ##
|
||||
|
||||
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.
|
||||
|
@ -27,4 +27,4 @@ more complex fields like `GridField`, `UploadField`
|
||||
or `DropdownField` with the chosen.js behaviour applied.
|
||||
|
||||
Note: For more advanced help text we recommend using
|
||||
[Custom form field templates](/topics/forms#custom-form-field-templates);
|
||||
[Custom form field templates](../form_templates);
|
||||
|
@ -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
|
||||
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
|
||||
[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
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@ -85,7 +85,7 @@ button configuration.
|
||||
|
||||
To have the link appear, make sure you add the extension to the `LeftAndMain`
|
||||
class. For more information about configuring extensions see the
|
||||
[DataExtension reference](../reference/dataextension).
|
||||
[extensions reference](../extending/extensions).
|
||||
|
||||
:::php
|
||||
LeftAndMain::add_extension('CustomLeftAndMain')
|
||||
@ -93,4 +93,4 @@ class. For more information about configuring extensions see the
|
||||
|
||||
## Related
|
||||
|
||||
* [How to extend the CMS interface](extend-cms-interface)
|
||||
* [How to extend the CMS interface](extend_cms_interface)
|
||||
|
@ -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
|
||||
LeftAndMain:
|
||||
|
@ -55,7 +55,7 @@ with the CMS interface. Paste the following content into a new file called
|
||||
.bookmarked-link.first {margin-top: 1em;}
|
||||
|
||||
Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requirements_css`
|
||||
[configuration value](/topics/configuration).
|
||||
[configuration value](../../configuration).
|
||||
|
||||
:::yml
|
||||
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
|
||||
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
|
||||
LeftAndMain:
|
||||
@ -191,11 +191,11 @@ Empty tabs will be automatically removed from the `FieldList` to prevent clutter
|
||||
</div>
|
||||
|
||||
New actions will need associated controller handlers to work. You can use a
|
||||
`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller)
|
||||
`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../../controllers)
|
||||
for instructions on setting up handlers.
|
||||
|
||||
To make the actions more user-friendly you can also use alternating buttons as
|
||||
detailed in the [CMS Alternating Button](../reference/cms-alternating-button)
|
||||
detailed in the [CMS Alternating Button](cms_alternating_button)
|
||||
how-to.
|
||||
|
||||
## Summary
|
||||
@ -207,7 +207,7 @@ blocks and concepts for more complex extensions as well.
|
||||
|
||||
## Related
|
||||
|
||||
* [Reference: CMS Architecture](../reference/cms-architecture)
|
||||
* [Reference: Layout](../reference/layout)
|
||||
* [Topics: Rich Text Editing](../topics/rich-text-editing)
|
||||
* [CMS Alternating Button](../howto/cms-alternating-button)
|
||||
* [Reference: CMS Architecture](../cms_architecture)
|
||||
* [Reference: Layout](../cms_layout)
|
||||
* [Rich Text Editing](/developer_guides/forms/field_types/htmleditorfield)
|
||||
* [CMS Alternating Button](cms_alternating_button)
|
||||
|
@ -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,
|
||||
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.
|
||||
|
||||
## Config Manifest
|
||||
|
||||
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.
|
||||
The chapter on [configuration](/topics/configuration) has more details.
|
||||
The chapter on [configuration](../configuration) has more details.
|
||||
|
||||
## Flushing
|
||||
|
||||
|
@ -103,14 +103,14 @@ before handing control off to SilverStripe's own `main.php`.
|
||||
|
||||
## 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)).
|
||||
|
||||
* Creates a `[api:SS_HTTPRequest]` object containing all request and environment information
|
||||
* 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
|
||||
[RequestFilter](../controller/request_filters)
|
||||
[RequestFilter](../controllers/requestfilters)
|
||||
to pre-process the request object (see below)
|
||||
* The `Controller` executes the actual business logic and populates an `[api:SS_HTTPResponse]`
|
||||
* 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
|
||||
to transform or filter request data, instanciate helpers, execute global logic,
|
||||
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
|
||||
|
||||
|
@ -35,14 +35,14 @@ Thanks to Rutger de Jong for reporting.
|
||||
Severity: Moderate
|
||||
|
||||
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
|
||||
|
||||
Severity: Moderate
|
||||
|
||||
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.
|
||||
Thanks to Nathaniel Carew (Sense of Security) for reporting.
|
||||
|
||||
|
71
docs/en/04_Changelogs/3.1.11.md
Normal 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)
|
43
docs/en/04_Changelogs/3.1.12.md
Normal 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->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)
|
@ -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-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-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
|
||||
|
||||
he alpha to be preserved when using the modifier methods. (jmwohl)
|
||||
* 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)
|
||||
* 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 [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)
|
||||
|
@ -22,7 +22,7 @@ Thanks to Rutger de Jong for reporting.
|
||||
Severity: Moderate
|
||||
|
||||
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.
|
||||
Thanks to Nathaniel Carew (Sense of Security) for reporting.
|
||||
|
||||
|
@ -25,7 +25,7 @@ API changes related to the below security patch:
|
||||
Severity: Moderate
|
||||
|
||||
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
|
||||
|
||||
|
28
docs/en/04_Changelogs/rc/3.1.11-rc1.md
Normal 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)
|
@ -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,
|
||||
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">
|
||||
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._
|
||||
|
||||
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
|
||||
|
||||
@ -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
|
||||
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](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.
|
||||
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.
|
||||
|
||||
@ -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.
|
||||
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,
|
||||
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
|
||||
|
||||
@ -96,11 +132,11 @@ After you have edited the file, GitHub will offer to create a pull request for y
|
||||
|
||||
## 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)
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
@ -110,7 +146,7 @@ changes
|
||||
[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.
|
||||
* 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
|
||||
|
||||
|
@ -9,7 +9,7 @@ only have time for a partial translation or quick review work - our system accom
|
||||
same language.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
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
|
||||
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?
|
||||
|
||||
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?
|
||||
|
||||
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
@ -140,7 +140,7 @@ board if you have specific comments on a translation.
|
||||
|
||||
## Related
|
||||
|
||||
* [i18n](/developer_guids/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.
|
||||
* [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.
|
||||
* [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
|
||||
|
@ -4,14 +4,14 @@ summary: Implement SilverStripe's internationalization system in your own module
|
||||
# Implementing Internationalization
|
||||
|
||||
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
|
||||
|
||||
### Collecting translatable text
|
||||
|
||||
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
|
||||
|
||||
@ -127,7 +127,7 @@ files back into the JS files SilverStripe can actually read. This requires an in
|
||||
|
||||
# Related
|
||||
|
||||
* [i18n](/topics/i18n): Developer-level documentation of Silverstripe's i18n capabilities
|
||||
* [contributing/translation](contributing/translation): Information for translators looking to contribute translations of the SilverStripe UI.
|
||||
* [i18n](/developer_guides/i18n/): Developer-level documentation of Silverstripe's i18n capabilities
|
||||
* [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 ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin
|
@ -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)
|
||||
* 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.
|
||||
* 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))
|
||||
* 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))
|
||||
|
@ -830,7 +830,7 @@ class File extends DataObject {
|
||||
'js' => _t('File.JsType', 'Javascript file'),
|
||||
'css' => _t('File.CssType', 'CSS file'),
|
||||
'html' => _t('File.HtmlType', 'HTML file'),
|
||||
'htm' => _t('File.HtlType', 'HTML file')
|
||||
'htm' => _t('File.HtmlType', 'HTML file')
|
||||
);
|
||||
|
||||
$ext = $this->getExtension();
|
||||
|
@ -43,14 +43,14 @@ class ImagickBackend extends Imagick implements Image_Backend {
|
||||
/**
|
||||
* 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
|
||||
* @return void
|
||||
*/
|
||||
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) {
|
||||
config::inst()->update('IMagickBackend', 'default_quality', (int) $quality);
|
||||
Config::inst()->update('ImagickBackend', 'default_quality', (int) $quality);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,6 +305,16 @@ class Upload extends Controller {
|
||||
*/
|
||||
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
|
||||
* by the PHP-runtime.
|
||||
@ -360,22 +370,46 @@ class Upload_Validator {
|
||||
* @return int Filesize in bytes
|
||||
*/
|
||||
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);
|
||||
if(isset($ext) && isset($this->allowedMaxFileSize[$ext])) {
|
||||
if ($ext) {
|
||||
if (isset($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 {
|
||||
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
|
||||
* for easier matching.
|
||||
*
|
||||
* Example:
|
||||
* <code>
|
||||
* array('*' => 200, 'jpg' => 1000)
|
||||
* array('*' => 200, 'jpg' => 1000, '[doc]' => '5m')
|
||||
* </code>
|
||||
*
|
||||
* @param array|int $rules
|
||||
@ -384,7 +418,22 @@ class Upload_Validator {
|
||||
if(is_array($rules) && count($rules)) {
|
||||
// make sure all extensions are lowercase
|
||||
$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) {
|
||||
$this->allowedMaxFileSize['*'] = (int)$rules;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class FileField extends FormField {
|
||||
|
||||
if($this->relationAutoSetting) {
|
||||
// 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
|
||||
$file = (is_string($hasOnes)) ? Object::create($hasOnes) : new $fileClass();
|
||||
} else if($record instanceof File) {
|
||||
|
@ -93,8 +93,8 @@ class FormScaffolder extends Object {
|
||||
}
|
||||
|
||||
// add has_one relation fields
|
||||
if($this->obj->has_one()) {
|
||||
foreach($this->obj->has_one() as $relationship => $component) {
|
||||
if($this->obj->hasOne()) {
|
||||
foreach($this->obj->hasOne() as $relationship => $component) {
|
||||
if($this->restrictFields && !in_array($relationship, $this->restrictFields)) continue;
|
||||
$fieldName = $component === 'DataObject'
|
||||
? $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
|
||||
if($this->obj->ID) {
|
||||
// add has_many relation fields
|
||||
if($this->obj->has_many()
|
||||
if($this->obj->hasMany()
|
||||
&& ($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) {
|
||||
$relationTab = $fields->findOrMakeTab(
|
||||
"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']))) {
|
||||
|
||||
foreach($this->obj->many_many() as $relationship => $component) {
|
||||
foreach($this->obj->manyMany() as $relationship => $component) {
|
||||
if($this->tabbed) {
|
||||
$relationTab = $fields->findOrMakeTab(
|
||||
"Root.$relationship",
|
||||
|
@ -88,5 +88,12 @@ class GroupedDropdownField extends DropdownField {
|
||||
return 'groupeddropdown dropdown';
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Implement DropdownField::validate() with group validation support
|
||||
*/
|
||||
public function validate(Validator $validator) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -453,7 +453,6 @@ class TreeDropdownField extends FormField {
|
||||
$this->labelField
|
||||
));
|
||||
}
|
||||
|
||||
$res = DataObject::get($this->sourceObject)->filterAny($filters);
|
||||
}
|
||||
|
||||
@ -463,18 +462,18 @@ class TreeDropdownField extends FormField {
|
||||
if ($row->ParentID) $parents[$row->ParentID] = true;
|
||||
$this->searchIds[$row->ID] = true;
|
||||
}
|
||||
|
||||
$sourceObject = $this->sourceObject;
|
||||
|
||||
while (!empty($parents)) {
|
||||
$idsClause = DB::placeholders($parents);
|
||||
$res = DB::prepared_query(
|
||||
"SELECT \"ParentID\", \"ID\" FROM \"{$this->sourceObject}\" WHERE \"ID\" in ($idsClause)",
|
||||
array_keys($parents)
|
||||
);
|
||||
$items = $sourceObject::get()
|
||||
->filter("ID",array_keys($parents));
|
||||
$parents = array();
|
||||
|
||||
foreach($res as $row) {
|
||||
if ($row['ParentID']) $parents[$row['ParentID']] = true;
|
||||
$this->searchIds[$row['ID']] = true;
|
||||
$this->searchExpanded[$row['ID']] = true;
|
||||
foreach($items as $item) {
|
||||
if ($item->ParentID) $parents[$item->ParentID] = true;
|
||||
$this->searchIds[$item->ID] = true;
|
||||
$this->searchExpanded[$item->ID] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -502,7 +502,7 @@ class UploadField extends FileField {
|
||||
if($relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
|
||||
// has_many or many_many
|
||||
$relation->setByIDList($idList);
|
||||
} elseif($record->has_one($fieldname)) {
|
||||
} elseif($record->hasOneComponent($fieldname)) {
|
||||
// has_one
|
||||
$record->{"{$fieldname}ID"} = $idList ? reset($idList) : 0;
|
||||
}
|
||||
@ -590,7 +590,7 @@ class UploadField extends FileField {
|
||||
if(empty($allowedMaxFileNumber)) {
|
||||
$record = $this->getRecord();
|
||||
$name = $this->getName();
|
||||
if($record && $record->has_one($name)) {
|
||||
if($record && $record->hasOneComponent($name)) {
|
||||
return 1; // Default for has_one
|
||||
} else {
|
||||
return null; // Default for has_many and many_many
|
||||
|
@ -841,7 +841,8 @@ class GridField_FormAction extends FormAction {
|
||||
'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);
|
||||
$actionData['StateID'] = $id;
|
||||
|
||||
|
@ -95,7 +95,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
|
||||
|
||||
// if no validator has been set on the GridField and the record has a
|
||||
// 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());
|
||||
}
|
||||
|
||||
@ -188,6 +188,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
|
||||
*/
|
||||
public function setItemEditFormCallback(Closure $cb) {
|
||||
$this->itemEditFormCallback = $cb;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,8 +155,10 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
|
||||
$fileData .= "\n";
|
||||
}
|
||||
|
||||
if ($item->hasMethod('destroy')) {
|
||||
$item->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return $fileData;
|
||||
}
|
||||
|
@ -137,6 +137,12 @@
|
||||
onclick: function(e){
|
||||
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'))){
|
||||
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
|
||||
$('.ss-gridfield .col-buttons .action.gridfield-button-delete, .cms-edit-form .Actions button.action.action-delete').entwine({
|
||||
onclick: function(e){
|
||||
|
41
javascript/lang/src/sv.js
Normal 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
@ -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"
|
||||
});
|
||||
}
|
12
lang/lt.yml
@ -235,7 +235,7 @@ lt:
|
||||
SINGULARNAME: Grupė
|
||||
Sort: 'Rūšiavimo tvarka'
|
||||
has_many_Permissions: Leidimai
|
||||
many_many_Members: Nariai
|
||||
many_many_Members: Vartotojai
|
||||
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>'
|
||||
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'
|
||||
NoPassword: 'Šis vartotojas neturi slaptažodžio.'
|
||||
PASSWORD: Slaptažodis
|
||||
PLURALNAME: Nariai
|
||||
PLURALNAME: Vartotojai
|
||||
REMEMBERME: 'Prisiminti jungiantis kitą kartą?'
|
||||
SINGULARNAME: Narys
|
||||
SINGULARNAME: Vartotojas
|
||||
SUBJECTPASSWORDCHANGED: 'Jūsų slaptažodis pakeistas'
|
||||
SUBJECTPASSWORDRESET: 'Slaptažodžio atstatymo nuoroda'
|
||||
SURNAME: Pavardė
|
||||
TIMEFORMAT: 'Laiko formatas'
|
||||
VALIDATIONMEMBEREXISTS: 'Tokį e. paštą jau naudoja kitas narys.'
|
||||
ValidationIdentifierFailed: 'Nepavyko perrašyti nario #{id} su tuo pačiu atpažinimo kodu ({name} = {value}))'
|
||||
VALIDATIONMEMBEREXISTS: 'Vartotojas šiuo el. pašto adresu %s jau egzistuoja.'
|
||||
ValidationIdentifierFailed: 'Nepavyko atnaujinti vartotojo #{id} duomenų su atpažinimo kodu ({name} = {value})'
|
||||
WELCOMEBACK: 'Sveiki, {firstname}'
|
||||
YOUROLDPASSWORD: 'Jūsų senas slaptažodis'
|
||||
belongs_many_many_Groups: Grupės
|
||||
@ -488,7 +488,7 @@ lt:
|
||||
GROUPNAME: 'Grupės pavadinimas'
|
||||
IMPORTGROUPS: 'Importuoti grupes'
|
||||
IMPORTUSERS: 'Importuoti vartotojus'
|
||||
MEMBERS: Nariai
|
||||
MEMBERS: Vartotojai
|
||||
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.'
|
||||
NEWGROUP: 'Nauja grupė'
|
||||
|
77
lang/sv.yml
@ -1,6 +1,7 @@
|
||||
sv:
|
||||
AssetAdmin:
|
||||
NEWFOLDER: Ny mapp
|
||||
SHOWALLOWEDEXTS: 'Visa tillåtna filtyper'
|
||||
AssetTableField:
|
||||
CREATED: 'Först uppladdad'
|
||||
DIM: Dimensioner
|
||||
@ -67,6 +68,8 @@ sv:
|
||||
ACCESSALLINTERFACES: 'Tillgång till alla CMS-sektioner'
|
||||
ACCESSALLINTERFACESHELP: 'Ersätter mer specifika behörighetsinställningar.'
|
||||
SAVE: Spara
|
||||
CMSPageHistoryController_versions_ss:
|
||||
PREVIEW: 'Förhandsgranska sida'
|
||||
CMSProfileController:
|
||||
MENUTITLE: 'Min Profil'
|
||||
ChangePasswordEmail_ss:
|
||||
@ -87,6 +90,8 @@ sv:
|
||||
FOURTH: fjärde
|
||||
SECOND: andra
|
||||
THIRD: tredje
|
||||
CurrencyField:
|
||||
CURRENCYSYMBOL: $
|
||||
DataObject:
|
||||
PLURALNAME: 'Dataobjekt'
|
||||
SINGULARNAME: 'Dataobjekt'
|
||||
@ -96,9 +101,12 @@ sv:
|
||||
HOUR: timme
|
||||
HOURS: timmar
|
||||
LessThanMinuteAgo: 'mindre än en minut'
|
||||
MIN: minut
|
||||
MINS: minuter
|
||||
MONTH: månad
|
||||
MONTHS: månader
|
||||
SEC: sek
|
||||
SEC: sekund
|
||||
SECS: sekunder
|
||||
TIMEDIFFAGO: '{difference} sen'
|
||||
TIMEDIFFIN: 'om {difference}'
|
||||
YEAR: år
|
||||
@ -121,37 +129,39 @@ sv:
|
||||
Enum:
|
||||
ANY: Vilken som helst
|
||||
File:
|
||||
AviType: 'AVI videofil'
|
||||
AviType: 'AVI-videofil'
|
||||
Content: Innehåll
|
||||
CssType: 'CSS fil'
|
||||
DmgType: 'Apple skivavbild'
|
||||
DocType: 'Word dokument'
|
||||
CssType: 'CSS-fil'
|
||||
DmgType: 'Apple-skivavbild'
|
||||
DocType: 'Word-dokument'
|
||||
Filename: Filnamn
|
||||
GifType: 'GIF bild - bra för diagram'
|
||||
GzType: 'GZIP packad fil'
|
||||
HtlType: 'HTML fil'
|
||||
HtmlType: 'HTML fil'
|
||||
GifType: 'GIF-bild - bra för diagram'
|
||||
GzType: 'GZIP-packad fil'
|
||||
HtlType: 'HTML-fil'
|
||||
HtmlType: 'HTML-fil'
|
||||
INVALIDEXTENSION: 'Filändelsen tillåts inte (tillåtna: {extensions})'
|
||||
INVALIDEXTENSIONSHORT: 'Filändelsen tillåts inte'
|
||||
IcoType: 'Icon bild'
|
||||
JpgType: 'JPEG bild - bra för fotografier'
|
||||
JsType: 'Javascript fil'
|
||||
Mp3Type: 'MP3 ljudfil'
|
||||
MpgType: 'MPEG videofil'
|
||||
IcoType: 'Ikonbild'
|
||||
JpgType: 'JPEG-bild - bra för fotografier'
|
||||
JsType: 'Javascript-fil'
|
||||
Mp3Type: 'MP3-ljudfil'
|
||||
MpgType: 'MPEG-videofil'
|
||||
NOFILESIZE: 'Filstorleken är noll bytes'
|
||||
NOVALIDUPLOAD: 'Filen är inte giltig för uppladdning'
|
||||
Name: Namn
|
||||
PLURALNAME: Filer
|
||||
PdfType: 'Adobe Acrobat PDF fil'
|
||||
PngType: 'PNG bild - bra allmänt format'
|
||||
PdfType: 'Adobe Acrobat PDF-fil'
|
||||
PngType: 'PNG-bild - bra allmänt format'
|
||||
SINGULARNAME: Fil
|
||||
TOOLARGE: 'Filen är för stor, max {size} tillåts'
|
||||
TOOLARGESHORT: 'Filstorlek överskriden {size}'
|
||||
TiffType: 'Tiff bildformat'
|
||||
Title: Titel
|
||||
WavType: 'WAV ljudfil'
|
||||
WavType: 'WAV-ljudfil'
|
||||
XlsType: 'Excel kalkylblad'
|
||||
ZipType: 'ZIP packad fil'
|
||||
ZipType: 'ZIP-packad fil'
|
||||
Filesystem:
|
||||
SYNCRESULTS: 'Synkning komplett: {createdcount} artiklar skapades, {deletedcount} artiklar raderades'
|
||||
Folder:
|
||||
PLURALNAME: Mappar
|
||||
SINGULARNAME: Mapp
|
||||
@ -161,14 +171,19 @@ sv:
|
||||
TEXT2: 'Återställningslänk för lösenord'
|
||||
TEXT3: för
|
||||
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
|
||||
VALIDATIONCREDITNUMBER: 'Kontrollera att du angav kortnummret {number} rätt'
|
||||
VALIDATIONNOTUNIQUE: 'Det angivna värdet är inte unikt'
|
||||
VALIDATIONPASSWORDSDONTMATCH: 'Lösenorden stämmer inte överrens '
|
||||
VALIDATIONPASSWORDSNOTEMPTY: 'Lösenordfältet får inte vara tomt'
|
||||
VALIDATIONSTRONGPASSWORD: 'Lösenord måste innehålla minst en siffra och en bokstav.'
|
||||
VALIDATOR: Validator
|
||||
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:
|
||||
Example: 't.ex. %s'
|
||||
NONE: ingen
|
||||
GridAction:
|
||||
DELETE_DESCRIPTION: Radera
|
||||
@ -191,6 +206,7 @@ sv:
|
||||
ResetFilter: Rensa
|
||||
GridFieldAction_Delete:
|
||||
DeletePermissionsFailure: 'Rättighet för att radera saknas'
|
||||
EditPermissionsFailure: 'Rättigheter för avlänkning saknas'
|
||||
GridFieldDetailForm:
|
||||
CancelBtn: Avbryt
|
||||
Create: Skapa
|
||||
@ -198,6 +214,7 @@ sv:
|
||||
DeletePermissionsFailure: 'Rättighet för att radera saknas'
|
||||
Deleted: 'Raderade %s %s'
|
||||
Save: Spara
|
||||
Saved: 'Sparade {name} {link}'
|
||||
GridFieldEditButton_ss:
|
||||
EDIT: Redigera
|
||||
GridFieldItemEditView:
|
||||
@ -209,9 +226,11 @@ sv:
|
||||
DefaultGroupTitleContentAuthors: 'Författare'
|
||||
Description: Beskrivning
|
||||
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?'
|
||||
NoRoles: 'Inga roller fun'
|
||||
PLURALNAME: Grupper
|
||||
Parent: 'Överordnad grupp'
|
||||
RolesAddEditLink: 'Hantera roller'
|
||||
SINGULARNAME: Grupp
|
||||
Sort: 'Sorteringsordning'
|
||||
@ -250,6 +269,7 @@ sv:
|
||||
FindInFolder: 'Hitta i mapp'
|
||||
IMAGEALT: 'Alternativ text (alt)'
|
||||
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
|
||||
IMAGEHEIGHTPX: Höjd
|
||||
IMAGETITLE: 'Titel text (tooltip) - för ytterligare information om bilden'
|
||||
@ -319,8 +339,10 @@ sv:
|
||||
EMAIL: E-post
|
||||
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.'
|
||||
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'
|
||||
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'
|
||||
INTERFACELANG: 'Gränssnittsspråk'
|
||||
INVALIDNEWPASSWORD: 'Vi kunde inte godkänna det lösenordet: {password}'
|
||||
@ -412,6 +434,10 @@ sv:
|
||||
Pagination:
|
||||
Page: Sida
|
||||
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:
|
||||
AdminGroup: Administratör
|
||||
CMS_ACCESS_CATEGORY: 'CMS-åtkomst'
|
||||
@ -431,6 +457,7 @@ sv:
|
||||
Title: Rollnamn
|
||||
PermissionRoleCode:
|
||||
PLURALNAME: 'Koder för rollrättigheter'
|
||||
PermsError: 'Koden "%s" kan inte ges privilegierad tillgång (adminrättigheter krävs)'
|
||||
SINGULARNAME: 'Kodför rollrättigheter'
|
||||
Permissions:
|
||||
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!'
|
||||
LOGGEDOUT: 'Du har blivit utloggad. Om du vill logga in igen anger du dina uppgifter nedan.'
|
||||
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.'
|
||||
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'
|
||||
@ -474,8 +502,21 @@ sv:
|
||||
FileFieldLabel: 'CSV-fil <small>(Tillåtna filtyper: *.csv)</small>'
|
||||
SilverStripeNavigator:
|
||||
Auto: Auto
|
||||
ChangeViewMode: 'Ändra visningsläge'
|
||||
Desktop: Stationär
|
||||
DualWindowView: 'Delat fönster'
|
||||
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
|
||||
SiteTree:
|
||||
TABMAIN: Allmän
|
||||
TableListField:
|
||||
CSVEXPORT: 'Exportera till CSV'
|
||||
Print: Skriv ut
|
||||
|
@ -78,7 +78,7 @@ class DataDifferencer extends ViewableData {
|
||||
$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
|
||||
foreach($fields as $field) {
|
||||
|
@ -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
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
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
|
||||
$this->duplicateRelations($sourceObject, $destinationObject, $name);
|
||||
}
|
||||
@ -666,24 +666,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if($this->class == 'DataObject') return;
|
||||
|
||||
// Set up accessors for joined items
|
||||
if($manyMany = $this->many_many()) {
|
||||
if($manyMany = $this->manyMany()) {
|
||||
foreach($manyMany as $relationship => $class) {
|
||||
$this->addWrapperMethod($relationship, 'getManyManyComponents');
|
||||
}
|
||||
}
|
||||
if($hasMany = $this->has_many()) {
|
||||
if($hasMany = $this->hasMany()) {
|
||||
|
||||
foreach($hasMany as $relationship => $class) {
|
||||
$this->addWrapperMethod($relationship, 'getComponents');
|
||||
}
|
||||
|
||||
}
|
||||
if($hasOne = $this->has_one()) {
|
||||
if($hasOne = $this->hasOne()) {
|
||||
foreach($hasOne as $relationship => $class) {
|
||||
$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');
|
||||
}
|
||||
}
|
||||
@ -972,7 +972,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// merge relations
|
||||
if($includeRelations) {
|
||||
if($manyMany = $this->many_many()) {
|
||||
if($manyMany = $this->manyMany()) {
|
||||
foreach($manyMany as $relationship => $class) {
|
||||
$leftComponents = $leftObj->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) {
|
||||
$leftComponents = $leftObj->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) {
|
||||
$leftComponent = $leftObj->getComponent($relationship);
|
||||
$rightComponent = $rightObj->getComponent($relationship);
|
||||
@ -1130,7 +1130,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->$fieldName = $fieldValue;
|
||||
}
|
||||
// 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->setByIdList($fieldValue);
|
||||
}
|
||||
@ -1497,7 +1497,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $this->components[$componentName];
|
||||
}
|
||||
|
||||
if($class = $this->has_one($componentName)) {
|
||||
if($class = $this->hasOneComponent($componentName)) {
|
||||
$joinField = $componentName . 'ID';
|
||||
$joinID = $this->getField($joinField);
|
||||
|
||||
@ -1514,7 +1514,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(empty($component)) {
|
||||
$component = $this->model->$class->newObject();
|
||||
}
|
||||
} elseif($class = $this->belongs_to($componentName)) {
|
||||
} elseif($class = $this->belongsToComponent($componentName)) {
|
||||
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to', $polymorphic);
|
||||
$joinID = $this->ID;
|
||||
@ -1553,18 +1553,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Returns a one-to-many relation as a HasManyList
|
||||
*
|
||||
* @param string $componentName Name of the component
|
||||
* @param string $filter 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
|
||||
* field $default_sort on the component class will be used.
|
||||
* @param string|null $filter Deprecated. A filter to be inserted into the WHERE clause
|
||||
* @param string|null|array $sort Deprecated. A sort expression to be inserted into the ORDER BY clause. If omitted,
|
||||
* the static field $default_sort on the component class will be used.
|
||||
* @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.
|
||||
*/
|
||||
public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null) {
|
||||
public function getComponents($componentName, $filter = null, $sort = null, $join = null, $limit = null) {
|
||||
$result = null;
|
||||
|
||||
if(!$componentClass = $this->has_many($componentName)) {
|
||||
if(!$componentClass = $this->hasManyComponent($componentName)) {
|
||||
user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName'"
|
||||
. " 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(!$this->ID) {
|
||||
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) {
|
||||
// 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)) {
|
||||
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
|
||||
*/
|
||||
public function getManyManyComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
|
||||
public function getManyManyComponents($componentName, $filter = null, $sort = null, $join = null, $limit = null) {
|
||||
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(!$this->ID) {
|
||||
@ -1730,7 +1747,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
$result = ManyManyList::create(
|
||||
$componentClass, $table, $componentField, $parentField,
|
||||
$this->many_many_extraFields($componentName)
|
||||
$this->manyManyExtraFieldsForComponent($componentName)
|
||||
);
|
||||
if($this->model) $result->setDataModel($this->model);
|
||||
|
||||
@ -1743,64 +1760,91 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
->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
|
||||
* 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
|
||||
*
|
||||
* @return string|array The class of the one-to-one component, or an array of all one-to-one components and their
|
||||
* classes.
|
||||
* @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
|
||||
* their classes.
|
||||
*/
|
||||
public function has_one($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;
|
||||
|
||||
public function hasOne($component = null) {
|
||||
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 $hasOne[$component];
|
||||
return (array)Config::inst()->get($this->class, 'has_one', Config::INHERITED);
|
||||
}
|
||||
} else {
|
||||
$newItems = (array)Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||
// Validate the data
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$has_one 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);
|
||||
|
||||
/**
|
||||
* Return data for a specific has_one component.
|
||||
* @param string $component
|
||||
* @return string|null
|
||||
*/
|
||||
public function hasOneComponent($component) {
|
||||
$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
|
||||
* 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
|
||||
* the field data stripped off. It defaults to TRUE.
|
||||
* @return string|array
|
||||
*/
|
||||
public function belongs_to($component = null, $classOnly = true) {
|
||||
$belongsTo = $this->config()->belongs_to;
|
||||
|
||||
public function belongsTo($component = null, $classOnly = true) {
|
||||
if($component) {
|
||||
if($belongsTo && array_key_exists($component, $belongsTo)) {
|
||||
$belongsTo = $belongsTo[$component];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
Deprecation::notice(
|
||||
'3.2',
|
||||
'Please use DataObject::belongsToComponent() instead of passing a component name to belongsTo()',
|
||||
Deprecation::SCOPE_GLOBAL
|
||||
);
|
||||
return $this->belongsToComponent($component, $classOnly);
|
||||
}
|
||||
|
||||
$belongsTo = (array)Config::inst()->get($this->class, 'belongs_to', Config::INHERITED);
|
||||
if($belongsTo && $classOnly) {
|
||||
return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
|
||||
} 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.
|
||||
* 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
|
||||
* @return array The database fields
|
||||
@ -1837,15 +1900,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $dbItems[$fieldName];
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -1853,26 +1907,42 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
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
|
||||
* 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
|
||||
* the field data stripped off. It defaults to TRUE.
|
||||
* @return string|array
|
||||
* @return string|array|false
|
||||
*/
|
||||
public function has_many($component = null, $classOnly = true) {
|
||||
$hasMany = $this->config()->has_many;
|
||||
|
||||
public function hasMany($component = null, $classOnly = true) {
|
||||
if($component) {
|
||||
if($hasMany && array_key_exists($component, $hasMany)) {
|
||||
$hasMany = $hasMany[$component];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
Deprecation::notice(
|
||||
'3.2',
|
||||
'Please use DataObject::hasManyComponent() instead of passing a component name to hasMany()',
|
||||
Deprecation::SCOPE_GLOBAL
|
||||
);
|
||||
return $this->hasManyComponent($component, $classOnly);
|
||||
}
|
||||
|
||||
$hasMany = (array)Config::inst()->get($this->class, 'has_many', Config::INHERITED);
|
||||
if($hasMany && $classOnly) {
|
||||
return preg_replace('/(.+)?\..+/', '$1', $hasMany);
|
||||
} 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.
|
||||
*
|
||||
* If you don't specify a component name, it returns all
|
||||
* extra fields for all components available.
|
||||
*
|
||||
* @param string $component Name of component
|
||||
* @return array
|
||||
* @param string $component Deprecated - Name of component
|
||||
* @return array|null
|
||||
*/
|
||||
public function many_many_extraFields($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
|
||||
public function manyManyExtraFields($component = null) {
|
||||
if($component) {
|
||||
$SNG_class = singleton($class);
|
||||
$extraFields = $SNG_class->stat('many_many_extraFields');
|
||||
Deprecation::notice(
|
||||
'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])) {
|
||||
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;
|
||||
if($candidate) {
|
||||
$SNG_candidate = singleton($candidate);
|
||||
$candidateManyMany = $SNG_candidate->stat('belongs_many_many');
|
||||
|
||||
// Find the relation given the class
|
||||
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
|
||||
if($relatedClass == $class) {
|
||||
$relationName = $relation;
|
||||
break;
|
||||
}
|
||||
$relationName = null;
|
||||
// Extract class and relation name from dot-notation
|
||||
if(strpos($candidate, '.') !== false) {
|
||||
list($candidate, $relationName) = explode('.', $candidate, 2);
|
||||
}
|
||||
|
||||
if($relationName) {
|
||||
$extraFields = $SNG_candidate->stat('many_many_extraFields');
|
||||
if(isset($extraFields[$relationName])) {
|
||||
return $extraFields[$relationName];
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we've not already found the relation name from dot notation, we need to find a relation that points
|
||||
// back to this class. As there's no dot-notation, there can only be one relation pointing to this class,
|
||||
// so it's safe to assume that it's the correct one
|
||||
if(!$relationName) {
|
||||
$candidateManyManys = (array)Config::inst()->get($candidate, 'many_many', Config::UNINHERITED);
|
||||
|
||||
$manyMany = $SNG_class->stat('belongs_many_many');
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
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) {
|
||||
foreach($candidateManyManys as $relation => $relatedClass) {
|
||||
if($relatedClass === $this->class) {
|
||||
$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])) {
|
||||
return $extraFields[$relationName];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// 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) ? $items : null;
|
||||
}
|
||||
|
||||
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
|
||||
* components are returned.
|
||||
*
|
||||
* @param string $component Name of component
|
||||
*
|
||||
* @return array An array of (parentclass, childclass), or an array of all many-many components
|
||||
* @see DataObject::manyManyComponent()
|
||||
* @param string $component Deprecated - Name of component
|
||||
* @return array|null An array of (parentclass, childclass), or an array of all many-many components
|
||||
*/
|
||||
public function many_many($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;
|
||||
|
||||
public function manyMany($component = null) {
|
||||
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);
|
||||
// Try many_many
|
||||
// Check if the component is defined in many_many on this class
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$parentField = $class . "ID";
|
||||
@ -1994,55 +2119,48 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
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);
|
||||
$candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null;
|
||||
if($candidate) {
|
||||
// Extract class and relation name from dot-notation
|
||||
if(strpos($candidate, '.') !== false) {
|
||||
list($candidate, $relationName) = explode('.', $candidate, 2);
|
||||
}
|
||||
|
||||
$childField = $candidate . "ID";
|
||||
|
||||
// We need to find the inverse component name
|
||||
$otherManyMany = Config::inst()->get($candidate, 'many_many', Config::UNINHERITED);
|
||||
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) {
|
||||
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";
|
||||
|
||||
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);
|
||||
}
|
||||
return array($class, $candidate, $parentField, $childField, $joinTable);
|
||||
}
|
||||
|
||||
$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 (
|
||||
array_key_exists($field, $this->record)
|
||||
|| $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}")
|
||||
);
|
||||
}
|
||||
@ -2623,7 +2741,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
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(!($p = $this->Parent)) {
|
||||
return false;
|
||||
@ -2807,12 +2925,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $obj;
|
||||
|
||||
// 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;
|
||||
return DBField::create_field('ForeignKey', $val, $fieldName, $this);
|
||||
|
||||
// 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();
|
||||
return DBField::create_field('PolymorphicForeignKey', $val, $fieldName, $this);
|
||||
|
||||
@ -2910,16 +3028,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return String
|
||||
*/
|
||||
public function getReverseAssociation($className) {
|
||||
if (is_array($this->many_many())) {
|
||||
$many_many = array_flip($this->many_many());
|
||||
if (is_array($this->manyMany())) {
|
||||
$many_many = array_flip($this->manyMany());
|
||||
if (array_key_exists($className, $many_many)) return $many_many[$className];
|
||||
}
|
||||
if (is_array($this->has_many())) {
|
||||
$has_many = array_flip($this->has_many());
|
||||
if (is_array($this->hasMany())) {
|
||||
$has_many = array_flip($this->hasMany());
|
||||
if (array_key_exists($className, $has_many)) return $has_many[$className];
|
||||
}
|
||||
if (is_array($this->has_one())) {
|
||||
$has_one = array_flip($this->has_one());
|
||||
if (is_array($this->hasOne())) {
|
||||
$has_one = array_flip($this->hasOne());
|
||||
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();
|
||||
|
||||
// Validate relationship configuration
|
||||
$this->validateModelDefinitions();
|
||||
|
||||
if($fields) {
|
||||
$hasAutoIncPK = ($this->class == ClassInfo::baseDataClass($this->class));
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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) {
|
||||
// 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();
|
||||
} else {
|
||||
return parent::hasValue($field, $arguments, $cache);
|
||||
|
@ -645,10 +645,9 @@ class DataQuery {
|
||||
|
||||
foreach($relation as $rel) {
|
||||
$model = singleton($modelClass);
|
||||
if ($component = $model->has_one($rel)) {
|
||||
if ($component = $model->hasOneComponent($rel)) {
|
||||
if(!$this->query->isJoinedTo($component)) {
|
||||
$has_one = array_flip($model->has_one());
|
||||
$foreignKey = $has_one[$component];
|
||||
$foreignKey = $rel;
|
||||
$realModelClass = ClassInfo::table_for_object_field($modelClass, "{$foreignKey}ID");
|
||||
$this->query->addLeftJoin($component,
|
||||
"\"$component\".\"ID\" = \"{$realModelClass}\".\"{$foreignKey}ID\"");
|
||||
@ -669,7 +668,7 @@ class DataQuery {
|
||||
}
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->has_many($rel)) {
|
||||
} elseif ($component = $model->hasManyComponent($rel)) {
|
||||
if(!$this->query->isJoinedTo($component)) {
|
||||
$ancestry = $model->getClassAncestry();
|
||||
$foreignKey = $model->getRemoteJoinField($rel);
|
||||
@ -691,7 +690,7 @@ class DataQuery {
|
||||
}
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->many_many($rel)) {
|
||||
} elseif ($component = $model->manyManyComponent($rel)) {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
|
||||
$parentBaseClass = ClassInfo::baseDataClass($parentClass);
|
||||
$componentBaseClass = ClassInfo::baseDataClass($componentClass);
|
||||
|
@ -28,7 +28,7 @@ class ForeignKey extends Int {
|
||||
|
||||
public function scaffoldFormField($title = null, $params = null) {
|
||||
$relationName = substr($this->name,0,-2);
|
||||
$hasOneClass = $this->object->has_one($relationName);
|
||||
$hasOneClass = $this->object->hasOneComponent($relationName);
|
||||
|
||||
if($hasOneClass && singleton($hasOneClass) instanceof Image) {
|
||||
$field = new UploadField($relationName, $title);
|
||||
|
@ -17,6 +17,7 @@ abstract class StringField extends DBField {
|
||||
*/
|
||||
private static $casting = array(
|
||||
"LimitCharacters" => "Text",
|
||||
"LimitCharactersToClosestWord" => "Text",
|
||||
'LimitWordCount' => 'Text',
|
||||
'LimitWordCountXML' => 'HTMLText',
|
||||
"LowerCase" => "Text",
|
||||
@ -116,7 +117,6 @@ abstract class StringField extends DBField {
|
||||
*/
|
||||
public function LimitCharacters($limit = 20, $add = '...') {
|
||||
$value = trim($this->value);
|
||||
|
||||
if($this->stat('escape_type') == 'xml') {
|
||||
$value = strip_tags($value);
|
||||
$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
|
||||
@ -126,10 +126,39 @@ abstract class StringField extends DBField {
|
||||
} else {
|
||||
$value = (mb_strlen($value) > $limit) ? mb_substr($value, 0, $limit) . $add : $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.
|
||||
|
@ -271,8 +271,13 @@ abstract class SQLConditionalExpression extends SQLExpression {
|
||||
$filter = "(" . implode(") AND (", $join['filter']) . ")";
|
||||
}
|
||||
|
||||
$table = strpos(strtoupper($join['table']), 'SELECT') ? $join['table'] : "\"" . $join['table'] . "\"";
|
||||
$aliasClause = ($alias != $join['table']) ? " AS \"$alias\"" : "";
|
||||
// Ensure tables are quoted, unless the table is actually a sub-select
|
||||
$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";
|
||||
if(!empty($join['parameters'])) {
|
||||
$parameters = array_merge($parameters, $join['parameters']);
|
||||
|
@ -580,6 +580,7 @@ $gf_grid_x: 16px;
|
||||
|
||||
span.non-sortable {
|
||||
display:block;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,10 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.middleColumn {
|
||||
// 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
|
||||
|
@ -228,7 +228,7 @@ abstract class SearchFilter extends Object {
|
||||
* @return DataQuery
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,9 +50,19 @@ class BasicAuth {
|
||||
$isRunningTests = (class_exists('SapphireTest', false) && SapphireTest::is_running_test());
|
||||
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();
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) &&
|
||||
preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
|
||||
if ($authHeader &&
|
||||
preg_match('/Basic\s+(.*)$/i', $authHeader, $matches)) {
|
||||
list($name, $password) = explode(':', base64_decode($matches[1]));
|
||||
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
|
||||
$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
|
||||
|
@ -120,6 +120,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
'Salt',
|
||||
'NumVisit'
|
||||
);
|
||||
|
||||
/**
|
||||
* @config
|
||||
* @var Array See {@link set_title_columns()}
|
||||
@ -513,7 +514,13 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
// Don't bother trying this multiple times
|
||||
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);
|
||||
|
||||
$member = DataObject::get_by_id("Member", $uid);
|
||||
@ -539,13 +546,14 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$hash = $member->encryptWithUserSettings($token);
|
||||
$member->RememberLoginToken = $hash;
|
||||
Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true);
|
||||
|
||||
$member->NumVisit++;
|
||||
$member->write();
|
||||
|
||||
// Audit logging hook
|
||||
$member->extend('memberAutoLoggedIn');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs this member out.
|
||||
@ -1442,8 +1450,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
if(!($member && $member->exists())) return false;
|
||||
|
||||
// If the requesting member is not an admin, but has access to manage members,
|
||||
// he still can't edit other members with ADMIN permission.
|
||||
// This is a bit weak, strictly speaking he shouldn't be allowed to
|
||||
// they still can't edit other members with ADMIN permission.
|
||||
// This is a bit weak, strictly speaking they shouldn't be allowed to
|
||||
// perform any action that could change the password on a member
|
||||
// with "higher" permissions than himself, but thats hard to determine.
|
||||
if(!Permission::checkMember($member, 'ADMIN') && Permission::checkMember($this, 'ADMIN')) return false;
|
||||
|
@ -227,7 +227,7 @@ JS;
|
||||
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();
|
||||
if($member) {
|
||||
$firstname = Convert::raw2xml($member->FirstName);
|
||||
|
@ -269,7 +269,7 @@ class PermissionCheckboxSetField extends FormField {
|
||||
$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
|
||||
|
||||
|
@ -97,9 +97,10 @@ class Security extends Controller implements TemplateGlobalProvider {
|
||||
/**
|
||||
* Default message set used in permission failures.
|
||||
*
|
||||
* @config
|
||||
* @var array|string
|
||||
*/
|
||||
private static $default_message_set = '';
|
||||
private static $default_message_set;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* following keys:
|
||||
* - default: The default message
|
||||
* - logInAgain: The message to show
|
||||
* if the user has just
|
||||
* logged out and the
|
||||
* - alreadyLoggedIn: The message to
|
||||
* show if the user
|
||||
* is already logged
|
||||
@ -231,8 +229,8 @@ class Security extends Controller implements TemplateGlobalProvider {
|
||||
} else {
|
||||
// Prepare the messageSet provided
|
||||
if(!$messageSet) {
|
||||
if(self::$default_message_set) {
|
||||
$messageSet = self::$default_message_set;
|
||||
if($configMessageSet = static::config()->get('default_message_set')) {
|
||||
$messageSet = $configMessageSet;
|
||||
} else {
|
||||
$messageSet = array(
|
||||
'default' => _t(
|
||||
@ -246,11 +244,6 @@ class Security extends Controller implements TemplateGlobalProvider {
|
||||
. "can access that page, you can log in again below.",
|
||||
|
||||
"%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 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.
|
||||
$customisedController = $controller->customise(array(
|
||||
"Content" => $message,
|
||||
"Message" => $message,
|
||||
"MessageType" => $messageType,
|
||||
"Form" => $content,
|
||||
@ -579,7 +587,8 @@ class Security extends Controller implements TemplateGlobalProvider {
|
||||
* @return Form Returns the lost password form
|
||||
*/
|
||||
public function LostPasswordForm() {
|
||||
return MemberLoginForm::create( $this,
|
||||
return MemberLoginForm::create(
|
||||
$this,
|
||||
'LostPasswordForm',
|
||||
new FieldList(
|
||||
new EmailField('Email', _t('Member.EMAIL', 'Email'))
|
||||
|
@ -97,6 +97,16 @@ class DirectorTest extends SapphireTest {
|
||||
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
|
||||
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
|
||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL));
|
||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, true));
|
||||
@ -135,8 +145,10 @@ class DirectorTest extends SapphireTest {
|
||||
|
||||
// absolute base URLs - you should end them in a /
|
||||
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::absoluteBaseURL());
|
||||
$this->assertEquals('http://www.example.org/', Director::absoluteURL(''));
|
||||
$this->assertEquals('http://www.example.org/subfolder/test', Director::absoluteURL('subfolder/test'));
|
||||
|
||||
// Setting it to false restores functionality
|
||||
|
@ -160,6 +160,26 @@ class HTTPTest extends FunctionalTest {
|
||||
*/
|
||||
public function testAbsoluteURLsAttributes() {
|
||||
$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
|
||||
$test->assertEquals(
|
||||
|
@ -6,6 +6,14 @@
|
||||
*/
|
||||
class ClassInfoTest extends SapphireTest {
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
'ClassInfoTest_BaseClass',
|
||||
'ClassInfoTest_ChildClass',
|
||||
'ClassInfoTest_GrandChildClass',
|
||||
'ClassInfoTest_BaseDataClass',
|
||||
'ClassInfoTest_NoFields',
|
||||
);
|
||||
|
||||
public function testExists() {
|
||||
$this->assertTrue(ClassInfo::exists('Object'));
|
||||
$this->assertTrue(ClassInfo::exists('ClassInfoTest'));
|
||||
@ -146,7 +154,7 @@ class ClassInfoTest extends SapphireTest {
|
||||
* @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
|
||||
*/
|
||||
|
||||
class ClassInfoTest_BaseDataClass extends DataObject {
|
||||
class ClassInfoTest_BaseDataClass extends DataObject implements TestOnly {
|
||||
|
||||
private static $db = array(
|
||||
'Title' => 'Varchar'
|
||||
|
@ -254,54 +254,92 @@ class ConfigTest extends SapphireTest {
|
||||
$this->markTestIncomplete();
|
||||
}
|
||||
|
||||
public function testLRUDiscarding() {
|
||||
$cache = new ConfigTest_Config_LRU();
|
||||
public function testCacheCleaning() {
|
||||
$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);
|
||||
$this->assertEquals(
|
||||
Config_LRU::SIZE, count($cache->indexing),
|
||||
'Homogenous usage gives exact discarding'
|
||||
);
|
||||
|
||||
$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(-1, -1);
|
||||
$this->assertLessThan(
|
||||
Config_LRU::SIZE, count($cache->indexing),
|
||||
'Heterogenous usage gives sufficient discarding'
|
||||
);
|
||||
Deprecation::restore_settings($depSettings);
|
||||
}
|
||||
|
||||
public function testLRUCleaning() {
|
||||
$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; $i++) $cache->set($i, $i);
|
||||
$this->assertEquals(Config_LRU::SIZE, count($cache->indexing));
|
||||
|
||||
$cache->clean();
|
||||
$this->assertEquals(0, count($cache->indexing), 'Clean clears all items');
|
||||
$this->assertFalse($cache->get(1), 'Clean clears all items');
|
||||
|
||||
$cache->set(1, 1, array('Foo'));
|
||||
$this->assertEquals(1, count($cache->indexing));
|
||||
|
||||
$cache->clean('Foo');
|
||||
$this->assertEquals(0, count($cache->indexing), 'Clean items with matching tag');
|
||||
$this->assertFalse($cache->get(1), 'Clean items with matching tag');
|
||||
|
||||
$cache->set(1, 1, array('Foo', 'Bar'));
|
||||
$this->assertEquals(1, count($cache->indexing));
|
||||
|
||||
$cache->clean('Bar');
|
||||
$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');
|
||||
Deprecation::restore_settings($depSettings);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigTest_Config_LRU extends Config_LRU implements TestOnly {
|
||||
|
||||
public $cache;
|
||||
public $indexing;
|
||||
}
|
||||
|
||||
class ConfigTest_Config_MemCache extends Config_MemCache implements TestOnly {
|
||||
|
||||
public $cache;
|
||||
public $tags;
|
||||
|
||||
}
|
||||
|
@ -280,4 +280,55 @@ PHP
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' =>
|
||||
'ManyManyRelation' =>
|
||||
array(
|
||||
array(
|
||||
"=>FixtureFactoryTest_DataObjectRelation.relation1" => array(),
|
||||
@ -48,18 +48,18 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(2, $obj->ManyMany()->Count());
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID));
|
||||
$this->assertEquals(2, $obj->ManyManyRelation()->Count());
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation2->ID));
|
||||
|
||||
$this->assertEquals(
|
||||
array('Label' => 'This is a label for relation 1'),
|
||||
$obj->ManyMany()->getExtraData('ManyMany', $relation1->ID)
|
||||
$obj->ManyManyRelation()->getExtraData('ManyManyRelation', $relation1->ID)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
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(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' =>
|
||||
'ManyManyRelation' =>
|
||||
'=>FixtureFactoryTest_DataObjectRelation.relation1,' .
|
||||
'=>FixtureFactoryTest_DataObjectRelation.relation2'
|
||||
),
|
||||
@ -104,9 +104,9 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(2, $obj->ManyMany()->Count());
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID));
|
||||
$this->assertEquals(2, $obj->ManyManyRelation()->Count());
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation2->ID));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,7 +119,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' => '=>UnknownClass.relation1'
|
||||
'ManyManyRelation' => '=>UnknownClass.relation1'
|
||||
),
|
||||
array(
|
||||
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||
@ -139,7 +139,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' => '=>FixtureFactoryTest_DataObjectRelation.unknown_identifier'
|
||||
'ManyManyRelation' => '=>FixtureFactoryTest_DataObjectRelation.unknown_identifier'
|
||||
),
|
||||
array(
|
||||
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||
@ -163,7 +163,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' => 'FixtureFactoryTest_DataObjectRelation.relation1'
|
||||
'ManyManyRelation' => 'FixtureFactoryTest_DataObjectRelation.relation1'
|
||||
),
|
||||
array(
|
||||
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||
|
@ -163,11 +163,11 @@ class FixtureFactoryTest_DataObject extends DataObject implements TestOnly {
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
"ManyMany" => "FixtureFactoryTest_DataObjectRelation"
|
||||
"ManyManyRelation" => "FixtureFactoryTest_DataObjectRelation"
|
||||
);
|
||||
|
||||
private static $many_many_extraFields = array(
|
||||
"ManyMany" => array(
|
||||
"ManyManyRelation" => array(
|
||||
"Label" => "Varchar"
|
||||
)
|
||||
);
|
||||
|
@ -84,15 +84,100 @@ class UploadTest extends SapphireTest {
|
||||
'error' => UPLOAD_ERR_OK,
|
||||
);
|
||||
|
||||
$v = new UploadTest_Validator();
|
||||
$v->setAllowedMaxFileSize(array('txt' => 10));
|
||||
|
||||
// test upload into default folder
|
||||
$u1 = new Upload();
|
||||
$v = new UploadTest_Validator();
|
||||
|
||||
$v->setAllowedMaxFileSize(array('txt' => 10));
|
||||
$u1->setValidator($v);
|
||||
$result = $u1->load($tmpFile);
|
||||
|
||||
$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() {
|
||||
|
107
tests/forms/TreeDropdownFieldTest.php
Normal 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'
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
25
tests/forms/TreeDropdownFieldTest.yml
Normal 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
|
@ -32,7 +32,7 @@ class GridFieldEditButtonTest extends SapphireTest {
|
||||
// Check that there are content
|
||||
$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
|
||||
// (he can still view the records)
|
||||
// (they can still view the records)
|
||||
$this->assertEquals(2, count($content->getBySelector('.edit-link')),
|
||||
'Edit links should show when not logged in.');
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class CompositeDBFieldTest extends SapphireTest {
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeDBFieldTest_DataObject extends DataObject {
|
||||
class CompositeDBFieldTest_DataObject extends DataObject implements TestOnly {
|
||||
private static $db = array(
|
||||
'Title' => 'Text',
|
||||
'MyMoney' => 'Money',
|
||||
|
@ -19,7 +19,12 @@ class DataListTest extends SapphireTest {
|
||||
'DataObjectTest_ValidatedObject',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment',
|
||||
'DataObjectTest_ExtendedTeamComment',
|
||||
'DataObjectTest_EquipmentCompany',
|
||||
'DataObjectTest_SubEquipmentCompany',
|
||||
'DataObjectTest\NamespacedClass',
|
||||
'DataObjectTest_Company',
|
||||
'DataObjectTest_Fan',
|
||||
);
|
||||
|
||||
public function testFilterDataObjectByCreatedDate() {
|
||||
|
@ -22,8 +22,12 @@ class DataObjectLazyLoadingTest extends SapphireTest {
|
||||
'DataObjectTest_ValidatedObject',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment',
|
||||
'DataObjectTest_EquipmentCompany',
|
||||
'DataObjectTest_SubEquipmentCompany',
|
||||
'VersionedTest_DataObject',
|
||||
'VersionedTest_Subclass'
|
||||
'VersionedTest_Subclass',
|
||||
'VersionedLazy_DataObject',
|
||||
'VersionedLazySub_DataObject',
|
||||
);
|
||||
|
||||
public function testQueriedColumnsID() {
|
||||
@ -403,7 +407,7 @@ class DataObjectLazyLoadingTest extends SapphireTest {
|
||||
|
||||
|
||||
/** Additional classes for versioned lazy loading testing */
|
||||
class VersionedLazy_DataObject extends DataObject {
|
||||
class VersionedLazy_DataObject extends DataObject implements TestOnly {
|
||||
private static $db = array(
|
||||
"PageName" => "Varchar"
|
||||
);
|
||||
|
@ -17,9 +17,15 @@ class DataObjectTest extends SapphireTest {
|
||||
'DataObjectTest_ValidatedObject',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment',
|
||||
'DataObjectTest_EquipmentCompany',
|
||||
'DataObjectTest_SubEquipmentCompany',
|
||||
'DataObjectTest\NamespacedClass',
|
||||
'DataObjectTest\RelationClass',
|
||||
'DataObjectTest_ExtendedTeamComment'
|
||||
'DataObjectTest_ExtendedTeamComment',
|
||||
'DataObjectTest_Company',
|
||||
'DataObjectTest_Staff',
|
||||
'DataObjectTest_CEO',
|
||||
'DataObjectTest_Fan',
|
||||
);
|
||||
|
||||
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() {
|
||||
$dataObject = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$changedDO = $dataObject->newClassInstance('DataObjectTest_SubTeam');
|
||||
@ -1074,18 +1160,85 @@ class DataObjectTest extends SapphireTest {
|
||||
$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() {
|
||||
$player = $this->objFromFixture('DataObjectTest_Player', 'player1');
|
||||
$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)
|
||||
$teamExtraFields = $team->many_many_extraFields('Players');
|
||||
$teamExtraFields = $team->manyManyExtraFieldsForComponent('Players');
|
||||
$this->assertEquals($teamExtraFields, array(
|
||||
'Position' => 'Varchar(100)'
|
||||
));
|
||||
|
||||
// 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(
|
||||
'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
|
||||
$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',
|
||||
'PreviousStaff' => 'DataObjectTest_Staff'
|
||||
),
|
||||
$company->has_many(),
|
||||
$company->hasMany(),
|
||||
'has_many strips field name data by default.'
|
||||
);
|
||||
|
||||
$this->assertEquals (
|
||||
'DataObjectTest_Staff',
|
||||
$company->has_many('CurrentStaff'),
|
||||
$company->hasManyComponent('CurrentStaff'),
|
||||
'has_many strips field name data by default on single relationships.'
|
||||
);
|
||||
|
||||
@ -1283,13 +1436,13 @@ class DataObjectTest extends SapphireTest {
|
||||
'CurrentStaff' => 'DataObjectTest_Staff.CurrentCompany',
|
||||
'PreviousStaff' => 'DataObjectTest_Staff.PreviousCompany'
|
||||
),
|
||||
$company->has_many(null, false),
|
||||
$company->hasMany(null, false),
|
||||
'has_many returns field name data when $classOnly is false.'
|
||||
);
|
||||
|
||||
$this->assertEquals (
|
||||
'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.'
|
||||
);
|
||||
}
|
||||
@ -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(
|
||||
'Title' => 'Custom Title',
|
||||
'Title.UpperCase' => 'Title',
|
||||
@ -1606,6 +1764,16 @@ class DataObjectTest_SubTeam extends DataObjectTest_Team implements TestOnly {
|
||||
private static $has_one = array(
|
||||
"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 {
|
||||
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(
|
||||
'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 (
|
||||
'CurrentCompany' => '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(
|
||||
'Name' => 'Varchar',
|
||||
'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(
|
||||
'Name' => 'Varchar(255)'
|
||||
|
@ -1,8 +1,20 @@
|
||||
DataObjectTest_EquipmentCompany:
|
||||
equipmentcompany1:
|
||||
Name: Company corp
|
||||
equipmentcompany2:
|
||||
Name: 'Team co.'
|
||||
DataObjectTest_SubEquipmentCompany:
|
||||
subequipmentcompany1:
|
||||
Name: John Smith and co
|
||||
DataObjectTest_Team:
|
||||
team1:
|
||||
Title: Team 1
|
||||
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
team2:
|
||||
Title: Team 2
|
||||
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany2,=>DataObjectTest_SubEquipmentCompany.subequipmentcompany1
|
||||
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
team3:
|
||||
Title: Team 3
|
||||
DataObjectTest_Player:
|
||||
@ -26,6 +38,8 @@ DataObjectTest_SubTeam:
|
||||
SubclassDatabaseField: Subclassed 1
|
||||
ExtendedDatabaseField: Extended 1
|
||||
ParentTeam: =>DataObjectTest_Team.team1
|
||||
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1
|
||||
subteam2_with_player_relation:
|
||||
Title: Subteam 2
|
||||
SubclassDatabaseField: Subclassed 2
|
||||
|
@ -49,6 +49,19 @@ class DataQueryTest extends SapphireTest {
|
||||
$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() {
|
||||
$newDQ = new DataQuery('DataQueryTest_E');
|
||||
//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(
|
||||
'TestC' => 'DataQueryTest_C',
|
||||
'TestCTwo' => 'DataQueryTest_C',
|
||||
);
|
||||
}
|
||||
|
||||
@ -273,7 +287,8 @@ class DataQueryTest_C extends DataObject implements TestOnly {
|
||||
|
||||
private static $has_many = array(
|
||||
'TestAs' => 'DataQueryTest_A',
|
||||
'TestBs' => 'DataQueryTest_B',
|
||||
'TestBs' => 'DataQueryTest_B.TestC',
|
||||
'TestBsTwo' => 'DataQueryTest_B.TestCTwo',
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
|
@ -9,6 +9,7 @@ class HasManyListTest extends SapphireTest {
|
||||
'DataObjectTest_Team',
|
||||
'DataObjectTest_SubTeam',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment',
|
||||
);
|
||||
|
||||
public function testRelationshipEmptyOnNewRecords() {
|
||||
|
@ -64,6 +64,10 @@ class PaginatedListTest extends SapphireTest {
|
||||
|
||||
$this->assertEquals(10, $list->CurrentPage());
|
||||
$this->assertEquals(90, $list->getPageStart());
|
||||
|
||||
// Test disabled paging
|
||||
$list->setPageLength(0);
|
||||
$this->assertEquals(1, $list->CurrentPage());
|
||||
}
|
||||
|
||||
public function testGetIterator() {
|
||||
@ -93,6 +97,20 @@ class PaginatedListTest extends SapphireTest {
|
||||
$list->setCurrentPage(999);
|
||||
$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();
|
||||
$list = new PaginatedList($players);
|
||||
$list->setPageLength(1);
|
||||
@ -127,6 +145,13 @@ class PaginatedListTest extends SapphireTest {
|
||||
array('PageNum' => 4),
|
||||
);
|
||||
$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() {
|
||||
@ -148,6 +173,13 @@ class PaginatedListTest extends SapphireTest {
|
||||
array('PageNum' => 25),
|
||||
);
|
||||
$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() {
|
||||
@ -170,6 +202,10 @@ class PaginatedListTest extends SapphireTest {
|
||||
$this->assertEquals(2, $list->CurrentPage());
|
||||
$list->setPageStart(40);
|
||||
$this->assertEquals(5, $list->CurrentPage());
|
||||
|
||||
// Disable paging
|
||||
$list->setPageLength(0);
|
||||
$this->assertEquals(1, $list->CurrentPage());
|
||||
}
|
||||
|
||||
public function testTotalPages() {
|
||||
@ -183,6 +219,13 @@ class PaginatedListTest extends SapphireTest {
|
||||
|
||||
$list->setTotalItems(5);
|
||||
$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() {
|
||||
@ -194,6 +237,10 @@ class PaginatedListTest extends SapphireTest {
|
||||
|
||||
$list->setTotalItems(2);
|
||||
$this->assertTrue($list->MoreThanOnePage());
|
||||
|
||||
// Disable paging
|
||||
$list->setPageLength(0);
|
||||
$this->assertFalse($list->MoreThanOnePage());
|
||||
}
|
||||
|
||||
public function testNotFirstPage() {
|
||||
@ -230,6 +277,10 @@ class PaginatedListTest extends SapphireTest {
|
||||
$this->assertEquals(20, $list->LastItem());
|
||||
$list->setCurrentPage(3);
|
||||
$this->assertEquals(25, $list->LastItem());
|
||||
|
||||
// Disable paging
|
||||
$list->setPageLength(0);
|
||||
$this->assertEquals(25, $list->LastItem());
|
||||
}
|
||||
|
||||
public function testFirstLink() {
|
||||
@ -242,6 +293,10 @@ class PaginatedListTest extends SapphireTest {
|
||||
$list->setPageLength(10);
|
||||
$list->setTotalItems(100);
|
||||
$this->assertContains('start=90', $list->LastLink());
|
||||
|
||||
// Disable paging
|
||||
$list->setPageLength(0);
|
||||
$this->assertContains('start=0', $list->LastLink());
|
||||
}
|
||||
|
||||
public function testNextLink() {
|
||||
@ -257,6 +312,11 @@ class PaginatedListTest extends SapphireTest {
|
||||
$this->assertContains('start=40', $list->NextLink());
|
||||
$list->setCurrentPage(5);
|
||||
$this->assertNull($list->NextLink());
|
||||
|
||||
// Disable paging
|
||||
$list->setCurrentPage(1);
|
||||
$list->setPageLength(0);
|
||||
$this->assertNull($list->NextLink());
|
||||
}
|
||||
|
||||
public function testPrevLink() {
|
||||
@ -270,6 +330,10 @@ class PaginatedListTest extends SapphireTest {
|
||||
$this->assertContains('start=10', $list->PrevLink());
|
||||
$list->setCurrentPage(5);
|
||||
$this->assertContains('start=30', $list->PrevLink());
|
||||
|
||||
// Disable paging
|
||||
$list->setPageLength(0);
|
||||
$this->assertNull($list->PrevLink());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class PolymorphicHasManyListTest extends SapphireTest {
|
||||
'DataObjectTest_Team',
|
||||
'DataObjectTest_SubTeam',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_Fan'
|
||||
'DataObjectTest_Fan',
|
||||
);
|
||||
|
||||
public function testRelationshipEmptyOnNewRecords() {
|
||||
|
@ -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 & ipsum dolor sit amet' => 'Lorem & ipsum dolor...'
|
||||
);
|
||||
|
||||
foreach($cases as $originalValue => $expectedValue) {
|
||||
$textObj = new Text('Test');
|
||||
$textObj->setValue($originalValue);
|
||||
$this->assertEquals($expectedValue, $textObj->LimitCharactersToClosestWord(24));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test {@link Text->LimitWordCount()}
|
||||
*/
|
||||
|
@ -46,7 +46,7 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
|
||||
$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("\"Code\"='ADMIN'")->Count(), 1,
|
||||
$this->assertEquals($untouchable->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
|
||||
'The other group has ADMIN permission');
|
||||
|
||||
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount, 'There are no orphaned permissions');
|
||||
@ -62,14 +62,14 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
|
||||
$untouchable->flushCache();
|
||||
$this->assertEquals($group->Permissions()->Count(), 2,
|
||||
'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');
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+2,
|
||||
@ -85,12 +85,12 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
|
||||
$untouchable->flushCache();
|
||||
$this->assertEquals($group->Permissions()->Count(), 1,
|
||||
'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');
|
||||
|
||||
$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');
|
||||
|
||||
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+1,
|
||||
|
@ -74,6 +74,104 @@ class SecurityTest extends FunctionalTest {
|
||||
$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() {
|
||||
$member = DataObject::get_one('Member');
|
||||
|
||||
@ -517,3 +615,11 @@ class SecurityTest_SecuredController extends Controller implements TestOnly {
|
||||
return 'Success';
|
||||
}
|
||||
}
|
||||
|
||||
class SecurityTest_NullController extends Controller implements TestOnly {
|
||||
|
||||
public function redirect($url, $code = 302) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class YamlFixtureTest extends SapphireTest {
|
||||
$factory->getId("YamlFixtureTest_DataObject", "testobject1")
|
||||
);
|
||||
$this->assertTrue(
|
||||
$object1->ManyMany()->Count() == 2,
|
||||
$object1->ManyManyRelation()->Count() == 2,
|
||||
"Should be two items in this relationship"
|
||||
);
|
||||
$this->assertGreaterThan(0, $factory->getId("YamlFixtureTest_DataObject", "testobject2"));
|
||||
@ -58,7 +58,7 @@ class YamlFixtureTest extends SapphireTest {
|
||||
$factory->getId("YamlFixtureTest_DataObject", "testobject2")
|
||||
);
|
||||
$this->assertTrue(
|
||||
$object2->ManyMany()->Count() == 1,
|
||||
$object2->ManyManyRelation()->Count() == 1,
|
||||
"Should be one item in this relationship"
|
||||
);
|
||||
}
|
||||
@ -79,7 +79,7 @@ class YamlFixtureTest_DataObject extends DataObject implements TestOnly {
|
||||
"Name" => "Varchar"
|
||||
);
|
||||
private static $many_many = array(
|
||||
"ManyMany" => "YamlFixtureTest_DataObjectRelation"
|
||||
"ManyManyRelation" => "YamlFixtureTest_DataObjectRelation"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ YamlFixtureTest_DataObjectRelation:
|
||||
YamlFixtureTest_DataObject:
|
||||
testobject1:
|
||||
Name: TestObject1
|
||||
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1,=>YamlFixtureTest_DataObjectRelation.relation2
|
||||
ManyManyRelation: =>YamlFixtureTest_DataObjectRelation.relation1,=>YamlFixtureTest_DataObjectRelation.relation2
|
||||
testobject2:
|
||||
Name: TestObject2
|
||||
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1
|
||||
ManyManyRelation: =>YamlFixtureTest_DataObjectRelation.relation1
|
@ -1,6 +1,11 @@
|
||||
<?php
|
||||
|
||||
class SSViewerTest extends SapphireTest {
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
'SSViewerTest_Object',
|
||||
);
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
Config::inst()->update('SSViewer', 'source_file_comments', false);
|
||||
@ -1112,6 +1117,8 @@ after')
|
||||
$orig = Config::inst()->get('SSViewer', 'rewrite_hash_links');
|
||||
Config::inst()->update('SSViewer', 'rewrite_hash_links', true);
|
||||
|
||||
$_SERVER['REQUEST_URI'] = 'http://path/to/file?foo"onclick="alert(\'xss\')""';
|
||||
|
||||
// Emulate SSViewer::process()
|
||||
$base = Convert::raw2att($_SERVER['REQUEST_URI']);
|
||||
|
||||
@ -1122,6 +1129,8 @@ after')
|
||||
<html>
|
||||
<head><% base_tag %></head>
|
||||
<body>
|
||||
<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
|
||||
$ExternalInsertedLink
|
||||
<a class="inline" href="#anchor">InlineLink</a>
|
||||
$InsertedLink
|
||||
<svg><use xlink:href="#sprite"></use></svg>
|
||||
@ -1130,15 +1139,24 @@ after')
|
||||
$tmpl = new SSViewer($tmplFile);
|
||||
$obj = new ViewableData();
|
||||
$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);
|
||||
$this->assertContains(
|
||||
'<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
|
||||
$result
|
||||
);
|
||||
$this->assertContains(
|
||||
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
|
||||
$result
|
||||
);
|
||||
$this->assertContains(
|
||||
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
|
||||
$result
|
||||
);
|
||||
$this->assertContains(
|
||||
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
|
||||
$result
|
||||
);
|
||||
$this->assertContains(
|
||||
'<svg><use xlink:href="#sprite"></use></svg>',
|
||||
$result,
|
||||
@ -1171,7 +1189,7 @@ after')
|
||||
$obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>';
|
||||
$result = $tmpl->process($obj);
|
||||
$this->assertContains(
|
||||
'<a class="inserted" href="<?php echo strip_tags(',
|
||||
'<a class="inserted" href="<?php echo Convert::raw2att(',
|
||||
$result
|
||||
);
|
||||
// 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;
|
||||
|
||||
|
@ -156,6 +156,25 @@ class ViewableDataTest extends SapphireTest {
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
/**#@-*/
|
||||
|
@ -10,7 +10,7 @@ jQuery(function($){
|
||||
nextText: 'Neste»',
|
||||
currentText: 'I dag',
|
||||
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'],
|
||||
dayNamesShort: ['søn','man','tir','ons','tor','fre','lør'],
|
||||
dayNamesMin: ['Sø','Ma','Ti','On','To','Fr','Lø'],
|
||||
|
@ -4675,7 +4675,7 @@ class SSTemplateParser extends Parser implements TemplateParser {
|
||||
$text = preg_replace(
|
||||
'/(<a[^>]+href *= *)"#/i',
|
||||
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
|
||||
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
||||
' Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
||||
\'#',
|
||||
$text
|
||||
);
|
||||
|
@ -1129,7 +1129,7 @@ class SSTemplateParser extends Parser implements TemplateParser {
|
||||
$text = preg_replace(
|
||||
'/(<a[^>]+href *= *)"#/i',
|
||||
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
|
||||
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
||||
' Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
||||
\'#',
|
||||
$text
|
||||
);
|
||||
|
@ -1115,9 +1115,9 @@ class SSViewer implements Flushable {
|
||||
if($this->rewriteHashlinks && $rewrite) {
|
||||
if(strpos($output, '<base') !== false) {
|
||||
if($rewrite === 'php') {
|
||||
$thisURLRelativeToBase = "<?php echo strip_tags(\$_SERVER['REQUEST_URI']); ?>";
|
||||
$thisURLRelativeToBase = "<?php echo Convert::raw2att(\$_SERVER['REQUEST_URI']); ?>";
|
||||
} else {
|
||||
$thisURLRelativeToBase = strip_tags($_SERVER['REQUEST_URI']);
|
||||
$thisURLRelativeToBase = Convert::raw2att($_SERVER['REQUEST_URI']);
|
||||
}
|
||||
|
||||
$output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
|
||||
|
@ -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
|
||||
* that have been specified.
|
||||
@ -354,12 +386,14 @@ class ViewableData extends Object implements IteratorAggregate {
|
||||
* @param array $arguments
|
||||
* @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
|
||||
* @param bool $cache Cache this object
|
||||
* @param string $cacheName a custom cache name
|
||||
*/
|
||||
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
|
||||
$methodIsAllowed = true;
|
||||
if($this instanceof FormField && $fieldName == 'Name') $methodIsAllowed = false;
|
||||
@ -381,9 +415,7 @@ class ViewableData extends Object implements IteratorAggregate {
|
||||
$value = $valueObject;
|
||||
}
|
||||
|
||||
if($cache) $this->objCache[$cacheName] = $value;
|
||||
} else {
|
||||
$value = $this->objCache[$cacheName];
|
||||
if($cache) $this->objCacheSet($cacheName, $value);
|
||||
}
|
||||
|
||||
if(!is_object($value) && $forceReturnedObject) {
|
||||
@ -669,6 +701,13 @@ class ViewableData_Debugger extends ViewableData {
|
||||
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
|
||||
* field, otherwise it will show information on all methods and fields.
|
||||
|