Merge branch '3.0' into 3.1

This commit is contained in:
Andrew Short 2013-03-19 22:27:09 +11:00
commit b8a51c3792
10 changed files with 312 additions and 38 deletions

View File

@ -22,6 +22,7 @@ matrix:
- php: 5.4 - php: 5.4
env: env:
- PHPCS=1 - PHPCS=1
- env: TESTDB=SQLITE
before_script: before_script:
- pear install pear/PHP_CodeSniffer - pear install pear/PHP_CodeSniffer

View File

@ -691,7 +691,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
* @return String Nested unordered list with links to each page * @return String Nested unordered list with links to each page
*/ */
public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null,
$filterFunction = null, $minNodeCount = 30) { $filterFunction = null, $nodeCountThreshold = 30) {
// Filter criteria // Filter criteria
$params = $this->request->getVar('q'); $params = $this->request->getVar('q');
@ -719,7 +719,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
// Mark the nodes of the tree to return // Mark the nodes of the tree to return
if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction); if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction);
$obj->markPartialTree($minNodeCount, $this, $childrenMethod, $numChildrenMethod); $obj->markPartialTree($nodeCountThreshold, $this, $childrenMethod, $numChildrenMethod);
// Ensure current page is exposed // Ensure current page is exposed
if($p = $this->currentPage()) $obj->markToExpose($p); if($p = $this->currentPage()) $obj->markToExpose($p);
@ -744,7 +744,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
true, true,
$childrenMethod, $childrenMethod,
$numChildrenMethod, $numChildrenMethod,
$minNodeCount $nodeCountThreshold
); );
// Wrap the root if needs be. // Wrap the root if needs be.

View File

@ -119,6 +119,7 @@ if(!isset($_SERVER['HTTP_HOST'])) {
if($_REQUEST) stripslashes_recursively($_REQUEST); if($_REQUEST) stripslashes_recursively($_REQUEST);
if($_GET) stripslashes_recursively($_GET); if($_GET) stripslashes_recursively($_GET);
if($_POST) stripslashes_recursively($_POST); if($_POST) stripslashes_recursively($_POST);
if($_COOKIE) stripslashes_recursively($_COOKIE);
} }
/** /**

View File

@ -9,7 +9,7 @@ implementation. Have a look at `[api:Object->useCustomClass()]`.
## Usage ## Usage
Your extension will nee to be a subclass of `[api:DataExtension]` or the `[api:Extension]` class. Your extension will need to be a subclass of `[api:DataExtension]` or the `[api:Extension]` class.
:::php :::php
<?php <?php
@ -155,4 +155,4 @@ extended by.
## API Documentation ## API Documentation
`[api:DataExtension]` `[api:DataExtension]`

View File

@ -9,23 +9,23 @@ This is a highlevel overview of available `[api:FormField]` subclasses. An autom
* `[api:ReadonlyField]`: Read-only field to display a non-editable value with a label. * `[api:ReadonlyField]`: Read-only field to display a non-editable value with a label.
* `[api:TextareaField]`: Multi-line text field. * `[api:TextareaField]`: Multi-line text field.
* `[api:TextField]`: Single-line text field. * `[api:TextField]`: Single-line text field.
* `[api:PasswordField]`: Masked input field * `[api:PasswordField]`: Masked input field.
## Actions ## Actions
* `[api:FormAction]`: Button element for forms, both for `<input type="submit">` and `<button>`. * `[api:FormAction]`: Button element for forms, both for `<input type="submit">` and `<button>`.
* `[api:ResetFormAction]`: Action that clears all fields on a form. * `[api:ResetFormAction]`: Action that clears all fields on a form.
## Formatted Input ## Formatted Input
* `[api:AjaxUniqueTextField]`: Text field that automatically checks that the value entered is unique for the given set of fields in a given set of tables * `[api:AjaxUniqueTextField]`: Text field that automatically checks that the value entered is unique for the given set of fields in a given set of tables.
* `[api:ConfirmedPasswordField]`: Two masked input fields, checks for matching passwords. * `[api:ConfirmedPasswordField]`: Two masked input fields, checks for matching passwords.
* `[api:CountryDropdownField]`: A simple extension to dropdown field, pre-configured to list countries. * `[api:CountryDropdownField]`: A simple extension to dropdown field, pre-configured to list countries.
* `[api:CreditCardField]`: Allows input of credit card numbers via four separate form fields, including generic validation of its numeric values. * `[api:CreditCardField]`: Allows input of credit card numbers via four separate form fields, including generic validation of its numeric values.
* `[api:CurrencyField]`: Text field, validating its input as a currency. Limited to US-centric formats, including a hardcoded currency symbol and decimal separators. * `[api:CurrencyField]`: Text field, validating its input as a currency. Limited to US-centric formats, including a hardcoded currency symbol and decimal separators.
See `[api:MoneyField]` for a more flexible implementation. See `[api:MoneyField]` for a more flexible implementation.
* `[api:DateField]`: Represents a date in a single input field, or separated into day, month, and year. Can optionally use a calendar popup. * `[api:DateField]`: Represents a date in a single input field, or separated into day, month, and year. Can optionally use a calendar popup.
* `[api:DatetimeField]`: Combined date- and time field * `[api:DatetimeField]`: Combined date- and time field.
* `[api:EmailField]`: Text input field with validation for correct email format according to RFC 2822. * `[api:EmailField]`: Text input field with validation for correct email format according to RFC 2822.
* `[api:GroupedDropdownField]`: Grouped dropdown, using <optgroup> tags. * `[api:GroupedDropdownField]`: Grouped dropdown, using <optgroup> tags.
* `[api:HTMLEditorField]. * `[api:HTMLEditorField].
@ -43,7 +43,7 @@ doesn't necessarily have any visible styling.
* `[api:FieldGroup] attached in CMS-context. * `[api:FieldGroup] attached in CMS-context.
* `[api:FieldList]`: Basic container for sequential fields, or nested fields through CompositeField. * `[api:FieldList]`: Basic container for sequential fields, or nested fields through CompositeField.
* `[api:TabSet]`: Collection of fields which is rendered as separate tabs. Can be nested. * `[api:TabSet]`: Collection of fields which is rendered as separate tabs. Can be nested.
* `[api:Tab]`: A single tab inside a `TabSet` * `[api:Tab]`: A single tab inside a `TabSet`.
* `[api:ToggleCompositeField]`: Allows visibility of a group of fields to be toggled. * `[api:ToggleCompositeField]`: Allows visibility of a group of fields to be toggled.
* `[api:ToggleField]`: ReadonlyField with added toggle-capabilities - will preview the first sentence of the contained text-value, and show the full content by a javascript-switch. * `[api:ToggleField]`: ReadonlyField with added toggle-capabilities - will preview the first sentence of the contained text-value, and show the full content by a javascript-switch.
@ -58,7 +58,7 @@ doesn't necessarily have any visible styling.
* `[api:TableField]`: In-place editing of tabular data. * `[api:TableField]`: In-place editing of tabular data.
* `[api:TreeDropdownField]`: Dropdown-like field that allows you to select an item from a hierarchical AJAX-expandable tree. * `[api:TreeDropdownField]`: Dropdown-like field that allows you to select an item from a hierarchical AJAX-expandable tree.
* `[api:TreeMultiselectField]`: Represents many-many joins using a tree selector shown in a dropdown-like element * `[api:TreeMultiselectField]`: Represents many-many joins using a tree selector shown in a dropdown-like element
* `[api:GridField](/reference/grid-field)`: Displays a `[api:SS_List]` in a tabular format. Versatile base class which can be configured to allow editing, sorting, etc. * `[api:GridField]`: Displays a `[api:SS_List]` in a tabular format. Versatile base class which can be configured to allow editing, sorting, etc.
* `[api:ListboxField]`: Multi-line listbox field, through `<select multiple>`. * `[api:ListboxField]`: Multi-line listbox field, through `<select multiple>`.
@ -67,6 +67,6 @@ doesn't necessarily have any visible styling.
* `[api:DatalessField]` - Base class for fields which add some HTML to the form but don't submit any data or * `[api:DatalessField]` - Base class for fields which add some HTML to the form but don't submit any data or
save it to the database save it to the database
* `[api:HeaderField]`: Renders a simple HTML header element. * `[api:HeaderField]`: Renders a simple HTML header element.
* `[api:HiddenField]` * `[api:HiddenField]`.
* `[api:LabelField]`: Simple label tag. This can be used to add extra text in your forms. * `[api:LabelField]`: Simple label tag. This can be used to add extra text in your forms.
* `[api:LiteralField]`: Renders arbitrary HTML into a form. * `[api:LiteralField]`: Renders arbitrary HTML into a form.

View File

@ -54,5 +54,4 @@ adherence to conventions, writing documentation, and releasing updates. See [con
* [Modules](modules) * [Modules](modules)
* [Module Release Process](module-release-process) * [Module Release Process](module-release-process)
* [Debugging methods](/topics/debugging) * [Debugging methods](/topics/debugging)
* [URL Variable Tools](/reference/urlvariabletools) - Lists a number of <20><><EFBFBD>page options<6E><73><EFBFBD> , <20><><EFBFBD>rendering tools<6C><73><EFBFBD> or <20><><EFBFBD>special * [URL Variable Tools](/reference/urlvariabletools) - Lists a number of page options, rendering tools or special URL variables that you can use to debug your SilverStripe applications
URL variables<65><73><EFBFBD> that you can use to debug your SilverStripe applications

View File

@ -245,7 +245,8 @@ First, the template for displaying a single article:
:::ss :::ss
<div class="content-container"> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
<div class="news-details"> <div class="news-details">
@ -255,7 +256,6 @@ First, the template for displaying a single article:
</article> </article>
$Form $Form
</div> </div>
<% include SideBar %>
Most of the code is just like the regular Page.ss, we include an informational div with the date and the author of the Article. Most of the code is just like the regular Page.ss, we include an informational div with the date and the author of the Article.
@ -278,7 +278,8 @@ We'll now create a template for the article holder. We want our news section to
**themes/simple/templates/Layout/ArticleHolder.ss** **themes/simple/templates/Layout/ArticleHolder.ss**
:::ss :::ss
<div class="content-container"> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
$Content $Content
@ -293,7 +294,6 @@ We'll now create a template for the article holder. We want our news section to
<% end_loop %> <% end_loop %>
$Form $Form
</div> </div>
<% include SideBar %>
Here we use the page control *Children*. As the name suggests, this control allows you to iterate over the children of a page. In this case, the children are our news articles. The *$Link* variable will give the address of the article which we can use to create a link, and the *FirstParagraph* function of the `[api:HTMLText]` field gives us a nice summary of the article. The function strips all tags from the paragraph extracted. Here we use the page control *Children*. As the name suggests, this control allows you to iterate over the children of a page. In this case, the children are our news articles. The *$Link* variable will give the address of the article which we can use to create a link, and the *FirstParagraph* function of the `[api:HTMLText]` field gives us a nice summary of the article. The function strips all tags from the paragraph extracted.
@ -482,7 +482,8 @@ The staff section templates aren't too difficult to create, thanks to the utilit
**themes/simple/templates/Layout/StaffHolder.ss** **themes/simple/templates/Layout/StaffHolder.ss**
:::ss :::ss
<div class="content-container"> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
$Content $Content
@ -498,7 +499,6 @@ The staff section templates aren't too difficult to create, thanks to the utilit
<% end_loop %> <% end_loop %>
$Form $Form
</div> </div>
<% include SideBar %>
This template is very similar to the *ArticleHolder* template. The *SetWidth* method of the `[api:Image]` class This template is very similar to the *ArticleHolder* template. The *SetWidth* method of the `[api:Image]` class
@ -512,7 +512,8 @@ The *StaffPage* template is also very straight forward.
**themes/simple/templates/Layout/StaffPage.ss** **themes/simple/templates/Layout/StaffPage.ss**
:::ss :::ss
<div class="content-container"> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
<div class="content"> <div class="content">
@ -521,7 +522,6 @@ The *StaffPage* template is also very straight forward.
</article> </article>
$Form $Form
</div> </div>
<% include SideBar %>
Here we use the *SetWidth* method to get a different sized image from the same source image. You should now have Here we use the *SetWidth* method to get a different sized image from the same source image. You should now have
a complete staff section. a complete staff section.

View File

@ -278,7 +278,8 @@ a named list of object.
**themes/simple/templates/Layout/ProjectsHolder.ss** **themes/simple/templates/Layout/ProjectsHolder.ss**
:::ss :::ss
<div class="content-container typography"> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
<div class="content"> <div class="content">
@ -314,7 +315,6 @@ a named list of object.
</div> </div>
</article> </article>
</div> </div>
<% include SideBar %>
Navigate to the holder page through your website navigation, Navigate to the holder page through your website navigation,
or the "Preview" feature in the CMS. You should see a list of all projects now. or the "Preview" feature in the CMS. You should see a list of all projects now.
@ -336,7 +336,8 @@ we can access the "Students" and "Mentors" relationships directly in the templat
**themes/simple/templates/Layout/Project.ss** **themes/simple/templates/Layout/Project.ss**
:::ss :::ss
<div class="content-container typography"> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
<div class="content"> <div class="content">
@ -364,7 +365,6 @@ we can access the "Students" and "Mentors" relationships directly in the templat
</div> </div>
</article> </article>
</div> </div>
<% include SideBar %>
Follow the link to a project detail from from your holder page, Follow the link to a project detail from from your holder page,
or navigate to it through the submenu provided by the theme. or navigate to it through the submenu provided by the theme.

View File

@ -74,12 +74,17 @@ class Hierarchy extends DataExtension {
* @param string $childrenMethod The name of the method used to get children from each object * @param string $childrenMethod The name of the method used to get children from each object
* @param boolean $rootCall Set to true for this first call, and then to false for calls inside the recursion. You * @param boolean $rootCall Set to true for this first call, and then to false for calls inside the recursion. You
* should not change this. * should not change this.
* @param int $minNodeCount * @param int $nodeCountThreshold The lower bounds for the amount of nodes to mark. If set, the logic will expand
* nodes until it eaches at least this number, and then stops. Root nodes will always
* show regardless of this settting. Further nodes can be lazy-loaded via ajax.
* This isn't a hard limit. Example: On a value of 10, with 20 root nodes, each having
* 30 children, the actual node count will be 50 (all root nodes plus first expanded child).
*
* @return string * @return string
*/ */
public function getChildrenAsUL($attributes = "", $titleEval = '"<li>" . $child->Title', $extraArg = null, public function getChildrenAsUL($attributes = "", $titleEval = '"<li>" . $child->Title', $extraArg = null,
$limitToMarked = false, $childrenMethod = "AllChildrenIncludingDeleted", $limitToMarked = false, $childrenMethod = "AllChildrenIncludingDeleted",
$numChildrenMethod = "numChildren", $rootCall = true, $minNodeCount = 30) { $numChildrenMethod = "numChildren", $rootCall = true, $nodeCountThreshold = 30) {
if($limitToMarked && $rootCall) { if($limitToMarked && $rootCall) {
$this->markingFinished($numChildrenMethod); $this->markingFinished($numChildrenMethod);
@ -103,9 +108,25 @@ class Hierarchy extends DataExtension {
if(!$limitToMarked || $child->isMarked()) { if(!$limitToMarked || $child->isMarked()) {
$foundAChild = true; $foundAChild = true;
$output .= (is_callable($titleEval)) ? $titleEval($child) : eval("return $titleEval;"); $output .= (is_callable($titleEval)) ? $titleEval($child) : eval("return $titleEval;");
$output .= "\n" . $output .= "\n";
$child->getChildrenAsUL("", $titleEval, $extraArg, $limitToMarked, $childrenMethod,
$numChildrenMethod, false, $minNodeCount) . "</li>\n";
$numChildren = $child->$numChildrenMethod();
if(
// Always traverse into opened nodes (they might be exposed as parents of search results)
$child->isExpanded()
// Only traverse into children if we haven't reached the maximum node count already.
// Otherwise, the remaining nodes are lazy loaded via ajax.
&& $child->isMarked()
) {
$output .= $child->getChildrenAsUL("", $titleEval, $extraArg, $limitToMarked, $childrenMethod,
$numChildrenMethod, false, $nodeCountThreshold);
}
elseif($child->isTreeOpened()) {
// Since we're not loading children, don't mark it as open either
$child->markClosed();
}
$output .= "</li>\n";
} }
} }
@ -125,21 +146,23 @@ class Hierarchy extends DataExtension {
* This method returns the number of nodes marked. After this method is called other methods * This method returns the number of nodes marked. After this method is called other methods
* can check isExpanded() and isMarked() on individual nodes. * can check isExpanded() and isMarked() on individual nodes.
* *
* @param int $minNodeCount The minimum amount of nodes to mark. * @param int $nodeCountThreshold See {@link getChildrenAsUL()}
* @return int The actual number of nodes marked. * @return int The actual number of nodes marked.
*/ */
public function markPartialTree($minNodeCount = 30, $context = null, public function markPartialTree($nodeCountThreshold = 30, $context = null,
$childrenMethod = "AllChildrenIncludingDeleted", $numChildrenMethod = "numChildren") { $childrenMethod = "AllChildrenIncludingDeleted", $numChildrenMethod = "numChildren") {
if(!is_numeric($minNodeCount)) $minNodeCount = 30; if(!is_numeric($nodeCountThreshold)) $nodeCountThreshold = 30;
$this->markedNodes = array($this->owner->ID => $this->owner); $this->markedNodes = array($this->owner->ID => $this->owner);
$this->owner->markUnexpanded(); $this->owner->markUnexpanded();
// foreach can't handle an ever-growing $nodes list // foreach can't handle an ever-growing $nodes list
while(list($id, $node) = each($this->markedNodes)) { while(list($id, $node) = each($this->markedNodes)) {
$this->markChildren($node, $context, $childrenMethod, $numChildrenMethod); $children = $this->markChildren($node, $context, $childrenMethod, $numChildrenMethod);
if($minNodeCount && sizeof($this->markedNodes) >= $minNodeCount) { if($nodeCountThreshold && sizeof($this->markedNodes) > $nodeCountThreshold) {
// Undo marking children as opened since they're lazy loaded
if($children) foreach($children as $child) $child->markClosed();
break; break;
} }
} }
@ -200,6 +223,7 @@ class Hierarchy extends DataExtension {
/** /**
* Mark all children of the given node that match the marking filter. * Mark all children of the given node that match the marking filter.
* @param DataObject $node Parent node. * @param DataObject $node Parent node.
* @return DataList
*/ */
public function markChildren($node, $context = null, $childrenMethod = "AllChildrenIncludingDeleted", public function markChildren($node, $context = null, $childrenMethod = "AllChildrenIncludingDeleted",
$numChildrenMethod = "numChildren") { $numChildrenMethod = "numChildren") {
@ -213,7 +237,13 @@ class Hierarchy extends DataExtension {
$node->markExpanded(); $node->markExpanded();
if($children) { if($children) {
foreach($children as $child) { foreach($children as $child) {
if(!$this->markingFilter || $this->markingFilterMatches($child)) { $markingMatches = $this->markingFilterMatches($child);
// Filtered results should always show opened, since actual matches
// might be hidden by non-matching parent nodes.
if($this->markingFilter && $markingMatches) {
$child->markOpened();
}
if(!$this->markingFilter || $markingMatches) {
if($child->$numChildrenMethod()) { if($child->$numChildrenMethod()) {
$child->markUnexpanded(); $child->markUnexpanded();
} else { } else {
@ -223,6 +253,8 @@ class Hierarchy extends DataExtension {
} }
} }
} }
return $children;
} }
/** /**
@ -350,6 +382,15 @@ class Hierarchy extends DataExtension {
self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true; self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true; self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
} }
/**
* Mark this DataObject's tree as closed.
*/
public function markClosed() {
if(isset(self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID])) {
unset(self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID]);
}
}
/** /**
* Check if this DataObject is marked. * Check if this DataObject is marked.

View File

@ -180,6 +180,238 @@ class HierarchyTest extends SapphireTest {
$this->assertEquals('Obj 2 &raquo; Obj 2a &raquo; Obj 2aa', $obj2aa->getBreadcrumbs()); $this->assertEquals('Obj 2 &raquo; Obj 2a &raquo; Obj 2aa', $obj2aa->getBreadcrumbs());
} }
public function testGetChildrenAsUL() {
$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
$nodeCountThreshold = 30;
$root = new HierarchyTest_Object();
$root->markPartialTree($nodeCountThreshold);
$html = $root->getChildrenAsUL(
"",
'"<li id=\"" . $child->ID . "\">" . $child->Title',
null,
false,
"AllChildrenIncludingDeleted",
"numChildren",
true, // rootCall
$nodeCountThreshold
);
$parser = new CSSContentParser($html);
$node2 = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]'
);
$this->assertTrue(
(bool)$node2,
'Contains root elements'
);
$node2a = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]' .
'/ul/li[@id="' . $obj2a->ID . '"]'
);
$this->assertTrue(
(bool)$node2a,
'Contains child elements (in correct nesting)'
);
$node2aa = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]' .
'/ul/li[@id="' . $obj2a->ID . '"]' .
'/ul/li[@id="' . $obj2aa->ID . '"]'
);
$this->assertTrue(
(bool)$node2aa,
'Contains grandchild elements (in correct nesting)'
);
}
public function testGetChildrenAsULMinNodeCount() {
$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
// Set low enough that it should be fulfilled by root only elements
$nodeCountThreshold = 3;
$root = new HierarchyTest_Object();
$root->markPartialTree($nodeCountThreshold);
$html = $root->getChildrenAsUL(
"",
'"<li id=\"" . $child->ID . "\">" . $child->Title',
null,
false,
"AllChildrenIncludingDeleted",
"numChildren",
true,
$nodeCountThreshold
);
$parser = new CSSContentParser($html);
$node1 = $parser->getByXpath(
'//ul/li[@id="' . $obj1->ID . '"]'
);
$this->assertTrue(
(bool)$node1,
'Contains root elements'
);
$node2 = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]'
);
$this->assertTrue(
(bool)$node2,
'Contains root elements'
);
$node2a = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]' .
'/ul/li[@id="' . $obj2a->ID . '"]'
);
$this->assertFalse(
(bool)$node2a,
'Does not contains child elements because they exceed minNodeCount'
);
}
public function testGetChildrenAsULMinNodeCountWithMarkToExpose() {
$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
// Set low enough that it should be fulfilled by root only elements
$nodeCountThreshold = 3;
$root = new HierarchyTest_Object();
$root->markPartialTree($nodeCountThreshold);
// Mark certain node which should be included regardless of minNodeCount restrictions
$root->markToExpose($obj2aa);
$html = $root->getChildrenAsUL(
"",
'"<li id=\"" . $child->ID . "\">" . $child->Title',
null,
false,
"AllChildrenIncludingDeleted",
"numChildren",
true,
$nodeCountThreshold
);
$parser = new CSSContentParser($html);
$node2 = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]'
);
$this->assertTrue(
(bool)$node2,
'Contains root elements'
);
$node2aa = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]' .
'/ul/li[@id="' . $obj2a->ID . '"]' .
'/ul/li[@id="' . $obj2aa->ID . '"]'
);
$this->assertTrue((bool)$node2aa);
}
public function testGetChildrenAsULMinNodeCountWithFilters() {
$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
// Set low enough that it should fit all search matches without lazy loading
$nodeCountThreshold = 3;
$root = new HierarchyTest_Object();
// Includes nodes by filter regardless of minNodeCount restrictions
$root->setMarkingFilterFunction(function($record) use($obj2, $obj2a, $obj2aa) {
// Results need to include parent hierarchy, even if we just want to
// match the innermost node.
// var_dump($record->Title);
// var_dump(in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID)));
return in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID));
});
$root->markPartialTree($nodeCountThreshold);
$html = $root->getChildrenAsUL(
"",
'"<li id=\"" . $child->ID . "\">" . $child->Title',
null,
true, // limit to marked
"AllChildrenIncludingDeleted",
"numChildren",
true,
$nodeCountThreshold
);
$parser = new CSSContentParser($html);
$node1 = $parser->getByXpath(
'//ul/li[@id="' . $obj1->ID . '"]'
);
$this->assertFalse(
(bool)$node1,
'Does not contain root elements which dont match the filter'
);
$node2aa = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]' .
'/ul/li[@id="' . $obj2a->ID . '"]' .
'/ul/li[@id="' . $obj2aa->ID . '"]'
);
$this->assertTrue(
(bool)$node2aa,
'Contains non-root elements which match the filter'
);
}
public function testGetChildrenAsULHardLimitsNodes() {
$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
// Set low enough that it should fit all search matches without lazy loading
$nodeCountThreshold = 3;
$root = new HierarchyTest_Object();
// Includes nodes by filter regardless of minNodeCount restrictions
$root->setMarkingFilterFunction(function($record) use($obj2, $obj2a, $obj2aa) {
// Results need to include parent hierarchy, even if we just want to
// match the innermost node.
// var_dump($record->Title);
// var_dump(in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID)));
return in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID));
});
$root->markPartialTree($nodeCountThreshold);
$html = $root->getChildrenAsUL(
"",
'"<li id=\"" . $child->ID . "\">" . $child->Title',
null,
true, // limit to marked
"AllChildrenIncludingDeleted",
"numChildren",
true,
$nodeCountThreshold
);
$parser = new CSSContentParser($html);
$node1 = $parser->getByXpath(
'//ul/li[@id="' . $obj1->ID . '"]'
);
$this->assertFalse(
(bool)$node1,
'Does not contain root elements which dont match the filter'
);
$node2aa = $parser->getByXpath(
'//ul/li[@id="' . $obj2->ID . '"]' .
'/ul/li[@id="' . $obj2a->ID . '"]' .
'/ul/li[@id="' . $obj2aa->ID . '"]'
);
$this->assertTrue(
(bool)$node2aa,
'Contains non-root elements which match the filter'
);
}
} }
class HierarchyTest_Object extends DataObject implements TestOnly { class HierarchyTest_Object extends DataObject implements TestOnly {
@ -191,4 +423,4 @@ class HierarchyTest_Object extends DataObject implements TestOnly {
'Hierarchy', 'Hierarchy',
"Versioned('Stage', 'Live')", "Versioned('Stage', 'Live')",
); );
} }