mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Merge branch '3.1'
This commit is contained in:
commit
94f209eb74
@ -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
|
||||||
|
@ -45,7 +45,7 @@ SS_Cache::add_backend('aggregatestore', 'File', array('cache_dir' => $aggregatec
|
|||||||
SS_Cache::pick_backend('aggregatestore', 'aggregate', 1000);
|
SS_Cache::pick_backend('aggregatestore', 'aggregate', 1000);
|
||||||
|
|
||||||
// If you don't want to see deprecation errors for the new APIs, change this to 3.0.0-dev.
|
// If you don't want to see deprecation errors for the new APIs, change this to 3.0.0-dev.
|
||||||
Deprecation::notification_version('3.0.0');
|
Deprecation::notification_version('3.1.0');
|
||||||
|
|
||||||
// TODO Remove once new ManifestBuilder with submodule support is in place
|
// TODO Remove once new ManifestBuilder with submodule support is in place
|
||||||
require_once('admin/_config.php');
|
require_once('admin/_config.php');
|
@ -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.
|
||||||
|
@ -437,8 +437,8 @@ jQuery.noConflict();
|
|||||||
contentEls.removeClass('loading');
|
contentEls.removeClass('loading');
|
||||||
},
|
},
|
||||||
success: function(data, status, xhr) {
|
success: function(data, status, xhr) {
|
||||||
var els = self.handleAjaxResponse(data, status, xhr);
|
var els = self.handleAjaxResponse(data, status, xhr, state);
|
||||||
self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: els});
|
self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: els, state: state});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -449,8 +449,14 @@ jQuery.noConflict();
|
|||||||
* Handles ajax responses containing plain HTML, or mulitple
|
* Handles ajax responses containing plain HTML, or mulitple
|
||||||
* PJAX fragments wrapped in JSON (see PjaxResponseNegotiator PHP class).
|
* PJAX fragments wrapped in JSON (see PjaxResponseNegotiator PHP class).
|
||||||
* Can be hooked into an ajax 'success' callback.
|
* Can be hooked into an ajax 'success' callback.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (Object) data
|
||||||
|
* (String) status
|
||||||
|
* (XMLHTTPRequest) xhr
|
||||||
|
* (Object) state The original history state which the request was initiated with
|
||||||
*/
|
*/
|
||||||
handleAjaxResponse: function(data, status, xhr) {
|
handleAjaxResponse: function(data, status, xhr, state) {
|
||||||
var self = this, url, selectedTabs, guessFragment;
|
var self = this, url, selectedTabs, guessFragment;
|
||||||
|
|
||||||
// Support a full reload
|
// Support a full reload
|
||||||
@ -545,7 +551,7 @@ jQuery.noConflict();
|
|||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
|
||||||
this.restoreTabState();
|
this.restoreTabState(state.data.tabState !== 'undefined' ? state.data.tabState : null);
|
||||||
|
|
||||||
return newContentEls;
|
return newContentEls;
|
||||||
},
|
},
|
||||||
@ -620,20 +626,35 @@ jQuery.noConflict();
|
|||||||
/**
|
/**
|
||||||
* Re-select previously saved tabs.
|
* Re-select previously saved tabs.
|
||||||
* Requires HTML5 sessionStorage support.
|
* Requires HTML5 sessionStorage support.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (Object) Map of tab container selectors to tab selectors.
|
||||||
|
* Used to mark a specific tab as active regardless of the previously saved options.
|
||||||
*/
|
*/
|
||||||
restoreTabState: function() {
|
restoreTabState: function(overrideStates) {
|
||||||
if(typeof(window.sessionStorage)=="undefined" || window.sessionStorage === null) return;
|
|
||||||
|
|
||||||
var self = this, url = this._tabStateUrl(),
|
var self = this, url = this._tabStateUrl(),
|
||||||
data = window.sessionStorage.getItem('tabs-' + url),
|
hasSessionStorage = (typeof(window.sessionStorage)!=="undefined" && window.sessionStorage),
|
||||||
selectedTabs = data ? JSON.parse(data) : false;
|
sessionData = hasSessionStorage ? window.sessionStorage.getItem('tabs-' + url) : null,
|
||||||
if(selectedTabs) {
|
sessionStates = sessionData ? JSON.parse(sessionData) : false;
|
||||||
$.each(selectedTabs, function(i, selectedTab) {
|
|
||||||
var el = self.find('#' + selectedTab.id);
|
this.find('.cms-tabset').each(function() {
|
||||||
if(!el.data('tabs')) return; // don't act on uninit'ed controls
|
var index, tabset = $(this), tabsetId = tabset.attr('id'), tab,
|
||||||
el.tabs('select', selectedTab.selected);
|
forcedTab = tabset.find('.ss-tabs-force-active');
|
||||||
});
|
|
||||||
}
|
if(!tabset.data('tabs')) return; // don't act on uninit'ed controls
|
||||||
|
|
||||||
|
if(forcedTab.length) {
|
||||||
|
index = forcedTab.index();
|
||||||
|
} else if(overrideStates && overrideStates[tabsetId]) {
|
||||||
|
tab = tabset.find(overrideStates[tabsetId].tabSelector);
|
||||||
|
if(tab.length) index = tab.index();
|
||||||
|
} else if(sessionStates) {
|
||||||
|
$.each(sessionStates, function(i, sessionState) {
|
||||||
|
if(tabset.is('#' + sessionState.id)) index = sessionState.selected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(index !== null) tabset.tabs('select', index);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
* - SS_DATABASE_SERVER: The database server to use, defaulting to localhost
|
* - SS_DATABASE_SERVER: The database server to use, defaulting to localhost
|
||||||
* - SS_DATABASE_USERNAME: The database username (mandatory)
|
* - SS_DATABASE_USERNAME: The database username (mandatory)
|
||||||
* - SS_DATABASE_PASSWORD: The database password (mandatory)
|
* - SS_DATABASE_PASSWORD: The database password (mandatory)
|
||||||
|
* - SS_DATABASE_PORT: The database port
|
||||||
* - SS_DATABASE_SUFFIX: A suffix to add to the database name.
|
* - SS_DATABASE_SUFFIX: A suffix to add to the database name.
|
||||||
* - SS_DATABASE_PREFIX: A prefix to add to the database name.
|
* - SS_DATABASE_PREFIX: A prefix to add to the database name.
|
||||||
* - SS_DATABASE_TIMEZONE: Set the database timezone to something other than the system timezone.
|
* - SS_DATABASE_TIMEZONE: Set the database timezone to something other than the system timezone.
|
||||||
@ -95,6 +96,11 @@ if(defined('SS_DATABASE_USERNAME') && defined('SS_DATABASE_PASSWORD')) {
|
|||||||
. (defined('SS_DATABASE_SUFFIX') ? SS_DATABASE_SUFFIX : ''),
|
. (defined('SS_DATABASE_SUFFIX') ? SS_DATABASE_SUFFIX : ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set the port if called for
|
||||||
|
if(defined('SS_DATABASE_PORT')) {
|
||||||
|
$databaseConfig['port'] = SS_DATABASE_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the timezone if called for
|
// Set the timezone if called for
|
||||||
if (defined('SS_DATABASE_TIMEZONE')) {
|
if (defined('SS_DATABASE_TIMEZONE')) {
|
||||||
$databaseConfig['timezone'] = SS_DATABASE_TIMEZONE;
|
$databaseConfig['timezone'] = SS_DATABASE_TIMEZONE;
|
||||||
|
@ -598,7 +598,13 @@ class Config_LRU {
|
|||||||
protected $c = 0;
|
protected $c = 0;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->cache = new SplFixedArray(self::SIZE);
|
if (version_compare(PHP_VERSION, '5.3.7', '<')) {
|
||||||
|
// SplFixedArray causes seg faults before PHP 5.3.7
|
||||||
|
$this->cache = array();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->cache = new SplFixedArray(self::SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-fill with stdClass instances. By reusing we avoid object-thrashing
|
// Pre-fill with stdClass instances. By reusing we avoid object-thrashing
|
||||||
for ($i = 0; $i < self::SIZE; $i++) {
|
for ($i = 0; $i < self::SIZE; $i++) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,7 +78,7 @@ class SS_ConfigStaticManifest {
|
|||||||
$static = $this->statics[$class][$name];
|
$static = $this->statics[$class][$name];
|
||||||
|
|
||||||
if ($static['access'] != T_PRIVATE) {
|
if ($static['access'] != T_PRIVATE) {
|
||||||
Deprecation::notice('3.1.0', "Config static $class::\$$name must be marked as private", Deprecation::SCOPE_GLOBAL);
|
Deprecation::notice('3.2.0', "Config static $class::\$$name must be marked as private", Deprecation::SCOPE_GLOBAL);
|
||||||
// Don't warn more than once per static
|
// Don't warn more than once per static
|
||||||
$static['access'] = T_PRIVATE;
|
$static['access'] = T_PRIVATE;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
* CMS form fields now support help text through `setDescription()`, both inline and as tooltips
|
* CMS form fields now support help text through `setDescription()`, both inline and as tooltips
|
||||||
* Removed SiteTree "MetaTitle" and "MetaKeywords" fields
|
* Removed SiteTree "MetaTitle" and "MetaKeywords" fields
|
||||||
* More legible and simplified tab and menu styling in the CMS
|
* More legible and simplified tab and menu styling in the CMS
|
||||||
|
* Dropped support for Internet Explorer 7
|
||||||
|
|
||||||
### Framework
|
### Framework
|
||||||
|
|
||||||
|
68
docs/en/howto/customize-cms-pages-list.md
Normal file
68
docs/en/howto/customize-cms-pages-list.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Howto: Customize the Pages List in the CMS
|
||||||
|
|
||||||
|
The pages "list" view in the CMS is a powerful alternative to visualizing
|
||||||
|
your site's content, and can be better suited than a tree for large flat
|
||||||
|
hierarchies. A good example would be a collection of news articles,
|
||||||
|
all contained under a "holder" page type, a quite common pattern in SilverStripe.
|
||||||
|
|
||||||
|
The "list" view allows you to paginate through a large number of records,
|
||||||
|
as well as sort and filter them in a way that would be hard to achieve in a tree structure.
|
||||||
|
But sometimes the default behaviour isn't powerful enough, and you want a more
|
||||||
|
specific list view for certain page types, for example to sort the list by
|
||||||
|
a different criteria, or add more columns to filter on. The resulting
|
||||||
|
form is mainly based around a `[GridField](/reference/grid-field)` instance,
|
||||||
|
which in turn includes all children in a `[DataList](/topics/datamodel)`.
|
||||||
|
You can use these two classes as a starting point for your customizations.
|
||||||
|
|
||||||
|
Here's a brief example on how to add sorting and a new column for a
|
||||||
|
hypothetical `NewsPageHolder` type, which contains `NewsPage` children.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
// mysite/code/NewsPageHolder.php
|
||||||
|
class NewsPageHolder extends Page {
|
||||||
|
static $allowed_children = array('NewsPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
// mysite/code/NewsPage.php
|
||||||
|
class NewsPage extends Page {
|
||||||
|
static $has_one = array(
|
||||||
|
'Author' => 'Member',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
We'll now add an `Extension` subclass to `LeftAndMain`, which is the main CMS controller.
|
||||||
|
This allows us to intercept the list building logic, and alter the `GridField`
|
||||||
|
before its rendered. In this case, we limit our logic to the desired page type,
|
||||||
|
although it's just as easy to implement changes which apply to all page types,
|
||||||
|
or across page types with common characteristics.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
// mysite/code/NewsPageHolderCMSMainExtension.php
|
||||||
|
class NewsPageHolderCMSMainExtension extends Extension {
|
||||||
|
function updateListView($listView) {
|
||||||
|
$parentId = $listView->getController()->getRequest()->requestVar('ParentID');
|
||||||
|
$parent = ($parentId) ? Page::get()->byId($parentId) : new Page();
|
||||||
|
|
||||||
|
// Only apply logic for this page type
|
||||||
|
if($parent && $parent instanceof NewsPageHolder) {
|
||||||
|
$gridField = $listView->Fields()->dataFieldByName('Page');
|
||||||
|
if($gridField) {
|
||||||
|
// Sort by created
|
||||||
|
$list = $gridField->getList();
|
||||||
|
$gridField->setList($list->sort('Created', 'DESC'));
|
||||||
|
// Add author to columns
|
||||||
|
$cols = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
|
||||||
|
if($cols) {
|
||||||
|
$fields = $cols->getDisplayFields($gridField);
|
||||||
|
$fields['Author.Title'] = 'Author';
|
||||||
|
$cols->setDisplayFields($fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mysite/_config/config.yml
|
||||||
|
LeftAndMain:
|
||||||
|
extensions:
|
||||||
|
- NewsPageHolderCMSMainExtension
|
@ -6,6 +6,7 @@ on tasks and goals rather than going into deep details.
|
|||||||
You will find it useful to read the introduction [tutorials](/tutorials) before tackling these How-Tos so you can understand some of
|
You will find it useful to read the introduction [tutorials](/tutorials) before tackling these How-Tos so you can understand some of
|
||||||
the language and functions which are used in the guides.
|
the language and functions which are used in the guides.
|
||||||
|
|
||||||
|
* [Howto: Customize the Pages List in the CMS](customize-cms-pages-list)
|
||||||
* [Import CSV Data](csv-import). Build a simple CSV importer using either [api:ModelAdmin] or a custom controller
|
* [Import CSV Data](csv-import). Build a simple CSV importer using either [api:ModelAdmin] or a custom controller
|
||||||
* [Dynamic Default Fields](dynamic-default-fields). Pre populate a [api:DataObject] with data.
|
* [Dynamic Default Fields](dynamic-default-fields). Pre populate a [api:DataObject] with data.
|
||||||
* [Grouping Lists](grouping-dataobjectsets). Group results in a [api:SS_List] to create sub sections.
|
* [Grouping Lists](grouping-dataobjectsets). Group results in a [api:SS_List] to create sub sections.
|
||||||
|
@ -42,7 +42,7 @@ A typical website page on a conservative single CPU machine (e.g., Intel 2Ghz) t
|
|||||||
|
|
||||||
## Client side (CMS) requirements
|
## Client side (CMS) requirements
|
||||||
|
|
||||||
SilverStripe CMS is designed to work well with Firefox 3.0+ and Internet Explorer 7.0+. We aim to provide satisfactory experiences in Apple Safari and Google Chrome. SilverStripe CMS works well across Windows, Linux, and Mac operating systems.
|
SilverStripe CMS is designed to work well with Google Chrome, Mozilla Firefox and Internet Explorer 8+. We aim to provide satisfactory experiences in Apple Safari. SilverStripe CMS works well across Windows, Linux, and Mac operating systems.
|
||||||
|
|
||||||
## End user requirements ##
|
## End user requirements ##
|
||||||
|
|
||||||
|
@ -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]`
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -57,8 +57,7 @@ class GDBackend extends Object implements Image_Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function setGD($gd) {
|
public function setGD($gd) {
|
||||||
Deprecation::notice('3.1', 'Use GD::setImageResource instead',
|
Deprecation::notice('3.1', 'Use GD::setImageResource instead');
|
||||||
Deprecation::SCOPE_CLASS);
|
|
||||||
return $this->setImageResource($gd);
|
return $this->setImageResource($gd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +66,7 @@ class GDBackend extends Object implements Image_Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getGD() {
|
public function getGD() {
|
||||||
Deprecation::notice('3.1', 'GD::getImageResource instead',
|
Deprecation::notice('3.1', 'GD::getImageResource instead');
|
||||||
Deprecation::SCOPE_CLASS);
|
|
||||||
return $this->getImageResource();
|
return $this->getImageResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class CheckboxSetField extends OptionsetField {
|
|||||||
public function saveInto(DataObjectInterface $record) {
|
public function saveInto(DataObjectInterface $record) {
|
||||||
$fieldname = $this->name;
|
$fieldname = $this->name;
|
||||||
$relation = ($fieldname && $record && $record->hasMethod($fieldname)) ? $record->$fieldname() : null;
|
$relation = ($fieldname && $record && $record->hasMethod($fieldname)) ? $record->$fieldname() : null;
|
||||||
if($fieldname && $record && $relation && $relation instanceof RelationList) {
|
if($fieldname && $record && $relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
|
||||||
$idList = array();
|
$idList = array();
|
||||||
if($this->value) foreach($this->value as $id => $bool) {
|
if($this->value) foreach($this->value as $id => $bool) {
|
||||||
if($bool) {
|
if($bool) {
|
||||||
|
@ -179,7 +179,7 @@ class ListboxField extends DropdownField {
|
|||||||
if($this->multiple) {
|
if($this->multiple) {
|
||||||
$fieldname = $this->name;
|
$fieldname = $this->name;
|
||||||
$relation = ($fieldname && $record && $record->hasMethod($fieldname)) ? $record->$fieldname() : null;
|
$relation = ($fieldname && $record && $record->hasMethod($fieldname)) ? $record->$fieldname() : null;
|
||||||
if($fieldname && $record && $relation && $relation instanceof RelationList) {
|
if($fieldname && $record && $relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
|
||||||
$idList = (is_array($this->value)) ? array_values($this->value) : array();
|
$idList = (is_array($this->value)) ? array_values($this->value) : array();
|
||||||
if(!$record->ID) {
|
if(!$record->ID) {
|
||||||
$record->write(); // record needs to have an ID in order to set relationships
|
$record->write(); // record needs to have an ID in order to set relationships
|
||||||
|
@ -115,13 +115,20 @@ class TreeMultiselectField extends TreeDropdownField {
|
|||||||
$title = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
|
$title = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dataUrlTree = '';
|
||||||
|
if ($this->form){
|
||||||
|
$dataUrlTree = $this->Link('tree');
|
||||||
|
if (isset($idArray) && count($idArray)){
|
||||||
|
$dataUrlTree .= '?forceValue='.implode(',',$idArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
return FormField::create_tag(
|
return FormField::create_tag(
|
||||||
'div',
|
'div',
|
||||||
array (
|
array (
|
||||||
'id' => "TreeDropdownField_{$this->id()}",
|
'id' => "TreeDropdownField_{$this->id()}",
|
||||||
'class' => 'TreeDropdownField multiple' . ($this->extraClass() ? " {$this->extraClass()}" : '')
|
'class' => 'TreeDropdownField multiple' . ($this->extraClass() ? " {$this->extraClass()}" : '')
|
||||||
. ($this->showSearch ? " searchable" : ''),
|
. ($this->showSearch ? " searchable" : ''),
|
||||||
'data-url-tree' => $this->form ? $this->Link('tree') : "",
|
'data-url-tree' => $dataUrlTree,
|
||||||
'data-title' => $title,
|
'data-title' => $title,
|
||||||
'title' => $this->getDescription()
|
'title' => $this->getDescription()
|
||||||
),
|
),
|
||||||
|
@ -13,7 +13,6 @@ var ss = ss || {};
|
|||||||
* Caution: Incomplete and unstable API.
|
* Caution: Incomplete and unstable API.
|
||||||
*/
|
*/
|
||||||
ss.editorWrappers = {};
|
ss.editorWrappers = {};
|
||||||
ss.editorWrappers.initial
|
|
||||||
ss.editorWrappers.tinyMCE = (function() {
|
ss.editorWrappers.tinyMCE = (function() {
|
||||||
return {
|
return {
|
||||||
init: function(config) {
|
init: function(config) {
|
||||||
|
@ -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.
|
||||||
|
@ -54,7 +54,12 @@ class MySQLDatabase extends SS_Database {
|
|||||||
* - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
|
* - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
|
||||||
*/
|
*/
|
||||||
public function __construct($parameters) {
|
public function __construct($parameters) {
|
||||||
$this->dbConn = new MySQLi($parameters['server'], $parameters['username'], $parameters['password']);
|
if(!empty($parameters['port'])) {
|
||||||
|
$this->dbConn = new MySQLi($parameters['server'], $parameters['username'], $parameters['password'],
|
||||||
|
'', $parameters['port']);
|
||||||
|
} else {
|
||||||
|
$this->dbConn = new MySQLi($parameters['server'], $parameters['username'], $parameters['password']);
|
||||||
|
}
|
||||||
|
|
||||||
if($this->dbConn->connect_error) {
|
if($this->dbConn->connect_error) {
|
||||||
$this->databaseError("Couldn't connect to MySQL database | " . $this->dbConn->connect_error);
|
$this->databaseError("Couldn't connect to MySQL database | " . $this->dbConn->connect_error);
|
||||||
|
@ -330,7 +330,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
|||||||
} else {
|
} else {
|
||||||
$this->RememberLoginToken = null;
|
$this->RememberLoginToken = null;
|
||||||
Cookie::set('alc_enc', null);
|
Cookie::set('alc_enc', null);
|
||||||
Cookie::forceExpiry('alc_enc');
|
Cookie::force_expiry('alc_enc');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the incorrect log-in count
|
// Clear the incorrect log-in count
|
||||||
@ -423,7 +423,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
|||||||
|
|
||||||
$this->RememberLoginToken = null;
|
$this->RememberLoginToken = null;
|
||||||
Cookie::set('alc_enc', null); // // Clear the Remember Me cookie
|
Cookie::set('alc_enc', null); // // Clear the Remember Me cookie
|
||||||
Cookie::forceExpiry('alc_enc');
|
Cookie::force_expiry('alc_enc');
|
||||||
|
|
||||||
// Switch back to live in order to avoid infinite loops when
|
// Switch back to live in order to avoid infinite loops when
|
||||||
// redirecting to the login screen (if this login screen is versioned)
|
// redirecting to the login screen (if this login screen is versioned)
|
||||||
|
@ -46,8 +46,8 @@ class GDTest extends SapphireTest {
|
|||||||
$samples = array();
|
$samples = array();
|
||||||
for($y = 0; $y < $vertical; $y++) {
|
for($y = 0; $y < $vertical; $y++) {
|
||||||
for($x = 0; $x < $horizontal; $x++) {
|
for($x = 0; $x < $horizontal; $x++) {
|
||||||
$colour = imagecolorat($gd->getGD(), $x * 5, $y * 5);
|
$colour = imagecolorat($gd->getImageResource(), $x * 5, $y * 5);
|
||||||
$samples[] = ImageColorsforIndex($gd->getGD(), $colour);
|
$samples[] = ImageColorsforIndex($gd->getImageResource(), $colour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $samples;
|
return $samples;
|
||||||
|
@ -177,19 +177,23 @@ class DataExtensionTest_Player extends DataObject implements TestOnly {
|
|||||||
|
|
||||||
class DataExtensionTest_PlayerExtension extends DataExtension implements TestOnly {
|
class DataExtensionTest_PlayerExtension extends DataExtension implements TestOnly {
|
||||||
|
|
||||||
public static function add_to_class($class = null, $extensionClass = null, $args = null) {
|
public static function get_extra_config($class = null, $extensionClass = null, $args = null) {
|
||||||
|
$config = array();
|
||||||
|
|
||||||
// Only add these extensions if the $class is set to DataExtensionTest_Player, to
|
// Only add these extensions if the $class is set to DataExtensionTest_Player, to
|
||||||
// test that the argument works.
|
// test that the argument works.
|
||||||
if($class == 'DataExtensionTest_Player') {
|
if($class == 'DataExtensionTest_Player') {
|
||||||
Config::inst()->update($class, 'db', array(
|
$config['db'] = array(
|
||||||
'Address' => 'Text',
|
'Address' => 'Text',
|
||||||
'DateBirth' => 'Date',
|
'DateBirth' => 'Date',
|
||||||
'Status' => "Enum('Shooter,Goalie')"
|
'Status' => "Enum('Shooter,Goalie')"
|
||||||
));
|
);
|
||||||
Config::inst()->update($class, 'defaults', array(
|
$config['defaults'] = array(
|
||||||
'Status' => 'Goalie'
|
'Status' => 'Goalie'
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -394,13 +394,6 @@ class DataListTest extends SapphireTest {
|
|||||||
// $this->assertEquals('Joe', $list->Last()->Name, 'Last comment should be from Joe');
|
// $this->assertEquals('Joe', $list->Last()->Name, 'Last comment should be from Joe');
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public function testSimpleNegationFilter() {
|
|
||||||
$list = DataObjectTest_TeamComment::get();
|
|
||||||
$list = $list->filter('TeamID:Negation', $this->idFromFixture('DataObjectTest_Team', 'team1'));
|
|
||||||
$this->assertEquals(1, $list->count());
|
|
||||||
$this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Bob');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSimplePartialMatchFilter() {
|
public function testSimplePartialMatchFilter() {
|
||||||
$list = DataObjectTest_TeamComment::get();
|
$list = DataObjectTest_TeamComment::get();
|
||||||
$list = $list->filter('Name:PartialMatch', 'o')->sort('Name');
|
$list = $list->filter('Name:PartialMatch', 'o')->sort('Name');
|
||||||
|
@ -180,6 +180,238 @@ class HierarchyTest extends SapphireTest {
|
|||||||
$this->assertEquals('Obj 2 » Obj 2a » Obj 2aa', $obj2aa->getBreadcrumbs());
|
$this->assertEquals('Obj 2 » Obj 2a » 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')",
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user