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
before_script:
- composer self-update
- composer self-update || true
- phpenv rehash
- 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"

View File

@ -830,16 +830,19 @@ class LeftAndMain extends Controller implements PermissionProvider {
$record = ($rootID) ? $this->getRecord($rootID) : null;
$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
if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction);
$obj->markPartialTree($nodeCountThreshold, $this, $childrenMethod, $numChildrenMethod);
// Ensure current page is exposed
// This call flushes the Hierarchy::$marked cache when the current node is deleted
// @see CMSMain::getRecord()
// This will make it impossible to show children under a deleted parent page
// if($p = $this->currentPage()) $obj->markToExpose($p);
if($currentPage) $obj->markToExpose($currentPage);
// NOTE: SiteTree/CMSMain coupling :-(
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 .middleColumn { 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; }
/** ---------------------------------------------------- Checkbox Field ---------------------------------------------------- */

View File

@ -681,6 +681,10 @@ form.small .field, .field.small {
label {
float: none;
margin-left: 0;
&.ss-ui-button {
float: left;
}
}
.description {
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 |
| ---- | ----------- |
| `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_SERVER`| The database server to use, defaulting to localhost|
| `SS_DATABASE_USERNAME`| The database username (mandatory)|
| `SS_DATABASE_PASSWORD`| The database password (mandatory)|
| `SS_DATABASE_PORT`| The database port|
| `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_SERVER`| The database server to use, defaulting to localhost.|
| `SS_DATABASE_USERNAME`| The database username (mandatory).|
| `SS_DATABASE_PASSWORD`| The database password (mandatory).|
| `SS_DATABASE_PORT`| The database port.|
| `SS_DATABASE_SUFFIX`| A suffix 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_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_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_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_FROM`| If you set this define, all emails will be send from this address.|
| `SS_ERROR_LOG` | Relative path to the log file |
| `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 define this constant, all emails will be sent from this address.|
| `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)
* [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 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)
@ -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 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 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

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
*
/
*/
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: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.
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).
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
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).
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
Documentation for a software project is a continued and collaborative effort, we encourage everybody to contribute, from
simply fixing spelling mistakes, to writing recipes, reviewing existing documentation, and translating the whole thing.
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.
Modifying documentation requires basic [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) and
[Markdown](http://daringfireball.net/projects/markdown/) knowledge, and a GitHub user account.
## 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
page you want to edit. Alternatively, you can find the appropriate .md file in the
[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**.
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, locate the appropriate .md file in the
[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**.
* After you have made your change, describe it in the "commit summary" and "extended description" fields below, and
press "Commit Changes".
* 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
will be sent to the core committers for approval.
* After editing the documentation, describe your changes in the "commit summary" and "extended description" fields below then press "Commit Changes".
* After that you will see a form to submit a Pull Request: "[pull requests](http://help.github.com/pull-requests/)". You should be able to adjust the version your
* documentation changes are for and then submit the form. Your changes will be sent to the core committers for approval.
<div class="warning" markdown='1'>
You should make the changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you
found in the 3.1 documentation, submit your fix to that branch in Github and it'll be copied to the master (3.2)
version of the documentation automatically. *Don't submit multiple pull requests*.
You should make your changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you found in the 3.1 documentation, submit your fix to that branch in Github and it'll be copied to the master (3.2) version of the documentation automatically. *Don't submit multiple pull requests*.
</div>
## 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-cms](http://github.com/silverstripe/silverstripe-cms) repositories and send us
"[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.
[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.
The documentation is kept alongside the source code in the `docs/` subfolder of any SilverStripe module, framework or
CMS folder.
The documentation is kept alongside the source code in the `docs/` subfolder of any SilverStripe module, framework or CMS folder.
<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
documentation. This helps prevent our documentation from getting out of date.
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.
</div>
## Repositories
@ -52,44 +43,38 @@ documentation. This helps prevent our documentation from getting out of date.
## Source control
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)).
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)).
## What to write
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, and
[producing OSS: "documentation"](http://producingoss.com/en/getting-started.html#documentation) for good rules of thumb
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
for documenting open source software.
## Structure
* 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
of text somewhere?
* 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?
* 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.
* Provide context: Give API documentation 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
inaccessible island. Your page should at least be linked on the index page in the same folder. It can also appear
* API and developer guides are two forms of source code documentation that complement each other.
* API documentation should provide context, ie, the "bigger picture", by referring to developer guides inside your PHPDoc.
* 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
as "related content" on other resource (e.g. `/tutorials/site_search` might link to `/developer_guides/forms/introduction`).
## 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.
* Write in an active and direct voice.
* Mark up correctly: Use preformatted text, emphasis and bold to make technical writing more "scannable".
* Avoid FAQs: FAQs are not a replacement of a coherent, well explained documentation. If you've done a good job
* Mark up correctly: Use preformatted text. Emphasis and bold make technical writing more easily "scannable".
* 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.
* "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 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.
* URLs: is the end of your sentence is a URL, 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.
* 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 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
@ -97,8 +82,7 @@ There are several built-in block styles for highlighting a paragraph of text. Pl
sparingly.
<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"
links.
"Tip box": A tip box is great for adding, deepening or accenting information in the main text. They can be used for background knowledge, or to provide links to further information (ie, a "see also" link).
</div>
Code:
@ -108,8 +92,7 @@ Code:
</div>
<div class="notice" markdown='1'>
"Notification box": Technical notifications relating to the main text. For example, notifying users about a deprecated
feature.
"Notification box": A notification box is good for technical notifications relating to the main text. For example, notifying users about a deprecated feature.
</div>
Code:
@ -119,8 +102,7 @@ Code:
</div>
<div class="warning" markdown='1'>
"Warning box": Highlight a severe bug or technical issue requiring a users attention. For example, a code block with
destructive functionality might not have its URL actions secured to keep the code shorter.
"Warning box": A warning box is useful for highlighting a severe bug or a technical issue requiring a user's attention. For example, suppose a rare edge case sometimes leads to a variable being overwritten incorrectly. A warning box can be used to alert the user to this case so they can write their own code to handle it.
</div>
Code:
@ -129,14 +111,12 @@ Code:
...
</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.
## Translating documentation
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 or the whole body of documentation. German documentation
would for example live in `framework/docs/de/`. The
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
[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.

View File

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

View File

@ -1,54 +1,59 @@
<?php
/**
* Text input field with validation for correct email format
* according to RFC 2822.
* Text input field with validation for correct email format according to RFC 2822.
*
* @package forms
* @subpackage fields-formattedinput
*/
class EmailField extends TextField {
/**
* {@inheritdoc}
*/
public function Type() {
return 'email text';
}
/**
* {@inheritdoc}
*/
public function getAttributes() {
return array_merge(
parent::getAttributes(),
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.ietf.org/rfc/rfc2822.txt
*
* @param Validator $validator
* @return String
*
* @return string
*/
public function validate($validator) {
$this->value = trim($this->value);
$pcrePattern = '^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*'
. '@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[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])?$';
// PHP uses forward slash (/) to delimit start/end of pattern, so it must be escaped
$pregSafePattern = str_replace('/', '\\/', $pcrePattern);
// Escape delimiter characters.
$safePattern = str_replace('/', '\\/', $pattern);
if($this->value && !preg_match('/' . $pregSafePattern . '/i', $this->value)){
if($this->value && !preg_match('/' . $safePattern . '/i', $this->value)) {
$validator->validationError(
$this->name,
_t('EmailField.VALIDATION', "Please enter an email address"),
"validation"
_t('EmailField.VALIDATION', 'Please enter an email address'),
'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
* 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.
* For example, the "URLSegment" field in a standard CMS form would be
* accessible through "admin/EditForm/field/URLSegment/FieldHolder".
@ -54,19 +54,34 @@ class Form extends RequestHandler {
*/
public $IncludeFormTag = true;
/**
* @var FieldList|null
*/
protected $fields;
/**
* @var FieldList|null
*/
protected $actions;
/**
* @var Controller
* @var Controller|null
*/
protected $controller;
/**
* @var string|null
*/
protected $name;
/**
* @var Validator|null
*/
protected $validator;
/**
* @var string
*/
protected $formMethod = "POST";
/**
@ -74,16 +89,21 @@ class Form extends RequestHandler {
*/
protected $strictFormMethodCheck = false;
/**
* @var string|null
*/
protected static $current_action;
/**
* @var Dataobject $record Populated by {@link loadDataFrom()}.
* @var DataObject|null $record Populated by {@link loadDataFrom()}.
*/
protected $record;
/**
* Keeps track of whether this form has a default action or not.
* Set to false by $this->disableDefaultAction();
*
* @var boolean
*/
protected $hasDefaultAction = true;
@ -92,7 +112,7 @@ class Form extends RequestHandler {
* Useful to open a new window upon
* form submission.
*
* @var string
* @var string|null
*/
protected $target;
@ -101,7 +121,7 @@ class Form extends RequestHandler {
* <legend> element before the <fieldset>
* in Form.ss template.
*
* @var string
* @var string|null
*/
protected $legend;
@ -111,14 +131,23 @@ class Form extends RequestHandler {
* another template for customisation.
*
* @see Form->setTemplate()
* @var string
* @var string|null
*/
protected $template;
/**
* @var callable|null
*/
protected $buttonClickedFunc;
/**
* @var string|null
*/
protected $message;
/**
* @var string|null
*/
protected $messageType;
/**
@ -129,10 +158,13 @@ class Form extends RequestHandler {
*/
protected $redirectToFormOnValidationError = false;
/**
* @var bool
*/
protected $security = true;
/**
* @var SecurityToken
* @var SecurityToken|null
*/
protected $securityToken = null;
@ -148,7 +180,7 @@ class Form extends RequestHandler {
private static $default_classes = array();
/**
* @var string
* @var string|null
*/
protected $encType;
@ -158,6 +190,9 @@ class Form extends RequestHandler {
*/
protected $attributes = array();
/**
* @var array
*/
private static $allowed_actions = array(
'handleField',
'httpSubmission',
@ -179,11 +214,16 @@ class Form extends RequestHandler {
*/
private $formActionPath = false;
/**
* @var bool
*/
protected $securityTokenAdded = false;
/**
* 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 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 $actions All of the action buttons in the form - a {@link FieldLis} of
* {@link FormAction} objects
@ -234,6 +274,9 @@ class Form extends RequestHandler {
$this->setupDefaultClasses();
}
/**
* @var array
*/
private static $url_handlers = array(
'field/$FieldName!' => 'handleField',
'POST ' => 'httpSubmission',
@ -244,6 +287,8 @@ class Form extends RequestHandler {
/**
* Set up current form errors in session to
* the current form if appropriate.
*
* @return $this
*/
public function setupFormErrors() {
$errorInfo = Session::get("FormInfo.{$this->FormName()}");
@ -289,6 +334,9 @@ class Form extends RequestHandler {
* Populates the form with {@link loadDataFrom()}, calls {@link validate()},
* and only triggers the requested form action/method
* if the form is valid.
*
* @param SS_HTTPRequest $request
* @throws SS_HTTPResponse_Exception
*/
public function httpSubmission($request) {
// 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()){
$funcName = $defaultAction->actionName();
}
@ -422,6 +470,10 @@ class Form extends RequestHandler {
return $this->httpError(404);
}
/**
* @param string $action
* @return bool
*/
public function checkAccessAction($action) {
return (
parent::checkAccessAction($action)
@ -439,7 +491,7 @@ class Form extends RequestHandler {
* Returns the appropriate response up the controller chain
* if {@link validate()} fails (which is checked prior to executing any form actions).
* 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}.
*
* @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
*
* @param SS_List|array $fields
* @param callable $funcName
* @return FormField
*/
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
* 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) {
$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
* 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) {
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) {
$newFields = new FieldList();
foreach($this->fields as $field) {
@ -586,8 +648,10 @@ class Form extends RequestHandler {
/**
* Set the {@link Validator} on this form.
* @param Validator $validator
* @return $this
*/
public function setValidator( Validator $validator ) {
public function setValidator(Validator $validator ) {
if($validator) {
$this->validator = $validator;
$this->validator->setForm($this);
@ -605,6 +669,7 @@ class Form extends RequestHandler {
/**
* Convert this form to another format.
* @param FormTransformation $format
*/
public function transformTo(FormTransformation $format) {
$newFields = new FieldList();
@ -678,6 +743,7 @@ class Form extends RequestHandler {
* Setter for the form fields.
*
* @param FieldList $fields
* @return $this
*/
public function setFields($fields) {
$this->fields = $fields;
@ -697,6 +763,7 @@ class Form extends RequestHandler {
* Setter for the form actions.
*
* @param FieldList $actions
* @return $this
*/
public function setActions($actions) {
$this->actions = $actions;
@ -712,8 +779,9 @@ class Form extends RequestHandler {
}
/**
* @param String
* @param String
* @param string $name
* @param string $value
* @return $this
*/
public function setAttribute($name, $value) {
$this->attributes[$name] = $value;
@ -721,12 +789,15 @@ class Form extends RequestHandler {
}
/**
* @return String
* @return string $name
*/
public function getAttribute($name) {
if(isset($this->attributes[$name])) return $this->attributes[$name];
}
/**
* @return array
*/
public function getAttributes() {
$attrs = array(
'id' => $this->FormName(),
@ -750,9 +821,10 @@ class Form extends RequestHandler {
/**
* 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.
* @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) {
$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
*/
*/
public function setTemplateHelper($helper) {
$this->templateHelper = $helper;
}
@ -831,8 +904,7 @@ class Form extends RequestHandler {
* contents in a new window or refreshing another frame.
*
* @param target $target The value of the target
*
* @return FormField
* @return $this
*/
public function setTarget($target) {
$this->target = $target;
@ -843,6 +915,8 @@ class Form extends RequestHandler {
/**
* Set the legend value to be inserted into
* the <legend> element in the Form.ss template.
* @param string $legend
* @return $this
*/
public function setLegend($legend) {
$this->legend = $legend;
@ -854,6 +928,7 @@ class Form extends RequestHandler {
* to render with. The default is "Form".
*
* @param string $template The name of the template (without the .ss extension)
* @return $this
*/
public function setTemplate($template) {
$this->template = $template;
@ -897,7 +972,8 @@ class Form extends RequestHandler {
* Sets the form encoding type. The most common encoding types are defined
* in {@link ENC_TYPE_URLENCODED} and {@link ENC_TYPE_MULTIPART}.
*
* @param string $enctype
* @param string $encType
* @return $this
*/
public function setEncType($encType) {
$this->encType = $encType;
@ -937,8 +1013,9 @@ class Form extends RequestHandler {
/**
* Set the form method: GET, POST, PUT, DELETE.
*
* @param $method string
* @param $strict If non-null, pass value to {@link setStrictFormMethodCheck()}.
* @param string $method
* @param bool $strict If non-null, pass value to {@link setStrictFormMethodCheck()}.
* @return $this
*/
public function setFormMethod($method, $strict = null) {
$this->formMethod = strtoupper($method);
@ -957,6 +1034,7 @@ class Form extends RequestHandler {
* form.
*
* @param $bool boolean
* @return $this
*/
public function setStrictFormMethodCheck($bool) {
$this->strictFormMethodCheck = (bool)$bool;
@ -993,9 +1071,8 @@ class Form extends RequestHandler {
* recommended only for situations where you have two relatively distinct
* parts of the system trying to communicate via a form post.
*
* @param string
*
* @return Form
* @param string $path
* @return $this
*/
public function setFormAction($path) {
$this->formActionPath = $path;
@ -1016,8 +1093,7 @@ class Form extends RequestHandler {
* Set the HTML ID attribute of the form.
*
* @param string $id
*
* @return FormField
* @return $this
*/
public function setHTMLID($id) {
$this->htmlID = $id;
@ -1099,8 +1175,9 @@ class Form extends RequestHandler {
}
/**
* The next functions store and modify the forms message attributes.
* messages are stored in session under $_SESSION[formname][message];
* The next functions store and modify the forms
* message attributes. messages are stored in session under
* $_SESSION[formname][message];
*
* @return string
*/
@ -1135,12 +1212,13 @@ class Form extends RequestHandler {
/**
* Set a status message for the form.
*
*
* @param string $message the text of the message
* @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.
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
* user supplied data in the message.
* @return $this
*/
public function setMessage($message, $type, $escapeHtml = true) {
$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.
*
*
* @param string $message the text of the message
* @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.
@ -1159,7 +1237,7 @@ class Form extends RequestHandler {
*/
public function sessionMessage($message, $type, $escapeHtml = true) {
Session::set(
"FormInfo.{$this->FormName()}.formError.message",
"FormInfo.{$this->FormName()}.formError.message",
$escapeHtml ? Convert::raw2xml($message) : $message
);
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) {
Session::set(
"FormInfo.{$formName}.formError.message",
"FormInfo.{$formName}.formError.message",
$escapeHtml ? Convert::raw2xml($message) : $message
);
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
* potential existing values.
*
* Passed data should not be escaped, and is saved to the FormField
* instances unescaped.
* Passed data should not be escaped, and is saved to the FormField instances unescaped.
* Escaping happens automatically on saving the data through {@link saveInto()}.
*
* Escaping happens automatically on saving the data through
* {@link saveInto()}.
@ -1263,8 +1341,7 @@ class Form extends RequestHandler {
*
* @param array|DataObject $data
* @param int $mergeStrategy
* For every field, {@link $data} is interogated whether it contains a
* relevant property/key, and
* For every field, {@link $data} is interrogated whether it contains a relevant property/key, and
* 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
@ -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
* 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.
* @return Form
*/
@ -1308,7 +1385,7 @@ class Form extends RequestHandler {
if($dataFields) foreach($dataFields as $field) {
$name = $field->getName();
// Skip fields that have been exlcuded
// Skip fields that have been excluded
if($fieldList && !in_array($name, $fieldList)) continue;
// 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.
* It will make use of setCastedField() to do this.
*
* @param $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 DataObjectInterface $dataObject The object to save data into
* @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.
*/
public function saveInto(DataObjectInterface $dataObject, $fieldList = null) {
@ -1395,11 +1472,11 @@ class Form extends RequestHandler {
/**
* Get the submitted data from this form through
* {@link FieldList->dataFields()}, which filters out any form-specific data
* like form-actions.
*
* Calls {@link FormField->dataValue()} on each field, which returns a value
* suitable for insertion into a DataObject property.
* {@link FieldList->dataFields()}, which filters out
* any form-specific data like form-actions.
* Calls {@link FormField->dataValue()} on each field,
* which returns a value suitable for insertion into a DataObject
* property.
*
* @return array
*/
@ -1421,11 +1498,8 @@ class Form extends RequestHandler {
/**
* Call the given method on the given field.
*
* This is used by Ajax-savvy form fields. By putting '&action=callfieldmethod'
* to the end of the form action, they can access server-side data.
*
* @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]
* @param array $data
* @return mixed
*/
public function callfieldmethod($data) {
$fieldName = $data['fieldName'];
@ -1448,7 +1522,6 @@ class Form extends RequestHandler {
} else {
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
* string.
*
* You can pass either an SSViewer or a template name.
*
* @param SSViewer|string $template
*
* @return HTML
* Render this form using the given template, and return the result as a string
* You can pass either an SSViewer or a template name
* @param string|array $template
* @return HTMLText
*/
public function renderWithoutActionButton($template) {
$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
* {@link Controller}
* Sets the button that was clicked. This should only be called by the Controller.
*
* @param string $funcName The name of the action method that will be called
*
* @return Form
* @param callable $funcName The name of the action method that will be called.
* @return $this
*/
public function setButtonClicked($funcName) {
$this->buttonClickedFunc = $funcName;
@ -1680,9 +1747,8 @@ class Form extends RequestHandler {
* be added by delimiting a string with spaces.
*
* @param string $class A string containing a classname or several class
* names delimited by a single space.
*
* @return Form
* names delimited by a single space.
* @return $this
*/
public function addExtraClass($class) {
//split at white space
@ -1699,6 +1765,7 @@ class Form extends RequestHandler {
* be passed through as a space delimited string
*
* @param string $class
* @return $this
*/
public function removeExtraClass($class) {
//split at white space
@ -1730,8 +1797,11 @@ class Form extends RequestHandler {
/**
* 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
* your unit test.
* @throws SS_HTTPResponse_Exception
*/
public function testSubmission($action, $data) {
$data['action_' . $action] = true;
@ -1741,6 +1811,9 @@ class Form extends RequestHandler {
/**
* 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
* your unit test.
*/
@ -1764,10 +1837,9 @@ class Form_FieldMap extends ViewableData {
}
/**
* Ensure that all potential method calls get passed to __call(), therefore
* to dataFieldByName.
*
* @param string
* Ensure that all potential method calls get passed to __call(), therefore to dataFieldByName
* @param string $method
* @return bool
*/
public function hasMethod($method) {
return true;

View File

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

View File

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

View File

@ -1,11 +1,12 @@
<?php
/**
* Password input field.
*
* @package forms
* @subpackage fields-formattedinput
*/
class PasswordField extends TextField {
/**
* Controls the autocomplete attribute on the field.
*
@ -14,32 +15,63 @@ class PasswordField extends TextField {
*/
private static $autocomplete;
public function getAttributes() {
$attributes = array_merge(
parent::getAttributes(),
array('type' => 'password')
);
$autocomplete = Config::inst()->get('PasswordField', 'autocomplete');
if (isset($autocomplete)) {
$attributes['autocomplete'] = $autocomplete ? 'on' : 'off';
/**
* Returns an input field.
*
* @param string $name
* @param null|string $title
* @param string $value
*/
public function __construct($name, $title = null, $value = '') {
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() {
$field = $this->castedCopy('ReadonlyField');
$field->setValue('*****');
return $field;
}
/**
* {@inheritdoc}
*/
public function Type() {
return 'text password';
}
}

View File

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

View File

@ -1,4 +1,5 @@
<?php
/**
* Allows visibility of a group of fields to be toggled.
*
@ -6,17 +7,21 @@
* @subpackage fields-structural
*/
class ToggleCompositeField extends CompositeField {
/**
* @var bool
*/
protected $startClosed = true;
/**
* @var $int
* @var int
*/
protected $headingLevel = 3;
/**
* @param string $name
* @param string $title
* @param array|FieldList $children
*/
public function __construct($name, $title, $children) {
$this->name = $name;
$this->title = $title;
@ -24,30 +29,46 @@ class ToggleCompositeField extends CompositeField {
parent::__construct($children);
}
/**
* @param array $properties
*
* @return HTMLText
*/
public function FieldHolder($properties = array()) {
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.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 . '/javascript/ToggleCompositeField.js');
Requirements::css(FRAMEWORK_DIR . '/thirdparty/jquery-ui-themes/smoothness/jquery.ui.css');
$obj = $properties ? $this->customise($properties) : $this;
return $obj->renderWith($this->getTemplates());
$context = $this;
if(count($properties)) {
$context = $this->customise($properties);
}
return $context->renderWith($this->getTemplates());
}
/**
* {@inheritdoc}
*/
public function getAttributes() {
$attributes = array(
'id' => $this->id(),
'class' => $this->extraClass(),
);
if($this->getStartClosed()) {
$class = 'ss-toggle ss-toggle-start-closed';
$attributes['class'] .= ' ss-toggle ss-toggle-start-closed';
} else {
$class = 'ss-toggle';
$attributes['class'] .= ' ss-toggle';
}
return array_merge(
$this->attributes,
array(
'id' => $this->id(),
'class' => $class . ' ' . $this->extraClass()
)
$attributes
);
}
@ -59,13 +80,15 @@ class ToggleCompositeField extends CompositeField {
}
/**
* Controls whether the field is open or closed by default. By default the
* field is closed.
* Controls whether the field is open or closed by default. By default the field is closed.
*
* @param bool $bool
* @param bool $startClosed
*
* @return $this
*/
public function setStartClosed($bool) {
$this->startClosed = (bool) $bool;
public function setStartClosed($startClosed) {
$this->startClosed = (bool) $startClosed;
return $this;
}
@ -77,12 +100,13 @@ class ToggleCompositeField extends CompositeField {
}
/**
* @param int $level
* @param int $headingLevel
*
* @return $this
*/
public function setHeadingLevel($level) {
$this->headingLevel = $level;
public function setHeadingLevel($headingLevel) {
$this->headingLevel = $headingLevel;
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 . '/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;
if($record instanceof ViewableData) {
$title = $record->obj($this->labelField)->forTemplate();
} elseif($record) {
$title = Convert::raw2xml($record->{$this->labelField});
} else if($this->showSearch) {
$title = _t('DropdownField.CHOOSESEARCH', '(Choose or Search)', 'start value of a dropdown');
} else {
$title = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
}
else {
$title = $emptyTitle;
}
// TODO Implement for TreeMultiSelectField
@ -238,6 +243,7 @@ class TreeDropdownField extends FormField {
$properties,
array(
'Title' => $title,
'EmptyTitle' => $emptyTitle,
'Metadata' => ($metadata) ? Convert::raw2json($metadata) : null,
)
);

View File

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

View File

@ -154,17 +154,21 @@
var updateFn = function() {
var val = self.getValue();
if(val) {
var node = tree.find('*[data-id="' + val + '"]'),
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) {
self.setTitle(title);
self.data('title', title);
}
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

View File

@ -258,8 +258,7 @@ fi:
many_many_Members: Jäsenet
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>'
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\
</div>"
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>"
ResultCreated: 'Luotiin {count} ryhmä(ä)'
ResultDeleted: 'Poistettu %d ryhmää'
ResultUpdated: 'Päivitetty %d ryhmää'

View File

@ -191,9 +191,7 @@ lt:
TEXT2: 'slaptažodžio atstatymo nuoroda'
TEXT3: svetainei
Form:
CSRF_FAILED_MESSAGE: 'Iškilo techninė problema. Prašome paspausti mygtuką Atgal,
perkraukite naršyklės langą ir bandykite vėl.'
CSRF_FAILED_MESSAGE: 'Iškilo techninė problema. Prašome paspausti mygtuką Atgal, perkraukite naršyklės langą ir bandykite vėl.'
FIELDISREQUIRED: '{name} yra privalomas'
SubmitBtnLabel: Vykdyti
VALIDATIONCREDITNUMBER: 'Prašome įsitikinti, ar teisingai suvedėte kreditinės kortelės numerį {number}'
@ -260,8 +258,7 @@ lt:
many_many_Members: Vartotojai
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>'
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\
</div>"
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>"
ResultCreated: 'Sukurta {count} grupių'
ResultDeleted: 'Ištrinta %d grupių'
ResultUpdated: 'Atnaujinta %d grupių'

View File

@ -392,7 +392,7 @@ nl:
Toggle: 'Toon opmaak hulp'
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>'
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'
ResultDeleted: '%d leden verwijderd'
ResultNone: 'Geen wijzingen'

View File

@ -410,8 +410,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(!is_string($fieldClass)) continue;
// Strip off any parameters
$bPos = strpos('(', $fieldClass);
if($bPos !== FALSE) $fieldClass = substr(0,$bPos, $fieldClass);
$bPos = strpos($fieldClass, '(');
if($bPos !== FALSE) $fieldClass = substr($fieldClass, 0, $bPos);
// Test to see if it implements CompositeDBField
if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
@ -2584,6 +2584,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return DataObject $this
*/
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
if($val instanceof DBField) {
$val->Name = $fieldName;

View File

@ -2,6 +2,7 @@
class="TreeDropdownField <% if $extraClass %> $extraClass<% end_if %><% if $ShowSearch %> searchable<% end_if %>"
data-url-tree="$Link('tree')"
data-title="$Title.ATT"
data-empty-title="$EmptyTitle.ATT"
<% if $Description %>title="$Description.ATT"<% end_if %>
<% if $Metadata %>data-metadata="$Metadata.ATT"<% end_if %> tabindex="0">
<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',
array('AllowedExtensionsField' => $this->getUploadFile($invalidFile))
);
$this->assertTrue($response->isError());
$this->assertContains('Extension is not allowed', $response->getBody());
$response = json_decode($response->getBody(), true);
$this->assertTrue(array_key_exists('error', $response[0]));
$this->assertContains('Extension is not allowed', $response[0]['error']);
// Test valid file
$validFile = 'valid.txt';
@ -189,8 +190,8 @@ class UploadFieldTest extends FunctionalTest {
'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload',
array('AllowedExtensionsField' => $this->getUploadFile($validFile))
);
$this->assertFalse($response->isError());
$this->assertNotContains('Extension is not allowed', $response->getBody());
$response = json_decode($response->getBody(), true);
$this->assertFalse(array_key_exists('error', $response[0]));
// Test that setAllowedExtensions rejects extensions explicitly denied by File.allowed_extensions
// Relies on File::validate failing to allow this extension
@ -200,8 +201,10 @@ class UploadFieldTest extends FunctionalTest {
'UploadFieldTest_Controller/Form/field/InvalidAllowedExtensionsField/upload',
array('InvalidAllowedExtensionsField' => $this->getUploadFile($invalidFile))
);
$this->assertTrue($response->isError());
$this->assertContains('Extension is not allowed', $response->getBody());
$response = json_decode($response->getBody(), true);
$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() {
$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
$player1 = $this->objFromFixture('DataObjectTest_Player', 'player1');
$player2 = $this->objFromFixture('DataObjectTest_Player', 'player2');
$fan1 = $this->objFromFixture('DataObjectTest_Fan', 'fan1');
// Test relation probing
@ -466,6 +467,15 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals($team1->getComponent('Captain')->FirstName, 'Player 1',
'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
$fan1->setField('FavouriteID', $team1->ID);
$fan1->setField('FavouriteClass', $team1->class);

View File

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