mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge remote-tracking branch 'origin/3.1'
Conflicts: .travis.yml forms/FormField.php
This commit is contained in:
commit
25b6175e67
19
.travis.yml
19
.travis.yml
@ -4,7 +4,13 @@ php:
|
||||
- 5.3
|
||||
|
||||
env:
|
||||
- DB=MYSQL CORE_RELEASE=master
|
||||
global:
|
||||
- "ARTIFACTS_AWS_REGION=us-east-1"
|
||||
- "ARTIFACTS_S3_BUCKET=silverstripe-travis-artifacts"
|
||||
- secure: "DjwZKhY/c0wXppGmd8oEMiTV0ayfOXiCmi9Lg1aXoSXNnj+sjLmhYwhUWjehjR6IX0MRtzJG6v7V5Y+4nSGe+i+XIrBQnhPQ95Jrkm1gKofX2mznWTl9npQElNS1DXi58NLPbiB3qxHWGFBRAWmRQrsAouyZabkPnChnSa9ldOg="
|
||||
- secure: "UmbXCNLK0f2Dk+7qX8bOVcgIt4QhRvccoWvMUxaPtIU+95HCbG10eeCxvfOeBax+tHcRXmeCG4vM4tcuT/WoANkAma/VX74DylFjbWhks2tsKOcr2kjTrOwe6Q9CXOBjVAlcx0lnV/a+w83KARjXGnCrIbE7p7r4EDw31rkVufg="
|
||||
matrix:
|
||||
- DB=MYSQL CORE_RELEASE=master
|
||||
|
||||
matrix:
|
||||
include:
|
||||
@ -16,7 +22,7 @@ matrix:
|
||||
env: DB=MYSQL CORE_RELEASE=master
|
||||
- php: 5.5
|
||||
env: DB=MYSQL CORE_RELEASE=master
|
||||
- php: 5.3
|
||||
- php: 5.4
|
||||
env: DB=MYSQL CORE_RELEASE=master BEHAT_TEST=1
|
||||
|
||||
before_script:
|
||||
@ -26,16 +32,15 @@ before_script:
|
||||
- "if [ \"$BEHAT_TEST\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi"
|
||||
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/behat-extension; fi"
|
||||
- cd ~/builds/ss
|
||||
- php ~/travis-support/travis_setup_selenium.php --base-url http://localhost --if-env BEHAT_TEST
|
||||
- php ~/travis-support/travis_setup_apache.php --if-env BEHAT_TEST
|
||||
- php ~/travis-support/travis_setup_selenium.php --if-env BEHAT_TEST
|
||||
- php ~/travis-support/travis_setup_php54_webserver.php --if-env BEHAT_TEST
|
||||
|
||||
script:
|
||||
- "if [ \"$BEHAT_TEST\" = \"\" ]; then phpunit framework/tests; fi"
|
||||
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then vendor/bin/behat --tags '~@todo&&~@assets' @framework; fi"
|
||||
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then vendor/bin/behat @framework; fi"
|
||||
|
||||
after_failure:
|
||||
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then sudo cat /var/log/apache2/error.log; fi"
|
||||
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then sudo cat /var/log/apache2/access.log; fi"
|
||||
- php ~/travis-support/travis_upload_artifacts.php --if-env BEHAT_TEST,ARTIFACTS_AWS_SECRET_ACCESS_KEY --target-path $TRAVIS_REPO_SLUG/$TRAVIS_BUILD_ID/$TRAVIS_JOB_ID --artifacts-base-url https://s3.amazonaws.com/$ARTIFACTS_S3_BUCKET/
|
||||
|
||||
branches:
|
||||
except:
|
||||
|
@ -3,7 +3,11 @@ name: Oembed
|
||||
Oembed:
|
||||
providers:
|
||||
'http://*.youtube.com/watch*':
|
||||
'http://www.youtube.com/oembed/'
|
||||
http: 'http://www.youtube.com/oembed/',
|
||||
https: 'https://www.youtube.com/oembed/?scheme=https'
|
||||
'https://*.youtube.com/watch*':
|
||||
http: 'http://www.youtube.com/oembed/',
|
||||
https: 'https://www.youtube.com/oembed/?scheme=https'
|
||||
'http://*.flickr.com/*':
|
||||
'http://www.flickr.com/services/oembed/'
|
||||
'http://*.viddler.com/*':
|
||||
@ -13,12 +17,18 @@ Oembed:
|
||||
'http://*.hulu.com/watch/*':
|
||||
'http://www.hulu.com/api/oembed.json'
|
||||
'http://*.vimeo.com/*':
|
||||
'http://www.vimeo.com/api/oembed.json'
|
||||
'https://twitter.com/*':
|
||||
'https://api.twitter.com/1/statuses/oembed.json'
|
||||
http: 'http://www.vimeo.com/api/oembed.json',
|
||||
https: 'https://www.vimeo.com/api/oembed.json'
|
||||
'https://*.vimeo.com/*':
|
||||
http: 'http://www.vimeo.com/api/oembed.json',
|
||||
https: 'https://www.vimeo.com/api/oembed.json'
|
||||
'http://twitter.com/*':
|
||||
'https://api.twitter.com/1/statuses/oembed.json'
|
||||
http: 'https://api.twitter.com/1/statuses/oembed.json',
|
||||
https: 'https://api.twitter.com/1/statuses/oembed.json'
|
||||
'https://twitter.com/*':
|
||||
http: 'https://api.twitter.com/1/statuses/oembed.json',
|
||||
https: 'https://api.twitter.com/1/statuses/oembed.json'
|
||||
autodiscover:
|
||||
true
|
||||
enabled:
|
||||
true
|
||||
true
|
||||
|
@ -15,8 +15,9 @@ class CMSForm extends Form {
|
||||
* @return boolean
|
||||
*/
|
||||
public function validate() {
|
||||
$buttonClicked = $this->buttonClicked();
|
||||
return (
|
||||
in_array($this->buttonClicked()->actionName(), $this->getValidationExemptActions())
|
||||
($buttonClicked && in_array($buttonClicked->actionName(), $this->getValidationExemptActions()))
|
||||
|| parent::validate()
|
||||
);
|
||||
}
|
||||
|
@ -513,6 +513,7 @@ body.cms { overflow: hidden; }
|
||||
.cms-content-tools .field { /* Fields are more compressed in the sidebar compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ }
|
||||
.cms-content-tools .field label { float: none; width: auto; font-size: 11px; padding: 0 8px 4px 0; }
|
||||
.cms-content-tools .field .middleColumn { margin: 0; }
|
||||
.cms-content-tools .field .description { margin-left: 0; }
|
||||
.cms-content-tools .field input.text, .cms-content-tools .field select, .cms-content-tools .field textarea { padding: 5px; font-size: 11px; }
|
||||
.cms-content-tools .field.checkbox { padding: 0 0 8px; }
|
||||
.cms-content-tools .field.checkbox input { margin: 2px 0; }
|
||||
@ -846,6 +847,7 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
|
||||
.cms-tree.multiple li > a > .jstree-icon { display: none; }
|
||||
.cms-tree.multiple li > a > .jstree-icon.jstree-checkbox { display: inline-block; }
|
||||
.cms-tree.multiple li#record-0 > a .jstree-checkbox { display: none; }
|
||||
.cms-tree.jstree-loading li#record-0 > .jstree-icon { background: url(../images/throbber.gif) top left no-repeat; }
|
||||
.cms-tree a.jstree-loading .jstree-icon { background-image: none !important; }
|
||||
.cms-tree a.jstree-loading .jstree-pageicon { background: url(../images/throbber.gif) top left no-repeat; }
|
||||
|
||||
|
@ -60,6 +60,9 @@
|
||||
|
||||
var url = $(node).find('a:first').attr('href');
|
||||
if(url && url != '#') {
|
||||
// strip possible querystrings from the url to avoid duplicateing document.location.search
|
||||
url = url.split('?')[0];
|
||||
|
||||
// Deselect all nodes (will be reselected after load according to form state)
|
||||
self.jstree('deselect_all');
|
||||
self.jstree('uncheck_all');
|
||||
|
@ -46,8 +46,7 @@
|
||||
.jstree(this.getTreeConfig())
|
||||
.bind('loaded.jstree', function(e, data) {
|
||||
self.setIsLoaded(true);
|
||||
self.updateFromEditForm();
|
||||
self.css('visibility', 'visible');
|
||||
|
||||
// Add ajax settings after init period to avoid unnecessary initial ajax load
|
||||
// of existing tree in DOM - see load_node_html()
|
||||
data.inst._set_settings({'html_data': {'ajax': {
|
||||
@ -61,6 +60,9 @@
|
||||
return params;
|
||||
}
|
||||
}}});
|
||||
|
||||
self.updateFromEditForm();
|
||||
self.css('visibility', 'visible');
|
||||
|
||||
// Only show checkboxes with .multiple class
|
||||
data.inst.hide_checkboxes();
|
||||
@ -239,8 +241,8 @@
|
||||
* Creates a new node from the given HTML.
|
||||
* Wrapping around jstree API because we want the flexibility to define
|
||||
* the node's <li> ourselves. Places the node in the tree
|
||||
* according to data.ParentID
|
||||
*
|
||||
* according to data.ParentID.
|
||||
*
|
||||
* Parameters:
|
||||
* (String) HTML New node content (<li>)
|
||||
* (Object) Map of additional data, e.g. ParentID
|
||||
@ -248,7 +250,7 @@
|
||||
*/
|
||||
createNode: function(html, data, callback) {
|
||||
var self = this,
|
||||
parentNode = data.ParentID ? self.find('li[data-id='+data.ParentID+']') : false,
|
||||
parentNode = data.ParentID ? self.getNodeByID(data.ParentID) : false,
|
||||
newNode = $(html);
|
||||
|
||||
this.jstree(
|
||||
@ -281,9 +283,9 @@
|
||||
updateNode: function(node, html, data) {
|
||||
var self = this, newNode = $(html), origClasses = node.attr('class');
|
||||
|
||||
var nextNode = data.NextID ? this.find('li[data-id='+data.NextID+']') : false;
|
||||
var prevNode = data.PrevID ? this.find('li[data-id='+data.PrevID+']') : false;
|
||||
var parentNode = data.ParentID ? this.find('li[data-id='+data.ParentID+']') : false;
|
||||
var nextNode = data.NextID ? this.getNodeByID(data.NextID) : false;
|
||||
var prevNode = data.PrevID ? this.getNodeByID(data.PrevID) : false;
|
||||
var parentNode = data.ParentID ? this.getNodeByID(data.ParentID) : false;
|
||||
|
||||
// Copy attributes. We can't replace the node completely
|
||||
// without removing or detaching its children nodes.
|
||||
@ -346,8 +348,22 @@
|
||||
updateNodesFromServer: function(ids) {
|
||||
if(this.getIsUpdatingTree() || !this.getIsLoaded()) return;
|
||||
|
||||
var self = this, includesNewNode = false;
|
||||
var self = this, i, includesNewNode = false;
|
||||
this.setIsUpdatingTree(true);
|
||||
self.jstree('save_selected');
|
||||
|
||||
var correctStateFn = function(node) {
|
||||
// Duplicates can be caused by the subtree reloading through
|
||||
// a tree "open"/"select" event, while at the same time creating a new node
|
||||
self.getNodeByID(node.data('id')).not(node).remove();
|
||||
|
||||
self.jstree('deselect_all');
|
||||
self.jstree('select_node', node);
|
||||
// Similar to jstree's correct_state, but doesn't remove children
|
||||
var hasChildren = (node.children('ul').length > 0);
|
||||
node.toggleClass('jstree-leaf', !hasChildren);
|
||||
if(!hasChildren) node.removeClass('jstree-closed jstree-open');
|
||||
};
|
||||
|
||||
// TODO 'initially_opened' config doesn't apply here
|
||||
self.jstree('open_node', this.getNodeByID(0));
|
||||
@ -367,15 +383,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var correctStateFn = function(node) {
|
||||
self.jstree('deselect_all');
|
||||
self.jstree('select_node', node);
|
||||
// Similar to jstree's correct_state, but doesn't remove children
|
||||
var hasChildren = (node.children('ul').length > 0);
|
||||
node.toggleClass('jstree-leaf', !hasChildren);
|
||||
if(!hasChildren) node.removeClass('jstree-closed jstree-open');
|
||||
};
|
||||
|
||||
// Check if node exists, create if necessary
|
||||
if(node.length) {
|
||||
self.updateNode(node, nodeData.html, nodeData);
|
||||
@ -384,9 +391,20 @@
|
||||
}, 500);
|
||||
} else {
|
||||
includesNewNode = true;
|
||||
self.createNode(nodeData.html, nodeData, function(newNode) {
|
||||
correctStateFn(newNode);
|
||||
});
|
||||
|
||||
// If the parent node can't be found, it might have not been loaded yet.
|
||||
// This can happen for deep trees which require ajax loading.
|
||||
// Assumes that the new node has been submitted to the server already.
|
||||
if(nodeData.ParentID && !self.find('li[data-id='+nodeData.ParentID+']').length) {
|
||||
self.jstree('load_node', -1, function() {
|
||||
newNode = self.find('li[data-id='+nodeId+']');
|
||||
correctStateFn(newNode);
|
||||
});
|
||||
} else {
|
||||
self.createNode(nodeData.html, nodeData, function(newNode) {
|
||||
correctStateFn(newNode);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -399,7 +417,7 @@
|
||||
complete: function() {
|
||||
self.setIsUpdatingTree(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -738,7 +738,7 @@ jQuery.noConflict();
|
||||
sessionData = hasSessionStorage ? window.sessionStorage.getItem('tabs-' + url) : null,
|
||||
sessionStates = sessionData ? JSON.parse(sessionData) : false;
|
||||
|
||||
this.find('.cms-tabset').each(function() {
|
||||
this.find('.cms-tabset, .ss-tabset').each(function() {
|
||||
var index, tabset = $(this), tabsetId = tabset.attr('id'), tab,
|
||||
forcedTab = tabset.find('.ss-tabs-force-active');
|
||||
|
||||
|
@ -798,6 +798,10 @@ body.cms {
|
||||
margin: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fieldgroup {
|
||||
|
@ -596,6 +596,12 @@ a .jstree-pageicon {
|
||||
}
|
||||
}
|
||||
|
||||
&.jstree-loading {
|
||||
li#record-0 > .jstree-icon {
|
||||
background: url(../images/throbber.gif) top left no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
// Show the loading indicator on the page icon rather than the default
|
||||
// jstree icon (which is only used for its dragging handles)
|
||||
a.jstree-loading{
|
||||
|
@ -15,7 +15,7 @@ abstract class DataFormatter extends Object {
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $priority = 50;
|
||||
private static $priority = 50;
|
||||
|
||||
/**
|
||||
* Follow relations for the {@link DataObject} instances
|
||||
|
@ -97,7 +97,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
$innerParts[] = ArrayData::array_to_object(array(
|
||||
"className" => $relClass,
|
||||
"href" => "$href.json",
|
||||
"id" => $item->$fieldName
|
||||
"id" => $item->ID
|
||||
));
|
||||
}
|
||||
$serobj->$relName = $innerParts;
|
||||
@ -118,7 +118,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
$innerParts[] = ArrayData::array_to_object(array(
|
||||
"className" => $relClass,
|
||||
"href" => "$href.json",
|
||||
"id" => $item->$fieldName
|
||||
"id" => $item->ID
|
||||
));
|
||||
}
|
||||
$serobj->$relName = $innerParts;
|
||||
|
@ -42,7 +42,16 @@ class Director implements TemplateGlobalProvider {
|
||||
* @var array
|
||||
*/
|
||||
private static $test_servers = array();
|
||||
|
||||
|
||||
/**
|
||||
* Setting this explicitly specifies the protocol (http or https) used, overriding
|
||||
* the normal behaviour of Director::is_https introspecting it from the request
|
||||
*
|
||||
* @config
|
||||
* @var string - "http" or "https" to force the protocol, or false-ish to use default introspection from request
|
||||
*/
|
||||
private static $alternate_protocol;
|
||||
|
||||
/**
|
||||
* @config
|
||||
* @var string
|
||||
@ -258,8 +267,18 @@ class Director implements TemplateGlobalProvider {
|
||||
|
||||
$request = new SS_HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
|
||||
if($headers) foreach($headers as $k => $v) $request->addHeader($k, $v);
|
||||
|
||||
// Pre-request filtering
|
||||
// @see issue #2517
|
||||
$model = DataModel::inst();
|
||||
$output = Injector::inst()->get('RequestProcessor')->preRequest($request, $session, $model);
|
||||
if ($output === false) {
|
||||
// @TODO Need to NOT proceed with the request in an elegant manner
|
||||
throw new SS_HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
|
||||
}
|
||||
|
||||
// TODO: Pass in the DataModel
|
||||
$result = Director::handleRequest($request, $session, DataModel::inst());
|
||||
$result = Director::handleRequest($request, $session, $model);
|
||||
|
||||
// Ensure that the result is an SS_HTTPResponse object
|
||||
if(is_string($result)) {
|
||||
@ -272,6 +291,11 @@ class Director implements TemplateGlobalProvider {
|
||||
}
|
||||
}
|
||||
|
||||
$output = Injector::inst()->get('RequestProcessor')->postRequest($request, $result, $model);
|
||||
if ($output === false) {
|
||||
throw new SS_HTTPResponse_Exception("Invalid response");
|
||||
}
|
||||
|
||||
// Restore the superglobals
|
||||
$_REQUEST = $existingRequestVars;
|
||||
$_GET = $existingGetVars;
|
||||
@ -443,6 +467,10 @@ class Director implements TemplateGlobalProvider {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_https() {
|
||||
if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) {
|
||||
return $protocol == 'https';
|
||||
}
|
||||
|
||||
if(isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) {
|
||||
if(strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https') {
|
||||
return true;
|
||||
|
@ -683,23 +683,24 @@ class Injector {
|
||||
* class name of the object to register)
|
||||
*
|
||||
*/
|
||||
public function registerService($service, $replace=null) {
|
||||
public function registerService($service, $replace = null) {
|
||||
$registerAt = get_class($service);
|
||||
if ($replace != null) {
|
||||
$registerAt = $replace;
|
||||
}
|
||||
|
||||
$this->specs[$registerAt] = array('class' => get_class($service));
|
||||
$this->serviceCache[$registerAt] = $service;
|
||||
$this->inject($service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a service with an explicit name
|
||||
*
|
||||
* @deprecated since 3.1.1
|
||||
*/
|
||||
public function registerNamedService($name, $service) {
|
||||
$this->specs[$name] = array('class' => get_class($service));
|
||||
$this->serviceCache[$name] = $service;
|
||||
$this->inject($service);
|
||||
return $this->registerService($service, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +90,7 @@ class SS_Backtrace {
|
||||
// Filter out arguments
|
||||
foreach($bt as $i => $frame) {
|
||||
$match = false;
|
||||
if(@$bt[$i]['class']) {
|
||||
if(!empty($bt[$i]['class'])) {
|
||||
foreach($ignoredArgs as $fnSpec) {
|
||||
if(is_array($fnSpec) && $bt[$i]['class'] == $fnSpec[0] && $bt[$i]['function'] == $fnSpec[1]) {
|
||||
$match = true;
|
||||
|
@ -94,7 +94,7 @@ class SS_Log {
|
||||
// Add default context (shouldn't change until the actual log event happens)
|
||||
foreach(static::$log_globals as $globalName => $keys) {
|
||||
foreach($keys as $key) {
|
||||
$val = @$GLOBALS[$globalName][$key];
|
||||
$val = isset($GLOBALS[$globalName][$key]) ? $GLOBALS[$globalName][$key] : null;
|
||||
static::$logger->setEventItem(sprintf('$%s[\'%s\']', $globalName, $key), $val);
|
||||
}
|
||||
}
|
||||
|
436
dev/install/install.php5
Normal file → Executable file
436
dev/install/install.php5
Normal file → Executable file
@ -27,7 +27,7 @@ ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
|
||||
// Attempt to start a session so that the username and password can be sent back to the user.
|
||||
if (function_exists('session_start') && !session_id()) {
|
||||
if(function_exists('session_start') && !session_id()) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
@ -45,36 +45,35 @@ $dirsToCheck = array(
|
||||
dirname($_SERVER['SCRIPT_FILENAME'])
|
||||
);
|
||||
//if they are the same, remove one of them
|
||||
if ($dirsToCheck[0] == $dirsToCheck[1]) {
|
||||
if($dirsToCheck[0] == $dirsToCheck[1]) {
|
||||
unset($dirsToCheck[1]);
|
||||
}
|
||||
foreach ($dirsToCheck as $dir) {
|
||||
foreach($dirsToCheck as $dir) {
|
||||
//check this dir and every parent dir (until we hit the base of the drive)
|
||||
// or until we hit a dir we can't read
|
||||
do {
|
||||
do {
|
||||
//add the trailing slash we need to concatenate properly
|
||||
$dir .= DIRECTORY_SEPARATOR;
|
||||
//if it's readable, go ahead
|
||||
if (@is_readable($dir)) {
|
||||
//if the file exists, then we include it, set relevant vars and break out
|
||||
if (file_exists($dir . $envFile)) {
|
||||
include_once($dir . $envFile);
|
||||
$envFileExists = true;
|
||||
//legacy variable assignment
|
||||
$usingEnv = true;
|
||||
if(@is_readable($dir)) {
|
||||
//if the file exists, then we include it, set relevant vars and break out
|
||||
if(file_exists($dir . $envFile)) {
|
||||
include_once($dir . $envFile);
|
||||
$envFileExists = true;
|
||||
//legacy variable assignment
|
||||
$usingEnv = true;
|
||||
//break out of BOTH loops because we found the $envFile
|
||||
break(2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
//break out of the while loop, we can't read the dir
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
//go up a directory
|
||||
$dir = dirname($dir);
|
||||
//here we need to check that the path of the last dir and the next one are
|
||||
//here we need to check that the path of the last dir and the next one are
|
||||
// not the same, if they are, we have hit the root of the drive
|
||||
} while (dirname($dir) != $dir);
|
||||
} while(dirname($dir) != $dir);
|
||||
}
|
||||
|
||||
if($envFileExists) {
|
||||
@ -91,62 +90,62 @@ require_once FRAMEWORK_NAME . '/dev/install/DatabaseAdapterRegistry.php';
|
||||
// Set default locale, but try and sniff from the user agent
|
||||
$defaultLocale = 'en_US';
|
||||
$locales = array(
|
||||
'af_ZA' => 'Afrikaans (South Africa)',
|
||||
'ar_EG' => 'Arabic (Egypt)',
|
||||
'hy_AM' => 'Armenian (Armenia)',
|
||||
'ast_ES' => 'Asturian (Spain)',
|
||||
'az_AZ' => 'Azerbaijani (Azerbaijan)',
|
||||
'bs_BA' => 'Bosnian (Bosnia and Herzegovina)',
|
||||
'bg_BG' => 'Bulgarian (Bulgaria)',
|
||||
'ca_ES' => 'Catalan (Spain)',
|
||||
'zh_CN' => 'Chinese (China)',
|
||||
'zh_TW' => 'Chinese (Taiwan)',
|
||||
'hr_HR' => 'Croatian (Croatia)',
|
||||
'cs_CZ' => 'Czech (Czech Republic)',
|
||||
'da_DK' => 'Danish (Denmark)',
|
||||
'nl_NL' => 'Dutch (Netherlands)',
|
||||
'en_GB' => 'English (United Kingdom)',
|
||||
'en_US' => 'English (United States)',
|
||||
'eo_XX' => 'Esperanto',
|
||||
'et_EE' => 'Estonian (Estonia)',
|
||||
'fo_FO' => 'Faroese (Faroe Islands)',
|
||||
'fi_FI' => 'Finnish (Finland)',
|
||||
'fr_FR' => 'French (France)',
|
||||
'de_DE' => 'German (Germany)',
|
||||
'el_GR' => 'Greek (Greece)',
|
||||
'he_IL' => 'Hebrew (Israel)',
|
||||
'hu_HU' => 'Hungarian (Hungary)',
|
||||
'is_IS' => 'Icelandic (Iceland)',
|
||||
'id_ID' => 'Indonesian (Indonesia)',
|
||||
'it_IT' => 'Italian (Italy)',
|
||||
'ja_JP' => 'Japanese (Japan)',
|
||||
'km_KH' => 'Khmer (Cambodia)',
|
||||
'lc_XX' => 'LOLCAT',
|
||||
'lv_LV' => 'Latvian (Latvia)',
|
||||
'lt_LT' => 'Lithuanian (Lithuania)',
|
||||
'ms_MY' => 'Malay (Malaysia)',
|
||||
'mi_NZ' => 'Maori (New Zealand)',
|
||||
'ne_NP' => 'Nepali (Nepal)',
|
||||
'nb_NO' => 'Norwegian',
|
||||
'fa_IR' => 'Persian (Iran)',
|
||||
'pl_PL' => 'Polish (Poland)',
|
||||
'pt_BR' => 'Portuguese (Brazil)',
|
||||
'pa_IN' => 'Punjabi (India)',
|
||||
'ro_RO' => 'Romanian (Romania)',
|
||||
'ru_RU' => 'Russian (Russia)',
|
||||
'sr_RS' => 'Serbian (Serbia)',
|
||||
'si_LK' => 'Sinhalese (Sri Lanka)',
|
||||
'sk_SK' => 'Slovak (Slovakia)',
|
||||
'sl_SI' => 'Slovenian (Slovenia)',
|
||||
'es_AR' => 'Spanish (Argentina)',
|
||||
'es_MX' => 'Spanish (Mexico)',
|
||||
'es_ES' => 'Spanish (Spain)',
|
||||
'sv_SE' => 'Swedish (Sweden)',
|
||||
'th_TH' => 'Thai (Thailand)',
|
||||
'tr_TR' => 'Turkish (Turkey)',
|
||||
'uk_UA' => 'Ukrainian (Ukraine)',
|
||||
'uz_UZ' => 'Uzbek (Uzbekistan)',
|
||||
'vi_VN' => 'Vietnamese (Vietnam)',
|
||||
'af_ZA' => 'Afrikaans (South Africa)',
|
||||
'ar_EG' => 'Arabic (Egypt)',
|
||||
'hy_AM' => 'Armenian (Armenia)',
|
||||
'ast_ES' => 'Asturian (Spain)',
|
||||
'az_AZ' => 'Azerbaijani (Azerbaijan)',
|
||||
'bs_BA' => 'Bosnian (Bosnia and Herzegovina)',
|
||||
'bg_BG' => 'Bulgarian (Bulgaria)',
|
||||
'ca_ES' => 'Catalan (Spain)',
|
||||
'zh_CN' => 'Chinese (China)',
|
||||
'zh_TW' => 'Chinese (Taiwan)',
|
||||
'hr_HR' => 'Croatian (Croatia)',
|
||||
'cs_CZ' => 'Czech (Czech Republic)',
|
||||
'da_DK' => 'Danish (Denmark)',
|
||||
'nl_NL' => 'Dutch (Netherlands)',
|
||||
'en_GB' => 'English (United Kingdom)',
|
||||
'en_US' => 'English (United States)',
|
||||
'eo_XX' => 'Esperanto',
|
||||
'et_EE' => 'Estonian (Estonia)',
|
||||
'fo_FO' => 'Faroese (Faroe Islands)',
|
||||
'fi_FI' => 'Finnish (Finland)',
|
||||
'fr_FR' => 'French (France)',
|
||||
'de_DE' => 'German (Germany)',
|
||||
'el_GR' => 'Greek (Greece)',
|
||||
'he_IL' => 'Hebrew (Israel)',
|
||||
'hu_HU' => 'Hungarian (Hungary)',
|
||||
'is_IS' => 'Icelandic (Iceland)',
|
||||
'id_ID' => 'Indonesian (Indonesia)',
|
||||
'it_IT' => 'Italian (Italy)',
|
||||
'ja_JP' => 'Japanese (Japan)',
|
||||
'km_KH' => 'Khmer (Cambodia)',
|
||||
'lc_XX' => 'LOLCAT',
|
||||
'lv_LV' => 'Latvian (Latvia)',
|
||||
'lt_LT' => 'Lithuanian (Lithuania)',
|
||||
'ms_MY' => 'Malay (Malaysia)',
|
||||
'mi_NZ' => 'Maori (New Zealand)',
|
||||
'ne_NP' => 'Nepali (Nepal)',
|
||||
'nb_NO' => 'Norwegian',
|
||||
'fa_IR' => 'Persian (Iran)',
|
||||
'pl_PL' => 'Polish (Poland)',
|
||||
'pt_BR' => 'Portuguese (Brazil)',
|
||||
'pa_IN' => 'Punjabi (India)',
|
||||
'ro_RO' => 'Romanian (Romania)',
|
||||
'ru_RU' => 'Russian (Russia)',
|
||||
'sr_RS' => 'Serbian (Serbia)',
|
||||
'si_LK' => 'Sinhalese (Sri Lanka)',
|
||||
'sk_SK' => 'Slovak (Slovakia)',
|
||||
'sl_SI' => 'Slovenian (Slovenia)',
|
||||
'es_AR' => 'Spanish (Argentina)',
|
||||
'es_MX' => 'Spanish (Mexico)',
|
||||
'es_ES' => 'Spanish (Spain)',
|
||||
'sv_SE' => 'Swedish (Sweden)',
|
||||
'th_TH' => 'Thai (Thailand)',
|
||||
'tr_TR' => 'Turkish (Turkey)',
|
||||
'uk_UA' => 'Ukrainian (Ukraine)',
|
||||
'uz_UZ' => 'Uzbek (Uzbekistan)',
|
||||
'vi_VN' => 'Vietnamese (Vietnam)',
|
||||
);
|
||||
|
||||
// Discover which databases are available
|
||||
@ -217,8 +216,8 @@ if(file_exists('mysite/_config.php')) {
|
||||
if(preg_match("/\\\$database\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) {
|
||||
eval($parts[0]);
|
||||
if($database) $alreadyInstalled = true;
|
||||
// Assume that if $databaseConfig is defined in mysite/_config.php, then a non-environment-based installation has
|
||||
// already gone ahead
|
||||
// Assume that if $databaseConfig is defined in mysite/_config.php, then a non-environment-based installation has
|
||||
// already gone ahead
|
||||
} else if(preg_match("/\\\$databaseConfig\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) {
|
||||
$alreadyInstalled = true;
|
||||
}
|
||||
@ -412,7 +411,12 @@ class InstallRequirements {
|
||||
$isIIS = $this->isIIS(7);
|
||||
$webserver = $this->findWebserver();
|
||||
|
||||
$this->requirePHPVersion('5.3.4', '5.3.2', array("PHP Configuration", "PHP5 installed", null, "PHP version " . phpversion()));
|
||||
$this->requirePHPVersion('5.3.4', '5.3.2', array(
|
||||
"PHP Configuration",
|
||||
"PHP5 installed",
|
||||
null,
|
||||
"PHP version " . phpversion()
|
||||
));
|
||||
|
||||
// Check that we can identify the root folder successfully
|
||||
$this->requireFile(FRAMEWORK_NAME . '/dev/install/config-form.html', array("File permissions",
|
||||
@ -425,15 +429,27 @@ class InstallRequirements {
|
||||
$this->requireModule(FRAMEWORK_NAME, array("File permissions", FRAMEWORK_NAME . "/ directory exists?"));
|
||||
|
||||
if($isApache) {
|
||||
$this->checkApacheVersion(array("Webserver Configuration", "Webserver is not Apache 1.x", "SilverStripe requires Apache version 2 or greater", $webserver));
|
||||
$this->checkApacheVersion(array(
|
||||
"Webserver Configuration",
|
||||
"Webserver is not Apache 1.x", "SilverStripe requires Apache version 2 or greater",
|
||||
$webserver
|
||||
));
|
||||
$this->requireWriteable('.htaccess', array("File permissions", "Is the .htaccess file writeable?", null));
|
||||
} elseif($isIIS) {
|
||||
$this->requireWriteable('web.config', array("File permissions", "Is the web.config file writeable?", null));
|
||||
}
|
||||
|
||||
$this->requireWriteable('mysite/_config.php', array("File permissions", "Is the mysite/_config.php file writeable?", null));
|
||||
if (!$this->checkModuleExists('cms')) {
|
||||
$this->requireWriteable('mysite/code/RootURLController.php', array("File permissions", "Is the mysite/code/RootURLController.php file writeable?", null));
|
||||
$this->requireWriteable('mysite/_config.php', array(
|
||||
"File permissions",
|
||||
"Is the mysite/_config.php file writeable?",
|
||||
null
|
||||
));
|
||||
if(!$this->checkModuleExists('cms')) {
|
||||
$this->requireWriteable('mysite/code/RootURLController.php', array(
|
||||
"File permissions",
|
||||
"Is the mysite/code/RootURLController.php file writeable?",
|
||||
null
|
||||
));
|
||||
}
|
||||
$this->requireWriteable('assets', array("File permissions", "Is the assets/ directory writeable?", null));
|
||||
|
||||
@ -441,72 +457,193 @@ class InstallRequirements {
|
||||
$this->requireTempFolder(array('File permissions', 'Is a temporary directory available?', null, $tempFolder));
|
||||
if($tempFolder) {
|
||||
// in addition to the temp folder being available, check it is writable
|
||||
$this->requireWriteable($tempFolder, array("File permissions", sprintf("Is the temporary directory writeable?", $tempFolder), null), true);
|
||||
$this->requireWriteable($tempFolder, array(
|
||||
"File permissions",
|
||||
sprintf("Is the temporary directory writeable?", $tempFolder),
|
||||
null
|
||||
), true);
|
||||
}
|
||||
|
||||
// Check for web server, unless we're calling the installer from the command-line
|
||||
$this->isRunningWebServer(array("Webserver Configuration", "Server software", "Unknown", $webserver));
|
||||
|
||||
if($isApache) {
|
||||
$this->requireApacheRewriteModule('mod_rewrite', array("Webserver Configuration", "URL rewriting support", "You need mod_rewrite to use friendly URLs with SilverStripe, but it is not enabled."));
|
||||
$this->requireApacheRewriteModule('mod_rewrite', array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"You need mod_rewrite to use friendly URLs with SilverStripe, but it is not enabled."
|
||||
));
|
||||
} elseif($isIIS) {
|
||||
$this->requireIISRewriteModule('IIS_UrlRewriteModule', array("Webserver Configuration", "URL rewriting support", "You need to enable the IIS URL Rewrite Module to use friendly URLs with SilverStripe, but it is not installed or enabled. Download it for IIS 7 from http://www.iis.net/expand/URLRewrite"));
|
||||
$this->requireIISRewriteModule('IIS_UrlRewriteModule', array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"You need to enable the IIS URL Rewrite Module to use friendly URLs with SilverStripe, "
|
||||
. "but it is not installed or enabled. Download it for IIS 7 from http://www.iis.net/expand/URLRewrite"
|
||||
));
|
||||
} else {
|
||||
$this->warning(array("Webserver Configuration", "URL rewriting support", "I can't tell whether any rewriting module is running. You may need to configure a rewriting rule yourself."));
|
||||
$this->warning(array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"I can't tell whether any rewriting module is running. You may need to configure a rewriting rule yourself."));
|
||||
}
|
||||
|
||||
$this->requireServerVariables(array('SCRIPT_NAME','HTTP_HOST','SCRIPT_FILENAME'), array("Webserver Configuration", "Recognised webserver", "You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."));
|
||||
$this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
|
||||
"Webserver Configuration",
|
||||
"Recognised webserver",
|
||||
"You seem to be using an unsupported webserver. "
|
||||
. "The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."
|
||||
));
|
||||
|
||||
$this->requirePostSupport(array("Webserver Configuration", "POST Support", 'I can\'t find $_POST, make sure POST is enabled.'));
|
||||
$this->requirePostSupport(array(
|
||||
"Webserver Configuration",
|
||||
"POST Support",
|
||||
'I can\'t find $_POST, make sure POST is enabled.'
|
||||
));
|
||||
|
||||
// Check for GD support
|
||||
if(!$this->requireFunction("imagecreatetruecolor", array("PHP Configuration", "GD2 support", "PHP must have GD version 2."))) {
|
||||
$this->requireFunction("imagecreate", array("PHP Configuration", "GD2 support", "GD support for PHP not included."));
|
||||
if(!$this->requireFunction("imagecreatetruecolor", array(
|
||||
"PHP Configuration",
|
||||
"GD2 support",
|
||||
"PHP must have GD version 2."
|
||||
))) {
|
||||
$this->requireFunction("imagecreate", array(
|
||||
"PHP Configuration",
|
||||
"GD2 support",
|
||||
"GD support for PHP not included."
|
||||
));
|
||||
}
|
||||
|
||||
// Check for XML support
|
||||
$this->requireFunction('xml_set_object', array("PHP Configuration", "XML support", "XML support not included in PHP."));
|
||||
$this->requireClass('DOMDocument', array("PHP Configuration", "DOM/XML support", "DOM/XML support not included in PHP."));
|
||||
$this->requireFunction('simplexml_load_file', array('PHP Configuration', 'SimpleXML support', 'SimpleXML support not included in PHP.'));
|
||||
$this->requireFunction('xml_set_object', array(
|
||||
"PHP Configuration",
|
||||
"XML support",
|
||||
"XML support not included in PHP."
|
||||
));
|
||||
$this->requireClass('DOMDocument', array(
|
||||
"PHP Configuration",
|
||||
"DOM/XML support",
|
||||
"DOM/XML support not included in PHP."
|
||||
));
|
||||
$this->requireFunction('simplexml_load_file', array(
|
||||
'PHP Configuration',
|
||||
'SimpleXML support',
|
||||
'SimpleXML support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for token_get_all
|
||||
$this->requireFunction('token_get_all', array("PHP Configuration", "Tokenizer support", "Tokenizer support not included in PHP."));
|
||||
$this->requireFunction('token_get_all', array(
|
||||
"PHP Configuration",
|
||||
"Tokenizer support",
|
||||
"Tokenizer support not included in PHP."
|
||||
));
|
||||
|
||||
// Check for CType support
|
||||
$this->requireFunction('ctype_digit', array('PHP Configuration', 'CType support', 'CType support not included in PHP.'));
|
||||
$this->requireFunction('ctype_digit', array(
|
||||
'PHP Configuration',
|
||||
'CType support',
|
||||
'CType support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for session support
|
||||
$this->requireFunction('session_start', array('PHP Configuration', 'Session support', 'Session support not included in PHP.'));
|
||||
$this->requireFunction('session_start', array(
|
||||
'PHP Configuration',
|
||||
'Session support',
|
||||
'Session support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for iconv support
|
||||
$this->requireFunction('iconv', array('PHP Configuration', 'iconv support', 'iconv support not included in PHP.'));
|
||||
$this->requireFunction('iconv', array(
|
||||
'PHP Configuration',
|
||||
'iconv support',
|
||||
'iconv support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for hash support
|
||||
$this->requireFunction('hash', array('PHP Configuration', 'hash support', 'hash support not included in PHP.'));
|
||||
|
||||
// Check for mbstring support
|
||||
$this->requireFunction('mb_internal_encoding', array('PHP Configuration', 'mbstring support', 'mbstring support not included in PHP.'));
|
||||
$this->requireFunction('mb_internal_encoding', array(
|
||||
'PHP Configuration',
|
||||
'mbstring support',
|
||||
'mbstring support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for Reflection support
|
||||
$this->requireClass('ReflectionClass', array('PHP Configuration', 'Reflection support', 'Reflection support not included in PHP.'));
|
||||
$this->requireClass('ReflectionClass', array(
|
||||
'PHP Configuration',
|
||||
'Reflection support',
|
||||
'Reflection support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for Standard PHP Library (SPL) support
|
||||
$this->requireFunction('spl_classes', array('PHP Configuration', 'SPL support', 'Standard PHP Library (SPL) not included in PHP.'));
|
||||
$this->requireFunction('spl_classes', array(
|
||||
'PHP Configuration',
|
||||
'SPL support',
|
||||
'Standard PHP Library (SPL) not included in PHP.'
|
||||
));
|
||||
|
||||
$this->requireDateTimezone(array('PHP Configuration', 'date.timezone setting and validity', 'date.timezone option in php.ini must be set correctly.', ini_get('date.timezone')));
|
||||
$this->requireDateTimezone(array(
|
||||
'PHP Configuration',
|
||||
'date.timezone setting and validity',
|
||||
'date.timezone option in php.ini must be set correctly.',
|
||||
ini_get('date.timezone')
|
||||
));
|
||||
|
||||
$this->suggestClass('finfo', array('PHP Configuration', 'fileinfo support', 'fileinfo should be enabled in PHP. SilverStripe uses it for MIME type detection of files. SilverStripe will still operate, but email attachments and sending files to browser (e.g. export data to CSV) may not work correctly without finfo.'));
|
||||
$this->suggestClass('finfo', array(
|
||||
'PHP Configuration',
|
||||
'fileinfo support',
|
||||
'fileinfo should be enabled in PHP. SilverStripe uses it for MIME type detection of files. '
|
||||
. 'SilverStripe will still operate, but email attachments and sending files to browser '
|
||||
. '(e.g. export data to CSV) may not work correctly without finfo.'
|
||||
));
|
||||
|
||||
$this->suggestFunction('curl_init', array('PHP Configuration', 'curl support', 'curl should be enabled in PHP. SilverStripe uses it for consuming web services via the RestfulService class and many modules rely on it.'));
|
||||
$this->suggestFunction('curl_init', array(
|
||||
'PHP Configuration',
|
||||
'curl support',
|
||||
'curl should be enabled in PHP. SilverStripe uses it for consuming web services'
|
||||
. ' via the RestfulService class and many modules rely on it.'
|
||||
));
|
||||
|
||||
$this->suggestClass('tidy', array('PHP Configuration', 'tidy support', 'Tidy provides a library of code to clean up your html. SilverStripe will operate fine without tidy but HTMLCleaner will not be effective.'));
|
||||
$this->suggestClass('tidy', array(
|
||||
'PHP Configuration',
|
||||
'tidy support',
|
||||
'Tidy provides a library of code to clean up your html. '
|
||||
. 'SilverStripe will operate fine without tidy but HTMLCleaner will not be effective.'
|
||||
));
|
||||
|
||||
$this->suggestPHPSetting('asp_tags', array(false), array('PHP Configuration', 'asp_tags option', 'This should be turned off as it can cause issues with SilverStripe'));
|
||||
$this->requirePHPSetting('magic_quotes_gpc', array(false), array('PHP Configuration', 'magic_quotes_gpc option', 'This should be turned off, as it can cause issues with cookies. More specifically, unserializing data stored in cookies.'));
|
||||
$this->suggestPHPSetting('display_errors', array(false), array('PHP Configuration', 'display_errors option', 'Unless you\'re in a development environment, this should be turned off, as it can expose sensitive data to website users.'));
|
||||
$this->suggestPHPSetting('asp_tags', array(false), array(
|
||||
'PHP Configuration',
|
||||
'asp_tags option',
|
||||
'This should be turned off as it can cause issues with SilverStripe'
|
||||
));
|
||||
$this->requirePHPSetting('magic_quotes_gpc', array(false), array(
|
||||
'PHP Configuration',
|
||||
'magic_quotes_gpc option',
|
||||
'This should be turned off, as it can cause issues with cookies. '
|
||||
. 'More specifically, unserializing data stored in cookies.'
|
||||
));
|
||||
$this->suggestPHPSetting('display_errors', array(false), array(
|
||||
'PHP Configuration',
|
||||
'display_errors option',
|
||||
'Unless you\'re in a development environment, this should be turned off, '
|
||||
. 'as it can expose sensitive data to website users.'
|
||||
));
|
||||
// on some weirdly configured webservers arg_separator.output is set to &
|
||||
// which will results in links like ?param=value&foo=bar which will not be i
|
||||
$this->suggestPHPSetting('arg_separator.output', array('&', ''), array(
|
||||
'PHP Configuration',
|
||||
'arg_separator.output option',
|
||||
'This option defines how URL parameters are concatenated. '
|
||||
. 'If not set to \'&\' this may cause issues with URL GET parameters'
|
||||
));
|
||||
|
||||
// Check memory allocation
|
||||
$this->requireMemory(32*1024*1024, 64*1024*1024, array("PHP Configuration", "Memory allocation (PHP config option 'memory_limit')", "SilverStripe needs a minimum of 32M allocated to PHP, but recommends 64M.", ini_get("memory_limit")));
|
||||
$this->requireMemory(32 * 1024 * 1024, 64 * 1024 * 1024, array(
|
||||
"PHP Configuration",
|
||||
"Memory allocation (PHP config option 'memory_limit')",
|
||||
"SilverStripe needs a minimum of 32M allocated to PHP, but recommends 64M.",
|
||||
ini_get("memory_limit")
|
||||
));
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
@ -517,6 +654,7 @@ class InstallRequirements {
|
||||
// special case for display_errors, check the original value before
|
||||
// it was changed at the start of this script.
|
||||
if($settingName = 'display_errors') {
|
||||
global $originalDisplayErrorsValue;
|
||||
$val = $originalDisplayErrorsValue;
|
||||
} else {
|
||||
$val = ini_get($settingName);
|
||||
@ -582,7 +720,8 @@ class InstallRequirements {
|
||||
$testDetails[2] .= " You only have " . ini_get("memory_limit") . " allocated";
|
||||
$this->warning($testDetails);
|
||||
} elseif($mem == 0) {
|
||||
$testDetails[2] .= " We can't determine how much memory you have allocated. Install only if you're sure you've allocated at least 20 MB.";
|
||||
$testDetails[2] .= " We can't determine how much memory you have allocated. "
|
||||
. "Install only if you're sure you've allocated at least 20 MB.";
|
||||
$this->warning($testDetails);
|
||||
}
|
||||
}
|
||||
@ -590,15 +729,15 @@ class InstallRequirements {
|
||||
function getPHPMemory() {
|
||||
$memString = ini_get("memory_limit");
|
||||
|
||||
switch(strtolower(substr($memString,-1))) {
|
||||
switch(strtolower(substr($memString, -1))) {
|
||||
case "k":
|
||||
return round(substr($memString,0,-1)*1024);
|
||||
return round(substr($memString, 0, -1) * 1024);
|
||||
|
||||
case "m":
|
||||
return round(substr($memString,0,-1)*1024*1024);
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024);
|
||||
|
||||
case "g":
|
||||
return round(substr($memString,0,-1)*1024*1024*1024);
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
|
||||
|
||||
default:
|
||||
return round($memString);
|
||||
@ -620,7 +759,8 @@ class InstallRequirements {
|
||||
$id = strtolower(str_replace(' ', '_', $section));
|
||||
echo "<table id=\"{$id}_results\" class=\"testResults\" width=\"100%\">";
|
||||
foreach($tests as $test => $result) {
|
||||
echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
echo "<tr class=\"$result[0]\"><td>$test</td><td>"
|
||||
. nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
@ -642,7 +782,8 @@ class InstallRequirements {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$output .= "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
$output .= "<tr class=\"$result[0]\"><td>$test</td><td>"
|
||||
. nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
}
|
||||
$className = "good";
|
||||
$text = "All Requirements Pass";
|
||||
@ -652,11 +793,10 @@ class InstallRequirements {
|
||||
$className = "error";
|
||||
$pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings';
|
||||
|
||||
$text = $failedRequirements . ' Failed and '. $warningRequirements . ' '. $pluralWarnings;
|
||||
}
|
||||
else if($warningRequirements > 0) {
|
||||
$text = $failedRequirements . ' Failed and ' . $warningRequirements . ' ' . $pluralWarnings;
|
||||
} else if($warningRequirements > 0) {
|
||||
$className = "warning";
|
||||
$text = "All Requirements Pass but ". $warningRequirements . ' '. $pluralWarnings;
|
||||
$text = "All Requirements Pass but " . $warningRequirements . ' ' . $pluralWarnings;
|
||||
}
|
||||
|
||||
echo "<h5 class='requirement $className'>$section <a href='#'>Show All Requirements</a> <span>$text</span></h5>";
|
||||
@ -669,17 +809,21 @@ class InstallRequirements {
|
||||
|
||||
function requireFunction($funcName, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
|
||||
if(!function_exists($funcName)) {
|
||||
$this->error($testDetails);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
else return true;
|
||||
}
|
||||
|
||||
function requireClass($className, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
if(!class_exists($className)) $this->error($testDetails);
|
||||
else return false;
|
||||
if(!class_exists($className)) {
|
||||
$this->error($testDetails);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -694,8 +838,9 @@ class InstallRequirements {
|
||||
if($badClasses) {
|
||||
$testDetails[2] .= ". The following classes are at fault: " . implode(', ', $badClasses);
|
||||
$this->error($testDetails);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
else return true;
|
||||
}
|
||||
|
||||
function checkApacheVersion($testDetails) {
|
||||
@ -753,7 +898,8 @@ class InstallRequirements {
|
||||
$testDetails[2] .= " Directory '$path' not found. Please make sure you have uploaded the SilverStripe files to your webserver correctly.";
|
||||
$this->error($testDetails);
|
||||
} elseif(!file_exists($path . '/_config.php') && $dirname != 'mysite') {
|
||||
$testDetails[2] .= " Directory '$path' exists, but is missing files. Please make sure you have uploaded the SilverStripe files to your webserver correctly.";
|
||||
$testDetails[2] .= " Directory '$path' exists, but is missing files. Please make sure you have uploaded "
|
||||
. "the SilverStripe files to your webserver correctly.";
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
@ -784,10 +930,11 @@ class InstallRequirements {
|
||||
$userID = posix_geteuid();
|
||||
$user = posix_getpwuid($userID);
|
||||
|
||||
$currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename) );
|
||||
$currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename));
|
||||
$currentOwner = posix_getpwuid($currentOwnerID);
|
||||
|
||||
$testDetails[2] .= "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe file is currently owned by '$currentOwner[name]'. ";
|
||||
$testDetails[2] .= "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe "
|
||||
. "file is currently owned by '$currentOwner[name]'. ";
|
||||
|
||||
if($user['name'] == $currentOwner['name']) {
|
||||
$testDetails[2] .= "We recommend that you make the file writeable.";
|
||||
@ -800,10 +947,13 @@ class InstallRequirements {
|
||||
if(in_array($currentOwner['name'], $groupInfo['members'])) $groupList[] = $groupInfo['name'];
|
||||
}
|
||||
if($groupList) {
|
||||
$testDetails[2] .= " We recommend that you make the file group-writeable and change the group to one of these groups:\n - ". implode("\n - ", $groupList)
|
||||
$testDetails[2] .= " We recommend that you make the file group-writeable "
|
||||
. "and change the group to one of these groups:\n - " . implode("\n - ", $groupList)
|
||||
. "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename";
|
||||
} else {
|
||||
$testDetails[2] .= " There is no user-group that contains both the web-server user and the owner of this file. Change the ownership of the file, create a new group, or temporarily make the file writeable by everyone during the install process.";
|
||||
$testDetails[2] .= " There is no user-group that contains both the web-server user and the "
|
||||
. "owner of this file. Change the ownership of the file, create a new group, or "
|
||||
. "temporarily make the file writeable by everyone during the install process.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1015,11 +1165,11 @@ class InstallRequirements {
|
||||
function requireServerVariables($varNames, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$missing = array();
|
||||
|
||||
|
||||
foreach($varNames as $varName) {
|
||||
if(!isset($_SERVER[$varName]) || !$_SERVER[$varName]) {
|
||||
if(!isset($_SERVER[$varName]) || !$_SERVER[$varName]) {
|
||||
$missing[] = '$_SERVER[' . $varName . ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$missing) {
|
||||
@ -1055,6 +1205,7 @@ class InstallRequirements {
|
||||
|
||||
// Must be PHP4 compatible
|
||||
var $baseDir;
|
||||
|
||||
function getBaseDir() {
|
||||
// Cache the value so that when the installer mucks with SCRIPT_FILENAME half way through, this method
|
||||
// still returns the correct value.
|
||||
@ -1107,22 +1258,23 @@ class Installer extends InstallRequirements {
|
||||
}
|
||||
|
||||
function install($config) {
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Installing SilverStripe...</title>
|
||||
<link rel="stylesheet" type="text/css" href="<?php echo FRAMEWORK_NAME; ?>/dev/install/css/install.css" />
|
||||
<script src="<?php echo FRAMEWORK_NAME; ?>/thirdparty/jquery/jquery.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Installing SilverStripe...</title>
|
||||
<link rel="stylesheet" type="text/css" href="<?php echo FRAMEWORK_NAME; ?>/dev/install/css/install.css"/>
|
||||
<script src="<?php echo FRAMEWORK_NAME; ?>/thirdparty/jquery/jquery.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="install-header">
|
||||
<div class="inner">
|
||||
<div class="brand">
|
||||
<span class="logo"></span>
|
||||
|
||||
<h1>SilverStripe</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="Navigation"> </div>
|
||||
@ -1131,7 +1283,9 @@ class Installer extends InstallRequirements {
|
||||
<div class="main">
|
||||
<div class="inner">
|
||||
<h2>Installing SilverStripe...</h2>
|
||||
|
||||
<p>I am now running through the installation steps (this should take about 30 seconds)</p>
|
||||
|
||||
<p>If you receive a fatal error, refresh this page to continue the installation</p>
|
||||
<ul>
|
||||
<?php
|
||||
@ -1236,7 +1390,7 @@ PHP
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->checkModuleExists('cms')) {
|
||||
if(!$this->checkModuleExists('cms')) {
|
||||
$this->writeToFile("mysite/code/RootURLController.php", <<<PHP
|
||||
<?php
|
||||
|
||||
|
@ -64,14 +64,14 @@ see [Using development versions](#using-development-versions).
|
||||
|
||||
Composer isn't only used to download SilverStripe CMS, it can also be used to manage all SilverStripe modules. Installing a module can be done with the following command:
|
||||
|
||||
composer require silverstripe/forum:*
|
||||
composer require "silverstripe/forum:*"
|
||||
|
||||
This will install the forum module in the latest compatible version.
|
||||
By default, Composer updates other existing modules (like `framework` and `cms`),
|
||||
and installs "dev" dependencies like PHPUnit. In case you don't need those dependencies,
|
||||
use the following command instead:
|
||||
|
||||
composer require --no-update silverstripe/forum:*
|
||||
composer require --no-update "silverstripe/forum:*"
|
||||
composer update --no-dev
|
||||
|
||||
The `require` command has two parts. First is `silverstripe/forum`. This is the name of the package.
|
||||
|
@ -249,6 +249,32 @@ Here is a list of components for generic use:
|
||||
- `[api:GridFieldPaginator]`
|
||||
- `[api:GridFieldDetailForm]`
|
||||
|
||||
## Flexible Area Assignment through Fragments
|
||||
|
||||
GridField layouts can contain many components other than the table itself,
|
||||
for example a search bar to find existing relations, a button to add those,
|
||||
and buttons to export and print the current data. The GridField has certain
|
||||
defined areas called "fragments" where these components can be placed.
|
||||
The goal is for multiple components to share the same space, for example a header row.
|
||||
|
||||
Built-in components:
|
||||
|
||||
- `header`/`footer`: Renders in a `<thead>`/`<tfoot>`, should contain table markup
|
||||
- `before`/`after`: Renders before/after the actual `<table>`
|
||||
- `buttons-before-left`/`buttons-before-right`/`buttons-after-left`/`buttons-after-right`:
|
||||
Renders in a shared row before the table. Requires [api:GridFieldButtonRow].
|
||||
|
||||
These built-ins can be used by passing the fragment names into the constructor
|
||||
of various components. Note that some [api:GridFieldConfig] classes
|
||||
will already have rows added to them. The following example will add a print button
|
||||
at the bottom right of the table.
|
||||
|
||||
:::php
|
||||
$config->addComponent(new GridFieldButtonRow('after'));
|
||||
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
|
||||
|
||||
Further down we'll explain how to write your own components using fragments.
|
||||
|
||||
## Creating a custom GridFieldComponent
|
||||
|
||||
A single component often uses a number of interfaces.
|
||||
@ -329,6 +355,42 @@ If you need more granular control, e.g. to consistently deny non-admins from del
|
||||
records, use the `DataObject->can...()` methods
|
||||
(see [DataObject permissions](/reference/dataobject#permissions)).
|
||||
|
||||
## Creating your own Fragments
|
||||
|
||||
Fragments are designated areas within a GridField which can be shared between component templates.
|
||||
You can define your own fragments by using a `\$DefineFragment' placeholder in your components' template.
|
||||
This example will simply create an area rendered before the table wrapped in a simple `<div>`.
|
||||
|
||||
:::php
|
||||
class MyAreaComponent implements GridField_HTMLProvider {
|
||||
public function getHTMLFragments( $gridField) {
|
||||
return array(
|
||||
'before' => '<div class="my-area">$DefineFragment(my-area)</div>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
We're returning raw HTML from the component, usually this would be handled by a SilverStripe template.
|
||||
Please note that in templates, you'll need to escape the dollar sign on `$DefineFragment`:
|
||||
These are specially processed placeholders as opposed to native template syntax.
|
||||
|
||||
Now you can add other components into this area by returning them as an array from
|
||||
your [api:GridFieldComponent->getHTMLFragments()] implementation:
|
||||
|
||||
:::php
|
||||
class MyShareLinkComponent implements GridField_HTMLProvider {
|
||||
public function getHTMLFragments( $gridField) {
|
||||
return array(
|
||||
'my-area' => '<a href>...</a>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Your new area can also be used by existing components, e.g. the [api:GridFieldPrintButton]
|
||||
|
||||
:::php
|
||||
new GridFieldPrintButton('my-component-area')
|
||||
|
||||
## Related
|
||||
|
||||
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin)
|
||||
|
@ -43,6 +43,7 @@ SilverStripe what values to include in the feed.
|
||||
|
||||
:::php
|
||||
class Page_Controller extends ContentController {
|
||||
private static $allowed_actions = array('rss');
|
||||
public function init() {
|
||||
// linkToFeed will add an appropriate HTML link tag to the website
|
||||
// <head> tag to notify web browsers that an RSS feed is available
|
||||
@ -80,6 +81,8 @@ updates. Update mysite/code/Page.php to something like this:
|
||||
<?php
|
||||
class Page extends SiteTree {}
|
||||
class Page_Controller extends ContentController {
|
||||
|
||||
private static $allowed_actions = array('rss');
|
||||
|
||||
public function init() {
|
||||
RSSFeed::linkToFeed($this->Link() . "rss", "10 Most Recently Updated Pages");
|
||||
@ -123,6 +126,7 @@ for all the students as we've seen before.
|
||||
|
||||
:::php
|
||||
class Page_Controller extends ContentController {
|
||||
private static $allowed_actions = array('students');
|
||||
public function init() {
|
||||
RSSFeed::linkToFeed($this->Link("students"), "Students feed");
|
||||
parent::init();
|
||||
@ -172,4 +176,4 @@ accessing feeds from external sources.
|
||||
|
||||
## API Documentation
|
||||
|
||||
* `[api:RSSFeed]`
|
||||
* `[api:RSSFeed]`
|
||||
|
@ -375,7 +375,7 @@ Data is defined in the static variable $db on each class, in the format:
|
||||
"FirstName" => "Varchar",
|
||||
"Surname" => "Varchar",
|
||||
"Description" => "Text",
|
||||
"Status" => "Enum('Active, Injured, Retired')",
|
||||
"Status" => "Enum(array('Active', 'Injured', 'Retired'))",
|
||||
"Birthday" => "Date"
|
||||
);
|
||||
}
|
||||
@ -393,7 +393,7 @@ the default behavior by making a function called "get`<fieldname>`" or
|
||||
:::php
|
||||
class Player extends DataObject {
|
||||
private static $db = array(
|
||||
"Status" => "Enum('Active, Injured, Retired')"
|
||||
"Status" => "Enum(array('Active', 'Injured', 'Retired'))"
|
||||
);
|
||||
|
||||
// access through $myPlayer->Status
|
||||
|
@ -61,6 +61,13 @@ To let browsers know which language they're displaying a document in, you can de
|
||||
Setting the '<html>' attribute is the most commonly used technique. There are other ways to specify content languages
|
||||
(meta tags, HTTP headers), explained in this [w3.org article](http://www.w3.org/International/tutorials/language-decl/).
|
||||
|
||||
You can also set the [script direction](http://www.w3.org/International/questions/qa-scripts),
|
||||
which is determined by the current locale, in order to indicate the preferred flow of characters
|
||||
and default alignment of paragraphs and tables to browsers.
|
||||
|
||||
:::html
|
||||
<html lang="$ContentLocale" dir="$i18nScriptDirection">
|
||||
|
||||
### Date and time formats
|
||||
|
||||
Formats can be set globally in the i18n class. These settings are currently only picked up by the CMS, you'll need
|
||||
|
@ -432,7 +432,7 @@ a [api:PasswordValidator]:
|
||||
$validator = new PasswordValidator();
|
||||
$validator->minLength(7);
|
||||
$validator->checkHistoricalPasswords(6);
|
||||
$validator->characterStrength('lowercase','uppercase','digits','punctuation');
|
||||
$validator->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
||||
Member::set_password_validator($validator);
|
||||
|
||||
In addition, you can tighten password security with the following configuration settings:
|
||||
|
@ -130,7 +130,7 @@ class FieldList extends ArrayList {
|
||||
// Check if a field by the same name exists in this tab
|
||||
if($insertBefore) {
|
||||
$tab->insertBefore($field, $insertBefore);
|
||||
} elseif($tab->fieldByName($field->getName())) {
|
||||
} elseif(($name = $field->getName()) && $tab->fieldByName($name)) {
|
||||
// It exists, so we need to replace the old one
|
||||
$this->replaceField($field->getName(), $field);
|
||||
} else {
|
||||
|
@ -147,17 +147,17 @@ class FormField extends RequestHandler {
|
||||
|
||||
if($content || $tag != 'input') {
|
||||
return "<$tag$preparedAttributes>$content</$tag>";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "<$tag$preparedAttributes />";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new field.
|
||||
* Creates a new field.
|
||||
*
|
||||
* @param string $name The internal field name, passed to forms.
|
||||
* @param string $title The field label.
|
||||
* @param string $title The human-readable field label.
|
||||
* @param mixed $value The value of the field.
|
||||
*/
|
||||
public function __construct($name, $title = null, $value = null) {
|
||||
@ -194,7 +194,7 @@ class FormField extends RequestHandler {
|
||||
|
||||
/**
|
||||
* Returns the HTML ID for the form field holder element.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function HolderID() {
|
||||
@ -240,7 +240,7 @@ class FormField extends RequestHandler {
|
||||
|
||||
/**
|
||||
* Returns the field message type, used by form validation.
|
||||
*
|
||||
*
|
||||
* Arbitrary value which is mostly used for CSS classes in the rendered HTML,
|
||||
* e.g. "required". Use {@link setError()} to set this property.
|
||||
*
|
||||
@ -495,7 +495,7 @@ class FormField extends RequestHandler {
|
||||
|
||||
/**
|
||||
* Set the field value.
|
||||
*
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return FormField.
|
||||
@ -552,7 +552,7 @@ class FormField extends RequestHandler {
|
||||
*/
|
||||
public function securityTokenEnabled() {
|
||||
$form = $this->getForm();
|
||||
|
||||
|
||||
if(!$form) {
|
||||
return false;
|
||||
}
|
||||
@ -605,7 +605,7 @@ class FormField extends RequestHandler {
|
||||
|
||||
/**
|
||||
* Set name of template (without path or extension).
|
||||
*
|
||||
*
|
||||
* Caution: Not consistently implemented in all subclasses, please check
|
||||
* the {@link Field()} method on the subclass for support.
|
||||
*
|
||||
@ -838,8 +838,8 @@ class FormField extends RequestHandler {
|
||||
} else {
|
||||
$clone = $this->castedCopy('ReadonlyField');
|
||||
$clone->setReadonly(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
|
@ -45,8 +45,8 @@ class MoneyField extends FormField {
|
||||
*/
|
||||
public function Field($properties = array()) {
|
||||
return "<div class=\"fieldgroup\">" .
|
||||
"<div class=\"fieldgroupField\">" . $this->fieldCurrency->SmallFieldHolder() . "</div>" .
|
||||
"<div class=\"fieldgroupField\">" . $this->fieldAmount->SmallFieldHolder() . "</div>" .
|
||||
"<div class=\"fieldgroup-field\">" . $this->fieldCurrency->SmallFieldHolder() . "</div>" .
|
||||
"<div class=\"fieldgroup-field\">" . $this->fieldAmount->SmallFieldHolder() . "</div>" .
|
||||
"</div>";
|
||||
}
|
||||
|
||||
|
@ -2468,6 +2468,25 @@ class i18n extends Object implements TemplateGlobalProvider {
|
||||
public static function set_default_locale($locale) {
|
||||
self::$default_locale = $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script direction in format compatible with the HTML "dir" attribute.
|
||||
*
|
||||
* @see http://www.w3.org/International/tutorials/bidi-xhtml/
|
||||
* @param String $locale Optional locale incl. region (underscored)
|
||||
* @return String "rtl" or "ltr"
|
||||
*/
|
||||
public static function get_script_direction($locale = null) {
|
||||
require_once 'Zend/Locale/Data.php';
|
||||
if(!$locale) $locale = i18n::get_locale();
|
||||
try {
|
||||
$dir = Zend_Locale_Data::getList($locale, 'layout');
|
||||
} catch(Zend_Locale_Exception $e) {
|
||||
$dir = Zend_Locale_Data::getList(i18n::get_lang_from_locale($locale), 'layout');
|
||||
}
|
||||
|
||||
return ($dir && $dir['characters'] == 'right-to-left') ? 'rtl' : 'ltr';
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes all available language files for a certain defined locale.
|
||||
@ -2506,7 +2525,8 @@ class i18n extends Object implements TemplateGlobalProvider {
|
||||
$sortedModules = array();
|
||||
foreach ($order as $module) {
|
||||
if (isset($modules[$module])) $sortedModules[$module] = $modules[$module];
|
||||
}
|
||||
}
|
||||
$sortedModules = array_reverse($sortedModules, true);
|
||||
|
||||
// Loop in reverse order, meaning the translator with the highest priority goes first
|
||||
$translators = array_reverse(self::get_translators(), true);
|
||||
@ -2587,6 +2607,7 @@ class i18n extends Object implements TemplateGlobalProvider {
|
||||
return array(
|
||||
'i18nLocale' => 'get_locale',
|
||||
'get_locale',
|
||||
'i18nScriptDirection' => 'get_script_direction',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -348,7 +348,7 @@ en:
|
||||
ERRORLOCKEDOUT2: 'Your account has been temporarily disabled because of too many failed attempts at logging in. Please try again in {count} minutes.'
|
||||
ERRORNEWPASSWORD: 'You have entered your new password differently, try again'
|
||||
ERRORPASSWORDNOTMATCH: 'Your current password does not match, please try again'
|
||||
ERRORWRONGCRED: 'That doesn''t seem to be the right e-mail address or password. Please try again.'
|
||||
ERRORWRONGCRED: 'The provided details don''t seem to be correct. Please try again.'
|
||||
FIRSTNAME: 'First Name'
|
||||
INTERFACELANG: 'Interface Language'
|
||||
INVALIDNEWPASSWORD: 'We couldn''t accept that password: {password}'
|
||||
|
@ -2801,7 +2801,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @param string|array $limit A limit expression to be inserted into the LIMIT clause.
|
||||
* @param string $containerClass The container class to return the results in.
|
||||
*
|
||||
* @return mixed The objects matching the filter, in the class specified by $containerClass
|
||||
* @return DataList
|
||||
*/
|
||||
public static function get($callerClass = null, $filter = "", $sort = "", $join = "", $limit = null,
|
||||
$containerClass = 'DataList') {
|
||||
|
@ -50,6 +50,13 @@ class Oembed {
|
||||
protected static function find_endpoint($url) {
|
||||
foreach(self::get_providers() as $scheme=>$endpoint) {
|
||||
if(self::matches_scheme($url, $scheme)) {
|
||||
$protocol = Director::is_https() ? 'https' : 'http';
|
||||
|
||||
if (is_array($endpoint)) {
|
||||
if (array_key_exists($protocol, $endpoint)) $endpoint = $endpoint[$protocol];
|
||||
else $endpoint = reset($endpoint);
|
||||
}
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
}
|
||||
@ -66,6 +73,7 @@ class Oembed {
|
||||
protected static function matches_scheme($url, $scheme) {
|
||||
$urlInfo = parse_url($url);
|
||||
$schemeInfo = parse_url($scheme);
|
||||
|
||||
foreach($schemeInfo as $k=>$v) {
|
||||
if(!array_key_exists($k, $urlInfo)) {
|
||||
return false;
|
||||
|
@ -227,9 +227,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
$e = PasswordEncryptor::create_for_algorithm($this->PasswordEncryption);
|
||||
if(!$e->check($this->Password, $password, $this->Salt, $this)) {
|
||||
$iidentifierField =
|
||||
$result->error(_t (
|
||||
'Member.ERRORWRONGCRED',
|
||||
'That doesn\'t seem to be the right e-mail address or password. Please try again.'
|
||||
'Member.ERRORWRONGCREDS',
|
||||
'The provided details don\'t seem to be correct. Please try again.'
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* $pwdVal = new PasswordValidator();
|
||||
* $pwdValidator->minLength(7);
|
||||
* $pwdValidator->checkHistoricalPasswords(6);
|
||||
* $pwdValidator->characterStrength('lowercase','uppercase','digits','punctuation');
|
||||
* $pwdValidator->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
||||
*
|
||||
* Member::set_password_validator($pwdValidator);
|
||||
* </code>
|
||||
|
@ -344,6 +344,72 @@ class DirectorTest extends SapphireTest {
|
||||
|
||||
$_SERVER = $origServer;
|
||||
}
|
||||
|
||||
public function testRequestFilterInDirectorTest() {
|
||||
$filter = new TestRequestFilter;
|
||||
|
||||
$processor = new RequestProcessor(array($filter));
|
||||
|
||||
$currentProcessor = Injector::inst()->get('RequestProcessor');
|
||||
|
||||
Injector::inst()->registerService($processor, 'RequestProcessor');
|
||||
|
||||
$response = Director::test('some-dummy-url');
|
||||
|
||||
$this->assertEquals(1, $filter->preCalls);
|
||||
$this->assertEquals(1, $filter->postCalls);
|
||||
|
||||
$filter->failPost = true;
|
||||
|
||||
$this->setExpectedException('SS_HTTPResponse_Exception');
|
||||
|
||||
$response = Director::test('some-dummy-url');
|
||||
|
||||
$this->assertEquals(2, $filter->preCalls);
|
||||
$this->assertEquals(2, $filter->postCalls);
|
||||
|
||||
$filter->failPre = true;
|
||||
|
||||
$response = Director::test('some-dummy-url');
|
||||
|
||||
$this->assertEquals(3, $filter->preCalls);
|
||||
|
||||
// preCall 'false' will trigger an exception and prevent post call execution
|
||||
$this->assertEquals(2, $filter->postCalls);
|
||||
|
||||
// swap back otherwise our wrapping test execution request may fail in the post processing later
|
||||
Injector::inst()->registerService($currentProcessor, 'RequestProcessor');
|
||||
}
|
||||
}
|
||||
|
||||
class TestRequestFilter implements RequestFilter, TestOnly {
|
||||
public $preCalls = 0;
|
||||
public $postCalls = 0;
|
||||
|
||||
public $failPre = false;
|
||||
public $failPost = false;
|
||||
|
||||
public function preRequest(\SS_HTTPRequest $request, \Session $session, \DataModel $model) {
|
||||
++$this->preCalls;
|
||||
|
||||
if ($this->failPre) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function postRequest(\SS_HTTPRequest $request, \SS_HTTPResponse $response, \DataModel $model) {
|
||||
++$this->postCalls;
|
||||
|
||||
if ($this->failPost) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
$this->preCalls = 0;
|
||||
$this->postCalls = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DirectorTestRequest_Controller extends Controller implements TestOnly {
|
||||
|
235
tests/email/EmailTest.php
Normal file
235
tests/email/EmailTest.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class EmailTest extends SapphireTest {
|
||||
|
||||
public function testAttachFiles() {
|
||||
$email = new Email();
|
||||
|
||||
$email->attachFileFromString('foo bar', 'foo.txt', 'text/plain');
|
||||
$email->attachFile(__DIR__ . '/fixtures/attachment.txt', null, 'text/plain');
|
||||
|
||||
$this->assertEquals(
|
||||
array('contents'=>'foo bar', 'filename'=>'foo.txt', 'mimetype'=>'text/plain'),
|
||||
$email->attachments[0],
|
||||
'File is attached correctly from string'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('contents'=>'Hello, I\'m a text document.', 'filename'=>'attachment.txt', 'mimetype'=>'text/plain'),
|
||||
$email->attachments[1],
|
||||
'File is attached correctly from file'
|
||||
);
|
||||
}
|
||||
|
||||
public function testCustomHeaders() {
|
||||
$email = new Email();
|
||||
|
||||
$email->addCustomHeader('Cc', 'test1@example.com');
|
||||
$email->addCustomHeader('Bcc', 'test2@example.com');
|
||||
|
||||
$this->assertEmpty(
|
||||
$email->customHeaders,
|
||||
'addCustomHeader() doesn\'t add Cc and Bcc headers'
|
||||
);
|
||||
|
||||
$email->addCustomHeader('Reply-To', 'test1@example.com');
|
||||
$this->assertEquals(
|
||||
array('Reply-To' => 'test1@example.com'),
|
||||
$email->customHeaders,
|
||||
'addCustomHeader() adds headers'
|
||||
);
|
||||
|
||||
$email->addCustomHeader('Reply-To', 'test2@example.com');
|
||||
$this->assertEquals(
|
||||
array('Reply-To' => 'test1@example.com, test2@example.com'),
|
||||
$email->customHeaders,
|
||||
'addCustomHeader() appends data to existing headers'
|
||||
);
|
||||
}
|
||||
|
||||
public function testValidEmailAddress() {
|
||||
$validEmails = array('test@example.com', 'test-123@example.sub.com');
|
||||
$invalidEmails = array('foo.bar@', '@example.com', 'foo@');
|
||||
|
||||
foreach ($validEmails as $email) {
|
||||
$this->assertEquals(
|
||||
$email,
|
||||
Email::validEmailAddress($email),
|
||||
'validEmailAddress() returns a valid email address'
|
||||
);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
Email::is_valid_address($email),
|
||||
'is_valid_address() returns 1 for a valid email address'
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($invalidEmails as $email) {
|
||||
$this->assertFalse(
|
||||
Email::validEmailAddress($email),
|
||||
'validEmailAddress() returns false for an invalid email address'
|
||||
);
|
||||
$this->assertEquals(
|
||||
0,
|
||||
Email::is_valid_address($email),
|
||||
'is_valid_address() returns 0 for an invalid email address'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testObfuscate() {
|
||||
$emailAddress = 'test-1@example.com';
|
||||
|
||||
$direction = Email::obfuscate($emailAddress, 'direction');
|
||||
$visible = Email::obfuscate($emailAddress, 'visible');
|
||||
$hex = Email::obfuscate($emailAddress, 'hex');
|
||||
|
||||
$this->assertEquals(
|
||||
'<span class="codedirection">moc.elpmaxe@1-tset</span>',
|
||||
$direction,
|
||||
'obfuscate() correctly reverses the email direction'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'test [dash] 1 [at] example [dot] com',
|
||||
$visible,
|
||||
'obfuscate() correctly obfuscates email characters'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'test-1@examp'
|
||||
. 'le.com',
|
||||
$hex,
|
||||
'obfuscate() correctly returns hex representation of email'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSendPlain() {
|
||||
// Set custom $project - used in email headers
|
||||
global $project;
|
||||
$oldProject = $project;
|
||||
$project = 'emailtest';
|
||||
|
||||
Email::set_mailer(new EmailTest_Mailer());
|
||||
$email = new Email(
|
||||
'from@example.com',
|
||||
'to@example.com',
|
||||
'Test send plain',
|
||||
'Testing Email->sendPlain()',
|
||||
null,
|
||||
'cc@example.com',
|
||||
'bcc@example.com'
|
||||
);
|
||||
$email->attachFile(__DIR__ . '/fixtures/attachment.txt', null, 'text/plain');
|
||||
$email->addCustomHeader('foo', 'bar');
|
||||
$sent = $email->sendPlain(123);
|
||||
|
||||
// Restore old project name after sending
|
||||
$project = $oldProject;
|
||||
|
||||
$this->assertEquals('to@example.com', $sent['to']);
|
||||
$this->assertEquals('from@example.com', $sent['from']);
|
||||
$this->assertEquals('Test send plain', $sent['subject']);
|
||||
$this->assertEquals('Testing Email->sendPlain()', $sent['content']);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
0 => array(
|
||||
'contents'=>'Hello, I\'m a text document.',
|
||||
'filename'=>'attachment.txt',
|
||||
'mimetype'=>'text/plain'
|
||||
)
|
||||
),
|
||||
$sent['files']
|
||||
);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'foo' => 'bar',
|
||||
'X-SilverStripeMessageID' => 'emailtest.123',
|
||||
'X-SilverStripeSite' => 'emailtest',
|
||||
'Cc' => 'cc@example.com',
|
||||
'Bcc' => 'bcc@example.com'
|
||||
),
|
||||
$sent['customheaders']
|
||||
);
|
||||
}
|
||||
|
||||
public function testSendHTML() {
|
||||
// Set custom $project - used in email headers
|
||||
global $project;
|
||||
$oldProject = $project;
|
||||
$project = 'emailtest';
|
||||
|
||||
Email::set_mailer(new EmailTest_Mailer());
|
||||
$email = new Email(
|
||||
'from@example.com',
|
||||
'to@example.com',
|
||||
'Test send plain',
|
||||
'Testing Email->sendPlain()',
|
||||
null,
|
||||
'cc@example.com',
|
||||
'bcc@example.com'
|
||||
);
|
||||
$email->attachFile(__DIR__ . '/fixtures/attachment.txt', null, 'text/plain');
|
||||
$email->addCustomHeader('foo', 'bar');
|
||||
$sent = $email->send(123);
|
||||
|
||||
// Restore old project name after sending
|
||||
$project = $oldProject;
|
||||
|
||||
$this->assertEquals('to@example.com', $sent['to']);
|
||||
$this->assertEquals('from@example.com', $sent['from']);
|
||||
$this->assertEquals('Test send plain', $sent['subject']);
|
||||
$this->assertContains('Testing Email->sendPlain()', $sent['content']);
|
||||
$this->assertNull($sent['plaincontent']);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
0 => array(
|
||||
'contents'=>'Hello, I\'m a text document.',
|
||||
'filename'=>'attachment.txt',
|
||||
'mimetype'=>'text/plain'
|
||||
)
|
||||
),
|
||||
$sent['files']
|
||||
);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'foo' => 'bar',
|
||||
'X-SilverStripeMessageID' => 'emailtest.123',
|
||||
'X-SilverStripeSite' => 'emailtest',
|
||||
'Cc' => 'cc@example.com',
|
||||
'Bcc' => 'bcc@example.com'
|
||||
),
|
||||
$sent['customheaders']
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EmailTest_Mailer extends Mailer {
|
||||
|
||||
public function sendHTML($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false,
|
||||
$plainContent = false) {
|
||||
return array(
|
||||
'to' => $to,
|
||||
'from' => $from,
|
||||
'subject' => $subject,
|
||||
'content' => $htmlContent,
|
||||
'files' => $attachedFiles,
|
||||
'customheaders' => $customheaders,
|
||||
'plaincontent' => $plainContent
|
||||
);
|
||||
}
|
||||
|
||||
public function sendPlain($to, $from, $subject, $plainContent, $attachedFiles = false, $customheaders = false) {
|
||||
return array(
|
||||
'to' => $to,
|
||||
'from' => $from,
|
||||
'subject' => $subject,
|
||||
'content' => $plainContent,
|
||||
'files' => $attachedFiles,
|
||||
'customheaders' => $customheaders
|
||||
);
|
||||
}
|
||||
|
||||
}
|
1
tests/email/fixtures/attachment.txt
Normal file
1
tests/email/fixtures/attachment.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello, I'm a text document.
|
@ -48,6 +48,43 @@ class FieldListTest extends SapphireTest {
|
||||
/* We'll have 3 fields inside the tab */
|
||||
$this->assertEquals(3, $tab->Fields()->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that groups can be added to a fieldlist
|
||||
*/
|
||||
public function testFieldgroup() {
|
||||
$fields = new FieldList();
|
||||
$tab = new Tab('Root');
|
||||
$fields->push($tab);
|
||||
|
||||
$fields->addFieldsToTab('Root', array(
|
||||
$group1 = new FieldGroup(
|
||||
new TextField('Name'),
|
||||
new EmailField('Email')
|
||||
),
|
||||
$group2 = new FieldGroup(
|
||||
new TextField('Company'),
|
||||
new TextareaField('Address')
|
||||
)
|
||||
));
|
||||
|
||||
/* Check that the field objects were created */
|
||||
$this->assertNotNull($fields->dataFieldByName('Name'));
|
||||
$this->assertNotNull($fields->dataFieldByName('Email'));
|
||||
$this->assertNotNull($fields->dataFieldByName('Company'));
|
||||
$this->assertNotNull($fields->dataFieldByName('Address'));
|
||||
|
||||
/* The field objects in the set should be the same as the ones we created */
|
||||
$this->assertSame($fields->dataFieldByName('Name'), $group1->fieldByName('Name'));
|
||||
$this->assertSame($fields->dataFieldByName('Email'), $group1->fieldByName('Email'));
|
||||
$this->assertSame($fields->dataFieldByName('Company'), $group2->fieldByName('Company'));
|
||||
$this->assertSame($fields->dataFieldByName('Address'), $group2->fieldByName('Address'));
|
||||
|
||||
/* We'll have 2 fields directly inside the tab */
|
||||
$this->assertEquals(2, $tab->Fields()->Count());
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test removing a single field from a tab in a set.
|
||||
|
@ -1,3 +1,4 @@
|
||||
de:
|
||||
i18nTestModule:
|
||||
OTHERENTITY: Other Entity (de)
|
||||
PRIORITYNOTICE: High Module Priority (de)
|
||||
OTHERENTITY: Other Entity (de)
|
||||
|
@ -11,5 +11,6 @@ de:
|
||||
WITHNAMESPACE: Include Entity with Namespace (de)
|
||||
LAYOUTTEMPLATE: Layout Template (de)
|
||||
SPRINTFNAMESPACE: My replacement: %s (de)
|
||||
PRIORITYNOTICE: Low Module Priority (de)
|
||||
i18nTestModuleInclude.ss:
|
||||
SPRINTFINCLUDENAMESPACE: My include replacement: %s (de)
|
||||
SPRINTFINCLUDENAMESPACE: My include replacement: %s (de)
|
||||
|
@ -435,6 +435,9 @@ class i18nTest extends SapphireTest {
|
||||
$this->assertFalse($adapter->isTranslated('i18nTestModule.ENTITY', 'af'),
|
||||
'Non-existing unloaded entity not available before call'
|
||||
);
|
||||
|
||||
// set _fakewebroot module priority
|
||||
Config::inst()->update('i18n', 'module_priority', array('subfolder','i18ntestmodule'));
|
||||
|
||||
i18n::include_by_locale('de');
|
||||
|
||||
@ -444,6 +447,11 @@ class i18nTest extends SapphireTest {
|
||||
$this->assertTrue($adapter->isTranslated('i18nTestTheme1.LAYOUTTEMPLATE', null, 'de'), 'Includes theme files');
|
||||
$this->assertTrue($adapter->isTranslated('i18nTestModule.OTHERENTITY', null, 'de'), 'Includes submodule files');
|
||||
|
||||
// check module priority
|
||||
$this->assertEquals($adapter->translate('i18nTestModule.PRIORITYNOTICE', 'de'),
|
||||
'High Module Priority (de)'
|
||||
);
|
||||
|
||||
SS_ClassLoader::instance()->popManifest();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,16 @@
|
||||
<?php
|
||||
|
||||
class OembedTest extends SapphireTest {
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
Config::nest();
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
Config::unnest();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testGetOembedFromUrl() {
|
||||
Config::inst()->update('Oembed', 'providers', array(
|
||||
'http://*.silverstripe.com/watch*'=>'http://www.silverstripe.com/oembed/'
|
||||
@ -37,4 +47,53 @@ class OembedTest extends SapphireTest {
|
||||
$this->assertEquals($query['maxheight'], 'foo', 'Magically creates maxheight option');
|
||||
$this->assertEquals($query['maxwidth'], 'bar', 'Magically creates maxwidth option');
|
||||
}
|
||||
|
||||
public function testRequestProtocolReflectedInGetOembedFromUrl() {
|
||||
Config::inst()->update('Oembed', 'providers', array(
|
||||
'http://*.silverstripe.com/watch*'=> array(
|
||||
'http' => 'http://www.silverstripe.com/oembed/',
|
||||
'https' => 'https://www.silverstripe.com/oembed/?scheme=https',
|
||||
),
|
||||
'https://*.silverstripe.com/watch*'=> array(
|
||||
'http' => 'http://www.silverstripe.com/oembed/',
|
||||
'https' => 'https://www.silverstripe.com/oembed/?scheme=https',
|
||||
)
|
||||
));
|
||||
|
||||
Config::inst()->update('Director', 'alternate_protocol', 'http');
|
||||
|
||||
foreach(array('http', 'https') as $protocol) {
|
||||
$url = $protocol.'://www.silverstripe.com/watch12345';
|
||||
$result = Oembed::get_oembed_from_url($url);
|
||||
|
||||
$this->assertInstanceOf('Oembed_Result', $result);
|
||||
$this->assertEquals($result->getOembedURL(),
|
||||
'http://www.silverstripe.com/oembed/?format=json&url='.urlencode($url),
|
||||
'Returns http based URLs when request is over http, regardless of source URL');
|
||||
}
|
||||
|
||||
Config::inst()->update('Director', 'alternate_protocol', 'https');
|
||||
|
||||
foreach(array('http', 'https') as $protocol) {
|
||||
$url = $protocol.'://www.silverstripe.com/watch12345';
|
||||
$result = Oembed::get_oembed_from_url($url);
|
||||
|
||||
$this->assertInstanceOf('Oembed_Result', $result);
|
||||
$this->assertEquals($result->getOembedURL(),
|
||||
'https://www.silverstripe.com/oembed/?scheme=https&format=json&url='.urlencode($url),
|
||||
'Returns https based URLs when request is over https, regardless of source URL');
|
||||
}
|
||||
|
||||
Config::inst()->update('Director', 'alternate_protocol', 'foo');
|
||||
|
||||
foreach(array('http', 'https') as $protocol) {
|
||||
$url = $protocol.'://www.silverstripe.com/watch12345';
|
||||
$result = Oembed::get_oembed_from_url($url);
|
||||
|
||||
$this->assertInstanceOf('Oembed_Result', $result);
|
||||
$this->assertEquals($result->getOembedURL(),
|
||||
'http://www.silverstripe.com/oembed/?format=json&url='.urlencode($url),
|
||||
'When request protocol doesn\'t have specific handler, fall back to first option');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user