Merge remote-tracking branch 'origin/3.1'

Conflicts:
	_config/routes.yml
	docs/en/topics/datamodel.md
	forms/DropdownField.php
This commit is contained in:
Ingo Schommer 2014-02-04 08:19:04 +13:00
commit 0d7e9a9692
85 changed files with 823 additions and 236 deletions

View File

@ -15,9 +15,6 @@ After:
Director:
rules:
'Security//$Action/$ID/$OtherID': 'Security'
'api/v1/live': 'VersionedRestfulServer'
'api/v1': 'RestfulServer'
'soap/v1': 'SOAPModelAccess'
'dev': 'DevelopmentAdmin'
'interactive': 'SapphireREPL'
'InstallerTest//$Action/$ID/$OtherID': 'InstallerTest'

View File

@ -928,6 +928,8 @@ class LeftAndMain extends Controller implements PermissionProvider {
$data = array();
$ids = explode(',', $request->getVar('ids'));
foreach($ids as $id) {
if($id === "") continue; // $id may be a blank string, which is invalid and should be skipped over
$record = $this->getRecord($id);
$recordController = ($this->stat('tree_class') == 'SiteTree')
? singleton('CMSPageEditController')

View File

@ -317,11 +317,11 @@ fieldset.switch-states.size_2 input:checked:nth-of-type(2) ~ .slide-button { lef
fieldset.switch-states.size_2 input:checked:nth-of-type(3) ~ .slide-button { left: 100%; }
fieldset.switch-states.size_2 input:checked:nth-of-type(4) ~ .slide-button { left: 150%; }
fieldset.switch-states.size_2 input:checked:nth-of-type(5) ~ .slide-button { left: 200%; }
fieldset.switch-states.size_3 label, fieldset.switch-states.size_3 .slide-button { width: 33.333%; }
fieldset.switch-states.size_3 input:checked:nth-of-type(2) ~ .slide-button { left: 33.333%; }
fieldset.switch-states.size_3 input:checked:nth-of-type(3) ~ .slide-button { left: 66.667%; }
fieldset.switch-states.size_3 label, fieldset.switch-states.size_3 .slide-button { width: 33.33333%; }
fieldset.switch-states.size_3 input:checked:nth-of-type(2) ~ .slide-button { left: 33.33333%; }
fieldset.switch-states.size_3 input:checked:nth-of-type(3) ~ .slide-button { left: 66.66667%; }
fieldset.switch-states.size_3 input:checked:nth-of-type(4) ~ .slide-button { left: 100%; }
fieldset.switch-states.size_3 input:checked:nth-of-type(5) ~ .slide-button { left: 133.333%; }
fieldset.switch-states.size_3 input:checked:nth-of-type(5) ~ .slide-button { left: 133.33333%; }
fieldset.switch-states.size_4 label, fieldset.switch-states.size_4 .slide-button { width: 25%; }
fieldset.switch-states.size_4 input:checked:nth-of-type(2) ~ .slide-button { left: 25%; }
fieldset.switch-states.size_4 input:checked:nth-of-type(3) ~ .slide-button { left: 50%; }
@ -393,9 +393,10 @@ body.cms { overflow: hidden; }
.ui-tabs .ui-tabs-nav li { top: 0; float: left; border-bottom: 0 !important; }
.ui-tabs .ui-tabs-nav li a { display: -moz-inline-stack; display: inline-block; vertical-align: middle; *vertical-align: auto; zoom: 1; *display: inline; float: none; font-weight: bold; color: #444444; line-height: 32px; padding: 0 16px 0; }
.ui-tabs .ui-tabs-nav li:last-child { margin-right: 0; }
.ui-tabs .ui-tabs-nav li.ui-tabs-active { margin-bottom: 0; }
.ui-tabs .ui-tabs-nav .ui-state-default { border: 1px solid #c0c0c2; background: #ced7dc; }
.ui-tabs .ui-tabs-nav .ui-state-default a { color: #5e5e5e; text-shadow: #e6e6e6 0 1px 0; }
.ui-tabs .ui-tabs-nav .ui-state-active { padding-bottom: 1px; border: 1px solid #c0c0c2; background-color: #eceff1; }
.ui-tabs .ui-tabs-nav .ui-state-active { padding-bottom: 1px; border: 1px solid #c0c0c2; background-color: #e6eaed; }
.ui-tabs .ui-tabs-nav .ui-state-active a { color: #444444; }
.ui-tabs .ui-tabs-nav.ui-state-active { border-color: gray; }
.ui-tabs .ui-tabs-nav li.cms-tabset-icon { text-indent: -9999em; }
@ -724,7 +725,7 @@ form.import-form label.left { width: 250px; }
/** -------------------------------------------- Page Edit Controller -------------------------------------------- */
/*.cms-container {
.CMSPageEditController, .CMSPageSettingsController, .CMSPageHistoryController {
/* Fix pixel gap between nav tree and main page header */
// Fix pixel gap between nav tree and main page header
margin-left: -1px; // Removed to close gap far right of right tabs?
}
}*/

View File

@ -61,15 +61,15 @@
if(this.is(':checked')) {
checkboxes.each(function() {
$(this).data('SecurityAdmin.oldChecked', $(this).attr('checked'));
$(this).data('SecurityAdmin.oldDisabled', $(this).attr('disabled'));
$(this).attr('disabled', 'disabled');
$(this).attr('checked', 'checked');
$(this).data('SecurityAdmin.oldChecked', $(this).is(':checked'));
$(this).data('SecurityAdmin.oldDisabled', $(this).is(':disabled'));
$(this).prop('disabled', true);
$(this).prop('checked', true);
});
} else {
checkboxes.each(function() {
$(this).attr('checked', $(this).data('SecurityAdmin.oldChecked'));
$(this).attr('disabled', $(this).data('SecurityAdmin.oldDisabled'));
$(this).prop('checked', $(this).data('SecurityAdmin.oldChecked'));
$(this).prop('disabled', $(this).data('SecurityAdmin.oldDisabled'));
});
}
}

View File

@ -188,59 +188,63 @@ body.cms {
}
.ui-tabs-nav {
float: right;
margin: $grid-x*2 0 -1px 0;
padding: 0 $grid-x*1.5 0 0;
border-bottom: none;
float: right;
margin: $grid-x*2 0 -1px 0;
padding: 0 $grid-x*1.5 0 0;
border-bottom: none;
~ .ui-tabs-panel {
border-top:1px solid $color-button-generic-border;
clear: both;
}
~ .ui-tabs-panel {
border-top:1px solid $color-button-generic-border;
clear: both;
}
li {
top: 0;
float: left;
border-bottom: 0 !important;
top: 0;
float: left;
border-bottom: 0 !important;
a {
@include inline-block;
float: none;
font-weight: bold;
color: $color-text;
line-height: $grid-y * 4;
padding: 0 $grid-x*2 0;
}
color: $color-text;
line-height: $grid-y * 4;
padding: 0 $grid-x*2 0;
}
&:last-child {
// correctly right-align last tab
margin-right: 0;
}
&:last-child {
// correctly right-align last tab
margin-right: 0;
}
&.ui-tabs-active {
margin-bottom: 0;
}
}
.ui-state-default {
border:1px solid $color-button-generic-border;
background:darken($color-widget-bg, 10%);
background: darken($color-widget-bg, 10%);
a {
color: lighten($color-text, 10%);
text-shadow: lighten($color-tab, 5%) 0 1px 0;
}
}
}
}
.ui-state-active {
padding-bottom:1px;
border:1px solid $color-button-generic-border;
background-color: $tab-panel-texture-color;
padding-bottom: 1px;
border: 1px solid $color-button-generic-border;
background-color: darken($tab-panel-texture-color, 2%);
a {
color: $color-text;
}
}
}
&.ui-state-active {
border-color: $color-medium-separator;
}
}
li.cms-tabset-icon {
text-indent:-9999em;
@ -249,7 +253,7 @@ body.cms {
display: block;
padding-left: 40px; // icon width
padding-right: 0;
}
}
&.list a {background: sprite($sprites64, tab-list) no-repeat;}
&.tree a {background: sprite($sprites64, tab-tree) no-repeat;}
@ -262,8 +266,8 @@ body.cms {
&.gallery.ui-state-active a {background: sprite($sprites64, tab-gallery-hover) no-repeat;}
&.edit.ui-state-active a {background: sprite($sprites64, tab-edit-hover) no-repeat;}
&.search.ui-state-active a {background: sprite($sprites64, tab-search-hover) no-repeat;}
}
}
}
.cms-panel-padded {
.ui-tabs-panel {
@ -1096,7 +1100,7 @@ form.member-profile-form {
// can trigger longer pages and the extra scroll bar doesn't fire our sizing bar
overflow-y: auto;
overflow-x: auto;
background:darken($tab-panel-texture-color,2%);
background: darken($tab-panel-texture-color, 2%);
width:100%;
#Root_Main {
.confirmedpassword {

View File

@ -17,7 +17,7 @@ class CMSMenuTest extends SapphireTest implements TestOnly {
$menuItems = CMSMenu::get_menu_items();
$menuItem = $menuItems['CMSMenuTest_LeftAndMainController'];
$this->assertInstanceOf('CMSMenuItem', $menuItem, 'Controller menu item is of class CMSMenuItem');
$this->assertEquals($menuItem->url, singleton('CMSMenuTest_LeftAndMainController')->Link(),
$this->assertContains($menuItem->url, singleton('CMSMenuTest_LeftAndMainController')->Link(),
'Controller menu item has the correct link');
$this->assertEquals($menuItem->controller, 'CMSMenuTest_LeftAndMainController',
'Controller menu item has the correct controller class');
@ -71,7 +71,7 @@ class CMSMenuTest extends SapphireTest implements TestOnly {
CMSMenu::populate_menu();
$menuItem = CMSMenu::get_menu_item('SecurityAdmin');
$this->assertInstanceOf('CMSMenuItem', $menuItem, 'SecurityAdmin menu item exists');
$this->assertEquals($menuItem->url, singleton('SecurityAdmin')->Link(), 'Menu item has the correct link');
$this->assertContains($menuItem->url, singleton('SecurityAdmin')->Link(), 'Menu item has the correct link');
$this->assertEquals($menuItem->controller, 'SecurityAdmin', 'Menu item has the correct controller class');
$this->assertEquals(
$menuItem->priority,

View File

@ -459,7 +459,7 @@ class Director implements TemplateGlobalProvider {
*/
public static function protocol() {
return (self::is_https()) ? 'https://' : 'http://';
}
}
/**
* Return whether the site is running as under HTTPS.
@ -469,18 +469,23 @@ class Director implements TemplateGlobalProvider {
public static function is_https() {
if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) {
return $protocol == 'https';
}
}
if(isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) {
if(strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https') {
return true;
}
}
}
}
if(isset($_SERVER['X-Forwarded-Proto'])) {
if(strtolower($_SERVER['X-Forwarded-Proto']) == "https") {
return true;
}
}
if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
return true;
}
else if(isset($_SERVER['SSL'])) {
} else if(isset($_SERVER['SSL'])) {
return true;
}
@ -507,11 +512,11 @@ class Director implements TemplateGlobalProvider {
$baseURL = '/';
} else {
$baseURL = $base . '/';
}
}
if(defined('BASE_SCRIPT_URL')) {
return $baseURL . BASE_SCRIPT_URL;
}
}
return $baseURL;
}

View File

@ -7,6 +7,8 @@
*
* @author marcus@silverstripe.com.au
* @license BSD License http://silverstripe.org/bsd-license/
* @package framework
* @subpackage control
*/
interface RequestFilter {
/**

View File

@ -169,7 +169,7 @@ class Session {
*/
public static function set_cookie_domain($domain) {
Deprecation::notice('3.2', 'Use the "Session.cookie_domain" config setting instead');
Config::inst()->update('Session', 'cookie_domain', $age);
Config::inst()->update('Session', 'cookie_domain', $domain);
}
/**

View File

@ -3,6 +3,9 @@
/**
* A Directed Acyclic Graph - used for doing topological sorts on dependencies, such as the before/after conditions
* in config yaml fragments
*
* @package framework
* @subpackage manifest
*/
class SS_DAG implements IteratorAggregate {
/**
@ -88,10 +91,20 @@ class SS_DAG implements IteratorAggregate {
}
}
/**
* Exception thrown when the {@link SS_DAG} class is unable to resolve sorting the DAG due to cyclic dependencies.
*
* @package framework
* @subpackage manifest
*/
class SS_DAG_CyclicException extends Exception {
public $dag;
/**
* @param string $message The Exception message
* @param SS_DAG $dag The remainder of the Directed Acyclic Graph (DAG) after the last successful sort
*/
public function __construct($message, $dag) {
$this->dag = $dag;
parent::__construct($message);
@ -99,6 +112,10 @@ class SS_DAG_CyclicException extends Exception {
}
/**
* @package framework
* @subpackage manifest
*/
class SS_DAG_Iterator implements Iterator {
protected $data;

View File

@ -353,7 +353,7 @@ class PaginatedList extends SS_ListDecorator {
* @return bool
*/
public function NotLastPage() {
return $this->CurrentPage() != $this->TotalPages();
return $this->CurrentPage() < $this->TotalPages();
}
/**
@ -421,4 +421,4 @@ class PaginatedList extends SS_ListDecorator {
}
}
}
}

View File

@ -2,6 +2,9 @@
/**
* Returns the temporary folder path that silverstripe should use for its cache files.
*
* @package framework
* @subpackage core
*
* @param $base The base path to use for determining the temporary path
* @return string Path to temp
*/
@ -20,6 +23,9 @@ function getTempFolder($base = null) {
/**
* Returns as best a representation of the current username as we can glean.
*
* @package framework
* @subpackage core
*/
function getTempFolderUsername() {
$user = getenv('APACHE_RUN_USER');
@ -38,6 +44,9 @@ function getTempFolderUsername() {
* Return the parent folder of the temp folder.
* The temp folder will be a subfolder of this, named by username.
* This structure prevents permission problems.
*
* @package framework
* @subpackage core
*/
function getTempParentFolder($base = null) {
if(!$base && defined('BASE_PATH')) $base = BASE_PATH;

View File

@ -9,6 +9,7 @@
* optionally catch attempts to modify the config statics (otherwise the modification will appear
* to work, but won't actually have any effect - the equvilent of failing silently)
*
* @package framework
* @subpackage manifest
*/
class SS_ConfigStaticManifest {
@ -150,6 +151,9 @@ class SS_ConfigStaticManifest {
* We can't do this using TokenisedRegularExpression because we need to keep track of state
* as we process the token list (when we enter and leave a namespace or class, when we see
* an access level keyword, etc)
*
* @package framework
* @subpackage manifest
*/
class SS_ConfigStaticManifest_Parser {

View File

@ -2,6 +2,9 @@
/**
* A basic caching interface that manifests use to store data.
*
* @package framework
* @subpackage manifest
*/
interface ManifestCache {
public function __construct($name);
@ -12,6 +15,9 @@ interface ManifestCache {
/**
* Stores manifest data in files in TEMP_DIR dir on filesystem
*
* @package framework
* @subpackage manifest
*/
class ManifestCache_File implements ManifestCache {
function __construct($name) {
@ -37,6 +43,9 @@ class ManifestCache_File implements ManifestCache {
/**
* Same as ManifestCache_File, but stores the data as valid PHP which gets included to load
* This is a bit faster if you have an opcode cache installed, but slower otherwise
*
* @package framework
* @subpackage manifest
*/
class ManifestCache_File_PHP extends ManifestCache_File {
function load($key) {
@ -58,6 +67,9 @@ class ManifestCache_File_PHP extends ManifestCache_File {
/**
* Stores manifest data in APC.
* Note: benchmarks seem to indicate this is not particularly faster than _File
*
* @package framework
* @subpackage manifest
*/
class ManifestCache_APC implements ManifestCache {
protected $pre;

View File

@ -1,8 +1,12 @@
<?php
// Inject SilverStripe 'setUpOnce' and 'tearDownOnce' unittest extension methods into phpunit
// This is already in later SilverStripe 2.4 versions, but having it here extends compatibility to older versions
/**
* Inject SilverStripe 'setUpOnce' and 'tearDownOnce' unittest extension methods into PHPUnit.
*
* This is already in later SilverStripe 2.4 versions, but having it here extends compatibility to older versions.
*
* @package framework
* @subpackage testing
*/
class SilverStripeListener implements PHPUnit_Framework_TestListener {
protected function isValidClass($name) {

View File

@ -1,7 +1,10 @@
<?php
// Bind TeamCity test listener. Echos messages to stdout that TeamCity interprets into the test results
/**
* Bind TeamCity test listener. Echos messages to stdout that TeamCity interprets into the test results
*
* @package framework
* @subpackage testing
*/
class TeamCityListener implements PHPUnit_Framework_TestListener {
private function escape($str) {

View File

@ -76,6 +76,20 @@ page. In this situation the automatic limiting done by `[api:PaginatedList]`
will break the pagination. You can disable automatic limiting using the
`[api:PaginatedList->setLimitItems()]` method when using custom lists.
## Setting the limit of items to be displayed on a page ##
To set the limit of items displayed in a paginated page use the `[api:PaginatedList->setPageLength()]` method. e.g:
:::php
/**
* Returns a paginated list of all pages in the site, and limits the items displayed to 4 per page.
*/
public function PaginatedPagesLimit() {
$paginatedItems = new PaginatedList(Page::get(), $this->request);
$paginatedItems->setPageLength(4);
return $pagination;
}
## Related
* [Howto: "Grouping Lists"](/howto/grouping-dataobjectsets)
* [Howto: "Grouping Lists"](/howto/grouping-dataobjectsets)

19
docs/en/topics/controller.md Normal file → Executable file
View File

@ -54,6 +54,25 @@ making any code changes to your controller.
so a `MyController` class is accessible through `http://localhost/MyController`.
</div>
## Linking to a controller
Each controller has a built-in `Link()` method,
which can be used to avoid hardcoding your routing in views etc.
The method should return a value that makes sense with your custom route (see above):
:::php
<?php
class FastFood_Controller extends Controller {
public function Link($action = null) {
return Controller::join_links('fastfood', $action);
}
}
The [api:Controller::join_links()] invocation is optional, but makes `Link()` more flexible
by allowing an `$action` argument, and concatenates the path segments with slashes.
The action should map to a method on your controller. `join_links()` also supports
## Access Control
### Through $allowed_actions

View File

@ -291,7 +291,7 @@ start with S, who has logged in since 1/1/2011.
:::php
$members = Member::get()->filter(array(
'FirstName:StartsWith:Not' => 'S'
'Birthday:GreaterThan' => '2011-01-01'
'LastVisited:GreaterThan' => '2011-01-01'
));
### Subtract

View File

@ -323,19 +323,14 @@ in your `mysite/_config.php`:
## Javascript Usage
i18n in javascript works with mostly the same assumption as its PHP-equivalent.
The i18n system in JavaScript is similar to its PHP equivalent.
Languages are typically stored in `<my-module-dir>/javascript/lang`.
Unlike the PHP logic, these files aren't auto-discovered and have to be included manually.
### Requirements
Add the i18n library requirement to your code.
:::php
Requirements::javascript(FRAMEWORK_DIR . "/javascript/i18n.js");
Each language has its own language table in a separate file.
To save bandwidth, only three tables are actually loaded by
To save bandwidth, only two files are actually loaded by
the browser: The current locale, and the default locale as a fallback.
The `Requirements` class has a special method to determine these includes:
Just point it to a directory instead of a file, and the class will figure out the includes.
@ -346,30 +341,31 @@ Just point it to a directory instead of a file, and the class will figure out th
### Translation Tables in JavaScript
Translation tables are automatically included as required, depending on the configured locale in *i18n::get_locale()*.
As a fallback for partially translated tables we always include the master table (en_US.js) as well.
Translation tables are automatically included as required, depending on the configured locale in `i18n::get_locale()`.
As a fallback for partially translated tables we always include the master table (`en.js`) as well.
Master Table (mymodule/javascript/lang/en_US.js)
Master Table (`<my-module-dir>/javascript/lang/en.js`)
:::js
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('en_US', {
ss.i18n.addDictionary('en', {
'MYMODULE.MYENTITY' : "Really delete these articles?"
});
}
Example Translation Table (mymodule/javascript/lang/de_DE.js)
Example Translation Table (`<my-module-dir>/javascript/lang/de.js`)
:::js
ss.i18n.addDictionary('de_DE', {
ss.i18n.addDictionary('de', {
'MYMODULE.MYENTITY' : "Artikel wirklich löschen?"
});
For most core modules, these files are generated by a
[build task](https://github.com/silverstripe/silverstripe-buildtools/blob/master/src/GenerateJavascriptI18nTask.php), with the actual source files in a JSON
[build task](https://github.com/silverstripe/silverstripe-buildtools/blob/master/src/GenerateJavascriptI18nTask.php),
with the actual source files in a JSON
format which can be processed more easily by external translation providers (see `javascript/lang/src`).
### Basic Usage
@ -386,6 +382,7 @@ format which can be processed more easily by external translation providers (see
ss.i18n._t('MYMODULE.MYENTITY'),
42,
'Douglas Adams'
));
// Displays: "Really delete 42 articles by Douglas Adams?"

View File

@ -8,16 +8,17 @@ example "framework" and "cms". These two modules are the core functionality and
templates for any initial installation.
If you want to add generic functionality that isn't specific to your
project, like a forum, an ecommerce package or a blog you can do it like this;
project, like a forum, an ecommerce package or a blog you can do it like this:
1. Create another directory at the root level (same level as "framework" and
"cms")
2. You must create a _config.php inside your module directory, or else
SilverStripe will not include it
3. Inside your module directory, follow our [directory structure guidelines](/topics/directory-structure#module_structure)
1. Create another directory at the root level (same level as "framework"
and "cms"). This will contain all your module files.
2. The module directory must contain a `_config` sub-directory, or a
`_config.php` file to be recognised.
3. Inside your module directory, follow our
[directory structure guidelines](/topics/directory-structure#module_structure)
As long as your module has a `_config.php` file inside it, SilverStripe will
automatically include any PHP classes from that module.
Once this is done, SilverStripe will automatically include any PHP classes and
templates from within your module.
## Tips
@ -93,7 +94,9 @@ provide an extension to [SiteConfig](/reference/siteconfig).
If you wish to submit your module to our public directory, you take
responsibility for a certain level of code quality, adherence to conventions,
writing documentation, and releasing updates. See
[contributing](/misc/contributing).
[contributing](/misc/contributing). All modules should be published
on [addons.silverstripe.org](http://addons.silverstripe.org) to make them
discoverable by others.
### Composer and Packagist
@ -109,60 +112,67 @@ A basic usage of a module for 3.1 that requires the CMS would look similar to
this:
{
"name": "yourname/silverstripe-modulename",
"description": "..",
"type": "silverstripe-module",
"keywords": ["silverstripe", ".."],
"license": "BSD-3-Clause",
"authors": [{
"name": "Your Name",
"email": "Your Email"
}],
"require": {
"silverstripe/framework": ">=3.1.x-dev,<4.0"
}
"name": "your-vendor-name/module-name",
"description": "One-liner describing your module",
"type": "silverstripe-module",
"homepage": "http://github.com/your-vendor-name/module-name",
"keywords": ["silverstripe", "some-tag", "some-other-tag"],
"license": "BSD-3-Clause",
"authors": [
{"name": "Your Name","email": "your@email.com"}
],
"support": {
"issues": "http://github.com/your-vendor-name/module-name/issues"
},
"require": {
"silverstripe/cms": "~3.1",
"silverstripe/framework": "~3.1"
},
"extra": {
"installer-name": "module-name",
"screenshots": [
"relative/path/screenshot1.png",
"http://myhost.com/screenshot2.png"
]
}
}
Once your module is released, submit it to [Packagist](https://packagist.org/)
to have the module accessible to developers.
to have the module accessible to developers. It'll automatically get picked
up by [addons.silverstripe.org](http://addons.silverstripe.org/).
### Versioning
Over time you may have to release new versions of your module to continue to
work with newer versions of SilverStripe. By using composer, this is made easy
work with newer versions of SilverStripe. By using Composer, this is made easy
for developers by allowing them to specify what version they want to use. Each
version of your module should be a separate branch in your version control and
each branch should have a `composer.json` file explicitly defining what versions
of SilverStripe you support.
<div class="notice" markdown='1'>
The convention to follow for support is the `master` or `trunk` branch of your
code should always be the one to work with the `master` branch of SilverStripe.
Other branches should be created as needed for other SilverStripe versions you
want to support.
</div>
Say you have a module which supports SilverStripe 3.0.
A new release of this module takes advantage of new features
in SilverStripe 3.1. In this case, you would create a new branch
for the 3.0 compatible codebase of your module.
This allows you to continue fixing bugs on this older release branch.
For example, if you release a module for 3.0 which works well but doesn't work
in 3.1.0 you should provide a separate `branch` of the module for 3.0 support.
As a convention, the `master` or `trunk` branch of your
module should always work with the `master` branch of SilverStripe.
Other branches should be created on your module as needed if they're
required to support specific SilverStripe releases.
// for module that supports 3.0.1. (git branch 1.0)
"require": {
"silverstripe/framework": "3.0.*",
}
You can have an overlap in supported versions,
e.g two branches in your module both support SilverStripe 3.1.
In this case, you should explain the differences in your `README.md` file.
// for branch of the module that only supports 3.1 (git branch master)
"require": {
"silverstripe/framework": ">=3.1.*",
}
You can have an overlap in supported versions (e.g two branches for 3.1) but you
should explain the differences in your `README.md` file.
If you want to change the minimum supported version of your module, make sure
you create a new branch which continues to support the minimum version as it
stands before you update the main branch.
Here's some common values for your `require` section
(see [getcomposer.org](http://getcomposer.org/doc/01-basic-usage.md#package-versions) for details):
* `3.0.*`: Version `3.0`, including `3.0.1`, `3.0.2` etc, excluding `3.1`
* `~3.0`: Version `3.0` or higher, including `3.0.1` and `3.1` etc, excluding `4.0`
* `~3.0,<3.2`: Version `3.0` or higher, up until `3.2`, which is excluded
* `~3.0,>3.0.4`: Version `3.0` or higher, starting with `3.0.4`
## Reference

View File

@ -112,11 +112,10 @@ The base_tag variable is replaced with the HTML [base element](http://www.w3.org
ensures the browser knows where to locate your site's images and css files.
:::ss
$MetaTitle
$Title
$SiteConfig.Title
These three variables are found within the html `<title>` tag, and are replaced by the text set in the "Meta Title", "Page Name", or "Settings -> Site Title" fields in the CMS.
These two variables are found within the html `<title>` tag, and are replaced by the "Page Name" and "Settings -> Site Title" fields in the CMS.
:::ss
$MetaTags

View File

@ -437,6 +437,8 @@ class Mailer extends Object {
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function htmlEmail($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false,
@ -449,6 +451,8 @@ function htmlEmail($to, $from, $subject, $htmlContent, $attachedFiles = false, $
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function plaintextEmail($to, $from, $subject, $plainContent, $attachedFiles, $customheaders = false) {
@ -459,6 +463,8 @@ function plaintextEmail($to, $from, $subject, $plainContent, $attachedFiles, $cu
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function encodeMultipart($parts, $contentType, $headers = false) {
@ -469,6 +475,8 @@ function encodeMultipart($parts, $contentType, $headers = false) {
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function wrapImagesInline($htmlContent) {
@ -479,6 +487,8 @@ function wrapImagesInline($htmlContent) {
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function wrapImagesInline_rewriter($url) {
@ -490,6 +500,8 @@ function wrapImagesInline_rewriter($url) {
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function processHeaders($headers, $body = false) {
@ -500,6 +512,8 @@ function processHeaders($headers, $body = false) {
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") {
@ -510,6 +524,8 @@ function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function QuotedPrintable_encode($quotprint) {
@ -520,6 +536,8 @@ function QuotedPrintable_encode($quotprint) {
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function validEmailAddr($emailAddress) {

View File

@ -61,6 +61,18 @@
*
* @package framework
* @subpackage filesystem
*
* @property string Name Basename of the file
* @property string Title Title of the file
* @property string Filename Filename including path
* @property string Content
* @property string ShowInSearch Boolean that indicates if file is shown in search. Doesn't apply to Folder
*
* @property int ParentID ID of parent File/Folder
* @property int OwnerID ID of Member who owns the file
*
* @method File Parent() Returns parent File
* @method Member Owner() Returns Member object of file owner.
*/
class File extends DataObject {
@ -596,9 +608,14 @@ class File extends DataObject {
$base = pathinfo($name, PATHINFO_BASENAME);
$ext = self::get_file_extension($name);
$suffix = 1;
while(DataObject::get_one("File", "\"Name\" = '" . Convert::raw2sql($name)
. "' AND \"ParentID\" = " . (int)$this->ParentID)) {
while(File::get()->filter(array(
'Name' => $name,
'ParentID' => (int) $this->ParentID
))->exclude(array(
'ID' => $this->ID
))->first()
) {
$suffix++;
$name = "$base-$suffix$ext";
}

View File

@ -486,7 +486,10 @@ class GDBackend extends Object implements Image_Backend {
}
/**
* Backwards compatibility
* This class is maintained for backwards-compatibility only. Please use the {@link GDBackend} class instead.
*
* @package framework
* @subpackage filesystem
*/
class GD extends GDBackend {

View File

@ -90,8 +90,13 @@ class CheckboxSetField extends OptionsetField {
if($values instanceof SS_List || is_array($values)) {
$items = $values;
} else {
$items = explode(',', $values);
$items = str_replace('{comma}', ',', $items);
if($values === null) {
$items = array();
}
else {
$items = explode(',', $values);
$items = str_replace('{comma}', ',', $items);
}
}
}

View File

@ -53,6 +53,13 @@ class ConfirmedPasswordField extends FormField {
* @param boolean $showOnClick
*/
protected $showOnClick = false;
/**
* A place to temporarly store the confirm password value
* @var string
*/
protected $confirmValue;
/**
* Title for the link that triggers the visibility of password fields.
@ -255,10 +262,8 @@ class ConfirmedPasswordField extends FormField {
$oldValue = $this->value;
if(is_array($value)) {
//only set the value if it's valid!
if($this->validate(RequiredFields::create())) {
$this->value = $value['_Password'];
}
$this->value = $value['_Password'];
$this->confirmValue = $value['_ConfirmPassword'];
if($this->showOnClick && isset($value['_PasswordFieldVisible'])) {
$this->children->fieldByName($this->getName() . '[_PasswordFieldVisible]')
@ -282,6 +287,20 @@ class ConfirmedPasswordField extends FormField {
return $this;
}
/**
* Update the names of the child fields when updating name of field.
*
* @param string $name new name to give to the field.
*/
public function setName($name) {
$this->children->fieldByName($this->getName() . '[_Password]')
->setName($name . '[_Password]');
$this->children->fieldByName($this->getName() . '[_ConfirmPassword]')
->setName($name . '[_ConfirmPassword]');
return parent::setName($name);
}
/**
* Determines if the field was actually shown on the client side - if not,
* we don't validate or save it.
@ -309,9 +328,9 @@ class ConfirmedPasswordField extends FormField {
$passwordField = $this->children->fieldByName($name.'[_Password]');
$passwordConfirmField = $this->children->fieldByName($name.'[_ConfirmPassword]');
$passwordField->setValue($_POST[$name]['_Password']);
$passwordConfirmField->setValue($_POST[$name]['_ConfirmPassword']);
$passwordField->setValue($this->value);
$passwordConfirmField->setValue($this->confirmValue);
$value = $passwordField->Value();
// both password-fields should be the same

View File

@ -164,9 +164,24 @@ class DropdownField extends FormField {
if ($source) {
foreach($source as $value => $title) {
// check against value, fallback to a type check comparison when !value
$selected = ($value) ? ($value == $this->value) : ($value === $this->value);
$disabled = (in_array($value, $this->disabledItems, true)) ? 'disabled' : false;
$selected = false;
if($value === '' && ($this->value === '' || $this->value === null)) {
$selected = true;
} else {
// check against value, fallback to a type check comparison when !value
if($value) {
$selected = ($value == $this->value);
} else {
$selected = ($value === $this->value) || (((string) $value) === ((string) $this->value));
}
$this->isSelected = $selected;
}
$disabled = false;
if(in_array($value, $this->disabledItems) && $title != $this->emptyString ){
$disabled = 'disabled';
}
$options[] = new ArrayData(array(
'Title' => $title,

View File

@ -356,32 +356,33 @@ class FormField extends RequestHandler {
}
/**
* Add one or more CSS-classes to the formfield-container.
* Add one or more CSS-classes to the formfield-container. Multiple class
* names should be space delimited.
*
* @param $class String
* @param string $class
*/
public function addExtraClass($class) {
//split at white space to extract all the classes
$classes = preg_split('/\s+/', $class);
foreach ($classes as $class) {
//add each class one by one
$this->extraClasses[$class] = $class;
$this->extraClasses[$class] = $class;
}
return $this;
}
/**
* Remove one or more CSS-classes from the formfield-container.
*
* @param $class String
* @param string $class
*/
public function removeExtraClass($class) {
//split at white space to extract all the classes
$classes = preg_split('/\s+/', $class);
foreach ($classes as $class) {
//unset each class one by one
unset($this->extraClasses[$class]);
}
return $this;
}

View File

@ -759,6 +759,8 @@ class HtmlEditorField_Toolbar extends RequestHandler {
* such as file name or the URL.
*
* @todo Remove once core has support for remote files
* @package forms
* @subpackage fields-formattedinput
*/
class HtmlEditorField_File extends ViewableData {
@ -827,6 +829,13 @@ class HtmlEditorField_File extends ViewableData {
}
/**
* Encapsulation of an oembed tag, linking to an external media source.
*
* @see Oembed
* @package forms
* @subpackage fields-formattedinput
*/
class HtmlEditorField_Embed extends HtmlEditorField_File {
protected $oembed;
@ -910,6 +919,12 @@ class HtmlEditorField_Embed extends HtmlEditorField_File {
}
}
/**
* Encapsulation of an image tag, linking to an image either internal or external to the site.
*
* @package forms
* @subpackage fields-formattedinput
*/
class HtmlEditorField_Image extends HtmlEditorField_File {
protected $width;

View File

@ -7,7 +7,8 @@
* See www.tinymce.com/wiki.php/configuration:valid_elements for details on the spec of TinyMCE's
* whitelist configuration
*
* Class HtmlEditorSanitiser
* @package forms
* @subpackage fields-formattedinput
*/
class HtmlEditorSanitiser {

View File

@ -6,7 +6,7 @@
*
* @author Ingo Schommer, SilverStripe Ltd. (<firstname>@silverstripe.com)
*
* @package framework
* @package forms
* @subpackage fields-formattedinput
*/
class MoneyField extends FormField {
@ -120,6 +120,8 @@ class MoneyField extends FormField {
*/
public function performReadonlyTransformation() {
$clone = clone $this;
$clone->fieldAmount = $clone->fieldAmount->performReadonlyTransformation();
$clone->fieldCurrency = $clone->fieldCurrency->performReadonlyTransformation();
$clone->setReadonly(true);
return $clone;
}

View File

@ -22,8 +22,8 @@
* </code>
*
* @author Zauberfisch
* @package framework
* @subpackage forms
* @package forms
* @subpackages fields-files
*/
class UploadField extends FileField {
@ -1349,8 +1349,8 @@ class UploadField extends FileField {
* RequestHandler for actions (edit, remove, delete) on a single item (File) of the UploadField
*
* @author Zauberfisch
* @package framework
* @subpackage forms
* @package forms
* @subpackages fields-files
*/
class UploadField_ItemHandler extends RequestHandler {
@ -1510,6 +1510,9 @@ class UploadField_ItemHandler extends RequestHandler {
/**
* File selection popup for attaching existing files.
*
* @package forms
* @subpackages fields-files
*/
class UploadField_SelectHandler extends RequestHandler {

View File

@ -15,7 +15,7 @@
*
* @see SS_List
*
* @package framework
* @package forms
* @subpackage fields-gridfield
* @property GridState_Data $State The gridstate of this object
*/
@ -765,7 +765,7 @@ class GridField extends FormField {
* This class is the base class when you want to have an action that alters
* the state of the {@link GridField}, rendered as a button element.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridField_FormAction extends FormAction {

View File

@ -15,7 +15,7 @@
* For easier setup, have a look at a sample configuration in
* {@link GridFieldConfig_RelationEditor}.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldAddExistingAutocompleter

View File

@ -6,7 +6,7 @@
* Only returns a button if {@link DataObject->canCreate()} for this record
* returns true.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldAddNewButton implements GridField_HTMLProvider {

View File

@ -8,7 +8,7 @@
* This row provides two new HTML fragment spaces: 'toolbar-header-left' and
* 'toolbar-header-right'.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldButtonRow implements GridField_HTMLProvider {

View File

@ -3,7 +3,7 @@
/**
* Base interface for all components that can be added to GridField.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
interface GridFieldComponent {
@ -13,7 +13,7 @@ interface GridFieldComponent {
* A GridField manipulator that provides HTML for the header/footer rows, or f
* or before/after the template.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
interface GridField_HTMLProvider extends GridFieldComponent {
@ -43,7 +43,7 @@ interface GridField_HTMLProvider extends GridFieldComponent {
*
* Used once per record/row.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
interface GridField_ColumnProvider extends GridFieldComponent {
@ -110,7 +110,7 @@ interface GridField_ColumnProvider extends GridFieldComponent {
*
* @see {@link GridField_FormAction}.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
interface GridField_ActionProvider extends GridFieldComponent {
@ -151,7 +151,7 @@ interface GridField_ActionProvider extends GridFieldComponent {
* Generally, the data manipulator will make use of to {@link GridState}
* variables to decide how to modify the {@link DataList}.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
interface GridField_DataManipulator extends GridFieldComponent {
@ -177,7 +177,7 @@ interface GridField_DataManipulator extends GridFieldComponent {
* For example a URL that will return JSON-formatted data for a javascript
* grid control.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
interface GridField_URLHandler extends GridFieldComponent {
@ -196,7 +196,7 @@ interface GridField_URLHandler extends GridFieldComponent {
* A component which is used to handle when a {@link GridField} is saved into
* a record.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
interface GridField_SaveHandler extends GridFieldComponent {

View File

@ -16,7 +16,7 @@
* - {@link GridFieldConfig_RecordEditor}
* - {@link GridFieldConfig_RelationEditor}
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldConfig extends Object {
@ -129,7 +129,7 @@ class GridFieldConfig extends Object {
* A simple readonly, paginated view of records, with sortable and searchable
* headers.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldConfig_Base extends GridFieldConfig {
@ -157,7 +157,7 @@ class GridFieldConfig_Base extends GridFieldConfig {
/**
* Allows viewing readonly details of individual records.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldConfig_RecordViewer extends GridFieldConfig_Base {
@ -174,7 +174,10 @@ class GridFieldConfig_RecordViewer extends GridFieldConfig_Base {
}
/**
* @package framework
* Allows editing of records contained within the GridField, instead of only allowing the ability to view records in
* the GridField.
*
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldConfig_RecordEditor extends GridFieldConfig {
@ -223,7 +226,7 @@ class GridFieldConfig_RecordEditor extends GridFieldConfig {
* ->setSearchFields('MyField');
* </code>
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldConfig_RelationEditor extends GridFieldConfig {

View File

@ -2,7 +2,7 @@
/**
* @see GridField
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldDataColumns implements GridField_ColumnProvider {

View File

@ -15,7 +15,7 @@
* $action = new GridFieldDeleteAction(true);
* </code>
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_ActionProvider {

View File

@ -13,7 +13,7 @@
* - <FormURL>/field/<GridFieldName>/item/<RecordID>
* - <FormURL>/field/<GridFieldName>/item/<RecordID>/edit
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldDetailForm implements GridField_URLHandler {
@ -90,6 +90,12 @@ class GridFieldDetailForm implements GridField_URLHandler {
$handler = Object::create($class, $gridField, $this, $record, $controller, $this->name);
$handler->setTemplate($this->template);
// if no validator has been set on the GridField and the record has a
// CMS validator, use that.
if(!$this->getValidator() && method_exists($record, 'getCMSValidator')) {
$this->setValidator($record->getCMSValidator());
}
return $handler->handleRequest($request, DataModel::inst());
}
@ -190,7 +196,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
}
/**
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldDetailForm_ItemRequest extends RequestHandler {

View File

@ -10,7 +10,7 @@
* The default routing applies to the {@link GridFieldDetailForm} component,
* which has to be added separately to the {@link GridField} configuration.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldEditButton implements GridField_ColumnProvider {

View File

@ -3,7 +3,7 @@
/**
* Adds an "Export list" button to the bottom of a {@link GridField}.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/

View File

@ -5,7 +5,7 @@
*
* @see GridField
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {

View File

@ -12,7 +12,7 @@
* The purpose of this class is to have a footer that can round off
* {@link GridField} without having to use pagination.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldFooter implements GridField_HTMLProvider {

View File

@ -4,7 +4,7 @@
* hierarchical data. Requires the managed record to have a "getParent()"
* method or has_one relationship called "Parent".
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldLevelup extends Object implements GridField_HTMLProvider {

View File

@ -6,7 +6,7 @@
*
* Depends on {@link GridFieldPaginator} being added to the {@link GridField}.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldPageCount implements GridField_HTMLProvider {

View File

@ -3,7 +3,7 @@
* GridFieldPaginator paginates the {@link GridField} list and adds controls
* to the bottom of the {@link GridField}.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {

View File

@ -3,7 +3,7 @@
/**
* Adds an "Print" button to the bottom or top of a GridField.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionProvider, GridField_URLHandler {

View File

@ -6,7 +6,7 @@
*
* @see GridField
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {

View File

@ -6,7 +6,7 @@
*
* The header serves to display the name of the data the GridField is showing.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldToolbarHeader implements GridField_HTMLProvider {

View File

@ -4,7 +4,7 @@
* disabled by default and intended for use in readonly {@link GridField}
* instances.
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridFieldViewButton implements GridField_ColumnProvider {

View File

@ -7,7 +7,7 @@
*
* @see GridField
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridState extends HiddenField {
@ -121,7 +121,7 @@ class GridState extends HiddenField {
*
* @see GridState
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridState_Data {
@ -192,7 +192,7 @@ class GridState_Data {
/**
* @see GridState
*
* @package framework
* @package forms
* @subpackage fields-gridfield
*/
class GridState_Component implements GridField_HTMLProvider {

View File

@ -6,7 +6,6 @@ require_once 'Zend/Translate/Adapter.php';
* @package framework
* @subpackage i18n
*/
class i18nSSLegacyAdapter extends Zend_Translate_Adapter implements i18nTranslateAdapterInterface {
/**
@ -65,6 +64,10 @@ class i18nSSLegacyAdapter extends Zend_Translate_Adapter implements i18nTranslat
}
/**
* @package framework
* @subpackage i18n
*/
class i18nSSLegacyAdapter_Iterator extends RecursiveIteratorIterator {
protected $keyStack = array();

View File

@ -483,6 +483,9 @@ class i18nTextCollector extends Object {
/**
* Allows serialization of entity definitions collected through {@link i18nTextCollector}
* into a persistent format, usually on the filesystem.
*
* @package framework
* @subpackage i18n
*/
interface i18nTextCollector_Writer {
/**
@ -499,6 +502,9 @@ interface i18nTextCollector_Writer {
/**
* Legacy writer for 2.x style persistence.
*
* @package framework
* @subpackage i18n
*/
class i18nTextCollector_Writer_Php implements i18nTextCollector_Writer {
@ -577,6 +583,9 @@ class i18nTextCollector_Writer_Php implements i18nTextCollector_Writer {
/**
* Writes files compatible with {@link i18nRailsYamlAdapter}.
*
* @package framework
* @subpackage i18n
*/
class i18nTextCollector_Writer_RailsYaml implements i18nTextCollector_Writer {
@ -631,6 +640,9 @@ class i18nTextCollector_Writer_RailsYaml implements i18nTextCollector_Writer {
/**
* Parser that scans through a template and extracts the parameters to the _t and <%t calls
*
* @package framework
* @subpackage i18n
*/
class i18nTextCollector_Parser extends SSTemplateParser {

View File

@ -10,8 +10,6 @@
$('.permissioncheckboxset .valADMIN input').entwine({
onmatch: function() {
this._super();
this.toggleCheckboxes();
},
onunmatch: function() {
this._super();
@ -55,30 +53,29 @@
}).find('.checkbox').not(this);
},
onmatch: function() {
var checkboxes = this.getCheckboxesExceptThisOne();
if($(this).is(':checked')) {
checkboxes.each(function() {
$(this).attr('disabled', 'disabled');
$(this).attr('checked', 'checked');
});
}
this.toggleCheckboxes();
this._super();
},
onunmatch: function() {
this._super();
},
onclick: function(e) {
this.toggleCheckboxes();
},
toggleCheckboxes: function() {
var checkboxes = this.getCheckboxesExceptThisOne();
if($(this).is(':checked')) {
checkboxes.each(function() {
$(this).attr('disabled', 'disabled');
$(this).attr('checked', 'checked');
$(this).data('PermissionCheckboxSetField.oldChecked', $(this).is(':checked'));
$(this).data('PermissionCheckboxSetField.oldDisabled', $(this).is(':disabled'));
$(this).prop('disabled', 'disabled');
$(this).prop('checked', 'checked');
});
} else {
checkboxes.each(function() {
$(this).prop('checked', false);
$(this).prop('disabled', false);
$(this).prop('checked', $(this).data('PermissionCheckboxSetField.oldChecked'));
$(this).prop('disabled', $(this).data('PermissionCheckboxSetField.oldDisabled'));
});
}
}

View File

@ -281,9 +281,14 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
*/
public function map($keyfield = 'ID', $titlefield = 'Title') {
$map = array();
foreach ($this->items as $item) {
$map[$this->extractValue($item, $keyfield)] = $this->extractValue($item, $titlefield);
$map[$this->extractValue($item, $keyfield)] = $this->extractValue(
$item,
$titlefield
);
}
return $map;
}
@ -296,7 +301,9 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
*/
public function find($key, $value) {
foreach ($this->items as $item) {
if ($this->extractValue($item, $key) == $value) return $item;
if ($this->extractValue($item, $key) == $value) {
return $item;
}
}
}
@ -308,9 +315,11 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
*/
public function column($colName = 'ID') {
$result = array();
foreach ($this->items as $item) {
$result[] = $this->extractValue($item, $colName);
}
return $result;
}
@ -410,9 +419,11 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
*/
public function canFilterBy($by) {
$firstRecord = $this->first();
if ($firstRecord === false) {
return false;
}
return array_key_exists($by, $firstRecord);
}
@ -470,9 +481,11 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
public function byID($id) {
$firstElement = $this->filter("ID", $id)->first();
if ($firstElement === false) {
return null;
}
return $firstElement;
}
@ -490,10 +503,13 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
gettype($callback)
));
}
$output = ArrayList::create();
foreach($this as $item) {
if(call_user_func($callback, $item, $this)) $output->push($item);
}
return $output;
}
@ -588,7 +604,11 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
* @param mixed $value
*/
public function offsetSet($offset, $value) {
$this->items[$offset] = $value;
if($offset == null) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
}
/**

View File

@ -65,6 +65,11 @@
*
* @package framework
* @subpackage model
*
* @property integer ID ID of the DataObject, 0 if the DataObject doesn't exist in database.
* @property string ClassName Class name of the DataObject
* @property string LastEdited Date and time of DataObject's last modification.
* @property string Created Date and time of DataObject creation.
*/
class DataObject extends ViewableData implements DataObjectInterface, i18nEntityProvider {

View File

@ -326,7 +326,7 @@ abstract class SS_Database {
$this->transCreateTable($table, $options, $extensions);
$this->alterationMessage("Table $table: created","created");
} else {
if(Config::inst()->get('Database', 'check_and_repair_on_build')) {
if(Config::inst()->get('SS_Database', 'check_and_repair_on_build')) {
$this->checkAndRepairTable($table, $options);
}
@ -1050,4 +1050,4 @@ abstract class SS_Database {
public function releaseLock($name) {
return false;
}
}
}

View File

@ -7,6 +7,8 @@
* applied, rather than applying the filter in place
*
* @see SS_List, SS_Sortable, SS_Limitable
* @package framework
* @subpackage model
*/
interface SS_Filterable {

View File

@ -7,6 +7,8 @@
* applied, rather than applying the limit in place
*
* @see SS_List, SS_Sortable, SS_Filterable
* @package framework
* @subpackage model
*/
interface SS_Limitable {

View File

@ -7,6 +7,8 @@
* applied, rather than applying the sort in place
*
* @see SS_List, SS_Filterable, SS_Limitable
* @package framework
* @subpackage model
*/
interface SS_Sortable {

View File

@ -32,6 +32,7 @@ class ValidationResult extends Object {
* Record an error against this validation result,
* @param $message The validation error message
* @param $code An optional error code string, that can be accessed with {@link $this->codeList()}.
* @return ValidationResult this
*/
public function error($message, $code = null) {
$this->isValid = false;
@ -47,10 +48,13 @@ class ValidationResult extends Object {
} else {
$this->errorList[] = $message;
}
return $this;
}
/**
* Returns true if the result is valid.
* @return boolean
*/
public function valid() {
return $this->isValid;
@ -58,6 +62,7 @@ class ValidationResult extends Object {
/**
* Get an array of errors
* @return array
*/
public function messageList() {
return $this->errorList;
@ -65,6 +70,7 @@ class ValidationResult extends Object {
/**
* Get an array of error codes
* @return array
*/
public function codeList() {
$codeList = array();
@ -74,6 +80,7 @@ class ValidationResult extends Object {
/**
* Get the error message as a string.
* @return string
*/
public function message() {
return implode("; ", $this->errorList);
@ -81,6 +88,7 @@ class ValidationResult extends Object {
/**
* Get a starred list of all messages
* @return string
*/
public function starredList() {
return " * " . implode("\n * ", $this->errorList);
@ -90,10 +98,15 @@ class ValidationResult extends Object {
* Combine this Validation Result with the ValidationResult given in other.
* It will be valid if both this and the other result are valid.
* This object will be modified to contain the new validation information.
*
* @param ValidationResult the validation result object to combine
* @return ValidationResult this
*/
public function combineAnd(ValidationResult $other) {
$this->isValid = $this->isValid && $other->valid();
$this->errorList = array_merge($this->errorList, $other->messageList());
return $this;
}

View File

@ -1341,6 +1341,22 @@ class Versioned extends DataExtension {
public function cacheKeyComponent() {
return 'versionedmode-'.self::get_reading_mode();
}
/**
* Returns an array of possible stages.
*
* @return array
*/
public function getVersionedStages() {
return $this->stages;
}
/**
* @return string
*/
public function getDefaultStage() {
return $this->defaultStage;
}
}
/**

View File

@ -5,7 +5,7 @@
* within a HTMLText or HTMLVarchar field when rendered into a template. The API is inspired by and very similar to the
* [Wordpress implementation](http://codex.wordpress.org/Shortcode_API) of shortcodes.
*
* @see http://doc.silverstripe.org/framework/en/topics/shortcodes
* @see http://doc.silverstripe.org/framework/en/reference/shortcodes
* @package framework
* @subpackage misc
*/

View File

@ -4,6 +4,20 @@
*
* @package framework
* @subpackage security
*
* @property string Title Name of the group
* @property string Description Description of the group
* @property string Code Group code
* @property string Locked Boolean indicating whether group is locked in security panel
* @property int Sort
* @property string HtmlEditorConfig
*
* @property int ParentID ID of parent group
*
* @method Group Parent() Return parent group
* @method HasManyList Permissions() List of group permissions
* @method HasManyList Groups() List of child groups
* @method ManyManyList Roles() List of PermissionRoles
*/
class Group extends DataObject {

View File

@ -11,6 +11,14 @@
*
* @package framework
* @subpackage security
*
* @property string Email Email address used for login attempt
* @property string Status Status of the login attempt, either 'Success' or 'Failure'
* @property string IP IP address of user attempting to login
*
* @property int MemberID ID of the Member, only if Member with Email exists
*
* @method Member Member() Member object of the user trying to log in, only if Member with Email exists
*/
class LoginAttempt extends DataObject {

View File

@ -4,6 +4,24 @@
*
* @package framework
* @subpackage security
*
* @property string FirstName
* @property string Surname
* @property string Email
* @property string Password
* @property string RememberLoginHash
* @property int NumVisit
* @property string LastVisited Date and time of last visit
* @property string AutoLoginHash
* @property string AutoLoginExpired
* @property string PasswordEncryption
* @property string Salt
* @property string PasswordExpiry
* @property string LockedOutUntil
* @property string Locale
* @property int FailedLoginCount
* @property string DateFormat
* @property string TimeFormat
*/
class Member extends DataObject implements TemplateGlobalProvider {
@ -627,14 +645,13 @@ class Member extends DataObject implements TemplateGlobalProvider {
/**
* Returns the current logged in user
*
* @return bool|Member Returns the member object of the current logged in
* user or FALSE.
* @return Member|null
*/
public static function currentUser() {
$id = Member::currentUserID();
if($id) {
return DataObject::get_one("Member", "\"Member\".\"ID\" = $id", true, 1);
return Member::get()->byId($id);
}
}

View File

@ -3,6 +3,14 @@
* Keep track of users' previous passwords, so that we can check that new passwords aren't changed back to old ones.
* @package framework
* @subpackage security
*
* @property string Password
* @property string Salt
* @property string PasswordEncryption
*
* @property int MemberID ID of the Member
*
* @method Member Member() Owner of the password
*/
class MemberPassword extends DataObject {
private static $db = array(

View File

@ -3,6 +3,14 @@
* Represents a permission assigned to a group.
* @package framework
* @subpackage security
*
* @property string Code
* @property int Arg
* @property int Type
*
* @property int GroupID
*
* @method Group Group()
*/
class Permission extends DataObject implements TemplateGlobalProvider {

View File

@ -1,9 +1,11 @@
<?php
/**
* Throw this exception to register that a user doesn't have permission to do the given action
* and potentially redirect them to the log-in page. The exception message may be presented to the
* user, so it shouldn't be in nerd-speak.
*
* @package framework
* @subpackage security
*/
class PermissionFailureException extends Exception {

View File

@ -12,6 +12,12 @@
*
* @package framework
* @subpackage security
*
* @property string Title
* @property string OnlyAdminCanApply
*
* @method HasManyList Codes() List of PermissionRoleCode objects
* @method ManyManyList Groups() List of Group objects
*/
class PermissionRole extends DataObject {
private static $db = array(

View File

@ -4,6 +4,12 @@
*
* @package framework
* @subpackage security
*
* @property string Code
*
* @property int RoleID
*
* @method PermissionRole Role()
*/
class PermissionRoleCode extends DataObject {
private static $db = array(

View File

@ -1,4 +1,4 @@
<div <% if $Name %>id="$Name"<% end_if %> class="field $Type">
<div <% if $Name %>id="$Name"<% end_if %> class="field <% if $extraClass %>$extraClass<% end_if %>">
<% if $Title %><label class="left">$Title</label><% end_if %>
<div class="middleColumn fieldgroup<% if $Zebra %> fieldgroup-$Zebra<% end_if %>">

View File

@ -89,20 +89,32 @@ class CmsFormsContext extends BehatContext {
}
/**
* @Then /^the "(?P<locator>([^"]*))" HTML field should contain "(?P<html>.*)"$/
* @Then /^the "(?P<locator>([^"]*))" HTML field should(?P<negative> not? |\s*)contain "(?P<html>.*)"$/
*/
public function theHtmlFieldShouldContain($locator, $html) {
public function theHtmlFieldShouldContain($locator, $negative, $html) {
$page = $this->getSession()->getPage();
$element = $page->findField($locator);
assertNotNull($element, sprintf('HTML field "%s" not found', $locator));
$actual = $element->getValue();
$regex = '/'.preg_quote($html, '/').'/ui';
if (!preg_match($regex, $actual)) {
$failed = false;
if(trim($negative)) {
if (preg_match($regex, $actual)) {
$failed = true;
}
} else {
if (!preg_match($regex, $actual)) {
$failed = true;
}
}
if($failed) {
$message = sprintf(
'The string "%s" was not found in the HTML of the element matching %s "%s". Actual content: "%s"',
$html,
'named',
'The string "%s" should%sbe found in the HTML of the element matching name "%s". Actual content: "%s"',
$html,
$negative,
$locator,
$actual
);

View File

@ -1,9 +1,11 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class CheckboxSetFieldTest extends SapphireTest {
protected static $fixture_file = 'CheckboxSetFieldTest.yml';
protected $extraDataObjects = array(
@ -144,7 +146,13 @@ class CheckboxSetFieldTest extends SapphireTest {
}
/**
* @package framework
* @subpackage tests
*/
class CheckboxSetFieldTest_Article extends DataObject implements TestOnly {
private static $db = array(
"Content" => "Text",
);
@ -155,7 +163,12 @@ class CheckboxSetFieldTest_Article extends DataObject implements TestOnly {
}
/**
* @package framework
* @subpackage tests
*/
class CheckboxSetFieldTest_Tag extends DataObject implements TestOnly {
private static $belongs_many_many = array(
'Articles' => 'CheckboxSetFieldTest_Article'
);

View File

@ -4,6 +4,7 @@
* @subpackage tests
*/
class ConfirmedPasswordFieldTest extends SapphireTest {
public function testSetValue() {
$field = new ConfirmedPasswordField('Test', 'Testing', 'valueA');
$this->assertEquals('valueA', $field->Value());
@ -52,4 +53,23 @@ class ConfirmedPasswordFieldTest extends SapphireTest {
$this->assertNotContains("showOnClick", $fieldHTML,
"Test class for hiding/showing the form contents is set");
}
public function testValidation() {
$field = new ConfirmedPasswordField('Test', 'Testing', array(
"_Password" => "abc123",
"_ConfirmPassword" => "abc123"
));
$validator = new RequiredFields();
$form = new Form($this, 'Form', new FieldList($field), new FieldList(), $validator);
$this->assertTrue($field->validate($validator));
$field->setName("TestNew"); //try changing name of field
$this->assertTrue($field->validate($validator));
//non-matching password should make the field invalid
$field->setValue(array(
"_Password" => "abc123",
"_ConfirmPassword" => "123abc"
));
$this->assertFalse($field->validate($validator));
}
}

View File

@ -93,7 +93,52 @@ class DropdownFieldTest extends SapphireTest {
'Two options exist in the markup, one for the source, one for empty'
);
}
public function testStringZeroValueSelectedOptionBehaviour() {
$field = new DropdownField('Field', null, array(
'-1' => 'some negative',
'0' => 'none',
'1' => 'one',
'2+' => 'two or more'
), '0');
$selectedOptions = $this->findSelectedOptionElements($field->Field());
$this->assertEquals((string) $selectedOptions[0], 'none', 'The selected option is "none"');
$field = new DropdownField('Field', null, array(
'-1' => 'some negative',
'0' => 'none',
'1' => 'one',
'2+' => 'two or more'
), 0);
$selectedOptions = $this->findSelectedOptionElements($field->Field());
$this->assertEquals((string) $selectedOptions[0], 'none', 'The selected option is "none"');
}
public function testStringOneValueSelectedOptionBehaviour() {
$field = new DropdownField('Field', null, array(
'-1' => 'some negative',
'0' => 'none',
'1' => 'one',
'2+' => 'two or more'
), '1');
$selectedOptions = $this->findSelectedOptionElements($field->Field());
$this->assertEquals((string) $selectedOptions[0], 'one', 'The selected option is "one"');
$field = new DropdownField('Field', null, array(
'-1' => 'some negative',
'0' => 'none',
'1' => 'one',
'2+' => 'two or more'
), 1);
$selectedOptions = $this->findSelectedOptionElements($field->Field());
$this->assertEquals((string) $selectedOptions[0], 'one', 'The selected option is "one"');
}
public function testNumberOfSelectOptionsAvailable() {
/* Create a field with a blank value */
$field = $this->createDropdownField('(Any)');

View File

@ -1,6 +1,11 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest extends FunctionalTest {
protected static $fixture_file = 'GridFieldDetailFormTest.yml';
protected $extraDataObjects = array(
@ -9,6 +14,48 @@ class GridFieldDetailFormTest extends FunctionalTest {
'GridFieldDetailFormTest_Category',
);
public function testValidator() {
$this->logInWithPermission('ADMIN');
$response = $this->get('GridFieldDetailFormTest_Controller');
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$addlinkitem = $parser->getBySelector('.ss-gridfield .new-link');
$addlink = (string) $addlinkitem[0]['href'];
$response = $this->get($addlink);
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$addform = $parser->getBySelector('#Form_ItemEditForm');
$addformurl = (string) $addform[0]['action'];
$response = $this->post(
$addformurl,
array(
'FirstName' => 'Jeremiah',
'ajax' => 1,
'action_doSave' => 1
)
);
$parser = new CSSContentParser($response->getBody());
$errors = $parser->getBySelector('span.required');
$this->assertEquals(1, count($errors));
$response = $this->post(
$addformurl,
array(
'ajax' => 1,
'action_doSave' => 1
)
);
$parser = new CSSContentParser($response->getBody());
$errors = $parser->getBySelector('span.required');
$this->assertEquals(2, count($errors));
}
public function testAddForm() {
$this->logInWithPermission('ADMIN');
$group = GridFieldDetailFormTest_PeopleGroup::get()
@ -247,7 +294,13 @@ class GridFieldDetailFormTest extends FunctionalTest {
}
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
private static $db = array(
'FirstName' => 'Varchar',
'Surname' => 'Varchar'
@ -280,8 +333,19 @@ class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
);
return $fields;
}
public function getCMSValidator() {
return new RequiredFields(array(
'FirstName', 'Surname'
));
}
}
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest_PeopleGroup extends DataObject implements TestOnly {
private static $db = array(
'Name' => 'Varchar'
@ -306,6 +370,11 @@ class GridFieldDetailFormTest_PeopleGroup extends DataObject implements TestOnly
}
}
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest_Category extends DataObject implements TestOnly {
private static $db = array(
@ -331,6 +400,11 @@ class GridFieldDetailFormTest_Category extends DataObject implements TestOnly {
}
}
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest_Controller extends Controller implements TestOnly {
private static $allowed_actions = array('Form');
@ -354,6 +428,11 @@ class GridFieldDetailFormTest_Controller extends Controller implements TestOnly
}
}
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest_GroupController extends Controller implements TestOnly {
private static $allowed_actions = array('Form');
@ -370,6 +449,11 @@ class GridFieldDetailFormTest_GroupController extends Controller implements Test
}
}
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest_CategoryController extends Controller implements TestOnly {
private static $allowed_actions = array('Form');
@ -391,5 +475,8 @@ class GridFieldDetailFormTest_CategoryController extends Controller implements T
}
}
class GridFieldDetailFormTest_ItemRequest extends GridFieldDetailForm_ItemRequest implements TestOnly {
}
/**
* @package framework
* @subpackage tests
*/
class GridFieldDetailFormTest_ItemRequest extends GridFieldDetailForm_ItemRequest implements TestOnly { }

View File

@ -5,6 +5,20 @@
*/
class ArrayListTest extends SapphireTest {
public function testPushOperator() {
$list = new ArrayList(array(
array('Num' => 1)
));
$list[] = array('Num' => 2);
$this->assertEquals(2, count($list));
$this->assertEquals(array('Num' => 2), $list->last());
$list[] = array('Num' => 3);
$this->assertEquals(3, count($list));
$this->assertEquals(array('Num' => 3), $list->last());
}
public function testArrayAccessExists() {
$list = new ArrayList(array(
$one = new DataObject(array('Title' => 'one')),

View File

@ -27,8 +27,8 @@ class ValidationExceptionTest extends SapphireTest
*/
public function testCreateFromComplexValidationResult() {
$result = new ValidationResult();
$result->error('Invalid type');
$result->error('Out of kiwis');
$result->error('Invalid type')
->error('Out of kiwis');
$exception = new ValidationException($result);
$this->assertEquals(0, $exception->getCode());
@ -71,8 +71,8 @@ class ValidationExceptionTest extends SapphireTest
*/
public function testCreateWithComplexValidationResultAndMessage() {
$result = new ValidationResult();
$result->error('A spork is not a knife');
$result->error('A knife is not a back scratcher');
$result->error('A spork is not a knife')
->error('A knife is not a back scratcher');
$exception = new ValidationException($result, 'An error has occurred', E_USER_WARNING);
$this->assertEquals(E_USER_WARNING, $exception->getCode());
@ -81,4 +81,28 @@ class ValidationExceptionTest extends SapphireTest
$this->assertEquals('A spork is not a knife; A knife is not a back scratcher',
$exception->getResult()->message());
}
/**
* Test combining validation results together
*/
public function testCombineResults(){
$result = new ValidationResult();
$anotherresult = new ValidationResult();
$yetanotherresult = new ValidationResult();
$anotherresult->error("Eat with your mouth closed", "EATING101");
$yetanotherresult->error("You didn't wash your hands", "BECLEAN");
$this->assertTrue($result->valid());
$this->assertFalse($anotherresult->valid());
$this->assertFalse($yetanotherresult->valid());
$result->combineAnd($anotherresult)
->combineAnd($yetanotherresult);
$this->assertFalse($result->valid());
$this->assertEquals(array(
"EATING101" => "Eat with your mouth closed",
"BECLEAN" => "You didn't wash your hands"
),$result->messageList());
}
}

View File

@ -20,6 +20,9 @@ else {
* This is the exception raised when failing to parse a template. Note that we don't currently do any static analysis,
* so we can't know if the template will run, just if it's malformed. It also won't catch mistakes that still look
* valid.
*
* @package framework
* @subpackage view
*/
class SSTemplateParseException extends Exception {
@ -64,6 +67,9 @@ class SSTemplateParseException extends Exception {
*
* Angle Bracket: angle brackets "<" and ">" are used to eat whitespace between template elements
* N: eats white space including newlines (using in legacy _t support)
*
* @package framework
* @subpackage view
*/
class SSTemplateParser extends Parser implements TemplateParser {

View File

@ -41,6 +41,9 @@ else {
* This is the exception raised when failing to parse a template. Note that we don't currently do any static analysis,
* so we can't know if the template will run, just if it's malformed. It also won't catch mistakes that still look
* valid.
*
* @package framework
* @subpackage view
*/
class SSTemplateParseException extends Exception {
@ -85,6 +88,9 @@ class SSTemplateParseException extends Exception {
*
* Angle Bracket: angle brackets "<" and ">" are used to eat whitespace between template elements
* N: eats white space including newlines (using in legacy _t support)
*
* @package framework
* @subpackage view
*/
class SSTemplateParser extends Parser implements TemplateParser {

View File

@ -18,7 +18,9 @@
* We also keep the index of the current starting point for lookups. A lookup is a sequence of obj calls -
* when in a loop or with tag the end result becomes the new scope, but for injections, we throw away the lookup
* and revert back to the original scope once we've got the value we're after
*
*
* @package framework
* @subpackage view
*/
class SSViewer_Scope {
@ -182,6 +184,13 @@ class SSViewer_Scope {
}
}
/**
* Defines an extra set of basic methods that can be used in templates
* that are not defined on sub-classes of {@link ViewableData}.
*
* @package framework
* @subpackage view
*/
class SSViewer_BasicIteratorSupport implements TemplateIteratorProvider {
protected $iteratorPos;
@ -345,6 +354,9 @@ class SSViewer_BasicIteratorSupport implements TemplateIteratorProvider {
* (like $FirstLast etc).
*
* It's separate from SSViewer_Scope to keep that fairly complex code as clean as possible.
*
* @package framework
* @subpackage view
*/
class SSViewer_DataPresenter extends SSViewer_Scope {