From 2e36e450ca4017b2f4ecddb4a4539e6d9d3b6f13 Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Tue, 5 Mar 2013 10:23:30 +1300 Subject: [PATCH 001/111] Fix alignment of selects within GridField filters --- css/GridField.css | 1 + scss/GridField.scss | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/css/GridField.css b/css/GridField.css index 4433e2f34..cdf6d27e3 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -81,6 +81,7 @@ Used in side panels and action tabs .cms table.ss-gridfield-table tr th.extra { position: relative; background: #637276; background: rgba(0, 0, 0, 0.7); padding: 5px; border-top: rgba(0, 0, 0, 0.2); } .cms table.ss-gridfield-table tr th.extra input { height: 28px; } .cms table.ss-gridfield-table tr th.extra button.ss-ui-button { padding: .3em; line-height: 1; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; position: relative; border-bottom-width: 0; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; } +.cms table.ss-gridfield-table tr th.extra select { margin: 0; } .cms table.ss-gridfield-table tr th.first { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; } .cms table.ss-gridfield-table tr th.last { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; } .cms table.ss-gridfield-table tr th button#action_gridfield_relationadd:hover { color: #444 !important; /* Not sure why IE think it needs this */ } diff --git a/scss/GridField.scss b/scss/GridField.scss index 10439ab2c..8273cbd7d 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -382,9 +382,11 @@ $gf_grid_x: 16px; background: rgba(#000, 0.7); padding: 5px; border-top: $gf_colour_text_shadow; + input { height:28px; //height of input field - to match design. } + button.ss-ui-button { padding: .3em; line-height: 1; @@ -392,7 +394,11 @@ $gf_grid_x: 16px; position: relative; border-bottom-width: 0; @include border-radius(2px, 2px); - } + } + + select { + margin: 0; + } } &.first { @include border-top-left-radius($gf_border_radius); From dda6fa660040090bb0f05a14813e70560efa632c Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Tue, 5 Mar 2013 10:25:19 +1300 Subject: [PATCH 002/111] Add no results warning to GridField print view. --- templates/Includes/GridField_print.ss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/templates/Includes/GridField_print.ss b/templates/Includes/GridField_print.ss index 80450a9cb..8595007fd 100644 --- a/templates/Includes/GridField_print.ss +++ b/templates/Includes/GridField_print.ss @@ -10,9 +10,15 @@ <% loop Header %>$CellString<% end_loop %> + <% if ItemRows %> <% loop ItemRows %> <% loop ItemRow %>$CellString<% end_loop %> <% end_loop %> + <% else %> + +

<% _t('GridField.NoItemsFound', 'No items found') %>

+ + <% end_if %>

From 36d3303e1b123ca8296643ef9e6a235650aac25c Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Tue, 5 Mar 2013 10:27:15 +1300 Subject: [PATCH 003/111] FIX GridField export and print actions should preserve state. --- forms/gridfield/GridFieldExportButton.php | 2 +- forms/gridfield/GridFieldPrintButton.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/forms/gridfield/GridFieldExportButton.php b/forms/gridfield/GridFieldExportButton.php index 4c19823c9..1d815e614 100644 --- a/forms/gridfield/GridFieldExportButton.php +++ b/forms/gridfield/GridFieldExportButton.php @@ -119,7 +119,7 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP $fileData .= "\n"; } - $items = $gridField->getList(); + $items = $gridField->getManipulatedList(); // @todo should GridFieldComponents change behaviour based on whether others are available in the config? foreach($gridField->getConfig()->getComponents() as $component){ diff --git a/forms/gridfield/GridFieldPrintButton.php b/forms/gridfield/GridFieldPrintButton.php index 9be52fff0..b9abe7b08 100644 --- a/forms/gridfield/GridFieldPrintButton.php +++ b/forms/gridfield/GridFieldPrintButton.php @@ -118,6 +118,7 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr } } + $items = $gridField->getManipulatedList(); $itemRows = new ArrayList(); foreach($items as $item) { $itemRow = new ArrayList(); From 1ddd1ddc47c698ef47161e9c22eaf027e535cedd Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Tue, 5 Mar 2013 10:29:27 +1300 Subject: [PATCH 004/111] Docblock and coding conventions for GridField related classes. --- forms/gridfield/GridField.php | 63 +++++-- forms/gridfield/GridFieldPrintButton.php | 184 ++++++++++++-------- forms/gridfield/GridFieldSortableHeader.php | 2 + forms/gridfield/GridState.php | 127 +++++++++----- 4 files changed, 245 insertions(+), 131 deletions(-) diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index 996763ea4..c254c5b6a 100644 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -54,7 +54,9 @@ class GridField extends FormField { protected $config = null; /** - * The components list + * The components list + * + * @var array */ protected $components = array(); @@ -68,9 +70,14 @@ class GridField extends FormField { /** * Map of callbacks for custom data fields + * + * @var array */ protected $customDataFields = array(); + /** + * @var string + */ protected $name = ''; /** @@ -222,12 +229,14 @@ class GridField extends FormField { * Get the current GridState_Data or the GridState * * @param bool $getData - flag for returning the GridState_Data or the GridState + * * @return GridState_data|GridState */ - public function getState($getData=true) { + public function getState($getData = true) { if($getData) { return $this->state->getData(); } + return $this->state; } @@ -465,11 +474,16 @@ class GridField extends FormField { /** * Add additional calculated data fields to be used on this GridField - * @param array $fields a map of fieldname to callback. The callback will bed passed the record as an argument. + * + * @param array $fields a map of fieldname to callback. The callback will + * be passed the record as an argument. */ public function addDataFields($fields) { - if($this->customDataFields) $this->customDataFields = array_merge($this->customDataFields, $fields); - else $this->customDataFields = $fields; + if($this->customDataFields) { + $this->customDataFields = array_merge($this->customDataFields, $fields); + } else { + $this->customDataFields = $fields; + } } /** @@ -580,9 +594,11 @@ class GridField extends FormField { */ protected function buildColumnDispatch() { $this->columnDispatch = array(); + foreach($this->getComponents() as $item) { if($item instanceof GridField_ColumnProvider) { $columns = $item->getColumnsHandled($this); + foreach($columns as $column) { $this->columnDispatch[$column][] = $item; } @@ -603,14 +619,19 @@ class GridField extends FormField { // Update state from client $state = $this->getState(false); - if(isset($fieldData['GridState'])) $state->setValue($fieldData['GridState']); + + if(isset($fieldData['GridState'])) { + $state->setValue($fieldData['GridState']); + } // Try to execute alter action foreach($data as $k => $v) { if(preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $k, $matches)) { $id = $matches[1]; $stateChange = Session::get($id); + $actionName = $stateChange['actionName']; + $args = isset($stateChange['args']) ? $stateChange['args'] : array(); $html = $this->handleAlterAction($actionName, $args, $data); // A field can optionally return its own HTML @@ -750,31 +771,31 @@ class GridField extends FormField { class GridField_FormAction extends FormAction { /** - * * @var GridField */ protected $gridField; /** - * * @var array */ protected $stateValues; /** - * * @var array */ - //protected $stateFields = array(); - - protected $actionName; - protected $args = array(); + /** + * @var string + */ + protected $actionName; + + /** + * @var boolean + */ public $useButtonTag = true; /** - * * @param GridField $gridField * @param type $name * @param type $label @@ -785,11 +806,13 @@ class GridField_FormAction extends FormAction { $this->gridField = $gridField; $this->actionName = $actionName; $this->args = $args; + parent::__construct($name, $title); } /** - * urlencode encodes less characters in percent form than we need - we need everything that isn't a \w + * urlencode encodes less characters in percent form than we need - we + * need everything that isn't a \w. * * @param string $val */ @@ -806,13 +829,17 @@ class GridField_FormAction extends FormAction { return '%'.dechex(ord($match[0])); } + /** + * @return array + */ public function getAttributes() { - // Store state in session, and pass ID to client side + // Store state in session, and pass ID to client side. $state = array( 'grid' => $this->getNameFromParent(), 'actionName' => $this->actionName, 'args' => $this->args, ); + $id = preg_replace('/[^\w]+/', '_', uniqid('', true)); Session::set($id, $state); $actionData['StateID'] = $id; @@ -837,10 +864,12 @@ class GridField_FormAction extends FormAction { protected function getNameFromParent() { $base = $this->gridField; $name = array(); + do { array_unshift($name, $base->getName()); $base = $base->getForm(); } while ($base && !($base instanceof Form)); + return implode('.', $name); } -} +} \ No newline at end of file diff --git a/forms/gridfield/GridFieldPrintButton.php b/forms/gridfield/GridFieldPrintButton.php index b9abe7b08..d7124e462 100644 --- a/forms/gridfield/GridFieldPrintButton.php +++ b/forms/gridfield/GridFieldPrintButton.php @@ -1,17 +1,18 @@ tag below the field + * + * @param GridField + * + * @return array */ public function getHTMLFragments($gridField) { $button = new GridField_FormAction( @@ -45,21 +52,34 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr 'print', null ); + $button->setAttribute('data-icon', 'grid_print'); $button->addExtraClass('gridfield-button-print'); - //$button->addExtraClass('no-ajax'); + return array( $this->targetFragment => '

' . $button->Field() . '

', ); } /** - * print is an action button + * Print is an action button. + * + * @param GridField + * + * @return array */ public function getActions($gridField) { return array('print'); } + /** + * Handle the print action. + * + * @param GridField + * @param string + * @param array + * @param array + */ public function handleAction(GridField $gridField, $actionName, $arguments, $data) { if($actionName == 'print') { return $this->handlePrint($gridField); @@ -67,7 +87,10 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr } /** - * it is also a URL + * Print is accessible via the url + * + * @param GridField + * @return array */ public function getURLHandlers($gridField) { return array( @@ -82,15 +105,20 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr set_time_limit(60); Requirements::clear(); Requirements::css(FRAMEWORK_DIR . '/css/GridField_print.css'); + if($data = $this->generatePrintData($gridField)){ return $data->renderWith("GridField_print"); } } /** - * Export core. - */ - public function generatePrintData($gridField) { + * Return the columns to print + * + * @param GridField + * + * @return array + */ + protected function getPrintColumnsForGridField(GridField $gridField) { if($this->printColumns) { $printColumns = $this->printColumns; } else if($dataCols = $gridField->getConfig()->getComponentByType('GridFieldDataColumns')) { @@ -98,75 +126,93 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr } else { $printColumns = singleton($gridField->getModelClass())->summaryFields(); } - - $header = null; - if($this->printHasHeader){ - $header = new ArrayList(); - foreach($printColumns as $field => $label){ - $header->push( - new ArrayData(array( - "CellString" => $label, - )) - ); - } - } - - $items = $gridField->getList(); - foreach($gridField->getConfig()->getComponents() as $component){ - if($component instanceof GridFieldFilterHeader || $component instanceof GridFieldSortableHeader) { - $items = $component->getManipulatedData($gridField, $items); - } - } - - $items = $gridField->getManipulatedList(); - $itemRows = new ArrayList(); - foreach($items as $item) { - $itemRow = new ArrayList(); - foreach($printColumns as $field => $label) { - $value = $gridField->getDataFieldValue($item, $field); - $itemRow->push( - new ArrayData(array( - "CellString" => $value, - )) - ); - } - $itemRows->push(new ArrayData( - array( - "ItemRow" => $itemRow - ) - )); - $item->destroy(); - } - - //get title for the print view + + return $printColumns; + } + + /** + * Return the title of the printed page + * + * @param GridField + * + * @return array + */ + public function getTitle(GridField $gridField) { $form = $gridField->getForm(); $currentController = Controller::curr(); $title = ''; + if(method_exists($currentController, 'Title')) { $title = $currentController->Title(); - }else{ - if($currentController->Title){ + } else { + if ($currentController->Title) { $title = $currentController->Title; - }else{ + } else { if($form->Name()){ $title = $form->Name(); } } } - if($fieldTitle = $gridField->Title()){ - if($title) $title .= " - "; + + if($fieldTitle = $gridField->Title()) { + if($title) { + $title .= " - "; + } + $title .= $fieldTitle; } + + return $title; + } + + /** + * Export core. + * + * @param GridField + */ + public function generatePrintData(GridField $gridField) { + $printColumns = $this->getPrintColumnsForGridField($gridField); - $ret = new ArrayData( - array( - "Title" => $title, - "Header" => $header, - "ItemRows" => $itemRows, - "Datetime" => SS_Datetime::now(), - "Member" => Member::currentUser(), - ) - ); + $header = null; + + if($this->printHasHeader) { + $header = new ArrayList(); + + foreach($printColumns as $field => $label){ + $header->push(new ArrayData(array( + "CellString" => $label, + ))); + } + } + + $items = $gridField->getManipulatedList(); + $itemRows = new ArrayList(); + + foreach($items as $item) { + $itemRow = new ArrayList(); + + foreach($printColumns as $field => $label) { + $value = $gridField->getDataFieldValue($item, $field); + + $itemRow->push(new ArrayData(array( + "CellString" => $value, + ))); + } + + $itemRows->push(new ArrayData(array( + "ItemRow" => $itemRow + ))); + + $item->destroy(); + } + + $ret = new ArrayData(array( + "Title" => $this->getTitle($gridField), + "Header" => $header, + "ItemRows" => $itemRows, + "Datetime" => SS_Datetime::now(), + "Member" => Member::currentUser(), + )); return $ret; } @@ -183,6 +229,7 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr */ public function setPrintColumns($cols) { $this->printColumns = $cols; + return $this; } @@ -198,6 +245,7 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr */ public function setPrintHasHeader($bool) { $this->printHasHeader = $bool; + return $this; } -} +} \ No newline at end of file diff --git a/forms/gridfield/GridFieldSortableHeader.php b/forms/gridfield/GridFieldSortableHeader.php index 003695b6b..012f48f9d 100644 --- a/forms/gridfield/GridFieldSortableHeader.php +++ b/forms/gridfield/GridFieldSortableHeader.php @@ -83,10 +83,12 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM $state = $gridField->State->GridFieldSortableHeader; $columns = $gridField->getColumns(); $currentColumn = 0; + foreach($columns as $columnField) { $currentColumn++; $metadata = $gridField->getColumnMetadata($columnField); $title = $metadata['title']; + if(isset($this->fieldSorting[$columnField]) && $this->fieldSorting[$columnField]) { $columnField = $this->fieldSorting[$columnField]; } diff --git a/forms/gridfield/GridState.php b/forms/gridfield/GridState.php index cc102214d..3716ab1f0 100644 --- a/forms/gridfield/GridState.php +++ b/forms/gridfield/GridState.php @@ -1,8 +1,8 @@ getName() . '[GridState]'); } - + /** - * - * @param type $value + * @param mixed $d + * @return object */ - public function setValue($value) { - if (is_string($value)) { - $this->gridStateData = new GridState_Data(json_decode($value, true)); + public static function array_to_object($d) { + if(is_array($d)) { + return (object) array_map(array('GridState', 'array_to_object'), $d); } - parent::setValue($value); - } - - public function getData() { - if(!$this->gridStateData) $this->gridStateData = new GridState_Data; - return $this->gridStateData; + + return $d; } /** - * - * @return type + * @param mixed $value + */ + public function setValue($value) { + if (is_string($value)) { + $this->data = new GridState_Data(json_decode($value, true)); + } + + parent::setValue($value); + } + + /** + * @var GridState_Data + */ + public function getData() { + if(!$this->data) { + $this->data = new GridState_Data(); + } + + return $this->data; + } + + /** + * @return DataList */ public function getList() { return $this->grid->getList(); } - /** @return string */ + /** + * Returns a json encoded string representation of this state. + * + * @return string + */ public function Value() { - if(!$this->gridStateData) { + if(!$this->data) { return json_encode(array()); } - return json_encode($this->gridStateData->toArray()); + + return json_encode($this->data->toArray()); } /** + * Returns a json encoded string representation of this state. * - * @return type + * @return string */ public function dataValue() { return $this->Value(); @@ -100,9 +115,18 @@ class GridState extends HiddenField { } /** - * Simple set of data, similar to stdClass, but without the notice-level errors + * Simple set of data, similar to stdClass, but without the notice-level errors. + * + * @see GridState + * + * @package framework + * @subpackage fields-relational */ class GridState_Data { + + /** + * @var array + */ protected $data; public function __construct($data = array()) { @@ -110,42 +134,53 @@ class GridState_Data { } public function __get($name) { - if(!isset($this->data[$name])) $this->data[$name] = new GridState_Data; - if(is_array($this->data[$name])) $this->data[$name] = new GridState_Data($this->data[$name]); + if(!isset($this->data[$name])) { + $this->data[$name] = new GridState_Data(); + } else if(is_array($this->data[$name])) { + $this->data[$name] = new GridState_Data($this->data[$name]); + } + return $this->data[$name]; } + public function __set($name, $value) { $this->data[$name] = $value; } + public function __isset($name) { return isset($this->data[$name]); } public function __toString() { - if(!$this->data) return ""; - else return json_encode($this->toArray()); + if(!$this->data) { + return ""; + } + + return json_encode($this->toArray()); } public function toArray() { $output = array(); + foreach($this->data as $k => $v) { $output[$k] = (is_object($v) && method_exists($v, 'toArray')) ? $v->toArray() : $v; } + return $output; } } - +/** + * @see GridState + * + * @package framework + * @subpackage fields-relational + */ class GridState_Component implements GridField_HTMLProvider { public function getHTMLFragments($gridField) { - - $forTemplate = new ArrayData(array()); - $forTemplate->Fields = new ArrayList; - return array( 'before' => $gridField->getState(false)->Field() ); } - } From 1853fc864ae4532543ad515247251afa64364780 Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Tue, 5 Mar 2013 10:30:39 +1300 Subject: [PATCH 005/111] FIX: Do not blindly pass input values to GridField_FormAction URL's The length of input fields can very quickly exceed the max URI length resulting in 414 errors when printing or exporting results. To access the input values for a specific GridField action, encapsulate this in your own Entwine instance. --- javascript/GridField.js | 48 ++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/javascript/GridField.js b/javascript/GridField.js index c06e3bdbb..6b372d411 100644 --- a/javascript/GridField.js +++ b/javascript/GridField.js @@ -157,16 +157,24 @@ }, onclick: function(e){ var btn = this.closest(':button'), grid = this.getGridField(), - form = this.closest('form'), data = form.find(':input').serialize(); + form = this.closest('form'), data = form.find(':input.gridstate').serialize();; // Add current button - data += '&' + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val()); + data += "&" + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val()); - // Include any GET parameters from the current URL, as the view state might depend on it. - // For example, a list prefiltered through external search criteria might be passed to GridField. - if(window.location.search) data = window.location.search.replace(/^\?/, '') + '&' + data; + // Include any GET parameters from the current URL, as the view + // state might depend on it. + // For example, a list prefiltered through external search criteria + // might be passed to GridField. + if(window.location.search) { + data = window.location.search.replace(/^\?/, '') + '&' + data; + } + + var url = $.path.makeUrlAbsolute( + grid.data('url') + '?' + data, + $('base').attr('href') + ); - var url = $.path.makeUrlAbsolute(grid.data('url') + '?' + data, $('base').attr('href')); var newWindow = window.open(url); return false; @@ -188,22 +196,30 @@ /** * Prevents actions from causing an ajax reload of the field. - * Useful e.g. for actions which rely on HTTP response headers being interpreted nativel - * by the browser, like file download triggers. + * + * Useful e.g. for actions which rely on HTTP response headers being + * interpreted natively by the browser, like file download triggers. */ $('.ss-gridfield .action.no-ajax').entwine({ onclick: function(e){ var self = this, btn = this.closest(':button'), grid = this.getGridField(), - form = this.closest('form'), data = form.find(':input').serialize(); + form = this.closest('form'), data = form.find(':input.gridstate').serialize(); // Add current button - data += '&' + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val()); + data += "&" + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val()); - // Include any GET parameters from the current URL, as the view state might depend on it. - // For example, a list prefiltered through external search criteria might be passed to GridField. - if(window.location.search) data = window.location.search.replace(/^\?/, '') + '&' + data; + // Include any GET parameters from the current URL, as the view + // state might depend on it. For example, a list pre-filtered + // through external search criteria might be passed to GridField. + if(window.location.search) { + data = window.location.search.replace(/^\?/, '') + '&' + data; + } + + window.location.href = $.path.makeUrlAbsolute( + grid.data('url') + '?' + data, + $('base').attr('href') + ); - window.location.href = $.path.makeUrlAbsolute(grid.data('url') + '?' + data, $('base').attr('href')); return false; } }); @@ -340,7 +356,5 @@ } } }); - }); - -}(jQuery)); +}(jQuery)); \ No newline at end of file From 05d13054979e2a068e22026a6db5c179b9924aed Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 28 Mar 2013 21:25:51 +0100 Subject: [PATCH 006/111] Simplified travis builds (external setup scripts) --- .travis.yml | 9 ++++---- tests/travis/_config.php | 22 ------------------- tests/travis/_manifest_exclude | 0 tests/travis/_ss_environment.php | 37 -------------------------------- tests/travis/before_script | 10 --------- 5 files changed, 5 insertions(+), 73 deletions(-) delete mode 100644 tests/travis/_config.php delete mode 100644 tests/travis/_manifest_exclude delete mode 100644 tests/travis/_ss_environment.php delete mode 100755 tests/travis/before_script diff --git a/.travis.yml b/.travis.yml index c4811ab2f..c97ef435c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,16 +3,17 @@ php: - 5.3 env: - - TESTDB=MYSQL - - TESTDB=PGSQL + - TESTDB=MYSQL CORE_RELEASE=2.4 + - TESTDB=PGSQL CORE_RELEASE=2.4 before_script: - phpenv rehash - - ./tests/travis/before_script ~/builds/ss + - git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support + - php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss - cd ~/builds/ss script: - - phpunit -c phpunit.xml.dist + - phpunit sapphire/tests branches: except: diff --git a/tests/travis/_config.php b/tests/travis/_config.php deleted file mode 100644 index 7b5da0dc3..000000000 --- a/tests/travis/_config.php +++ /dev/null @@ -1,22 +0,0 @@ - Date: Thu, 28 Mar 2013 22:18:08 +0100 Subject: [PATCH 007/111] Require cms in 2.4 composer.json (which is used in travis) --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1aa80df55..433cae4f1 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=5.2.4", - "composer/installers": "*" + "composer/installers": "*", + "silverstripe/cms": "2.4.*" } } \ No newline at end of file From 1eadff5a4f4f3ff92ebe8f7b59a2cd6c20800859 Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Fri, 12 Apr 2013 10:59:00 +1200 Subject: [PATCH 008/111] BUG Security::permissionFailure() fixing permissionDenied hook inconsistency permissionDenied only works if Security::permissionFailure() is called when there's currently no logged in Member. This fixes it so failed attempts with logged in Member also includes the permissionDenied hook. In addition, fix an undefined $member variable --- security/Security.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/security/Security.php b/security/Security.php index 1ab0bfe85..be60c3a61 100644 --- a/security/Security.php +++ b/security/Security.php @@ -222,17 +222,20 @@ class Security extends Controller { $messageSet = array('default' => $messageSet); } + $member = Member::currentUser(); + // Work out the right message to show - if(Member::currentUser()) { + if($member && $member->exists()) { $response = ($controller) ? $controller->getResponse() : new SS_HTTPResponse(); $response->setStatusCode(403); //If 'alreadyLoggedIn' is not specified in the array, then use the default //which should have been specified in the lines above - if(isset($messageSet['alreadyLoggedIn'])) - $message=$messageSet['alreadyLoggedIn']; - else - $message=$messageSet['default']; + if(isset($messageSet['alreadyLoggedIn'])) { + $message = $messageSet['alreadyLoggedIn']; + } else { + $message = $messageSet['default']; + } // Somewhat hackish way to render a login form with an error message. $me = new Security(); @@ -240,8 +243,11 @@ class Security extends Controller { $form->sessionMessage($message, 'warning'); Session::set('MemberLoginForm.force_message',1); $formText = $me->login(); - + $response->setBody($formText); + + $controller->extend('permissionDenied', $member); + return $response; } else { $message = $messageSet['default']; From f86ca423ca73982de2aa35a55fc46b5666a13fef Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 18 Apr 2013 16:08:08 +0200 Subject: [PATCH 009/111] Added --no-update hint to composer docs --- docs/en/installation/composer.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/en/installation/composer.md b/docs/en/installation/composer.md index 0b2715fab..f020ebc62 100644 --- a/docs/en/installation/composer.md +++ b/docs/en/installation/composer.md @@ -66,7 +66,16 @@ Composer isn't only used to download SilverStripe CMS, it can also be used to ma composer require silverstripe/forum:* -This command has two parts. First is `silverstripe/forum`. This is the name of the package. You can find other packages with the following command: +This will install the forum module in the latest compatible version. +By default, Composer updates other existing modules (like `framework` and `cms`), +and installs "dev" dependencies like PHPUnit. In case you don't need those dependencies, +use the following command instead: + + composer require --no-update silverstripe/forum:* + composer update --no-dev + +The `require` command has two parts. First is `silverstripe/forum`. This is the name of the package. +You can find other packages with the following command: composer search silverstripe @@ -84,7 +93,7 @@ Except for the control code of the Voyager space probe, every piece of code in t To get the latest updates of the modules in your project, run this command: - composer update + composer update --no-dev Updates to the required modules will be installed, and the `composer.lock` file will get updated with the specific commits of each of those. From eb583c5f1450e272b1b3c91eef36f255ced7e382 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Sun, 21 Apr 2013 13:18:18 +1200 Subject: [PATCH 010/111] NEW: Added DataObject::getQueriedDatabaseFields() as faster alternative to toMap() API: CompositeDBField::setValue() may be passed an object as its second argument, in addition to array. These changes provide a 15% - 20% performance improvement, and as such justify an small API change in the 3.0 branch. It will likely affect anyone who has created their own composite fields, which is fortunately not all that common. --- docs/en/changelogs/3.0.6.md | 5 +++++ model/DataObject.php | 12 ++++++++++++ model/fieldtypes/CompositeDBField.php | 2 +- model/fieldtypes/Money.php | 5 +++++ view/ViewableData.php | 2 +- 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 docs/en/changelogs/3.0.6.md diff --git a/docs/en/changelogs/3.0.6.md b/docs/en/changelogs/3.0.6.md new file mode 100644 index 000000000..7e6448cf0 --- /dev/null +++ b/docs/en/changelogs/3.0.6.md @@ -0,0 +1,5 @@ +# 3.0.6 (Not yet released) + +## Upgrading + + * If you have created your own composite database fields, then you shoulcd amend the setValue() to allow the passing of an object (usually DataObject) as well as an array. diff --git a/model/DataObject.php b/model/DataObject.php index 421ba8b4a..a1f81ce34 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -730,6 +730,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity return $this->record; } + /** + * Return all currently fetched database fields. + * + * This function is similar to toMap() but doesn't trigger the lazy-loading of all unfetched fields. + * Obviously, this makes it a lot faster. + * + * @return array The data as a map. + */ + public function getQueriedDatabaseFields() { + return $this->record; + } + /** * Update a number of fields on this object, given a map of the desired changes. * diff --git a/model/fieldtypes/CompositeDBField.php b/model/fieldtypes/CompositeDBField.php index 2cdf1808c..979bba6b9 100644 --- a/model/fieldtypes/CompositeDBField.php +++ b/model/fieldtypes/CompositeDBField.php @@ -118,7 +118,7 @@ interface CompositeDBField { * parameter. * * @param DBField|array $value - * @param array $record Map of values loaded from the database + * @param DataObject|array $record An array or object that this field is part of * @param boolean $markChanged Indicate wether this field should be marked changed. * Set to FALSE if you are initializing this field after construction, rather * than setting a new value. diff --git a/model/fieldtypes/Money.php b/model/fieldtypes/Money.php index 30aeb5a44..42ef083c6 100644 --- a/model/fieldtypes/Money.php +++ b/model/fieldtypes/Money.php @@ -103,6 +103,11 @@ class Money extends DBField implements CompositeDBField { } public function setValue($value, $record = null, $markChanged = true) { + // Convert an object to an array + if($record && $record instanceof DataObject) { + $record = $record->getQueriedDatabaseFields(); + } + // @todo Allow resetting value to NULL through Money $value field if ($value instanceof Money && $value->exists()) { $this->setCurrency($value->getCurrency(), $markChanged); diff --git a/view/ViewableData.php b/view/ViewableData.php index 97fb99eb5..d2bc75726 100644 --- a/view/ViewableData.php +++ b/view/ViewableData.php @@ -374,7 +374,7 @@ class ViewableData extends Object implements IteratorAggregate { } $valueObject = Object::create_from_string($castConstructor, $fieldName); - $valueObject->setValue($value, ($this->hasMethod('toMap') ? $this->toMap() : null)); + $valueObject->setValue($value, $this); $value = $valueObject; } From 9ba26a04c4d7c050022b13505023a58634197fa8 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Mon, 22 Apr 2013 11:50:57 +1200 Subject: [PATCH 011/111] Added test for lazy-loading edge-case in Money field. --- tests/model/MoneyTest.php | 16 ++++++++++++++++ tests/model/MoneyTest.yml | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/model/MoneyTest.php b/tests/model/MoneyTest.php index 9b8bfc92d..f9a547cc6 100644 --- a/tests/model/MoneyTest.php +++ b/tests/model/MoneyTest.php @@ -17,6 +17,7 @@ class MoneyTest extends SapphireTest { protected $extraDataObjects = array( 'MoneyTest_DataObject', + 'MoneyTest_SubClass', ); public function testMoneyFieldsReturnedAsObjects() { @@ -268,6 +269,15 @@ class MoneyTest extends SapphireTest { ))->value() ); } + + public function testMoneyLazyLoading() { + // Get the object, ensuring that MyOtherMoney will be lazy loaded + $id = $this->idFromFixture('MoneyTest_SubClass', 'test2'); + $obj = MoneyTest_DataObject::get()->byID($id); + + $this->assertEquals('£2.46', $obj->obj('MyOtherMoney')->Nice()); + } + } class MoneyTest_DataObject extends DataObject implements TestOnly { @@ -277,3 +287,9 @@ class MoneyTest_DataObject extends DataObject implements TestOnly { ); } +class MoneyTest_SubClass extends MoneyTest_DataObject implements TestOnly { + static $db = array( + 'MyOtherMoney' => 'Money', + ); + +} diff --git a/tests/model/MoneyTest.yml b/tests/model/MoneyTest.yml index 1e49e6e35..816f4f9e3 100644 --- a/tests/model/MoneyTest.yml +++ b/tests/model/MoneyTest.yml @@ -1,4 +1,8 @@ MoneyTest_DataObject: test1: MyMoneyCurrency: EUR - MyMoneyAmount: 1.23 \ No newline at end of file + MyMoneyAmount: 1.23 +MoneyTest_SubClass: + test2: + MyOtherMoneyCurrency: GBP + MyOtherMoneyAmount: 2.46 \ No newline at end of file From a303a39d71c450578a238c5c015aa0a9f97243cf Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 22 Apr 2013 14:14:32 +0200 Subject: [PATCH 012/111] Allow SS_DATABASE_NAME in _ss_environment.php configuration Makes setups which are completely driven by that file a bit easier to automate, particularly if the same codebase is deployed multiple times (e.g. to a staging and live instance) --- conf/ConfigureFromEnv.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/conf/ConfigureFromEnv.php b/conf/ConfigureFromEnv.php index ffa0beb99..b081ca694 100644 --- a/conf/ConfigureFromEnv.php +++ b/conf/ConfigureFromEnv.php @@ -73,8 +73,9 @@ global $database; // No database provided if(!isset($database) || !$database) { - // if SS_DATABASE_CHOOSE_NAME - if(defined('SS_DATABASE_CHOOSE_NAME') && SS_DATABASE_CHOOSE_NAME) { + if(defined('SS_DATABASE_NAME')) { + $database = SS_DATABASE_NAME; + } else if(defined('SS_DATABASE_CHOOSE_NAME') && SS_DATABASE_CHOOSE_NAME) { $loopCount = (int)SS_DATABASE_CHOOSE_NAME; $databaseDir = BASE_PATH; for($i=0;$i<$loopCount-1;$i++) $databaseDir = dirname($databaseDir); From 50d2c262303df21271f5090811326ba5f87ffeff Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 18 Apr 2013 15:16:54 +1000 Subject: [PATCH 013/111] Issue #1775 Add reference to $Me in loop and update occurence of Children with $ prefix. --- docs/en/reference/templates.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/en/reference/templates.md b/docs/en/reference/templates.md index 81b3ca064..ec83d3c9a 100644 --- a/docs/en/reference/templates.md +++ b/docs/en/reference/templates.md @@ -218,7 +218,10 @@ This loops over the children of a page, and generates an unordered list showing the `Title` property from each one. Note that `$Title` *inside* the loop refers to the `Title` property on each object that is looped over, not the current page. To refer to the current page's `Title` property inside the loop, you can do -`$Up.Title`. More about `Up` later. +`$Up.Title`. More about `Up` later. + +`$Me` can be used to refer to the current object context the template is rendered +with. ### Position Indicators @@ -245,7 +248,7 @@ custom column names based on your loop statement. Note that this works for any control statement (not just children). :::ss - <% loop Children %> + <% loop $Children %>
...
@@ -258,7 +261,7 @@ You can also use $MultipleOf(value, offset) to help build columned layouts. In this case we want to add a
after every 3th item. :::ss - <% loop Children %> + <% loop $Children %> <% if MultipleOf(3) %>
<% end_if %> @@ -282,11 +285,11 @@ the scope back to the previous level. Take the following example: :::ss $Title -- - <% loop Children %> + <% loop $Children %> $Title $Up.Title -- - <% loop Children %> + <% loop $Children %> $Title $Up.Title <% end_loop %> @@ -314,12 +317,12 @@ include `$Top`: :::ss $Title -- - <% loop Children %> + <% loop $Children %> $Title $Up.Title $Top.Title -- - <% loop Children %> + <% loop $Children %> $Title $Up.Title $Top.Title From 26ed69508a183c9a43f1f7b1ad563392a72c4a3f Mon Sep 17 00:00:00 2001 From: Christian Lohmaier Date: Mon, 22 Apr 2013 01:51:00 +0200 Subject: [PATCH 014/111] fix nginx documentation to one that actually works :-) example configuration wuldn't allow to install silverstripe, as install.php does exist as a regular file (and that was ignored in the old version of the documentation) Similarily, the last rule in the htaccess snippet that should allow the access to the tinymce php files were never applied, as a previously listed regex did match and denied access. Even if it would have taken effect: as those files do exist on disk, they would have been handed out as-is and not been interpreted by php. Also the statement regarding accidental/exploitable execution of arbitrary php was misleading (and to some degree even wrong) in the old context. squashed commit as per pr#1791 --- docs/en/installation/nginx.md | 163 ++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 49 deletions(-) diff --git a/docs/en/installation/nginx.md b/docs/en/installation/nginx.md index b9390385d..844a6d296 100644 --- a/docs/en/installation/nginx.md +++ b/docs/en/installation/nginx.md @@ -6,83 +6,147 @@ These instructions are also covered in less detail on the The prerequisite is that you have already installed Nginx and you are able to run PHP files via the FastCGI-wrapper from Nginx. -Now you need to set up a virtual host in Nginx with the following -configuration settings: +Now you need to set up a virtual host in Nginx with configuration settings +that are similar to those shown below. +
+If you don't fully understand the configuration presented here, consult the +[nginx documentation](http://nginx.org/en/docs/). + +Especially be aware of [accidental php-execution](https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ "Don't trust the tutorials") when extending the configuration. +
+But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`: server { listen 80; - - # SSL configuration (optional, but recommended for security) - include ssl - - root /var/www/example.com; - index index.php index.html index.htm; - server_name example.com; - include silverstripe3; - include htaccess; + root /var/www/example.com; + + # SSL configuration (optional, but recommended for security) + # (remember to actually force logins to use ssl) + include ssl + + include silverstripe3.conf; + include htaccess.conf; + + # rest of the server section is optional, but helpful + # maintenance page if it exists + error_page 503 @maintenance; + if (-f $document_root/maintenance.html ) { + return 503; + } + location @maintenance { + try_files /maintenance.html =503; + } + + # always show SilverStripe's version of 500 error page + error_page 500 /assets/error-500.html; + + # let the user's browser cache static files (e.g. 2 weeks) + expires 2w; + + # in case your machine is slow, increase the timeout + # (also remembers php's own timeout settings) + #fastcgi_read_timeout 300s; } -Here is the include file `silverstripe3`: +Here is the include file `silverstripe3.conf`: location / { try_files $uri @silverstripe; } - + + # only needed for installation - disable this location (and remove the + # index.php and install.php files) after you installed SilverStripe + # (you did read the blogentry linked above, didn't you) + location ~ ^/(index|install).php { + fastcgi_split_path_info ^((?U).+\.php)(/?.+)$; + include fastcgi.conf; + fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock; + } + + # whitelist php files that are called directly and need to be interpreted + location = /framework/thirdparty/tinymce/tiny_mce_gzip.php { + include fastcgi.conf; + fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock; + } + location = /framework/thirdparty/tinymce-spellchecker/rpc.php { + include fastcgi.conf; + fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock; + } + location @silverstripe { - include fastcgi_params; - - # Defend against arbitrary PHP code execution - # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini - # More info: - # https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ - fastcgi_split_path_info ^(.+\.php)(/.+)$; - + expires off; + include fastcgi.conf; + fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock; + # note that specifying a fixed script already protects against execution + # of arbitrary files, but remember the advice above for any other rules + # you add yourself (monitoring, etc,....) fastcgi_param SCRIPT_FILENAME $document_root/framework/main.php; fastcgi_param SCRIPT_NAME /framework/main.php; fastcgi_param QUERY_STRING url=$uri&$args; - fastcgi_pass unix:/var/run/php5-fpm.sock; - fastcgi_index index.php; - fastcgi_buffer_size 32k; - fastcgi_buffers 4 32k; - fastcgi_busy_buffers_size 64k; + # tuning is up to your expertise, but buffer_size needs to be >= 8k, + # otherwise you'll get "upstream sent too big header while reading + # response header from upstream" errors. + fastcgi_buffer_size 8k; + #fastcgi_buffers 4 32k; + #fastcgi_busy_buffers_size 64k; } +
+With only the above configuration, nginx would hand out any existing file +uninterpreted, so it would happily serve your precious configuration files, +including all your private api-keys and whatnot to any random visitor. So you +**must** restrict access further. +
+You don't need to use separate files, but it is easier to have the permissive +rules distinct from the restricting ones. -Here is the include file `htaccess`: +Here is the include file `htaccess.conf`: - # Don't serve up any .htaccess files - location ~ /\.ht { - deny all; - } - - # Deny access to silverstripe-cache - location ~ ^/silverstripe-cache { - deny all; - } - - # Don't execute scripts in the assets + # Don't try to find nonexisting stuff in assets (esp. don't pass through php) location ^~ /assets/ { - try_files $uri $uri/ =404; + try_files $uri =404; } - # Block access to yaml files - location ~ \.yml$ { + # Deny access to silverstripe-cache, vendor or composer.json/.lock + location ^~ /silverstripe-cache/ { + deny all; + } + location ^~ /vendor/ { + deny all; + } + location ~ /composer\.(json|lock) { deny all; } - # cms & framework .htaccess rules - location ~ ^/(cms|framework|mysite)/.*\.(php|php[345]|phtml|inc)$ { + # Don't serve up any "hidden" files or directories + # (starting with dot, like .htaccess or .git) + # also don't serve web.config files + location ~ /(\.|web\.config) { + deny all; + } + + # Block access to yaml files (and don't forget about backup + # files that editors tend to leave behind) + location ~ \.(yml|bak|swp)$ { + deny all; + } + location ~ ~$ { + deny all; + } + + # generally don't serve any php-like files + # (as they exist, they would be served as regular files, and not interpreted. + # But as those can contain configuration data, this is bad nevertheless) + # If needed, you can always whitelist entries. + location ~ \.(php|php[345]|phtml|inc)$ { deny all; } location ~ ^/(cms|framework)/silverstripe_version$ { deny all; } - location ~ ^/framework/.*(main|static-main|rpc|tiny_mce_gzip)\.php$ { - allow all; - } Here is the optional include file `ssl`: @@ -95,8 +159,9 @@ Here is the optional include file `ssl`: The above configuration sets up a virtual host `example.com` with rewrite rules suited for SilverStripe. The location block named -`@silverstripe` passes all php scripts to the FastCGI-wrapper via a Unix -socket. This example is from a site running Ubuntu with the php5-fpm -package. +`@silverstripe` passes all requests that aren't matched by one of the other +location rules (and cannot be satisfied by serving an existing file) to +SilverStripe framework's main.php script, that is run by the FastCGI-wrapper, +that in turn is accessed via a Unix socket. Now you can proceed with the SilverStripe installation normally. From c8d98861c0a070455a8eb618915f1a2aa461576a Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Thu, 25 Apr 2013 11:53:33 +1200 Subject: [PATCH 015/111] Update template documentation to include DataList methods https://github.com/silverstripe/sapphire/pull/1799. --- docs/en/reference/templates.md | 59 ++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/docs/en/reference/templates.md b/docs/en/reference/templates.md index ec83d3c9a..79e08db8e 100644 --- a/docs/en/reference/templates.md +++ b/docs/en/reference/templates.md @@ -214,12 +214,15 @@ collection of items. For example: <% end_loop %> -This loops over the children of a page, and generates an unordered list showing -the `Title` property from each one. Note that `$Title` *inside* the loop refers -to the `Title` property on each object that is looped over, not the current page. -To refer to the current page's `Title` property inside the loop, you can do -`$Up.Title`. More about `Up` later. - +This loops over the children of a page, and generates an unordered list showing +the `Title` property from each one. Note that `$Title` *inside* the loop refers +to the `Title` property on each object that is looped over, not the current page. +This is know as the `Scope` of the template. For more information about `Scope` +see the section below. + +To refer to the current page's `Title` property inside the loop, you can do +`$Up.Title`. More about `Up` later. + `$Me` can be used to refer to the current object context the template is rendered with. @@ -235,6 +238,50 @@ current position in the list and iteration: * `$Pos`: The current position in the list (integer). Will start at 1. * `$TotalItems`: Number of items in the list (integer) +### Altering the list + +`<% loop %>` statements iterate over a `[api:DataList]` instance. As the +template has access to the list object, templates can call `[api:DataList]` +functions. For instance, see the following examples: + +Providing a custom sort. + + :::ss +
    + <% loop $Children.Sort(Title) %> +
  • $Title
  • + <% end_loop %> +
+ +Limiting the number of items displayed. + + :::ss +
    + <% loop $Children.Limit(10) %> +
  • $Title
  • + <% end_loop %> +
+ +Reversing the loop. + + :::ss +
    + <% loop $Children.Reverse %> +
  • $Title
  • + <% end_loop %> +
+ + +The `DataList` class also supports chaining methods. For example, to reverse +the list and output the last 3 items we would write: + + :::ss +
    + <% loop $Children.Reverse.Limit(3) %> +
  • $Title
  • + <% end_loop %> +
+ ### Modulus and MultipleOf $Modulus and $MultipleOf can help to build column layouts. From b211f22bf91715b20fb20c697431a8b5ea77d19d Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 26 Apr 2013 11:40:09 +0200 Subject: [PATCH 016/111] FIX JSONDataFormatter wrong relation identifiers (fixes #1795) --- api/JSONDataFormatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/JSONDataFormatter.php b/api/JSONDataFormatter.php index 638c36a24..648e1a04e 100644 --- a/api/JSONDataFormatter.php +++ b/api/JSONDataFormatter.php @@ -96,7 +96,7 @@ class JSONDataFormatter extends DataFormatter { $innerParts[] = ArrayData::array_to_object(array( "className" => $relClass, "href" => "$href.json", - "id" => $obj->$fieldName + "id" => $item->$fieldName )); } $serobj->$relName = $innerParts; @@ -117,7 +117,7 @@ class JSONDataFormatter extends DataFormatter { $innerParts[] = ArrayData::array_to_object(array( "className" => $relClass, "href" => "$href.json", - "id" => $obj->$fieldName + "id" => $item->$fieldName )); } $serobj->$relName = $innerParts; From 5f91c3724dd1aa22a356e17c8a17be5793ac8611 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 26 Apr 2013 11:48:59 +0200 Subject: [PATCH 017/111] Consistently used dollar notation in template docs (see #1794) --- docs/en/howto/extend-cms-interface.md | 2 +- docs/en/howto/grouping-dataobjectsets.md | 8 ++++---- docs/en/howto/navigation-menu.md | 2 +- docs/en/howto/pagination.md | 14 ++++++------- docs/en/misc/coding-conventions.md | 2 +- docs/en/reference/partial-caching.md | 4 ++-- docs/en/reference/searchcontext.md | 16 +++++++-------- docs/en/reference/siteconfig.md | 2 +- .../en/reference/templates-upgrading-guide.md | 4 ++-- docs/en/reference/templates.md | 18 ++++++++--------- docs/en/topics/data-types.md | 2 +- docs/en/topics/datamodel.md | 2 +- docs/en/topics/forms.md | 8 ++++---- docs/en/topics/javascript.md | 2 +- docs/en/topics/page-type-templates.md | 18 ++++++++--------- docs/en/topics/rich-text-editing.md | 2 +- docs/en/topics/theme-development.md | 12 +++++------ docs/en/tutorials/1-building-a-basic-site.md | 18 ++++++++--------- docs/en/tutorials/2-extending-a-basic-site.md | 8 ++++---- docs/en/tutorials/3-forms.md | 4 ++-- docs/en/tutorials/4-site-search.md | 20 +++++++++---------- .../5-dataobject-relationship-management.md | 20 +++++++++---------- 22 files changed, 94 insertions(+), 94 deletions(-) diff --git a/docs/en/howto/extend-cms-interface.md b/docs/en/howto/extend-cms-interface.md index c48059dcb..44cb847af 100644 --- a/docs/en/howto/extend-cms-interface.md +++ b/docs/en/howto/extend-cms-interface.md @@ -129,7 +129,7 @@ and replace it with the following: :::ss
    - <% loop BookmarkedPages %> + <% loop $BookmarkedPages %>
  • Edit "$Title"
  • <% end_loop %>
diff --git a/docs/en/howto/grouping-dataobjectsets.md b/docs/en/howto/grouping-dataobjectsets.md index 87dbcf0e5..f170b0282 100644 --- a/docs/en/howto/grouping-dataobjectsets.md +++ b/docs/en/howto/grouping-dataobjectsets.md @@ -74,10 +74,10 @@ In this case, the `getTitleFirstLetter()` method defined earlier is used to brea :::ss <%-- Modules list grouped by TitleFirstLetter --%>

Modules

- <% loop GroupedModules.GroupedBy(TitleFirstLetter) %> + <% loop $GroupedModules.GroupedBy(TitleFirstLetter) %>

$TitleFirstLetter

    - <% loop Children %> + <% loop $Children %>
  • $Title
  • <% end_loop %>
@@ -133,10 +133,10 @@ The final step is the render this into the template using the [api:GroupedList-> :::ss // Modules list grouped by the Month Posted

Modules

- <% loop GroupedModulesByDate.GroupedBy(MonthCreated) %> + <% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %>

$MonthCreated

    - <% loop Children %> + <% loop $Children %>
  • $Title ($Created.Nice)
  • <% end_loop %>
diff --git a/docs/en/howto/navigation-menu.md b/docs/en/howto/navigation-menu.md index 056c47e2e..f31bc0ce8 100644 --- a/docs/en/howto/navigation-menu.md +++ b/docs/en/howto/navigation-menu.md @@ -9,7 +9,7 @@ located in `themes//templates/Page.ss`. :::ss