mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge remote-tracking branch 'origin/3.1'
This commit is contained in:
commit
60fc7e5346
@ -1,16 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* The object manages the main CMS menu. See {@link LeftAndMain::init()} for example usage.
|
||||
* The object manages the main CMS menu. See {@link LeftAndMain::init()} for
|
||||
* example usage.
|
||||
*
|
||||
* The menu will be automatically populated with menu items for subclasses of {@link LeftAndMain}.
|
||||
* That is, for each class in the CMS that creates an administration panel, a CMS menu item will be created.
|
||||
* The default configuration will also include a 'help' link to the SilverStripe user documentation.
|
||||
* The menu will be automatically populated with menu items for subclasses of
|
||||
* {@link LeftAndMain}. That is, for each class in the CMS that creates an
|
||||
* administration panel, a CMS menu item will be created. The default
|
||||
* configuration will also include a 'help' link to the SilverStripe user
|
||||
* documentation.
|
||||
*
|
||||
* Additional CMSMenu items can be added through {@link LeftAndMainExtension::init()}
|
||||
* extensions added to {@link LeftAndMain}.
|
||||
*
|
||||
* @package cms
|
||||
* @subpackage content
|
||||
* @package framework
|
||||
* @subpackage admin
|
||||
*/
|
||||
class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider
|
||||
{
|
||||
class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider {
|
||||
|
||||
/**
|
||||
* An array of changes to be made to the menu items, in the order that the changes should be
|
||||
@ -79,10 +84,12 @@ class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider
|
||||
* @param string $url The url of the link
|
||||
* @param integer $priority The menu priority (sorting order) of the menu item. Higher priorities will be further
|
||||
* left.
|
||||
* @param array $attributes an array of attributes to include on the link.
|
||||
*
|
||||
* @return boolean The result of the operation.
|
||||
*/
|
||||
public static function add_link($code, $menuTitle, $url, $priority = -1) {
|
||||
return self::add_menu_item($code, $menuTitle, $url, null, $priority);
|
||||
public static function add_link($code, $menuTitle, $url, $priority = -1, $attributes = null) {
|
||||
return self::add_menu_item($code, $menuTitle, $url, null, $priority, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,13 +104,17 @@ class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider
|
||||
* @param string $controllerClass The controller class for this menu, used to check permisssions.
|
||||
* If blank, it's assumed that this is public, and always shown to users who
|
||||
* have the rights to access some other part of the admin area.
|
||||
* @param array $attributes an array of attributes to include on the link.
|
||||
*
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function add_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1) {
|
||||
public static function add_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1, $attributes = null) {
|
||||
// If a class is defined, then force the use of that as a code. This helps prevent menu item duplication
|
||||
if($controllerClass) $code = $controllerClass;
|
||||
if($controllerClass) {
|
||||
$code = $controllerClass;
|
||||
}
|
||||
|
||||
return self::replace_menu_item($code, $menuTitle, $url, $controllerClass, $priority);
|
||||
return self::replace_menu_item($code, $menuTitle, $url, $controllerClass, $priority, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,18 +234,26 @@ class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider
|
||||
* @param string $controllerClass The controller class for this menu, used to check permisssions.
|
||||
* If blank, it's assumed that this is public, and always shown to users who
|
||||
* have the rights to access some other part of the admin area.
|
||||
* @param array $attributes an array of attributes to include on the link.
|
||||
*
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function replace_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1) {
|
||||
public static function replace_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1, $attributes = null) {
|
||||
$item = new CMSMenuItem($menuTitle, $url, $controllerClass, $priority);
|
||||
|
||||
if($attributes) {
|
||||
$item->setAttributes($attributes);
|
||||
}
|
||||
|
||||
self::$menu_item_changes[] = array(
|
||||
'type' => 'add',
|
||||
'code' => $code,
|
||||
'item' => new CMSMenuItem($menuTitle, $url, $controllerClass, $priority),
|
||||
'item' => $item,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a previously built menuitem object to the menu
|
||||
* Add a previously built menu item object to the menu
|
||||
*/
|
||||
protected static function add_menu_item_obj($code, $cmsMenuItem) {
|
||||
self::$menu_item_changes[] = array(
|
||||
|
@ -1,12 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A simple CMS menu item
|
||||
* A simple CMS menu item.
|
||||
*
|
||||
* Items can be added to the menu through custom {@link LeftAndMainExtension}
|
||||
* classes and {@link CMSMenu}.
|
||||
*
|
||||
* @package cms
|
||||
* @subpackage content
|
||||
* @see CMSMenu
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage admin
|
||||
*/
|
||||
class CMSMenuItem extends Object
|
||||
{
|
||||
class CMSMenuItem extends Object {
|
||||
|
||||
/**
|
||||
* The (translated) menu title
|
||||
* @var string $title
|
||||
@ -31,8 +37,17 @@ class CMSMenuItem extends Object
|
||||
*/
|
||||
public $priority;
|
||||
|
||||
/**
|
||||
* Attributes for the link. For instance, custom data attributes or standard
|
||||
* HTML anchor properties.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $attributes = array();
|
||||
|
||||
/**
|
||||
* Create a new CMS Menu Item
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $url
|
||||
* @param string $controller Controller class name
|
||||
@ -43,7 +58,41 @@ class CMSMenuItem extends Object
|
||||
$this->url = $url;
|
||||
$this->controller = $controller;
|
||||
$this->priority = $priority;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function setAttributes($attributes) {
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array
|
||||
*
|
||||
* @return HTML
|
||||
*/
|
||||
public function getAttributesHTML($attrs = null) {
|
||||
$exclude = (is_string($attrs)) ? func_get_args() : null;
|
||||
|
||||
if(!$attrs || is_string($attrs)) {
|
||||
$attrs = $this->attributes;
|
||||
}
|
||||
|
||||
// Remove empty
|
||||
$attrs = array_filter((array)$attrs, function($v) {
|
||||
return ($v || $v === 0 || $v === '0');
|
||||
});
|
||||
|
||||
// Create markkup
|
||||
$parts = array();
|
||||
|
||||
foreach($attrs as $name => $value) {
|
||||
$parts[] = ($value === true) ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\"";
|
||||
}
|
||||
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +219,10 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
||||
'Help',
|
||||
_t('LeftAndMain.HELP', 'Help', 'Menu title'),
|
||||
$this->config()->help_link,
|
||||
-2
|
||||
-2,
|
||||
array(
|
||||
'target' => '_blank'
|
||||
)
|
||||
);
|
||||
|
||||
// Allow customisation of the access check by a extension
|
||||
@ -616,6 +619,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
||||
if($menuItems) {
|
||||
foreach($menuItems as $code => $menuItem) {
|
||||
// alternate permission checks (in addition to LeftAndMain->canView())
|
||||
|
||||
if(
|
||||
isset($menuItem->controller)
|
||||
&& $this->hasMethod('alternateMenuDisplayCheck')
|
||||
@ -661,9 +665,10 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
||||
$menuIcon = LeftAndMain::menu_icon_for_class($menuItem->controller);
|
||||
if (!empty($menuIcon)) $menuIconStyling .= $menuIcon;
|
||||
}
|
||||
|
||||
|
||||
$menu->push(new ArrayData(array(
|
||||
"MenuItem" => $menuItem,
|
||||
"AttributesHTML" => $menuItem->getAttributesHTML(),
|
||||
"Title" => Convert::raw2xml($title),
|
||||
"Code" => DBField::create_field('Text', $code),
|
||||
"Link" => $menuItem->url,
|
||||
|
@ -747,6 +747,7 @@ form.import-form label.left { width: 250px; }
|
||||
.cms .jstree li.jstree-open > ul, .TreeDropdownField .treedropdownfield-panel .jstree li.jstree-open > ul { display: block; }
|
||||
.cms .jstree li.jstree-closed > ul, .TreeDropdownField .treedropdownfield-panel .jstree li.jstree-closed > ul { display: none; }
|
||||
.cms .jstree li.disabled > a, .TreeDropdownField .treedropdownfield-panel .jstree li.disabled > a { color: #aaaaaa; }
|
||||
.cms .jstree li.disabled > a:hover, .TreeDropdownField .treedropdownfield-panel .jstree li.disabled > a:hover { background: transparent; cursor: default; }
|
||||
.cms .jstree li.edit-disabled > a, .TreeDropdownField .treedropdownfield-panel .jstree li.edit-disabled > a { color: #aaaaaa; }
|
||||
.cms .jstree li > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .jstree li > .jstree-icon { cursor: pointer; }
|
||||
.cms .jstree ins, .TreeDropdownField .treedropdownfield-panel .jstree ins { display: inline-block; text-decoration: none; width: 18px; height: 18px; margin: 0 0 0 0; padding: 0; float: left; }
|
||||
@ -817,6 +818,7 @@ form.import-form label.left { width: 250px; }
|
||||
.tree-holder.jstree-apple li.Root > a .jstree-icon, .cms-tree.jstree-apple li.Root > a .jstree-icon { background-position: -56px -36px; }
|
||||
.tree-holder.jstree-apple li.status-deletedonlive .text, .cms-tree.jstree-apple li.status-deletedonlive .text { text-decoration: line-through; }
|
||||
.tree-holder.jstree-apple li.jstree-checked > a, .tree-holder.jstree-apple li.jstree-checked > a:link, .cms-tree.jstree-apple li.jstree-checked > a, .cms-tree.jstree-apple li.jstree-checked > a:link { background-color: #efe999; }
|
||||
.tree-holder.jstree-apple li.disabled > a > .jstree-checkbox, .cms-tree.jstree-apple li.disabled > a > .jstree-checkbox { background-position: -57px -54px; }
|
||||
.tree-holder.jstree-apple li.readonly, .cms-tree.jstree-apple li.readonly { color: #aaaaaa; padding-left: 18px; }
|
||||
.tree-holder.jstree-apple li.readonly a, .tree-holder.jstree-apple li.readonly a:link, .cms-tree.jstree-apple li.readonly a, .cms-tree.jstree-apple li.readonly a:link { margin: 0; padding: 0; }
|
||||
.tree-holder.jstree-apple li.readonly .jstree-icon, .cms-tree.jstree-apple li.readonly .jstree-icon { display: none; }
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 2.5 KiB |
@ -230,6 +230,13 @@
|
||||
// Ignore external links, fallback to standard link behaviour
|
||||
var isExternal = $.path.isExternal(this.attr('href'));
|
||||
if(e.which > 1 || isExternal) return;
|
||||
|
||||
// if the developer has this to open in a new window, handle
|
||||
// that
|
||||
if(this.attr('target') == "_blank") {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
var item = this.getMenuItem();
|
||||
|
@ -32,6 +32,10 @@
|
||||
}
|
||||
&.disabled > a {
|
||||
color: #aaaaaa;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
&.edit-disabled > a {
|
||||
color: #aaaaaa;
|
||||
@ -438,7 +442,12 @@
|
||||
> a, > a:link{
|
||||
background-color: $color-cms-batchactions-menu-selected-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
> a > .jstree-checkbox {
|
||||
background-position: -57px -54px;
|
||||
}
|
||||
}
|
||||
&.readonly {
|
||||
color: $color-text-disabled;
|
||||
padding-left: 18px;
|
||||
|
@ -24,7 +24,7 @@
|
||||
<ul class="cms-menu-list">
|
||||
<% loop $MainMenu %>
|
||||
<li class="$LinkingMode $FirstLast <% if $LinkingMode == 'link' %><% else %>opened<% end_if %>" id="Menu-$Code" title="$Title.ATT">
|
||||
<a href="$Link" <% if $Code == 'Help' %>target="_blank"<% end_if%>>
|
||||
<a href="$Link" $AttributesHTML>
|
||||
<span class="icon icon-16 icon-{$Code.LowerCase}"> </span>
|
||||
<span class="text">$Title</span>
|
||||
</a>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @package cms
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class CMSMenuTest extends SapphireTest implements TestOnly {
|
||||
@ -35,7 +35,21 @@ class CMSMenuTest extends SapphireTest implements TestOnly {
|
||||
$this->assertNull($menuItem->controller, 'Link menu item has no controller class');
|
||||
$this->assertEquals($menuItem->priority, -1, 'Link menu item has the correct priority');
|
||||
CMSMenu::clear_menu();
|
||||
}
|
||||
|
||||
public function testLinkWithExternalAttributes() {
|
||||
CMSMenu::clear_menu();
|
||||
|
||||
CMSMenu::add_link('LinkCode', 'link title', 'http://www.example.com', -2, array(
|
||||
'target' => '_blank'
|
||||
));
|
||||
|
||||
$menuItems = CMSMenu::get_menu_items();
|
||||
$menuItem = $menuItems['LinkCode'];
|
||||
|
||||
$this->assertEquals('target="_blank"', $menuItem->getAttributesHTML());
|
||||
|
||||
CMSMenu::clear_menu();
|
||||
}
|
||||
|
||||
public function testCmsClassDetection() {
|
||||
@ -81,8 +95,15 @@ class CMSMenuTest extends SapphireTest implements TestOnly {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class CMSMenuTest_LeftAndMainController extends LeftAndMain implements TestOnly {
|
||||
|
||||
private static $url_segment = 'CMSMenuTest_LeftAndMainController';
|
||||
|
||||
private static $menu_title = 'CMSMenuTest_LeftAndMainController';
|
||||
|
||||
private static $menu_priority = 50;
|
||||
}
|
||||
|
@ -184,6 +184,7 @@ class ContentNegotiator extends Object {
|
||||
$content = str_replace('<hr>','<hr />', $content);
|
||||
$content = preg_replace('#(<img[^>]*[^/>])>#i', '\\1/>', $content);
|
||||
$content = preg_replace('#(<input[^>]*[^/>])>#i', '\\1/>', $content);
|
||||
$content = preg_replace('#(<param[^>]*[^/>])>#i', '\\1/>', $content);
|
||||
$content = preg_replace("#(\<option[^>]*[\s]+selected)(?!\s*\=)#si", "$1=\"selected\"$2", $content);
|
||||
$content = preg_replace("#(\<input[^>]*[\s]+checked)(?!\s*\=)#si", "$1=\"checked\"$2", $content);
|
||||
|
||||
|
@ -14,4 +14,13 @@ See [announcement](http://www.silverstripe.org/ss-2013-009-xss-in-cms-pages-sect
|
||||
|
||||
Due to cross-site scripting concerns when user data is used for form messages,
|
||||
it is no longer possible to use HTML in `Form->sessionMessage()`, and consequently
|
||||
in the `FormField->validate()` API.
|
||||
in the `FormField->validate()` API.
|
||||
|
||||
## Changelog
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* 2013-09-24 [114fb59](https://github.com/silverstripe/sapphire/commit/114fb59) Auto-escape titles in TreeDropdownField (Ingo Schommer)
|
||||
* 2013-09-24 [e170f4c](https://github.com/silverstripe/silverstripe-cms/commit/e170f4c) Escaping in "dependent pages" (SS-2013-009) (Ingo Schommer)
|
||||
* 2013-09-20 [b383a07](https://github.com/silverstripe/sapphire/commit/b383a07) Fixing tabindex added to CreditCardField when tabindex is NULL (Sean Harvey)
|
||||
* 2013-09-20 [c453ea3](https://github.com/silverstripe/sapphire/commit/c453ea3) Fixing tabindex added to CreditCardField when tabindex is NULL (Sean Harvey)
|
@ -1,4 +1,4 @@
|
||||
# 3.1.0 (unreleased)
|
||||
# 3.1.0
|
||||
|
||||
## Overview ##
|
||||
|
||||
@ -400,7 +400,7 @@ the following configuration: `RestfulService::set_default_curl_option(CURLOPT_SS
|
||||
The `[api:Deprecation]` API generates deprecation notices to help you future-proof your code.
|
||||
Calls to ceprecated methods will only produce errors if the API was deprecated in the
|
||||
release equal to or earlier than the "notification version" (currently set to "3.1.0").
|
||||
|
||||
|
||||
If you change the notification version to 3.1.0-dev, then only methods deprecated in older versions
|
||||
(e.g. 3.0) will trigger notices, and the other methods will silently pass. This can be useful if
|
||||
you don't yet have time to remove all calls to deprecated methods.
|
||||
@ -469,8 +469,8 @@ you can enable those warnings and future-proof your code already.
|
||||
* `DataList#dataQuery` has been changed to return a clone of the query, and so can't be used to modify the
|
||||
list's query directly. Use `DataList#alterDataQuery` instead to modify dataQuery in a safe manner.
|
||||
* `ScheduledTask`, `QuarterHourlyTask`, `HourlyTask`, `DailyTask`, `MonthlyTask`, `WeeklyTask` and
|
||||
`YearlyTask` are deprecated, please extend from `BuildTask` or `CliController`,
|
||||
and invoke them in self-defined frequencies through Unix cronjobs etc.
|
||||
`YearlyTask` are deprecated, please extend from `BuildTask` or `CliController`,
|
||||
and invoke them in self-defined frequencies through Unix cronjobs etc.
|
||||
* `i18n::$common_locales` and `i18n::$common_languages` are now accessed via the Config API, and contain
|
||||
associative rather than indexed arrays.
|
||||
Before: `array('de_DE' => array('German', 'Deutsch'))`,
|
||||
@ -484,13 +484,9 @@ you can enable those warnings and future-proof your code already.
|
||||
To remove the hints, use `setDescription(null)` and `setAttribute('placeholder', null)`.
|
||||
* Changed the way FreeStrings in `SSTemplateParser` are recognized, they will now also break on inequality
|
||||
operators (`<`, `>`). If you use inequality operators in free strings in comparisions like
|
||||
|
||||
`<% if Some<String == Some>Other>String %>...<% end_if %>`
|
||||
|
||||
you have to replace them with explicitly markes strings like
|
||||
|
||||
`<% if "Some<String" == "Some>Other>String" %>...<% end_if %>`
|
||||
|
||||
`<% if "Some<String" == "Some>Other>String" %>...<% end_if %>`.
|
||||
This change was necessary in order to support inequality operators in comparisons in templates
|
||||
* Hard limit displayed pages in the CMS tree to `500`, and the number of direct children to `250`,
|
||||
to avoid excessive resource usage. Configure through `Hierarchy.node_threshold_total` and `
|
||||
@ -503,3 +499,4 @@ you can enable those warnings and future-proof your code already.
|
||||
* Forms created in the CMS should now be instances of a new `CMSForm` class,
|
||||
and have the CMS controller's response negotiator passed into them.
|
||||
Example: `$form = new CMSForm(...); $form->setResponseNegotiator($this->getResponseNegotiator());`
|
||||
|
||||
|
@ -9,9 +9,9 @@ For information on how to upgrade to newer versions consult the [upgrading](/ins
|
||||
|
||||
## Stable Releases
|
||||
|
||||
* [3.1.0](3.1.0) - Unreleased
|
||||
* [3.1.0](3.1.0) - 1 October 2013
|
||||
|
||||
* [3.0.5](3.0.5) - 2013-02-20
|
||||
* [3.0.5](3.0.5) - 20 February 2013
|
||||
* [3.0.4](3.0.4) - 19 February 2013
|
||||
* [3.0.3](3.0.3) - 26 November 2012
|
||||
* [3.0.2](3.0.2) - 17 September 2012
|
||||
|
@ -1,11 +1,10 @@
|
||||
# 3.1.0-rc3
|
||||
|
||||
# Overview
|
||||
## Overview
|
||||
|
||||
### Security: XSS in CMS "Security" section (SS-2013-007)
|
||||
|
||||
See [announcement](http://www.silverstripe.org/ss-2013-007-xss-in-cms-security-section/)
|
||||
|
||||
### Security: XSS in form validation errors (SS-2013-008)
|
||||
|
||||
See [announcement](http://www.silverstripe.org/ss-2013-008-xss-in-numericfield-validation/)
|
||||
|
@ -1,16 +1,24 @@
|
||||
# How to customize the CMS Menu #
|
||||
# How to customize the CMS Menu
|
||||
|
||||
## Defining a Custom Icon ##
|
||||
## Adding a administration panel
|
||||
|
||||
Every time you add a new extension of the `api:LeftAndMain` class to the CMS, SilverStripe will automatically create a new menu-item for it, with a default title and icon.
|
||||
We can easily change that behaviour by using the static `$menu_title` and `$menu_icon` statics to
|
||||
Every time you add a new extension of the `[api:LeftAndMain]` class to the CMS,
|
||||
SilverStripe will automatically create a new `[api:CMSMenuItem]` for it
|
||||
|
||||
The most popular extension of LeftAndMain is a `[api:ModelAdmin]` class, so
|
||||
for a more detailed introduction to creating new `ModelAdmin` interfaces, read
|
||||
the [ModelAdmin referencee](../reference/modeladmin).
|
||||
|
||||
In this document we'll take the `ProductAdmin` class used in the
|
||||
[ModelAdmin referencee](../reference/modeladmin#setup) and so how we can change
|
||||
the menu behaviour by using the static `$menu_title` and `$menu_icon` statics to
|
||||
provide a custom title and icon.
|
||||
|
||||
The most popular extension of LeftAndMain is the `api:ModelAdmin` class, so we'll use that for an example.
|
||||
We'll take the `ProductAdmin` class used in the [ModelAdmin reference](../reference/modeladmin#setup).
|
||||
### Defining a Custom Icon
|
||||
|
||||
First we'll need a custom icon. For this purpose SilverStripe uses 16x16 black-and-transparent PNG graphics.
|
||||
In this case we'll place the icon in `mysite/images`, but you are free to use any location.
|
||||
First we'll need a custom icon. For this purpose SilverStripe uses 16x16
|
||||
black-and-transparent PNG graphics. In this case we'll place the icon in
|
||||
`mysite/images`, but you are free to use any location.
|
||||
|
||||
:::php
|
||||
class ProductAdmin extends ModelAdmin {
|
||||
@ -18,11 +26,11 @@ In this case we'll place the icon in `mysite/images`, but you are free to use an
|
||||
private static $menu_icon = 'mysite/images/product-icon.png';
|
||||
}
|
||||
|
||||
## Defining a Custom Title ##
|
||||
### Defining a Custom Title
|
||||
|
||||
The title of menu entries is configured through the `$menu_title` static.
|
||||
If its not defined, the CMS falls back to using the class name of the controller,
|
||||
removing the "Admin" bit at the end.
|
||||
If its not defined, the CMS falls back to using the class name of the
|
||||
controller, removing the "Admin" bit at the end.
|
||||
|
||||
:::php
|
||||
class ProductAdmin extends ModelAdmin {
|
||||
@ -30,9 +38,58 @@ removing the "Admin" bit at the end.
|
||||
private static $menu_title = 'My Custom Admin';
|
||||
}
|
||||
|
||||
In order to localize the menu title in different languages, use the `<classname>.MENUTITLE`
|
||||
entity name, which is automatically created when running the i18n text collection.
|
||||
For more information on language and translations, please refer to the [i18n](../reference/ii8n) docs.
|
||||
In order to localize the menu title in different languages, use the
|
||||
`<classname>.MENUTITLE` entity name, which is automatically created when running
|
||||
the i18n text collection.
|
||||
|
||||
For more information on language and translations, please refer to the
|
||||
[i18n](../reference/ii8n) docs.
|
||||
|
||||
## Adding an external link to the menu
|
||||
|
||||
On top of your administration windows, the menu can also have external links
|
||||
(e.g to external reference). In this example, we're going to add a link to
|
||||
Google to the menu.
|
||||
|
||||
First, we need to define a `[api:LeftAndMainExtension]` which will contain our
|
||||
button configuration.
|
||||
|
||||
:::php
|
||||
<?php
|
||||
|
||||
class CustomLeftAndMain extends LeftAndMainExtension {
|
||||
|
||||
public function init() {
|
||||
// unique identifier for this item. Will have an ID of Menu-$ID
|
||||
$id = 'LinkToGoogle';
|
||||
|
||||
// your 'nice' title
|
||||
$title = 'Google';
|
||||
|
||||
// the link you want to item to go to
|
||||
$link = 'http://google.com';
|
||||
|
||||
// priority controls the ordering of the link in the stack. The
|
||||
// lower the number, the lower in the list
|
||||
$priority = -2;
|
||||
|
||||
// Add your own attributes onto the link. In our case, we want to
|
||||
// open the link in a new window (not the original)
|
||||
$attributes = array(
|
||||
'target' => '_blank'
|
||||
);
|
||||
|
||||
CMSMenu::add_link($id, $title, $link, $priority, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
To have the link appear, make sure you add the extension to the `LeftAndMain`
|
||||
class. For more information about configuring extensions see the
|
||||
[DataExtension referencee](../reference/dataextension).
|
||||
|
||||
:::php
|
||||
LeftAndMain::add_extension('CustomLeftAndMain')
|
||||
|
||||
|
||||
## Related
|
||||
|
||||
|
@ -260,6 +260,14 @@ This is not the only way to set things up in Composer. For more information on t
|
||||
|
||||
## FAQ
|
||||
|
||||
### Error "The requested package silverstripe/framework 1.0.0 could not be found"
|
||||
|
||||
Composer needs hints about the base package version, either by using `composer create-project`
|
||||
as described above, or by checking out the `silverstripe-installer` project directly from version control.
|
||||
In order to use Composer on archive downloads from silverstripe.org, or other unversioned sources,
|
||||
an advanced workaround is to set the `COMPOSER_ROOT_VERSION` before every command
|
||||
([details](http://getcomposer.org/doc/03-cli.md#composer-root-version))
|
||||
|
||||
### How do I convert an existing module to using Composer?
|
||||
|
||||
Simply decide on a [unique name and vendor prefix](https://packagist.org/about),
|
||||
|
@ -16,16 +16,16 @@ class CreditCardField extends TextField {
|
||||
$parts = array_pad($parts, 4, "");
|
||||
|
||||
// TODO Mark as disabled/readonly
|
||||
$field = "<span id=\"{$this->name}_Holder\" class=\"creditCardField\">"
|
||||
. "<input autocomplete=\"off\" name=\"{$this->name}[0]\" value=\"$parts[0]\" maxlength=\"4\""
|
||||
. $this->getTabIndexHTML(0) . " /> - "
|
||||
. "<input autocomplete=\"off\" name=\"{$this->name}[1]\" value=\"$parts[1]\" maxlength=\"4\""
|
||||
. $this->getTabIndexHTML(1) . " /> - "
|
||||
. "<input autocomplete=\"off\" name=\"{$this->name}[2]\" value=\"$parts[2]\" maxlength=\"4\""
|
||||
. $this->getTabIndexHTML(2) . " /> - "
|
||||
. "<input autocomplete=\"off\" name=\"{$this->name}[3]\" value=\"$parts[3]\" maxlength=\"4\""
|
||||
. $this->getTabIndexHTML(3) . " /></span>";
|
||||
return $field;
|
||||
$properties['ValueOne'] = $parts[0];
|
||||
$properties['ValueTwo'] = $parts[1];
|
||||
$properties['ValueThree'] = $parts[2];
|
||||
$properties['ValueFour'] = $parts[3];
|
||||
$properties['TabIndexOne'] = $this->getTabIndexHTML(0);
|
||||
$properties['TabIndexTwo'] = $this->getTabIndexHTML(1);
|
||||
$properties['TabIndexThree'] = $this->getTabIndexHTML(2);
|
||||
$properties['TabIndexFour'] = $this->getTabIndexHTML(3);
|
||||
|
||||
return parent::Field($properties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,16 +19,23 @@ class RequiredFields extends Validator {
|
||||
* to the constructor of this object. (an array of elements are ok)
|
||||
*/
|
||||
public function __construct() {
|
||||
$Required = func_get_args();
|
||||
if( isset($Required[0]) && is_array( $Required[0] ) )
|
||||
$Required = $Required[0];
|
||||
$this->required = $Required;
|
||||
$required = func_get_args();
|
||||
if(isset($required[0]) && is_array($required[0])) {
|
||||
$required = $required[0];
|
||||
}
|
||||
if(!empty($required)) {
|
||||
$this->required = ArrayLib::valuekey($required);
|
||||
} else {
|
||||
$this->required = array();
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function useLabels($flag) {
|
||||
Deprecation::notice('3.2', 'useLabels will be removed from 3.2, please do not use it or implement it yourself');
|
||||
$this->useLabels = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,6 +43,7 @@ class RequiredFields extends Validator {
|
||||
*/
|
||||
public function removeValidation(){
|
||||
$this->required = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,7 +82,7 @@ class RequiredFields extends Validator {
|
||||
// submitted data for file upload fields come back as an array
|
||||
$value = isset($data[$fieldName]) ? $data[$fieldName] : null;
|
||||
if(is_array($value)) {
|
||||
if ($formField instanceof FileField && isset($value['error']) && $value['error']) {
|
||||
if($formField instanceof FileField && isset($value['error']) && $value['error']) {
|
||||
$error = true;
|
||||
} else {
|
||||
$error = (count($value)) ? false : true;
|
||||
@ -115,22 +123,21 @@ class RequiredFields extends Validator {
|
||||
* Add's a single required field to requiredfields stack
|
||||
*/
|
||||
public function addRequiredField( $field ) {
|
||||
$this->required[] = $field;
|
||||
$this->required[$field] = $field;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeRequiredField($field) {
|
||||
foreach ($this->required as $i => $required) {
|
||||
if ($field == $required) {
|
||||
array_splice($this->required, $i);
|
||||
}
|
||||
}
|
||||
unset($this->required[$field]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* allows you too add more required fields to this object after construction.
|
||||
*/
|
||||
public function appendRequiredFields($requiredFields){
|
||||
$this->required = array_merge($this->required,$requiredFields->getRequired());
|
||||
$this->required = $this->required + ArrayLib::valuekey($requiredFields->getRequired());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,14 +145,14 @@ class RequiredFields extends Validator {
|
||||
* Used by FormField to return a value for FormField::Required(), to do things like show *s on the form template.
|
||||
*/
|
||||
public function fieldIsRequired($fieldName) {
|
||||
return in_array($fieldName, $this->required);
|
||||
return isset($this->required[$fieldName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* getter function for append
|
||||
*/
|
||||
public function getRequired(){
|
||||
return $this->required;
|
||||
return array_values($this->required);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ class TreeDropdownField extends FormField {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
protected $sourceObject, $keyField, $labelField, $filterCallback, $searchCallback, $baseID = 0;
|
||||
protected $sourceObject, $keyField, $labelField, $filterCallback,
|
||||
$disableCallback, $searchCallback, $baseID = 0;
|
||||
/**
|
||||
* @var string default child method in Hierarcy->getChildrenAsUL
|
||||
*/
|
||||
@ -122,6 +123,20 @@ class TreeDropdownField extends FormField {
|
||||
$this->filterCallback = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback used to disable checkboxes for some items in the tree
|
||||
*
|
||||
* @param callback $callback
|
||||
*/
|
||||
public function setDisableFunction($callback) {
|
||||
if(!is_callable($callback, true)) {
|
||||
throw new InvalidArgumentException('TreeDropdownField->setDisableFunction(): not passed a valid callback');
|
||||
}
|
||||
|
||||
$this->disableCallback = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback used to search the hierarchy globally, even before
|
||||
@ -181,11 +196,11 @@ class TreeDropdownField extends FormField {
|
||||
if($record) {
|
||||
$title = $record->{$this->labelField};
|
||||
} else {
|
||||
if($this->showSearch){
|
||||
if($this->showSearch) {
|
||||
$title = _t('DropdownField.CHOOSESEARCH', '(Choose or Search)', 'start value of a dropdown');
|
||||
}else{
|
||||
$title = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
|
||||
}
|
||||
} else {
|
||||
$title = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Implement for TreeMultiSelectField
|
||||
@ -276,12 +291,13 @@ class TreeDropdownField extends FormField {
|
||||
$keyField = $self->keyField;
|
||||
$labelField = $self->labelField;
|
||||
return sprintf(
|
||||
'<li id="selector-%s-%s" data-id="%s" class="class-%s %s"><a rel="%d">%s</a>',
|
||||
'<li id="selector-%s-%s" data-id="%s" class="class-%s %s %s"><a rel="%d">%s</a>',
|
||||
Convert::raw2xml($self->getName()),
|
||||
Convert::raw2xml($child->$keyField),
|
||||
Convert::raw2xml($child->$keyField),
|
||||
Convert::raw2xml($child->class),
|
||||
Convert::raw2xml($child->markingClasses()),
|
||||
($self->nodeIsDisabled($child)) ? 'disabled' : '',
|
||||
(int)$child->ID,
|
||||
$escapeLabelField ? Convert::raw2xml($child->$labelField) : $child->$labelField
|
||||
);
|
||||
@ -351,6 +367,15 @@ class TreeDropdownField extends FormField {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marking a specific node in the tree as disabled
|
||||
* @param $node
|
||||
* @return boolean
|
||||
*/
|
||||
public function nodeIsDisabled($node) {
|
||||
return ($this->disableCallback && call_user_func($this->disableCallback, $node));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $field
|
||||
*/
|
||||
@ -409,7 +434,7 @@ class TreeDropdownField extends FormField {
|
||||
$sourceObject = $this->sourceObject;
|
||||
$wheres = array();
|
||||
if(singleton($sourceObject)->hasDatabaseField($this->labelField)) {
|
||||
$wheres[] = "\"$searchField\" LIKE '%$this->search%'";
|
||||
$wheres[] = "\"$this->labelField\" LIKE '%$this->search%'";
|
||||
} else {
|
||||
if(singleton($sourceObject)->hasDatabaseField('Title')) {
|
||||
$wheres[] = "\"Title\" LIKE '%$this->search%'";
|
||||
|
@ -119,7 +119,7 @@ class TreeMultiselectField extends TreeDropdownField {
|
||||
if ($this->form){
|
||||
$dataUrlTree = $this->Link('tree');
|
||||
if (isset($idArray) && count($idArray)){
|
||||
$dataUrlTree .= '?forceValue='.implode(',',$idArray);
|
||||
$dataUrlTree = Controller::join_links($dataUrlTree, '?forceValue='.implode(',',$idArray));
|
||||
}
|
||||
}
|
||||
return FormField::create_tag(
|
||||
|
@ -107,8 +107,7 @@ class GridFieldAddExistingAutocompleter
|
||||
|
||||
$searchField = new TextField('gridfield_relationsearch',
|
||||
_t('GridField.RelationSearch', "Relation search"), $value);
|
||||
// Apparently the data-* needs to be double qouted for the jQuery.meta data plugin
|
||||
$searchField->setAttribute('data-search-url', '\''.Controller::join_links($gridField->Link('search').'\''));
|
||||
$searchField->setAttribute('data-search-url', Controller::join_links($gridField->Link('search')));
|
||||
$searchField->setAttribute('placeholder', $this->getPlaceholderText($dataClass));
|
||||
$searchField->addExtraClass('relation-search no-change-track');
|
||||
|
||||
|
@ -508,9 +508,11 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
||||
|
||||
// TODO Save this item into the given relationship
|
||||
|
||||
$link = '<a href="' . $this->Link('edit') . '">"'
|
||||
. htmlspecialchars($this->record->Title, ENT_QUOTES)
|
||||
. '"</a>';
|
||||
// TODO Allow HTML in form messages
|
||||
// $link = '<a href="' . $this->Link('edit') . '">"'
|
||||
// . htmlspecialchars($this->record->Title, ENT_QUOTES)
|
||||
// . '"</a>';
|
||||
$link = '"' . $this->record->Title . '"';
|
||||
$message = _t(
|
||||
'GridFieldDetailForm.Saved',
|
||||
'Saved {name} {link}',
|
||||
|
@ -316,14 +316,12 @@
|
||||
source: function(request, response){
|
||||
var searchField = $(this.element);
|
||||
var form = $(this.element).closest("form");
|
||||
// Due to some very weird behaviout of jquery.metadata, the url have to be double quoted
|
||||
var suggestionUrl = $(searchField).attr('data-search-url').substr(1,$(searchField).attr('data-search-url').length-2);
|
||||
$.ajax({
|
||||
headers: {
|
||||
"X-Pjax" : 'Partial'
|
||||
},
|
||||
type: "GET",
|
||||
url: suggestionUrl,
|
||||
url: $(searchField).data('searchUrl'),
|
||||
data: encodeURIComponent(searchField.attr('name'))+'='+encodeURIComponent(searchField.val()),
|
||||
success: function(data) {
|
||||
response( $.map(JSON.parse(data), function( name, id ) {
|
||||
|
@ -228,7 +228,25 @@
|
||||
'themes': {
|
||||
'theme': 'apple'
|
||||
},
|
||||
'plugins': ['html_data', 'ui', 'themes']
|
||||
'types' : {
|
||||
'types' : {
|
||||
'default': {
|
||||
'check_node': function(node) {
|
||||
return ( ! node.hasClass('disabled'));
|
||||
},
|
||||
'uncheck_node': function(node) {
|
||||
return ( ! node.hasClass('disabled'));
|
||||
},
|
||||
'select_node': function(node) {
|
||||
return ( ! node.hasClass('disabled'));
|
||||
},
|
||||
'deselect_node': function(node) {
|
||||
return ( ! node.hasClass('disabled'));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'plugins': ['html_data', 'ui', 'themes', 'types']
|
||||
};
|
||||
},
|
||||
/**
|
||||
|
61
lang/it.yml
61
lang/it.yml
@ -13,6 +13,7 @@ it:
|
||||
SIZE: 'Dimensione'
|
||||
TITLE: Titolo
|
||||
TYPE: 'Tipo di file'
|
||||
URL: URL
|
||||
AssetUploadField:
|
||||
ChooseFiles: 'Scegli file'
|
||||
DRAGFILESHERE: 'Trascina file qui'
|
||||
@ -20,6 +21,7 @@ it:
|
||||
EDITALL: 'Modifica tutti'
|
||||
EDITANDORGANIZE: 'Modifica & organizza'
|
||||
EDITINFO: 'Modifica file'
|
||||
FILES: Files
|
||||
FROMCOMPUTER: 'Scegli file dal tuo computer'
|
||||
FROMCOMPUTERINFO: 'Carica dal tuo conputer'
|
||||
TOTAL: Totale
|
||||
@ -72,7 +74,9 @@ it:
|
||||
ChangePasswordEmail_ss:
|
||||
CHANGEPASSWORDTEXT1: 'Hai cambiato la password per'
|
||||
CHANGEPASSWORDTEXT2: 'Ora puoi utilizzare le seguenti credenziali per accedere:'
|
||||
EMAIL: Email
|
||||
HELLO: Ciao
|
||||
PASSWORD: Password
|
||||
ComplexTableField:
|
||||
CLOSEPOPUP: 'Chiudi Finestra'
|
||||
SUCCESSADD2: 'Aggiunto {name}'
|
||||
@ -95,12 +99,19 @@ it:
|
||||
FOURTH: quarto
|
||||
SECOND: secondo
|
||||
THIRD: terzo
|
||||
CurrencyField:
|
||||
CURRENCYSYMBOL: $
|
||||
Date:
|
||||
DAY: giorno
|
||||
DAYS: giorni
|
||||
HOUR: ora
|
||||
HOURS: ora
|
||||
MIN: minuto
|
||||
MINS: minuti
|
||||
MONTH: mese
|
||||
SECS: sec
|
||||
MONTHS: mesi
|
||||
SEC: secondo
|
||||
SECS: secondi
|
||||
TIMEDIFFAGO: '{difference} fa'
|
||||
YEAR: anno
|
||||
YEARS: anni
|
||||
@ -145,6 +156,7 @@ it:
|
||||
PLURALNAME: File
|
||||
PdfType: 'File Adobe Acrobat PDF'
|
||||
PngType: 'Immagine PNG - consigliata per utilizzo generico'
|
||||
SINGULARNAME: File
|
||||
TOOLARGE: 'La dimensione del file è troppo grande, massimo consentito {size}'
|
||||
TOOLARGESHORT: 'La dimensione del file eccede {size}'
|
||||
TiffType: 'Immagine TIFF'
|
||||
@ -158,6 +170,7 @@ it:
|
||||
ATTACHONCESAVED2: 'I file possono essere allegati quando hai salvato per la prima volta.'
|
||||
DELETE: 'Elimina {type}'
|
||||
DISALLOWEDFILETYPE: 'Non è consentito caricare questo tipo di file'
|
||||
FILE: File
|
||||
FROMCOMPUTER: 'Dal tuo computer'
|
||||
FROMFILESTORE: 'Dal CMS'
|
||||
NOSOURCE: 'Seleziona un file da allegare'
|
||||
@ -221,9 +234,12 @@ it:
|
||||
GridFieldItemEditView_ss:
|
||||
Go_back: 'Torna indietro'
|
||||
Group:
|
||||
AddRole: 'Aggiungi un ruolo per questo gruppo'
|
||||
Code: 'Codice gruppo'
|
||||
DefaultGroupTitleAdministrators: Amministratori
|
||||
DefaultGroupTitleContentAuthors: 'Autori di contenuto'
|
||||
Description: Descrizione
|
||||
GroupReminder: 'Se scegli un gruppo genitore, questo gruppo erediterà tutti i suoi ruoli'
|
||||
Locked: 'Bloccato?'
|
||||
NoRoles: 'Nessun ruolo trovato'
|
||||
PLURALNAME: Gruppi
|
||||
@ -235,6 +251,7 @@ it:
|
||||
many_many_Members: Membri
|
||||
GroupImportForm:
|
||||
Help1: '<p>Importa gruppi in formato <em>CSV</em> (valori separati da virgole). <small><a href="#" class="toggle-advanced">Mostra utilizzo avanzato</a></small></p>'
|
||||
Help2: "<div class=\"advanced\">\n<h4>Utilizzo avanzato</h4>\n<ul>\n<li>Colonne consentite: <em>%s</em></li>\n<li>Gruppi esistenti sono individuati tramite il loro valore univoco <em>Code</em> e aggiornati con i nuovi valori dal file importato.</li>\n<li>È possibile creare gerarchie tra i gruppi usando la colonna <em>ParentCode</em></li>\n<li>Codici di permessi possono essere assegnati tramite la colonna <em>PermissionCode</em>. Codici di permessi esistenti non verranno cancellati.</li>\n</ul>\n</div>"
|
||||
ResultCreated: 'Creati {count} gruppi'
|
||||
ResultDeleted: 'Eliminati %d gruppi'
|
||||
ResultUpdated: 'Aggiornati %d gruppi'
|
||||
@ -256,6 +273,7 @@ it:
|
||||
CSSCLASSRIGHT: 'Sulla destra, con il testo inserito attorno.'
|
||||
DETAILS: Dettagli
|
||||
EMAIL: 'Indirizzo email'
|
||||
FILE: File
|
||||
FOLDER: Cartella
|
||||
FROMCMS: 'Dal CMS'
|
||||
FROMCOMPUTER: 'Dal tuo computer'
|
||||
@ -281,11 +299,18 @@ it:
|
||||
LINKOPENNEWWIN: 'Apri il link in una nuova finestra?'
|
||||
LINKTO: 'Collega a'
|
||||
PAGE: Pagina
|
||||
URL: URL
|
||||
URLNOTANOEMBEDRESOURCE: 'L''URL ''{url}'' non può essere convertito in una risorsa media.'
|
||||
UpdateMEDIA: 'Aggiorna Media'
|
||||
BUTTONADDURL: 'Aggiungi url'
|
||||
Image:
|
||||
PLURALNAME: Files
|
||||
SINGULARNAME: File
|
||||
ImageField:
|
||||
IMAGE: Immagine
|
||||
Image_Cached:
|
||||
PLURALNAME: Files
|
||||
SINGULARNAME: File
|
||||
Image_iframe_ss:
|
||||
TITLE: 'Iframe per l''inserimento dell''immagine'
|
||||
LeftAndMain:
|
||||
@ -326,9 +351,11 @@ it:
|
||||
DATEFORMAT: 'Formato della data'
|
||||
DefaultAdminFirstname: 'Amministratore Predefinito'
|
||||
DefaultDateTime: predefinito
|
||||
EMAIL: Email
|
||||
EMPTYNEWPASSWORD: 'La nuova password non può essere vuota, riprova'
|
||||
ENTEREMAIL: 'Inserisci un indirizzo e-mail per ricevere il link di azzeramento della password'
|
||||
ERRORLOCKEDOUT: 'Il tuo account è stato temporaneamente disabilitato perchè ci sono stati troppi tentativi di accesso errati. Riprova tra 20 minuti.'
|
||||
ERRORLOCKEDOUT2: 'Il tuo account è stato temporaneamente disabilitato perchè ci sono stati troppi tentativi di accesso errati. Riprova tra {count} minuti.'
|
||||
ERRORNEWPASSWORD: 'Hai inserito la tua nuova password in modo differente, prova di nuovo'
|
||||
ERRORPASSWORDNOTMATCH: 'La tua password attuale non corrisponde, per favore prova ancora'
|
||||
ERRORWRONGCRED: 'E-mail o password non sembrano essere corretti. Per favore, prova di nuovo.'
|
||||
@ -337,6 +364,7 @@ it:
|
||||
INVALIDNEWPASSWORD: 'Non possiamo accettare questa password: {password}'
|
||||
LOGGEDINAS: 'Sei collegato come {name}.'
|
||||
NEWPASSWORD: 'Nuova password'
|
||||
PASSWORD: Password
|
||||
PLURALNAME: Utenti
|
||||
REMEMBERME: 'Ricordati di me la prossima volta?'
|
||||
SINGULARNAME: Utente
|
||||
@ -353,7 +381,11 @@ it:
|
||||
db_Locale: 'Localizzazione interfaccia'
|
||||
db_LockedOutUntil: 'Bloccato fino al'
|
||||
db_NumVisit: 'Numero di visite'
|
||||
db_Password: Password
|
||||
db_PasswordExpiry: 'Data di scadenza della password'
|
||||
NoPassword: 'Manca la password per questo utente.'
|
||||
MemberAuthenticator:
|
||||
TITLE: 'E-mail & Password'
|
||||
MemberDatetimeOptionsetField:
|
||||
AMORPM: 'AM (Ante meridiem) o PM (Post meridiem)'
|
||||
Custom: Personalizza
|
||||
@ -374,8 +406,10 @@ it:
|
||||
TWODIGITMONTH: 'Mese a due cifre (01=Gennaio, ecc.)'
|
||||
TWODIGITSECOND: 'Secondi a due cifre (00 a 59)'
|
||||
TWODIGITYEAR: 'Anno a due cifre'
|
||||
Toggle: 'Mostra aiuto per la formattazione'
|
||||
MemberImportForm:
|
||||
Help1: '<p>Importa utenti in <em>formato CSV</em> (valori separati da virgole). <small><a href="#" class="toggle-advanced">Mostra utilizzo avanzato</a></small></p>'
|
||||
Help2: "<div class=\"advanced\">\n<h4>Utilizzo avanzato</h4>\n<ul>\n<li>Colonne consentite: <em>%s</em></li>\n<li>Utenti esistenti sono individuati attraverso la proprietà univoca <em>Code</em> e aggiornati con i nuovi valori dal file importato.</li>\n<li>I gruppi possono essere assegnati attraverso la colonna <em>Groups</em>. I gruppi sono identificati attraverso la loro colonna <em>Code</em>, più gruppi devono essere separati da virgola. L'appartenenza esistente a gruppi non viene cancellata.</li>\n</ul>\n</div>"
|
||||
ResultCreated: 'Creati {count} utenti'
|
||||
ResultDeleted: 'Eliminati %d utenti'
|
||||
ResultNone: 'Nessun cambiamento'
|
||||
@ -435,9 +469,11 @@ it:
|
||||
SINGULARNAME: Ruolo
|
||||
Title: Titolo
|
||||
PermissionRoleCode:
|
||||
PLURALNAME: 'Codici di Ruolo'
|
||||
SINGULARNAME: 'Codice Ruolo'
|
||||
Permissions:
|
||||
PERMISSIONS_CATEGORY: 'Ruoli e permessi d''accesso'
|
||||
UserPermissionsIntro: 'Assegnando gruppi a questo utente modificherà i suoi permessi. Vedi la sezione gruppi per dettagli sui permessi dei singoli gruppi.'
|
||||
PhoneNumberField:
|
||||
VALIDATION: 'Per favore inserisci un numero di telefono valido'
|
||||
RelationComplexTableField_ss:
|
||||
@ -446,6 +482,7 @@ it:
|
||||
NOTFOUND: 'Nessun elemento trovato'
|
||||
Security:
|
||||
ALREADYLOGGEDIN: 'Non hai accesso a questa pagina. Se hai un altro account che può accederci, puoi autenticarti qui sotto.'
|
||||
LOSTPASSWORDHEADER: 'Password smarrita'
|
||||
BUTTONSEND: 'Inviami il link per azzerare la password'
|
||||
CHANGEPASSWORDBELOW: 'Puoi cambiare la tua password qui sotto.'
|
||||
CHANGEPASSWORDHEADER: 'Cambia la tua password'
|
||||
@ -463,10 +500,13 @@ it:
|
||||
APPLY_ROLES: 'Applica ruoli ai gruppi'
|
||||
APPLY_ROLES_HELP: 'Abilità di modificare i ruoli assegnati a un gruppo. Richiede il permesso "Accesso alla sezione ''Utenti''".'
|
||||
EDITPERMISSIONS: 'Modifica i permessi e gli indirizzi IP in ogni gruppo'
|
||||
EDITPERMISSIONS_HELP: 'Capacità di modificare Permessi e Indirizzi IP per un gruppo. Richiede il permesso "Accesso alla sezione ''Sicurezza''"'
|
||||
GROUPNAME: 'Nome del gruppo'
|
||||
IMPORTGROUPS: 'Importa gruppi'
|
||||
IMPORTUSERS: 'Importa utenti'
|
||||
MEMBERS: Utenti
|
||||
MENUTITLE: Sicurezza
|
||||
MemberListCaution: 'Attenzione: Gli utenti rimossi da questa lista verranno rimossi anche da tutti i gruppi e dal database'
|
||||
NEWGROUP: 'Nuovo gruppo'
|
||||
PERMISSIONS: Permessi
|
||||
ROLES: Ruoli
|
||||
@ -478,7 +518,13 @@ it:
|
||||
FileFieldLabel: 'File CSV <small>(Estensioni consentite: *.csv)</small>'
|
||||
SilverStripeNavigator:
|
||||
Edit: Modifica
|
||||
Auto: Auto
|
||||
ChangeViewMode: 'Cambia visualizzazione'
|
||||
Desktop: Desktop
|
||||
EditView: 'Modalità modifica'
|
||||
Mobile: Mobile
|
||||
PreviewView: 'Modalità anteprima'
|
||||
Responsive: Responsivo
|
||||
Tablet: Preview mode
|
||||
ViewDeviceWidth: 'Seleziona una larghezza di preview'
|
||||
Width: larghezza
|
||||
@ -532,15 +578,28 @@ it:
|
||||
HOTLINKINFO: 'Info: Questa immagine sarà collegata. Assicurati di avere il permesso di farlo.'
|
||||
MAXNUMBEROFFILES: 'Numero massimo di {count} file ecceduto.'
|
||||
MAXNUMBEROFFILESSHORT: 'Puoi caricare solo {count} file'
|
||||
MAXNUMBEROFFILESONE: 'Puoi caricare solo un file'
|
||||
REMOVE: Rimuovi
|
||||
REMOVEERROR: 'Errore eliminando il file'
|
||||
REMOVEINFO: 'Rimuove il file da qui, ma non lo elimina dal CMS'
|
||||
STARTALL: 'Avvia tutti'
|
||||
STARTALLINFO: 'Avvia tutti i caricamenti'
|
||||
Saved: Salvato
|
||||
CHOOSEANOTHERFILE: 'Scegli un altro file'
|
||||
CHOOSEANOTHERINFO: 'Sostituisci questo file con un altro dal CMS'
|
||||
OVERWRITEWARNING: 'Esiste già un file con lo stesso nome'
|
||||
UPLOADSINTO: 'salva in /{path}'
|
||||
Versioned:
|
||||
has_many_Versions: Versioni
|
||||
CMSPageHistoryController_versions_ss:
|
||||
PREVIEW: 'Preview del sito'
|
||||
GridFieldEditButton_ss:
|
||||
EDIT: Modifica
|
||||
ContentController:
|
||||
NOTLOGGEDIN: 'Non autenticato'
|
||||
GridFieldItemEditView:
|
||||
Go_back: 'Torna indietro'
|
||||
PasswordValidator:
|
||||
LOWCHARSTRENGTH: 'Perfavore aumenta la sicurezza della password aggiungendo alcuni dei seguenti caratteri: %s'
|
||||
PREVPASSWORD: 'Hai già usato questa password in passato, per favore scegline una nuova'
|
||||
TOOSHORT: 'La password è troppo corta, deve essere lunga %s o più caratteri'
|
||||
|
@ -1,4 +1,4 @@
|
||||
ja_JP:
|
||||
ja:
|
||||
AssetAdmin:
|
||||
NEWFOLDER: 新しいフォルダ
|
||||
AssetTableField:
|
14
lang/nl.yml
14
lang/nl.yml
@ -392,6 +392,7 @@ nl:
|
||||
db_NumVisit: 'Aantal bezoeken'
|
||||
db_Password: Wachtwoord
|
||||
db_PasswordExpiry: 'Wachtwoord Vervaldatum'
|
||||
NoPassword: 'Er is geen wachtwoord voor deze gebruiker.'
|
||||
MemberAuthenticator:
|
||||
TITLE: 'Email & Wachtwoord'
|
||||
MemberDatetimeOptionsetField:
|
||||
@ -490,7 +491,8 @@ nl:
|
||||
NOTFOUND: 'Geen items gevonden.'
|
||||
Security:
|
||||
ALREADYLOGGEDIN: 'Je hebt niet de juiste rechten, om deze pagina te kunnen bekijken. Als je een ander account met de juiste rechten hebt, kun je hier <a href="%s">opnieuw inloggen</a>.'
|
||||
BUTTONSEND: 'Zend mij de link om mijn wachtwoord opnieuw aan te maken'
|
||||
LOSTPASSWORDHEADER: 'Wachtwoord vergeten'
|
||||
BUTTONSEND: 'Nieuw wachtwoord aanmaken'
|
||||
CHANGEPASSWORDBELOW: 'U kunt Uw wachtwoord hier beneden veranderen.'
|
||||
CHANGEPASSWORDHEADER: 'Verander Uw wachtwoord'
|
||||
ENTERNEWPASSWORD: 'Voer een nieuw wachtwoord in.'
|
||||
@ -499,7 +501,7 @@ nl:
|
||||
LOGIN: 'Meld aan'
|
||||
NOTEPAGESECURED: 'Deze pagina is beveiligd. Voer Uw gegevens in en U wordt automatisch doorgestuurd.'
|
||||
NOTERESETLINKINVALID: '<p>De link om uw wachtwoord te kunnen wijzigen is niet meer geldig.</p><p>U kunt het <a href="{link1}">opnieuw proberen</a> of uw wachtwoord aanpassen door <a href="{link2}">in te loggen</a>.</p>'
|
||||
NOTERESETPASSWORD: 'Voer Uw emailadres in en we zenden U een link waarmee U Uw wachtwoord opnieuw kunt aanmaken'
|
||||
NOTERESETPASSWORD: 'Voer uw e-mailadres in en we sturen een link waarmee u een nieuw wachtwoord kunt instellen.'
|
||||
PASSWORDSENTHEADER: 'Wachtwoord herstel link verzonden naar {email}'
|
||||
PASSWORDSENTTEXT: 'Bedankt! Er is een link verstuurt naar {email} om uw wachtwoord te herstellen.'
|
||||
SecurityAdmin:
|
||||
@ -605,3 +607,11 @@ nl:
|
||||
PREVIEW: 'Website voorbeeld'
|
||||
GridFieldEditButton_ss:
|
||||
EDIT: Bewerken
|
||||
ContentController:
|
||||
NOTLOGGEDIN: 'Niet ingelogd'
|
||||
GridFieldItemEditView:
|
||||
Go_back: 'Ga terug'
|
||||
PasswordValidator:
|
||||
LOWCHARSTRENGTH: 'Maak a.u.b. uw wachtwoord sterker door meer van de volgende karakters te gebruiken: %s'
|
||||
PREVPASSWORD: 'U heeft dit wachtwoord in het verleden al gebruikt, kies a.u.b. een nieuw wachtwoord.'
|
||||
TOOSHORT: 'Het wachtwoord is te kort, het moet minimaal %s karakters hebben'
|
||||
|
@ -256,7 +256,10 @@ class DataQuery {
|
||||
$baseClass = array_shift($tableClasses);
|
||||
|
||||
if($orderby = $query->getOrderBy()) {
|
||||
$newOrderby = array();
|
||||
foreach($orderby as $k => $dir) {
|
||||
$newOrderby[$k] = $dir;
|
||||
|
||||
// don't touch functions in the ORDER BY or public function calls
|
||||
// selected as fields
|
||||
if(strpos($k, '(') !== false) continue;
|
||||
@ -283,10 +286,10 @@ class DataQuery {
|
||||
$qualCol = "\"$parts[0]\"";
|
||||
}
|
||||
|
||||
// remove original sort
|
||||
unset($orderby[$k]);
|
||||
// add new columns sort
|
||||
$orderby[$qualCol] = $dir;
|
||||
// remove original sort
|
||||
unset($newOrderby[$k]);
|
||||
// add new columns sort
|
||||
$newOrderby[$qualCol] = $dir;
|
||||
|
||||
// To-do: Remove this if block once SQLQuery::$select has been refactored to store getSelect()
|
||||
// format internally; then this check can be part of selectField()
|
||||
@ -305,7 +308,7 @@ class DataQuery {
|
||||
}
|
||||
}
|
||||
|
||||
$query->setOrderBy($orderby);
|
||||
$query->setOrderBy($newOrderby);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -519,7 +519,7 @@ class SQLQuery {
|
||||
* @example $sql->orderby("Column", "DESC");
|
||||
* @example $sql->orderby(array("Column" => "ASC", "ColumnTwo" => "DESC"));
|
||||
*
|
||||
* @param string|array $orderby Clauses to add (escaped SQL statements)
|
||||
* @param string|array $clauses Clauses to add (escaped SQL statements)
|
||||
* @param string $dir Sort direction, ASC or DESC
|
||||
*
|
||||
* @return SQLQuery
|
||||
@ -566,21 +566,23 @@ class SQLQuery {
|
||||
// directly in the ORDER BY
|
||||
if($this->orderby) {
|
||||
$i = 0;
|
||||
$orderby = array();
|
||||
foreach($this->orderby as $clause => $dir) {
|
||||
|
||||
// public function calls and multi-word columns like "CASE WHEN ..."
|
||||
if(strpos($clause, '(') !== false || strpos($clause, " ") !== false ) {
|
||||
// remove the old orderby
|
||||
unset($this->orderby[$clause]);
|
||||
|
||||
// Move the clause to the select fragment, substituting a placeholder column in the sort fragment.
|
||||
$clause = trim($clause);
|
||||
$column = "_SortColumn{$i}";
|
||||
|
||||
$this->selectField($clause, $column);
|
||||
$this->addOrderBy('"' . $column . '"', $dir);
|
||||
$clause = '"' . $column . '"';
|
||||
$i++;
|
||||
}
|
||||
|
||||
$orderby[$clause] = $dir;
|
||||
}
|
||||
$this->orderby = $orderby;
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -14,6 +14,15 @@ class MemberLoginForm extends LoginForm {
|
||||
|
||||
protected $authenticator_class = 'MemberAuthenticator';
|
||||
|
||||
/**
|
||||
* Since the logout and dologin actions may be conditionally removed, it's necessary to ensure these
|
||||
* remain valid actions regardless of the member login state.
|
||||
*
|
||||
* @var array
|
||||
* @config
|
||||
*/
|
||||
private static $allowed_actions = array('dologin', 'logout');
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
6
templates/forms/CreditCardField.ss
Normal file
6
templates/forms/CreditCardField.ss
Normal file
@ -0,0 +1,6 @@
|
||||
<span id="{$Name}_Holder" class="creditCardField">
|
||||
<input autocomplete="off" name="{$Name}[0]" value="{$ValueOne}" maxlength="4" $TabIndexOne/> -
|
||||
<input autocomplete="off" name="{$Name}[1]" value="{$ValueTwo}" maxlength="4" $TabIndexTwo/> -
|
||||
<input autocomplete="off" name="{$Name}[2]" value="{$ValueThree}" maxlength="4" $TabIndexThree/> -
|
||||
<input autocomplete="off" name="{$Name}[3]" value="{$ValueFour}" maxlength="4" $TabIndexFour/>
|
||||
</span>
|
@ -74,7 +74,10 @@ class CmsUiContext extends BehatContext
|
||||
|
||||
protected function getCmsTabsElement()
|
||||
{
|
||||
$this->getSession()->wait(5000, "window.jQuery('.cms-content-header-tabs').size() > 0");
|
||||
$this->getSession()->wait(
|
||||
5000,
|
||||
"window.jQuery && window.jQuery('.cms-content-header-tabs').size() > 0"
|
||||
);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs');
|
||||
@ -87,7 +90,7 @@ class CmsUiContext extends BehatContext
|
||||
{
|
||||
$this->getSession()->wait(
|
||||
5000,
|
||||
"window.jQuery('.cms-content-toolbar').size() > 0 "
|
||||
"window.jQuery && window.jQuery('.cms-content-toolbar').size() > 0 "
|
||||
. "&& window.jQuery('.cms-content-toolbar').children().size() > 0"
|
||||
);
|
||||
|
||||
@ -100,7 +103,10 @@ class CmsUiContext extends BehatContext
|
||||
|
||||
protected function getCmsTreeElement()
|
||||
{
|
||||
$this->getSession()->wait(5000, "window.jQuery('.cms-tree').size() > 0");
|
||||
$this->getSession()->wait(
|
||||
5000,
|
||||
"window.jQuery && window.jQuery('.cms-tree').size() > 0"
|
||||
);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$cms_tree_element = $page->find('css', '.cms-tree');
|
||||
@ -193,7 +199,10 @@ class CmsUiContext extends BehatContext
|
||||
*/
|
||||
public function iClickTheCmsTab($tab)
|
||||
{
|
||||
$this->getSession()->wait(5000, "window.jQuery('.ui-tabs-nav').size() > 0");
|
||||
$this->getSession()->wait(
|
||||
5000,
|
||||
"window.jQuery && window.jQuery('.ui-tabs-nav').size() > 0"
|
||||
);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$tabsets = $page->findAll('css', '.ui-tabs-nav');
|
||||
@ -407,7 +416,8 @@ class CmsUiContext extends BehatContext
|
||||
// wait for ajax dropdown to load
|
||||
$this->getSession()->wait(
|
||||
5000,
|
||||
"jQuery('#" . $container->getAttribute('id') . " .treedropdownfield-panel li').length > 0"
|
||||
"window.jQuery && "
|
||||
. "window.jQuery('#" . $container->getAttribute('id') . " .treedropdownfield-panel li').length > 0"
|
||||
);
|
||||
} else {
|
||||
// wait for dropdown overlay to appear (might be animated)
|
||||
|
2
tests/cache/CacheTest.php
vendored
2
tests/cache/CacheTest.php
vendored
@ -27,7 +27,7 @@ class CacheTest extends SapphireTest {
|
||||
$cache->save('Good', 'cachekey');
|
||||
$this->assertEquals('Good', $cache->load('cachekey'));
|
||||
|
||||
sleep(1);
|
||||
sleep(2);
|
||||
|
||||
$this->assertFalse($cache->load('cachekey'));
|
||||
}
|
||||
|
@ -66,10 +66,11 @@ class LookupFieldTest extends SapphireTest {
|
||||
$member1 = $this->objFromFixture('Member', 'member1');
|
||||
$member2 = $this->objFromFixture('Member', 'member2');
|
||||
$member3 = $this->objFromFixture('Member', 'member3');
|
||||
|
||||
|
||||
$source = DataObject::get('Member');
|
||||
$f = new LookupField('test', 'test', $source->map());
|
||||
$f = new LookupField('test', 'test', $source->map('ID', 'FirstName'));
|
||||
$f->setValue(array($member1->ID, $member2->ID));
|
||||
|
||||
$this->assertEquals(
|
||||
sprintf(
|
||||
'<span class="readonly" id="test">member1, member2</span>'
|
||||
|
278
tests/forms/RequiredFieldsTest.php
Normal file
278
tests/forms/RequiredFieldsTest.php
Normal file
@ -0,0 +1,278 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*
|
||||
* @todo Test the validation method php()
|
||||
*/
|
||||
class RequiredFieldsTest extends SapphireTest {
|
||||
|
||||
public function testConstructingWithArray() {
|
||||
//can we construct with an array?
|
||||
$fields = array(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
);
|
||||
$requiredFields = new RequiredFields($fields);
|
||||
//check the fields and the array match
|
||||
$this->assertEquals(
|
||||
$fields,
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to set the required fields using an array"
|
||||
);
|
||||
}
|
||||
|
||||
public function testConstructingWithArguments() {
|
||||
//can we construct with arguments?
|
||||
$requiredFields = new RequiredFields(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
);
|
||||
//check the fields match
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to set the required fields using arguments"
|
||||
);
|
||||
}
|
||||
|
||||
public function testRemoveValidation() {
|
||||
//can we remove all fields at once?
|
||||
$requiredFields = new RequiredFields(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
);
|
||||
$requiredFields->removeValidation();
|
||||
//check there are no required fields
|
||||
$this->assertEmpty(
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to remove all the required fields using 'removeValidation()'"
|
||||
);
|
||||
}
|
||||
|
||||
public function testRemoveRequiredField() {
|
||||
//set up the required fields
|
||||
$requiredFields = new RequiredFields(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
);
|
||||
//remove one
|
||||
$requiredFields->removeRequiredField('Content');
|
||||
//compare the arrays
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Title',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to remove the 'Content' field from required list"
|
||||
);
|
||||
//let's remove another
|
||||
$requiredFields->removeRequiredField('Title');
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Image',
|
||||
'AnotherField'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to remove 'Title' field from required list"
|
||||
);
|
||||
//lets try to remove one that doesn't exist
|
||||
$requiredFields->removeRequiredField('DontExists');
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Image',
|
||||
'AnotherField'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Removing a non-existant field from required list altered the list of required fields"
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddRequiredField() {
|
||||
//set up the validator
|
||||
$requiredFields = new RequiredFields(
|
||||
'Title'
|
||||
);
|
||||
//add a field
|
||||
$requiredFields->addRequiredField('Content');
|
||||
//check it was added
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Title',
|
||||
'Content'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to add a new field to the required list"
|
||||
);
|
||||
//add another for good measure
|
||||
$requiredFields->addRequiredField('Image');
|
||||
//check it was added
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to add a second new field to the required list"
|
||||
);
|
||||
//remove a field
|
||||
$requiredFields->removeRequiredField('Title');
|
||||
//check it was removed
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Content',
|
||||
'Image'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to remove 'Title' field from required list"
|
||||
);
|
||||
//add the same field back to check we can add and remove at will
|
||||
$requiredFields->addRequiredField('Title');
|
||||
//check it's in there
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Content',
|
||||
'Image',
|
||||
'Title'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Failed to add 'Title' back to the required field list"
|
||||
);
|
||||
//add a field that already exists (we can't have the same field twice, can we?)
|
||||
$requiredFields->addRequiredField('Content');
|
||||
//check the field wasn't added
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Content',
|
||||
'Image',
|
||||
'Title'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Adding a duplicate field to required field list had unexpected behaviour"
|
||||
);
|
||||
}
|
||||
|
||||
public function testAppendRequiredFields() {
|
||||
//get the validator
|
||||
$requiredFields = new RequiredFields(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
);
|
||||
//create another validator with other fields
|
||||
$otherRequiredFields = new RequiredFields(array(
|
||||
'ExtraField1',
|
||||
'ExtraField2'
|
||||
));
|
||||
//append the new fields
|
||||
$requiredFields->appendRequiredFields($otherRequiredFields);
|
||||
//check they were added correctly
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField',
|
||||
'ExtraField1',
|
||||
'ExtraField2'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Merging of required fields failed to behave as expected"
|
||||
);
|
||||
// create the standard validator so we can check duplicates are ignored
|
||||
$otherRequiredFields = new RequiredFields(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
);
|
||||
//add the new validator
|
||||
$requiredFields->appendRequiredFields($otherRequiredFields);
|
||||
//check nothing was changed
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField',
|
||||
'ExtraField1',
|
||||
'ExtraField2'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Merging of required fields with duplicates failed to behave as expected"
|
||||
);
|
||||
//add some new fields and some old ones in a strange order
|
||||
$otherRequiredFields = new RequiredFields(
|
||||
'ExtraField3',
|
||||
'Title',
|
||||
'ExtraField4',
|
||||
'Image',
|
||||
'Content'
|
||||
);
|
||||
//add the new validator
|
||||
$requiredFields->appendRequiredFields($otherRequiredFields);
|
||||
//check that only the new fields were added
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField',
|
||||
'ExtraField1',
|
||||
'ExtraField2',
|
||||
'ExtraField3',
|
||||
'ExtraField4'
|
||||
),
|
||||
$requiredFields->getRequired(),
|
||||
"Merging of required fields with some duplicates in a muddled order failed to behave as expected"
|
||||
);
|
||||
}
|
||||
|
||||
public function testFieldIsRequired() {
|
||||
//get the validator
|
||||
$requiredFields = new RequiredFields($fieldNames = array(
|
||||
'Title',
|
||||
'Content',
|
||||
'Image',
|
||||
'AnotherField'
|
||||
));
|
||||
|
||||
foreach($fieldNames as $field) {
|
||||
$this->assertTrue(
|
||||
$requiredFields->fieldIsRequired($field),
|
||||
sprintf("Failed to find '%s' field in required list", $field)
|
||||
);
|
||||
}
|
||||
|
||||
//add a new field
|
||||
$requiredFields->addRequiredField('ExtraField1');
|
||||
//check the new field is required
|
||||
$this->assertTrue(
|
||||
$requiredFields->fieldIsRequired('ExtraField1'),
|
||||
"Failed to find 'ExtraField1' field in required list after adding it to the list"
|
||||
);
|
||||
//check a non-existant field returns false
|
||||
$this->assertFalse(
|
||||
$requiredFields->fieldIsRequired('DoesntExist'),
|
||||
"Unexpectedly returned true for a non-existant field"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -115,6 +115,15 @@ class DataQueryTest extends SapphireTest {
|
||||
|
||||
$this->assertEquals($dq->sql(), $orgDq->sql());
|
||||
}
|
||||
|
||||
public function testOrderByMultiple() {
|
||||
$dq = new DataQuery('SQLQueryTest_DO');
|
||||
$dq = $dq->sort('"Name" ASC, MID("Name", 8, 1) DESC');
|
||||
$this->assertContains(
|
||||
'ORDER BY "Name" ASC, "_SortColumn0" DESC',
|
||||
$dq->sql()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -479,6 +479,31 @@ class SQLQueryTest extends SapphireTest {
|
||||
$this->assertEquals('Object 2', $records[0]['Name']);
|
||||
$this->assertEquals('2012-05-01 09:00:00', $records['0']['_SortColumn0']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that multiple order elements are maintained in the given order
|
||||
*/
|
||||
public function testOrderByMultiple() {
|
||||
if(DB::getConn() instanceof MySQLDatabase) {
|
||||
$query = new SQLQuery();
|
||||
$query->setSelect(array('"Name"', '"Meta"'));
|
||||
$query->setFrom('"SQLQueryTest_DO"');
|
||||
$query->setOrderBy(array('MID("Name", 8, 1) DESC', '"Name" ASC'));
|
||||
|
||||
$records = array();
|
||||
foreach($query->execute() as $record) {
|
||||
$records[] = $record;
|
||||
}
|
||||
|
||||
$this->assertCount(2, $records);
|
||||
|
||||
$this->assertEquals('Object 2', $records[0]['Name']);
|
||||
$this->assertEquals('2', $records[0]['_SortColumn0']);
|
||||
|
||||
$this->assertEquals('Object 1', $records[1]['Name']);
|
||||
$this->assertEquals('1', $records[1]['_SortColumn0']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test passing in a LIMIT with OFFSET clause string.
|
||||
|
Loading…
Reference in New Issue
Block a user