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

Conflicts:
	.travis.yml
	admin/code/CMSProfileController.php
	admin/tests/LeftAndMainTest.php
	control/HTTP.php
	security/Permission.php
	tests/forms/FormTest.php
	tests/model/ArrayListTest.php
	tests/security/PermissionTest.php
This commit is contained in:
Damian Mooyman 2015-09-09 14:35:29 +12:00
commit 309ac0d196
25 changed files with 441 additions and 169 deletions

View File

@ -7,9 +7,6 @@ addons:
packages:
- tidy
php:
- 5.4
env:
global:
- CORE_RELEASE=3
@ -17,28 +14,19 @@ env:
- "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
- DB=SQLITE
- DB=PGSQL
matrix:
allow_failures:
- php: hhvm
include:
- php: 5.4
env: DB=MYSQL PDO=1
- php: 5.5
env: DB=MYSQL
- php: 5.6
env: DB=MYSQL
- php: 5.4
env: DB=MYSQL BEHAT_TEST=1
- php: 5.3
env: DB=MYSQL
- php: hhvm
env: DB=MYSQL
- php: 5.4
env: DB=PGSQL
- php: 5.5
env: DB=SQLITE
- php: 5.6
env: DB=MYSQL PDO=1
- php: 5.6
env: DB=MYSQL BEHAT_TEST=1
before_script:
- composer self-update || true
@ -52,6 +40,7 @@ before_script:
script:
- "if [ \"$BEHAT_TEST\" = \"\" ]; then vendor/bin/phpunit framework/tests; fi"
- "if [ \"$BEHAT_TEST\" = \"\" ]; then vendor/bin/phpunit framework/admin/tests; fi"
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then vendor/bin/behat @framework; fi"
after_failure:
@ -64,11 +53,6 @@ branches:
- 2.3
- translation-staging
notifications:
irc:
channels:
- "irc.freenode.org#silverstripe"
# global:
# - secure: "AZmjVPtUD8JBA7ag4ULlEwEKXSEZbIUjDHeRBFugaOtdsn5yigGLmwYbzsg2tq7k7UkdbbAlGct0SUbiRJb9F2wPA5+eUd/p49fgDIU6CTSWIlT87H2BwgOrxKwS9sDwxLptPFM6vWQ8JKYSNGmVIepie9kQZbu4L2k5k6B69jQ="
# - secure: "f3kKpUn9cS5K+p/E52cMqN18cDApol/43LanDmHO6mo3iRAztk3jZLyfNOUq6JASKMqdh8+9kencRpEoaAYbcQnDPoZsT9POResiJ9/ADKB6RwWy+lcFHUp9E2Zf/x2VRh9FmXEguDhpWzkJqzWYJGCSig1IBp/+TjzKnsjQHIY="

View File

@ -50,20 +50,20 @@ class CMSProfileController extends LeftAndMain {
}
public function canView($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser();
if(!$member && $member !== false) $member = Member::currentUser();
// cms menus only for logged-in members
if(!$member) return false;
// Only check for generic CMS permissions
// Check they can access the CMS and that they are trying to edit themselves
if(
!Permission::checkMember($member, "CMS_ACCESS_LeftAndMain")
&& !Permission::checkMember($member, "CMS_ACCESS_CMSMain")
Permission::checkMember($member, "CMS_ACCESS")
&& $member->ID === Member::currentUserID()
) {
return false;
return true;
}
return true;
return false;
}
public function save($data, $form) {

View File

@ -669,7 +669,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai
.htmleditorfield-dialog.ui-dialog-content { padding: 0; position: relative; }
.htmleditorfield-dialog .htmleditorfield-from-web .CompositeField { overflow: hidden; *zoom: 1; }
.htmleditorfield-dialog .htmleditorfield-from-web #RemoteURL { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; width: 55%; max-width: 512px; float: left; position: relative; }
.htmleditorfield-dialog .htmleditorfield-from-web #RemoteURL label { position: absolute; left: 8px; top: 13px; font-weight: normal; color: #888; }
.htmleditorfield-dialog .htmleditorfield-from-web #RemoteURL label { position: absolute; left: 8px; top: 13px; font-weight: normal; color: #888; width: 35px; padding-right: 0; }
.htmleditorfield-dialog .htmleditorfield-from-web #RemoteURL .middleColumn { margin-left: 0; }
.htmleditorfield-dialog .htmleditorfield-from-web #RemoteURL input.remoteurl { padding-left: 40px; max-width: 350px; }
.htmleditorfield-dialog .htmleditorfield-from-web button.add-url { margin-top: 20px; overflow: hidden; *zoom: 1; border: none; background: none; opacity: 0.8; cursor: hand; }

View File

@ -179,18 +179,14 @@
* Store the preview options for this page.
*/
saveState : function(name, value) {
if(!window.localStorage) return;
window.localStorage.setItem('cms-preview-state-' + name, value);
if(this._supportsLocalStorage()) window.localStorage.setItem('cms-preview-state-' + name, value);
},
/**
* Load previously stored preferences
*/
loadState : function(name) {
if(!window.localStorage) return;
return window.localStorage.getItem('cms-preview-state-' + name);
if(this._supportsLocalStorage()) return window.localStorage.getItem('cms-preview-state-' + name);
},
/**
@ -276,6 +272,23 @@
this._super();
},
/**
* Detect and use localStorage if available. In IE11 windows 8.1 call to window.localStorage was throwing out an access denied error in some cases which was causing the preview window not to display correctly in the CMS admin area.
*/
_supportsLocalStorage: function() {
var uid = new Date;
var storage;
var result;
try {
(storage = window.localStorage).setItem(uid, uid);
result = storage.getItem(uid) == uid;
storage.removeItem(uid);
return result && storage;
} catch (exception) {
console.warn('localStorge is not available due to current browser / system settings.');
}
},
/**
* Set the preview to unavailable - could be still visible. This is purely visual.

View File

@ -1422,11 +1422,14 @@ body.cms-dialog {
float:left;
position: relative;
label {
position: absolute;
left: 8px;
top: 13px;
font-weight: normal; color: #888;
label {
position: absolute;
left: 8px;
top: 13px;
font-weight: normal;
color: #888;
width: 35px;
padding-right: 0;
}
.middleColumn {

View File

@ -17,7 +17,7 @@ class LeftAndMainTest extends FunctionalTest {
// @todo fix controller stack problems and re-activate
//$this->autoFollowRedirection = false;
CMSMenu::populate_menu();
$this->resetMenu();
$this->backupCss = Config::inst()->get('LeftAndMain', 'extra_requirements_css');
$this->backupJs = Config::inst()->get('LeftAndMain', 'extra_requirements_javascript');
@ -34,6 +34,23 @@ class LeftAndMainTest extends FunctionalTest {
Requirements::set_combined_files_enabled(false);
}
/**
* Clear menu to default state as per LeftAndMain::init()
*/
protected function resetMenu() {
CMSMenu::clear_menu();
CMSMenu::populate_menu();
CMSMenu::add_link(
'Help',
_t('LeftAndMain.HELP', 'Help', 'Menu title'),
LeftAndMain::config()->help_link,
-2,
array(
'target' => '_blank'
)
);
}
public function tearDown() {
parent::tearDown();
@ -130,7 +147,8 @@ class LeftAndMainTest extends FunctionalTest {
$adminuser = $this->objFromFixture('Member','admin');
$this->session()->inst_set('loggedInAs', $adminuser->ID);
$menuItems = singleton('LeftAndMain')->MainMenu();
$this->resetMenu();
$menuItems = singleton('LeftAndMain')->MainMenu(false);
foreach($menuItems as $menuItem) {
$link = $menuItem->Link;
@ -159,6 +177,7 @@ class LeftAndMainTest extends FunctionalTest {
// anonymous user
$this->session()->inst_set('loggedInAs', null);
$this->resetMenu();
$menuItems = singleton('LeftAndMain')->MainMenu(false);
$this->assertEquals(
array_map($allValsFn, $menuItems->column('Code')),
@ -167,19 +186,25 @@ class LeftAndMainTest extends FunctionalTest {
);
// restricted cms user
$this->session()->inst_set('loggedInAs', $securityonlyuser->ID);
$this->logInAs($securityonlyuser);
$this->resetMenu();
$menuItems = singleton('LeftAndMain')->MainMenu(false);
$menuItems = array_map($allValsFn, $menuItems->column('Code'));
sort($menuItems);
$this->assertEquals(
$menuItems,
array('Help', 'SecurityAdmin'),
array('CMSProfileController', 'SecurityAdmin','Help'),
'Groups with limited access can only access the interfaces they have permissions for'
);
// all cms sections user
$this->session()->inst_set('loggedInAs', $allcmssectionsuser->ID);
$this->logInAs($allcmssectionsuser);
$this->resetMenu();
$menuItems = singleton('LeftAndMain')->MainMenu(false);
$this->assertContains('CMSProfileController',
array_map($allValsFn, $menuItems->column('Code')),
'Group with CMS_ACCESS_LeftAndMain permission can edit own profile'
);
$this->assertContains('SecurityAdmin',
array_map($allValsFn, $menuItems->column('Code')),
'Group with CMS_ACCESS_LeftAndMain permission can access all sections'
@ -190,7 +215,8 @@ class LeftAndMainTest extends FunctionalTest {
);
// admin
$this->session()->inst_set('loggedInAs', $adminuser->ID);
$this->logInAs($adminuser);
$this->resetMenu();
$menuItems = singleton('LeftAndMain')->MainMenu(false);
$this->assertContains(
'SecurityAdmin',
@ -270,6 +296,8 @@ class LeftAndMainTest_Object extends DataObject implements TestOnly {
'Sort' => 'Int',
);
private static $default_sort = '"Sort"';
private static $extensions = array(
'Hierarchy'
);

View File

@ -307,6 +307,9 @@ class HTTP {
/**
* Add the appropriate caching headers to the response, including If-Modified-Since / 304 handling.
* Note that setting HTTP::$cache_age will overrule any cache headers set by PHP's
* session_cache_limiter functionality. It is your responsibility to ensure only cacheable data
* is in fact cached, and HTTP::$cache_age isn't set when the HTTP body contains session-specific content.
*
* @param SS_HTTPResponse $body The SS_HTTPResponse object to augment. Omitted the argument or passing a string is
* deprecated; in these cases, the headers are output directly.
@ -346,6 +349,11 @@ class HTTP {
if($cacheAge > 0) {
$cacheControlHeaders['max-age'] = self::$cache_age;
// Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information,
// defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter).
// Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should
// prefer the caching information indicated through the "Cache-Control" header.
$responseHeaders["Pragma"] = "";
// To do: User-Agent should only be added in situations where you *are* actually
@ -370,6 +378,11 @@ class HTTP {
// (http://support.microsoft.com/kb/323308)
// Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options.
$cacheControlHeaders['max-age'] = 3;
// Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information,
// defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter).
// Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should
// prefer the caching information indicated through the "Cache-Control" header.
$responseHeaders["Pragma"] = "";
} else {
$cacheControlHeaders['no-cache'] = "true";

View File

@ -351,6 +351,27 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
* tearDown method that's called once per test class rather once per test method.
*/
public function tearDownOnce() {
// If we have made changes to the extensions present, then migrate the database schema.
if($this->extensionsToReapply || $this->extensionsToRemove) {
// @todo: This isn't strictly necessary to restore extensions, but only to ensure that
// Object::$extra_methods is properly flushed. This should be replaced with a simple
// flush mechanism for each $class.
//
// Remove extensions added for testing
foreach($this->extensionsToRemove as $class => $extensions) {
foreach($extensions as $extension) {
$class::remove_extension($extension);
}
}
// Reapply ones removed
foreach($this->extensionsToReapply as $class => $extensions) {
foreach($extensions as $extension) {
$class::add_extension($extension);
}
}
}
//unnest injector / config now that the test suite is over
// this will reset all the extensions on the object too (see setUpOnce)
Injector::unnest();
@ -400,7 +421,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
* Will collate all IDs form all fixtures if multiple fixtures are provided.
*
* @param string $className
* @return A map of fixture-identifier => object-id
* @return array A map of fixture-identifier => object-id
*/
protected function allFixtureIDs($className) {
return $this->getFixtureFactory()->getIds($className);

View File

@ -46,7 +46,7 @@ class TeamCityListener implements PHPUnit_Framework_TestListener {
public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {
$class = get_class($test);
$message = $this->escape($e->getMessage());
$message = $this->escape(PHPUnit_Framework_TestFailure::exceptionToString($e));
$trace = $this->escape($e->getTraceAsString());
echo "##teamcity[testFailed type='failure' name='{$class}.{$test->getName()}' message='$message'"
. " details='$trace']\n";

View File

@ -82,9 +82,9 @@ class TestRunner extends Controller {
* top of the loader stacks.
*/
public static function use_test_manifest() {
$flush = true;
if(isset($_GET['flush']) && $_GET['flush'] === '0') {
$flush = false;
$flush = false;
if(isset($_GET['flush']) && ($_GET['flush'] === '1' || $_GET['flush'] == 'all')) {
$flush = true;
}
$classManifest = new SS_ClassManifest(

View File

@ -114,7 +114,7 @@ So, your deployment process, as it relates to Composer, should be as follows:
Modules and themes managed by composer should not be committed with your projects source code. For more details read [Should I commit the dependencies in my vendor directory?](https://getcomposer.org/doc/faqs/should-i-commit-the-dependencies-in-my-vendor-directory.md).
Since SilverStripe modules are installed in to thier own folder, you have to manage your [.gitignore](http://git-scm.com/docs/gitignore) to ensure they are ignored from your repository.
Since SilverStripe modules are installed into their own folder, you have to manage your [.gitignore](http://git-scm.com/docs/gitignore) to ensure they are ignored from your repository.
Here is the default SilverStripe [.gitignore](http://git-scm.com/docs/gitignore) with the forum module ignored

View File

@ -124,7 +124,7 @@ Action methods can return one of four main things:
$this->response->addHeader("Content-type", "application/json");
return $this->response.
return $this->response;
}
For more information on how a URL gets mapped to an action see the [Routing](routing) documentation.

View File

@ -73,6 +73,10 @@ The `phpunit` binary should be used from the root directory of your website.
phpunit framework/tests '' flush=all
# Run tests with optional `$_GET` parameters (you need an empty second argument)
<div class="alert" markdown="1">
The manifest is not flushed when running tests. Add `flush=all` to the test command to do this (see above example.)
</div>
<div class="alert" markdown="1">
If phpunit is not installed globally on your machine, you may need to replace the above usage of `phpunit` with the full
path (e.g `vendor/bin/phpunit framework/tests`)

View File

@ -66,6 +66,36 @@ currently logged in member is assumed.
* On a request, $request->hasPermission("View", $member = null) can be called. See [datamodel](/topics/datamodel) for
information on request objects.
## Special cases
### ADMIN permissions
By default the config option `admin_implies_all` is true - this means that any user granted the `ADMIN` permission has
all other permissions granted to them. This is a type of cascading of permissions that is hard coded into the permission
system.
### CMS access permissions
Access to the CMS has a couple of special cases where permission codes can imply other permissions.
#### 1. Granting access to all CMS permissions
The `CMS_ACCESS_LeftAndMain` grants access to every single area of the CMS, without exception. Internally, this works by
adding the `CMS_ACCESS_LeftAndMain` code to the set of accepted codes when a `CMS_ACCESS_*` permission is required.
This works much like ADMIN permissions (see above)
#### 2. Checking for any access to the CMS
You can check if a user has access to the CMS by simply performing a check against `CMS_ACCESS`.
:::php
if (Permission::checkMember($member, 'CMS_ACCESS')) {
//user can access the CMS
}
Internally, this checks that the user has any of the defined `CMS_ACCESS_*` permissions.
## API Documentation
`[api:Permission]`

View File

@ -0,0 +1,42 @@
# 3.1.14-rc1
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### API Changes
* 2015-01-28 [782c4cb](https://github.com/silverstripe/silverstripe-framework/commit/782c4cbf6f5cde2fa4d45cdbd17552773a67f88f) Enable single-column fulltext filter search as fallback (Damian Mooyman)
### Bugfixes
* 2015-08-27 [899eb0b](https://github.com/silverstripe/silverstripe-framework/commit/899eb0b235859c843890c790e99c03f4fd4b825c) Use complete fieldlist for extracting data (Daniel Hensby)
* 2015-08-26 [2d4b743](https://github.com/silverstripe/silverstripe-framework/commit/2d4b743090935e7c10bd95e00398df7bfb5763af) Members can access their own profiles in CMS (Daniel Hensby)
* 2015-08-26 [0943b3b](https://github.com/silverstripe/silverstripe-framework/commit/0943b3b1a06e6c9130500532fd979c720b65c761) Recursion errors when sorting objects with circular dependencies (fixes #4464) (Loz Calver)
* 2015-08-20 [fc212e0](https://github.com/silverstripe/silverstripe-framework/commit/fc212e030c474d966ffb1821423ddcb3ae361b72) Fix illegalExtensions breaking tests. (Damian Mooyman)
* 2015-08-18 [8b638f5](https://github.com/silverstripe/silverstripe-framework/commit/8b638f56fb737dac18126c291297c87469eb7d0f) Using undefined var in ModelAdmin (Loz Calver)
* 2015-07-26 [5f5ce8a](https://github.com/silverstripe/silverstripe-framework/commit/5f5ce8a82c2bb1a29f9f8b7011d5cd990c34f128) Disable cache to prevent caching of build target (Damian Mooyman)
* 2015-07-16 [a3201d6](https://github.com/silverstripe/silverstripe-framework/commit/a3201d6ed9967179aa020802e6fb88d2a6a0e37e) $callerClass is undefined (Christopher Darling)
* 2015-07-08 [c7bd504](https://github.com/silverstripe/silverstripe-framework/commit/c7bd50427a4e0ad446502547b81648d78d354062) Fix cookie errors when running in CLI (Damian Mooyman)
* 2015-07-07 [5ace490](https://github.com/silverstripe/silverstripe-framework/commit/5ace4905c90be1373f49dbb0e1a579b279786a1c) Fix issue when SS_ALLOWED_HOSTS is run in CLI (Damian Mooyman)
* 2015-07-05 [a556b48](https://github.com/silverstripe/silverstripe-framework/commit/a556b4854a44b9dfe86c40140ec03d781d354d19) Fix of multiple i18nTextCollector issues: #3797, #3798, #3417 (Damian Mooyman)
* 2015-07-01 [6fabd01](https://github.com/silverstripe/silverstripe-framework/commit/6fabd0122be37faa671923b534a74e5684d58220) Fix potential XSS injection (Damian Mooyman)
* 2015-06-26 [d78d325](https://github.com/silverstripe/silverstripe-cms/commit/d78d3250736c5d2f48c5cfc1690fba8b98cc222b) RedirectorPage_Controller shouldn't attempt redirection if the response is finished (fixes #1230) (Loz Calver)
* 2015-06-18 [f7f92b3](https://github.com/silverstripe/silverstripe-installer/commit/f7f92b32260f31a5969dde4b1d8c55d81c289056) Invalid comment syntax for web.config (Daniel Hensby)
* 2015-06-16 [6169bf2](https://github.com/silverstripe/silverstripe-framework/commit/6169bf2760366b0aebf255c973803621472ce1fb) No longer caching has_one after ID change (Daniel Hensby)
* 2015-06-11 [6be0488](https://github.com/silverstripe/silverstripe-framework/commit/6be04887315522e5b95b83be1e301691441b985c) TreeDropdownField doesnt change label on unselect (Daniel Hensby)
* 2015-05-28 [0319f78](https://github.com/silverstripe/silverstripe-framework/commit/0319f7855bc4e8a6eb71d2766ac24a7d760d502e) Incorrect env setting in 3.1.13 (Damian Mooyman)
* 2015-05-22 [e0710ae](https://github.com/silverstripe/silverstripe-framework/commit/e0710ae4e4a03c191b841cc45a6c103a0e21ec7f) Fix DirectorTest failing when run with sake (Damian Mooyman)
* 2015-05-20 [94f6a13](https://github.com/silverstripe/silverstripe-framework/commit/94f6a137297d6638065583c388dffeeb9eccb55b) Fixed setting LastEdited for DataObject with class ancestry (Gregory Smirnov)
* 2015-05-20 [869e69a](https://github.com/silverstripe/silverstripe-framework/commit/869e69a9b2c1352e1fa6246432d9180eb81cf7e3) Clicking icon in site tree link fails (Jonathon Menz)
* 2015-05-20 [f9bdf61](https://github.com/silverstripe/silverstripe-framework/commit/f9bdf61b6f4cdd2f55ff2729a5b6be0a200f876a) Fixed handling of numbers in certain locales (Gregory Smirnov)
* 2015-05-19 [dbe2ad4](https://github.com/silverstripe/silverstripe-cms/commit/dbe2ad4f9fe818fe21755eff2ecf8d359c578736) Folder expansion icons (Jonathon Menz)
* 2015-05-19 [a56d08b](https://github.com/silverstripe/silverstripe-framework/commit/a56d08b1aeeb0a2dfc16e134ddc3bd7b699bd606) TreeDropdownField Folder expansion (Jonathon Menz)
* 2015-05-16 [c6bcfea](https://github.com/silverstripe/silverstripe-framework/commit/c6bcfea3e36a4211d2f69ff5c73db2fcab474ba8) FieldList::changeFieldOrder() leftovers discarded (Jonathon Menz)
* 2015-05-04 [1cca37c](https://github.com/silverstripe/silverstripe-framework/commit/1cca37c9082ef53f02633d1bdac27f4a815d4208) File::getFileType() was case sensitive (fixes #3631) (Loz Calver)
* 2015-04-01 [7ff131d](https://github.com/silverstripe/silverstripe-framework/commit/7ff131daa76d345cff90410469accdcca9049cf1) Fix default casted (boolean)false evaluating to true in templates (Damian Mooyman)
* 2014-12-31 [71a14c3](https://github.com/silverstripe/silverstripe-framework/commit/71a14c30352e69e4c0ac59e5ea72e1da0c79009b) Prevent url= querystring argument override (Damian Mooyman)
* 2014-10-25 [28be51c](https://github.com/silverstripe/silverstripe-framework/commit/28be51cab0b567b692632503e0f440d30a2fe09e) Config state leaking between unit tests (Loz Calver)
* 2014-09-20 [bbc1cb8](https://github.com/silverstripe/silverstripe-framework/commit/bbc1cb82702b678b21bef15394f067c146e47625) #3458 iframe transport multi file upload FIX #3343, FIX #3148 (Thierry François)
* 2014-05-25 [40c5b8b](https://github.com/silverstripe/silverstripe-framework/commit/40c5b8b6758676a3e2a5daf3c438a7720c49baaf) FulltextFilter did not work and was not usable (micmania1)
* 2014-03-24 [fd755a7](https://github.com/silverstripe/silverstripe-framework/commit/fd755a7ff9de69802f04763570f69e4c3b68c08c) ChangePasswordForm validation message should render HTML correctly. (Sean Harvey)

View File

@ -693,8 +693,10 @@ class Form extends RequestHandler {
$extraFields = new FieldList();
$token = $this->getSecurityToken();
$tokenField = $token->updateFieldSet($this->fields);
if($tokenField) $tokenField->setForm($this);
if ($token) {
$tokenField = $token->updateFieldSet($this->fields);
if($tokenField) $tokenField->setForm($this);
}
$this->securityTokenAdded = true;
// add the "real" HTTP method if necessary (for PUT, DELETE and HEAD)
@ -1378,7 +1380,7 @@ class Form extends RequestHandler {
if(is_object($data)) $this->record = $data;
// dont include fields without data
$dataFields = $this->fields->dataFields();
$dataFields = $this->Fields()->dataFields();
if($dataFields) foreach($dataFields as $field) {
$name = $field->getName();

View File

@ -422,10 +422,15 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
throw new InvalidArgumentException("Bad arguments passed to sort()");
}
// Store the original keys of the items as a sort fallback, so we can preserve the original order in the event
// that array_multisort is unable to work out a sort order for them. This also prevents array_multisort trying
// to inspect object properties which can result in errors with circular dependencies
$originalKeys = array_keys($this->items);
// This the main sorting algorithm that supports infinite sorting params
$multisortArgs = array();
$values = array();
foreach($columnsToSort as $column => $direction ) {
foreach($columnsToSort as $column => $direction) {
// The reason these are added to columns is of the references, otherwise when the foreach
// is done, all $values and $direction look the same
$values[$column] = array();
@ -442,6 +447,8 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
$multisortArgs[] = &$sortDirection[$column];
}
$multisortArgs[] = &$originalKeys;
$list = clone $this;
// As the last argument we pass in a reference to the items that all the sorting will be applied upon
$multisortArgs[] = &$list->items;

View File

@ -725,7 +725,7 @@ class Hierarchy extends DataExtension {
$children = $baseClass::get()
->filter('ParentID', (int)$this->owner->ID)
->sort('Sort', 'ASC');
->sort('"Sort"', 'ASC');
if ($afterNode) {
$children = $children->filter('Sort:GreaterThan', $afterNode->Sort);
}

View File

@ -163,16 +163,35 @@ class Permission extends DataObject implements TemplateGlobalProvider {
$memberID = (is_object($member)) ? $member->ID : $member;
}
// Turn the code into an array as we may need to add other permsissions to the set we check
if(!is_array($code)) $code = array($code);
if($arg == 'any') {
$adminImpliesAll = (bool)Config::inst()->get('Permission', 'admin_implies_all');
// Cache the permissions in memory
if(!isset(self::$cache_permissions[$memberID])) {
self::$cache_permissions[$memberID] = self::permissions_for_member($memberID);
}
// If $admin_implies_all was false then this would be inefficient, but that's an edge
// case and this keeps the code simpler
if(!is_array($code)) $code = array($code);
if(Config::inst()->get('Permission', 'admin_implies_all')) $code[] = "ADMIN";
foreach ($code as $permCode) {
if ($permCode === 'CMS_ACCESS') {
foreach (self::$cache_permissions[$memberID] as $perm) {
//if they have admin rights OR they have an explicit access to the CMS then give permission
if (($adminImpliesAll && $perm == 'ADMIN') || substr($perm, 0, 11) === 'CMS_ACCESS_') {
return true;
}
}
}
elseif (substr($permCode, 0, 11) === 'CMS_ACCESS_') {
//cms_access_leftandmain means access to all CMS areas
$code[] = 'CMS_ACCESS_LeftAndMain';
break;
}
}
// if ADMIN has all privileges, then we need to push that code in
if($adminImpliesAll) {
$code[] = "ADMIN";
}
// Multiple $code values - return true if at least one matches, ie, intersection exists
return (bool)array_intersect($code, self::$cache_permissions[$memberID]);

View File

@ -32,7 +32,7 @@ class CMSProfileControllerTest extends FunctionalTest {
}
public function testMemberEditsOwnProfile() {
$member = $this->objFromFixture('Member', 'user1');
$member = $this->objFromFixture('Member', 'user3');
$this->session()->inst_set('loggedInAs', $member->ID);
$response = $this->post('admin/myprofile/EditForm', array(
@ -46,9 +46,9 @@ class CMSProfileControllerTest extends FunctionalTest {
'Password[_ConfirmPassword]' => 'password',
));
$member = $this->objFromFixture('Member', 'user1');
$member = $this->objFromFixture('Member', 'user3');
$this->assertEquals($member->FirstName, 'JoeEdited', 'FirstName field was changed');
$this->assertEquals('JoeEdited', $member->FirstName, 'FirstName field was changed');
}
public function testExtendedPermissionsStopEditingOwnProfile() {

View File

@ -1,27 +1,38 @@
Permission:
admin:
Code: ADMIN
cmsmain:
Code: CMS_ACCESS_LeftAndMain
leftandmain:
Code: CMS_ACCESS_CMSMain
admin:
Code: ADMIN
cmsmain:
Code: CMS_ACCESS_LeftAndMain
leftandmain:
Code: CMS_ACCESS_CMSMain
test:
Code: CMS_ACCESS_TestController
Group:
admins:
Title: Administrators
Permissions: =>Permission.admin
cmsusers:
Title: CMS Users
Permissions: =>Permission.cmsmain, =>Permission.leftandmain
admins:
Title: Administrators
Permissions: =>Permission.admin
cmsusers:
Title: CMS Users
Permissions: =>Permission.cmsmain, =>Permission.leftandmain
test:
Title: Test group
Permissions: =>Permission.test
Member:
admin:
FirstName: Admin
Email: admin@user.com
Groups: =>Group.admins
user1:
FirstName: Joe
Email: user1@user.com
Groups: =>Group.cmsusers
user2:
FirstName: Steve
Email: user2@user.com
Groups: =>Group.cmsusers
admin:
FirstName: Admin
Email: admin@user.com
Groups: =>Group.admins
user1:
FirstName: Joe
Email: user1@user.com
Groups: =>Group.cmsusers
user2:
FirstName: Steve
Email: user2@user.com
Groups: =>Group.cmsusers
user3:
FirstName: Files
Email: user3@example.com
Groups: =>Group.test

View File

@ -587,6 +587,26 @@ class FormTest extends FunctionalTest {
$messageEls[0]->asXML()
);
}
public function testGetExtraFields()
{
$form = new FormTest_ExtraFieldsForm(
new FormTest_Controller(),
'Form',
new FieldList(new TextField('key1')),
new FieldList()
);
$data = array(
'key1' => 'test',
'ExtraFieldCheckbox' => false,
);
$form->loadDataFrom($data);
$formData = $form->getData();
$this->assertEmpty($formData['ExtraFieldCheckbox']);
}
protected function getStubForm() {
return new Form(
@ -732,41 +752,57 @@ class FormTest_ControllerWithSecurityToken extends Controller implements TestOnl
}
class FormTest_ControllerWithStrictPostCheck extends Controller implements TestOnly {
class FormTest_ControllerWithStrictPostCheck extends Controller implements TestOnly
{
private static $allowed_actions = array('Form');
private static $allowed_actions = array('Form');
protected $template = 'BlankPage';
protected $template = 'BlankPage';
public function Link($action = null) {
return Controller::join_links(
'FormTest_ControllerWithStrictPostCheck',
$this->getRequest()->latestParam('Action'),
$this->getRequest()->latestParam('ID'),
$action
);
}
public function Link($action = null)
{
return Controller::join_links(
'FormTest_ControllerWithStrictPostCheck',
$this->request->latestParam('Action'),
$this->request->latestParam('ID'),
$action
);
}
public function Form() {
$form = new Form(
$this,
'Form',
new FieldList(
new EmailField('Email')
),
new FieldList(
new FormAction('doSubmit')
)
);
$form->setFormMethod('POST');
$form->setStrictFormMethodCheck(true);
$form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
public function Form()
{
$form = new Form(
$this,
'Form',
new FieldList(
new EmailField('Email')
),
new FieldList(
new FormAction('doSubmit')
)
);
$form->setFormMethod('POST');
$form->setStrictFormMethodCheck(true);
$form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
return $form;
}
return $form;
}
public function doSubmit($data, $form, $request)
{
$form->sessionMessage('Test save was successful', 'good');
return $this->redirectBack();
}
}
class FormTest_ExtraFieldsForm extends Form implements TestOnly {
public function getExtraFields() {
$fields = parent::getExtraFields();
$fields->push(new CheckboxField('ExtraFieldCheckbox', 'Extra Field Checkbox', 1));
return $fields;
}
public function doSubmit($data, $form, $request) {
$form->sessionMessage('Test save was successful', 'good');
return $this->redirectBack();
}
}

View File

@ -425,6 +425,29 @@ class ArrayListTest extends SapphireTest {
$this->assertEquals($list->last()->ID, 3, 'Bert.3 should be last in the list');
}
/**
* Check that we don't cause recursion errors with array_multisort() and circular dependencies
*/
public function testSortWithCircularDependencies() {
$itemA = new stdClass;
$childA = new stdClass;
$itemA->child = $childA;
$childA->parent = $itemA;
$itemA->Sort = 1;
$itemB = new stdClass;
$childB = new stdClass;
$itemB->child = $childB;
$childB->parent = $itemB;
$itemB->Sort = 1;
$items = new ArrayList;
$items->add($itemA);
$items->add($itemB);
// This call will trigger a fatal error if there are issues with circular dependencies
$items->sort('Sort');
}
/**
* $list->filter('Name', 'bob'); // only bob in the list
*/

View File

@ -14,7 +14,7 @@ class PermissionTest extends SapphireTest {
}
public function testGetCodesUngrouped() {
$codes = Permission::get_codes(null, false);
$codes = Permission::get_codes(false);
$this->assertArrayHasKey('SITETREE_VIEW_ALL', $codes);
}
@ -23,6 +23,31 @@ class PermissionTest extends SapphireTest {
$this->assertTrue(Permission::checkMember($member, "SITETREE_VIEW_ALL"));
}
public function testCMSAccess() {
$members = Member::get()->byIDs($this->allFixtureIDs('Member'));
foreach ($members as $member) {
$this->assertTrue(Permission::checkMember($member, 'CMS_ACCESS'));
}
$member = new Member();
$member->update(array(
'FirstName' => 'No CMS',
'Surname' => 'Access',
'Email' => 'no-access@example.com',
));
$member->write();
$this->assertFalse(Permission::checkMember($member, 'CMS_ACCESS'));
}
public function testLeftAndMainAccessAll() {
//add user and group
$member = $this->objFromFixture('Member', 'leftandmain');
$this->assertTrue(Permission::checkMember($member, "CMS_ACCESS_MyAdmin"));
$this->assertTrue(Permission::checkMember($member, "CMS_ACCESS_AssetAdmin"));
$this->assertTrue(Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin"));
}
public function testPermissionAreInheritedFromOneRole() {
$member = $this->objFromFixture('Member', 'author');
$this->assertTrue(Permission::checkMember($member, "CMS_ACCESS_MyAdmin"));
@ -39,7 +64,7 @@ class PermissionTest extends SapphireTest {
$this->assertFalse(Permission::checkMember($member, "SITETREE_VIEW_ALL"));
}
function testPermissionsForMember() {
public function testPermissionsForMember() {
$member = $this->objFromFixture('Member', 'access');
$permissions = Permission::permissions_for_member($member->ID);
$this->assertEquals(4, count($permissions));

View File

@ -1,52 +1,63 @@
PermissionRole:
author:
Title: Author
access:
Title: Access Administrator
author:
Title: Author
access:
Title: Access Administrator
PermissionRoleCode:
author1:
Role: =>PermissionRole.author
Code: CMS_ACCESS_MyAdmin
author2:
Role: =>PermissionRole.author
Code: CMS_ACCESS_AssetAdmin
access1:
Role: =>PermissionRole.access
Code: CMS_ACCESS_SecurityAdmin
access2:
Role: =>PermissionRole.access
Code: EDIT_PERMISSIONS
author1:
Role: =>PermissionRole.author
Code: CMS_ACCESS_MyAdmin
author2:
Role: =>PermissionRole.author
Code: CMS_ACCESS_AssetAdmin
access1:
Role: =>PermissionRole.access
Code: CMS_ACCESS_SecurityAdmin
access2:
Role: =>PermissionRole.access
Code: EDIT_PERMISSIONS
Member:
author:
FirstName: Test
Surname: Author
access:
FirstName: Test
Surname: Access Administrator
globalauthor:
FirstName: Test
Surname: Global Author
author:
FirstName: Test
Surname: Author
access:
FirstName: Test
Surname: Access Administrator
globalauthor:
FirstName: Test
Surname: Global Author
leftandmain:
FirstName: Left
Surname: AndMain
Email: leftandmain@example.com
Group:
author:
Title: Authors
Members: =>Member.author
Roles: =>PermissionRole.author
access:
Title: Access Administrators + Authors
Members: =>Member.access
Roles: =>PermissionRole.access,=>PermissionRole.author
globalauthor:
Parent: =>Group.author
Title: Global Authors
Members: =>Member.globalauthor
author:
Title: Authors
Members: =>Member.author
Roles: =>PermissionRole.author
access:
Title: Access Administrators + Authors
Members: =>Member.access
Roles: =>PermissionRole.access,=>PermissionRole.author
globalauthor:
Parent: =>Group.author
Title: Global Authors
Members: =>Member.globalauthor
leftandmain:
Title: LeftAndMain
Members: =>Member.leftandmain
Permission:
extra1:
Code: SITETREE_VIEW_ALL
Group: =>Group.author
globalauthor:
Code: SITETREE_EDIT_ALL
Group: =>Group.globalauthor
extra1:
Code: SITETREE_VIEW_ALL
Group: =>Group.author
globalauthor:
Code: SITETREE_EDIT_ALL
Group: =>Group.globalauthor
leftandmain:
Code: CMS_ACCESS_LeftAndMain
Group: =>Group.leftandmain