mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Merge branch '3.1'
Conflicts: forms/Form.php forms/FormField.php security/Member.php security/MemberLoginForm.php
This commit is contained in:
commit
d431e98ecf
@ -245,7 +245,7 @@ class Director implements TemplateGlobalProvider {
|
||||
Requirements::set_backend(new Requirements_Backend());
|
||||
|
||||
// Handle absolute URLs
|
||||
if (@parse_url($url, PHP_URL_HOST) != '') {
|
||||
if (parse_url($url, PHP_URL_HOST)) {
|
||||
$bits = parse_url($url);
|
||||
// If a port is mentioned in the absolute URL, be sure to add that into the
|
||||
// HTTP host
|
||||
|
@ -401,7 +401,12 @@ class Debug {
|
||||
$reporter = self::create_debug_view();
|
||||
|
||||
// Coupling alert: This relies on knowledge of how the director gets its URL, it could be improved.
|
||||
$httpRequest = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : @$_REQUEST['url'];
|
||||
$httpRequest = null;
|
||||
if(isset($_SERVER['REQUEST_URI'])) {
|
||||
$httpRequest = $_SERVER['REQUEST_URI'];
|
||||
} elseif(isset($_REQUEST['url'])) {
|
||||
$httpRequest = $_REQUEST['url'];
|
||||
}
|
||||
if(isset($_SERVER['REQUEST_METHOD'])) $httpRequest = $_SERVER['REQUEST_METHOD'] . ' ' . $httpRequest;
|
||||
|
||||
$reporter->writeHeader($httpRequest);
|
||||
|
@ -128,8 +128,28 @@ class FixtureBlueprint {
|
||||
// Populate all relations
|
||||
if($data) foreach($data as $fieldName => $fieldVal) {
|
||||
if($obj->many_many($fieldName) || $obj->has_many($fieldName)) {
|
||||
$obj->write();
|
||||
|
||||
$parsedItems = array();
|
||||
|
||||
if(is_array($fieldVal)) {
|
||||
// handle lists of many_many relations. Each item can
|
||||
// specify the many_many_extraFields against each
|
||||
// related item.
|
||||
foreach($fieldVal as $relVal) {
|
||||
$item = key($relVal);
|
||||
$id = $this->parseValue($item, $fixtures);
|
||||
$parsedItems[] = $id;
|
||||
|
||||
array_shift($relVal);
|
||||
|
||||
$obj->getManyManyComponents($fieldName)->add(
|
||||
$id, $relVal
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$items = preg_split('/ *, */',trim($fieldVal));
|
||||
|
||||
foreach($items as $item) {
|
||||
// Check for correct format: =><relationname>.<identifier>.
|
||||
// Ignore if the item has already been replaced with a numeric DB identifier
|
||||
@ -144,12 +164,13 @@ class FixtureBlueprint {
|
||||
|
||||
$parsedItems[] = $this->parseValue($item, $fixtures);
|
||||
}
|
||||
$obj->write();
|
||||
|
||||
if($obj->has_many($fieldName)) {
|
||||
$obj->getComponents($fieldName)->setByIDList($parsedItems);
|
||||
} elseif($obj->many_many($fieldName)) {
|
||||
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
|
||||
}
|
||||
}
|
||||
} elseif($obj->has_one($fieldName)) {
|
||||
// Sets has_one with relation name
|
||||
$obj->{$fieldName . 'ID'} = $this->parseValue($fieldVal, $fixtures);
|
||||
|
@ -167,8 +167,8 @@ class SS_Log {
|
||||
$message = array(
|
||||
'errno' => '',
|
||||
'errstr' => $message,
|
||||
'errfile' => @$lastTrace['file'],
|
||||
'errline' => @$lastTrace['line'],
|
||||
'errfile' => isset($lastTrace['file']) ? $lastTrace['file'] : null,
|
||||
'errline' => isset($lastTrace['line']) ? $lastTrace['line'] : null,
|
||||
'errcontext' => $trace
|
||||
);
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ class SS_LogErrorEmailFormatter implements Zend_Log_Formatter_Interface {
|
||||
$errorType = 'Notice';
|
||||
$colour = 'grey';
|
||||
break;
|
||||
default:
|
||||
$errorType = $event['priorityName'];
|
||||
$colour = 'grey';
|
||||
}
|
||||
|
||||
if(!is_array($event['message'])) {
|
||||
@ -63,8 +66,8 @@ class SS_LogErrorEmailFormatter implements Zend_Log_Formatter_Interface {
|
||||
$relfile = Director::makeRelative($errfile);
|
||||
if($relfile && $relfile[0] == '/') $relfile = substr($relfile, 1);
|
||||
|
||||
$host = @$_SERVER['HTTP_HOST'];
|
||||
$uri = @$_SERVER['REQUEST_URI'];
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
|
||||
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null;
|
||||
|
||||
$subject = "[$errorType] in $relfile:{$errline} (http://{$host}{$uri})";
|
||||
|
||||
|
@ -26,6 +26,8 @@ class SS_LogErrorFileFormatter implements Zend_Log_Formatter_Interface {
|
||||
case 'NOTICE':
|
||||
$errtype = 'Notice';
|
||||
break;
|
||||
default:
|
||||
$errtype = $event['priorityName'];
|
||||
}
|
||||
|
||||
$urlSuffix = '';
|
||||
|
@ -408,8 +408,10 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
/**
|
||||
* Get an object from the fixture.
|
||||
*
|
||||
* @param $className The data class, as specified in your fixture file. Parent classes won't work
|
||||
* @param $identifier The identifier string, as provided in your fixture file
|
||||
* @param string $className The data class, as specified in your fixture file. Parent classes won't work
|
||||
* @param string $identifier The identifier string, as provided in your fixture file
|
||||
*
|
||||
* @return DataObject
|
||||
*/
|
||||
protected function objFromFixture($className, $identifier) {
|
||||
$obj = $this->getFixtureFactory()->get($className, $identifier);
|
||||
|
@ -395,11 +395,15 @@ class InstallRequirements {
|
||||
*/
|
||||
function findWebserver() {
|
||||
// Try finding from SERVER_SIGNATURE or SERVER_SOFTWARE
|
||||
$webserver = @$_SERVER['SERVER_SIGNATURE'];
|
||||
if(!$webserver) $webserver = @$_SERVER['SERVER_SOFTWARE'];
|
||||
if(!empty($_SERVER['SERVER_SIGNATURE'])) {
|
||||
$webserver = $_SERVER['SERVER_SIGNATURE'];
|
||||
} elseif(!empty($_SERVER['SERVER_SOFTWARE'])) {
|
||||
$webserver = $_SERVER['SERVER_SOFTWARE'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($webserver) return strip_tags(trim($webserver));
|
||||
else return false;
|
||||
return strip_tags(trim($webserver));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1125,7 +1129,7 @@ class InstallRequirements {
|
||||
$this->testing($testDetails);
|
||||
return true;
|
||||
} else {
|
||||
if(!@$result['cannotCreate']) {
|
||||
if(empty($result['cannotCreate'])) {
|
||||
$testDetails[2] .= ". Please create the database manually.";
|
||||
} else {
|
||||
$testDetails[2] .= " (user '$databaseConfig[username]' doesn't have CREATE DATABASE permissions.)";
|
||||
@ -1217,7 +1221,7 @@ class InstallRequirements {
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
|
||||
$this->tests[$section][$test] = array("error", @$testDetails[2]);
|
||||
$this->tests[$section][$test] = array("error", isset($testDetails[2]) ? $testDetails[2] : null);
|
||||
$this->errors[] = $testDetails;
|
||||
}
|
||||
|
||||
@ -1225,7 +1229,7 @@ class InstallRequirements {
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
|
||||
$this->tests[$section][$test] = array("warning", @$testDetails[2]);
|
||||
$this->tests[$section][$test] = array("warning", isset($testDetails[2]) ? $testDetails[2] : null);
|
||||
$this->warnings[] = $testDetails;
|
||||
}
|
||||
|
||||
|
@ -2,34 +2,11 @@
|
||||
|
||||
## Overview
|
||||
|
||||
### Default current Versioned "stage" to "Live" rather than "Stage"
|
||||
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
|
||||
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
|
||||
|
||||
Previously only the controllers responsible for page and CMS display
|
||||
(`LeftAndMain` and `ContentController`) explicitly set a stage through
|
||||
`Versioned::choose_site_stage()`. Unless this method is called,
|
||||
the default stage will be "Stage", showing draft content.
|
||||
Any direct subclasses of `Controller` interacting with "versioned" objects
|
||||
are vulnerable to exposing unpublished content, unless `choose_site_stage()`
|
||||
is called explicitly in their own logic.
|
||||
## Changelog
|
||||
|
||||
In order to provide more secure default behaviour, we have changed
|
||||
`choose_site_stage()` to be called on all requests, defaulting to the "Live" stage.
|
||||
If your logic relies on querying draft content, use `Versioned::reading_stage('Stage')`.
|
||||
|
||||
Important: The `choose_site_stage()` call only deals with setting the default stage,
|
||||
and doesn't check if the user is authenticated to view it. As with any other controller logic,
|
||||
please use `DataObject->canView()` to determine permissions.
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
private static $allowed_actions = array('showpage');
|
||||
public function showpage($request) {
|
||||
$page = Page::get()->byID($request->param('ID'));
|
||||
if(!$page->canView()) return $this->httpError(401);
|
||||
// continue with authenticated logic...
|
||||
}
|
||||
}
|
||||
|
||||
### API Changes
|
||||
|
||||
* 2013-08-03 [0e7231f](https://github.com/silverstripe/sapphire/commit/0e7231f) Disable discontinued Google Spellcheck in TinyMCE (Ingo Schommer)
|
||||
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9)
|
||||
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9)
|
||||
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9)
|
@ -2,6 +2,9 @@
|
||||
|
||||
## Overview
|
||||
|
||||
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
|
||||
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
|
||||
* Security: SiteTree.ExtraMeta allows JavaScript for malicious CMS authors ([SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/))
|
||||
* Better loading performance when using multiple `UploadField` instances
|
||||
* Option for `force_js_to_bottom` on `Requirements` class (ignoring inline `<script>` tags)
|
||||
* Added `ListDecorator->filterByCallback()` for more sophisticated filtering
|
||||
@ -13,4 +16,14 @@
|
||||
|
||||
## Upgrading
|
||||
|
||||
### SiteTree.ExtraMeta allows JavaScript for malicious CMS authors
|
||||
|
||||
If you have previously used the `SiteTree.ExtraMeta` field for `<head>` markup
|
||||
other than its intended use case (`<meta>` and `<link>`), please consult
|
||||
[SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/).
|
||||
|
||||
## Changelog
|
||||
|
||||
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3)
|
||||
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3)
|
||||
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3)
|
12
docs/en/changelogs/rc/3.0.9-rc1.md
Normal file
12
docs/en/changelogs/rc/3.0.9-rc1.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 3.0.9-rc1 (2014-02-19)
|
||||
|
||||
## Overview
|
||||
|
||||
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
|
||||
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
|
||||
|
||||
## Changelog
|
||||
|
||||
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9-rc1)
|
||||
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9-rc1)
|
||||
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9-rc1)
|
@ -2,12 +2,28 @@
|
||||
|
||||
## Overview
|
||||
|
||||
* ExtraMeta fields can now only contain `meta` and `link` elements
|
||||
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
|
||||
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
|
||||
* Security: SiteTree.ExtraMeta allows JavaScript for malicious CMS authors ([SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/))
|
||||
* Better loading performance when using multiple `UploadField` instances
|
||||
* Option for `force_js_to_bottom` on `Requirements` class (ignoring inline `<script>` tags)
|
||||
* Added `ListDecorator->filterByCallback()` for more sophisticated filtering
|
||||
* New `DataList` filters: `LessThanOrEqualFilter` and `GreaterThanOrEqualFilter`
|
||||
* "Cancel" button on "Add Page" form
|
||||
* Better code hinting on magic properties (for IDE autocompletion)
|
||||
* Increased Behat test coverage (editing HTML content, managing page permissions)
|
||||
* Support for PHPUnit 3.8
|
||||
|
||||
## Upgrading
|
||||
|
||||
### ExtraMeta fields can now only contain `meta` and `link` elements
|
||||
### SiteTree.ExtraMeta allows JavaScript for malicious CMS authors
|
||||
|
||||
Previously ExtraMeta fields could contain any HTML elements. From 3.1.3-rc1 the contents are filtered
|
||||
on write to only allow `meta` and `link` elements. The first time after upgrading that you save a page
|
||||
that has other elements in ExtraMeta they will be deleted.
|
||||
If you have previously used the `SiteTree.ExtraMeta` field for `<head>` markup
|
||||
other than its intended use case (`<meta>` and `<link>`), please consult
|
||||
[SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/).
|
||||
|
||||
## Changelog
|
||||
|
||||
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3-rc1)
|
||||
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3-rc1)
|
||||
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3-rc1)
|
12
docs/en/changelogs/rc/3.1.3-rc2.md
Normal file
12
docs/en/changelogs/rc/3.1.3-rc2.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 3.1.3-rc2
|
||||
|
||||
# Overview
|
||||
|
||||
* Fixed regression around CMS loading in IE8
|
||||
* Fixed regression in folder creation on upload
|
||||
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* 2014-02-20 [ebeb663](https://github.com/silverstripe/sapphire/commit/ebeb663) Fixed critical issue with Folder::find_or_make failing to handle invalid filename characters BUG Fix UploadField duplicate checking with invalid folderName (Damian Mooyman)
|
||||
* 2014-02-19 [a681bd7](https://github.com/silverstripe/sapphire/commit/a681bd7) IE8 support in jquery.ondemand.js (fixes #2872) (Loz Calver)
|
@ -71,7 +71,7 @@ Example `mysite/_config.php`:
|
||||
// Customized configuration for running with different database settings.
|
||||
// Ensure this code comes after ConfigureFromEnv.php
|
||||
if(Director::isDev()) {
|
||||
if($db = @$_GET['db']) {
|
||||
if(isset($_GET['db']) && ($db = $_GET['db'])) {
|
||||
global $databaseConfig;
|
||||
if($db == 'sqlite3') $databaseConfig['type'] = 'SQLite3Database';
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ Composer updates regularly, so you should run this command fairly often. These i
|
||||
|
||||
## Create a new site
|
||||
|
||||
Composer can create a new site for you, using the installer as a template:
|
||||
Composer can create a new site for you, using the installer as a template (by default composer will download the latest stable version):
|
||||
|
||||
composer create-project silverstripe/installer ./my/website/folder
|
||||
|
||||
@ -50,8 +50,7 @@ For example, on OS X, you might use a subdirectory of `~/Sites`.
|
||||
As long as your web server is up and running, this will get all the code that you need.
|
||||
Now visit the site in your web browser, and the installation process will be completed.
|
||||
|
||||
By default composer will download the latest stable version. You can also specify
|
||||
a version to download that version explicitly, i.e. this will download the older `3.0.3` release:
|
||||
You can also specify a version to download that version explicitly, i.e. this will download the older `3.0.3` release:
|
||||
|
||||
composer create-project silverstripe/installer ./my/website/folder 3.0.3
|
||||
|
||||
@ -116,7 +115,7 @@ So you want to contribute to SilverStripe? Fantastic! You can do this with compo
|
||||
You have to tell composer three things in order to be able to do this:
|
||||
|
||||
- Keep the full git repository information
|
||||
- Include dependancies marked as "developer" requirements
|
||||
- Include dependencies marked as "developer" requirements
|
||||
- Use the development version, not the latest stable version
|
||||
|
||||
The first two steps are done as part of the initial create project using additional arguments.
|
||||
@ -233,7 +232,7 @@ For more information, read the ["Repositories" chapter of the Composer documenta
|
||||
|
||||
### Forks and branch names
|
||||
|
||||
Generally, you should keep using the same pattern of branch names as the main repositories does. If your version is a fork of 3.0, then call the branch `3.0`, not `3.0-myproj` or `myproj`. Otherwise, the depenency resolution gets confused.
|
||||
Generally, you should keep using the same pattern of branch names as the main repositories does. If your version is a fork of 3.0, then call the branch `3.0`, not `3.0-myproj` or `myproj`. Otherwise, the dependency resolution gets confused.
|
||||
|
||||
Sometimes, however, this isn't feasible. For example, you might have a number of project forks stored in a single repository, such as your personal github fork of a project. Or you might be testing/developing a feature branch. Or it might just be confusing to other team members to call the branch of your modified version `3.0`.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Nginx
|
||||
|
||||
These instructions are also covered in less detail on the
|
||||
These instructions are also covered on the
|
||||
[Nginx Wiki](http://wiki.nginx.org/SilverStripe).
|
||||
|
||||
The prerequisite is that you have already installed Nginx and you are
|
||||
@ -18,150 +18,79 @@ But enough of the disclaimer, on to the actual configuration — typically in `n
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
root /path/to/ss/folder;
|
||||
|
||||
root /var/www/example.com;
|
||||
|
||||
# SSL configuration (optional, but recommended for security)
|
||||
# (remember to actually force logins to use ssl)
|
||||
include ssl
|
||||
|
||||
include silverstripe3.conf;
|
||||
include htaccess.conf;
|
||||
|
||||
# rest of the server section is optional, but helpful
|
||||
# maintenance page if it exists
|
||||
error_page 503 @maintenance;
|
||||
if (-f $document_root/maintenance.html ) {
|
||||
return 503;
|
||||
}
|
||||
location @maintenance {
|
||||
try_files /maintenance.html =503;
|
||||
}
|
||||
|
||||
# always show SilverStripe's version of 500 error page
|
||||
error_page 500 /assets/error-500.html;
|
||||
|
||||
# let the user's browser cache static files (e.g. 2 weeks)
|
||||
expires 2w;
|
||||
|
||||
# in case your machine is slow, increase the timeout
|
||||
# (also remembers php's own timeout settings)
|
||||
#fastcgi_read_timeout 300s;
|
||||
}
|
||||
|
||||
Here is the include file `silverstripe3.conf`:
|
||||
server_name site.com www.site.com;
|
||||
|
||||
location / {
|
||||
try_files $uri @silverstripe;
|
||||
try_files $uri /framework/main.php?url=$uri&$query_string;
|
||||
}
|
||||
|
||||
# only needed for installation - disable this location (and remove the
|
||||
# index.php and install.php files) after you installed SilverStripe
|
||||
# (you did read the blogentry linked above, didn't you)
|
||||
location ~ ^/(index|install).php {
|
||||
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
|
||||
include fastcgi.conf;
|
||||
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
|
||||
}
|
||||
error_page 404 /assets/error-404.html;
|
||||
error_page 500 /assets/error-500.html;
|
||||
|
||||
# whitelist php files that are called directly and need to be interpreted
|
||||
location = /framework/thirdparty/tinymce/tiny_mce_gzip.php {
|
||||
include fastcgi.conf;
|
||||
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
|
||||
}
|
||||
location = /framework/thirdparty/tinymce-spellchecker/rpc.php {
|
||||
include fastcgi.conf;
|
||||
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
|
||||
}
|
||||
|
||||
location @silverstripe {
|
||||
expires off;
|
||||
include fastcgi.conf;
|
||||
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
|
||||
# note that specifying a fixed script already protects against execution
|
||||
# of arbitrary files, but remember the advice above for any other rules
|
||||
# you add yourself (monitoring, etc,....)
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/framework/main.php;
|
||||
fastcgi_param SCRIPT_NAME /framework/main.php;
|
||||
fastcgi_param QUERY_STRING url=$uri&$args;
|
||||
|
||||
# tuning is up to your expertise, but buffer_size needs to be >= 8k,
|
||||
# otherwise you'll get "upstream sent too big header while reading
|
||||
# response header from upstream" errors.
|
||||
fastcgi_buffer_size 8k;
|
||||
#fastcgi_buffers 4 32k;
|
||||
#fastcgi_busy_buffers_size 64k;
|
||||
}
|
||||
|
||||
<div class="warning" markdown='1'>
|
||||
With only the above configuration, nginx would hand out any existing file
|
||||
uninterpreted, so it would happily serve your precious configuration files,
|
||||
including all your private api-keys and whatnot to any random visitor. So you
|
||||
**must** restrict access further.
|
||||
</div>
|
||||
You don't need to use separate files, but it is easier to have the permissive
|
||||
rules distinct from the restricting ones.
|
||||
|
||||
Here is the include file `htaccess.conf`:
|
||||
|
||||
# Don't try to find nonexisting stuff in assets (esp. don't pass through php)
|
||||
location ^~ /assets/ {
|
||||
sendfile on;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Deny access to silverstripe-cache, vendor or composer.json/.lock
|
||||
location ^~ /silverstripe-cache/ {
|
||||
location ~ /framework/.*(main|rpc|tiny_mce_gzip)\.php$ {
|
||||
fastcgi_keep_conn on;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /(mysite|framework|cms)/.*\.(php|php3|php4|php5|phtml|inc)$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ /\.. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.ss$ {
|
||||
satisfy any;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ web\.config$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.ya?ml$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ^~ /vendor/ {
|
||||
deny all;
|
||||
}
|
||||
location ~ /composer\.(json|lock) {
|
||||
|
||||
location ~* /silverstripe-cache/ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# Don't serve up any "hidden" files or directories
|
||||
# (starting with dot, like .htaccess or .git)
|
||||
# also don't serve web.config files
|
||||
location ~ /(\.|web\.config) {
|
||||
location ~* composer\.(json|lock)$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# Block access to yaml files (and don't forget about backup
|
||||
# files that editors tend to leave behind)
|
||||
location ~ \.(yml|bak|swp)$ {
|
||||
deny all;
|
||||
}
|
||||
location ~ ~$ {
|
||||
location ~* /(cms|framework)/silverstripe_version$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# generally don't serve any php-like files
|
||||
# (as they exist, they would be served as regular files, and not interpreted.
|
||||
# But as those can contain configuration data, this is bad nevertheless)
|
||||
# If needed, you can always whitelist entries.
|
||||
location ~ \.(php|php[345]|phtml|inc)$ {
|
||||
deny all;
|
||||
location ~ \.php$ {
|
||||
fastcgi_keep_conn on;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
location ~ ^/(cms|framework)/silverstripe_version$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
Here is the optional include file `ssl`:
|
||||
|
||||
listen 443 ssl;
|
||||
ssl_certificate server.crt;
|
||||
ssl_certificate_key server.key;
|
||||
ssl_session_timeout 5m;
|
||||
ssl_protocols SSLv3 TLSv1;
|
||||
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
|
||||
|
||||
The above configuration sets up a virtual host `example.com` with
|
||||
rewrite rules suited for SilverStripe. The location block named
|
||||
`@silverstripe` passes all requests that aren't matched by one of the other
|
||||
location rules (and cannot be satisfied by serving an existing file) to
|
||||
SilverStripe framework's main.php script, that is run by the FastCGI-wrapper,
|
||||
that in turn is accessed via a Unix socket.
|
||||
The above configuration sets up a virtual host `site.com` with
|
||||
rewrite rules suited for SilverStripe. The location block for php files
|
||||
passes all php scripts to the FastCGI-wrapper via a TCP socket.
|
||||
|
||||
Now you can proceed with the SilverStripe installation normally.
|
||||
|
@ -208,7 +208,7 @@ Non-textual elements (such as images and their manipulations) can also be used i
|
||||
'HeroImage' => 'Image'
|
||||
);
|
||||
private static $summary_fields = array(
|
||||
'Name' => 'Name,
|
||||
'Name' => 'Name',
|
||||
'HeroImage.CMSThumbnail' => 'Hero Image'
|
||||
);
|
||||
}
|
||||
|
@ -66,7 +66,6 @@ Note how the configuration happens in different entwine namespaces
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}(jQuery));
|
||||
|
||||
Load the file in the CMS via setting adding 'mysite/javascript/MyLeftAndMain.Preview.js'
|
||||
|
@ -97,9 +97,10 @@ plus some `width` and `height` arguments. We'll add defaults to those in our sho
|
||||
|
||||
The hard bits are taken care of (parsing out the shortcodes), everything we need to do is a bit of string replacement.
|
||||
CMS users still need to remember the specific syntax, but these shortcodes can form the basis
|
||||
for more advanced editing interfaces (with visual placeholders). See the built-in `[embed]` shortcode as an example
|
||||
for more advanced editing interfaces (with visual placeholders). See the built-in `embed` shortcode as an example
|
||||
for coupling shortcodes with a form to create and edit placeholders.
|
||||
|
||||
|
||||
## Built-in Shortcodes
|
||||
|
||||
SilverStripe comes with several shortcode parsers already.
|
||||
|
@ -21,7 +21,7 @@ In your controller (e.g. `mysite/code/Page.php`):
|
||||
// either specify the css file manually
|
||||
Requirements::css("mymodule/css/my.css", "screen,projection");
|
||||
// or mention the css filename and SilverStripe will get the file from the current theme and add it to the template
|
||||
Requirements::themedCSS('print', 'print');
|
||||
Requirements::themedCSS('print', null,'print');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -720,7 +720,7 @@ an object, not for displaying the objects contained in the relation.
|
||||
## Validation and Constraints
|
||||
|
||||
Traditionally, validation in SilverStripe has been mostly handled on the
|
||||
controller through [form validation](/topics/form-validation).
|
||||
controller through [form validation](/topics/forms#form-validation).
|
||||
|
||||
While this is a useful approach, it can lead to data inconsistencies if the
|
||||
record is modified outside of the controller and form context.
|
||||
|
@ -1,159 +0,0 @@
|
||||
# Form Validation
|
||||
|
||||
SilverStripe provides PHP form validation out of the box,
|
||||
but doesn't come with any built-in JavaScript validation
|
||||
(the previously used `Validator.js` approach has been deprecated).
|
||||
|
||||
## Required Fields
|
||||
|
||||
Validators are implemented as an argument to the `[api:Form]` constructor,
|
||||
and are subclasses of the abstract `[api:Validator]` base class.
|
||||
The only implementation which comes with SilverStripe is
|
||||
the `[api:RequiredFields]` class, which ensures fields are filled out
|
||||
when the form is submitted.
|
||||
|
||||
:::php
|
||||
public function Form() {
|
||||
$form = new Form($this, 'Form',
|
||||
new FieldList(
|
||||
new TextField('MyRequiredField'),
|
||||
new TextField('MyOptionalField')
|
||||
),
|
||||
new FieldList(
|
||||
new FormAction('submit', 'Submit form')
|
||||
),
|
||||
new RequiredFields(array('MyRequiredField'))
|
||||
);
|
||||
// Optional: Add a CSS class for custom styling
|
||||
$form->dataFieldByName('MyRequiredField')->addExtraClass('required');
|
||||
return $form;
|
||||
}
|
||||
|
||||
## Form Field Validation
|
||||
|
||||
Form fields are responsible for validating the data they process,
|
||||
through the `[api:FormField->validate()] method. There are many fields
|
||||
for different purposes (see ["form field types"](/reference/form-field-types) for a full list).
|
||||
|
||||
## Adding your own validation messages
|
||||
|
||||
In many cases, you want to add PHP validation which is more complex than
|
||||
validating the format or existence of a single form field input.
|
||||
For example, you might want to have dependent validation on
|
||||
a postcode which depends on the country you've selected in a different field.
|
||||
|
||||
There's two ways to go about this: Either you can attach a custom error message
|
||||
to a specific field, or a generic message for the whole form.
|
||||
|
||||
Example: Validate postcodes based on the selected country (on the controller).
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
private static $allowed_actions = array('Form');
|
||||
public function Form() {
|
||||
return Form::create($this, 'Form',
|
||||
new FieldList(
|
||||
new NumericField('Postcode'),
|
||||
new CountryDropdownField('Country')
|
||||
),
|
||||
new FieldList(
|
||||
new FormAction('submit', 'Submit form')
|
||||
),
|
||||
new RequiredFields(array('Country'))
|
||||
);
|
||||
}
|
||||
public function submit($data, $form) {
|
||||
// At this point, RequiredFields->validate() will have been called already,
|
||||
// so we can assume that the values exist.
|
||||
|
||||
// German postcodes need to be five digits
|
||||
if($data['Country'] == 'de' && isset($data['Postcode']) && strlen($data['Postcode']) != 5) {
|
||||
$form->addErrorMessage('Postcode', 'Need five digits for German postcodes', 'bad');
|
||||
return $this->redirectBack();
|
||||
}
|
||||
|
||||
// Global validation error (not specific to form field)
|
||||
if($data['Country'] == 'IR' && isset($data['Postcode']) && $data['Postcode']) {
|
||||
$form->sessionMessage("Ireland doesn't have postcodes!", 'bad');
|
||||
return $this->redirectBack();
|
||||
}
|
||||
|
||||
// continue normal processing...
|
||||
}
|
||||
}
|
||||
|
||||
## JavaScript Validation
|
||||
|
||||
While there are no built-in JavaScript validation handlers in SilverStripe,
|
||||
the `FormField` API is flexible enough to provide the information required
|
||||
in order to plug in custom libraries.
|
||||
|
||||
### HTML5 attributes
|
||||
|
||||
HTML5 specifies some built-in form validations ([source](http://www.w3.org/wiki/HTML5_form_additions)),
|
||||
which are evaluated by modern browsers without any need for JavaScript.
|
||||
SilverStripe supports this by allowing to set custom attributes on fields.
|
||||
|
||||
:::php
|
||||
// Markup contains <input type="text" required />
|
||||
TextField::create('MyText')->setAttribute('required', true);
|
||||
|
||||
// Markup contains <input type="url" pattern="https?://.+" />
|
||||
TextField::create('MyText')
|
||||
->setAttribute('type', 'url')
|
||||
->setAttribute('pattern', 'https?://.+')
|
||||
|
||||
### HTML5 metadata
|
||||
|
||||
In addition, HTML5 elements can contain custom data attributes with the `data-` prefix.
|
||||
These are general purpose attributes, but can be used to hook in your own validation.
|
||||
|
||||
:::php
|
||||
// Validate a specific date format (in PHP)
|
||||
// Markup contains <input type="text" data-dateformat="dd.MM.yyyy" />
|
||||
DateField::create('MyDate')->setConfig('dateformat', 'dd.MM.yyyy');
|
||||
|
||||
// Limit extensions on upload (in PHP)
|
||||
// Markup contains <input type="file" data-allowed-extensions="jpg,jpeg,gif" />
|
||||
$exts = array('jpg', 'jpeg', 'gif');
|
||||
$validator = new Upload_Validator();
|
||||
$validator->setAllowedExtensions($exts);
|
||||
$upload = Upload::create()->setValidator($validator);
|
||||
$fileField = FileField::create('MyFile')->setUpload(new);
|
||||
$fileField->setAttribute('data-allowed-extensions', implode(',', $exts));
|
||||
|
||||
Note that these examples don't have any effect on the client as such,
|
||||
but are just a starting point for custom validation with JavaScript.
|
||||
|
||||
## Model Validation
|
||||
|
||||
An alternative (or additional) approach to validation is to place it directly
|
||||
on the model. SilverStripe provides a `[api:DataObject->validate()]` method for this purpose.
|
||||
Refer to the ["datamodel" topic](/topics/datamodel#validation-and-constraints) for more information.
|
||||
|
||||
## Validation in the CMS
|
||||
|
||||
Since you're not creating the forms for editing CMS records,
|
||||
SilverStripe provides you with a `getCMSValidator()` method on your models
|
||||
to return a `[api:Validator]` instance.
|
||||
|
||||
:::php
|
||||
class Page extends SiteTree {
|
||||
private static $db = array('MyRequiredField' => 'Text');
|
||||
|
||||
public function getCMSValidator() {
|
||||
return new RequiredFields(array('MyRequiredField'));
|
||||
}
|
||||
}
|
||||
|
||||
## Subclassing Validator
|
||||
|
||||
To create your own validator, you need to subclass validator and define two methods:
|
||||
|
||||
* **javascript()** Should output a snippet of JavaScript that will get called to perform javascript validation.
|
||||
* **php($data)** Should return true if the given data is valid, and call $this->validationError() if there were any
|
||||
errors.
|
||||
|
||||
## Related
|
||||
|
||||
* Model Validation with [api:DataObject->validate()]
|
@ -1,6 +1,6 @@
|
||||
# Forms
|
||||
|
||||
HTML forms are in practice the most used way to communicate with a browser.
|
||||
HTML forms are in practice the most used way to interact with a user.
|
||||
SilverStripe provides classes to generate and handle the actions and data from a
|
||||
form.
|
||||
|
||||
@ -9,14 +9,14 @@ form.
|
||||
A fully implemented form in SilverStripe includes a couple of classes that
|
||||
individually have separate concerns.
|
||||
|
||||
* Controller - Takes care of assembling the form and receiving data from it.
|
||||
* Form - Holds sets of fields, actions and validators.
|
||||
* FormField - Fields that receive data or displays them, e.g input fields.
|
||||
* FormActions - Often submit buttons that executes actions.
|
||||
* Validators - Validate the whole form, see [Form validation](form-validation.md) topic for more information.
|
||||
* Controller—Takes care of assembling the form and receiving data from it.
|
||||
* Form—Holds sets of fields, actions and validators.
|
||||
* FormField —Fields that receive data or displays them, e.g input fields.
|
||||
* FormActions—Often submit buttons that executes actions.
|
||||
* Validators—Validate the whole form.
|
||||
|
||||
Depending on your needs you can customize and override any of the above classes,
|
||||
however the defaults are often sufficient.
|
||||
Depending on your needs you can customize and override any of the above classes;
|
||||
the defaults, however, are often sufficient.
|
||||
|
||||
## The Controller
|
||||
|
||||
@ -53,12 +53,13 @@ in a controller.
|
||||
The name of the form ("HelloForm") is passed into the `Form` constructor as a
|
||||
second argument. It needs to match the method name.
|
||||
|
||||
Since forms need a URL, the `HelloForm()` method needs to be handled like any
|
||||
other controller action. In order to whitelist its access through URLs, we add
|
||||
it to the `$allowed_actions` array.
|
||||
Because forms need a URL, the `HelloForm()` method needs to be handled like any
|
||||
other controller action. To grant it access through URLs, we add it to the
|
||||
`$allowed_actions` array.
|
||||
|
||||
Form actions ("doSayHello") on the other hand should NOT be included here, these
|
||||
are handled separately through `Form->httpSubmission()`.
|
||||
Form actions ("doSayHello"), on the other hand, should _not_ be included in
|
||||
`$allowed_actions`; these are handled separately through
|
||||
`Form->httpSubmission()`.
|
||||
|
||||
You can control access on form actions either by conditionally removing a
|
||||
`FormAction` from the form construction, or by defining `$allowed_actions` in
|
||||
@ -68,19 +69,21 @@ your own `Form` class (more information in the
|
||||
**Page.ss**
|
||||
|
||||
:::ss
|
||||
<!-- place where you would like the form to show up -->
|
||||
<%-- place where you would like the form to show up --%>
|
||||
<div>$HelloForm</div>
|
||||
|
||||
<div class="warning" markdown='1'>
|
||||
Be sure to add the Form name 'HelloForm' to the Controller::$allowed_actions()
|
||||
to be sure that form submissions get through to the correct action.
|
||||
Be sure to add the Form name 'HelloForm' to your controller's $allowed_actions
|
||||
array to enable form submissions.
|
||||
</div>
|
||||
|
||||
<div class="notice" markdown='1'>
|
||||
You'll notice that we've used a new notation for creating form fields, using `create()` instead of the `new` operator.
|
||||
These are functionally equivalent, but allows PHP to chain operations like `setTitle()` without assigning the field
|
||||
instance to a temporary variable. For in-depth information on the create syntax, see the [Injector](/reference/injector)
|
||||
documentation or the API documentation for `[api:Object]`::create().
|
||||
You'll notice that we've used a new notation for creating form fields, using
|
||||
`create()` instead of the `new` operator. These are functionally equivalent, but
|
||||
allows PHP to chain operations like `setTitle()` without assigning the field
|
||||
instance to a temporary variable. For in-depth information on the create syntax,
|
||||
see the [Injector](/reference/injector) documentation or the API documentation
|
||||
for `[api:Object]`::create().
|
||||
</div>
|
||||
|
||||
## The Form
|
||||
@ -95,12 +98,17 @@ Creating a form is a matter of defining a method to represent that form. This
|
||||
method should return a form object. The constructor takes the following
|
||||
arguments:
|
||||
|
||||
* `$controller`: This must be an instance of the controller that contains the form, often `$this`.
|
||||
* `$name`: This must be the name of the method on that controller that is called to return the form. The first two
|
||||
fields allow the form object to be re-created after submission. **It's vital that they are properly set - if you ever
|
||||
have problems with form action handler not working, check that these values are correct.**
|
||||
* `$fields`: A `[api:FieldList]` containing `[api:FormField]` instances make up fields in the form.
|
||||
* `$actions`: A `[api:FieldList]` containing the `[api:FormAction]` objects - the buttons at the bottom.
|
||||
* `$controller`: This must be an instance of the controller that contains the
|
||||
form, often `$this`.
|
||||
* `$name`: This must be the name of the method on that controller that is
|
||||
called to return the form. The first two arguments allow the form object
|
||||
to be re-created after submission. **It's vital that they be properly
|
||||
set—if you ever have problems with a form action handler not working,
|
||||
check that these values are correct.**
|
||||
* `$fields`: A `[api:FieldList]` containing `[api:FormField]` instances make
|
||||
up fields in the form.
|
||||
* `$actions`: A `[api:FieldList]` containing the `[api:FormAction]` objects -
|
||||
the buttons at the bottom.
|
||||
* `$validator`: An optional `[api:Validator]` for validation of the form.
|
||||
|
||||
Example:
|
||||
@ -119,13 +127,14 @@ Example:
|
||||
|
||||
## Subclassing a form
|
||||
|
||||
It's the responsibility of your subclass' constructor to call
|
||||
It's the responsibility of your subclass's constructor to call
|
||||
|
||||
:::php
|
||||
parent::__construct()
|
||||
|
||||
with the right parameters. You may choose to take $fields and $actions as arguments if you wish, but $controller and
|
||||
$name must be passed - their values depend on where the form is instantiated.
|
||||
with the right parameters. You may choose to take $fields and $actions as
|
||||
arguments if you wish, but $controller and $name must be passed—their values
|
||||
depend on where the form is instantiated.
|
||||
|
||||
:::php
|
||||
class MyForm extends Form {
|
||||
@ -141,8 +150,9 @@ $name must be passed - their values depend on where the form is instantiated.
|
||||
}
|
||||
|
||||
|
||||
The real difference, however, is that you can then define your controller methods within the form class itself. This
|
||||
means that the form takes responsibilities from the controller and manage how to parse and use the form
|
||||
The real difference, however, is that you can then define your controller
|
||||
methods within the form class itself. This means that the form takes
|
||||
responsibilities from the controller and manage how to parse and use the form
|
||||
data.
|
||||
|
||||
**Page.php**
|
||||
@ -214,7 +224,7 @@ form.
|
||||
## Readonly
|
||||
|
||||
You can turn a form or individual fields into a readonly version. This is handy
|
||||
in the case of confirmation pages or when certain fields can be edited due to
|
||||
in the case of confirmation pages or when certain fields cannot be edited due to
|
||||
permissions.
|
||||
|
||||
Readonly on a Form
|
||||
@ -241,10 +251,13 @@ Readonly on a FormField
|
||||
|
||||
You can use a custom form template to render with, instead of *Form.ss*
|
||||
|
||||
It's recommended you only do this if you've got a lot of presentation text, graphics that surround the form fields. This
|
||||
is better than defining those as *LiteralField* objects, as it doesn't clutter the data layer with presentation junk.
|
||||
It's recommended you do this only if you have a lot of presentation text or
|
||||
graphics that surround the form fields. This is better than defining those as
|
||||
*LiteralField* objects, as it doesn't clutter the data layer with presentation
|
||||
junk.
|
||||
|
||||
First of all, you need to create your form on it's own class, that way you can define a custom template using a `forTemplate()` method on your Form class.
|
||||
First you need to create your own form class extending Form; that way you can
|
||||
define a custom template using a `forTemplate()` method on your Form class.
|
||||
|
||||
:::php
|
||||
class MyForm extends Form {
|
||||
@ -305,14 +318,16 @@ your project. Here is an example of basic customization:
|
||||
<% end_if %>
|
||||
</form>
|
||||
|
||||
`$Fields.dataFieldByName(FirstName)` will return the form control contents of `Field()` for the particular field object,
|
||||
in this case `EmailField->Field()` or `PasswordField->Field()` which returns an `<input>` element with specific markup
|
||||
for the type of field. Pass in the name of the field as the first parameter, as done above, to render it into the
|
||||
template.
|
||||
`$Fields.dataFieldByName(FirstName)` will return the form control contents of
|
||||
`Field()` for the particular field object, in this case `EmailField->Field()` or
|
||||
`PasswordField->Field()` which returns an `<input>` element with specific markup
|
||||
for the type of field. Pass in the name of the field as the first parameter, as
|
||||
done above, to render it into the template.
|
||||
|
||||
To find more methods, have a look at the `[api:Form]` class and `[api:FieldList]` class as there is a lot of different
|
||||
methods of customising the form templates. An example is that you could use `<% loop $Fields %>` instead of specifying
|
||||
each field manually, as we've done above.
|
||||
To find more methods, have a look at the `[api:Form]` class and
|
||||
`[api:FieldList]` class as there is a lot of different methods of customising
|
||||
the form templates. An example is that you could use `<% loop $Fields %>`
|
||||
instead of specifying each field manually, as we've done above.
|
||||
|
||||
### Custom form field templates
|
||||
|
||||
@ -333,14 +348,15 @@ Each form field is rendered into a form via the
|
||||
`<div>` as well as a `<label>` element (if applicable).
|
||||
|
||||
You can also render each field without these structural elements through the
|
||||
`[FormField->Field()](api:FormField)` method. In order to influence the form
|
||||
rendering, overloading these two methods is a good start.
|
||||
`[FormField->Field()](api:FormField)` method. To influence form rendering,
|
||||
overriding these two methods is a good start.
|
||||
|
||||
In addition, most form fields are rendered through SilverStripe templates, e.g.
|
||||
`TextareaField` is rendered via `framework/templates/forms/TextareaField.ss`.
|
||||
In addition, most form fields are rendered through SilverStripe templates; for
|
||||
example, `TextareaField` is rendered via
|
||||
`framework/templates/forms/TextareaField.ss`.
|
||||
|
||||
These templates can be overwritten globally by placing a template with the same
|
||||
name in your `mysite` directory, or set on a form field instance via anyone of
|
||||
These templates can be overridden globally by placing a template with the same
|
||||
name in your `mysite` directory, or set on a form field instance via any of
|
||||
these methods:
|
||||
|
||||
- FormField->setTemplate()
|
||||
@ -358,7 +374,7 @@ by adding a hidden *SecurityID* parameter to each form. See
|
||||
[secure-development](/topics/security) for details.
|
||||
|
||||
In addition, you should limit forms to the intended HTTP verb (mostly `GET` or `POST`)
|
||||
to further reduce attack surface, by using `[api:Form->setStrictFormMethodCheck()]`.
|
||||
to further reduce attack exposure, by using `[api:Form->setStrictFormMethodCheck()]`.
|
||||
|
||||
:::php
|
||||
$myForm->setFormMethod('POST');
|
||||
@ -391,10 +407,170 @@ Adds a new text field called FavouriteColour next to the Content field in the CM
|
||||
:::php
|
||||
$this->Fields()->addFieldToTab('Root.Content', new TextField('FavouriteColour'), 'Content');
|
||||
|
||||
## Form Validation
|
||||
|
||||
SilverStripe provides PHP form validation out of the box, but doesn't come with
|
||||
any built-in JavaScript validation (the previously used `Validator.js` approach
|
||||
has been deprecated).
|
||||
|
||||
### Required Fields
|
||||
|
||||
Validators are implemented as an argument to the `[api:Form]` constructor, and
|
||||
are subclasses of the abstract `[api:Validator]` base class. The only
|
||||
implementation that comes with SilverStripe is the `[api:RequiredFields]` class,
|
||||
which ensures that fields are filled out when the form is submitted.
|
||||
|
||||
:::php
|
||||
public function Form() {
|
||||
$form = new Form($this, 'Form',
|
||||
new FieldList(
|
||||
new TextField('MyRequiredField'),
|
||||
new TextField('MyOptionalField')
|
||||
),
|
||||
new FieldList(
|
||||
new FormAction('submit', 'Submit form')
|
||||
),
|
||||
new RequiredFields(array('MyRequiredField'))
|
||||
);
|
||||
// Optional: Add a CSS class for custom styling
|
||||
$form->dataFieldByName('MyRequiredField')->addExtraClass('required');
|
||||
return $form;
|
||||
}
|
||||
|
||||
### Form Field Validation
|
||||
|
||||
Form fields are responsible for validating the data they process, through the
|
||||
`[api:FormField->validate()]` method. There are many fields for different
|
||||
purposes (see ["form field types"](/reference/form-field-types) for a full list).
|
||||
|
||||
### Adding your own validation messages
|
||||
|
||||
In many cases, you want to add PHP validation that is more complex than
|
||||
validating the format or existence of a single form field input. For example,
|
||||
you might want to have dependent validation on a postcode which depends on the
|
||||
country you've selected in a different field.
|
||||
|
||||
There are two ways to go about this: attach a custom error message to a specific
|
||||
field, or a generic message to the whole form.
|
||||
|
||||
Example: Validate postcodes based on the selected country (on the controller).
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
private static $allowed_actions = array('Form');
|
||||
public function Form() {
|
||||
return Form::create($this, 'Form',
|
||||
new FieldList(
|
||||
new NumericField('Postcode'),
|
||||
new CountryDropdownField('Country')
|
||||
),
|
||||
new FieldList(
|
||||
new FormAction('submit', 'Submit form')
|
||||
),
|
||||
new RequiredFields(array('Country'))
|
||||
);
|
||||
}
|
||||
public function submit($data, $form) {
|
||||
// At this point, RequiredFields->validate() will have been called already,
|
||||
// so we can assume that the values exist.
|
||||
|
||||
// German postcodes need to be five digits
|
||||
if($data['Country'] == 'de' && isset($data['Postcode']) && strlen($data['Postcode']) != 5) {
|
||||
$form->addErrorMessage('Postcode', 'Need five digits for German postcodes', 'bad');
|
||||
return $this->redirectBack();
|
||||
}
|
||||
|
||||
// Global validation error (not specific to form field)
|
||||
if($data['Country'] == 'IR' && isset($data['Postcode']) && $data['Postcode']) {
|
||||
$form->sessionMessage("Ireland doesn't have postcodes!", 'bad');
|
||||
return $this->redirectBack();
|
||||
}
|
||||
|
||||
// continue normal processing...
|
||||
}
|
||||
}
|
||||
|
||||
### JavaScript Validation
|
||||
|
||||
Although there are no built-in JavaScript validation handlers in SilverStripe,
|
||||
the `FormField` API is flexible enough to provide the information required in
|
||||
order to plug in custom libraries.
|
||||
|
||||
#### HTML5 attributes
|
||||
|
||||
HTML5 specifies some built-in form validations
|
||||
([source](http://www.w3.org/wiki/HTML5_form_additions)), which are evaluated by
|
||||
modern browsers without any need for JavaScript. SilverStripe supports this by
|
||||
allowing to set custom attributes on fields.
|
||||
|
||||
:::php
|
||||
// Markup contains <input type="text" required />
|
||||
TextField::create('MyText')->setAttribute('required', true);
|
||||
|
||||
// Markup contains <input type="url" pattern="https?://.+" />
|
||||
TextField::create('MyText')
|
||||
->setAttribute('type', 'url')
|
||||
->setAttribute('pattern', 'https?://.+')
|
||||
|
||||
#### HTML5 metadata
|
||||
|
||||
In addition, HTML5 elements can contain custom data attributes with the `data-`
|
||||
prefix. These are general-purpose attributes, but can be used to hook in your
|
||||
own validation.
|
||||
|
||||
:::php
|
||||
// Validate a specific date format (in PHP)
|
||||
// Markup contains <input type="text" data-dateformat="dd.MM.yyyy" />
|
||||
DateField::create('MyDate')->setConfig('dateformat', 'dd.MM.yyyy');
|
||||
|
||||
// Limit extensions on upload (in PHP)
|
||||
// Markup contains <input type="file" data-allowed-extensions="jpg,jpeg,gif" />
|
||||
$exts = array('jpg', 'jpeg', 'gif');
|
||||
$validator = new Upload_Validator();
|
||||
$validator->setAllowedExtensions($exts);
|
||||
$upload = Upload::create()->setValidator($validator);
|
||||
$fileField = FileField::create('MyFile')->setUpload(new);
|
||||
$fileField->setAttribute('data-allowed-extensions', implode(',', $exts));
|
||||
|
||||
Note that these examples don't have any effect on the client as such, but are
|
||||
just a starting point for custom validation with JavaScript.
|
||||
|
||||
### Model Validation
|
||||
|
||||
An alternative (or additional) approach to validation is to place it directly on
|
||||
the model. SilverStripe provides a `[api:DataObject->validate()]` method for
|
||||
this purpose. Refer to the
|
||||
["datamodel" topic](/topics/datamodel#validation-and-constraints) for more information.
|
||||
|
||||
### Validation in the CMS
|
||||
|
||||
Since you're not creating the forms for editing CMS records, SilverStripe
|
||||
provides you with a `getCMSValidator()` method on your models to return a
|
||||
`[api:Validator]` instance.
|
||||
|
||||
:::php
|
||||
class Page extends SiteTree {
|
||||
private static $db = array('MyRequiredField' => 'Text');
|
||||
|
||||
public function getCMSValidator() {
|
||||
return new RequiredFields(array('MyRequiredField'));
|
||||
}
|
||||
}
|
||||
|
||||
### Subclassing Validator
|
||||
|
||||
To create your own validator, you need to subclass validator and define two methods:
|
||||
|
||||
* **javascript()** Should output a snippet of JavaScript that will get called
|
||||
to perform javascript validation.
|
||||
* **php($data)** Should return true if the given data is valid, and call
|
||||
$this->validationError() if there were any errors.
|
||||
|
||||
## Related
|
||||
|
||||
* [Form Field Types](/reference/form-field-types)
|
||||
* [MultiForm Module](http://silverstripe.org/multi-form-module)
|
||||
* Model Validation with [api:DataObject->validate()]
|
||||
|
||||
## API Documentation
|
||||
|
||||
|
@ -16,18 +16,19 @@ It is where most documentation should live, and is the natural "second step" aft
|
||||
* [Environment management](environment-management): Sharing configuration details (e.g. database login, passwords) with multiple websites via a `_ss_environment.php` file
|
||||
* [Error Handling](error-handling): Error messages and filesystem logs
|
||||
* [Files and Images](files): File and Image management in the database and how to manipulate images
|
||||
* [Form Validation](form-validation): Built-in validation on form fields, and how to extend it
|
||||
* [Forms](forms): Create your own form, add fields and create your own form template using the existing `Form` class
|
||||
* [Forms & form validation](forms): Create your own form, add fields and create your own form template using the existing `Form` class
|
||||
* [Internationalization (i18n)](i18n): Displaying templates and PHP code in different languages using i18n
|
||||
* [Javascript](javascript): Best practices for developing with JavaScript in SilverStripe
|
||||
* [Module Development](module-development): Creating a module (also known as "extension" or "plugin") to contain reusable functionality
|
||||
* [Modules](modules): Introduction, how to download and install a module (e.g. with blog or forum functionality)
|
||||
* [Page Type Templates](page-type-templates): How to build templates for all your different page types
|
||||
* [Page Types](page-types): What is a "page type" and how do you create one?
|
||||
* [Rich Text Editing](rich-text-editing): How to use and configure SilverStripes built in HTML Editor
|
||||
* [Search](search): Searching for properties in the database as well as other documents
|
||||
* [Security](security): How to develop secure SilverStripe applications with good code examples
|
||||
* [Shortcodes](shortcodes): Use simple placeholders for powerful content replacements like multimedia embeds
|
||||
* [Templates](templates): SilverStripe template syntax: Variables, loops, includes and much more
|
||||
* [Testing](testing): Functional and Unit Testing with PHPUnit and SilverStripe's testing framework
|
||||
* [Developing Themes](theme-development): Package templates, images and CSS to a reusable theme
|
||||
* [Widgets](widgets): Small feature blocks which can be placed on a page by the CMS editor, also outlines how to create and add widgets
|
||||
* [Theme Development](theme-development): Package templates, images and CSS to a reusable theme
|
||||
* [Using Themes](themes): How to download and install themes
|
||||
* [Versioning](versioning): Extension for SiteTree and other classes to store old versions and provide "staging"
|
||||
* [Widgets](widgets): Small feature blocks which can be placed on a page by the CMS editor, also outlines how to create and add widgets
|
||||
|
@ -2,38 +2,42 @@
|
||||
|
||||
## Overview
|
||||
|
||||
You will often find the need to test your functionality with some consistent data.
|
||||
If we are testing our code with the same data each time,
|
||||
we can trust our tests to yeild reliable results.
|
||||
In Silverstripe we define this data via 'fixtures' (so called because of their fixed nature).
|
||||
The `[api:SapphireTest]` class takes care of populating a test database with data from these fixtures -
|
||||
all we have to do is define them, and we have a few ways in which we can do this.
|
||||
You will often find the need to test your functionality with some consistent
|
||||
data. If we are testing our code with the same data each time, we can trust our
|
||||
tests to yield reliable results.
|
||||
|
||||
In Silverstripe we define this data via 'fixtures' (so called because of their
|
||||
fixed nature). The `[api:SapphireTest]` class takes care of populating a test
|
||||
database with data from these fixtures - all we have to do is define them, and
|
||||
we have a few ways in which we can do this.
|
||||
|
||||
## YAML Fixtures
|
||||
|
||||
YAML is a markup language which is deliberately simple and easy to read,
|
||||
so it is ideal for fixture generation.
|
||||
YAML is a markup language which is deliberately simple and easy to read, so it
|
||||
is ideal for fixture generation.
|
||||
|
||||
Say we have the following two DataObjects:
|
||||
|
||||
:::php
|
||||
class Player extends DataObject {
|
||||
static $db = array (
|
||||
|
||||
private static $db = array (
|
||||
'Name' => 'Varchar(255)'
|
||||
);
|
||||
|
||||
static $has_one = array(
|
||||
private static $has_one = array(
|
||||
'Team' => 'Team'
|
||||
);
|
||||
}
|
||||
|
||||
class Team extends DataObject {
|
||||
static $db = array (
|
||||
|
||||
private static $db = array (
|
||||
'Name' => 'Varchar(255)',
|
||||
'Origin' => 'Varchar(255)'
|
||||
);
|
||||
|
||||
static $has_many = array(
|
||||
private static $has_many = array(
|
||||
'Players' => 'Player'
|
||||
);
|
||||
}
|
||||
@ -59,31 +63,42 @@ We can represent multiple instances of them in `YAML` as follows:
|
||||
Name: The Crusaders
|
||||
Origin: Bay of Plenty
|
||||
|
||||
Our `YAML` is broken up into three levels, signified by the indentation of each line.
|
||||
In the first level of indentation, `Player` and `Team`,
|
||||
represent the class names of the objects we want to be created for the test.
|
||||
Our `YAML` is broken up into three levels, signified by the indentation of each
|
||||
line. In the first level of indentation, `Player` and `Team`, represent the
|
||||
class names of the objects we want to be created for the test.
|
||||
|
||||
The second level, `john`/`joe`/`jack` & `hurricanes`/`crusaders`, are identifiers.
|
||||
These are what you pass as the second argument of `SapphireTest::objFromFixture()`.
|
||||
Each identifier you specify represents a new object.
|
||||
The second level, `john`/`joe`/`jack` & `hurricanes`/`crusaders`, are
|
||||
identifiers. These are what you pass as the second argument of
|
||||
`SapphireTest::objFromFixture()`. Each identifier you specify represents a new
|
||||
object.
|
||||
|
||||
The third and final level represents each individual object's fields.
|
||||
A field can either be provided with raw data (such as the Names for our Players),
|
||||
or we can define a relationship, as seen by the fields prefixed with `=>`.
|
||||
|
||||
Each one of our Players has a relationship to a Team,
|
||||
this is shown with the `Team` field for each `Player` being set to `=>Team.` followed by a team name.
|
||||
Take the player John for example, his team is the Hurricanes which is represented by `=>Team.hurricanes`.
|
||||
This is tells the system that we want to set up a relationship for the `Player` object `john` with the `Team` object `hurricanes`.
|
||||
A field can either be provided with raw data (such as the names for our
|
||||
Players), or we can define a relationship, as seen by the fields prefixed with
|
||||
`=>`.
|
||||
|
||||
Each one of our Players has a relationship to a Team, this is shown with the
|
||||
`Team` field for each `Player` being set to `=>Team.` followed by a team name.
|
||||
|
||||
Take the player John for example, his team is the Hurricanes which is
|
||||
represented by `=>Team.hurricanes`.
|
||||
|
||||
This is tells the system that we want to set up a relationship for the `Player`
|
||||
object `john` with the `Team` object `hurricanes`.
|
||||
|
||||
It will populate the `Player` object's `TeamID` with the ID of `hurricanes`,
|
||||
just like how a relationship is always set up.
|
||||
|
||||
<div class="hint" markdown='1'>
|
||||
Note that we use the name of the relationship (Team), and not the name of the database field (TeamID).
|
||||
Note that we use the name of the relationship (Team), and not the name of the
|
||||
database field (TeamID).
|
||||
</div>
|
||||
|
||||
This style of relationship declaration can be used for both a `has-one` and a `many-many` relationship.
|
||||
For `many-many` relationships, we specify a comma separated list of values.
|
||||
This style of relationship declaration can be used for both a `has-one` and a
|
||||
`many-many` relationship. For `many-many` relationships, we specify a comma
|
||||
separated list of values.
|
||||
|
||||
For example we could just as easily write the above as:
|
||||
|
||||
:::yml
|
||||
@ -104,27 +119,97 @@ For example we could just as easily write the above as:
|
||||
Origin: Bay of Plenty
|
||||
Players: =>Player.joe,=>Player.jack
|
||||
|
||||
A crucial thing to note is that **the YAML file specifies DataObjects, not database records**.
|
||||
The database is populated by instantiating DataObject objects and setting the fields declared in the YML,
|
||||
then calling write() on those objects.
|
||||
This means that any `onBeforeWrite()` or default value logic will be executed as part of the test.
|
||||
The reasoning behind this is to allow us to test the `onBeforeWrite` functionality of our objects.
|
||||
You can see this kind of testing in action in the `testURLGeneration()` test from the example in
|
||||
[Creating a SilverStripe Test](creating-a-silverstripe-test).
|
||||
A crucial thing to note is that **the YAML file specifies DataObjects, not
|
||||
database records**.
|
||||
|
||||
The database is populated by instantiating DataObject objects and setting the
|
||||
fields declared in the YML, then calling write() on those objects. This means
|
||||
that any `onBeforeWrite()` or default value logic will be executed as part of
|
||||
the test. The reasoning behind this is to allow us to test the `onBeforeWrite`
|
||||
functionality of our objects.
|
||||
|
||||
You can see this kind of testing in action in the `testURLGeneration()` test
|
||||
from the example in [Creating a SilverStripe Test](creating-a-silverstripe-test).
|
||||
|
||||
### Defining many_many_extraFields
|
||||
|
||||
`many_many` relations can have additional database fields attached to the
|
||||
relationship. For example we may want to declare the role each player has in the
|
||||
team.
|
||||
|
||||
:::php
|
||||
class Player extends DataObject {
|
||||
|
||||
private static $db = array (
|
||||
'Name' => 'Varchar(255)'
|
||||
);
|
||||
|
||||
private static $belongs_many_many = array(
|
||||
'Teams' => 'Team'
|
||||
);
|
||||
}
|
||||
|
||||
class Team extends DataObject {
|
||||
|
||||
private static $db = array (
|
||||
'Name' => 'Varchar(255)'
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
'Players' => 'Player'
|
||||
);
|
||||
|
||||
private static $many_many_extraFields = array(
|
||||
"Players" => array(
|
||||
"Role" => "Varchar"
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
To provide the value for the many_many_extraField use the YAML list syntax.
|
||||
|
||||
:::yml
|
||||
Player:
|
||||
john:
|
||||
Name: John
|
||||
joe:
|
||||
Name: Joe
|
||||
jack:
|
||||
Name: Jack
|
||||
Team:
|
||||
hurricanes:
|
||||
Name: The Hurricanes
|
||||
Players:
|
||||
- =>Player.john:
|
||||
Role: Captain
|
||||
|
||||
crusaders:
|
||||
Name: The Crusaders
|
||||
Players:
|
||||
- =>Player.joe:
|
||||
Role: Captain
|
||||
- =>Player.jack:
|
||||
Role: Winger
|
||||
|
||||
## Test Class Definition
|
||||
|
||||
### Manual Object Creation
|
||||
|
||||
Sometimes statically defined fixtures don't suffice. This could be because of the complexity of the tested model,
|
||||
or because the YAML format doesn't allow you to modify all of a model's state.
|
||||
One common example here is publishing pages (page fixtures aren't published by default).
|
||||
Sometimes statically defined fixtures don't suffice. This could be because of
|
||||
the complexity of the tested model, or because the YAML format doesn't allow you
|
||||
to modify all of a model's state.
|
||||
|
||||
One common example here is publishing pages (page fixtures aren't published by
|
||||
default).
|
||||
|
||||
You can always resort to creating objects manually in the test setup phase.
|
||||
Since the test database is cleared on every test method, you'll get a fresh set of test instances every time.
|
||||
|
||||
Since the test database is cleared on every test method, you'll get a fresh set
|
||||
of test instances every time.
|
||||
|
||||
:::php
|
||||
class SiteTreeTest extends SapphireTest {
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
@ -140,16 +225,20 @@ Since the test database is cleared on every test method, you'll get a fresh set
|
||||
|
||||
### Why Factories?
|
||||
|
||||
While manually defined fixtures provide full flexibility, they offer very little in terms of structure and convention.
|
||||
Alternatively, you can use the `[api:FixtureFactory]` class, which allows you to set default values,
|
||||
callbacks on object creation, and dynamic/lazy value setting.
|
||||
While manually defined fixtures provide full flexibility, they offer very little
|
||||
in terms of structure and convention. Alternatively, you can use the
|
||||
`[api:FixtureFactory]` class, which allows you to set default values, callbacks
|
||||
on object creation, and dynamic/lazy value setting.
|
||||
|
||||
<div class="hint" markdown='1'>
|
||||
SapphireTest uses FixtureFactory under the hood when it is provided with YAML based fixtures.
|
||||
SapphireTest uses FixtureFactory under the hood when it is provided with YAML
|
||||
based fixtures.
|
||||
</div>
|
||||
|
||||
The idea is that rather than instantiating objects directly, we'll have a factory class for them.
|
||||
This factory can have so called "blueprints" defined on it, which tells the factory how to instantiate an object of a specific type. Blueprints need a name, which is usually set to the class it creates.
|
||||
The idea is that rather than instantiating objects directly, we'll have a
|
||||
factory class for them. This factory can have so called "blueprints" defined on
|
||||
it, which tells the factory how to instantiate an object of a specific type.
|
||||
Blueprints need a name, which is usually set to the class it creates.
|
||||
|
||||
### Usage
|
||||
|
||||
|
@ -45,7 +45,7 @@ When designing your site you should only need to modify the *mysite*, *themes* a
|
||||
|
||||
![](_images/tutorial1_cms-basic.jpg)
|
||||
|
||||
The CMS is the area in which you can manage your site content. You can access the cms at http://localhost/your_site_name/admin (or http://yourdomain.com/admin if you are using you own domain name). You
|
||||
The CMS is the area in which you can manage your site content. You can access the cms at http://localhost/your_site_name/admin (or http://yourdomain.com/admin if you are using your own domain name). You
|
||||
will be presented with a login screen. Login using the details you provided at installation. After logging in you
|
||||
should see the CMS interface with a list of the pages currently on your website (the site tree). Here you can add, delete and reorganize pages. If you need to delete, publish, or unpublish a page, first check "multi-selection" at the top. You will then be able to perform actions on any checked files using the "Actions" dropdown. Clicking on a page will open it in the page editing interface pictured below (we've entered some test content).
|
||||
|
||||
|
@ -732,7 +732,7 @@ class File extends DataObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not change the filesystem itself, please use {@link write()} for this.
|
||||
* Caution: this does not change the location of the file on the filesystem.
|
||||
*/
|
||||
public function setFilename($val) {
|
||||
$this->setField('Filename', $val);
|
||||
|
@ -57,20 +57,23 @@ class Folder extends File {
|
||||
|
||||
$parentID = 0;
|
||||
$item = null;
|
||||
$filter = FileNameFilter::create();
|
||||
foreach($parts as $part) {
|
||||
if(!$part) continue; // happens for paths with a trailing slash
|
||||
$item = DataObject::get_one(
|
||||
"Folder",
|
||||
sprintf(
|
||||
"\"Name\" = '%s' AND \"ParentID\" = %d",
|
||||
Convert::raw2sql($part),
|
||||
(int)$parentID
|
||||
)
|
||||
);
|
||||
|
||||
// Ensure search includes folders with illegal characters removed, but
|
||||
// err in favour of matching existing folders if $folderPath
|
||||
// includes illegal characters itself.
|
||||
$partSafe = $filter->filter($part);
|
||||
$item = Folder::get()->filter(array(
|
||||
'ParentID' => $parentID,
|
||||
'Name' => array($partSafe, $part)
|
||||
))->first();
|
||||
|
||||
if(!$item) {
|
||||
$item = new Folder();
|
||||
$item->ParentID = $parentID;
|
||||
$item->Name = $part;
|
||||
$item->Name = $partSafe;
|
||||
$item->Title = $part;
|
||||
$item->write();
|
||||
}
|
||||
@ -460,7 +463,7 @@ class Folder extends File {
|
||||
* Get the children of this folder that are also folders.
|
||||
*/
|
||||
public function ChildFolders() {
|
||||
return DataObject::get("Folder", "\"ParentID\" = " . (int)$this->ID);
|
||||
return Folder::get()->filter('ParentID', $this->ID);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,19 +134,24 @@ class Upload extends Controller {
|
||||
$file = $nameFilter->filter($tmpFile['name']);
|
||||
$fileName = basename($file);
|
||||
|
||||
$relativeFilePath = $parentFolder->getRelativePath() . "/$fileName";
|
||||
$relativeFilePath = $parentFolder ? $parentFolder->getRelativePath() . "$fileName" : $fileName;
|
||||
|
||||
// Create a new file record (or try to retrieve an existing one)
|
||||
if(!$this->file) {
|
||||
$fileClass = File::get_class_for_file_extension(pathinfo($tmpFile['name'], PATHINFO_EXTENSION));
|
||||
if($this->replaceFile) {
|
||||
$this->file = File::get()
|
||||
$this->file = new $fileClass();
|
||||
}
|
||||
if(!$this->file->ID && $this->replaceFile) {
|
||||
$fileClass = $this->file->class;
|
||||
$file = File::get()
|
||||
->filter(array(
|
||||
'ClassName' => $fileClass,
|
||||
'Name' => $fileName,
|
||||
'ParentID' => $parentFolder ? $parentFolder->ID : 0
|
||||
))->First();
|
||||
if($file) {
|
||||
$this->file = $file;
|
||||
}
|
||||
if(!$this->file) $this->file = new $fileClass();
|
||||
}
|
||||
|
||||
// if filename already exists, version the filename (e.g. test.gif to test1.gif)
|
||||
|
@ -117,15 +117,15 @@ class DropdownField extends FormField {
|
||||
protected $disabledItems = array();
|
||||
|
||||
/**
|
||||
* Creates a new dropdown field.
|
||||
* @param $name The field name
|
||||
* @param $title The field title
|
||||
* @param $source An map of the dropdown items
|
||||
* @param $value The current value
|
||||
* @param $form The parent form
|
||||
* @param $emptyString mixed Add an empty selection on to of the {@link $source}-Array (can also be boolean, which
|
||||
* results in an empty string). Argument is deprecated in 3.1, please use
|
||||
* {@link setEmptyString()} and/or {@link setHasEmptyDefault(true)} instead.
|
||||
* @param string $name The field name
|
||||
* @param string $title The field title
|
||||
* @param array $source An map of the dropdown items
|
||||
* @param string $value The current value
|
||||
* @param Form $form The parent form
|
||||
* @param string|bool $emptyString Add an empty selection on to of the {@link $source}-Array (can also be
|
||||
* boolean, which results in an empty string). Argument is deprecated
|
||||
* in 3.1, please use{@link setEmptyString()} and/or
|
||||
* {@link setHasEmptyDefault(true)} instead.
|
||||
*/
|
||||
public function __construct($name, $title=null, $source=array(), $value='', $form=null, $emptyString=null) {
|
||||
$this->setSource($source);
|
||||
|
@ -698,7 +698,7 @@ class Form extends RequestHandler {
|
||||
* @return String
|
||||
*/
|
||||
public function getAttribute($name) {
|
||||
return @$this->attributes[$name];
|
||||
if(isset($this->attributes[$name])) return $this->attributes[$name];
|
||||
}
|
||||
|
||||
public function getAttributes() {
|
||||
@ -1093,13 +1093,17 @@ class Form extends RequestHandler {
|
||||
return $this->messageType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getMessageFromSession() {
|
||||
if($this->message || $this->messageType) {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->message = Session::get("FormInfo.{$this->FormName()}.formError.message");
|
||||
$this->messageType = Session::get("FormInfo.{$this->FormName()}.formError.type");
|
||||
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -416,8 +416,7 @@ class FormField extends RequestHandler {
|
||||
*/
|
||||
public function getAttribute($name) {
|
||||
$attrs = $this->getAttributes();
|
||||
|
||||
return @$attrs[$name];
|
||||
if(isset($attrs[$name])) return $attrs[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,10 +62,10 @@ class HtmlEditorSanitiser {
|
||||
foreach(explode(',', $validElements) as $validElement) {
|
||||
if(preg_match($elementRuleRegExp, $validElement, $matches)) {
|
||||
|
||||
$prefix = @$matches[1];
|
||||
$elementName = @$matches[2];
|
||||
$outputName = @$matches[3];
|
||||
$attrData = @$matches[4];
|
||||
$prefix = isset($matches[1]) ? $matches[1] : null;
|
||||
$elementName = isset($matches[2]) ? $matches[2] : null;
|
||||
$outputName = isset($matches[3]) ? $matches[3] : null;
|
||||
$attrData = isset($matches[4]) ? $matches[4] : null;
|
||||
|
||||
// Create the new element
|
||||
$element = new stdClass();
|
||||
@ -91,10 +91,10 @@ class HtmlEditorSanitiser {
|
||||
if(preg_match($attrRuleRegExp, $attr, $matches)) {
|
||||
$attr = new stdClass();
|
||||
|
||||
$attrType = @$matches[1];
|
||||
$attrName = str_replace('::', ':', @$matches[2]);
|
||||
$prefix = @$matches[3];
|
||||
$value = @$matches[4];
|
||||
$attrType = isset($matches[1]) ? $matches[1] : null;
|
||||
$attrName = isset($matches[2]) ? str_replace('::', ':', $matches[2]) : null;
|
||||
$prefix = isset($matches[3]) ? $matches[3] : null;
|
||||
$value = isset($matches[4]) ? $matches[4] : null;
|
||||
|
||||
// Required
|
||||
if($attrType === '!') {
|
||||
|
@ -1297,11 +1297,15 @@ class UploadField extends FileField {
|
||||
$nameFilter = FileNameFilter::create();
|
||||
$filteredFile = basename($nameFilter->filter($originalFile));
|
||||
|
||||
// Resolve expected folder name
|
||||
$folderName = $this->getFolderName();
|
||||
$folder = Folder::find_or_make($folderName);
|
||||
$parentPath = BASE_PATH."/".$folder->getFilename();
|
||||
|
||||
// check if either file exists
|
||||
$folder = $this->getFolderName();
|
||||
$exists = false;
|
||||
foreach(array($originalFile, $filteredFile) as $file) {
|
||||
if(file_exists(ASSETS_PATH."/$folder/$file")) {
|
||||
if(file_exists($parentPath.$file)) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
|
@ -616,7 +616,8 @@ class GridField extends FormField {
|
||||
public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) {
|
||||
$html = '';
|
||||
$data = $request->requestVars();
|
||||
$fieldData = @$data[$this->getName()];
|
||||
$name = $this->getName();
|
||||
$fieldData = isset($data[$name]) ? $data[$name] : null;
|
||||
|
||||
// Update state from client
|
||||
$state = $this->getState(false);
|
||||
|
@ -16,7 +16,7 @@
|
||||
(function($){
|
||||
|
||||
var decodePath = function(str) {
|
||||
return str.replace(/%2C/g,',').replace(/\&/g, '&').trim();
|
||||
return str.replace(/%2C/g,',').replace(/\&/g, '&').replace(/^\s+|\s+$/g, '');
|
||||
};
|
||||
|
||||
$.extend({
|
||||
|
@ -212,13 +212,15 @@ abstract class SS_Database {
|
||||
switch($changes['command']) {
|
||||
case 'create':
|
||||
$this->createTable($tableName, $changes['newFields'], $changes['newIndexes'], $changes['options'],
|
||||
@$changes['advancedOptions']);
|
||||
isset($changes['advancedOptions']) ? $changes['advancedOptions'] : null
|
||||
);
|
||||
break;
|
||||
|
||||
case 'alter':
|
||||
$this->alterTable($tableName, $changes['newFields'], $changes['newIndexes'],
|
||||
$changes['alteredFields'], $changes['alteredIndexes'], $changes['alteredOptions'],
|
||||
@$changes['advancedOptions']);
|
||||
isset($changes['advancedOptions']) ? $changes['advancedOptions'] : null
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -163,9 +163,13 @@ class SS_HTML4Value extends SS_HTMLValue {
|
||||
// Reset the document if we're in an invalid state for some reason
|
||||
if (!$this->isValid()) $this->setDocument(null);
|
||||
|
||||
return @$this->getDocument()->loadHTML(
|
||||
$errorState = libxml_use_internal_errors(true);
|
||||
$result = $this->getDocument()->loadHTML(
|
||||
'<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head>' .
|
||||
"<body>$content</body></html>"
|
||||
);
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($errorState);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ class URLSegmentFilter extends Object {
|
||||
'/[_.]+/u' => '-', // underscores and dots to dashes
|
||||
'/[^A-Za-z0-9\-]+/u' => '', // remove non-ASCII chars, only allow alphanumeric and dashes
|
||||
'/[\-]{2,}/u' => '-', // remove duplicate dashes
|
||||
'/^[\-_]/u' => '', // Remove all leading dashes or underscores
|
||||
'/^[\-]+/u' => '', // Remove all leading dashes
|
||||
'/[\-]+$/u' => '' // Remove all trailing dashes
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -229,8 +229,8 @@ class ShortcodeParser {
|
||||
'text' => $match[0][0],
|
||||
's' => $match[0][1],
|
||||
'e' => $match[0][1] + strlen($match[0][0]),
|
||||
'open' => @$match['open'][0],
|
||||
'close' => @$match['close'][0],
|
||||
'open' => isset($match['open'][0]) ? $match['open'][0] : null,
|
||||
'close' => isset($match['close'][0]) ? $match['close'][0] : null,
|
||||
'attrs' => $attrs,
|
||||
'content' => '',
|
||||
'escaped' => !empty($match['oesc'][0]) || !empty($match['cesc1'][0]) || !empty($match['cesc2'][0])
|
||||
|
@ -105,6 +105,11 @@ class ChangePasswordForm extends Form {
|
||||
// TODO Add confirmation message to login redirect
|
||||
Session::clear('AutoLoginHash');
|
||||
|
||||
// Clear locked out status
|
||||
$member->LockedOutUntil = null;
|
||||
$member->FailedLoginCount = null;
|
||||
$member->write();
|
||||
|
||||
if (isset($_REQUEST['BackURL'])
|
||||
&& $_REQUEST['BackURL']
|
||||
// absolute redirection URLs may cause spoofing
|
||||
|
@ -1180,10 +1180,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* editing this member.
|
||||
*/
|
||||
public function getCMSFields() {
|
||||
require_once('Zend/Date.php');
|
||||
|
||||
$fields = parent::getCMSFields();
|
||||
require_once 'Zend/Date.php';
|
||||
|
||||
$self = $this;
|
||||
$this->beforeUpdateCMSFields(function($fields) use ($self) {
|
||||
$mainFields = $fields->fieldByName("Root")->fieldByName("Main")->Children;
|
||||
|
||||
$password = new ConfirmedPasswordField(
|
||||
@ -1194,7 +1194,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
true // showOnClick
|
||||
);
|
||||
$password->setCanBeEmpty(true);
|
||||
if(!$this->ID) $password->showOnClick = false;
|
||||
if( ! $self->ID) $password->showOnClick = false;
|
||||
$mainFields->replaceField('Password', $password);
|
||||
|
||||
$mainFields->replaceField('Locale', new DropdownField(
|
||||
@ -1210,11 +1210,14 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$mainFields->removeByName('PasswordExpiry');
|
||||
$mainFields->removeByName('LockedOutUntil');
|
||||
|
||||
if(!self::config()->lock_out_after_incorrect_logins) {
|
||||
if( ! $self->config()->lock_out_after_incorrect_logins) {
|
||||
$mainFields->removeByName('FailedLoginCount');
|
||||
}
|
||||
|
||||
$mainFields->removeByName('Salt');
|
||||
$mainFields->removeByName('NumVisit');
|
||||
|
||||
$mainFields->makeFieldReadonly('LastVisited');
|
||||
|
||||
$fields->removeByName('Subscriptions');
|
||||
|
||||
@ -1239,17 +1242,18 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// Add permission field (readonly to avoid complicated group assignment logic).
|
||||
// This should only be available for existing records, as new records start
|
||||
// with no permissions until they have a group assignment anyway.
|
||||
if($this->ID) {
|
||||
if($self->ID) {
|
||||
$permissionsField = new PermissionCheckboxSetField_Readonly(
|
||||
'Permissions',
|
||||
false,
|
||||
'Permission',
|
||||
'GroupID',
|
||||
// we don't want parent relationships, they're automatically resolved in the field
|
||||
$this->getManyManyComponents('Groups')
|
||||
$self->getManyManyComponents('Groups')
|
||||
);
|
||||
$fields->findOrMakeTab('Root.Permissions', singleton('Permission')->i18n_plural_name());
|
||||
$fields->addFieldToTab('Root.Permissions', $permissionsField);
|
||||
@ -1259,44 +1263,42 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$permissionsTab = $fields->fieldByName("Root")->fieldByName('Permissions');
|
||||
if($permissionsTab) $permissionsTab->addExtraClass('readonly');
|
||||
|
||||
$localDateFormat = Zend_Locale_Data::getContent(new Zend_Locale($this->Locale), 'date', 'short');
|
||||
// Ensure short dates always use four digit dates to avoid confusion
|
||||
$localDateFormat = preg_replace('/(^|[^y])yy($|[^y])/', '$1yyyy$2', $localDateFormat);
|
||||
$defaultDateFormat = Zend_Locale_Format::getDateFormat(new Zend_Locale($self->Locale));
|
||||
$dateFormatMap = array(
|
||||
$this->DateFormat => Zend_Date::now()->toString($this->DateFormat),
|
||||
$localDateFormat => Zend_Date::now()->toString($localDateFormat),
|
||||
'MMM d, yyyy' => Zend_Date::now()->toString('MMM d, yyyy'),
|
||||
'yyyy/MM/dd' => Zend_Date::now()->toString('yyyy/MM/dd'),
|
||||
'MM/dd/yyyy' => Zend_Date::now()->toString('MM/dd/yyyy'),
|
||||
'dd/MM/yyyy' => Zend_Date::now()->toString('dd/MM/yyyy'),
|
||||
);
|
||||
$dateFormatMap[$defaultDateFormat] = Zend_Date::now()->toString($defaultDateFormat)
|
||||
. sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
|
||||
$mainFields->push(
|
||||
$dateFormatField = new MemberDatetimeOptionsetField(
|
||||
'DateFormat',
|
||||
$this->fieldLabel('DateFormat'),
|
||||
$self->fieldLabel('DateFormat'),
|
||||
$dateFormatMap
|
||||
)
|
||||
);
|
||||
$dateFormatField->setValue($this->DateFormat);
|
||||
$dateFormatField->setValue($self->DateFormat);
|
||||
|
||||
$localTimeFormat = Zend_Locale_Format::getTimeFormat(new Zend_Locale($this->Locale));
|
||||
$defaultTimeFormat = Zend_Locale_Format::getTimeFormat(new Zend_Locale($self->Locale));
|
||||
$timeFormatMap = array(
|
||||
$this->TimeFormat => Zend_Date::now()->toString($this->TimeFormat),
|
||||
$localTimeFormat => Zend_Date::now()->toString($localTimeFormat),
|
||||
'h:mm a' => Zend_Date::now()->toString('h:mm a'),
|
||||
'H:mm' => Zend_Date::now()->toString('H:mm'),
|
||||
);
|
||||
$timeFormatMap[$defaultTimeFormat] = Zend_Date::now()->toString($defaultTimeFormat)
|
||||
. sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
|
||||
$mainFields->push(
|
||||
$timeFormatField = new MemberDatetimeOptionsetField(
|
||||
'TimeFormat',
|
||||
$this->fieldLabel('TimeFormat'),
|
||||
$self->fieldLabel('TimeFormat'),
|
||||
$timeFormatMap
|
||||
)
|
||||
);
|
||||
$timeFormatField->setValue($this->TimeFormat);
|
||||
$timeFormatField->setValue($self->TimeFormat);
|
||||
});
|
||||
|
||||
$this->extend('updateCMSFields', $fields);
|
||||
|
||||
return $fields;
|
||||
return parent::getCMSFields();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Log-in form for the "member" authentication method
|
||||
* Log-in form for the "member" authentication method.
|
||||
*
|
||||
* Available extension points:
|
||||
* - "authenticationFailed": Called when login was not successful.
|
||||
* Arguments: $data containing the form submission
|
||||
* - "forgotPassword": Called before forgot password logic kicks in,
|
||||
* allowing extensions to "veto" execution by returning FALSE.
|
||||
* Arguments: $member containing the detected Member record
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage security
|
||||
*/
|
||||
@ -256,9 +264,12 @@ JS
|
||||
|
||||
|
||||
/**
|
||||
* Forgot password form handler method
|
||||
*
|
||||
* This method is called when the user clicks on "I've lost my password"
|
||||
* Forgot password form handler method.
|
||||
* Called when the user clicks on "I've lost my password".
|
||||
* Extensions can use the 'forgotPassword' method to veto executing
|
||||
* the logic, by returning FALSE. In this case, the user will be redirected back
|
||||
* to the form without further action. It is recommended to set a message
|
||||
* in the form detailing why the action was denied.
|
||||
*
|
||||
* @param array $data Submitted data
|
||||
*/
|
||||
@ -267,6 +278,12 @@ JS
|
||||
$SQL_email = $SQL_data['Email'];
|
||||
$member = DataObject::get_one('Member', "\"Email\" = '{$SQL_email}'");
|
||||
|
||||
// Allow vetoing forgot password requests
|
||||
$results = $this->extend('forgotPassword', $member);
|
||||
if($results && is_array($results) && in_array(false, $results, true)) {
|
||||
return $this->controller->redirect('Security/lostpassword');
|
||||
}
|
||||
|
||||
if($member) {
|
||||
$token = $member->generateAutologinTokenAndStoreHash();
|
||||
|
||||
|
@ -184,14 +184,14 @@ class CmsUiContext extends BehatContext {
|
||||
* @When /^I expand the "([^"]*)" CMS Panel$/
|
||||
*/
|
||||
public function iExpandTheCmsPanel() {
|
||||
// TODO Make dynamic, currently hardcoded to first panel
|
||||
//Tries to find the first visiable toggle in the page
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$panel_toggle_element = $page->find('css', '.cms-content > .cms-panel > .cms-panel-toggle > .toggle-expand');
|
||||
assertNotNull($panel_toggle_element, 'Panel toggle not found');
|
||||
|
||||
if ($panel_toggle_element->isVisible()) {
|
||||
$panel_toggle_element->click();
|
||||
$toggle_elements = $page->findAll('css', '.toggle-expand');
|
||||
assertNotNull($toggle_elements, 'Panel toggle not found');
|
||||
foreach($toggle_elements as $toggle){
|
||||
if($toggle->isVisible()){
|
||||
$toggle->click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
BIN
tests/behat/features/files/document.pdf
Normal file
BIN
tests/behat/features/files/document.pdf
Normal file
Binary file not shown.
@ -12,6 +12,58 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
'FixtureFactoryTest_DataObjectRelation'
|
||||
);
|
||||
|
||||
public function testCreateWithRelationshipExtraFields() {
|
||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||
|
||||
$relation1 = new FixtureFactoryTest_DataObjectRelation();
|
||||
$relation1->write();
|
||||
$relation2 = new FixtureFactoryTest_DataObjectRelation();
|
||||
$relation2->write();
|
||||
|
||||
// in YAML these look like
|
||||
// RelationName:
|
||||
// - =>Relational.obj:
|
||||
// ExtraFieldName: test
|
||||
// - =>..
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' =>
|
||||
array(
|
||||
array(
|
||||
"=>FixtureFactoryTest_DataObjectRelation.relation1" => array(),
|
||||
"Label" => 'This is a label for relation 1'
|
||||
),
|
||||
array(
|
||||
"=>FixtureFactoryTest_DataObjectRelation.relation2" => array(),
|
||||
"Label" => 'This is a label for relation 2'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||
'relation1' => $relation1->ID,
|
||||
'relation2' => $relation2->ID
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(2, $obj->ManyMany()->Count());
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID));
|
||||
|
||||
$this->assertEquals(
|
||||
array('Label' => 'This is a label for relation 1'),
|
||||
$obj->ManyMany()->getExtraData('ManyMany', $relation1->ID)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('Label' => 'This is a label for relation 2'),
|
||||
$obj->ManyMany()->getExtraData('ManyMany', $relation2->ID)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function testCreateWithoutData() {
|
||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||
$obj = $blueprint->createObject('one');
|
||||
@ -28,6 +80,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$this->assertEquals('My Name', $obj->Name);
|
||||
}
|
||||
|
||||
|
||||
public function testCreateWithRelationship() {
|
||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||
|
||||
@ -127,7 +180,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$this->assertEquals(99, $obj->ID);
|
||||
}
|
||||
|
||||
function testCallbackOnBeforeCreate() {
|
||||
public function testCallbackOnBeforeCreate() {
|
||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||
$this->_called = 0;
|
||||
$self = $this;
|
||||
@ -144,7 +197,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$this->_called = 0;
|
||||
}
|
||||
|
||||
function testCallbackOnAfterCreate() {
|
||||
public function testCallbackOnAfterCreate() {
|
||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||
$this->_called = 0;
|
||||
$self = $this;
|
||||
@ -161,7 +214,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$this->_called = 0;
|
||||
}
|
||||
|
||||
function testDefineWithDefaultCustomSetters() {
|
||||
public function testDefineWithDefaultCustomSetters() {
|
||||
$blueprint = new FixtureBlueprint(
|
||||
'FixtureFactoryTest_DataObject',
|
||||
null,
|
||||
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
@ -151,19 +152,37 @@ class FixtureFactoryTest extends SapphireTest {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class FixtureFactoryTest_DataObject extends DataObject implements TestOnly {
|
||||
|
||||
private static $db = array(
|
||||
"Name" => "Varchar"
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
"ManyMany" => "FixtureFactoryTest_DataObjectRelation"
|
||||
);
|
||||
|
||||
private static $many_many_extraFields = array(
|
||||
"ManyMany" => array(
|
||||
"Label" => "Varchar"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class FixtureFactoryTest_DataObjectRelation extends DataObject implements TestOnly {
|
||||
|
||||
private static $db = array(
|
||||
"Name" => "Varchar"
|
||||
);
|
||||
|
||||
private static $belongs_many_many = array(
|
||||
"TestParent" => "FixtureFactoryTest_DataObject"
|
||||
);
|
||||
|
@ -338,4 +338,21 @@ class FolderTest extends SapphireTest {
|
||||
))->count());
|
||||
}
|
||||
|
||||
public function testIllegalFilenames() {
|
||||
|
||||
// Test that generating a filename with invalid characters generates a correctly named folder.
|
||||
$folder = Folder::find_or_make('/FolderTest/EN_US Lang');
|
||||
$this->assertEquals(ASSETS_DIR.'/FolderTest/EN-US-Lang/', $folder->getRelativePath());
|
||||
|
||||
// Test repeatitions of folder
|
||||
$folder2 = Folder::find_or_make('/FolderTest/EN_US Lang');
|
||||
$this->assertEquals($folder->ID, $folder2->ID);
|
||||
|
||||
$folder3 = Folder::find_or_make('/FolderTest/EN--US_L!ang');
|
||||
$this->assertEquals($folder->ID, $folder3->ID);
|
||||
|
||||
$folder4 = Folder::find_or_make('/FolderTest/EN-US-Lang');
|
||||
$this->assertEquals($folder->ID, $folder4->ID);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -378,6 +378,93 @@ class UploadTest extends SapphireTest {
|
||||
$file2->delete();
|
||||
}
|
||||
|
||||
public function testReplaceFileWithLoadIntoFile() {
|
||||
// create tmp file
|
||||
$tmpFileName = 'UploadTest-testUpload.txt';
|
||||
$tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName;
|
||||
$tmpFileContent = '';
|
||||
for ($i = 0; $i < 10000; $i++)
|
||||
$tmpFileContent .= '0';
|
||||
file_put_contents($tmpFilePath, $tmpFileContent);
|
||||
|
||||
// emulates the $_FILES array
|
||||
$tmpFile = array(
|
||||
'name' => $tmpFileName,
|
||||
'type' => 'text/plaintext',
|
||||
'size' => filesize($tmpFilePath),
|
||||
'tmp_name' => $tmpFilePath,
|
||||
'extension' => 'txt',
|
||||
'error' => UPLOAD_ERR_OK,
|
||||
);
|
||||
|
||||
// Make sure there are none here, otherwise they get renamed incorrectly for the test.
|
||||
$this->deleteTestUploadFiles("/UploadTest-testUpload.*/");
|
||||
|
||||
$v = new UploadTest_Validator();
|
||||
|
||||
// test upload into default folder
|
||||
$u = new Upload();
|
||||
$u->setValidator($v);
|
||||
$u->load($tmpFile);
|
||||
$file = $u->getFile();
|
||||
|
||||
$this->assertEquals(
|
||||
'UploadTest-testUpload.txt',
|
||||
$file->Name,
|
||||
'File is uploaded without extension'
|
||||
);
|
||||
$this->assertFileExists(
|
||||
BASE_PATH . '/' . $file->getFilename(),
|
||||
'File exists'
|
||||
);
|
||||
|
||||
// replace=true
|
||||
$u = new Upload();
|
||||
$u->setValidator($v);
|
||||
$u->setReplaceFile(true);
|
||||
$u->loadIntoFile($tmpFile, new File());
|
||||
$file2 = $u->getFile();
|
||||
$this->assertEquals(
|
||||
'UploadTest-testUpload.txt',
|
||||
$file2->Name,
|
||||
'File does not receive new name'
|
||||
);
|
||||
$this->assertFileExists(
|
||||
BASE_PATH . '/' . $file2->getFilename(),
|
||||
'File exists'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$file->ID,
|
||||
$file2->ID,
|
||||
'File database record is the same'
|
||||
);
|
||||
|
||||
// replace=false
|
||||
$u = new Upload();
|
||||
$u->setValidator($v);
|
||||
$u->setReplaceFile(false);
|
||||
$u->loadIntoFile($tmpFile, new File());
|
||||
$file3 = $u->getFile();
|
||||
$this->assertEquals(
|
||||
'UploadTest-testUpload2.txt',
|
||||
$file3->Name,
|
||||
'File does receive new name'
|
||||
);
|
||||
$this->assertFileExists(
|
||||
BASE_PATH . '/' . $file3->getFilename(),
|
||||
'File exists'
|
||||
);
|
||||
$this->assertGreaterThan(
|
||||
$file2->ID,
|
||||
$file3->ID,
|
||||
'File database record is not the same'
|
||||
);
|
||||
|
||||
$file->delete();
|
||||
$file2->delete();
|
||||
$file3->delete();
|
||||
}
|
||||
|
||||
}
|
||||
class UploadTest_Validator extends Upload_Validator implements TestOnly {
|
||||
|
||||
|
@ -342,7 +342,6 @@ class SQLQueryTest extends SapphireTest {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function testSetWhereAny() {
|
||||
$query = new SQLQuery();
|
||||
$query->setFrom('MyTable');
|
||||
@ -352,7 +351,6 @@ class SQLQueryTest extends SapphireTest {
|
||||
}
|
||||
|
||||
public function testSelectFirst() {
|
||||
|
||||
// Test first from sequence
|
||||
$query = new SQLQuery();
|
||||
$query->setFrom('"SQLQueryTest_DO"');
|
||||
@ -398,7 +396,6 @@ class SQLQueryTest extends SapphireTest {
|
||||
}
|
||||
|
||||
public function testSelectLast() {
|
||||
|
||||
// Test last in sequence
|
||||
$query = new SQLQuery();
|
||||
$query->setFrom('"SQLQueryTest_DO"');
|
||||
|
@ -41,8 +41,8 @@ class URLSegmentFilterTest extends SapphireTest {
|
||||
public function testReplacesCommonNonAsciiCharacters() {
|
||||
$f = new URLSegmentFilter();
|
||||
$this->assertEquals(
|
||||
urlencode('aa1-'),
|
||||
$f->filter('Aa1~!@#$%^*()_`-=;\':"[]\{}|,./<>?')
|
||||
urlencode('aa1-a'),
|
||||
$f->filter('Aa1~!@#$%^*()_`-=;\':"[]\{}|,./<>?a')
|
||||
);
|
||||
}
|
||||
|
||||
@ -77,4 +77,14 @@ class URLSegmentFilterTest extends SapphireTest {
|
||||
$this->assertEquals('url-contains-dot', $filter->filter('url-contains.dot'));
|
||||
}
|
||||
|
||||
public function testRemoveLeadingDashes() {
|
||||
$filter = new URLSegmentFilter();
|
||||
$this->assertEquals('url-has-leading-dashes', $filter->filter('---url-has-leading-dashes'));
|
||||
}
|
||||
|
||||
public function testReplacesTrailingDashes() {
|
||||
$filter = new URLSegmentFilter();
|
||||
$this->assertEquals('url-has-trailing-dashes', $filter->filter('url-has-trailing-dashes--'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ class BasicAuthTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testBasicAuthEnabledWithoutLogin() {
|
||||
$origUser = @$_SERVER['PHP_AUTH_USER'];
|
||||
$origPw = @$_SERVER['PHP_AUTH_PW'];
|
||||
$origUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
|
||||
$origPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
|
||||
|
||||
unset($_SERVER['PHP_AUTH_USER']);
|
||||
unset($_SERVER['PHP_AUTH_PW']);
|
||||
@ -40,8 +40,8 @@ class BasicAuthTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testBasicAuthDoesntCallActionOrFurtherInitOnAuthFailure() {
|
||||
$origUser = @$_SERVER['PHP_AUTH_USER'];
|
||||
$origPw = @$_SERVER['PHP_AUTH_PW'];
|
||||
$origUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
|
||||
$origPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
|
||||
|
||||
unset($_SERVER['PHP_AUTH_USER']);
|
||||
unset($_SERVER['PHP_AUTH_PW']);
|
||||
@ -60,8 +60,8 @@ class BasicAuthTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testBasicAuthEnabledWithPermission() {
|
||||
$origUser = @$_SERVER['PHP_AUTH_USER'];
|
||||
$origPw = @$_SERVER['PHP_AUTH_PW'];
|
||||
$origUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
|
||||
$origPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
|
||||
|
||||
$_SERVER['PHP_AUTH_USER'] = 'user-in-mygroup@test.com';
|
||||
$_SERVER['PHP_AUTH_PW'] = 'wrongpassword';
|
||||
@ -83,8 +83,8 @@ class BasicAuthTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testBasicAuthEnabledWithoutPermission() {
|
||||
$origUser = @$_SERVER['PHP_AUTH_USER'];
|
||||
$origPw = @$_SERVER['PHP_AUTH_PW'];
|
||||
$origUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
|
||||
$origPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
|
||||
|
||||
$_SERVER['PHP_AUTH_USER'] = 'user-without-groups@test.com';
|
||||
$_SERVER['PHP_AUTH_PW'] = 'wrongpassword';
|
||||
|
@ -610,6 +610,22 @@ class MemberTest extends FunctionalTest {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that extensions using updateCMSFields() are applied correctly
|
||||
*/
|
||||
public function testUpdateCMSFields() {
|
||||
Member::add_extension('MemberTest_FieldsExtension');
|
||||
|
||||
$member = singleton('Member');
|
||||
$fields = $member->getCMSFields();
|
||||
|
||||
$this->assertNotNull($fields->dataFieldByName('Email'), 'Scaffolded fields are retained');
|
||||
$this->assertNull($fields->dataFieldByName('Salt'), 'Field modifications run correctly');
|
||||
$this->assertNotNull($fields->dataFieldByName('TestMemberField'), 'Extension is applied correctly');
|
||||
|
||||
Member::remove_extension('MemberTest_FieldsExtension');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all members are returned
|
||||
*/
|
||||
@ -847,6 +863,18 @@ class MemberTest_ViewingDeniedExtension extends DataExtension implements TestOnl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class MemberTest_FieldsExtension extends DataExtension implements TestOnly {
|
||||
|
||||
public function updateCMSFields(FieldList $fields) {
|
||||
$fields->addFieldToTab('Root.Main', new TextField('TestMemberField', 'Test'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
|
@ -213,6 +213,9 @@ class SecurityTest extends FunctionalTest {
|
||||
|
||||
public function testChangePasswordFromLostPassword() {
|
||||
$admin = $this->objFromFixture('Member', 'test');
|
||||
$admin->FailedLoginCount = 99;
|
||||
$admin->LockedOutUntil = SS_Datetime::now()->Format('Y-m-d H:i:s');
|
||||
$admin->write();
|
||||
|
||||
$this->assertNull($admin->AutoLoginHash, 'Hash is empty before lost password');
|
||||
|
||||
@ -243,6 +246,10 @@ class SecurityTest extends FunctionalTest {
|
||||
$goodResponse = $this->doTestLoginForm('sam@silverstripe.com' , 'changedPassword');
|
||||
$this->assertEquals(302, $goodResponse->getStatusCode());
|
||||
$this->assertEquals($this->idFromFixture('Member', 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
|
||||
$admin = DataObject::get_by_id('Member', $admin->ID, false);
|
||||
$this->assertNull($admin->LockedOutUntil);
|
||||
$this->assertEquals(0, $admin->FailedLoginCount);
|
||||
}
|
||||
|
||||
public function testRepeatedLoginAttemptsLockingPeopleOut() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user