mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Merge remote-tracking branch 'origin/3.1' into 3.2
Conflicts: dev/SapphireTest.php docs/en/02_Developer_Guides/01_Templates/01_Syntax.md forms/DatetimeField.php forms/NullableField.php forms/NumericField.php forms/gridfield/GridField.php tests/control/DirectorTest.php tests/model/DataObjectSchemaGenerationTest.php tests/model/MySQLDatabaseTest.php
This commit is contained in:
commit
1d122803cc
@ -557,11 +557,11 @@ body.cms { overflow: hidden; }
|
|||||||
.cms-content-batchactions { float: left; position: relative; display: block; }
|
.cms-content-batchactions { float: left; position: relative; display: block; }
|
||||||
.cms-content-batchactions .view-mode-batchactions-wrapper { height: 18px; float: left; padding: 4px 6px; border: 1px solid #aaa; margin-bottom: 8px; margin-right: -1px; background-color: #D9D9D9; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background-image: -moz-linear-gradient(top, #ffffff, #d9d9d9); background-image: -webkit-linear-gradient(top, #ffffff, #d9d9d9); background-image: linear-gradient(to bottom, #ffffff, #d9d9d9); border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
|
.cms-content-batchactions .view-mode-batchactions-wrapper { height: 18px; float: left; padding: 4px 6px; border: 1px solid #aaa; margin-bottom: 8px; margin-right: -1px; background-color: #D9D9D9; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background-image: -moz-linear-gradient(top, #ffffff, #d9d9d9); background-image: -webkit-linear-gradient(top, #ffffff, #d9d9d9); background-image: linear-gradient(to bottom, #ffffff, #d9d9d9); border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
|
||||||
.cms-content-batchactions .view-mode-batchactions-wrapper input { vertical-align: middle; }
|
.cms-content-batchactions .view-mode-batchactions-wrapper input { vertical-align: middle; }
|
||||||
.cms-content-batchactions .view-mode-batchactions-wrapper label { vertical-align: middle; display: none; }
|
.cms-content-batchactions .view-mode-batchactions-wrapper .view-mode-batchactions-label { vertical-align: middle; display: none; }
|
||||||
.cms-content-batchactions .view-mode-batchactions-wrapper fieldset, .cms-content-batchactions .view-mode-batchactions-wrapper .Actions { display: inline-block; }
|
.cms-content-batchactions .view-mode-batchactions-wrapper fieldset, .cms-content-batchactions .view-mode-batchactions-wrapper .Actions { display: inline-block; }
|
||||||
.cms-content-batchactions .view-mode-batchactions-wrapper #view-mode-batchactions { margin-top: 2px; }
|
.cms-content-batchactions .view-mode-batchactions-wrapper #view-mode-batchactions { margin-top: 2px; }
|
||||||
.cms-content-batchactions.inactive .view-mode-batchactions-wrapper { border-radius: 4px; }
|
.cms-content-batchactions.inactive .view-mode-batchactions-wrapper { border-radius: 4px; }
|
||||||
.cms-content-batchactions.inactive .view-mode-batchactions-wrapper label { display: inline; }
|
.cms-content-batchactions.inactive .view-mode-batchactions-wrapper .view-mode-batchactions-label { display: inline; }
|
||||||
.cms-content-batchactions form > * { display: block; float: left; }
|
.cms-content-batchactions form > * { display: block; float: left; }
|
||||||
.cms-content-batchactions form.cms-batch-actions { float: left; }
|
.cms-content-batchactions form.cms-batch-actions { float: left; }
|
||||||
.cms-content-batchactions.inactive form { display: none; }
|
.cms-content-batchactions.inactive form { display: none; }
|
||||||
|
@ -907,7 +907,7 @@ body.cms {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
.view-mode-batchactions-label {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -923,7 +923,7 @@ body.cms {
|
|||||||
&.inactive .view-mode-batchactions-wrapper {
|
&.inactive .view-mode-batchactions-wrapper {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
label {
|
.view-mode-batchactions-label {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,9 +157,20 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
|||||||
return static::$fixture_file;
|
return static::$fixture_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $fixtures Array of {@link YamlFixture} instances
|
||||||
|
* @deprecated 3.1 Use $fixtureFactory instad
|
||||||
|
*/
|
||||||
|
protected $fixtures = array();
|
||||||
|
|
||||||
protected $model;
|
protected $model;
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
|
|
||||||
|
//nest config and injector for each test so they are effectively sandboxed per test
|
||||||
|
Config::nest();
|
||||||
|
Injector::nest();
|
||||||
|
|
||||||
// We cannot run the tests on this abstract class.
|
// We cannot run the tests on this abstract class.
|
||||||
if(get_class($this) == "SapphireTest") $this->skipTest = true;
|
if(get_class($this) == "SapphireTest") $this->skipTest = true;
|
||||||
|
|
||||||
@ -284,6 +295,10 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
|||||||
* for tearing down the state again.
|
* for tearing down the state again.
|
||||||
*/
|
*/
|
||||||
public function setUpOnce() {
|
public function setUpOnce() {
|
||||||
|
|
||||||
|
//nest config and injector for each suite so they are effectively sandboxed
|
||||||
|
Config::nest();
|
||||||
|
Injector::nest();
|
||||||
$isAltered = false;
|
$isAltered = false;
|
||||||
|
|
||||||
if(!Director::isDev()) {
|
if(!Director::isDev()) {
|
||||||
@ -332,24 +347,12 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
|||||||
* tearDown method that's called once per test class rather once per test method.
|
* tearDown method that's called once per test class rather once per test method.
|
||||||
*/
|
*/
|
||||||
public function tearDownOnce() {
|
public function tearDownOnce() {
|
||||||
// If we have made changes to the extensions present, then migrate the database schema.
|
//unnest injector / config now that the test suite is over
|
||||||
if($this->extensionsToReapply || $this->extensionsToRemove) {
|
// this will reset all the extensions on the object too (see setUpOnce)
|
||||||
// Remove extensions added for testing
|
Injector::unnest();
|
||||||
foreach($this->extensionsToRemove as $class => $extensions) {
|
Config::unnest();
|
||||||
foreach($extensions as $extension) {
|
|
||||||
$class::remove_extension($extension);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reapply ones removed
|
if(!empty($this->extensionsToReapply) || !empty($this->extensionsToRemove) || !empty($this->extraDataObjects)) {
|
||||||
foreach($this->extensionsToReapply as $class => $extensions) {
|
|
||||||
foreach($extensions as $extension) {
|
|
||||||
$class::add_extension($extension);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($this->extensionsToReapply || $this->extensionsToRemove || $this->extraDataObjects) {
|
|
||||||
$this->resetDBSchema();
|
$this->resetDBSchema();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -499,6 +502,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
|||||||
$controller->response->setStatusCode(200);
|
$controller->response->setStatusCode(200);
|
||||||
$controller->response->removeHeader('Location');
|
$controller->response->removeHeader('Location');
|
||||||
}
|
}
|
||||||
|
//unnest injector / config now that tests are over
|
||||||
|
Injector::unnest();
|
||||||
|
Config::unnest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function assertContains(
|
public static function assertContains(
|
||||||
@ -763,7 +769,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
|||||||
|
|
||||||
$this->assertNotContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
|
$this->assertNotContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for the DOS matchers
|
* Helper function for the DOS matchers
|
||||||
*/
|
*/
|
||||||
|
@ -182,18 +182,10 @@ end of each test.
|
|||||||
$page->publish('Stage', 'Live');
|
$page->publish('Stage', 'Live');
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset configuration for the test.
|
// set custom configuration for the test.
|
||||||
Config::nest();
|
|
||||||
Config::inst()->update('Foo', 'bar', 'Hello!');
|
Config::inst()->update('Foo', 'bar', 'Hello!');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown() {
|
|
||||||
// restores the config variables
|
|
||||||
Config::unnest();
|
|
||||||
|
|
||||||
parent::tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMyMethod() {
|
public function testMyMethod() {
|
||||||
// ..
|
// ..
|
||||||
}
|
}
|
||||||
@ -224,6 +216,32 @@ individual test case.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### Config and Injector Nesting
|
||||||
|
|
||||||
|
A powerful feature of both [`Config`](/developer_guides/configuration/configuration/) and [`Injector`](/developer_guides/extending/injector/) is the ability to "nest" them so that you can make changes that can easily be discarded without having to manage previous values.
|
||||||
|
|
||||||
|
The testing suite makes use of this to "sandbox" each of the unit tests as well as each suite to prevent leakage between tests.
|
||||||
|
|
||||||
|
If you need to make changes to `Config` (or `Injector) for each test (or the whole suite) you can safely update `Config` (or `Injector`) settings in the `setUp` or `tearDown` functions.
|
||||||
|
|
||||||
|
It's important to remember that the `parent::setUp();` functions will need to be called first to ensure the nesting feature works as expected.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
function setUpOnce() {
|
||||||
|
parent::setUpOnce();
|
||||||
|
//this will remain for the whole suite and be removed for any other tests
|
||||||
|
Config::inst()->update('ClassName', 'var_name', 'var_value');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFeatureDoesAsExpected() {
|
||||||
|
//this will be reset to 'var_value' at the end of this test function
|
||||||
|
Config::inst()->update('ClassName', 'var_name', 'new_var_value');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAnotherFeatureDoesAsExpected() {
|
||||||
|
Config::inst()->get('ClassName', 'var_name'); // this will be 'var_value'
|
||||||
|
}
|
||||||
|
|
||||||
## Generating a Coverage Report
|
## Generating a Coverage Report
|
||||||
|
|
||||||
PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html))
|
PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
title: Changelogs
|
title: Changelogs
|
||||||
introduction: Key information on new features and improvements in each version.
|
introduction: Key information on new features and improvements in each version.
|
||||||
|
|
||||||
Keep up to date with new releases subscribe to the [SilverStripe Release Announcements](https://groups.google.com/group/silverstripe-announce) group,
|
Keep up to date with new releases by subscribing to the [SilverStripe Release Announcements](https://groups.google.com/group/silverstripe-announce) group,
|
||||||
or read our [blog posts about releases](http://silverstripe.org/blog/tag/release).
|
or read our [blog posts about releases](http://silverstripe.org/blog/tag/release).
|
||||||
|
|
||||||
We also keep an overview of [security-related releases](http://silverstripe.org/security-releases/).
|
We also keep an overview of [security-related releases](http://silverstripe.org/security-releases/).
|
||||||
|
@ -62,4 +62,4 @@ read our guide on [how to write secure code](/developer_guides/security/secure_c
|
|||||||
|
|
||||||
* [silverstripe.org/forums](http://www.silverstripe.org/community/forums/): Forums on silverstripe.org
|
* [silverstripe.org/forums](http://www.silverstripe.org/community/forums/): Forums on silverstripe.org
|
||||||
* [silverstripe-dev](http://groups.google.com/group/silverstripe-dev/): Core development mailinglist
|
* [silverstripe-dev](http://groups.google.com/group/silverstripe-dev/): Core development mailinglist
|
||||||
* [silverstripe-documentation](http://groups.google.com/group/silverstripe-translators/): Translation team mailing list
|
* [silverstripe-documentation](http://groups.google.com/group/silverstripe-documentation/): Documentation mailing list
|
||||||
|
@ -16,8 +16,7 @@ page you want to edit. Alternatively, locate the appropriate .md file in the
|
|||||||
|
|
||||||
|
|
||||||
* After editing the documentation, describe your changes in the "commit summary" and "extended description" fields below then press "Commit Changes".
|
* After editing the documentation, describe your changes in the "commit summary" and "extended description" fields below then press "Commit Changes".
|
||||||
* After that you will see a form to submit a Pull Request: "[pull requests](http://help.github.com/pull-requests/)". You should be able to adjust the version your
|
* After that you will see a form to submit a Pull Request: "[pull requests](http://help.github.com/pull-requests/)". You should be able to adjust the version your documentation changes apply to and then submit the form. Your changes will be sent to the core committers for approval.
|
||||||
* documentation changes are for and then submit the form. Your changes will be sent to the core committers for approval.
|
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
<div class="warning" markdown='1'>
|
||||||
You should make your changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you found in the 3.1 documentation, submit your fix to that branch in Github and it'll be copied to the master (3.2) version of the documentation automatically. *Don't submit multiple pull requests*.
|
You should make your changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you found in the 3.1 documentation, submit your fix to that branch in Github and it'll be copied to the master (3.2) version of the documentation automatically. *Don't submit multiple pull requests*.
|
||||||
@ -57,7 +56,7 @@ for documenting open source software.
|
|||||||
* Use PHPDoc in source code: Leave low level technical documentation to code comments within PHP, in [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) format.
|
* Use PHPDoc in source code: Leave low level technical documentation to code comments within PHP, in [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) format.
|
||||||
* API and developer guides are two forms of source code documentation that complement each other.
|
* API and developer guides are two forms of source code documentation that complement each other.
|
||||||
* API documentation should provide context, ie, the "bigger picture", by referring to developer guides inside your PHPDoc.
|
* API documentation should provide context, ie, the "bigger picture", by referring to developer guides inside your PHPDoc.
|
||||||
* Make your documentation easy to find: Documentation lives by interlinking content so please make sure your contribution doesn't become an inaccessible island. At the very least, put a link to your should on the index page in the same folder. A link to your page can also appear
|
* Make your documentation easy to find: Documentation is useful only when it is interlinked so please make sure your contribution doesn't become an inaccessible island. At the very least, put a link to your index page in the same folder. A link to your page can also appear
|
||||||
as "related content" on other resource (e.g. `/tutorials/site_search` might link to `/developer_guides/forms/introduction`).
|
as "related content" on other resource (e.g. `/tutorials/site_search` might link to `/developer_guides/forms/introduction`).
|
||||||
|
|
||||||
## Writing style
|
## Writing style
|
||||||
@ -85,7 +84,7 @@ sparingly.
|
|||||||
"Tip box": A tip box is great for adding, deepening or accenting information in the main text. They can be used for background knowledge, or to provide links to further information (ie, a "see also" link).
|
"Tip box": A tip box is great for adding, deepening or accenting information in the main text. They can be used for background knowledge, or to provide links to further information (ie, a "see also" link).
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Code:
|
Code for a Tip box:
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
<div class="hint" markdown='1'>
|
||||||
...
|
...
|
||||||
@ -95,7 +94,7 @@ Code:
|
|||||||
"Notification box": A notification box is good for technical notifications relating to the main text. For example, notifying users about a deprecated feature.
|
"Notification box": A notification box is good for technical notifications relating to the main text. For example, notifying users about a deprecated feature.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Code:
|
Code for a Notification box:
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
<div class="notice" markdown='1'>
|
||||||
...
|
...
|
||||||
@ -105,7 +104,7 @@ Code:
|
|||||||
"Warning box": A warning box is useful for highlighting a severe bug or a technical issue requiring a user's attention. For example, suppose a rare edge case sometimes leads to a variable being overwritten incorrectly. A warning box can be used to alert the user to this case so they can write their own code to handle it.
|
"Warning box": A warning box is useful for highlighting a severe bug or a technical issue requiring a user's attention. For example, suppose a rare edge case sometimes leads to a variable being overwritten incorrectly. A warning box can be used to alert the user to this case so they can write their own code to handle it.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Code:
|
Code for a Warning box:
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
<div class="warning" markdown='1'>
|
||||||
...
|
...
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
title: Implement Internationalization
|
title: Implement Internationalisation
|
||||||
summary: Implement SilverStripe's internationalization system in your own modules.
|
summary: Implement SilverStripe's internationalisation system in your own modules.
|
||||||
|
|
||||||
# Implementing Internationalization
|
# Implementing Internationalisation
|
||||||
|
|
||||||
To find out about how to assist with translating SilverStripe from a users point of view, see the
|
To find out about how to assist with translating SilverStripe from a user's point of view, see the
|
||||||
[Contributing Translations page](/contributing/translations).
|
[Contributing Translations page](/contributing/translations).
|
||||||
|
|
||||||
## Set up your own module for localization
|
## Set up your own module for localisation
|
||||||
|
|
||||||
### Collecting translatable text
|
### Collecting translatable text
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ source_lang = en
|
|||||||
type = YML
|
type = YML
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't have existing translations, your project is ready to go - simply point translators to the URL, have them
|
If you don't have existing translations to import, your project is ready to go - simply point translators to the URL, have them
|
||||||
sign up, and they can create languages and translations as required.
|
sign up, and they can create languages and translations as required.
|
||||||
|
|
||||||
### Import existing translations
|
### Import existing translations
|
||||||
@ -57,7 +57,7 @@ assumes you're adhering to the following guidelines:
|
|||||||
- Run the `i18nTextCollectorTask` with the `merge=true` option to avoid deleting unused entities
|
- Run the `i18nTextCollectorTask` with the `merge=true` option to avoid deleting unused entities
|
||||||
(which might still be relevant in older release branches)
|
(which might still be relevant in older release branches)
|
||||||
|
|
||||||
### Converting your language files from 2.4 PHP format
|
### Converting your language files from 2.4 PHP format to YML
|
||||||
|
|
||||||
The conversion from PHP format to YML is taken care of by a module called
|
The conversion from PHP format to YML is taken care of by a module called
|
||||||
[i18n_yml_converter](https://github.com/chillu/i18n_yml_converter).
|
[i18n_yml_converter](https://github.com/chillu/i18n_yml_converter).
|
||||||
@ -116,7 +116,7 @@ First of all, you need to create those source files in JSON, and store them in `
|
|||||||
source_lang = en
|
source_lang = en
|
||||||
type = KEYVALUEJSON
|
type = KEYVALUEJSON
|
||||||
|
|
||||||
Now you can upload the source files via a normal `tx push`. Once translations come in, you need to convert the source
|
Then you can upload the source files via a normal `tx push`. Once translations come in, you need to convert the source
|
||||||
files back into the JS files SilverStripe can actually read. This requires an installation of our
|
files back into the JS files SilverStripe can actually read. This requires an installation of our
|
||||||
[buildtools](https://github.com/silverstripe/silverstripe-buildtools).
|
[buildtools](https://github.com/silverstripe/silverstripe-buildtools).
|
||||||
|
|
||||||
|
@ -633,7 +633,7 @@ class File extends DataObject {
|
|||||||
|
|
||||||
// If it's changed, check for duplicates
|
// If it's changed, check for duplicates
|
||||||
if($oldName && $oldName != $name) {
|
if($oldName && $oldName != $name) {
|
||||||
$base = pathinfo($name, PATHINFO_BASENAME);
|
$base = pathinfo($name, PATHINFO_FILENAME);
|
||||||
$ext = self::get_file_extension($name);
|
$ext = self::get_file_extension($name);
|
||||||
$suffix = 1;
|
$suffix = 1;
|
||||||
|
|
||||||
@ -645,7 +645,7 @@ class File extends DataObject {
|
|||||||
))->first()
|
))->first()
|
||||||
) {
|
) {
|
||||||
$suffix++;
|
$suffix++;
|
||||||
$name = "$base-$suffix$ext";
|
$name = "$base-$suffix.$ext";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,8 +96,12 @@ class CheckboxSetField extends OptionsetField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif($values && is_string($values)) {
|
} elseif($values && is_string($values)) {
|
||||||
$items = explode(',', $values);
|
if(!empty($values)) {
|
||||||
$items = str_replace('{comma}', ',', $items);
|
$items = explode(',', $values);
|
||||||
|
$items = str_replace('{comma}', ',', $items);
|
||||||
|
} else {
|
||||||
|
$items = array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -109,8 +113,12 @@ class CheckboxSetField extends OptionsetField {
|
|||||||
$items = array();
|
$items = array();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$items = explode(',', $values);
|
if(!empty($values)) {
|
||||||
$items = str_replace('{comma}', ',', $items);
|
$items = explode(',', $values);
|
||||||
|
$items = str_replace('{comma}', ',', $items);
|
||||||
|
} else {
|
||||||
|
$items = array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ class DatetimeField extends FormField {
|
|||||||
->addExtraClass('fieldgroup-field');
|
->addExtraClass('fieldgroup-field');
|
||||||
$this->timeField = TimeField::create($name . '[time]', false)
|
$this->timeField = TimeField::create($name . '[time]', false)
|
||||||
->addExtraClass('fieldgroup-field');
|
->addExtraClass('fieldgroup-field');
|
||||||
$this->timezoneField = new HiddenField($this->getName() . '[timezone]');
|
$this->timezoneField = new HiddenField($name . '[timezone]');
|
||||||
|
|
||||||
parent::__construct($name, $title, $value);
|
parent::__construct($name, $title, $value);
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,43 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NullableField is a field that wraps other fields when you want to allow the user to specify whether the value of
|
* NullableField is a field that wraps other fields when you want to allow the user to specify
|
||||||
* the field is null or not.
|
* whether the value of the field is null or not.
|
||||||
|
*
|
||||||
|
* The classic case is to wrap a TextField so that the user can distinguish between an empty string
|
||||||
|
* and a null string.
|
||||||
*
|
*
|
||||||
* The classic case is to wrap a TextField so that the user can distinguish between an empty string and a null string.
|
|
||||||
* $a = new NullableField(new TextField("Field1", "Field 1", "abc"));
|
* $a = new NullableField(new TextField("Field1", "Field 1", "abc"));
|
||||||
*
|
*
|
||||||
* It displays the field that is wrapped followed by a checkbox that is used to specify if the value is null or not.
|
* It displays the field that is wrapped followed by a checkbox that is used to specify if the
|
||||||
* It uses the Title of the wrapped field for its title.
|
* value is null or not. It uses the Title of the wrapped field for its title.
|
||||||
* When a form is submitted the field tests the value of the "is null" checkbox and sets its value accordingly.
|
*
|
||||||
* You can retrieve the value of the wrapped field from the NullableField as follows:
|
* When a form is submitted the field tests the value of the "is null" checkbox and sets its value
|
||||||
|
* accordingly. You can retrieve the value of the wrapped field from the NullableField as follows:
|
||||||
|
*
|
||||||
* $field->Value() or $field->dataValue()
|
* $field->Value() or $field->dataValue()
|
||||||
*
|
*
|
||||||
* You can specify the label to use for the "is null" checkbox. If you want to use I8N for this label then specify it
|
* You can specify the label to use for the "is null" checkbox. If you want to use i18n for this
|
||||||
* like this:
|
* label then specify it like this:
|
||||||
*
|
*
|
||||||
* $field->setIsNullLabel(_T(SOME_MODULE_ISNULL_LABEL, "Is Null");
|
* $field->setIsNullLabel(_T(SOME_MODULE_ISNULL_LABEL, "Is Null"));
|
||||||
*
|
*
|
||||||
* @author Pete Bacon Darwin
|
* @author Pete Bacon Darwin
|
||||||
|
*
|
||||||
* @package forms
|
* @package forms
|
||||||
* @subpackage fields-basic
|
* @subpackage fields-basic
|
||||||
*/
|
*/
|
||||||
class NullableField extends FormField {
|
class NullableField extends FormField {
|
||||||
/**
|
/**
|
||||||
* The field that holds the value of this field
|
* The field that holds the value of this field
|
||||||
|
*
|
||||||
* @var FormField
|
* @var FormField
|
||||||
*/
|
*/
|
||||||
protected $valueField;
|
protected $valueField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The label to show next to the is null check box.
|
* The label to show next to the is null check box.
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $isNullLabel;
|
protected $isNullLabel;
|
||||||
@ -37,39 +45,55 @@ class NullableField extends FormField {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new nullable field
|
* Create a new nullable field
|
||||||
* @param $valueField
|
*
|
||||||
* @return NullableField
|
* @param FormField $valueField
|
||||||
|
* @param null|string $isNullLabel
|
||||||
*/
|
*/
|
||||||
public function __construct(FormField $valueField, $isNullLabel = null) {
|
public function __construct(FormField $valueField, $isNullLabel = null) {
|
||||||
$this->valueField = $valueField;
|
$this->valueField = $valueField;
|
||||||
$this->isNullLabel = $isNullLabel;
|
|
||||||
if ( is_null($this->isNullLabel) ) {
|
if(isset($isNullLabel)) {
|
||||||
// Set a default label if one is not provided.
|
$this->setIsNullLabel($isNullLabel);
|
||||||
|
} else {
|
||||||
$this->isNullLabel = _t('NullableField.IsNullLabel', 'Is Null');
|
$this->isNullLabel = _t('NullableField.IsNullLabel', 'Is Null');
|
||||||
}
|
}
|
||||||
parent::__construct($valueField->getName(), $valueField->Title(), $valueField->Value(),
|
|
||||||
$valueField->getForm(), $valueField->RightTitle());
|
parent::__construct(
|
||||||
$this->readonly = $valueField->isReadonly();
|
$valueField->getName(),
|
||||||
|
$valueField->Title(),
|
||||||
|
$valueField->Value()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->setForm($valueField->getForm());
|
||||||
|
$this->setRightTitle($valueField->RightTitle());
|
||||||
|
$this->setReadonly($valueField->isReadonly());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the label used for the Is Null checkbox.
|
* Get the label used for the Is Null checkbox.
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIsNullLabel() {
|
public function getIsNullLabel() {
|
||||||
return $this->isNullLabel;
|
return $this->isNullLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the label used for the Is Null checkbox.
|
* Set the label used for the Is Null checkbox.
|
||||||
|
*
|
||||||
* @param $isNulLabel string
|
* @param $isNulLabel string
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setIsNullLabel(string $isNulLabel){
|
public function setIsNullLabel($isNulLabel) {
|
||||||
$this->isNullLabel = $isNulLabel;
|
$this->isNullLabel = $isNulLabel;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the id used for the Is Null check box.
|
* Get the id used for the Is Null check box.
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIsNullId() {
|
public function getIsNullId() {
|
||||||
@ -77,54 +101,81 @@ class NullableField extends FormField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (non-PHPdoc)
|
* @param array $properties
|
||||||
* @see framework/forms/FormField#Field()
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function Field($properties = array()) {
|
public function Field($properties = array()) {
|
||||||
if ( $this->isReadonly()) {
|
if($this->isReadonly()) {
|
||||||
$nullableCheckbox = new CheckboxField_Readonly($this->getIsNullId());
|
$nullableCheckbox = new CheckboxField_Readonly($this->getIsNullId());
|
||||||
} else {
|
} else {
|
||||||
$nullableCheckbox = new CheckboxField($this->getIsNullId());
|
$nullableCheckbox = new CheckboxField($this->getIsNullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
$nullableCheckbox->setValue(is_null($this->dataValue()));
|
$nullableCheckbox->setValue(is_null($this->dataValue()));
|
||||||
|
|
||||||
return $this->valueField->Field() . ' ' . $nullableCheckbox->Field()
|
return sprintf(
|
||||||
. ' <span>' . $this->getIsNullLabel().'</span>';
|
'%s %s <span>%s</span>',
|
||||||
|
$this->valueField->Field(),
|
||||||
|
$nullableCheckbox->Field(),
|
||||||
|
$this->getIsNullLabel()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value is sometimes an array, and sometimes a single value, so we need to handle both cases
|
* Value is sometimes an array, and sometimes a single value, so we need to handle both cases
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @param null|array $data
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setValue($value, $data = null) {
|
public function setValue($value, $data = null) {
|
||||||
if ( is_array($data) && array_key_exists($this->getIsNullId(), $data) && $data[$this->getIsNullId()] ) {
|
$id = $this->getIsNullId();
|
||||||
|
|
||||||
|
if(is_array($data) && array_key_exists($id, $data) && $data[$id]) {
|
||||||
$value = null;
|
$value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->valueField->setValue($value);
|
$this->valueField->setValue($value);
|
||||||
|
|
||||||
parent::setValue($value);
|
parent::setValue($value);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (non-PHPdoc)
|
* @param string $name
|
||||||
* @see forms/FormField#setName($name)
|
*
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setName($name) {
|
public function setName($name) {
|
||||||
// We need to pass through the name change to the underlying value field.
|
|
||||||
$this->valueField->setName($name);
|
$this->valueField->setName($name);
|
||||||
|
|
||||||
parent::setName($name);
|
parent::setName($name);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (non-PHPdoc)
|
* @return string
|
||||||
* @see framework/forms/FormField#debug()
|
|
||||||
*/
|
*/
|
||||||
public function debug() {
|
public function debug() {
|
||||||
$result = "$this->class ($this->name: $this->title : <font style='color:red;'>$this->message</font>) = ";
|
$result = sprintf(
|
||||||
$result .= (is_null($this->value)) ? "<<null>>" : $this->value;
|
'%s (%s: $s : <span style="color: red">%s</span>) = ',
|
||||||
return result;
|
$this->class,
|
||||||
|
$this->name,
|
||||||
|
$this->title,
|
||||||
|
$this->message
|
||||||
|
);
|
||||||
|
|
||||||
|
if($this->value === null) {
|
||||||
|
$result .= "<<null>>";
|
||||||
|
} else {
|
||||||
|
$result .= (string) $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,56 +9,75 @@
|
|||||||
* @subpackage fields-formattedinput
|
* @subpackage fields-formattedinput
|
||||||
*/
|
*/
|
||||||
class NumericField extends TextField {
|
class NumericField extends TextField {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override locale for this field
|
* Override locale for this field.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $locale = null;
|
protected $locale = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $value
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws Zend_Locale_Exception
|
||||||
|
*/
|
||||||
public function setValue($value, $data = array()) {
|
public function setValue($value, $data = array()) {
|
||||||
require_once "Zend/Locale/Format.php";
|
require_once "Zend/Locale/Format.php";
|
||||||
|
|
||||||
// If passing in a non-string number, or a value
|
// If passing in a non-string number, or a value
|
||||||
// directly from a dataobject then localise this number
|
// directly from a DataObject then localise this number
|
||||||
if ((is_numeric($value) && !is_string($value)) ||
|
|
||||||
($value && $data instanceof DataObject)
|
if(is_int($value) || is_float($value) || $data instanceof DataObject) {
|
||||||
){
|
|
||||||
$locale = new Zend_Locale($this->getLocale());
|
$locale = new Zend_Locale($this->getLocale());
|
||||||
$this->value = Zend_Locale_Format::toNumber($value, array('locale' => $locale));
|
|
||||||
|
$this->value = Zend_Locale_Format::toNumber(
|
||||||
|
$value,
|
||||||
|
array('locale' => $locale)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// If an invalid number, store it anyway, but validate() will fail
|
|
||||||
$this->value = $this->clean($value);
|
$this->value = $this->clean($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In some cases and locales, validation expects non-breaking spaces
|
* In some cases and locales, validation expects non-breaking spaces.
|
||||||
|
*
|
||||||
|
* Returns the value, with all spaces replaced with non-breaking spaces.
|
||||||
*
|
*
|
||||||
* @param string $input
|
* @param string $input
|
||||||
* @return string The input value, with all spaces replaced with non-breaking spaces
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function clean($input) {
|
protected function clean($input) {
|
||||||
$nbsp = html_entity_decode(' ', null, 'UTF-8');
|
$replacement = html_entity_decode(' ', null, 'UTF-8');
|
||||||
return str_replace(' ', $nbsp, trim($input));
|
|
||||||
|
return str_replace(' ', $replacement, trim($input));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the current value is a valid number in the current locale
|
* Determine if the current value is a valid number in the current locale.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function isNumeric() {
|
protected function isNumeric() {
|
||||||
require_once "Zend/Locale/Format.php";
|
require_once "Zend/Locale/Format.php";
|
||||||
|
|
||||||
$locale = new Zend_Locale($this->getLocale());
|
$locale = new Zend_Locale($this->getLocale());
|
||||||
|
|
||||||
return Zend_Locale_Format::isNumber(
|
return Zend_Locale_Format::isNumber(
|
||||||
$this->clean($this->value),
|
$this->clean($this->value),
|
||||||
array('locale' => $locale)
|
array('locale' => $locale)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function Type() {
|
public function Type() {
|
||||||
return 'numeric text';
|
return 'numeric text';
|
||||||
}
|
}
|
||||||
@ -81,74 +100,111 @@ class NumericField extends TextField {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->isNumeric()) return true;
|
if($this->isNumeric()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
$validator->validationError(
|
$validator->validationError(
|
||||||
$this->name,
|
$this->name,
|
||||||
_t(
|
_t(
|
||||||
'NumericField.VALIDATION', "'{value}' is not a number, only numbers can be accepted for this field",
|
'NumericField.VALIDATION',
|
||||||
|
"'{value}' is not a number, only numbers can be accepted for this field",
|
||||||
array('value' => $this->value)
|
array('value' => $this->value)
|
||||||
),
|
),
|
||||||
"validation"
|
"validation"
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the number value from the localised string value
|
* Extracts the number value from the localised string value.
|
||||||
*
|
*
|
||||||
* @return string number value
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function dataValue() {
|
public function dataValue() {
|
||||||
require_once "Zend/Locale/Format.php";
|
require_once "Zend/Locale/Format.php";
|
||||||
if(!$this->isNumeric()) return 0;
|
|
||||||
|
if(!$this->isNumeric()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
$locale = new Zend_Locale($this->getLocale());
|
$locale = new Zend_Locale($this->getLocale());
|
||||||
|
|
||||||
$number = Zend_Locale_Format::getNumber(
|
$number = Zend_Locale_Format::getNumber(
|
||||||
$this->clean($this->value),
|
$this->clean($this->value),
|
||||||
array('locale' => $locale)
|
array('locale' => $locale)
|
||||||
);
|
);
|
||||||
|
|
||||||
return $number;
|
return $number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a readonly version of this field
|
* Creates a read-only version of the field.
|
||||||
|
*
|
||||||
|
* @return NumericField_Readonly
|
||||||
*/
|
*/
|
||||||
public function performReadonlyTransformation() {
|
public function performReadonlyTransformation() {
|
||||||
$field = new NumericField_Readonly($this->name, $this->title, $this->value);
|
$field = new NumericField_Readonly(
|
||||||
|
$this->name,
|
||||||
|
$this->title,
|
||||||
|
$this->value
|
||||||
|
);
|
||||||
|
|
||||||
$field->setForm($this->form);
|
$field->setForm($this->form);
|
||||||
|
|
||||||
return $field;
|
return $field;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current locale this field is set to
|
* Gets the current locale this field is set to.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getLocale() {
|
public function getLocale() {
|
||||||
return $this->locale ?: i18n::get_locale();
|
if($this->locale) {
|
||||||
|
return $this->locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i18n::get_locale();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the locale for this field
|
* Override the locale for this field.
|
||||||
*
|
*
|
||||||
* @param string $locale
|
* @param string $locale
|
||||||
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setLocale($locale) {
|
public function setLocale($locale) {
|
||||||
$this->locale = $locale;
|
$this->locale = $locale;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Readonly version of a numeric field.
|
||||||
|
*
|
||||||
|
* @package forms
|
||||||
|
* @subpackage fields-basic
|
||||||
|
*/
|
||||||
class NumericField_Readonly extends ReadonlyField {
|
class NumericField_Readonly extends ReadonlyField {
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
public function performReadonlyTransformation() {
|
public function performReadonlyTransformation() {
|
||||||
return clone $this;
|
return clone $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function Value() {
|
public function Value() {
|
||||||
return Convert::raw2xml($this->value ? "$this->value" : "0");
|
if($this->value) {
|
||||||
}
|
return Convert::raw2xml((string) $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,11 @@
|
|||||||
/**
|
/**
|
||||||
* Displays a {@link SS_List} in a grid format.
|
* Displays a {@link SS_List} in a grid format.
|
||||||
*
|
*
|
||||||
* GridField is a field that takes an SS_List and displays it in an table with rows
|
* GridField is a field that takes an SS_List and displays it in an table with rows and columns.
|
||||||
* and columns. It reminds of the old TableFields but works with SS_List types
|
* It reminds of the old TableFields but works with SS_List types and only loads the necessary
|
||||||
* and only loads the necessary rows from the list.
|
* rows from the list.
|
||||||
*
|
*
|
||||||
* The minimum configuration is to pass in name and title of the field and a
|
* The minimum configuration is to pass in name and title of the field and a SS_List.
|
||||||
* SS_List.
|
|
||||||
*
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'));
|
* $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'));
|
||||||
@ -21,45 +20,44 @@
|
|||||||
* @property GridState_Data $State The gridstate of this object
|
* @property GridState_Data $State The gridstate of this object
|
||||||
*/
|
*/
|
||||||
class GridField extends FormField {
|
class GridField extends FormField {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $allowed_actions = array(
|
private static $allowed_actions = array(
|
||||||
'index',
|
'index',
|
||||||
'gridFieldAlterAction'
|
'gridFieldAlterAction',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The datasource
|
* Data source.
|
||||||
*
|
*
|
||||||
* @var SS_List
|
* @var SS_List
|
||||||
*/
|
*/
|
||||||
protected $list = null;
|
protected $list = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The classname of the DataObject that the GridField will display. Defaults to the value of $this->list->dataClass
|
* Class name of the DataObject that the GridField will display.
|
||||||
|
*
|
||||||
|
* Defaults to the value of $this->list->dataClass.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $modelClassName = '';
|
protected $modelClassName = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the current state of the GridField
|
* Current state of the GridField.
|
||||||
*
|
*
|
||||||
* @var GridState
|
* @var GridState
|
||||||
*/
|
*/
|
||||||
protected $state = null;
|
protected $state = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @var GridFieldConfig
|
* @var GridFieldConfig
|
||||||
*/
|
*/
|
||||||
protected $config = null;
|
protected $config = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The components list
|
* Components list.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -67,14 +65,15 @@ class GridField extends FormField {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dispatcher for column handlers.
|
* Internal dispatcher for column handlers.
|
||||||
* Keys are column names and values are GridField_ColumnProvider objects
|
*
|
||||||
|
* Keys are column names and values are GridField_ColumnProvider objects.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $columnDispatch = null;
|
protected $columnDispatch = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of callbacks for custom data fields
|
* Map of callbacks for custom data fields.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -86,8 +85,6 @@ class GridField extends FormField {
|
|||||||
protected $name = '';
|
protected $name = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new GridField field
|
|
||||||
*
|
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $title
|
* @param string $title
|
||||||
* @param SS_List $dataList
|
* @param SS_List $dataList
|
||||||
@ -95,13 +92,18 @@ class GridField extends FormField {
|
|||||||
*/
|
*/
|
||||||
public function __construct($name, $title = null, SS_List $dataList = null, GridFieldConfig $config = null) {
|
public function __construct($name, $title = null, SS_List $dataList = null, GridFieldConfig $config = null) {
|
||||||
parent::__construct($name, $title, null);
|
parent::__construct($name, $title, null);
|
||||||
|
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
|
|
||||||
if($dataList) {
|
if($dataList) {
|
||||||
$this->setList($dataList);
|
$this->setList($dataList);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setConfig($config ?: GridFieldConfig_Base::create());
|
if(!$config) {
|
||||||
|
$config = GridFieldConfig_Base::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setConfig($config);
|
||||||
|
|
||||||
$this->config->addComponent(new GridState_Component());
|
$this->config->addComponent(new GridState_Component());
|
||||||
$this->state = new GridState($this);
|
$this->state = new GridState($this);
|
||||||
@ -109,44 +111,58 @@ class GridField extends FormField {
|
|||||||
$this->addExtraClass('ss-gridfield');
|
$this->addExtraClass('ss-gridfield');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function index($request) {
|
public function index($request) {
|
||||||
return $this->gridFieldAlterAction(array(), $this->getForm(), $request);
|
return $this->gridFieldAlterAction(array(), $this->getForm(), $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the modelClass (dataobject) that this field will get it column headers from.
|
* Set the modelClass (data object) that this field will get it column headers from.
|
||||||
* If no $displayFields has been set, the displayfields will be fetched from
|
*
|
||||||
* this modelclass $summary_fields
|
* If no $displayFields has been set, the display fields will be $summary_fields.
|
||||||
|
*
|
||||||
|
* @see GridFieldDataColumns::getDisplayFields()
|
||||||
*
|
*
|
||||||
* @param string $modelClassName
|
* @param string $modelClassName
|
||||||
*
|
*
|
||||||
* @see GridFieldDataColumns::getDisplayFields()
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setModelClass($modelClassName) {
|
public function setModelClass($modelClassName) {
|
||||||
$this->modelClassName = $modelClassName;
|
$this->modelClassName = $modelClassName;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a dataclass that is a DataObject type that this GridField should look like.
|
* Returns a data class that is a DataObject type that this GridField should look like.
|
||||||
*
|
*
|
||||||
* @throws Exception
|
|
||||||
* @return string
|
* @return string
|
||||||
|
*
|
||||||
|
* @throws LogicException
|
||||||
*/
|
*/
|
||||||
public function getModelClass() {
|
public function getModelClass() {
|
||||||
if($this->modelClassName) return $this->modelClassName;
|
if($this->modelClassName) {
|
||||||
if($this->list && method_exists($this->list, 'dataClass')) {
|
return $this->modelClassName;
|
||||||
$class = $this->list->dataClass();
|
|
||||||
if($class) return $class;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new LogicException('GridField doesn\'t have a modelClassName,'
|
if($this->list && method_exists($this->list, 'dataClass')) {
|
||||||
. ' so it doesn\'t know the columns of this grid.');
|
$class = $this->list->dataClass();
|
||||||
|
|
||||||
|
if($class) {
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new LogicException(
|
||||||
|
'GridField doesn\'t have a modelClassName, so it doesn\'t know the columns of this grid.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the GridFieldConfig
|
|
||||||
*
|
|
||||||
* @return GridFieldConfig
|
* @return GridFieldConfig
|
||||||
*/
|
*/
|
||||||
public function getConfig() {
|
public function getConfig() {
|
||||||
@ -156,61 +172,69 @@ class GridField extends FormField {
|
|||||||
/**
|
/**
|
||||||
* @param GridFieldConfig $config
|
* @param GridFieldConfig $config
|
||||||
*
|
*
|
||||||
* @return GridField
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setConfig(GridFieldConfig $config) {
|
public function setConfig(GridFieldConfig $config) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ArrayList
|
||||||
|
*/
|
||||||
public function getComponents() {
|
public function getComponents() {
|
||||||
return $this->config->getComponents();
|
return $this->config->getComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cast a arbitrary value with the help of a castingDefintion
|
* Cast an arbitrary value with the help of a $castingDefinition.
|
||||||
*
|
|
||||||
* @param $value
|
|
||||||
* @param $castingDefinition
|
|
||||||
*
|
*
|
||||||
* @todo refactor this into GridFieldComponent
|
* @todo refactor this into GridFieldComponent
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @param string|array $castingDefinition
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getCastedValue($value, $castingDefinition) {
|
public function getCastedValue($value, $castingDefinition) {
|
||||||
|
$castingParams = array();
|
||||||
|
|
||||||
if(is_array($castingDefinition)) {
|
if(is_array($castingDefinition)) {
|
||||||
$castingParams = $castingDefinition;
|
$castingParams = $castingDefinition;
|
||||||
array_shift($castingParams);
|
array_shift($castingParams);
|
||||||
$castingDefinition = array_shift($castingDefinition);
|
$castingDefinition = array_shift($castingDefinition);
|
||||||
} else {
|
|
||||||
$castingParams = array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(strpos($castingDefinition, '->') === false) {
|
if(strpos($castingDefinition, '->') === false) {
|
||||||
$castingFieldType = $castingDefinition;
|
$castingFieldType = $castingDefinition;
|
||||||
$castingField = DBField::create_field($castingFieldType, $value);
|
$castingField = DBField::create_field($castingFieldType, $value);
|
||||||
$value = call_user_func_array(array($castingField, 'XML'), $castingParams);
|
|
||||||
} else {
|
return call_user_func_array(array($castingField, 'XML'), $castingParams);
|
||||||
$fieldTypeParts = explode('->', $castingDefinition);
|
|
||||||
$castingFieldType = $fieldTypeParts[0];
|
|
||||||
$castingMethod = $fieldTypeParts[1];
|
|
||||||
$castingField = DBField::create_field($castingFieldType, $value);
|
|
||||||
$value = call_user_func_array(array($castingField, $castingMethod), $castingParams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
list($castingFieldType, $castingMethod) = explode('->', $castingDefinition);
|
||||||
|
|
||||||
|
$castingField = DBField::create_field($castingFieldType, $value);
|
||||||
|
|
||||||
|
return call_user_func_array(array($castingField, $castingMethod), $castingParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the datasource
|
* Set the data source.
|
||||||
*
|
*
|
||||||
* @param SS_List $list
|
* @param SS_List $list
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setList(SS_List $list) {
|
public function setList(SS_List $list) {
|
||||||
$this->list = $list;
|
$this->list = $list;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the datasource
|
* Get the data source.
|
||||||
*
|
*
|
||||||
* @return SS_List
|
* @return SS_List
|
||||||
*/
|
*/
|
||||||
@ -219,26 +243,28 @@ class GridField extends FormField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the datasource after applying the {@link GridField_DataManipulator}s to it.
|
* Get the data source after applying every {@link GridField_DataManipulator} to it.
|
||||||
*
|
*
|
||||||
* @return SS_List
|
* @return SS_List
|
||||||
*/
|
*/
|
||||||
public function getManipulatedList() {
|
public function getManipulatedList() {
|
||||||
$list = $this->getList();
|
$list = $this->getList();
|
||||||
|
|
||||||
foreach($this->getComponents() as $item) {
|
foreach($this->getComponents() as $item) {
|
||||||
if($item instanceof GridField_DataManipulator) {
|
if($item instanceof GridField_DataManipulator) {
|
||||||
$list = $item->getManipulatedData($this, $list);
|
$list = $item->getManipulatedData($this, $list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current GridState_Data or the GridState
|
* Get the current GridState_Data or the GridState.
|
||||||
*
|
*
|
||||||
* @param bool $getData - flag for returning the GridState_Data or the GridState
|
* @param bool $getData
|
||||||
*
|
*
|
||||||
* @return GridState_data|GridState
|
* @return GridState_Data|GridState
|
||||||
*/
|
*/
|
||||||
public function getState($getData = true) {
|
public function getState($getData = true) {
|
||||||
if($getData) {
|
if($getData) {
|
||||||
@ -249,7 +275,9 @@ class GridField extends FormField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the whole gridfield rendered with all the attached components
|
* Returns the whole gridfield rendered with all the attached components.
|
||||||
|
*
|
||||||
|
* @param array $properties
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@ -265,88 +293,115 @@ class GridField extends FormField {
|
|||||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
|
||||||
Requirements::javascript(FRAMEWORK_DIR . '/javascript/GridField.js');
|
Requirements::javascript(FRAMEWORK_DIR . '/javascript/GridField.js');
|
||||||
|
|
||||||
// Get columns
|
|
||||||
$columns = $this->getColumns();
|
$columns = $this->getColumns();
|
||||||
|
|
||||||
// Get data
|
|
||||||
$list = $this->getManipulatedList();
|
$list = $this->getManipulatedList();
|
||||||
|
|
||||||
// Render headers, footers, etc
|
|
||||||
$content = array(
|
$content = array(
|
||||||
"before" => "",
|
'before' => '',
|
||||||
"after" => "",
|
'after' => '',
|
||||||
"header" => "",
|
'header' => '',
|
||||||
"footer" => "",
|
'footer' => '',
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach($this->getComponents() as $item) {
|
foreach($this->getComponents() as $item) {
|
||||||
if($item instanceof GridField_HTMLProvider) {
|
if($item instanceof GridField_HTMLProvider) {
|
||||||
$fragments = $item->getHTMLFragments($this);
|
$fragments = $item->getHTMLFragments($this);
|
||||||
if($fragments) foreach($fragments as $k => $v) {
|
|
||||||
$k = strtolower($k);
|
|
||||||
if(!isset($content[$k])) $content[$k] = "";
|
|
||||||
$content[$k] .= $v . "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($content as $k => $v) {
|
if($fragments) {
|
||||||
$content[$k] = trim($v);
|
foreach($fragments as $fragmentKey => $fragmentValue) {
|
||||||
}
|
$fragmentKey = strtolower($fragmentKey);
|
||||||
|
|
||||||
// Replace custom fragments and check which fragments are defined
|
if(!isset($content[$fragmentKey])) {
|
||||||
// Nested dependencies are handled by deferring the rendering of any content item that
|
$content[$fragmentKey] = '';
|
||||||
// Circular dependencies are detected by disallowing any item to be deferred more than 5 times
|
|
||||||
// It's a fairly crude algorithm but it works
|
|
||||||
|
|
||||||
$fragmentDefined = array('header' => true, 'footer' => true, 'before' => true, 'after' => true);
|
|
||||||
reset($content);
|
|
||||||
while(list($k, $v) = each($content)) {
|
|
||||||
if(preg_match_all('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $v, $matches)) {
|
|
||||||
foreach($matches[1] as $match) {
|
|
||||||
$fragmentName = strtolower($match);
|
|
||||||
$fragmentDefined[$fragmentName] = true;
|
|
||||||
$fragment = isset($content[$fragmentName]) ? $content[$fragmentName] : "";
|
|
||||||
|
|
||||||
// If the fragment still has a fragment definition in it, when we should defer this item until
|
|
||||||
// later.
|
|
||||||
if(preg_match('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $fragment, $matches)) {
|
|
||||||
// If we've already deferred this fragment, then we have a circular dependency
|
|
||||||
if(isset($fragmentDeferred[$k]) && $fragmentDeferred[$k] > 5) {
|
|
||||||
throw new LogicException("GridField HTML fragment '$fragmentName' and '$matches[1]' " .
|
|
||||||
"appear to have a circular dependency.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise we can push to the end of the content array
|
$content[$fragmentKey] .= $fragmentValue . "\n";
|
||||||
unset($content[$k]);
|
|
||||||
$content[$k] = $v;
|
|
||||||
if(!isset($fragmentDeferred[$k])) {
|
|
||||||
$fragmentDeferred[$k] = 1;
|
|
||||||
} else {
|
|
||||||
$fragmentDeferred[$k]++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
$content[$k] = preg_replace('/\$DefineFragment\(' . $fragmentName . '\)/i', $fragment,
|
|
||||||
$content[$k]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for any undefined fragments, and if so throw an exception
|
foreach($content as $contentKey => $contentValue) {
|
||||||
// While we're at it, trim whitespace off the elements
|
$content[$contentKey] = trim($contentValue);
|
||||||
foreach($content as $k => $v) {
|
}
|
||||||
if(empty($fragmentDefined[$k])) {
|
|
||||||
throw new LogicException("GridField HTML fragment '$k' was given content,"
|
// Replace custom fragments and check which fragments are defined. Circular dependencies
|
||||||
. " but not defined. Perhaps there is a supporting GridField component you need to add?");
|
// are detected by disallowing any item to be deferred more than 5 times.
|
||||||
|
|
||||||
|
$fragmentDefined = array(
|
||||||
|
'header' => true,
|
||||||
|
'footer' => true,
|
||||||
|
'before' => true,
|
||||||
|
'after' => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
reset($content);
|
||||||
|
|
||||||
|
while(list($contentKey, $contentValue) = each($content)) {
|
||||||
|
if(preg_match_all('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $contentValue, $matches)) {
|
||||||
|
foreach($matches[1] as $match) {
|
||||||
|
$fragmentName = strtolower($match);
|
||||||
|
$fragmentDefined[$fragmentName] = true;
|
||||||
|
|
||||||
|
$fragment = '';
|
||||||
|
|
||||||
|
if(isset($content[$fragmentName])) {
|
||||||
|
$fragment = $content[$fragmentName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the fragment still has a fragment definition in it, when we should defer
|
||||||
|
// this item until later.
|
||||||
|
|
||||||
|
if(preg_match('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $fragment, $matches)) {
|
||||||
|
if(isset($fragmentDeferred[$contentKey]) && $fragmentDeferred[$contentKey] > 5) {
|
||||||
|
throw new LogicException(sprintf(
|
||||||
|
'GridField HTML fragment "%s" and "%s" appear to have a circular dependency.',
|
||||||
|
$fragmentName,
|
||||||
|
$matches[1]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($content[$contentKey]);
|
||||||
|
|
||||||
|
$content[$contentKey] = $contentValue;
|
||||||
|
|
||||||
|
if(!isset($fragmentDeferred[$contentKey])) {
|
||||||
|
$fragmentDeferred[$contentKey] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fragmentDeferred[$contentKey]++;
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
$content[$contentKey] = preg_replace(
|
||||||
|
sprintf('/\$DefineFragment\(%s\)/i', $fragmentName),
|
||||||
|
$fragment,
|
||||||
|
$content[$contentKey]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any undefined fragments, and if so throw an exception.
|
||||||
|
// While we're at it, trim whitespace off the elements.
|
||||||
|
|
||||||
|
foreach($content as $contentKey => $contentValue) {
|
||||||
|
if(empty($fragmentDefined[$contentKey])) {
|
||||||
|
throw new LogicException(sprintf(
|
||||||
|
'GridField HTML fragment "%s" was given content, but not defined. Perhaps there is a supporting GridField component you need to add?',
|
||||||
|
$contentKey
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$total = count($list);
|
$total = count($list);
|
||||||
|
|
||||||
if($total > 0) {
|
if($total > 0) {
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach($list as $idx => $record) {
|
|
||||||
|
foreach($list as $index => $record) {
|
||||||
if($record->hasMethod('canView') && !$record->canView()) {
|
if($record->hasMethod('canView') && !$record->canView()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -356,58 +411,80 @@ class GridField extends FormField {
|
|||||||
foreach($this->getColumns() as $column) {
|
foreach($this->getColumns() as $column) {
|
||||||
$colContent = $this->getColumnContent($record, $column);
|
$colContent = $this->getColumnContent($record, $column);
|
||||||
|
|
||||||
// A return value of null means this columns should be skipped altogether.
|
// Null means this columns should be skipped altogether.
|
||||||
|
|
||||||
if($colContent === null) {
|
if($colContent === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$colAttributes = $this->getColumnAttributes($record, $column);
|
$colAttributes = $this->getColumnAttributes($record, $column);
|
||||||
|
|
||||||
$rowContent .= $this->newCell($total, $idx, $record, $colAttributes, $colContent);
|
$rowContent .= $this->newCell(
|
||||||
|
$total,
|
||||||
|
$index,
|
||||||
|
$record,
|
||||||
|
$colAttributes,
|
||||||
|
$colContent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rowAttributes = $this->getRowAttributes($total, $idx, $record);
|
$rowAttributes = $this->getRowAttributes($total, $index, $record);
|
||||||
|
|
||||||
$rows[] = $this->newRow($total, $idx, $record, $rowAttributes, $rowContent);
|
$rows[] = $this->newRow($total, $index, $record, $rowAttributes, $rowContent);
|
||||||
}
|
}
|
||||||
$content['body'] = implode("\n", $rows);
|
$content['body'] = implode("\n", $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display a message when the grid field is empty
|
// Display a message when the grid field is empty.
|
||||||
if(!(isset($content['body']) && $content['body'])) {
|
|
||||||
$content['body'] = FormField::create_tag(
|
if(empty($content['body'])) {
|
||||||
'tr',
|
$cell = FormField::create_tag(
|
||||||
array("class" => 'ss-gridfield-item ss-gridfield-no-items'),
|
'td',
|
||||||
FormField::create_tag(
|
array(
|
||||||
'td',
|
'colspan' => count($columns),
|
||||||
array('colspan' => count($columns)),
|
),
|
||||||
_t('GridField.NoItemsFound', 'No items found')
|
_t('GridField.NoItemsFound', 'No items found')
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$row = FormField::create_tag(
|
||||||
|
'tr',
|
||||||
|
array(
|
||||||
|
'class' => 'ss-gridfield-item ss-gridfield-no-items',
|
||||||
|
),
|
||||||
|
$cell
|
||||||
|
);
|
||||||
|
|
||||||
|
$content['body'] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn into the relevant parts of a table
|
$header = $this->getOptionalTableHeader($content);
|
||||||
$head = $content['header']
|
$body = $this->getOptionalTableBody($content);
|
||||||
? FormField::create_tag('thead', array(), $content['header'])
|
$footer = $this->getOptionalTableFooter($content);
|
||||||
: '';
|
|
||||||
$body = $content['body']
|
|
||||||
? FormField::create_tag('tbody', array('class' => 'ss-gridfield-items'), $content['body'])
|
|
||||||
: '';
|
|
||||||
$foot = $content['footer']
|
|
||||||
? FormField::create_tag('tfoot', array(), $content['footer'])
|
|
||||||
: '';
|
|
||||||
|
|
||||||
$this->addExtraClass('ss-gridfield field');
|
$this->addExtraClass('ss-gridfield field');
|
||||||
$attrs = array_diff_key(
|
|
||||||
|
$fieldsetAttributes = array_diff_key(
|
||||||
$this->getAttributes(),
|
$this->getAttributes(),
|
||||||
array('value' => false, 'type' => false, 'name' => false)
|
array(
|
||||||
|
'value' => false,
|
||||||
|
'type' => false,
|
||||||
|
'name' => false,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
$attrs['data-name'] = $this->getName();
|
|
||||||
$tableAttrs = array(
|
$fieldsetAttributes['data-name'] = $this->getName();
|
||||||
'id' => isset($this->id) ? $this->id : null,
|
|
||||||
|
$tableId = null;
|
||||||
|
|
||||||
|
if($this->id) {
|
||||||
|
$tableId = $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tableAttributes = array(
|
||||||
|
'id' => $tableId,
|
||||||
'class' => 'ss-gridfield-table',
|
'class' => 'ss-gridfield-table',
|
||||||
'cellpadding' => '0',
|
'cellpadding' => '0',
|
||||||
'cellspacing' => '0'
|
'cellspacing' => '0',
|
||||||
);
|
);
|
||||||
|
|
||||||
if($this->getDescription()) {
|
if($this->getDescription()) {
|
||||||
@ -418,12 +495,17 @@ class GridField extends FormField {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
$table = FormField::create_tag(
|
||||||
FormField::create_tag('fieldset', $attrs,
|
'table',
|
||||||
$content['before'] .
|
$tableAttributes,
|
||||||
FormField::create_tag('table', $tableAttrs, $head . "\n" . $foot . "\n" . $body) .
|
$header . "\n" . $footer . "\n" . $body
|
||||||
$content['after']
|
);
|
||||||
);
|
|
||||||
|
return FormField::create_tag(
|
||||||
|
'fieldset',
|
||||||
|
$fieldsetAttributes,
|
||||||
|
$content['before'] . $table . $content['after']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -495,27 +577,44 @@ class GridField extends FormField {
|
|||||||
$classes[] = 'last';
|
$classes[] = 'last';
|
||||||
}
|
}
|
||||||
|
|
||||||
$classes[] = ($index % 2) ? 'even' : 'odd';
|
if($index % 2) {
|
||||||
|
$classes[] = 'even';
|
||||||
|
} else {
|
||||||
|
$classes[] = 'odd';
|
||||||
|
}
|
||||||
|
|
||||||
return $classes;
|
return $classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $properties
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function Field($properties = array()) {
|
public function Field($properties = array()) {
|
||||||
return $this->FieldHolder($properties);
|
return $this->FieldHolder($properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function getAttributes() {
|
public function getAttributes() {
|
||||||
return array_merge(parent::getAttributes(), array('data-url' => $this->Link()));
|
return array_merge(
|
||||||
|
parent::getAttributes(),
|
||||||
|
array(
|
||||||
|
'data-url' => $this->Link(),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the columns of this GridField, they are provided by attached GridField_ColumnProvider
|
* Get the columns of this GridField, they are provided by attached GridField_ColumnProvider.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getColumns() {
|
public function getColumns() {
|
||||||
// Get column list
|
|
||||||
$columns = array();
|
$columns = array();
|
||||||
|
|
||||||
foreach($this->getComponents() as $item) {
|
foreach($this->getComponents() as $item) {
|
||||||
if($item instanceof GridField_ColumnProvider) {
|
if($item instanceof GridField_ColumnProvider) {
|
||||||
$item->augmentColumns($this, $columns);
|
$item->augmentColumns($this, $columns);
|
||||||
@ -526,28 +625,36 @@ class GridField extends FormField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value from a column
|
* Get the value from a column.
|
||||||
*
|
*
|
||||||
* @param DataObject $record
|
* @param DataObject $record
|
||||||
* @param string $column
|
* @param string $column
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
|
*
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function getColumnContent($record, $column) {
|
public function getColumnContent($record, $column) {
|
||||||
// Build the column dispatch
|
|
||||||
if(!$this->columnDispatch) {
|
if(!$this->columnDispatch) {
|
||||||
$this->buildColumnDispatch();
|
$this->buildColumnDispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($this->columnDispatch[$column])) {
|
if(!empty($this->columnDispatch[$column])) {
|
||||||
$content = "";
|
$content = '';
|
||||||
|
|
||||||
foreach($this->columnDispatch[$column] as $handler) {
|
foreach($this->columnDispatch[$column] as $handler) {
|
||||||
|
/**
|
||||||
|
* @var GridField_ColumnProvider $handler
|
||||||
|
*/
|
||||||
$content .= $handler->getColumnContent($this, $record, $column);
|
$content .= $handler->getColumnContent($this, $record, $column);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentException("Bad column '$column'");
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'Bad column "%s"',
|
||||||
|
$column
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,111 +674,139 @@ class GridField extends FormField {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of a named field on the given record.
|
* Get the value of a named field on the given record.
|
||||||
* Use of this method ensures that any special rules around the data for this gridfield are followed.
|
*
|
||||||
|
* Use of this method ensures that any special rules around the data for this gridfield are
|
||||||
|
* followed.
|
||||||
|
*
|
||||||
|
* @param DataObject $record
|
||||||
|
* @param string $fieldName
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getDataFieldValue($record, $fieldName) {
|
public function getDataFieldValue($record, $fieldName) {
|
||||||
// Custom callbacks
|
|
||||||
if(isset($this->customDataFields[$fieldName])) {
|
if(isset($this->customDataFields[$fieldName])) {
|
||||||
$callback = $this->customDataFields[$fieldName];
|
$callback = $this->customDataFields[$fieldName];
|
||||||
|
|
||||||
return $callback($record);
|
return $callback($record);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation
|
|
||||||
if($record->hasMethod('relField')) {
|
if($record->hasMethod('relField')) {
|
||||||
return $record->relField($fieldName);
|
return $record->relField($fieldName);
|
||||||
} elseif($record->hasMethod($fieldName)) {
|
|
||||||
return $record->$fieldName();
|
|
||||||
} else {
|
|
||||||
return $record->$fieldName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($record->hasMethod($fieldName)) {
|
||||||
|
return $record->$fieldName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $record->$fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get extra columns attributes used as HTML attributes
|
* Get extra columns attributes used as HTML attributes.
|
||||||
*
|
*
|
||||||
* @param DataObject $record
|
* @param DataObject $record
|
||||||
* @param string $column
|
* @param string $column
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
|
*
|
||||||
* @throws LogicException
|
* @throws LogicException
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function getColumnAttributes($record, $column) {
|
public function getColumnAttributes($record, $column) {
|
||||||
// Build the column dispatch
|
|
||||||
if(!$this->columnDispatch) {
|
if(!$this->columnDispatch) {
|
||||||
$this->buildColumnDispatch();
|
$this->buildColumnDispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($this->columnDispatch[$column])) {
|
if(!empty($this->columnDispatch[$column])) {
|
||||||
$attrs = array();
|
$attributes = array();
|
||||||
|
|
||||||
foreach($this->columnDispatch[$column] as $handler) {
|
foreach($this->columnDispatch[$column] as $handler) {
|
||||||
$column_attrs = $handler->getColumnAttributes($this, $record, $column);
|
/**
|
||||||
|
* @var GridField_ColumnProvider $handler
|
||||||
|
*/
|
||||||
|
$columnAttributes = $handler->getColumnAttributes($this, $record, $column);
|
||||||
|
|
||||||
if(is_array($column_attrs)) {
|
if(is_array($columnAttributes)) {
|
||||||
$attrs = array_merge($attrs, $column_attrs);
|
$attributes = array_merge($attributes, $columnAttributes);
|
||||||
} elseif($column_attrs) {
|
continue;
|
||||||
$methodSignature = get_class($handler) . "::getColumnAttributes()";
|
|
||||||
throw new LogicException("Non-array response from $methodSignature.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new LogicException(sprintf(
|
||||||
|
'Non-array response from %s::getColumnAttributes().',
|
||||||
|
get_class($handler)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $attrs;
|
return $attributes;
|
||||||
} else {
|
|
||||||
throw new InvalidArgumentException("Bad column '$column'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'Bad column "%s"',
|
||||||
|
$column
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get metadata for a column, example array('Title'=>'Email address')
|
* Get metadata for a column.
|
||||||
|
*
|
||||||
|
* @example "array('Title'=>'Email address')"
|
||||||
*
|
*
|
||||||
* @param string $column
|
* @param string $column
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
|
*
|
||||||
* @throws LogicException
|
* @throws LogicException
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function getColumnMetadata($column) {
|
public function getColumnMetadata($column) {
|
||||||
// Build the column dispatch
|
|
||||||
if(!$this->columnDispatch) {
|
if(!$this->columnDispatch) {
|
||||||
$this->buildColumnDispatch();
|
$this->buildColumnDispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($this->columnDispatch[$column])) {
|
if(!empty($this->columnDispatch[$column])) {
|
||||||
$metadata = array();
|
$metaData = array();
|
||||||
|
|
||||||
foreach($this->columnDispatch[$column] as $handler) {
|
foreach($this->columnDispatch[$column] as $handler) {
|
||||||
$column_metadata = $handler->getColumnMetadata($this, $column);
|
/**
|
||||||
|
* @var GridField_ColumnProvider $handler
|
||||||
|
*/
|
||||||
|
$columnMetaData = $handler->getColumnMetadata($this, $column);
|
||||||
|
|
||||||
if(is_array($column_metadata)) {
|
if(is_array($columnMetaData)) {
|
||||||
$metadata = array_merge($metadata, $column_metadata);
|
$metaData = array_merge($metaData, $columnMetaData);
|
||||||
} else {
|
continue;
|
||||||
$methodSignature = get_class($handler) . "::getColumnMetadata()";
|
|
||||||
throw new LogicException("Non-array response from $methodSignature.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new LogicException(sprintf(
|
||||||
|
'Non-array response from %s::getColumnMetadata().',
|
||||||
|
get_class($handler)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $metadata;
|
return $metaData;
|
||||||
}
|
}
|
||||||
throw new InvalidArgumentException("Bad column '$column'");
|
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'Bad column "%s"',
|
||||||
|
$column
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return how many columns the grid will have
|
* Return how many columns the grid will have.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getColumnCount() {
|
public function getColumnCount() {
|
||||||
// Build the column dispatch
|
if(!$this->columnDispatch) {
|
||||||
if(!$this->columnDispatch) $this->buildColumnDispatch();
|
$this->buildColumnDispatch();
|
||||||
|
}
|
||||||
|
|
||||||
return count($this->columnDispatch);
|
return count($this->columnDispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an columnDispatch that maps a GridField_ColumnProvider to a column
|
* Build an columnDispatch that maps a GridField_ColumnProvider to a column for reference later.
|
||||||
* for reference later
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
protected function buildColumnDispatch() {
|
protected function buildColumnDispatch() {
|
||||||
$this->columnDispatch = array();
|
$this->columnDispatch = array();
|
||||||
@ -691,140 +826,172 @@ class GridField extends FormField {
|
|||||||
* This is the action that gets executed when a GridField_AlterAction gets clicked.
|
* This is the action that gets executed when a GridField_AlterAction gets clicked.
|
||||||
*
|
*
|
||||||
* @param array $data
|
* @param array $data
|
||||||
|
* @param Form $form
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) {
|
public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) {
|
||||||
$html = '';
|
|
||||||
$data = $request->requestVars();
|
$data = $request->requestVars();
|
||||||
$name = $this->getName();
|
$name = $this->getName();
|
||||||
$fieldData = isset($data[$name]) ? $data[$name] : null;
|
|
||||||
|
|
||||||
// Update state from client
|
$fieldData = null;
|
||||||
|
|
||||||
|
if(isset($data[$name])) {
|
||||||
|
$fieldData = $data[$name];
|
||||||
|
}
|
||||||
|
|
||||||
$state = $this->getState(false);
|
$state = $this->getState(false);
|
||||||
|
|
||||||
if(isset($fieldData['GridState'])) {
|
if(isset($fieldData['GridState'])) {
|
||||||
$state->setValue($fieldData['GridState']);
|
$state->setValue($fieldData['GridState']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to execute alter action
|
foreach($data as $dataKey => $dataValue) {
|
||||||
foreach($data as $k => $v) {
|
if(preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) {
|
||||||
if(preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $k, $matches)) {
|
$stateChange = Session::get($matches[1]);
|
||||||
$id = $matches[1];
|
|
||||||
$stateChange = Session::get($id);
|
|
||||||
|
|
||||||
$actionName = $stateChange['actionName'];
|
$actionName = $stateChange['actionName'];
|
||||||
|
|
||||||
$args = isset($stateChange['args']) ? $stateChange['args'] : array();
|
$arguments = array();
|
||||||
$html = $this->handleAlterAction($actionName, $args, $data);
|
|
||||||
// A field can optionally return its own HTML
|
if(isset($stateChange['args'])) {
|
||||||
if($html) return $html;
|
$arguments = $stateChange['args'];
|
||||||
|
};
|
||||||
|
|
||||||
|
$html = $this->handleAlterAction($actionName, $arguments, $data);
|
||||||
|
|
||||||
|
if($html) {
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch($request->getHeader('X-Pjax')) {
|
if($request->getHeader('X-Pjax') === 'CurrentField') {
|
||||||
case 'CurrentField':
|
return $this->FieldHolder();
|
||||||
return $this->FieldHolder();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'CurrentForm':
|
|
||||||
return $form->forTemplate();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return $form->forTemplate();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $form->forTemplate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass an action on the first GridField_ActionProvider that matches the $actionName
|
* Pass an action on the first GridField_ActionProvider that matches the $actionName.
|
||||||
*
|
*
|
||||||
* @param string $actionName
|
* @param string $actionName
|
||||||
* @param mixed $args
|
* @param mixed $arguments
|
||||||
* @param array $data - send data from a form
|
* @param array $data
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
*
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function handleAlterAction($actionName, $args, $data) {
|
public function handleAlterAction($actionName, $arguments, $data) {
|
||||||
$actionName = strtolower($actionName);
|
$actionName = strtolower($actionName);
|
||||||
foreach($this->getComponents() as $component) {
|
|
||||||
if(!($component instanceof GridField_ActionProvider)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(in_array($actionName, array_map('strtolower', (array) $component->getActions($this)))) {
|
foreach($this->getComponents() as $component) {
|
||||||
return $component->handleAction($this, $actionName, $args, $data);
|
if($component instanceof GridField_ActionProvider) {
|
||||||
|
$actions = array_map('strtolower', (array) $component->getActions($this));
|
||||||
|
|
||||||
|
if(in_array($actionName, $actions)) {
|
||||||
|
return $component->handleAction($this, $actionName, $arguments, $data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new InvalidArgumentException("Can't handle action '$actionName'");
|
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'Can\'t handle action "%s"',
|
||||||
|
$actionName
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom request handler that will check component handlers before proceeding to the default implementation.
|
* Custom request handler that will check component handlers before proceeding to the default
|
||||||
|
* implementation.
|
||||||
*
|
*
|
||||||
* @todo There is too much code copied from RequestHandler here.
|
* @todo copy less code from RequestHandler.
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
* @param DataModel $model
|
||||||
|
*
|
||||||
|
* @return array|RequestHandler|SS_HTTPResponse|string|void
|
||||||
|
*
|
||||||
|
* @throws SS_HTTPResponse_Exception
|
||||||
*/
|
*/
|
||||||
public function handleRequest(SS_HTTPRequest $request, DataModel $model) {
|
public function handleRequest(SS_HTTPRequest $request, DataModel $model) {
|
||||||
if($this->brokenOnConstruct) {
|
if($this->brokenOnConstruct) {
|
||||||
user_error("parent::__construct() needs to be called on {$handlerClass}::__construct()", E_USER_WARNING);
|
user_error(
|
||||||
|
sprintf(
|
||||||
|
"parent::__construct() needs to be called on %s::__construct()",
|
||||||
|
__CLASS__
|
||||||
|
),
|
||||||
|
E_USER_WARNING
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setRequest($request);
|
$this->setRequest($request);
|
||||||
$this->setDataModel($model);
|
$this->setDataModel($model);
|
||||||
|
|
||||||
$fieldData = $this->getRequest()->requestVar($this->getName());
|
$fieldData = $this->getRequest()->requestVar($this->getName());
|
||||||
if($fieldData && isset($fieldData['GridState'])) $this->getState(false)->setValue($fieldData['GridState']);
|
|
||||||
|
if($fieldData && isset($fieldData['GridState'])) {
|
||||||
|
$this->getState(false)->setValue($fieldData['GridState']);
|
||||||
|
}
|
||||||
|
|
||||||
foreach($this->getComponents() as $component) {
|
foreach($this->getComponents() as $component) {
|
||||||
if(!($component instanceof GridField_URLHandler)) {
|
if($component instanceof GridField_URLHandler && $urlHandlers = $component->getURLHandlers($this)) {
|
||||||
continue;
|
foreach($urlHandlers as $rule => $action) {
|
||||||
}
|
if($params = $request->match($rule, true)) {
|
||||||
|
// Actions can reference URL parameters.
|
||||||
|
// e.g. '$Action/$ID/$OtherID' → '$Action'
|
||||||
|
|
||||||
$urlHandlers = $component->getURLHandlers($this);
|
if($action[0] == '$') {
|
||||||
|
$action = $params[substr($action, 1)];
|
||||||
if($urlHandlers) foreach($urlHandlers as $rule => $action) {
|
|
||||||
if($params = $request->match($rule, true)) {
|
|
||||||
// Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action',
|
|
||||||
if($action[0] == '$') $action = $params[substr($action, 1)];
|
|
||||||
if(!method_exists($component, 'checkAccessAction') || $component->checkAccessAction($action)) {
|
|
||||||
if(!$action) {
|
|
||||||
$action = "index";
|
|
||||||
} else if(!is_string($action)) {
|
|
||||||
throw new LogicException("Non-string method name: " . var_export($action, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if(!method_exists($component, 'checkAccessAction') || $component->checkAccessAction($action)) {
|
||||||
$result = $component->$action($this, $request);
|
if(!$action) {
|
||||||
} catch(SS_HTTPResponse_Exception $responseException) {
|
$action = "index";
|
||||||
$result = $responseException->getResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
if($result instanceof SS_HTTPResponse && $result->isError()) {
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result)
|
|
||||||
&& $result instanceof RequestHandler
|
|
||||||
) {
|
|
||||||
|
|
||||||
$returnValue = $result->handleRequest($request, $model);
|
|
||||||
|
|
||||||
if(is_array($returnValue)) {
|
|
||||||
throw new LogicException("GridField_URLHandler handlers can't return arrays");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $returnValue;
|
if(!is_string($action)) {
|
||||||
|
throw new LogicException(sprintf(
|
||||||
|
'Non-string method name: %s',
|
||||||
|
var_export($action, true)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// If we return some other data, and all the URL is parsed, then return that
|
try {
|
||||||
} else if($request->allParsed()) {
|
$result = $component->$action($this, $request);
|
||||||
return $result;
|
} catch(SS_HTTPResponse_Exception $responseException) {
|
||||||
|
$result = $responseException->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
// But if we have more content on the URL and we don't know what to do with it, return an error
|
if($result instanceof SS_HTTPResponse && $result->isError()) {
|
||||||
} else {
|
return $result;
|
||||||
return $this->httpError(404,
|
}
|
||||||
"I can't handle sub-URLs of a " . get_class($result) . " object.");
|
|
||||||
|
if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result) && $result instanceof RequestHandler) {
|
||||||
|
$returnValue = $result->handleRequest($request, $model);
|
||||||
|
|
||||||
|
if(is_array($returnValue)) {
|
||||||
|
throw new LogicException(
|
||||||
|
'GridField_URLHandler handlers can\'t return arrays'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->allParsed()) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->httpError(
|
||||||
|
404,
|
||||||
|
sprintf(
|
||||||
|
'I can\'t handle sub-URLs of a %s object.',
|
||||||
|
get_class($result)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -834,6 +1001,9 @@ class GridField extends FormField {
|
|||||||
return parent::handleRequest($request, $model);
|
return parent::handleRequest($request, $model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function saveInto(DataObjectInterface $record) {
|
public function saveInto(DataObjectInterface $record) {
|
||||||
foreach($this->getComponents() as $component) {
|
foreach($this->getComponents() as $component) {
|
||||||
if($component instanceof GridField_SaveHandler) {
|
if($component instanceof GridField_SaveHandler) {
|
||||||
@ -842,18 +1012,61 @@ class GridField extends FormField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $content
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getOptionalTableHeader(array $content) {
|
||||||
|
if($content['header']) {
|
||||||
|
return FormField::create_tag(
|
||||||
|
'thead', array(), $content['header']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $content
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getOptionalTableBody(array $content) {
|
||||||
|
if($content['body']) {
|
||||||
|
return FormField::create_tag(
|
||||||
|
'tbody', array('class' => 'ss-gridfield-items'), $content['body']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $content
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getOptionalTableFooter($content) {
|
||||||
|
if($content['footer']) {
|
||||||
|
return FormField::create_tag(
|
||||||
|
'tfoot', array(), $content['footer']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the base class when you want to have an action that alters
|
* This class is the base class when you want to have an action that alters the state of the
|
||||||
* the state of the {@link GridField}, rendered as a button element.
|
* {@link GridField}, rendered as a button element.
|
||||||
*
|
*
|
||||||
* @package forms
|
* @package forms
|
||||||
* @subpackage fields-gridfield
|
* @subpackage fields-gridfield
|
||||||
*/
|
*/
|
||||||
class GridField_FormAction extends FormAction {
|
class GridField_FormAction extends FormAction {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var GridField
|
* @var GridField
|
||||||
*/
|
*/
|
||||||
@ -882,7 +1095,7 @@ class GridField_FormAction extends FormAction {
|
|||||||
/**
|
/**
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $label
|
* @param string $title
|
||||||
* @param string $actionName
|
* @param string $actionName
|
||||||
* @param array $args
|
* @param array $args
|
||||||
*/
|
*/
|
||||||
@ -895,19 +1108,20 @@ class GridField_FormAction extends FormAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* urlencode encodes less characters in percent form than we need - we
|
* Encode all non-word characters.
|
||||||
* need everything that isn't a \w.
|
|
||||||
*
|
*
|
||||||
* @param string $val
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function nameEncode($val) {
|
public function nameEncode($value) {
|
||||||
return preg_replace_callback('/[^\w]/', array($this, '_nameEncode'), $val);
|
return (string) preg_replace_callback('/[^\w]/', array($this, '_nameEncode'), $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback for nameEncode
|
* @param array $match
|
||||||
*
|
*
|
||||||
* @param string $val
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function _nameEncode($match) {
|
public function _nameEncode($match) {
|
||||||
return '%' . dechex(ord($match[0]));
|
return '%' . dechex(ord($match[0]));
|
||||||
@ -941,9 +1155,7 @@ class GridField_FormAction extends FormAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the name of the gridfield relative to the Form
|
* Calculate the name of the gridfield relative to the form.
|
||||||
*
|
|
||||||
* @param GridField $base
|
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -18,8 +18,6 @@ class DirectorTest extends SapphireTest {
|
|||||||
public function setUp() {
|
public function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
// Required for testRequestFilterInDirectorTest
|
|
||||||
Injector::nest();
|
|
||||||
|
|
||||||
// Hold the original request URI once so it doesn't get overwritten
|
// Hold the original request URI once so it doesn't get overwritten
|
||||||
if(!self::$originalRequestURI) {
|
if(!self::$originalRequestURI) {
|
||||||
@ -53,9 +51,6 @@ class DirectorTest extends SapphireTest {
|
|||||||
public function tearDown() {
|
public function tearDown() {
|
||||||
// TODO Remove director rule, currently API doesnt allow this
|
// TODO Remove director rule, currently API doesnt allow this
|
||||||
|
|
||||||
// Remove base URL override (setting to false reverts to default behaviour)
|
|
||||||
Config::inst()->update('Director', 'alternate_base_url', false);
|
|
||||||
|
|
||||||
$_GET = $this->originalGet;
|
$_GET = $this->originalGet;
|
||||||
$_SESSION = $this->originalSession;
|
$_SESSION = $this->originalSession;
|
||||||
|
|
||||||
@ -68,7 +63,6 @@ class DirectorTest extends SapphireTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Injector::unnest();
|
|
||||||
|
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
@ -189,8 +189,7 @@ class ConfigTest extends SapphireTest {
|
|||||||
// But it won't affect subclasses - this is *uninherited* static
|
// But it won't affect subclasses - this is *uninherited* static
|
||||||
$this->assertNotContains('test_2b',
|
$this->assertNotContains('test_2b',
|
||||||
Config::inst()->get('ConfigStaticTest_Third', 'first', Config::UNINHERITED));
|
Config::inst()->get('ConfigStaticTest_Third', 'first', Config::UNINHERITED));
|
||||||
$this->assertNotContains('test_2b',
|
$this->assertNull(Config::inst()->get('ConfigStaticTest_Fourth', 'first', Config::UNINHERITED));
|
||||||
Config::inst()->get('ConfigStaticTest_Fourth', 'first', Config::UNINHERITED));
|
|
||||||
|
|
||||||
// Subclasses that don't have the static explicitly defined should allow definition, also
|
// Subclasses that don't have the static explicitly defined should allow definition, also
|
||||||
// This also checks that set can be called after the first uninherited get()
|
// This also checks that set can be called after the first uninherited get()
|
||||||
|
@ -391,7 +391,7 @@ class ConfigManifestTest extends SapphireTest {
|
|||||||
|
|
||||||
public function testEnvironmentRules() {
|
public function testEnvironmentRules() {
|
||||||
foreach (array('dev', 'test', 'live') as $env) {
|
foreach (array('dev', 'test', 'live') as $env) {
|
||||||
Config::inst()->nest();
|
Config::nest();
|
||||||
|
|
||||||
Config::inst()->update('Director', 'environment_type', $env);
|
Config::inst()->update('Director', 'environment_type', $env);
|
||||||
$config = $this->getConfigFixtureValue('Environment');
|
$config = $this->getConfigFixtureValue('Environment');
|
||||||
@ -403,13 +403,11 @@ class ConfigManifestTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Config::inst()->unnest();
|
Config::unnest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDynamicEnvironmentRules() {
|
public function testDynamicEnvironmentRules() {
|
||||||
Config::inst()->nest();
|
|
||||||
|
|
||||||
// First, make sure environment_type is live
|
// First, make sure environment_type is live
|
||||||
Config::inst()->update('Director', 'environment_type', 'live');
|
Config::inst()->update('Director', 'environment_type', 'live');
|
||||||
$this->assertEquals('live', Config::inst()->get('Director', 'environment_type'));
|
$this->assertEquals('live', Config::inst()->get('Director', 'environment_type'));
|
||||||
@ -423,8 +421,6 @@ class ConfigManifestTest extends SapphireTest {
|
|||||||
|
|
||||||
// And that the dynamic rule was calculated correctly
|
// And that the dynamic rule was calculated correctly
|
||||||
$this->assertEquals('dev', Config::inst()->get('ConfigManifestTest', 'DynamicEnvironment'));
|
$this->assertEquals('dev', Config::inst()->get('ConfigManifestTest', 'DynamicEnvironment'));
|
||||||
|
|
||||||
Config::inst()->unnest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMultipleRules() {
|
public function testMultipleRules() {
|
||||||
|
@ -11,7 +11,7 @@ class DevAdminControllerTest extends FunctionalTest {
|
|||||||
public function setUp(){
|
public function setUp(){
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
Config::nest()->update('DevelopmentAdmin', 'registered_controllers', array(
|
Config::inst()->update('DevelopmentAdmin', 'registered_controllers', array(
|
||||||
'x1' => array(
|
'x1' => array(
|
||||||
'controller' => 'DevAdminControllerTest_Controller1',
|
'controller' => 'DevAdminControllerTest_Controller1',
|
||||||
'links' => array(
|
'links' => array(
|
||||||
@ -28,11 +28,6 @@ class DevAdminControllerTest extends FunctionalTest {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown(){
|
|
||||||
parent::tearDown();
|
|
||||||
Config::unnest();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function testGoodRegisteredControllerOutput(){
|
public function testGoodRegisteredControllerOutput(){
|
||||||
// Check for the controller running from the registered url above
|
// Check for the controller running from the registered url above
|
||||||
|
@ -214,6 +214,24 @@ class DatetimeFieldTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetName() {
|
||||||
|
$field = new DatetimeField('Datetime');
|
||||||
|
|
||||||
|
$this->assertEquals('Datetime', $field->getName());
|
||||||
|
$this->assertEquals('Datetime[date]', $field->getDateField()->getName());
|
||||||
|
$this->assertEquals('Datetime[time]', $field->getTimeField()->getName());
|
||||||
|
$this->assertEquals('Datetime[timezone]', $field->getTimezoneField()->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetName() {
|
||||||
|
$field = new DatetimeField('Datetime', 'Datetime');
|
||||||
|
$field->setName('CustomDatetime');
|
||||||
|
$this->assertEquals('CustomDatetime', $field->getName());
|
||||||
|
$this->assertEquals('CustomDatetime[date]', $field->getDateField()->getName());
|
||||||
|
$this->assertEquals('CustomDatetime[time]', $field->getTimeField()->getName());
|
||||||
|
$this->assertEquals('CustomDatetime[timezone]', $field->getTimezoneField()->getName());
|
||||||
|
}
|
||||||
|
|
||||||
protected function getMockForm() {
|
protected function getMockForm() {
|
||||||
return new Form(
|
return new Form(
|
||||||
new Controller(),
|
new Controller(),
|
||||||
|
@ -91,6 +91,14 @@ class TimeFieldTest extends SapphireTest {
|
|||||||
$f = new TimeField('Time', 'Time');
|
$f = new TimeField('Time', 'Time');
|
||||||
$f->setValue('23:59:38');
|
$f->setValue('23:59:38');
|
||||||
$this->assertEquals($f->dataValue(), '23:59:38');
|
$this->assertEquals($f->dataValue(), '23:59:38');
|
||||||
|
|
||||||
|
$f = new TimeField('Time', 'Time');
|
||||||
|
$f->setValue('12:00 am');
|
||||||
|
$this->assertEquals($f->dataValue(), '00:00:00');
|
||||||
|
|
||||||
|
$f = new TimeField('Time', 'Time');
|
||||||
|
$f->setValue('12:00:01 am');
|
||||||
|
$this->assertEquals($f->dataValue(), '00:00:01');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testOverrideWithNull() {
|
public function testOverrideWithNull() {
|
||||||
|
@ -46,7 +46,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
// Table will have been initially created by the $extraDataObjects setting
|
// Table will have been initially created by the $extraDataObjects setting
|
||||||
|
|
||||||
// Let's insert a new field here
|
// Let's insert a new field here
|
||||||
Config::nest();
|
|
||||||
Config::inst()->update('DataObjectSchemaGenerationTest_DO', 'db', array(
|
Config::inst()->update('DataObjectSchemaGenerationTest_DO', 'db', array(
|
||||||
'SecretField' => 'Varchar(100)'
|
'SecretField' => 'Varchar(100)'
|
||||||
));
|
));
|
||||||
@ -59,9 +58,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
$schema->cancelSchemaUpdate();
|
$schema->cancelSchemaUpdate();
|
||||||
$test->assertTrue($needsUpdating);
|
$test->assertTrue($needsUpdating);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore db configuration
|
|
||||||
Config::unnest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,7 +80,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Test with alternate index format, although these indexes are the same
|
// Test with alternate index format, although these indexes are the same
|
||||||
Config::nest();
|
|
||||||
Config::inst()->remove('DataObjectSchemaGenerationTest_IndexDO', 'indexes');
|
Config::inst()->remove('DataObjectSchemaGenerationTest_IndexDO', 'indexes');
|
||||||
Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes',
|
Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes',
|
||||||
Config::inst()->get('DataObjectSchemaGenerationTest_IndexDO', 'indexes_alt')
|
Config::inst()->get('DataObjectSchemaGenerationTest_IndexDO', 'indexes_alt')
|
||||||
@ -98,9 +93,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
$schema->cancelSchemaUpdate();
|
$schema->cancelSchemaUpdate();
|
||||||
$test->assertFalse($needsUpdating);
|
$test->assertFalse($needsUpdating);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore old index format
|
|
||||||
Config::unnest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,7 +106,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
// Table will have been initially created by the $extraDataObjects setting
|
// Table will have been initially created by the $extraDataObjects setting
|
||||||
|
|
||||||
// Update the SearchFields index here
|
// Update the SearchFields index here
|
||||||
Config::nest();
|
|
||||||
Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes', array(
|
Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes', array(
|
||||||
'SearchFields' => array(
|
'SearchFields' => array(
|
||||||
'value' => 'Title'
|
'value' => 'Title'
|
||||||
@ -129,9 +120,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
$schema->cancelSchemaUpdate();
|
$schema->cancelSchemaUpdate();
|
||||||
$test->assertTrue($needsUpdating);
|
$test->assertTrue($needsUpdating);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore old indexes
|
|
||||||
Config::unnest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,16 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
class OembedTest extends SapphireTest {
|
class OembedTest extends SapphireTest {
|
||||||
public function setUp() {
|
|
||||||
parent::setUp();
|
|
||||||
Config::nest();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function tearDown() {
|
|
||||||
Config::unnest();
|
|
||||||
parent::tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetOembedFromUrl() {
|
public function testGetOembedFromUrl() {
|
||||||
Config::inst()->update('Oembed', 'providers', array(
|
Config::inst()->update('Oembed', 'providers', array(
|
||||||
'http://*.silverstripe.com/watch*'=>'http://www.silverstripe.com/oembed/'
|
'http://*.silverstripe.com/watch*'=>'http://www.silverstripe.com/oembed/'
|
||||||
|
@ -14,17 +14,11 @@ class BasicAuthTest extends FunctionalTest {
|
|||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
// Fixtures assume Email is the field used to identify the log in identity
|
// Fixtures assume Email is the field used to identify the log in identity
|
||||||
Config::nest();
|
|
||||||
Member::config()->unique_identifier_field = 'Email';
|
Member::config()->unique_identifier_field = 'Email';
|
||||||
Security::$force_database_is_ready = true; // Prevents Member test subclasses breaking ready test
|
Security::$force_database_is_ready = true; // Prevents Member test subclasses breaking ready test
|
||||||
Member::config()->lock_out_after_incorrect_logins = 10;
|
Member::config()->lock_out_after_incorrect_logins = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown() {
|
|
||||||
Config::unnest();
|
|
||||||
parent::tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBasicAuthEnabledWithoutLogin() {
|
public function testBasicAuthEnabledWithoutLogin() {
|
||||||
$origUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
|
$origUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
|
||||||
$origPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
|
$origPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user