Merge remote-tracking branch 'origin/3.0' into 3.1

Conflicts:
	dev/CsvBulkLoader.php
This commit is contained in:
Ingo Schommer 2013-05-09 10:34:20 +02:00
commit 3b02d22989
38 changed files with 243 additions and 132 deletions

View File

@ -97,7 +97,7 @@ class JSONDataFormatter extends DataFormatter {
$innerParts[] = ArrayData::array_to_object(array( $innerParts[] = ArrayData::array_to_object(array(
"className" => $relClass, "className" => $relClass,
"href" => "$href.json", "href" => "$href.json",
"id" => $obj->$fieldName "id" => $item->$fieldName
)); ));
} }
$serobj->$relName = $innerParts; $serobj->$relName = $innerParts;
@ -118,7 +118,7 @@ class JSONDataFormatter extends DataFormatter {
$innerParts[] = ArrayData::array_to_object(array( $innerParts[] = ArrayData::array_to_object(array(
"className" => $relClass, "className" => $relClass,
"href" => "$href.json", "href" => "$href.json",
"id" => $obj->$fieldName "id" => $item->$fieldName
)); ));
} }
$serobj->$relName = $innerParts; $serobj->$relName = $innerParts;

View File

@ -94,22 +94,22 @@ class CsvBulkLoader extends BulkLoader {
$obj->{"{$relationName}ID"} = $relationObj->ID; $obj->{"{$relationName}ID"} = $relationObj->ID;
//write if we are not previewing //write if we are not previewing
if (!$preview) { if (!$preview) {
$obj->write(); $obj->write();
$obj->flushCache(); // avoid relation caching confusion $obj->flushCache(); // avoid relation caching confusion
} }
} elseif(strpos($fieldName, '.') !== false) { } elseif(strpos($fieldName, '.') !== false) {
// we have a relation column with dot notation // we have a relation column with dot notation
list($relationName, $columnName) = explode('.', $fieldName); list($relationName,$columnName) = explode('.', $fieldName);
// always gives us an component (either empty or existing) // always gives us an component (either empty or existing)
$relationObj = $obj->getComponent($relationName); $relationObj = $obj->getComponent($relationName);
if (!$preview) $relationObj->write(); if (!$preview) $relationObj->write();
$obj->{"{$relationName}ID"} = $relationObj->ID; $obj->{"{$relationName}ID"} = $relationObj->ID;
//write if we are not previewing //write if we are not previewing
if (!$preview) { if (!$preview) {
$obj->write(); $obj->write();
$obj->flushCache(); // avoid relation caching confusion $obj->flushCache(); // avoid relation caching confusion
} }
} }
} }
@ -166,10 +166,12 @@ class CsvBulkLoader extends BulkLoader {
foreach($this->duplicateChecks as $fieldName => $duplicateCheck) { foreach($this->duplicateChecks as $fieldName => $duplicateCheck) {
if(is_string($duplicateCheck)) { if(is_string($duplicateCheck)) {
$SQL_fieldName = Convert::raw2sql($duplicateCheck); $SQL_fieldName = Convert::raw2sql($duplicateCheck);
if(!isset($record[$SQL_fieldName]) || empty($record[$SQL_fieldName])) { //skip current duplicate check if field value is empty if(!isset($record[$SQL_fieldName])) {
continue; return false;
//user_error("CsvBulkLoader:processRecord: Couldn't find duplicate identifier '{$fieldName}'
//in columns", E_USER_ERROR);
} }
$SQL_fieldValue = Convert::raw2sql($record[$fieldName]); $SQL_fieldValue = Convert::raw2sql($record[$SQL_fieldName]);
$existingRecord = DataObject::get_one($this->objectClass, "\"$SQL_fieldName\" = '{$SQL_fieldValue}'"); $existingRecord = DataObject::get_one($this->objectClass, "\"$SQL_fieldName\" = '{$SQL_fieldValue}'");
if($existingRecord) return $existingRecord; if($existingRecord) return $existingRecord;
} elseif(is_array($duplicateCheck) && isset($duplicateCheck['callback'])) { } elseif(is_array($duplicateCheck) && isset($duplicateCheck['callback'])) {

View File

@ -177,15 +177,23 @@ class DevelopmentAdmin extends Controller {
$generator = Injector::inst()->create('RandomGenerator'); $generator = Injector::inst()->create('RandomGenerator');
$token = $generator->randomToken('sha1'); $token = $generator->randomToken('sha1');
echo <<<TXT $path = $this->request->getVar('path');
if($path) {
Token: $token if(file_exists(BASE_PATH . '/' . $path)) {
echo sprintf(
Please add this to your mysite/_config.php with the following code: "Configuration file '%s' exists, can't merge. Please choose a new file.\n",
Config::inst()->update('Security', 'token', '$token'); BASE_PATH . '/' . $path
);
exit(1);
TXT; }
$yml = "Security:\n token: $token";
file_put_contents(BASE_PATH . '/' . $path, $yml);
echo "Configured token in $path\n";
} else {
echo "Generated new token. Please add the following code to your YAML configuration:\n\n";
echo "Security:\n";
echo " token: $token\n";
}
} }
public function errors() { public function errors() {

View File

@ -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.

View File

@ -126,7 +126,7 @@ and replace it with the following:
:::ss :::ss
<ul> <ul>
<% loop BookmarkedPages %> <% loop $BookmarkedPages %>
<li><a href="admin/pages/edit/show/$ID">Edit "$Title"</a></li> <li><a href="admin/pages/edit/show/$ID">Edit "$Title"</a></li>
<% end_loop %> <% end_loop %>
</ul> </ul>

View File

@ -74,10 +74,10 @@ In this case, the `getTitleFirstLetter()` method defined earlier is used to brea
:::ss :::ss
<%-- Modules list grouped by TitleFirstLetter --%> <%-- Modules list grouped by TitleFirstLetter --%>
<h2>Modules</h2> <h2>Modules</h2>
<% loop GroupedModules.GroupedBy(TitleFirstLetter) %> <% loop $GroupedModules.GroupedBy(TitleFirstLetter) %>
<h3>$TitleFirstLetter</h3> <h3>$TitleFirstLetter</h3>
<ul> <ul>
<% loop Children %> <% loop $Children %>
<li>$Title</li> <li>$Title</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -133,10 +133,10 @@ The final step is the render this into the template using the [api:GroupedList->
:::ss :::ss
// Modules list grouped by the Month Posted // Modules list grouped by the Month Posted
<h2>Modules</h2> <h2>Modules</h2>
<% loop GroupedModulesByDate.GroupedBy(MonthCreated) %> <% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %>
<h3>$MonthCreated</h3> <h3>$MonthCreated</h3>
<ul> <ul>
<% loop Children %> <% loop $Children %>
<li>$Title ($Created.Nice)</li> <li>$Title ($Created.Nice)</li>
<% end_loop %> <% end_loop %>
</ul> </ul>

View File

@ -9,7 +9,7 @@ located in `themes/<mytheme>/templates/Page.ss`.
:::ss :::ss
<ul> <ul>
<% loop Menu(1) %> <% loop $Menu(1) %>
<li> <li>
<a href="$Link" title="Go to the $Title page" class="$LinkingMode"> <a href="$Link" title="Go to the $Title page" class="$LinkingMode">
<span>$MenuTitle</span> <span>$MenuTitle</span>

View File

@ -36,7 +36,7 @@ The first step is to simply list the objects in the template:
:::ss :::ss
<ul> <ul>
<% loop PaginatedPages %> <% loop $PaginatedPages %>
<li><a href="$Link">$Title</a></li> <li><a href="$Link">$Title</a></li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -45,22 +45,22 @@ By default this will display 10 pages at a time. The next step is to add paginat
controls below this so the user can switch between pages: controls below this so the user can switch between pages:
:::ss :::ss
<% if PaginatedPages.MoreThanOnePage %> <% if $PaginatedPages.MoreThanOnePage %>
<% if PaginatedPages.NotFirstPage %> <% if $PaginatedPages.NotFirstPage %>
<a class="prev" href="$PaginatedPages.PrevLink">Prev</a> <a class="prev" href="$PaginatedPages.PrevLink">Prev</a>
<% end_if %> <% end_if %>
<% loop PaginatedPages.Pages %> <% loop $PaginatedPages.Pages %>
<% if CurrentBool %> <% if $CurrentBool %>
$PageNum $PageNum
<% else %> <% else %>
<% if Link %> <% if $Link %>
<a href="$Link">$PageNum</a> <a href="$Link">$PageNum</a>
<% else %> <% else %>
... ...
<% end_if %> <% end_if %>
<% end_if %> <% end_if %>
<% end_loop %> <% end_loop %>
<% if PaginatedPages.NotLastPage %> <% if $PaginatedPages.NotLastPage %>
<a class="next" href="$PaginatedPages.NextLink">Next</a> <a class="next" href="$PaginatedPages.NextLink">Next</a>
<% end_if %> <% end_if %>
<% end_if %> <% end_if %>

View File

@ -66,7 +66,16 @@ Composer isn't only used to download SilverStripe CMS, it can also be used to ma
composer require silverstripe/forum:* 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 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: 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. Updates to the required modules will be installed, and the `composer.lock` file will get updated with the specific commits of each of those.

View File

@ -427,7 +427,7 @@ Put code into the classes in the following order (where applicable).
* Commonly used methods like `getCMSFields()` * Commonly used methods like `getCMSFields()`
* Accessor methods (`getMyField()` and `setMyField()`) * Accessor methods (`getMyField()` and `setMyField()`)
* Controller action methods * Controller action methods
* Template data-access methods (methods that will be called by a `$MethodName` or `<% loop MethodName %>` construct in a template somewhere) * Template data-access methods (methods that will be called by a `$MethodName` or `<% loop $MethodName %>` construct in a template somewhere)
* Object methods * Object methods
### SQL Format ### SQL Format

View File

@ -215,7 +215,7 @@ Failing example:
:::ss :::ss
<% cached LastEdited %> <% cached LastEdited %>
<% loop Children %> <% loop $Children %>
<% cached LastEdited %> <% cached LastEdited %>
$Name $Name
<% end_cached %> <% end_cached %>
@ -231,7 +231,7 @@ Can be re-written as:
<% cached LastEdited %> <% cached LastEdited %>
<% cached Children.max(LastEdited) %> <% cached Children.max(LastEdited) %>
<% loop Children %> <% loop $Children %>
$Name $Name
<% end_loop %> <% end_loop %>
<% end_cached %> <% end_cached %>

View File

@ -139,9 +139,9 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
:::ss :::ss
<% if Results %> <% if $Results %>
<ul> <ul>
<% loop Results %> <% loop $Results %>
<li>$Title, $Autor</li> <li>$Title, $Autor</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -149,19 +149,19 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
<p>Sorry, your search query did not return any results.</p> <p>Sorry, your search query did not return any results.</p>
<% end_if %> <% end_if %>
<% if Results.MoreThanOnePage %> <% if $Results.MoreThanOnePage %>
<div id="PageNumbers"> <div id="PageNumbers">
<p> <p>
<% if Results.NotFirstPage %> <% if $Results.NotFirstPage %>
<a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a> <a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a>
<% end_if %> <% end_if %>
<span> <span>
<% loop Results.PaginationSummary(4) %> <% loop $Results.PaginationSummary(4) %>
<% if CurrentBool %> <% if $CurrentBool %>
$PageNum $PageNum
<% else %> <% else %>
<% if Link %> <% if $Link %>
<a href="$Link" title="View page number $PageNum">$PageNum</a> <a href="$Link" title="View page number $PageNum">$PageNum</a>
<% else %> <% else %>
&hellip; &hellip;
@ -170,7 +170,7 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
<% end_loop %> <% end_loop %>
</span> </span>
<% if Results.NotLastPage %> <% if $Results.NotLastPage %>
<a class="next" href="$Results.NextLink" title="View the next page">Next</a> <a class="next" href="$Results.NextLink" title="View the next page">Next</a>
<% end_if %> <% end_if %>
</p> </p>

View File

@ -15,7 +15,7 @@ You can access `[api:SiteConfig]` options from any SS template by using the func
// or // or
<% loop SiteConfig %> <% loop $SiteConfig %>
$Title $AnotherField $Title $AnotherField
<% end_loop %> <% end_loop %>

View File

@ -4,9 +4,9 @@ These are the main changes to the SiverStripe 3 template language.
## Control blocks: Loops vs. Scope ## Control blocks: Loops vs. Scope
The `<% control var %>...<% end_control %>` in SilverStripe prior to version 3 has two different meanings. Firstly, if the control variable is a collection (e.g. DataList), then `<% control %>` iterates over that set. If it's a non-iteratable object, however, `<% control %>` introduces a new scope, which is used to render the inner template code. This dual-use is confusing to some people, and doesn't allow a collection of objects to be used as a scope. The `<% control $var %>...<% end_control %>` in SilverStripe prior to version 3 has two different meanings. Firstly, if the control variable is a collection (e.g. DataList), then `<% control %>` iterates over that set. If it's a non-iteratable object, however, `<% control %>` introduces a new scope, which is used to render the inner template code. This dual-use is confusing to some people, and doesn't allow a collection of objects to be used as a scope.
In SilverStripe 3, the first usage (iteration) is replaced by `<% loop var %>`. The second usage (scoping) is replaced by `<% with var %>` In SilverStripe 3, the first usage (iteration) is replaced by `<% loop $var %>`. The second usage (scoping) is replaced by `<% with $var %>`
## Literals in Expressions ## Literals in Expressions

View File

@ -26,9 +26,9 @@ Here is a very simple template:
<p>Welcome $FirstName $Surname.</p> <p>Welcome $FirstName $Surname.</p>
<% end_with %> <% end_with %>
<% if Dishes %> <% if $Dishes %>
<ul> <ul>
<% loop Dishes %> <% loop $Dishes %>
<li>$Title ($Price.Nice)</li> <li>$Title ($Price.Nice)</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -106,7 +106,7 @@ The "include" tag can be particularly helpful for nested functionality. In this
a variable is true a variable is true
:::ss :::ss
<% if CurrentMember %> <% if $CurrentMember %>
<% include MembersOnlyInclude %> <% include MembersOnlyInclude %>
<% end_if %> <% end_if %>
@ -114,7 +114,7 @@ Includes can't directly access the parent scope of the scope active when the inc
pass arguments to the include, which are available on the scope top within the include pass arguments to the include, which are available on the scope top within the include
:::ss :::ss
<% with CurrentMember %> <% with $CurrentMember %>
<% include MemberDetails PageTitle=$Top.Title, PageID=$Top.ID %> <% include MemberDetails PageTitle=$Top.Title, PageID=$Top.ID %>
<% end_with %> <% end_with %>
@ -182,7 +182,7 @@ the markup in the `else` clause is used, if that clause is present.
This example shows the use of `not` to negate the test. This example shows the use of `not` to negate the test.
:::ss :::ss
<% if not $DinnerInOven %> <% if $not $DinnerInOven %>
I'm going out for dinner tonight. I'm going out for dinner tonight.
<% end_if %> <% end_if %>
@ -221,12 +221,18 @@ collection of items. For example:
<% end_loop %> <% end_loop %>
</ul> </ul>
This loops over the children of a page, and generates an unordered list showing 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 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 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 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. `$Up.Title`. More about `Up` later.
`$Me` can be used to refer to the current object context the template is rendered
with.
### Position Indicators ### Position Indicators
Inside the loop scope, there are many variables at your disposal to determine the Inside the loop scope, there are many variables at your disposal to determine the
@ -239,6 +245,50 @@ current position in the list and iteration:
* `$Pos`: The current position in the list (integer). Will start at 1. * `$Pos`: The current position in the list (integer). Will start at 1.
* `$TotalItems`: Number of items in the list (integer) * `$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
<ul>
<% loop $Children.Sort(Title) %>
<li>$Title</li>
<% end_loop %>
</ul>
Limiting the number of items displayed.
:::ss
<ul>
<% loop $Children.Limit(10) %>
<li>$Title</li>
<% end_loop %>
</ul>
Reversing the loop.
:::ss
<ul>
<% loop $Children.Reverse %>
<li>$Title</li>
<% end_loop %>
</ul>
The `DataList` class also supports chaining methods. For example, to reverse
the list and output the last 3 items we would write:
:::ss
<ul>
<% loop $Children.Reverse.Limit(3) %>
<li>$Title</li>
<% end_loop %>
</ul>
### Modulus and MultipleOf ### Modulus and MultipleOf
$Modulus and $MultipleOf can help to build column layouts. $Modulus and $MultipleOf can help to build column layouts.
@ -252,7 +302,7 @@ custom column names based on your loop statement. Note that this works for any
control statement (not just children). control statement (not just children).
:::ss :::ss
<% loop Children %> <% loop $Children %>
<div class="column-{$Modulus(4)}"> <div class="column-{$Modulus(4)}">
... ...
</div> </div>
@ -265,8 +315,8 @@ You can also use $MultipleOf(value, offset) to help build columned layouts. In
this case we want to add a <br> after every 3th item. this case we want to add a <br> after every 3th item.
:::ss :::ss
<% loop Children %> <% loop $Children %>
<% if MultipleOf(3) %> <% if $MultipleOf(3) %>
<br> <br>
<% end_if %> <% end_if %>
<% end_loop %> <% end_loop %>
@ -289,11 +339,11 @@ the scope back to the previous level. Take the following example:
:::ss :::ss
$Title $Title
-- --
<% loop Children %> <% loop $Children %>
$Title $Title
$Up.Title $Up.Title
-- --
<% loop Children %> <% loop $Children %>
$Title $Title
$Up.Title $Up.Title
<% end_loop %> <% end_loop %>
@ -321,12 +371,12 @@ include `$Top`:
:::ss :::ss
$Title $Title
-- --
<% loop Children %> <% loop $Children %>
$Title $Title
$Up.Title $Up.Title
$Top.Title $Top.Title
-- --
<% loop Children %> <% loop $Children %>
$Title $Title
$Up.Title $Up.Title
$Top.Title $Top.Title
@ -459,11 +509,11 @@ It renders in the template as `<base href="http://www.mydomain.com" /><!--[if lt
Returns the currently logged in member, if there is one. Returns the currently logged in member, if there is one.
All of their details or any special Member page controls can be called on this. All of their details or any special Member page controls can be called on this.
Alternately, you can use `<% if CurrentMember %>` to detect whether someone has logged Alternately, you can use `<% if $CurrentMember %>` to detect whether someone has logged
in. in.
:::ss :::ss
<% if CurrentMember %> <% if $CurrentMember %>
Welcome Back, $CurrentMember.FirstName Welcome Back, $CurrentMember.FirstName
<% end_if %> <% end_if %>
@ -498,7 +548,7 @@ Your function could return a single value as above or it could be a subclass of
And now you could call these values by using And now you could call these values by using
:::ss :::ss
<% with MyCustomValues %> <% with $MyCustomValues %>
$Hi , $Name $Hi , $Name
<% end_with %> <% end_with %>
// output "Kia Ora , John Smith" // output "Kia Ora , John Smith"

View File

@ -83,7 +83,7 @@ WARNING: Currently the UploadField doesn't fully support has_many relations, so
### Overview ### Overview
The field can either be configured on an instance level through `setConfig(<key>, <value>)`, The field can either be configured on an instance level through `setConfig(<key>, <value>)`,
or globally by overriding the YAML defaults. or globally by overriding the YAML defaults. See the [Configuration Reference](#configuration-reference) section for possible values.
Example: mysite/_config/uploadfield.yml Example: mysite/_config/uploadfield.yml

View File

@ -79,7 +79,7 @@ In templates, casting helpers are available without the need for an `obj()` call
Example: Flagging an object of type `MyObject` (see above) if it's date is in the past. Example: Flagging an object of type `MyObject` (see above) if it's date is in the past.
:::ss :::ss
<% if MyObjectInstance.MyDate.InPast %>Outdated!<% end_if %> <% if $MyObjectInstance.MyDate.InPast %>Outdated!<% end_if %>
## Casting HTML Text ## Casting HTML Text

View File

@ -419,7 +419,7 @@ but using the *obj()*-method or accessing through a template will cast the value
// $myPlayer->MembershipFee() returns a float (e.g. 123.45) // $myPlayer->MembershipFee() returns a float (e.g. 123.45)
// $myPlayer->obj('MembershipFee') returns a object of type Currency // $myPlayer->obj('MembershipFee') returns a object of type Currency
// In a template: <% loop MyPlayer %>MembershipFee.Nice<% end_loop %> returns a casted string (e.g. "$123.45") // In a template: <% loop $MyPlayer %>MembershipFee.Nice<% end_loop %> returns a casted string (e.g. "$123.45")
public function getMembershipFee() { public function getMembershipFee() {
return $this->Team()->BaseFee * $this->MembershipYears; return $this->Team()->BaseFee * $this->MembershipYears;
} }

View File

@ -264,7 +264,7 @@ basic customisation:
:::ss :::ss
<form $FormAttributes> <form $FormAttributes>
<% if Message %> <% if $Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p> <p id="{$FormName}_error" class="message $MessageType">$Message</p>
<% else %> <% else %>
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p> <p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
@ -284,9 +284,9 @@ basic customisation:
$Fields.dataFieldByName(SecurityID) $Fields.dataFieldByName(SecurityID)
</fieldset> </fieldset>
<% if Actions %> <% if $Actions %>
<div class="Actions"> <div class="Actions">
<% loop Actions %>$Field<% end_loop %> <% loop $Actions %>$Field<% end_loop %>
</div> </div>
<% end_if %> <% end_if %>
</form> </form>
@ -297,7 +297,7 @@ for the type of field. Pass in the name of the field as the first parameter, as
template. template.
To find more methods, have a look at the `[api:Form]` class and `[api:FieldList]` class as there is a lot of different To find more methods, have a look at the `[api:Form]` class and `[api:FieldList]` class as there is a lot of different
methods of customising the form templates. An example is that you could use `<% loop Fields %>` instead of specifying methods of customising the form templates. An example is that you could use `<% loop $Fields %>` instead of specifying
each field manually, as we've done above. each field manually, as we've done above.
### Custom form field templates ### Custom form field templates

View File

@ -378,7 +378,7 @@ Template:
:::ss :::ss
<ul> <ul>
<% loop Results %> <% loop $Results %>
<li id="Result-$ID">$Title</li> <li id="Result-$ID">$Title</li>
<% end_loop %> <% end_loop %>
</ul> </ul>

View File

@ -95,15 +95,15 @@ Note that pages with the `ShowInMenus` property set to FALSE will be filtered ou
### Children Loops ### Children Loops
:::ss :::ss
<% loop Children %>...<% end_loop %> <% loop $Children %>...<% end_loop %>
Will loop over all children of the current page context. Will loop over all children of the current page context.
Helpful to create page-specific subnavigations. Helpful to create page-specific subnavigations.
Most likely, you'll want to use `<% loop Menu %>` for your main menus, Most likely, you'll want to use `<% loop $Menu %>` for your main menus,
since its independent of the page context. since its independent of the page context.
:::ss :::ss
<% loop ChildrenOf(<my-page-url>) %>...<% end_loop %> <% loop $ChildrenOf(<my-page-url>) %>...<% end_loop %>
Will create a list of the children of the given page, Will create a list of the children of the given page,
as identified by its `URLSegment` value. This can come in handy because its not dependent as identified by its `URLSegment` value. This can come in handy because its not dependent
@ -111,7 +111,7 @@ on the context of the current page. For example, it would allow you to list all
underneath a "staff" holder on any page, regardless if its on the top level or elsewhere. underneath a "staff" holder on any page, regardless if its on the top level or elsewhere.
:::ss :::ss
<% loop allChildren %>...<% end_loop %> <% loop $allChildren %>...<% end_loop %>
This will show all children of a page even if the `ShowInMenus` property is set to FALSE. This will show all children of a page even if the `ShowInMenus` property is set to FALSE.
@ -137,7 +137,7 @@ To simply retrieve the parent page of the current context (if existing), use the
### Access to a specific Page ### Access to a specific Page
:::ss :::ss
<% loop Page(my-page) %>...<% end_loop %>` <% with $Page(my-page) %>...<% end_with %>`
"Page" will return a single page from the site tree, looking it up by URL. You can use it in the `<% loop %>` format. "Page" will return a single page from the site tree, looking it up by URL. You can use it in the `<% loop %>` format.
Can't be called using `$Page(my-page).Title`. Can't be called using `$Page(my-page).Title`.
@ -180,7 +180,7 @@ More common uses:
Example: Only show the menu item linked if its the current one: Example: Only show the menu item linked if its the current one:
:::ss :::ss
<% if LinkOrCurrent = current %> <% if $LinkOrCurrent = current %>
$Title $Title
<% else %> <% else %>
<a href="$Link">$Title</a> <a href="$Link">$Title</a>
@ -199,9 +199,9 @@ Simply place a file with the same name in your `themes/<your-theme>/templates`
folder to customize its output. Here's the default template: folder to customize its output. Here's the default template:
:::ss :::ss
<% if Pages %> <% if $Pages %>
<% loop Pages %> <% loop $Pages %>
<% if Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> &raquo;<% end_if %> <% if $Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> &raquo;<% end_if %>
<% end_loop %> <% end_loop %>
<% end_if %> <% end_if %>
@ -282,4 +282,4 @@ we recommend that you dive into the API docs for the following classes:
* `[api:ContentController]`: The main controller responsible for handling pages * `[api:ContentController]`: The main controller responsible for handling pages
* `[api:Controller]`: Generic controller (not specific to pages) * `[api:Controller]`: Generic controller (not specific to pages)
* `[api:DataObject]`: Underlying model class for page objects * `[api:DataObject]`: Underlying model class for page objects
* `[api:ViewableData]`: Underlying object class for pretty much anything displayable * `[api:ViewableData]`: Underlying object class for pretty much anything displayable

View File

@ -253,7 +253,7 @@ of the CMS you have to take care of instanciation yourself:
:::ss :::ss
// File: mysite/templates/MyController.ss // File: mysite/templates/MyController.ss
$Form $Form
<% with EditorToolbar %> <% with $EditorToolbar %>
$MediaForm $MediaForm
$LinkForm $LinkForm
<% end_with %> <% end_with %>

View File

@ -72,9 +72,9 @@ our theme in action. The code for mine is below.
</div> </div>
<div id="Navigation"> <div id="Navigation">
<% if Menu(1) %> <% if $Menu(1) %>
<ul> <ul>
<% loop Menu(1) %> <% loop $Menu(1) %>
<li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li> <li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -182,9 +182,9 @@ Next is a division for the main navigation. This may contain something like:
:::ss :::ss
<div id="Navigation"> <div id="Navigation">
<% if Menu(1) %> <% if $Menu(1) %>
<ul> <ul>
<% loop Menu(1) %> <% loop $Menu(1) %>
<li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li> <li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -198,8 +198,8 @@ Before stepping into a loop it's good practise to check if it exists first. This
important in manipulating SilverStripe templates, but in any programming language! important in manipulating SilverStripe templates, but in any programming language!
:::ss :::ss
<% if MyFunction %> <% if $MyFunction %>
<% loop MyFunction %> <% loop $MyFunction %>
$Title $Title
<% end_loop %> <% end_loop %>
<% end_if %> <% end_if %>

View File

@ -158,7 +158,7 @@ Open up *themes/simple/templates/Includes/Navigation.ss*
The Menu for our site is created using a **loop**. Loops allow us to iterate over a data set, and render each item using a sub-template. The Menu for our site is created using a **loop**. Loops allow us to iterate over a data set, and render each item using a sub-template.
:::ss :::ss
<% loop Menu(1) %> <% loop $Menu(1) %>
returns a set of first level menu items. We can then use the template variable returns a set of first level menu items. We can then use the template variable
*$MenuTitle* to show the title of the page we are linking to, *$Link* for the URL of the page and *$LinkingMode* to help style our menu with CSS (explained in more detail shortly). *$MenuTitle* to show the title of the page we are linking to, *$Link* for the URL of the page and *$LinkingMode* to help style our menu with CSS (explained in more detail shortly).
@ -168,7 +168,7 @@ returns a set of first level menu items. We can then use the template variable
:::ss :::ss
<ul> <ul>
<% loop Menu(1) %> <% loop $Menu(1) %>
<li class="$LinkingMode"> <li class="$LinkingMode">
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a> <a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
</li> </li>
@ -225,7 +225,7 @@ Adding a second level menu is very similar to adding the first level menu. Open
:::ss :::ss
<ul> <ul>
<% loop Menu(2) %> <% loop $Menu(2) %>
<li class="$LinkingMode"> <li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page"> <a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span> <span class="arrow">&rarr;</span>
@ -245,10 +245,10 @@ Look again in the *Sidebar.ss* file and you will see that the menu is surrounded
like this: like this:
:::ss :::ss
<% if Menu(2) %> <% if $Menu(2) %>
... ...
<ul> <ul>
<% loop Menu(2) %> <% loop $Menu(2) %>
<li class="$LinkingMode"> <li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page"> <a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span> <span class="arrow">&rarr;</span>
@ -269,7 +269,7 @@ Now that we have two levels of navigation, it would also be useful to include so
Open up */themes/simple/templates/Includes/BreadCrumbs.ss* template and look at the following code: Open up */themes/simple/templates/Includes/BreadCrumbs.ss* template and look at the following code:
:::ss :::ss
<% if Level(2) %> <% if $Level(2) %>
<div id="Breadcrumbs"> <div id="Breadcrumbs">
$Breadcrumbs $Breadcrumbs
</div> </div>
@ -295,12 +295,12 @@ The following example runs an if statement, and a loop on *Children*, checking t
:::ss :::ss
<ul> <ul>
<% loop Menu(1) %> <% loop $Menu(1) %>
<li class="$LinkingMode"> <li class="$LinkingMode">
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a> <a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
<% if Children %> <% if $Children %>
<ul> <ul>
<% loop Children %> <% loop $Children %>
<li class="$LinkingMode"> <li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page"> <a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span> <span class="arrow">&rarr;</span>

View File

@ -285,7 +285,7 @@ We'll now create a template for the article holder. We want our news section to
$Content $Content
<div class="content">$Content</div> <div class="content">$Content</div>
</article> </article>
<% loop Children %> <% loop $Children %>
<article> <article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2> <h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
<p>$Content.FirstParagraph</p> <p>$Content.FirstParagraph</p>
@ -313,7 +313,7 @@ Cut the code between "loop Children" in *ArticleHolder.ss** and replace it with
:::ss :::ss
... ...
<% loop Children %> <% loop $Children %>
<% include ArticleTeaser %> <% include ArticleTeaser %>
<% end_loop %> <% end_loop %>
... ...
@ -370,7 +370,7 @@ This function simply runs a database query that gets the latest news articles fr
<!-- ... --> <!-- ... -->
<div class="content">$Content</div> <div class="content">$Content</div>
</article> </article>
<% loop LatestNews %> <% loop $LatestNews %>
<% include ArticleTeaser %> <% include ArticleTeaser %>
<% end_loop %> <% end_loop %>
@ -486,7 +486,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit
$Content $Content
<div class="content">$Content</div> <div class="content">$Content</div>
</article> </article>
<% loop Children %> <% loop $Children %>
<article> <article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2> <h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
$Photo.SetWidth(150) $Photo.SetWidth(150)

View File

@ -325,11 +325,11 @@ The final step is to create the template to display our data. Change the 'Browse
:::ss :::ss
<div id="BrowserPoll"> <div id="BrowserPoll">
<h2>Browser Poll</h2> <h2>Browser Poll</h2>
<% if BrowserPollForm %> <% if $BrowserPollForm %>
$BrowserPollForm $BrowserPollForm
<% else %> <% else %>
<ul> <ul>
<% loop BrowserPollResults %> <% loop $BrowserPollResults %>
<li> <li>
<div class="browser">$Browser: $Percentage%</div> <div class="browser">$Browser: $Percentage%</div>
<div class="bar" style="width:$Percentage%">&nbsp;</div> <div class="bar" style="width:$Percentage%">&nbsp;</div>

View File

@ -33,7 +33,7 @@ To add the search form, we can add `$SearchForm` anywhere in our templates. In t
:::ss :::ss
... ...
<% if SearchForm %> <% if $SearchForm %>
<span class="search-dropdown-icon">L</span> <span class="search-dropdown-icon">L</span>
<div class="search-bar"> <div class="search-bar">
$SearchForm $SearchForm
@ -98,16 +98,16 @@ class.
<div id="Content" class="searchResults"> <div id="Content" class="searchResults">
<h1>$Title</h1> <h1>$Title</h1>
<% if Query %> <% if $Query %>
<p class="searchQuery"><strong>You searched for &quot;{$Query}&quot;</strong></p> <p class="searchQuery"><strong>You searched for &quot;{$Query}&quot;</strong></p>
<% end_if %> <% end_if %>
<% if Results %> <% if $Results %>
<ul id="SearchResults"> <ul id="SearchResults">
<% loop Results %> <% loop $Results %>
<li> <li>
<a class="searchResultHeader" href="$Link"> <a class="searchResultHeader" href="$Link">
<% if MenuTitle %> <% if $MenuTitle %>
$MenuTitle $MenuTitle
<% else %> <% else %>
$Title $Title
@ -124,17 +124,17 @@ class.
<p>Sorry, your search query did not return any results.</p> <p>Sorry, your search query did not return any results.</p>
<% end_if %> <% end_if %>
<% if Results.MoreThanOnePage %> <% if $Results.MoreThanOnePage %>
<div id="PageNumbers"> <div id="PageNumbers">
<% if Results.NotLastPage %> <% if $Results.NotLastPage %>
<a class="next" href="$Results.NextLink" title="View the next page">Next</a> <a class="next" href="$Results.NextLink" title="View the next page">Next</a>
<% end_if %> <% end_if %>
<% if Results.NotFirstPage %> <% if $Results.NotFirstPage %>
<a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a> <a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a>
<% end_if %> <% end_if %>
<span> <span>
<% loop Results.Pages %> <% loop $Results.Pages %>
<% if CurrentBool %> <% if $CurrentBool %>
$PageNum $PageNum
<% else %> <% else %>
<a href="$Link" title="View page number $PageNum">$PageNum</a> <a href="$Link" title="View page number $PageNum">$PageNum</a>

View File

@ -293,19 +293,19 @@ a named list of object.
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% loop Children %> <% loop $Children %>
<tr> <tr>
<td> <td>
<a href="$Link">$Title</a> <a href="$Link">$Title</a>
</td> </td>
<td> <td>
<% loop Students %> <% loop $Students %>
$Name ($University)<% if Last !=1 %>,<% end_if %> $Name ($University)<% if $Last !=1 %>,<% end_if %>
<% end_loop %> <% end_loop %>
</td> </td>
<td> <td>
<% loop Mentor %> <% loop $Mentor %>
$Name<% if Last !=1 %>,<% end_if %> $Name<% if $Last !=1 %>,<% end_if %>
<% end_loop %> <% end_loop %>
</td> </td>
</tr> </tr>
@ -343,9 +343,9 @@ we can access the "Students" and "Mentors" relationships directly in the templat
<div class="content"> <div class="content">
$Content $Content
<h2>Students</h2> <h2>Students</h2>
<% if Students %> <% if $Students %>
<ul> <ul>
<% loop Students %> <% loop $Students %>
<li>$Name ($University)</li> <li>$Name ($University)</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -353,9 +353,9 @@ we can access the "Students" and "Mentors" relationships directly in the templat
<p>No students found</p> <p>No students found</p>
<% end_if %> <% end_if %>
<h2>Mentors</h2> <h2>Mentors</h2>
<% if Mentors %> <% if $Mentors %>
<ul> <ul>
<% loop Mentors %> <% loop $Mentors %>
<li>$Name</li> <li>$Name</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
@ -392,7 +392,7 @@ To use this template, we need to add a new method to our student class:
Replace the student template code in both `Project.ss` Replace the student template code in both `Project.ss`
and `ProjectHolder.ss` templates with the new placeholder, `$Info`. and `ProjectHolder.ss` templates with the new placeholder, `$Info`.
That's the code enclosed in `<% loop Students %>` and `<% end_loop %>`. That's the code enclosed in `<% loop $Students %>` and `<% end_loop %>`.
With this pattern, you can increase code reuse across templates. With this pattern, you can increase code reuse across templates.
## Summary ## Summary

View File

@ -936,7 +936,7 @@ class File extends DataObject {
if(!is_array($exts)) $exts = array($exts); if(!is_array($exts)) $exts = array($exts);
foreach($exts as $ext) { foreach($exts as $ext) {
if(!is_subclass_of($ext, 'File')) { if(!is_subclass_of($class, 'File')) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
sprintf('Class "%s" (for extension "%s") is not a valid subclass of File', $class, $ext) sprintf('Class "%s" (for extension "%s") is not a valid subclass of File', $class, $ext)
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 B

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 372 B

View File

@ -749,6 +749,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $this->record; 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. * Update a number of fields on this object, given a map of the desired changes.
* *

View File

@ -118,7 +118,7 @@ interface CompositeDBField {
* parameter. * parameter.
* *
* @param DBField|array $value * @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. * @param boolean $markChanged Indicate wether this field should be marked changed.
* Set to FALSE if you are initializing this field after construction, rather * Set to FALSE if you are initializing this field after construction, rather
* than setting a new value. * than setting a new value.

View File

@ -103,6 +103,11 @@ class Money extends DBField implements CompositeDBField {
} }
public function setValue($value, $record = null, $markChanged = true) { 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 // @todo Allow resetting value to NULL through Money $value field
if ($value instanceof Money && $value->exists()) { if ($value instanceof Money && $value->exists()) {
$this->setCurrency($value->getCurrency(), $markChanged); $this->setCurrency($value->getCurrency(), $markChanged);

View File

@ -17,6 +17,7 @@ class MoneyTest extends SapphireTest {
protected $extraDataObjects = array( protected $extraDataObjects = array(
'MoneyTest_DataObject', 'MoneyTest_DataObject',
'MoneyTest_SubClass',
); );
public function testMoneyFieldsReturnedAsObjects() { public function testMoneyFieldsReturnedAsObjects() {
@ -268,6 +269,15 @@ class MoneyTest extends SapphireTest {
))->value() ))->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 { 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',
);
}

View File

@ -1,4 +1,8 @@
MoneyTest_DataObject: MoneyTest_DataObject:
test1: test1:
MyMoneyCurrency: EUR MyMoneyCurrency: EUR
MyMoneyAmount: 1.23 MyMoneyAmount: 1.23
MoneyTest_SubClass:
test2:
MyOtherMoneyCurrency: GBP
MyOtherMoneyAmount: 2.46

View File

@ -371,7 +371,7 @@ class ViewableData extends Object implements IteratorAggregate {
} }
$valueObject = Object::create_from_string($castConstructor, $fieldName); $valueObject = Object::create_from_string($castConstructor, $fieldName);
$valueObject->setValue($value, ($this->hasMethod('toMap') ? $this->toMap() : null)); $valueObject->setValue($value, $this);
$value = $valueObject; $value = $valueObject;
} }