diff --git a/_config/routes.yml b/_config/routes.yml index 5a269498e..77971cd7c 100644 --- a/_config/routes.yml +++ b/_config/routes.yml @@ -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' diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index b729d3dba..eab0c6e28 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -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') diff --git a/admin/css/screen.css b/admin/css/screen.css index 9717f15f8..aaf2502c3 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -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? } }*/ diff --git a/admin/javascript/SecurityAdmin.js b/admin/javascript/SecurityAdmin.js index cb37954f2..44c8ef8bf 100644 --- a/admin/javascript/SecurityAdmin.js +++ b/admin/javascript/SecurityAdmin.js @@ -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')); }); } } diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index ea8570cfd..1c5ea4db2 100644 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -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 { diff --git a/admin/tests/CMSMenuTest.php b/admin/tests/CMSMenuTest.php index f256ffa86..b96aa55f1 100644 --- a/admin/tests/CMSMenuTest.php +++ b/admin/tests/CMSMenuTest.php @@ -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, diff --git a/control/Director.php b/control/Director.php index f9f2410f3..6a2beb744 100644 --- a/control/Director.php +++ b/control/Director.php @@ -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; } diff --git a/control/RequestFilter.php b/control/RequestFilter.php index 35fbb4a1b..f0cb0d823 100644 --- a/control/RequestFilter.php +++ b/control/RequestFilter.php @@ -7,6 +7,8 @@ * * @author marcus@silverstripe.com.au * @license BSD License http://silverstripe.org/bsd-license/ + * @package framework + * @subpackage control */ interface RequestFilter { /** diff --git a/control/Session.php b/control/Session.php index 0610d4fb1..5940e92aa 100644 --- a/control/Session.php +++ b/control/Session.php @@ -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); } /** diff --git a/core/DAG.php b/core/DAG.php index a4c7fae41..db90b67c6 100644 --- a/core/DAG.php +++ b/core/DAG.php @@ -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; diff --git a/core/PaginatedList.php b/core/PaginatedList.php index acb81fd71..8a27ccb91 100644 --- a/core/PaginatedList.php +++ b/core/PaginatedList.php @@ -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 { } } -} \ No newline at end of file +} diff --git a/core/TempPath.php b/core/TempPath.php index 7a21e60d1..b64f2609d 100644 --- a/core/TempPath.php +++ b/core/TempPath.php @@ -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; diff --git a/core/manifest/ConfigStaticManifest.php b/core/manifest/ConfigStaticManifest.php index 7583dae42..79885cd0d 100644 --- a/core/manifest/ConfigStaticManifest.php +++ b/core/manifest/ConfigStaticManifest.php @@ -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 { diff --git a/core/manifest/ManifestCache.php b/core/manifest/ManifestCache.php index 93e339d11..65466f70f 100644 --- a/core/manifest/ManifestCache.php +++ b/core/manifest/ManifestCache.php @@ -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; diff --git a/dev/SilverStripeListener.php b/dev/SilverStripeListener.php index 009591f82..d5c0a4437 100644 --- a/dev/SilverStripeListener.php +++ b/dev/SilverStripeListener.php @@ -1,8 +1,12 @@ 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) \ No newline at end of file + * [Howto: "Grouping Lists"](/howto/grouping-dataobjectsets) diff --git a/docs/en/topics/controller.md b/docs/en/topics/controller.md old mode 100644 new mode 100755 index 17e08529b..36815910e --- a/docs/en/topics/controller.md +++ b/docs/en/topics/controller.md @@ -54,6 +54,25 @@ making any code changes to your controller. so a `MyController` class is accessible through `http://localhost/MyController`. +## 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 + filter(array( 'FirstName:StartsWith:Not' => 'S' - 'Birthday:GreaterThan' => '2011-01-01' + 'LastVisited:GreaterThan' => '2011-01-01' )); ### Subtract diff --git a/docs/en/topics/i18n.md b/docs/en/topics/i18n.md index 4ec46c5b3..8dfaef3cb 100644 --- a/docs/en/topics/i18n.md +++ b/docs/en/topics/i18n.md @@ -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 `/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 (`/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 (`/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?" diff --git a/docs/en/topics/module-development.md b/docs/en/topics/module-development.md index 581c48b4e..0f31b9755 100644 --- a/docs/en/topics/module-development.md +++ b/docs/en/topics/module-development.md @@ -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. -
-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. -
+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 diff --git a/docs/en/tutorials/1-building-a-basic-site.md b/docs/en/tutorials/1-building-a-basic-site.md index 4df89cf9a..e89f81f66 100644 --- a/docs/en/tutorials/1-building-a-basic-site.md +++ b/docs/en/tutorials/1-building-a-basic-site.md @@ -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 `` 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 diff --git a/email/Mailer.php b/email/Mailer.php index ba5dda1d4..37f7bc885 100644 --- a/email/Mailer.php +++ b/email/Mailer.php @@ -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) { diff --git a/filesystem/File.php b/filesystem/File.php index 4ee662ab9..a12a8fbe5 100644 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -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"; } diff --git a/filesystem/GD.php b/filesystem/GD.php index 6788b2597..de657e0e6 100644 --- a/filesystem/GD.php +++ b/filesystem/GD.php @@ -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 { diff --git a/forms/CheckboxSetField.php b/forms/CheckboxSetField.php index c4bed6cbe..7cafc2661 100644 --- a/forms/CheckboxSetField.php +++ b/forms/CheckboxSetField.php @@ -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); + } } } diff --git a/forms/ConfirmedPasswordField.php b/forms/ConfirmedPasswordField.php index c8202b394..37983f9fb 100644 --- a/forms/ConfirmedPasswordField.php +++ b/forms/ConfirmedPasswordField.php @@ -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 diff --git a/forms/DropdownField.php b/forms/DropdownField.php index e6e97ca30..de37c71fe 100644 --- a/forms/DropdownField.php +++ b/forms/DropdownField.php @@ -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, diff --git a/forms/FormField.php b/forms/FormField.php index f0d212203..513f02d1c 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -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; } diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index 21b147e0b..ad2a642e1 100644 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -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; diff --git a/forms/HtmlEditorSanitiser.php b/forms/HtmlEditorSanitiser.php index 98b78faf7..a4aa1fe4a 100644 --- a/forms/HtmlEditorSanitiser.php +++ b/forms/HtmlEditorSanitiser.php @@ -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 { diff --git a/forms/MoneyField.php b/forms/MoneyField.php index 129a1b25c..77254045a 100644 --- a/forms/MoneyField.php +++ b/forms/MoneyField.php @@ -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; } diff --git a/forms/UploadField.php b/forms/UploadField.php index 97c75261e..305852a9f 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -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 { diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index f53b58d6e..59973f941 100644 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -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 { diff --git a/forms/gridfield/GridFieldAddExistingAutocompleter.php b/forms/gridfield/GridFieldAddExistingAutocompleter.php index 4f83ee156..57836e02e 100644 --- a/forms/gridfield/GridFieldAddExistingAutocompleter.php +++ b/forms/gridfield/GridFieldAddExistingAutocompleter.php @@ -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 diff --git a/forms/gridfield/GridFieldAddNewButton.php b/forms/gridfield/GridFieldAddNewButton.php index 8eb5f67ef..8442187ae 100644 --- a/forms/gridfield/GridFieldAddNewButton.php +++ b/forms/gridfield/GridFieldAddNewButton.php @@ -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 { diff --git a/forms/gridfield/GridFieldButtonRow.php b/forms/gridfield/GridFieldButtonRow.php index 2c0f9738e..2a50012ee 100644 --- a/forms/gridfield/GridFieldButtonRow.php +++ b/forms/gridfield/GridFieldButtonRow.php @@ -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 { diff --git a/forms/gridfield/GridFieldComponent.php b/forms/gridfield/GridFieldComponent.php index f4ddf7175..d3cc73025 100644 --- a/forms/gridfield/GridFieldComponent.php +++ b/forms/gridfield/GridFieldComponent.php @@ -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 { diff --git a/forms/gridfield/GridFieldConfig.php b/forms/gridfield/GridFieldConfig.php index 5d0131126..57d668d82 100644 --- a/forms/gridfield/GridFieldConfig.php +++ b/forms/gridfield/GridFieldConfig.php @@ -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 { diff --git a/forms/gridfield/GridFieldDataColumns.php b/forms/gridfield/GridFieldDataColumns.php index f3ae0f4f2..d307fc0a8 100644 --- a/forms/gridfield/GridFieldDataColumns.php +++ b/forms/gridfield/GridFieldDataColumns.php @@ -2,7 +2,7 @@ /** * @see GridField * - * @package framework + * @package forms * @subpackage fields-gridfield */ class GridFieldDataColumns implements GridField_ColumnProvider { diff --git a/forms/gridfield/GridFieldDeleteAction.php b/forms/gridfield/GridFieldDeleteAction.php index 8a5f0521f..cbe33dbee 100644 --- a/forms/gridfield/GridFieldDeleteAction.php +++ b/forms/gridfield/GridFieldDeleteAction.php @@ -15,7 +15,7 @@ * $action = new GridFieldDeleteAction(true); * </code> * - * @package framework + * @package forms * @subpackage fields-gridfield */ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_ActionProvider { diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php index 108af996d..432987a01 100644 --- a/forms/gridfield/GridFieldDetailForm.php +++ b/forms/gridfield/GridFieldDetailForm.php @@ -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 { diff --git a/forms/gridfield/GridFieldEditButton.php b/forms/gridfield/GridFieldEditButton.php index 7aff78041..18801bc47 100644 --- a/forms/gridfield/GridFieldEditButton.php +++ b/forms/gridfield/GridFieldEditButton.php @@ -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 { diff --git a/forms/gridfield/GridFieldExportButton.php b/forms/gridfield/GridFieldExportButton.php index 04ec2396b..08a90060c 100644 --- a/forms/gridfield/GridFieldExportButton.php +++ b/forms/gridfield/GridFieldExportButton.php @@ -3,7 +3,7 @@ /** * Adds an "Export list" button to the bottom of a {@link GridField}. * - * @package framework + * @package forms * @subpackage fields-gridfield */ diff --git a/forms/gridfield/GridFieldFilterHeader.php b/forms/gridfield/GridFieldFilterHeader.php index b5b2d9d47..560e385a8 100755 --- a/forms/gridfield/GridFieldFilterHeader.php +++ b/forms/gridfield/GridFieldFilterHeader.php @@ -5,7 +5,7 @@ * * @see GridField * - * @package framework + * @package forms * @subpackage fields-gridfield */ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider { diff --git a/forms/gridfield/GridFieldFooter.php b/forms/gridfield/GridFieldFooter.php index 8fb674715..8ef3d9f5a 100644 --- a/forms/gridfield/GridFieldFooter.php +++ b/forms/gridfield/GridFieldFooter.php @@ -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 { diff --git a/forms/gridfield/GridFieldLevelup.php b/forms/gridfield/GridFieldLevelup.php index d15ed27d1..fbcddbd4c 100644 --- a/forms/gridfield/GridFieldLevelup.php +++ b/forms/gridfield/GridFieldLevelup.php @@ -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 { diff --git a/forms/gridfield/GridFieldPageCount.php b/forms/gridfield/GridFieldPageCount.php index b87affa13..f86e3e8c3 100755 --- a/forms/gridfield/GridFieldPageCount.php +++ b/forms/gridfield/GridFieldPageCount.php @@ -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 { diff --git a/forms/gridfield/GridFieldPaginator.php b/forms/gridfield/GridFieldPaginator.php index ef9418442..7af5bd2e3 100755 --- a/forms/gridfield/GridFieldPaginator.php +++ b/forms/gridfield/GridFieldPaginator.php @@ -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 { diff --git a/forms/gridfield/GridFieldPrintButton.php b/forms/gridfield/GridFieldPrintButton.php index 606d41db1..6adcf7a87 100644 --- a/forms/gridfield/GridFieldPrintButton.php +++ b/forms/gridfield/GridFieldPrintButton.php @@ -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 { diff --git a/forms/gridfield/GridFieldSortableHeader.php b/forms/gridfield/GridFieldSortableHeader.php index c0b9a5735..8c265416f 100644 --- a/forms/gridfield/GridFieldSortableHeader.php +++ b/forms/gridfield/GridFieldSortableHeader.php @@ -6,7 +6,7 @@ * * @see GridField * - * @package framework + * @package forms * @subpackage fields-gridfield */ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider { diff --git a/forms/gridfield/GridFieldToolbarHeader.php b/forms/gridfield/GridFieldToolbarHeader.php index 3f774212f..83af9e8c4 100644 --- a/forms/gridfield/GridFieldToolbarHeader.php +++ b/forms/gridfield/GridFieldToolbarHeader.php @@ -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 { diff --git a/forms/gridfield/GridFieldViewButton.php b/forms/gridfield/GridFieldViewButton.php index 05a74ed19..c18190e3a 100644 --- a/forms/gridfield/GridFieldViewButton.php +++ b/forms/gridfield/GridFieldViewButton.php @@ -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 { diff --git a/forms/gridfield/GridState.php b/forms/gridfield/GridState.php index b9edec273..79f0b5ce6 100644 --- a/forms/gridfield/GridState.php +++ b/forms/gridfield/GridState.php @@ -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 { diff --git a/i18n/i18nSSLegacyAdapter.php b/i18n/i18nSSLegacyAdapter.php index b39c4a1f8..0bf497f53 100644 --- a/i18n/i18nSSLegacyAdapter.php +++ b/i18n/i18nSSLegacyAdapter.php @@ -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(); diff --git a/i18n/i18nTextCollector.php b/i18n/i18nTextCollector.php index beeabc165..cb65db1d2 100644 --- a/i18n/i18nTextCollector.php +++ b/i18n/i18nTextCollector.php @@ -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 { diff --git a/javascript/PermissionCheckboxSetField.js b/javascript/PermissionCheckboxSetField.js index 5e234befe..04205e95e 100644 --- a/javascript/PermissionCheckboxSetField.js +++ b/javascript/PermissionCheckboxSetField.js @@ -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')); }); } } diff --git a/model/ArrayList.php b/model/ArrayList.php index e0d4d1616..9d06e8219 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -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; + } } /** diff --git a/model/DataObject.php b/model/DataObject.php index 6406fe431..027ee4aac 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -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 { diff --git a/model/Database.php b/model/Database.php index 024c6cf19..752eb8153 100644 --- a/model/Database.php +++ b/model/Database.php @@ -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; } -} \ No newline at end of file +} diff --git a/model/Filterable.php b/model/Filterable.php index cd3bee3a9..d534ad96e 100644 --- a/model/Filterable.php +++ b/model/Filterable.php @@ -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 { diff --git a/model/Limitable.php b/model/Limitable.php index 633762d2a..b2489887b 100644 --- a/model/Limitable.php +++ b/model/Limitable.php @@ -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 { diff --git a/model/Sortable.php b/model/Sortable.php index c3432a945..98b8881b3 100644 --- a/model/Sortable.php +++ b/model/Sortable.php @@ -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 { diff --git a/model/ValidationResult.php b/model/ValidationResult.php index dee93bbc4..540c0277e 100644 --- a/model/ValidationResult.php +++ b/model/ValidationResult.php @@ -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; } diff --git a/model/Versioned.php b/model/Versioned.php index 3634ad7fd..37868633a 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -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; + } } /** diff --git a/parsers/ShortcodeParser.php b/parsers/ShortcodeParser.php index ed7e659d9..c091a835a 100644 --- a/parsers/ShortcodeParser.php +++ b/parsers/ShortcodeParser.php @@ -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 */ diff --git a/security/Group.php b/security/Group.php index 65d4a48bc..eba1e1dbb 100755 --- a/security/Group.php +++ b/security/Group.php @@ -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 { diff --git a/security/LoginAttempt.php b/security/LoginAttempt.php index 5d39350fe..43c4917bc 100644 --- a/security/LoginAttempt.php +++ b/security/LoginAttempt.php @@ -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 { diff --git a/security/Member.php b/security/Member.php index 5305d273d..ed5a8f5af 100644 --- a/security/Member.php +++ b/security/Member.php @@ -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); } } diff --git a/security/MemberPassword.php b/security/MemberPassword.php index 95b4256dd..f6b4e3bf4 100644 --- a/security/MemberPassword.php +++ b/security/MemberPassword.php @@ -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( diff --git a/security/Permission.php b/security/Permission.php index fa62cf15c..deb79111d 100644 --- a/security/Permission.php +++ b/security/Permission.php @@ -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 { diff --git a/security/PermissionFailureException.php b/security/PermissionFailureException.php index 97b50e39c..bc64ce7cd 100644 --- a/security/PermissionFailureException.php +++ b/security/PermissionFailureException.php @@ -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 { diff --git a/security/PermissionRole.php b/security/PermissionRole.php index f0775c691..e9e9c77cd 100644 --- a/security/PermissionRole.php +++ b/security/PermissionRole.php @@ -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( diff --git a/security/PermissionRoleCode.php b/security/PermissionRoleCode.php index eac50f061..3f623372b 100644 --- a/security/PermissionRoleCode.php +++ b/security/PermissionRoleCode.php @@ -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( diff --git a/templates/forms/FieldGroup_holder.ss b/templates/forms/FieldGroup_holder.ss index 37811c28f..0fe48fece 100644 --- a/templates/forms/FieldGroup_holder.ss +++ b/templates/forms/FieldGroup_holder.ss @@ -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 %>"> diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php index e73b8b73c..5ae293d9a 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php @@ -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 ); diff --git a/tests/forms/CheckboxSetFieldTest.php b/tests/forms/CheckboxSetFieldTest.php index 609084782..ac4bc22d6 100644 --- a/tests/forms/CheckboxSetFieldTest.php +++ b/tests/forms/CheckboxSetFieldTest.php @@ -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' ); diff --git a/tests/forms/ConfirmedPasswordFieldTest.php b/tests/forms/ConfirmedPasswordFieldTest.php index abe8a9794..ec22fb600 100644 --- a/tests/forms/ConfirmedPasswordFieldTest.php +++ b/tests/forms/ConfirmedPasswordFieldTest.php @@ -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)); + } + } diff --git a/tests/forms/DropdownFieldTest.php b/tests/forms/DropdownFieldTest.php index fb976dab2..bdde7e4b4 100644 --- a/tests/forms/DropdownFieldTest.php +++ b/tests/forms/DropdownFieldTest.php @@ -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)'); diff --git a/tests/forms/gridfield/GridFieldDetailFormTest.php b/tests/forms/gridfield/GridFieldDetailFormTest.php index f94cfed8b..9b4b9a87a 100644 --- a/tests/forms/gridfield/GridFieldDetailFormTest.php +++ b/tests/forms/gridfield/GridFieldDetailFormTest.php @@ -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 { -} \ No newline at end of file +/** + * @package framework + * @subpackage tests + */ +class GridFieldDetailFormTest_ItemRequest extends GridFieldDetailForm_ItemRequest implements TestOnly { } diff --git a/tests/model/ArrayListTest.php b/tests/model/ArrayListTest.php index 7f0e2477e..1c9ce1a6c 100644 --- a/tests/model/ArrayListTest.php +++ b/tests/model/ArrayListTest.php @@ -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')), diff --git a/tests/model/ValidationExceptionTest.php b/tests/model/ValidationExceptionTest.php index 011fc4eba..568e87482 100644 --- a/tests/model/ValidationExceptionTest.php +++ b/tests/model/ValidationExceptionTest.php @@ -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()); + } + } diff --git a/view/SSTemplateParser.php b/view/SSTemplateParser.php index 1e815dbe5..f0f6f4f33 100644 --- a/view/SSTemplateParser.php +++ b/view/SSTemplateParser.php @@ -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 { diff --git a/view/SSTemplateParser.php.inc b/view/SSTemplateParser.php.inc index 500068d19..5a750c7dd 100644 --- a/view/SSTemplateParser.php.inc +++ b/view/SSTemplateParser.php.inc @@ -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 { diff --git a/view/SSViewer.php b/view/SSViewer.php index 44ae67aa2..71271cbed 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -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 {