Merge remote-tracking branch 'origin/3.1' into 3

Conflicts:
	admin/code/LeftAndMain.php
	forms/EmailField.php
	forms/Form.php
	forms/HeaderField.php
	forms/LiteralField.php
	forms/PasswordField.php
	forms/TextareaField.php
	forms/TreeDropdownField.php
	model/DataObject.php
	tests/forms/uploadfield/UploadFieldTest.php
	tests/model/DataObjectTest.php
This commit is contained in:
Damian Mooyman 2015-06-17 11:24:25 +12:00
commit 0abacaead6
28 changed files with 528 additions and 287 deletions

View File

@ -43,7 +43,7 @@ matrix:
- sudo apt-get install -y tidy - sudo apt-get install -y tidy
before_script: before_script:
- composer self-update - composer self-update || true
- phpenv rehash - phpenv rehash
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support - git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- "if [ \"$BEHAT_TEST\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi" - "if [ \"$BEHAT_TEST\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi"

View File

@ -830,16 +830,19 @@ class LeftAndMain extends Controller implements PermissionProvider {
$record = ($rootID) ? $this->getRecord($rootID) : null; $record = ($rootID) ? $this->getRecord($rootID) : null;
$obj = $record ? $record : singleton($className); $obj = $record ? $record : singleton($className);
// Get the current page
// NOTE: This *must* be fetched before markPartialTree() is called, as this
// causes the Hierarchy::$marked cache to be flushed (@see CMSMain::getRecord)
// which means that deleted pages stored in the marked tree would be removed
$currentPage = $this->currentPage();
// Mark the nodes of the tree to return // Mark the nodes of the tree to return
if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction); if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction);
$obj->markPartialTree($nodeCountThreshold, $this, $childrenMethod, $numChildrenMethod); $obj->markPartialTree($nodeCountThreshold, $this, $childrenMethod, $numChildrenMethod);
// Ensure current page is exposed // Ensure current page is exposed
// This call flushes the Hierarchy::$marked cache when the current node is deleted if($currentPage) $obj->markToExpose($currentPage);
// @see CMSMain::getRecord()
// This will make it impossible to show children under a deleted parent page
// if($p = $this->currentPage()) $obj->markToExpose($p);
// NOTE: SiteTree/CMSMain coupling :-( // NOTE: SiteTree/CMSMain coupling :-(
if(class_exists('SiteTree')) { if(class_exists('SiteTree')) {

View File

@ -265,6 +265,7 @@ form.small .field input.text, form.small .field textarea, form.small .field sele
.ss-toggle .ui-accordion-content .field:last-child { margin-bottom: 0; } .ss-toggle .ui-accordion-content .field:last-child { margin-bottom: 0; }
.ss-toggle .ui-accordion-content .field .middleColumn { margin-left: 0; } .ss-toggle .ui-accordion-content .field .middleColumn { margin-left: 0; }
.ss-toggle .ui-accordion-content .field label { float: none; margin-left: 0; } .ss-toggle .ui-accordion-content .field label { float: none; margin-left: 0; }
.ss-toggle .ui-accordion-content .field label.ss-ui-button { float: left; }
.ss-toggle .ui-accordion-content .field .description { margin-left: 0; } .ss-toggle .ui-accordion-content .field .description { margin-left: 0; }
/** ---------------------------------------------------- Checkbox Field ---------------------------------------------------- */ /** ---------------------------------------------------- Checkbox Field ---------------------------------------------------- */

View File

@ -681,6 +681,10 @@ form.small .field, .field.small {
label { label {
float: none; float: none;
margin-left: 0; margin-left: 0;
&.ss-ui-button {
float: left;
}
} }
.description { .description {
margin-left: 0; margin-left: 0;

View File

@ -105,21 +105,21 @@ This is my `_ss_environment.php` file. I have it placed in `/var`, as each of th
| Name | Description | | Name | Description |
| ---- | ----------- | | ---- | ----------- |
| `TEMP_FOLDER` | Absolute file path to store temporary files such as cached templates or the class manifest. Needs to be writeable by the webserver user. Defaults to *silverstripe-cache* in the webroot, and falls back to *sys_get_temp_dir()*. See *getTempFolder()* in *framework/core/TempPath.php* | | `TEMP_FOLDER` | Absolute file path to store temporary files such as cached templates or the class manifest. Needs to be writeable by the webserver user. Defaults to *silverstripe-cache* in the webroot, and falls back to *sys_get_temp_dir()*. See *getTempFolder()* in *framework/core/TempPath.php*.|
| `SS_DATABASE_CLASS` | The database class to use, MySQLDatabase, MSSQLDatabase, etc. defaults to MySQLDatabase| | `SS_DATABASE_CLASS` | The database class to use, MySQLDatabase, MSSQLDatabase, etc. defaults to MySQLDatabase.|
| `SS_DATABASE_SERVER`| The database server to use, defaulting to localhost| | `SS_DATABASE_SERVER`| The database server to use, defaulting to localhost.|
| `SS_DATABASE_USERNAME`| The database username (mandatory)| | `SS_DATABASE_USERNAME`| The database username (mandatory).|
| `SS_DATABASE_PASSWORD`| The database password (mandatory)| | `SS_DATABASE_PASSWORD`| The database password (mandatory).|
| `SS_DATABASE_PORT`| The database port| | `SS_DATABASE_PORT`| The database port.|
| `SS_DATABASE_SUFFIX`| A suffix to add to the database name.| | `SS_DATABASE_SUFFIX`| A suffix to add to the database name.|
| `SS_DATABASE_PREFIX`| A prefix to add to the database name.| | `SS_DATABASE_PREFIX`| A prefix to add to the database name.|
| `SS_DATABASE_TIMEZONE`| Set the database timezone to something other than the system timezone. | `SS_DATABASE_TIMEZONE`| Set the database timezone to something other than the system timezone.
| `SS_DATABASE_NAME` | Set the database name. Assumes the `$database` global variable in your config is missing or empty. | | `SS_DATABASE_NAME` | Set the database name. Assumes the `$database` global variable in your config is missing or empty. |
| `SS_DATABASE_CHOOSE_NAME`| Boolean/Int. If set, then the system will choose a default database name for you if one isn't give in the $database variable. The database name will be "SS_" followed by the name of the folder into which you have installed SilverStripe. If this is enabled, it means that the phpinstaller will work out of the box without the installer needing to alter any files. This helps prevent accidental changes to the environment. If `SS_DATABASE_CHOOSE_NAME` is an integer greater than one, then an ancestor folder will be used for the database name. This is handy for a site that's hosted from /sites/examplesite/www or /buildbot/allmodules-2.3/build. If it's 2, the parent folder will be chosen; if it's 3 the grandparent, and so on.| | `SS_DATABASE_CHOOSE_NAME`| Boolean/Int. If defined, then the system will choose a default database name for you if one isn't give in the $database variable. The database name will be "SS_" followed by the name of the folder into which you have installed SilverStripe. If this is enabled, it means that the phpinstaller will work out of the box without the installer needing to alter any files. This helps prevent accidental changes to the environment. If `SS_DATABASE_CHOOSE_NAME` is an integer greater than one, then an ancestor folder will be used for the database name. This is handy for a site that's hosted from /sites/examplesite/www or /buildbot/allmodules-2.3/build. If it's 2, the parent folder will be chosen; if it's 3 the grandparent, and so on.|
| `SS_ENVIRONMENT_TYPE`| The environment type: dev, test or live.| | `SS_ENVIRONMENT_TYPE`| The environment type: dev, test or live.|
| `SS_DEFAULT_ADMIN_USERNAME`| The username of the default admin. This is a user with administrative privileges.| | `SS_DEFAULT_ADMIN_USERNAME`| The username of the default admin. This is a user with administrative privileges.|
| `SS_DEFAULT_ADMIN_PASSWORD`| The password of the default admin. This will not be stored in the database.| | `SS_DEFAULT_ADMIN_PASSWORD`| The password of the default admin. This will not be stored in the database.|
| `SS_USE_BASIC_AUTH`| Protect the site with basic auth (good for test sites).<br/>When using CGI/FastCGI with Apache, you will have to add the `RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]` rewrite rule to your `.htaccess` file| | `SS_USE_BASIC_AUTH`| Protect the site with basic auth (good for test sites).<br/>When using CGI/FastCGI with Apache, you will have to add the `RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]` rewrite rule to your `.htaccess` file|
| `SS_SEND_ALL_EMAILS_TO`| If you set this define, all emails will be redirected to this address.| | `SS_SEND_ALL_EMAILS_TO`| If you define this constant, all emails will be redirected to this address.|
| `SS_SEND_ALL_EMAILS_FROM`| If you set this define, all emails will be send from this address.| | `SS_SEND_ALL_EMAILS_FROM`| If you define this constant, all emails will be sent from this address.|
| `SS_ERROR_LOG` | Relative path to the log file | | `SS_ERROR_LOG` | Relative path to the log file. |

View File

@ -11,7 +11,7 @@ These include video screencasts, written tutorials and code examples to get you
* [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537) * [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537)
* [Lesson 1: Creating your first theme](http://www.silverstripe.org/learn/lessons/creating-your-first-theme) * [Lesson 1: Creating your first theme](http://www.silverstripe.org/learn/lessons/creating-your-first-theme)
* [Lesson 2: Migrating static templates into your theme]http://www.silverstripe.org/learn/lessons/migrating-static-templates-into-your-theme) * [Lesson 2: Migrating static templates into your theme](http://www.silverstripe.org/learn/lessons/migrating-static-templates-into-your-theme)
* [Lesson 3: Adding dynamic content](http://www.silverstripe.org/learn/lessons/adding-dynamic-content) * [Lesson 3: Adding dynamic content](http://www.silverstripe.org/learn/lessons/adding-dynamic-content)
* [Lesson 4: Working with multiple templates](http://www.silverstripe.org/learn/lessons/working-with-multiple-templates) * [Lesson 4: Working with multiple templates](http://www.silverstripe.org/learn/lessons/working-with-multiple-templates)
* [Lesson 5: The holder/page pattern](http://www.silverstripe.org/learn/lessons/the-holderpage-pattern) * [Lesson 5: The holder/page pattern](http://www.silverstripe.org/learn/lessons/the-holderpage-pattern)
@ -20,7 +20,13 @@ These include video screencasts, written tutorials and code examples to get you
* [Lesson 8: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/introduction-to-the-orm) * [Lesson 8: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/introduction-to-the-orm)
* [Lesson 9: Data Relationships - $has_many](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-has-many) * [Lesson 9: Data Relationships - $has_many](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-has-many)
* [Lesson 10: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-many-many) * [Lesson 10: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-many-many)
* [Lesson 11: Introduction to frontend forms](http://www.silverstripe.org/learn/lessons/introduction-to-frontend-forms)
* [Lesson 12: Data Extensions and SiteConfig](http://www.silverstripe.org/learn/lessons/data-extensions-and-siteconfig)
* [Lesson 13: Introduction to ModelAdmin](http://www.silverstripe.org/learn/lessons/introduction-to-modeladmin)
* [Lesson 14: Controller Actions/DataObjects as Pages](http://www.silverstripe.org/learn/lessons/controller-actions-dataobjects-as-pages)
* [Lesson 15: Building a Search Form](http://www.silverstripe.org/learn/lessons/building-a-search-form)
* [Lesson 16: Lists and Pagination](http://www.silverstripe.org/learn/lessons/lists-and-pagination)
* [Lesson 17: Ajax Behaviour and Viewable Data](http://www.silverstripe.org/learn/lessons/ajax-behaviour-and-viewabledata)
## Help: If you get stuck ## Help: If you get stuck

View File

@ -15,7 +15,7 @@ how you can load default records into the test database.
/** /**
* Defines the fixture file to use for this test class * Defines the fixture file to use for this test class
* *
/ */
protected static $fixture_file = 'SiteTreeTest.yml'; protected static $fixture_file = 'SiteTreeTest.yml';
/** /**
@ -79,4 +79,4 @@ For more information on PHPUnit's assertions see the [PHPUnit manual](http://www
## API Documentation ## API Documentation
* [api:SapphireTest] * [api:SapphireTest]
* [api:FunctionalTest] * [api:FunctionalTest]

View File

@ -11,7 +11,7 @@ SilverStripe needs to boot its core and run through several stages of processing
The first step in most environments is a rewrite of a request path into parameters passed to a PHP script. The first step in most environments is a rewrite of a request path into parameters passed to a PHP script.
This allows writing friendly URLs instead of linking directly to PHP files. This allows writing friendly URLs instead of linking directly to PHP files.
The implementation depends on your web server, we'll show you the most common one here: The implementation depends on your web server; we'll show you the most common one here:
Apache with [mod_rewrite](http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html). Apache with [mod_rewrite](http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html).
Check our [installation guides](/getting_started/installation) on how other web servers like IIS or nginx handle rewriting. Check our [installation guides](/getting_started/installation) on how other web servers like IIS or nginx handle rewriting.
@ -123,7 +123,7 @@ further filtering before content is sent to the end user
The framework provides the ability to hook into the request both before and The framework provides the ability to hook into the request both before and
after it is handled to allow binding custom logic. This can be used after it is handled to allow binding custom logic. This can be used
to transform or filter request data, instanciate helpers, execute global logic, to transform or filter request data, instantiate helpers, execute global logic,
or even short-circuit execution (e.g. to enforce custom authentication schemes). or even short-circuit execution (e.g. to enforce custom authentication schemes).
The ["Request Filters" documentation](../controllers/requestfilters) shows you how. The ["Request Filters" documentation](../controllers/requestfilters) shows you how.

View File

@ -3,45 +3,36 @@ summary: Writing guide for contributing to SilverStripe developer and CMS user h
# Contributing documentation # Contributing documentation
Documentation for a software project is a continued and collaborative effort, we encourage everybody to contribute, from Documentation for a software project is a continued and collaborative effort. We encourage everybody to contribute in any way they can, from simply fixing spelling mistakes, to writing recipes, to reviewing existing documentation and translating it to another language.
simply fixing spelling mistakes, to writing recipes, reviewing existing documentation, and translating the whole thing.
Modifying documentation requires basic [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) and Modifying documentation requires basic [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) and
[Markdown](http://daringfireball.net/projects/markdown/) knowledge, and a GitHub user account. [Markdown](http://daringfireball.net/projects/markdown/) knowledge, and a GitHub user account.
## Editing online ## Editing online
The easiest way of making a change to the documentation is by clicking the "Edit this page" link at the bottom of the The easiest way of editing any documentation is by clicking the "Edit this page" link at the bottom of the
page you want to edit. Alternatively, you can find the appropriate .md file in the page you want to edit. Alternatively, locate the appropriate .md file in the
[github.com/silverstripe/silverstripe-framework](https://github.com/silverstripe/silverstripe-framework/tree/master/docs/) [github.com/silverstripe/silverstripe-framework](https://github.com/silverstripe/silverstripe-framework/tree/master/docs/) repository and press the "edit" button. **You will need a free GitHub account to do this**.
repository and press the "edit" button. **You will need a free GitHub account to do this**.
* After you have made your change, describe it in the "commit summary" and "extended description" fields below, and * After editing the documentation, describe your changes in the "commit summary" and "extended description" fields below then press "Commit Changes".
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 form to submit a Pull Request. You should be able to adjust the version your document ion change is for and then submit the form. Your changes * documentation changes are for and then submit the form. Your changes will be sent to the core committers for approval.
will be sent to the core committers for approval.
<div class="warning" markdown='1'> <div class="warning" markdown='1'>
You should make the changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you 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*.
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*.
</div> </div>
## Editing on your computer ## Editing on your computer
If you prefer to edit the content on your local machine, you can "[fork](http://help.github.com/forking/)" the If you prefer to edit content on your local machine, you can "[fork](http://help.github.com/forking/)" the
[github.com/silverstripe/silverstripe-framework](http://github.com/silverstripe/silverstripe-framework) and [github.com/silverstripe/silverstripe-framework](http://github.com/silverstripe/silverstripe-framework) and
[github.com/silverstripe/silverstripe-cms](http://github.com/silverstripe/silverstripe-cms) repositories and send us [github.com/silverstripe/silverstripe-cms](http://github.com/silverstripe/silverstripe-cms) repositories, and send us "[pull requests](http://help.github.com/pull-requests/)" to incorporate your changes. If you have previously downloaded SilverStripe or a module, chances are that you already have these repositories on your machine.
"[pull requests](http://help.github.com/pull-requests/)". If you have downloaded SilverStripe or a module, chances are
that you already have these repositories on your machine.
The documentation is kept alongside the source code in the `docs/` subfolder of any SilverStripe module, framework or The documentation is kept alongside the source code in the `docs/` subfolder of any SilverStripe module, framework or CMS folder.
CMS folder.
<div class="warning" markdown='1'> <div class="warning" markdown='1'>
If you submit a new feature or an API change, we strongly recommend that your patch includes updates to the necessary If you submit a new feature or an API change, we strongly recommend that your patch includes updates to the necessary documentation. This helps prevent our documentation from getting out of date.
documentation. This helps prevent our documentation from getting out of date.
</div> </div>
## Repositories ## Repositories
@ -52,44 +43,38 @@ documentation. This helps prevent our documentation from getting out of date.
## Source control ## Source control
In order to balance editorial control with effective collaboration, we keep documentation alongside the module source In order to balance editorial control with effective collaboration, we keep documentation alongside the module source code, e.g. in `framework/docs/`, or as code comments within PHP code. Contributing documentation is the same process as providing any other patch (see [Contributing code](code)).
code, e.g. in `framework/docs/`, or as code comments within PHP code. Contributing documentation is the same process as
providing any other patch (see [Contributing code](code)).
## What to write ## What to write
See [what to write (jacobian.org)](http://jacobian.org/writing/great-documentation/what-to-write/) for an excellent See [what to write (jacobian.org)](http://jacobian.org/writing/great-documentation/what-to-write/) for an excellent introduction to the different types of documentation. Also see [producing OSS: "documentation"](http://producingoss.com/en/getting-started.html#documentation) for good rules of thumb
introduction to the different types of documentation, and
[producing OSS: "documentation"](http://producingoss.com/en/getting-started.html#documentation) for good rules of thumb
for documenting open source software. for documenting open source software.
## Structure ## Structure
* Keep documentation lines to 120 characters. * Keep documentation lines to 120 characters.
* Don't duplicate: Search for existing places to put your documentation. Do you really require a new page, or just a new paragraph * Don't duplicate: Search for existing places to put your documentation. Do you really require a new page, or just a new paragraph of text somewhere?
of text somewhere?
* 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 complement each other: Both forms of documenting source code (API and Developer Guides) are valuable resources. * API and developer guides are two forms of source code documentation that complement each other.
* Provide context: Give API documentation 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 findable: Documentation lives by interlinking content, so please make sure your contribution doesn't become an * 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
inaccessible island. Your page should at least be linked on the index page in the same folder. It 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
* Write in second plural form: Use "we" instead of "I". It gives the text an instructive and collaborative style. * Write in second person plural form: Use "we" instead of "I". It gives the text an instructive and collaborative style.
* It's okay to address the reader: For example "First you'll install a webserver" is good style. * It's okay to address the reader: For example "First you'll install a webserver" is good style.
* Write in an active and direct voice. * Write in an active and direct voice.
* Mark up correctly: Use preformatted text, emphasis and bold to make technical writing more "scannable". * Mark up correctly: Use preformatted text. Emphasis and bold make technical writing more easily "scannable".
* Avoid FAQs: FAQs are not a replacement of a coherent, well explained documentation. If you've done a good job * Avoid FAQs: FAQs are not a replacement for coherent, well explained documentation. If you've done a good job
documenting, there shouldn't be any "frequently asked questions" left. documenting, there shouldn't be any "frequently asked questions" left.
* "SilverStripe" should always appear without a space, use two capital S. * "SilverStripe" should always appear without a space with both "S"s capitalised.
* Use simple language and words. Avoid uncommon jargon and overly long words. * Use simple language and words. Avoid uncommon jargon and overly long words.
* Use UK English and not US English. SilverStripe is proudly a New Zealand open source project we use the UK spelling and forms of English. The most common of these differences are -ize vs -ise, or -or vs our (eg color vs colour). * Use UK English and not US English. SilverStripe is proudly a New Zealand open source project we use the UK spelling and forms of English. The most common of these differences are -ize vs -ise, or -or vs our (eg color vs colour).
* We use sentence case for titles so only capitalise the first letter of the first word of a title. Only exceptions to this are when using branded (e.g. SilverStripe), acronyms (e.g. PHP) and class names (e.g. ModelAdmin). * We use sentence case for titles so only capitalise the first letter of the first word of a title. The only exceptions to this are when using brand names (e.g. SilverStripe), acronyms (e.g. PHP) and class names (e.g. ModelAdmin).
* Use gender neutral language throughout the document, unless referencing a specific person. Use them, they, their, instead of he and she, his or her. * Use gender neutral language throughout the document, unless referencing a specific person. Use them, they, their, instead of he and she, his or her.
* URLs: is the end of your sentence is a URL, you don't need to use a full stop. * URLs: if the end of your sentence is a URL then you don't need to use a full stop.
* Bullet points: Sentence case your bullet points, if it is a full sentence then end with a full stop. If it is a short point or list full stops are not required. * Bullet points: Sentence case your bullet points. If a bullet point is a full sentence then end with a full stop. If it is a short point or a list, full stops are not required.
## Highlighted blocks ## Highlighted blocks
@ -97,8 +82,7 @@ There are several built-in block styles for highlighting a paragraph of text. Pl
sparingly. sparingly.
<div class="hint" markdown='1'> <div class="hint" markdown='1'>
"Tip box": Adds, deepens or accents information in the main text. Can be used for background knowledge, or "see also" "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).
links.
</div> </div>
Code: Code:
@ -108,8 +92,7 @@ Code:
</div> </div>
<div class="notice" markdown='1'> <div class="notice" markdown='1'>
"Notification box": Technical notifications relating to the main text. For example, notifying users about a deprecated "Notification box": A notification box is good for technical notifications relating to the main text. For example, notifying users about a deprecated feature.
feature.
</div> </div>
Code: Code:
@ -119,8 +102,7 @@ Code:
</div> </div>
<div class="warning" markdown='1'> <div class="warning" markdown='1'>
"Warning box": Highlight a severe bug or technical issue requiring a users attention. For example, a code block with "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.
destructive functionality might not have its URL actions secured to keep the code shorter.
</div> </div>
Code: Code:
@ -129,14 +111,12 @@ Code:
... ...
</div> </div>
See [markdown extra documentation](http://michelf.com/projects/php-markdown/extra/#html) for more restriction See [markdown extra documentation](http://michelf.com/projects/php-markdown/extra/#html) for more restrictions
on placing HTML blocks inside Markdown. on placing HTML blocks inside Markdown.
## Translating documentation ## Translating documentation
Documentation is kept alongside the source code, typically in a module subdirectory like `framework/docs/en/`. Each Documentation is kept alongside the source code, typically in a module subdirectory like `framework/docs/en/`. Each language has its own subfolder, which can duplicate parts of or the entire body of documentation. German documentation would, for example, live in `framework/docs/de/`. The
language has its own subfolder, which can duplicate parts or the whole body of documentation. German documentation
would for example live in `framework/docs/de/`. The
[docsviewer](https://github.com/silverstripe/silverstripe-docsviewer) module that drives [docsviewer](https://github.com/silverstripe/silverstripe-docsviewer) module that drives
[doc.silverstripe.org](http://doc.silverstripe.org) automatically resolves these subfolders into a language dropdown. [doc.silverstripe.org](http://doc.silverstripe.org) automatically resolves these subfolders into a language dropdown.

View File

@ -285,9 +285,11 @@ class Upload extends Controller {
/** /**
* Clear out all errors (mostly set by {loadUploaded()}) * Clear out all errors (mostly set by {loadUploaded()})
* including the validator's errors
*/ */
public function clearErrors() { public function clearErrors() {
$this->errors = array(); $this->errors = array();
$this->validator->clearErrors();
} }
/** /**
@ -367,6 +369,13 @@ class Upload_Validator {
return $this->errors; return $this->errors;
} }
/**
* Clear out all errors
*/
public function clearErrors() {
$this->errors = array();
}
/** /**
* Set information about temporary file produced by PHP. * Set information about temporary file produced by PHP.
* @param array $tmpFile * @param array $tmpFile

View File

@ -1,54 +1,59 @@
<?php <?php
/** /**
* Text input field with validation for correct email format * Text input field with validation for correct email format according to RFC 2822.
* according to RFC 2822.
* *
* @package forms * @package forms
* @subpackage fields-formattedinput * @subpackage fields-formattedinput
*/ */
class EmailField extends TextField { class EmailField extends TextField {
/**
* {@inheritdoc}
*/
public function Type() { public function Type() {
return 'email text'; return 'email text';
} }
/**
* {@inheritdoc}
*/
public function getAttributes() { public function getAttributes() {
return array_merge( return array_merge(
parent::getAttributes(), parent::getAttributes(),
array( array(
'type' => 'email' 'type' => 'email',
) )
); );
} }
/** /**
* Validates for RFC 2822 compliant email adresses. * Validates for RFC 2822 compliant email addresses.
* *
* @see http://www.regular-expressions.info/email.html * @see http://www.regular-expressions.info/email.html
* @see http://www.ietf.org/rfc/rfc2822.txt * @see http://www.ietf.org/rfc/rfc2822.txt
* *
* @param Validator $validator * @param Validator $validator
* @return String *
* @return string
*/ */
public function validate($validator) { public function validate($validator) {
$this->value = trim($this->value); $this->value = trim($this->value);
$pcrePattern = '^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*' $pattern = '^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$';
. '@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$';
// PHP uses forward slash (/) to delimit start/end of pattern, so it must be escaped // Escape delimiter characters.
$pregSafePattern = str_replace('/', '\\/', $pcrePattern); $safePattern = str_replace('/', '\\/', $pattern);
if($this->value && !preg_match('/' . $pregSafePattern . '/i', $this->value)){ if($this->value && !preg_match('/' . $safePattern . '/i', $this->value)) {
$validator->validationError( $validator->validationError(
$this->name, $this->name,
_t('EmailField.VALIDATION', "Please enter an email address"), _t('EmailField.VALIDATION', 'Please enter an email address'),
"validation" 'validation'
); );
return false;
} else{
return true;
}
}
return false;
}
return true;
}
} }

View File

@ -35,7 +35,7 @@
* "admin/EditForm". This URL will render the form without its surrounding * "admin/EditForm". This URL will render the form without its surrounding
* template when called through GET instead of POST. * template when called through GET instead of POST.
* *
* By appending to this URL, you can render invidual form elements * By appending to this URL, you can render individual form elements
* through the {@link FormField->FieldHolder()} method. * through the {@link FormField->FieldHolder()} method.
* For example, the "URLSegment" field in a standard CMS form would be * For example, the "URLSegment" field in a standard CMS form would be
* accessible through "admin/EditForm/field/URLSegment/FieldHolder". * accessible through "admin/EditForm/field/URLSegment/FieldHolder".
@ -54,19 +54,34 @@ class Form extends RequestHandler {
*/ */
public $IncludeFormTag = true; public $IncludeFormTag = true;
/**
* @var FieldList|null
*/
protected $fields; protected $fields;
/**
* @var FieldList|null
*/
protected $actions; protected $actions;
/** /**
* @var Controller * @var Controller|null
*/ */
protected $controller; protected $controller;
/**
* @var string|null
*/
protected $name; protected $name;
/**
* @var Validator|null
*/
protected $validator; protected $validator;
/**
* @var string
*/
protected $formMethod = "POST"; protected $formMethod = "POST";
/** /**
@ -74,16 +89,21 @@ class Form extends RequestHandler {
*/ */
protected $strictFormMethodCheck = false; protected $strictFormMethodCheck = false;
/**
* @var string|null
*/
protected static $current_action; protected static $current_action;
/** /**
* @var Dataobject $record Populated by {@link loadDataFrom()}. * @var DataObject|null $record Populated by {@link loadDataFrom()}.
*/ */
protected $record; protected $record;
/** /**
* Keeps track of whether this form has a default action or not. * Keeps track of whether this form has a default action or not.
* Set to false by $this->disableDefaultAction(); * Set to false by $this->disableDefaultAction();
*
* @var boolean
*/ */
protected $hasDefaultAction = true; protected $hasDefaultAction = true;
@ -92,7 +112,7 @@ class Form extends RequestHandler {
* Useful to open a new window upon * Useful to open a new window upon
* form submission. * form submission.
* *
* @var string * @var string|null
*/ */
protected $target; protected $target;
@ -101,7 +121,7 @@ class Form extends RequestHandler {
* <legend> element before the <fieldset> * <legend> element before the <fieldset>
* in Form.ss template. * in Form.ss template.
* *
* @var string * @var string|null
*/ */
protected $legend; protected $legend;
@ -111,14 +131,23 @@ class Form extends RequestHandler {
* another template for customisation. * another template for customisation.
* *
* @see Form->setTemplate() * @see Form->setTemplate()
* @var string * @var string|null
*/ */
protected $template; protected $template;
/**
* @var callable|null
*/
protected $buttonClickedFunc; protected $buttonClickedFunc;
/**
* @var string|null
*/
protected $message; protected $message;
/**
* @var string|null
*/
protected $messageType; protected $messageType;
/** /**
@ -129,10 +158,13 @@ class Form extends RequestHandler {
*/ */
protected $redirectToFormOnValidationError = false; protected $redirectToFormOnValidationError = false;
/**
* @var bool
*/
protected $security = true; protected $security = true;
/** /**
* @var SecurityToken * @var SecurityToken|null
*/ */
protected $securityToken = null; protected $securityToken = null;
@ -148,7 +180,7 @@ class Form extends RequestHandler {
private static $default_classes = array(); private static $default_classes = array();
/** /**
* @var string * @var string|null
*/ */
protected $encType; protected $encType;
@ -158,6 +190,9 @@ class Form extends RequestHandler {
*/ */
protected $attributes = array(); protected $attributes = array();
/**
* @var array
*/
private static $allowed_actions = array( private static $allowed_actions = array(
'handleField', 'handleField',
'httpSubmission', 'httpSubmission',
@ -179,11 +214,16 @@ class Form extends RequestHandler {
*/ */
private $formActionPath = false; private $formActionPath = false;
/**
* @var bool
*/
protected $securityTokenAdded = false;
/** /**
* Create a new form, with the given fields an action buttons. * Create a new form, with the given fields an action buttons.
* *
* @param Controller $controller The parent controller, necessary to create the appropriate form action tag. * @param Controller $controller The parent controller, necessary to create the appropriate form action tag.
* @param String $name The method on the controller that will return this form object. * @param string $name The method on the controller that will return this form object.
* @param FieldList $fields All of the fields in the form - a {@link FieldList} of {@link FormField} objects. * @param FieldList $fields All of the fields in the form - a {@link FieldList} of {@link FormField} objects.
* @param FieldList $actions All of the action buttons in the form - a {@link FieldLis} of * @param FieldList $actions All of the action buttons in the form - a {@link FieldLis} of
* {@link FormAction} objects * {@link FormAction} objects
@ -234,6 +274,9 @@ class Form extends RequestHandler {
$this->setupDefaultClasses(); $this->setupDefaultClasses();
} }
/**
* @var array
*/
private static $url_handlers = array( private static $url_handlers = array(
'field/$FieldName!' => 'handleField', 'field/$FieldName!' => 'handleField',
'POST ' => 'httpSubmission', 'POST ' => 'httpSubmission',
@ -244,6 +287,8 @@ class Form extends RequestHandler {
/** /**
* Set up current form errors in session to * Set up current form errors in session to
* the current form if appropriate. * the current form if appropriate.
*
* @return $this
*/ */
public function setupFormErrors() { public function setupFormErrors() {
$errorInfo = Session::get("FormInfo.{$this->FormName()}"); $errorInfo = Session::get("FormInfo.{$this->FormName()}");
@ -289,6 +334,9 @@ class Form extends RequestHandler {
* Populates the form with {@link loadDataFrom()}, calls {@link validate()}, * Populates the form with {@link loadDataFrom()}, calls {@link validate()},
* and only triggers the requested form action/method * and only triggers the requested form action/method
* if the form is valid. * if the form is valid.
*
* @param SS_HTTPRequest $request
* @throws SS_HTTPResponse_Exception
*/ */
public function httpSubmission($request) { public function httpSubmission($request) {
// Strict method check // Strict method check
@ -351,7 +399,7 @@ class Form extends RequestHandler {
} }
} }
// If the action wasnt' set, choose the default on the form. // If the action wasn't set, choose the default on the form.
if(!isset($funcName) && $defaultAction = $this->defaultAction()){ if(!isset($funcName) && $defaultAction = $this->defaultAction()){
$funcName = $defaultAction->actionName(); $funcName = $defaultAction->actionName();
} }
@ -422,6 +470,10 @@ class Form extends RequestHandler {
return $this->httpError(404); return $this->httpError(404);
} }
/**
* @param string $action
* @return bool
*/
public function checkAccessAction($action) { public function checkAccessAction($action) {
return ( return (
parent::checkAccessAction($action) parent::checkAccessAction($action)
@ -439,7 +491,7 @@ class Form extends RequestHandler {
* Returns the appropriate response up the controller chain * Returns the appropriate response up the controller chain
* if {@link validate()} fails (which is checked prior to executing any form actions). * if {@link validate()} fails (which is checked prior to executing any form actions).
* By default, returns different views for ajax/non-ajax request, and * By default, returns different views for ajax/non-ajax request, and
* handles 'appliction/json' requests with a JSON object containing the error messages. * handles 'application/json' requests with a JSON object containing the error messages.
* Behaviour can be influenced by setting {@link $redirectToFormOnValidationError}. * Behaviour can be influenced by setting {@link $redirectToFormOnValidationError}.
* *
* @return SS_HTTPResponse|string * @return SS_HTTPResponse|string
@ -480,6 +532,8 @@ class Form extends RequestHandler {
/** /**
* Fields can have action to, let's check if anyone of the responds to $funcname them * Fields can have action to, let's check if anyone of the responds to $funcname them
* *
* @param SS_List|array $fields
* @param callable $funcName
* @return FormField * @return FormField
*/ */
protected function checkFieldsForAction($fields, $funcName) { protected function checkFieldsForAction($fields, $funcName) {
@ -528,7 +582,8 @@ class Form extends RequestHandler {
* form on the page upon validation errors in the form or if * form on the page upon validation errors in the form or if
* they just need to redirect back to the page * they just need to redirect back to the page
* *
* @param bool Redirect to the form * @param bool $bool Redirect to form on error?
* @return $this
*/ */
public function setRedirectToFormOnValidationError($bool) { public function setRedirectToFormOnValidationError($bool) {
$this->redirectToFormOnValidationError = $bool; $this->redirectToFormOnValidationError = $bool;
@ -548,6 +603,10 @@ class Form extends RequestHandler {
/** /**
* Add a plain text error message to a field on this form. It will be saved into the session * Add a plain text error message to a field on this form. It will be saved into the session
* and used the next time this form is displayed. * and used the next time this form is displayed.
* @param string $fieldName
* @param string $message
* @param string $messageType
* @param bool $escapeHtml
*/ */
public function addErrorMessage($fieldName, $message, $messageType, $escapeHtml = true) { public function addErrorMessage($fieldName, $message, $messageType, $escapeHtml = true) {
Session::add_to_array("FormInfo.{$this->FormName()}.errors", array( Session::add_to_array("FormInfo.{$this->FormName()}.errors", array(
@ -557,6 +616,9 @@ class Form extends RequestHandler {
)); ));
} }
/**
* @param FormTransformation $trans
*/
public function transform(FormTransformation $trans) { public function transform(FormTransformation $trans) {
$newFields = new FieldList(); $newFields = new FieldList();
foreach($this->fields as $field) { foreach($this->fields as $field) {
@ -586,8 +648,10 @@ class Form extends RequestHandler {
/** /**
* Set the {@link Validator} on this form. * Set the {@link Validator} on this form.
* @param Validator $validator
* @return $this
*/ */
public function setValidator( Validator $validator ) { public function setValidator(Validator $validator ) {
if($validator) { if($validator) {
$this->validator = $validator; $this->validator = $validator;
$this->validator->setForm($this); $this->validator->setForm($this);
@ -605,6 +669,7 @@ class Form extends RequestHandler {
/** /**
* Convert this form to another format. * Convert this form to another format.
* @param FormTransformation $format
*/ */
public function transformTo(FormTransformation $format) { public function transformTo(FormTransformation $format) {
$newFields = new FieldList(); $newFields = new FieldList();
@ -678,6 +743,7 @@ class Form extends RequestHandler {
* Setter for the form fields. * Setter for the form fields.
* *
* @param FieldList $fields * @param FieldList $fields
* @return $this
*/ */
public function setFields($fields) { public function setFields($fields) {
$this->fields = $fields; $this->fields = $fields;
@ -697,6 +763,7 @@ class Form extends RequestHandler {
* Setter for the form actions. * Setter for the form actions.
* *
* @param FieldList $actions * @param FieldList $actions
* @return $this
*/ */
public function setActions($actions) { public function setActions($actions) {
$this->actions = $actions; $this->actions = $actions;
@ -712,8 +779,9 @@ class Form extends RequestHandler {
} }
/** /**
* @param String * @param string $name
* @param String * @param string $value
* @return $this
*/ */
public function setAttribute($name, $value) { public function setAttribute($name, $value) {
$this->attributes[$name] = $value; $this->attributes[$name] = $value;
@ -721,12 +789,15 @@ class Form extends RequestHandler {
} }
/** /**
* @return String * @return string $name
*/ */
public function getAttribute($name) { public function getAttribute($name) {
if(isset($this->attributes[$name])) return $this->attributes[$name]; if(isset($this->attributes[$name])) return $this->attributes[$name];
} }
/**
* @return array
*/
public function getAttributes() { public function getAttributes() {
$attrs = array( $attrs = array(
'id' => $this->FormName(), 'id' => $this->FormName(),
@ -750,9 +821,10 @@ class Form extends RequestHandler {
/** /**
* Return the attributes of the form tag - used by the templates. * Return the attributes of the form tag - used by the templates.
* *
* @param Array Custom attributes to process. Falls back to {@link getAttributes()}. * @param array Custom attributes to process. Falls back to {@link getAttributes()}.
* If at least one argument is passed as a string, all arguments act as excludes by name. * If at least one argument is passed as a string, all arguments act as excludes by name.
* @return String HTML attributes, ready for insertion into an HTML tag *
* @return string HTML attributes, ready for insertion into an HTML tag
*/ */
public function getAttributesHTML($attrs = null) { public function getAttributesHTML($attrs = null) {
$exclude = (is_string($attrs)) ? func_get_args() : null; $exclude = (is_string($attrs)) ? func_get_args() : null;
@ -800,10 +872,11 @@ class Form extends RequestHandler {
} }
/** /**
* Set the {@link FormTemplateHelper} * Set the target of this form to any value - useful for opening the form contents in a new window or refreshing
* * another frame
*
* @param string|FormTemplateHelper * @param string|FormTemplateHelper
*/ */
public function setTemplateHelper($helper) { public function setTemplateHelper($helper) {
$this->templateHelper = $helper; $this->templateHelper = $helper;
} }
@ -831,8 +904,7 @@ class Form extends RequestHandler {
* contents in a new window or refreshing another frame. * contents in a new window or refreshing another frame.
* *
* @param target $target The value of the target * @param target $target The value of the target
* * @return $this
* @return FormField
*/ */
public function setTarget($target) { public function setTarget($target) {
$this->target = $target; $this->target = $target;
@ -843,6 +915,8 @@ class Form extends RequestHandler {
/** /**
* Set the legend value to be inserted into * Set the legend value to be inserted into
* the <legend> element in the Form.ss template. * the <legend> element in the Form.ss template.
* @param string $legend
* @return $this
*/ */
public function setLegend($legend) { public function setLegend($legend) {
$this->legend = $legend; $this->legend = $legend;
@ -854,6 +928,7 @@ class Form extends RequestHandler {
* to render with. The default is "Form". * to render with. The default is "Form".
* *
* @param string $template The name of the template (without the .ss extension) * @param string $template The name of the template (without the .ss extension)
* @return $this
*/ */
public function setTemplate($template) { public function setTemplate($template) {
$this->template = $template; $this->template = $template;
@ -897,7 +972,8 @@ class Form extends RequestHandler {
* Sets the form encoding type. The most common encoding types are defined * Sets the form encoding type. The most common encoding types are defined
* in {@link ENC_TYPE_URLENCODED} and {@link ENC_TYPE_MULTIPART}. * in {@link ENC_TYPE_URLENCODED} and {@link ENC_TYPE_MULTIPART}.
* *
* @param string $enctype * @param string $encType
* @return $this
*/ */
public function setEncType($encType) { public function setEncType($encType) {
$this->encType = $encType; $this->encType = $encType;
@ -937,8 +1013,9 @@ class Form extends RequestHandler {
/** /**
* Set the form method: GET, POST, PUT, DELETE. * Set the form method: GET, POST, PUT, DELETE.
* *
* @param $method string * @param string $method
* @param $strict If non-null, pass value to {@link setStrictFormMethodCheck()}. * @param bool $strict If non-null, pass value to {@link setStrictFormMethodCheck()}.
* @return $this
*/ */
public function setFormMethod($method, $strict = null) { public function setFormMethod($method, $strict = null) {
$this->formMethod = strtoupper($method); $this->formMethod = strtoupper($method);
@ -957,6 +1034,7 @@ class Form extends RequestHandler {
* form. * form.
* *
* @param $bool boolean * @param $bool boolean
* @return $this
*/ */
public function setStrictFormMethodCheck($bool) { public function setStrictFormMethodCheck($bool) {
$this->strictFormMethodCheck = (bool)$bool; $this->strictFormMethodCheck = (bool)$bool;
@ -993,9 +1071,8 @@ class Form extends RequestHandler {
* recommended only for situations where you have two relatively distinct * recommended only for situations where you have two relatively distinct
* parts of the system trying to communicate via a form post. * parts of the system trying to communicate via a form post.
* *
* @param string * @param string $path
* * @return $this
* @return Form
*/ */
public function setFormAction($path) { public function setFormAction($path) {
$this->formActionPath = $path; $this->formActionPath = $path;
@ -1016,8 +1093,7 @@ class Form extends RequestHandler {
* Set the HTML ID attribute of the form. * Set the HTML ID attribute of the form.
* *
* @param string $id * @param string $id
* * @return $this
* @return FormField
*/ */
public function setHTMLID($id) { public function setHTMLID($id) {
$this->htmlID = $id; $this->htmlID = $id;
@ -1099,8 +1175,9 @@ class Form extends RequestHandler {
} }
/** /**
* The next functions store and modify the forms message attributes. * The next functions store and modify the forms
* messages are stored in session under $_SESSION[formname][message]; * message attributes. messages are stored in session under
* $_SESSION[formname][message];
* *
* @return string * @return string
*/ */
@ -1135,12 +1212,13 @@ class Form extends RequestHandler {
/** /**
* Set a status message for the form. * Set a status message for the form.
* *
* @param string $message the text of the message * @param string $message the text of the message
* @param string $type Should be set to good, bad, or warning. * @param string $type Should be set to good, bad, or warning.
* @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML. * @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
* In that case, you might want to use {@link Convert::raw2xml()} to escape any * In that case, you might want to use {@link Convert::raw2xml()} to escape any
* user supplied data in the message. * user supplied data in the message.
* @return $this
*/ */
public function setMessage($message, $type, $escapeHtml = true) { public function setMessage($message, $type, $escapeHtml = true) {
$this->message = ($escapeHtml) ? Convert::raw2xml($message) : $message; $this->message = ($escapeHtml) ? Convert::raw2xml($message) : $message;
@ -1150,7 +1228,7 @@ class Form extends RequestHandler {
/** /**
* Set a message to the session, for display next time this form is shown. * Set a message to the session, for display next time this form is shown.
* *
* @param string $message the text of the message * @param string $message the text of the message
* @param string $type Should be set to good, bad, or warning. * @param string $type Should be set to good, bad, or warning.
* @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML. * @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
@ -1159,7 +1237,7 @@ class Form extends RequestHandler {
*/ */
public function sessionMessage($message, $type, $escapeHtml = true) { public function sessionMessage($message, $type, $escapeHtml = true) {
Session::set( Session::set(
"FormInfo.{$this->FormName()}.formError.message", "FormInfo.{$this->FormName()}.formError.message",
$escapeHtml ? Convert::raw2xml($message) : $message $escapeHtml ? Convert::raw2xml($message) : $message
); );
Session::set("FormInfo.{$this->FormName()}.formError.type", $type); Session::set("FormInfo.{$this->FormName()}.formError.type", $type);
@ -1167,7 +1245,7 @@ class Form extends RequestHandler {
public static function messageForForm($formName, $message, $type, $escapeHtml = true) { public static function messageForForm($formName, $message, $type, $escapeHtml = true) {
Session::set( Session::set(
"FormInfo.{$formName}.formError.message", "FormInfo.{$formName}.formError.message",
$escapeHtml ? Convert::raw2xml($message) : $message $escapeHtml ? Convert::raw2xml($message) : $message
); );
Session::set("FormInfo.{$formName}.formError.type", $type); Session::set("FormInfo.{$formName}.formError.type", $type);
@ -1252,8 +1330,8 @@ class Form extends RequestHandler {
* its value will not be saved to the field, retaining * its value will not be saved to the field, retaining
* potential existing values. * potential existing values.
* *
* Passed data should not be escaped, and is saved to the FormField * Passed data should not be escaped, and is saved to the FormField instances unescaped.
* instances unescaped. * Escaping happens automatically on saving the data through {@link saveInto()}.
* *
* Escaping happens automatically on saving the data through * Escaping happens automatically on saving the data through
* {@link saveInto()}. * {@link saveInto()}.
@ -1263,8 +1341,7 @@ class Form extends RequestHandler {
* *
* @param array|DataObject $data * @param array|DataObject $data
* @param int $mergeStrategy * @param int $mergeStrategy
* For every field, {@link $data} is interogated whether it contains a * For every field, {@link $data} is interrogated whether it contains a relevant property/key, and
* relevant property/key, and
* what that property/key's value is. * what that property/key's value is.
* *
* By default, if {@link $data} does contain a property/key, the fields value is always replaced by {@link $data}'s * By default, if {@link $data} does contain a property/key, the fields value is always replaced by {@link $data}'s
@ -1282,7 +1359,7 @@ class Form extends RequestHandler {
* For backwards compatibility reasons, this parameter can also be set to === true, which is the same as passing * For backwards compatibility reasons, this parameter can also be set to === true, which is the same as passing
* CLEAR_MISSING * CLEAR_MISSING
* *
* @param $fieldList An optional list of fields to process. This can be useful when you have a * @param FieldList $fieldList An optional list of fields to process. This can be useful when you have a
* form that has some fields that save to one object, and some that save to another. * form that has some fields that save to one object, and some that save to another.
* @return Form * @return Form
*/ */
@ -1308,7 +1385,7 @@ class Form extends RequestHandler {
if($dataFields) foreach($dataFields as $field) { if($dataFields) foreach($dataFields as $field) {
$name = $field->getName(); $name = $field->getName();
// Skip fields that have been exlcuded // Skip fields that have been excluded
if($fieldList && !in_array($name, $fieldList)) continue; if($fieldList && !in_array($name, $fieldList)) continue;
// First check looks for (fieldname)_unchanged, an indicator that we shouldn't overwrite the field value // First check looks for (fieldname)_unchanged, an indicator that we shouldn't overwrite the field value
@ -1368,8 +1445,8 @@ class Form extends RequestHandler {
* Save the contents of this form into the given data object. * Save the contents of this form into the given data object.
* It will make use of setCastedField() to do this. * It will make use of setCastedField() to do this.
* *
* @param $dataObject The object to save data into * @param DataObjectInterface $dataObject The object to save data into
* @param $fieldList An optional list of fields to process. This can be useful when you have a * @param FieldList $fieldList An optional list of fields to process. This can be useful when you have a
* form that has some fields that save to one object, and some that save to another. * form that has some fields that save to one object, and some that save to another.
*/ */
public function saveInto(DataObjectInterface $dataObject, $fieldList = null) { public function saveInto(DataObjectInterface $dataObject, $fieldList = null) {
@ -1395,11 +1472,11 @@ class Form extends RequestHandler {
/** /**
* Get the submitted data from this form through * Get the submitted data from this form through
* {@link FieldList->dataFields()}, which filters out any form-specific data * {@link FieldList->dataFields()}, which filters out
* like form-actions. * any form-specific data like form-actions.
* * Calls {@link FormField->dataValue()} on each field,
* Calls {@link FormField->dataValue()} on each field, which returns a value * which returns a value suitable for insertion into a DataObject
* suitable for insertion into a DataObject property. * property.
* *
* @return array * @return array
*/ */
@ -1421,11 +1498,8 @@ class Form extends RequestHandler {
/** /**
* Call the given method on the given field. * Call the given method on the given field.
* *
* This is used by Ajax-savvy form fields. By putting '&action=callfieldmethod' * @param array $data
* to the end of the form action, they can access server-side data. * @return mixed
*
* @param fieldName The name of the field. Can be overridden by $_REQUEST[fieldName]
* @param methodName The name of the field. Can be overridden by $_REQUEST[methodName]
*/ */
public function callfieldmethod($data) { public function callfieldmethod($data) {
$fieldName = $data['fieldName']; $fieldName = $data['fieldName'];
@ -1448,7 +1522,6 @@ class Form extends RequestHandler {
} else { } else {
user_error("Form::callfieldmethod() Field '$fieldName' not found", E_USER_ERROR); user_error("Form::callfieldmethod() Field '$fieldName' not found", E_USER_ERROR);
} }
} }
/** /**
@ -1517,14 +1590,10 @@ class Form extends RequestHandler {
} }
/** /**
* Render this form using the given template, and return the result as a * Render this form using the given template, and return the result as a string
* string. * You can pass either an SSViewer or a template name
* * @param string|array $template
* You can pass either an SSViewer or a template name. * @return HTMLText
*
* @param SSViewer|string $template
*
* @return HTML
*/ */
public function renderWithoutActionButton($template) { public function renderWithoutActionButton($template) {
$custom = $this->customise(array( $custom = $this->customise(array(
@ -1540,12 +1609,10 @@ class Form extends RequestHandler {
/** /**
* Sets the button that was clicked. This should only be called by the * Sets the button that was clicked. This should only be called by the Controller.
* {@link Controller}
* *
* @param string $funcName The name of the action method that will be called * @param callable $funcName The name of the action method that will be called.
* * @return $this
* @return Form
*/ */
public function setButtonClicked($funcName) { public function setButtonClicked($funcName) {
$this->buttonClickedFunc = $funcName; $this->buttonClickedFunc = $funcName;
@ -1680,9 +1747,8 @@ class Form extends RequestHandler {
* be added by delimiting a string with spaces. * be added by delimiting a string with spaces.
* *
* @param string $class A string containing a classname or several class * @param string $class A string containing a classname or several class
* names delimited by a single space. * names delimited by a single space.
* * @return $this
* @return Form
*/ */
public function addExtraClass($class) { public function addExtraClass($class) {
//split at white space //split at white space
@ -1699,6 +1765,7 @@ class Form extends RequestHandler {
* be passed through as a space delimited string * be passed through as a space delimited string
* *
* @param string $class * @param string $class
* @return $this
*/ */
public function removeExtraClass($class) { public function removeExtraClass($class) {
//split at white space //split at white space
@ -1730,8 +1797,11 @@ class Form extends RequestHandler {
/** /**
* Test a submission of this form. * Test a submission of this form.
* @param string $action
* @param array $data
* @return SS_HTTPResponse the response object that the handling controller produces. You can interrogate this in * @return SS_HTTPResponse the response object that the handling controller produces. You can interrogate this in
* your unit test. * your unit test.
* @throws SS_HTTPResponse_Exception
*/ */
public function testSubmission($action, $data) { public function testSubmission($action, $data) {
$data['action_' . $action] = true; $data['action_' . $action] = true;
@ -1741,6 +1811,9 @@ class Form extends RequestHandler {
/** /**
* Test an ajax submission of this form. * Test an ajax submission of this form.
*
* @param string $action
* @param array $data
* @return SS_HTTPResponse the response object that the handling controller produces. You can interrogate this in * @return SS_HTTPResponse the response object that the handling controller produces. You can interrogate this in
* your unit test. * your unit test.
*/ */
@ -1764,10 +1837,9 @@ class Form_FieldMap extends ViewableData {
} }
/** /**
* Ensure that all potential method calls get passed to __call(), therefore * Ensure that all potential method calls get passed to __call(), therefore to dataFieldByName
* to dataFieldByName. * @param string $method
* * @return bool
* @param string
*/ */
public function hasMethod($method) { public function hasMethod($method) {
return true; return true;

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Field that generates a heading tag. * Field that generates a heading tag.
* *
@ -10,50 +11,75 @@
class HeaderField extends DatalessField { class HeaderField extends DatalessField {
/** /**
* @var int $headingLevel The level of the <h1> to <h6> HTML tag. Default: 2 * The level of the <h1> to <h6> HTML tag.
*
* @var int
*/ */
protected $headingLevel = 2; protected $headingLevel = 2;
/**
* @param string $name
* @param null|string $title
* @param int $headingLevel
*/
public function __construct($name, $title = null, $headingLevel = 2) { public function __construct($name, $title = null, $headingLevel = 2) {
// legacy handling for old parameters: $title, $heading, ... // legacy handling:
// instead of new handling: $name, $title, $heading, ... // $title, $headingLevel...
$args = func_get_args(); $args = func_get_args();
if(!isset($args[1]) || is_numeric($args[1])) { if(!isset($args[1]) || is_numeric($args[1])) {
$title = (isset($args[0])) ? $args[0] : null; if(isset($args[0])) {
// Use "HeaderField(title)" as the default field name for a HeaderField; if it's just set to title then we $title = $args[0];
// risk causing accidental duplicate-field creation. }
// this means i18nized fields won't be easily accessible through fieldByName()
// Prefix name to avoid collisions.
$name = 'HeaderField' . $title; $name = 'HeaderField' . $title;
$headingLevel = (isset($args[1])) ? $args[1] : null;
$form = (isset($args[3])) ? $args[3] : null; if(isset($args[1])) {
$headingLevel = $args[1];
}
} }
if($headingLevel) $this->headingLevel = $headingLevel; $this->setHeadingLevel($headingLevel);
parent::__construct($name, $title); parent::__construct($name, $title);
} }
/**
* @return int
*/
public function getHeadingLevel() { public function getHeadingLevel() {
return $this->headingLevel; return $this->headingLevel;
} }
public function setHeadingLevel($level) { /**
$this->headingLevel = $level; * @param int $headingLevel
*
* @return $this
*/
public function setHeadingLevel($headingLevel) {
$this->headingLevel = $headingLevel;
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function getAttributes() { public function getAttributes() {
return array_merge( return array_merge(
parent::getAttributes(),
array( array(
'id' => $this->ID(), 'id' => $this->ID(),
'class' => $this->extraClass() 'class' => $this->extraClass(),
), )
$this->attributes
); );
} }
/**
* @return null
*/
public function Type() { public function Type() {
return null; return null;
} }
} }

View File

@ -1,9 +1,8 @@
<?php <?php
/** /**
* This field lets you put an arbitrary piece of HTML into your forms. * This field lets you put an arbitrary piece of HTML into your forms.
* *
* <b>Usage</b>
*
* <code> * <code>
* new LiteralField ( * new LiteralField (
* $name = "literalfield", * $name = "literalfield",
@ -15,40 +14,59 @@
* @subpackage fields-dataless * @subpackage fields-dataless
*/ */
class LiteralField extends DatalessField { class LiteralField extends DatalessField {
/** /**
* @var string $content * @var string|FormField
*/ */
protected $content; protected $content;
/**
* @param string $name
* @param string|FormField $content
*/
public function __construct($name, $content) { public function __construct($name, $content) {
$this->content = $content; $this->setContent($content);
parent::__construct($name); parent::__construct($name);
} }
/**
* @param array $properties
*
* @return string
*/
public function FieldHolder($properties = array()) { public function FieldHolder($properties = array()) {
if(is_object($this->content)) { if($this->content instanceof ViewableData) {
$obj = $this->content; $context = $this->content;
if($properties)
$obj = $obj->customise($properties); if($properties) {
return $obj->forTemplate(); $context = $context->customise($properties);
} else { }
return $this->content;
return $context->forTemplate();
} }
return $this->content;
} }
/**
* @param array $properties
*
* @return string
*/
public function Field($properties = array()) { public function Field($properties = array()) {
return $this->FieldHolder($properties); return $this->FieldHolder($properties);
} }
/** /**
* Sets the content of this field to a new value * Sets the content of this field to a new value.
* *
* @param string $content * @param string|FormField $content
*
* @return $this
*/ */
public function setContent($content) { public function setContent($content) {
$this->content = $content; $this->content = $content;
return $this; return $this;
} }
@ -61,16 +79,25 @@ class LiteralField extends DatalessField {
/** /**
* Synonym of {@link setContent()} so that LiteralField is more compatible with other field types. * Synonym of {@link setContent()} so that LiteralField is more compatible with other field types.
*
* @param string|FormField $content
*
* @return $this
*/ */
public function setValue($value) { public function setValue($content) {
$this->setContent($value); $this->setContent($content);
return $this; return $this;
} }
/**
* @return static
*/
public function performReadonlyTransformation() { public function performReadonlyTransformation() {
$clone = clone $this; $clone = clone $this;
$clone->setReadonly(true); $clone->setReadonly(true);
return $clone; return $clone;
} }
} }

View File

@ -1,11 +1,12 @@
<?php <?php
/** /**
* Password input field. * Password input field.
*
* @package forms * @package forms
* @subpackage fields-formattedinput * @subpackage fields-formattedinput
*/ */
class PasswordField extends TextField { class PasswordField extends TextField {
/** /**
* Controls the autocomplete attribute on the field. * Controls the autocomplete attribute on the field.
* *
@ -14,32 +15,63 @@ class PasswordField extends TextField {
*/ */
private static $autocomplete; private static $autocomplete;
public function getAttributes() { /**
$attributes = array_merge( * Returns an input field.
parent::getAttributes(), *
array('type' => 'password') * @param string $name
); * @param null|string $title
* @param string $value
$autocomplete = Config::inst()->get('PasswordField', 'autocomplete'); */
if (isset($autocomplete)) { public function __construct($name, $title = null, $value = '') {
$attributes['autocomplete'] = $autocomplete ? 'on' : 'off'; if(count(func_get_args()) > 3) {
Deprecation::notice(
'3.0', 'Use setMaxLength() instead of constructor arguments',
Deprecation::SCOPE_GLOBAL
);
} }
return $attributes; parent::__construct($name, $title, $value);
} }
/** /**
* Makes a pretty readonly field with some stars in it * {@inheritdoc}
*/
public function getAttributes() {
$attributes = array(
'type' => 'password',
);
$autocomplete = Config::inst()->get('PasswordField', 'autocomplete');
if($autocomplete) {
$attributes['autocomplete'] = 'on';
} else {
$attributes['autocomplete'] = 'off';
}
return array_merge(
parent::getAttributes(),
$attributes
);
}
/**
* Creates a read-only version of the field.
*
* @return FormField
*/ */
public function performReadonlyTransformation() { public function performReadonlyTransformation() {
$field = $this->castedCopy('ReadonlyField'); $field = $this->castedCopy('ReadonlyField');
$field->setValue('*****'); $field->setValue('*****');
return $field; return $field;
} }
/**
* {@inheritdoc}
*/
public function Type() { public function Type() {
return 'text password'; return 'text password';
} }
} }

View File

@ -1,12 +1,11 @@
<?php <?php
/** /**
* TextareaField creates a multi-line text field, * TextareaField creates a multi-line text field,
* allowing more data to be entered than a standard * allowing more data to be entered than a standard
* text field. It creates the <textarea> tag in the * text field. It creates the <textarea> tag in the
* form HTML. * form HTML.
* *
* <b>Usage</b>
*
* <code> * <code>
* new TextareaField( * new TextareaField(
* $name = "description", * $name = "description",
@ -19,17 +18,49 @@
* @subpackage fields-basic * @subpackage fields-basic
*/ */
class TextareaField extends FormField { class TextareaField extends FormField {
/** /**
* @var int Visible number of text lines. * Visible number of text lines.
*
* @var int
*/ */
protected $rows = 5; protected $rows = 5;
/** /**
* @var int Width of the text area (in average character widths) * Visible number of text columns.
*
* @var int
*/ */
protected $cols = 20; protected $cols = 20;
/**
* Set the number of rows in the textarea
*
* @param int $rows
*
* @return $this
*/
public function setRows($rows) {
$this->rows = $rows;
return $this;
}
/**
* Set the number of columns in the textarea
*
* @param int $cols
*
* @return $this
*/
public function setColumns($cols) {
$this->cols = $cols;
return $this;
}
/**
* {@inheritdoc}
*/
public function getAttributes() { public function getAttributes() {
return array_merge( return array_merge(
parent::getAttributes(), parent::getAttributes(),
@ -42,30 +73,23 @@ class TextareaField extends FormField {
); );
} }
/**
* {@inheritdoc}
*/
public function Type() { public function Type() {
return parent::Type() . ($this->readonly ? ' readonly' : ''); $parent = parent::Type();
if($this->readonly) {
return $parent . ' readonly';
}
return $parent;
} }
/** /**
* Set the number of rows in the textarea * @return string
*
* @param int
*/ */
public function setRows($rows) {
$this->rows = $rows;
return $this;
}
/**
* Set the number of columns in the textarea
*
* @return int
*/
public function setColumns($cols) {
$this->cols = $cols;
return $this;
}
public function Value() { public function Value() {
return htmlentities($this->value, ENT_COMPAT, 'UTF-8'); return htmlentities($this->value, ENT_COMPAT, 'UTF-8');
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Allows visibility of a group of fields to be toggled. * Allows visibility of a group of fields to be toggled.
* *
@ -6,17 +7,21 @@
* @subpackage fields-structural * @subpackage fields-structural
*/ */
class ToggleCompositeField extends CompositeField { class ToggleCompositeField extends CompositeField {
/** /**
* @var bool * @var bool
*/ */
protected $startClosed = true; protected $startClosed = true;
/** /**
* @var $int * @var int
*/ */
protected $headingLevel = 3; protected $headingLevel = 3;
/**
* @param string $name
* @param string $title
* @param array|FieldList $children
*/
public function __construct($name, $title, $children) { public function __construct($name, $title, $children) {
$this->name = $name; $this->name = $name;
$this->title = $title; $this->title = $title;
@ -24,30 +29,46 @@ class ToggleCompositeField extends CompositeField {
parent::__construct($children); parent::__construct($children);
} }
/**
* @param array $properties
*
* @return HTMLText
*/
public function FieldHolder($properties = array()) { public function FieldHolder($properties = array()) {
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js'); Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-ui/jquery-ui.js'); Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-ui/jquery-ui.js');
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js'); Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(FRAMEWORK_DIR . '/javascript/ToggleCompositeField.js'); Requirements::javascript(FRAMEWORK_DIR . '/javascript/ToggleCompositeField.js');
Requirements::css(FRAMEWORK_DIR . '/thirdparty/jquery-ui-themes/smoothness/jquery.ui.css'); Requirements::css(FRAMEWORK_DIR . '/thirdparty/jquery-ui-themes/smoothness/jquery.ui.css');
$obj = $properties ? $this->customise($properties) : $this; $context = $this;
return $obj->renderWith($this->getTemplates());
if(count($properties)) {
$context = $this->customise($properties);
}
return $context->renderWith($this->getTemplates());
} }
/**
* {@inheritdoc}
*/
public function getAttributes() { public function getAttributes() {
$attributes = array(
'id' => $this->id(),
'class' => $this->extraClass(),
);
if($this->getStartClosed()) { if($this->getStartClosed()) {
$class = 'ss-toggle ss-toggle-start-closed'; $attributes['class'] .= ' ss-toggle ss-toggle-start-closed';
} else { } else {
$class = 'ss-toggle'; $attributes['class'] .= ' ss-toggle';
} }
return array_merge( return array_merge(
$this->attributes, $this->attributes,
array( $attributes
'id' => $this->id(),
'class' => $class . ' ' . $this->extraClass()
)
); );
} }
@ -59,13 +80,15 @@ class ToggleCompositeField extends CompositeField {
} }
/** /**
* Controls whether the field is open or closed by default. By default the * Controls whether the field is open or closed by default. By default the field is closed.
* field is closed.
* *
* @param bool $bool * @param bool $startClosed
*
* @return $this
*/ */
public function setStartClosed($bool) { public function setStartClosed($startClosed) {
$this->startClosed = (bool) $bool; $this->startClosed = (bool) $startClosed;
return $this; return $this;
} }
@ -77,12 +100,13 @@ class ToggleCompositeField extends CompositeField {
} }
/** /**
* @param int $level * @param int $headingLevel
*
* @return $this
*/ */
public function setHeadingLevel($level) { public function setHeadingLevel($headingLevel) {
$this->headingLevel = $level; $this->headingLevel = $headingLevel;
return $this; return $this;
} }
} }

View File

@ -217,15 +217,20 @@ class TreeDropdownField extends FormField {
Requirements::css(FRAMEWORK_DIR . '/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css'); Requirements::css(FRAMEWORK_DIR . '/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css');
Requirements::css(FRAMEWORK_DIR . '/css/TreeDropdownField.css'); Requirements::css(FRAMEWORK_DIR . '/css/TreeDropdownField.css');
if($this->showSearch) {
$emptyTitle = _t('DropdownField.CHOOSESEARCH', '(Choose or Search)', 'start value of a dropdown');
} else {
$emptyTitle = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
}
$record = $this->Value() ? $this->objectForKey($this->Value()) : null; $record = $this->Value() ? $this->objectForKey($this->Value()) : null;
if($record instanceof ViewableData) { if($record instanceof ViewableData) {
$title = $record->obj($this->labelField)->forTemplate(); $title = $record->obj($this->labelField)->forTemplate();
} elseif($record) { } elseif($record) {
$title = Convert::raw2xml($record->{$this->labelField}); $title = Convert::raw2xml($record->{$this->labelField});
} else if($this->showSearch) { }
$title = _t('DropdownField.CHOOSESEARCH', '(Choose or Search)', 'start value of a dropdown'); else {
} else { $title = $emptyTitle;
$title = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
} }
// TODO Implement for TreeMultiSelectField // TODO Implement for TreeMultiSelectField
@ -238,6 +243,7 @@ class TreeDropdownField extends FormField {
$properties, $properties,
array( array(
'Title' => $title, 'Title' => $title,
'EmptyTitle' => $emptyTitle,
'Metadata' => ($metadata) ? Convert::raw2json($metadata) : null, 'Metadata' => ($metadata) ? Convert::raw2json($metadata) : null,
) )
); );

View File

@ -1182,20 +1182,25 @@ class UploadField extends FileField {
$name = $this->getName(); $name = $this->getName();
$postVars = $request->postVar($name); $postVars = $request->postVar($name);
// Save the temporary file into a File object // Extract uploaded files from Form data
$uploadedFiles = $this->extractUploadedFileData($postVars); $uploadedFiles = $this->extractUploadedFileData($postVars);
$firstFile = reset($uploadedFiles); $return = array();
$file = $this->saveTemporaryFile($firstFile, $error);
if(empty($file)) { // Save the temporary files into a File objects
$return = array('error' => $error); // and save data/error on a per file basis
} else { foreach ($uploadedFiles as $tempFile) {
$return = $this->encodeFileAttributes($file); $file = $this->saveTemporaryFile($tempFile, $error);
if(empty($file)) {
array_push($return, array('error' => $error));
} else {
array_push($return, $this->encodeFileAttributes($file));
}
$this->upload->clearErrors();
} }
// Format response with json // Format response with json
$response = new SS_HTTPResponse(Convert::raw2json(array($return))); $response = new SS_HTTPResponse(Convert::raw2json($return));
$response->addHeader('Content-Type', 'text/plain'); $response->addHeader('Content-Type', 'text/plain');
if (!empty($return['error'])) $response->setStatusCode(403);
return $response; return $response;
} }

View File

@ -154,17 +154,21 @@
var updateFn = function() { var updateFn = function() {
var val = self.getValue(); var val = self.getValue();
if(val) { if(val) {
var node = tree.find('*[data-id="' + val + '"]'), var node = tree.find('*[data-id="' + val + '"]'),
title = node.children('a').find("span.jstree_pageicon")?node.children('a').find("span.item").html():null; title = node.children('a').find("span.jstree_pageicon")?node.children('a').find("span.item").html():null;
if(!title) title=(node.length > 0) ? tree.jstree('get_text', node[0]) : null; if(!title) title=(node.length > 0) ? tree.jstree('get_text', node[0]) : null;
if(title) { if(title) {
self.setTitle(title); self.setTitle(title);
self.data('title', title); self.data('title', title);
} }
if(node) tree.jstree('select_node', node); if(node) tree.jstree('select_node', node);
} }
else {
self.setTitle(self.data('empty-title'));
self.removeData('title');
}
}; };
// Load the tree if its not already present // Load the tree if its not already present

View File

@ -258,8 +258,7 @@ fi:
many_many_Members: Jäsenet many_many_Members: Jäsenet
GroupImportForm: GroupImportForm:
Help1: '<p>Tuo yksi tai useampi ryhmä <em>CSV</em>-muotoisena (arvot pilkulla erotettuina). <small><a href="#" class="toggle-advanced">Näytä edistyksellinen käyttö</a></small></p>' Help1: '<p>Tuo yksi tai useampi ryhmä <em>CSV</em>-muotoisena (arvot pilkulla erotettuina). <small><a href="#" class="toggle-advanced">Näytä edistyksellinen käyttö</a></small></p>'
Help2: "<div class=\"advanced\">\n\t<h4>Edistynyt käyttö</h4>\n\t<ul>\n\t<li>Sallitut palstat: <em>%s</em></li>\n\t<li>Olemassa olevat ryhmät kohdistetaan niiden uniikin <em>Code</em> arvolla, ja päivitetään uudet arvot tuodusta tiedostosta</li>\n\t<li>Oikeustasot voidaan luoda käyttämällä <em>ParentCode</em> palstaa.</li>\n\t<li>Oikeustasokoodit voidaan kohdistaa <em>PermissionCode</em> palstassa. Olemassaolevia oikeusia ei tyhjennetä.</li>\n\t</ul>\n\ Help2: "<div class=\"advanced\">\n<h4>Edistynyt käyttö</h4>\n<ul>\n<li>Sallitut sarakkeet: <em>%s</em></li>\n<li>Olemassa olevat rhymes kohdistetaan niiden uniikin <em>Code</em> arvolla, ja päivitetään arvot tuodusta tiedostosta</li>\n<li>Ryhmien hierarkiat voidaan luoda <em>ParentCode</em> sarakkeessa.</li>\n<li>Oikeustasokoodit voidaan kohdistaa <em>PermissionCode</em> sarakkeessa. Olemassa olevia oikeuksia ei tyhjennetä.</li>\n</ul>\n</div>"
</div>"
ResultCreated: 'Luotiin {count} ryhmä(ä)' ResultCreated: 'Luotiin {count} ryhmä(ä)'
ResultDeleted: 'Poistettu %d ryhmää' ResultDeleted: 'Poistettu %d ryhmää'
ResultUpdated: 'Päivitetty %d ryhmää' ResultUpdated: 'Päivitetty %d ryhmää'

View File

@ -191,9 +191,7 @@ lt:
TEXT2: 'slaptažodžio atstatymo nuoroda' TEXT2: 'slaptažodžio atstatymo nuoroda'
TEXT3: svetainei TEXT3: svetainei
Form: Form:
CSRF_FAILED_MESSAGE: 'Iškilo techninė problema. Prašome paspausti mygtuką Atgal, CSRF_FAILED_MESSAGE: 'Iškilo techninė problema. Prašome paspausti mygtuką Atgal, perkraukite naršyklės langą ir bandykite vėl.'
perkraukite naršyklės langą ir bandykite vėl.'
FIELDISREQUIRED: '{name} yra privalomas' FIELDISREQUIRED: '{name} yra privalomas'
SubmitBtnLabel: Vykdyti SubmitBtnLabel: Vykdyti
VALIDATIONCREDITNUMBER: 'Prašome įsitikinti, ar teisingai suvedėte kreditinės kortelės numerį {number}' VALIDATIONCREDITNUMBER: 'Prašome įsitikinti, ar teisingai suvedėte kreditinės kortelės numerį {number}'
@ -260,8 +258,7 @@ lt:
many_many_Members: Vartotojai many_many_Members: Vartotojai
GroupImportForm: GroupImportForm:
Help1: '<p>Importuoti vieną ar kelias grupes <em>CSV</em> formatu (kableliu atskirtos reikšmės). <small><a href="#" class="toggle-advanced">Rodyti detalesnį aprašymą</a></small></p>' Help1: '<p>Importuoti vieną ar kelias grupes <em>CSV</em> formatu (kableliu atskirtos reikšmės). <small><a href="#" class="toggle-advanced">Rodyti detalesnį aprašymą</a></small></p>'
Help2: "<div class=\"advanced\">\n\t<h4>Sudėtingesni pasirinkimai</h4>\n\t<ul>\n\t<li>Galimi stulpeliai: <em>%s</em></li>\n\t<li>Esamos grupės yra surišamos su jų unikalia <em>Code</em> reikšme ir atnaujinamos duomenimis iš importuojamos bylos</li>\n\t<li>Grupių hierarchija gali būti sukurta naudojant <em>ParentCode</em> stulpelį.</li>\n\t<li>Leidimų kodai gali būti priskirti naudojant <em>PermissionCode</em> stulpelį. Esami leidimai nebus pakeisti.</li>\n\t</ul>\n\ Help2: "<div class=\"advanced\">\n<h4>Sudėtingesni pasirinkimai</h4>\n<ul>\n<li>Galimi stulpeliai: <em>%s</em></li>\n<li>Esamos grupės yra surišamos su jų unikalia <em>Code</em> reikšme ir atnaujinamos duomenimis iš importuojamos bylos</li>\n<li>Grupių hierarchija gali būti sukurta naudojant <em>ParentCode</em> stulpelį.</li>\n<li>Leidimų kodai gali būti priskirti naudojant <em>PermissionCode</em> stulpelį. Esami leidimai nebus pakeisti.</li>\n</ul>\n</div>"
</div>"
ResultCreated: 'Sukurta {count} grupių' ResultCreated: 'Sukurta {count} grupių'
ResultDeleted: 'Ištrinta %d grupių' ResultDeleted: 'Ištrinta %d grupių'
ResultUpdated: 'Atnaujinta %d grupių' ResultUpdated: 'Atnaujinta %d grupių'

View File

@ -392,7 +392,7 @@ nl:
Toggle: 'Toon opmaak hulp' Toggle: 'Toon opmaak hulp'
MemberImportForm: MemberImportForm:
Help1: '<p>Importeer leden in <em>CSV</em>-formaat (comma-separated values). <small><a href="#" class="toggle-advanced">Toon geavanceerd gebruik</a></small></p>' Help1: '<p>Importeer leden in <em>CSV</em>-formaat (comma-separated values). <small><a href="#" class="toggle-advanced">Toon geavanceerd gebruik</a></small></p>'
Help2: "<div class=\"advanced\">\n\t<h4>Advanced usage</h4>\n\t<ul>\n\t<li>Allowed columns: <em>%s</em></li>\n\t<li>Existing users are matched by their unique <em>Code</em> property, and updated with any new values from\n\tthe imported file.</li>\n\t<li>Groups can be assigned by the <em>Groups</em> column. Groups are identified by their <em>Code</em> property,\n\tmultiple groups can be separated by comma. Existing group memberships are not cleared.</li>\n\t</ul>\n</div>" Help2: "<div class=\"advanced\">\n<h4>Geavanceerd gebruik</h4>\n<ul>\n<li>Toegestane kolommen: <em>%s</em></li>\n<li>Bestaande groepen worden geïdentificeerd door middel van hun unieke <em>Code</em>-waarde, en aangepast met de nieuwe waarden vanuit het geïmporteerde bestand</li>\n<li>Groepshiërarchiën kunnen aangemaakt worden door een <em>ParentCode</em>-kolom te gebruiken</li>\n<li>Toegangscodeskunnen toegewezen worden met de <em>PermissionCode</em> kolom. Bestaande toegangscodes worden niet verwijderd.</li>\n</ul>\n</div>"
ResultCreated: '{count} leden aangemaakt' ResultCreated: '{count} leden aangemaakt'
ResultDeleted: '%d leden verwijderd' ResultDeleted: '%d leden verwijderd'
ResultNone: 'Geen wijzingen' ResultNone: 'Geen wijzingen'

View File

@ -410,8 +410,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(!is_string($fieldClass)) continue; if(!is_string($fieldClass)) continue;
// Strip off any parameters // Strip off any parameters
$bPos = strpos('(', $fieldClass); $bPos = strpos($fieldClass, '(');
if($bPos !== FALSE) $fieldClass = substr(0,$bPos, $fieldClass); if($bPos !== FALSE) $fieldClass = substr($fieldClass, 0, $bPos);
// Test to see if it implements CompositeDBField // Test to see if it implements CompositeDBField
if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) { if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
@ -2584,6 +2584,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return DataObject $this * @return DataObject $this
*/ */
public function setField($fieldName, $val) { public function setField($fieldName, $val) {
//if it's a has_one component, destroy the cache
if (substr($fieldName, -2) == 'ID') {
unset($this->components[substr($fieldName, 0, -2)]);
}
// Situation 1: Passing an DBField // Situation 1: Passing an DBField
if($val instanceof DBField) { if($val instanceof DBField) {
$val->Name = $fieldName; $val->Name = $fieldName;

View File

@ -2,6 +2,7 @@
class="TreeDropdownField <% if $extraClass %> $extraClass<% end_if %><% if $ShowSearch %> searchable<% end_if %>" class="TreeDropdownField <% if $extraClass %> $extraClass<% end_if %><% if $ShowSearch %> searchable<% end_if %>"
data-url-tree="$Link('tree')" data-url-tree="$Link('tree')"
data-title="$Title.ATT" data-title="$Title.ATT"
data-empty-title="$EmptyTitle.ATT"
<% if $Description %>title="$Description.ATT"<% end_if %> <% if $Description %>title="$Description.ATT"<% end_if %>
<% if $Metadata %>data-metadata="$Metadata.ATT"<% end_if %> tabindex="0"> <% if $Metadata %>data-metadata="$Metadata.ATT"<% end_if %> tabindex="0">
<input id="$ID" type="hidden" name="$Name.ATT" value="$Value.ATT" /> <input id="$ID" type="hidden" name="$Name.ATT" value="$Value.ATT" />

View File

@ -179,8 +179,9 @@ class UploadFieldTest extends FunctionalTest {
'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload', 'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload',
array('AllowedExtensionsField' => $this->getUploadFile($invalidFile)) array('AllowedExtensionsField' => $this->getUploadFile($invalidFile))
); );
$this->assertTrue($response->isError()); $response = json_decode($response->getBody(), true);
$this->assertContains('Extension is not allowed', $response->getBody()); $this->assertTrue(array_key_exists('error', $response[0]));
$this->assertContains('Extension is not allowed', $response[0]['error']);
// Test valid file // Test valid file
$validFile = 'valid.txt'; $validFile = 'valid.txt';
@ -189,8 +190,8 @@ class UploadFieldTest extends FunctionalTest {
'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload', 'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload',
array('AllowedExtensionsField' => $this->getUploadFile($validFile)) array('AllowedExtensionsField' => $this->getUploadFile($validFile))
); );
$this->assertFalse($response->isError()); $response = json_decode($response->getBody(), true);
$this->assertNotContains('Extension is not allowed', $response->getBody()); $this->assertFalse(array_key_exists('error', $response[0]));
// Test that setAllowedExtensions rejects extensions explicitly denied by File.allowed_extensions // Test that setAllowedExtensions rejects extensions explicitly denied by File.allowed_extensions
// Relies on File::validate failing to allow this extension // Relies on File::validate failing to allow this extension
@ -200,8 +201,10 @@ class UploadFieldTest extends FunctionalTest {
'UploadFieldTest_Controller/Form/field/InvalidAllowedExtensionsField/upload', 'UploadFieldTest_Controller/Form/field/InvalidAllowedExtensionsField/upload',
array('InvalidAllowedExtensionsField' => $this->getUploadFile($invalidFile)) array('InvalidAllowedExtensionsField' => $this->getUploadFile($invalidFile))
); );
$this->assertTrue($response->isError()); $response = json_decode($response->getBody(), true);
$this->assertContains('Extension is not allowed', $response->getBody()); $this->assertTrue(array_key_exists('error', $response[0]));
$this->assertContains('Extension is not allowed', $response[0]['error']);
} }
/** /**

View File

@ -443,6 +443,7 @@ class DataObjectTest extends SapphireTest {
public function testHasOneRelationship() { public function testHasOneRelationship() {
$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1'); $team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
$player1 = $this->objFromFixture('DataObjectTest_Player', 'player1'); $player1 = $this->objFromFixture('DataObjectTest_Player', 'player1');
$player2 = $this->objFromFixture('DataObjectTest_Player', 'player2');
$fan1 = $this->objFromFixture('DataObjectTest_Fan', 'fan1'); $fan1 = $this->objFromFixture('DataObjectTest_Fan', 'fan1');
// Test relation probing // Test relation probing
@ -466,6 +467,15 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals($team1->getComponent('Captain')->FirstName, 'Player 1', $this->assertEquals($team1->getComponent('Captain')->FirstName, 'Player 1',
'Player 1 is the captain'); 'Player 1 is the captain');
$team1->CaptainID = $player2->ID;
$team1->write();
$this->assertEquals($player2->ID, $team1->Captain()->ID);
$this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
$this->assertEquals('Player 2', $team1->Captain()->FirstName);
$this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
// Set the favourite team for fan1 // Set the favourite team for fan1
$fan1->setField('FavouriteID', $team1->ID); $fan1->setField('FavouriteID', $team1->ID);
$fan1->setField('FavouriteClass', $team1->class); $fan1->setField('FavouriteClass', $team1->class);

View File

@ -1089,7 +1089,6 @@ after')
// Let's throw something random in there. // Let's throw something random in there.
$self->setExpectedException('InvalidArgumentException'); $self->setExpectedException('InvalidArgumentException');
$templates = SSViewer::get_templates_by_class(array()); $templates = SSViewer::get_templates_by_class(array());
$this->assertCount(0, $templates);
}); });
} }