mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge branch '4.2' into 4
This commit is contained in:
commit
ee24413c30
@ -94,7 +94,6 @@ SilverStripe core environment variables are listed here, though you're free to d
|
||||
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name |
|
||||
| `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file |
|
||||
| `SS_BASE_URL` | The url to use when it isn't determinable by other means (eg: for CLI commands) |
|
||||
| `SS_CONFIGSTATICMANIFEST` | Set to `SS_ConfigStaticManifest_Reflection` to use the Silverstripe 4 Reflection config manifest (speed improvement during dev/build and ?flush) |
|
||||
| `SS_DATABASE_SSL_KEY` | Absolute path to SSL key file |
|
||||
| `SS_DATABASE_SSL_CERT` | Absolute path to SSL certificate file |
|
||||
| `SS_DATABASE_SSL_CA` | Absolute path to SSL Certificate Authority bundle file |
|
||||
|
@ -22,6 +22,8 @@ class HTTPResponse
|
||||
protected static $status_codes = [
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing',
|
||||
103 => 'Early Hints',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
@ -29,6 +31,9 @@ class HTTPResponse
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-Status',
|
||||
208 => 'Already Reported',
|
||||
226 => 'IM Used',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
@ -38,6 +43,7 @@ class HTTPResponse
|
||||
308 => 'Permanent Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
@ -53,14 +59,27 @@ class HTTPResponse
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Request Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
418 => 'I\'m a Teapot',
|
||||
421 => 'Misdirected Request',
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
424 => 'Failed Dependency',
|
||||
426 => 'Upgrade Required',
|
||||
428 => 'Precondition Required',
|
||||
429 => 'Too Many Requests',
|
||||
431 => 'Request Header Fields Too Large',
|
||||
451 => 'Unavailable For Legal Reasons',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
506 => 'Variant Also Negotiates',
|
||||
507 => 'Unsufficient Storage',
|
||||
508 => 'Loop Detected',
|
||||
510 => 'Not Extended',
|
||||
511 => 'Network Authentication Required',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -105,6 +105,24 @@ class GridField extends FormField
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* A whitelist of readonly component classes allowed if performReadonlyTransform is called.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $readonlyComponents = array(
|
||||
GridField_ActionMenu::class,
|
||||
GridState_Component::class,
|
||||
GridFieldConfig_RecordViewer::class,
|
||||
GridFieldDetailForm::class,
|
||||
GridFieldDataColumns::class,
|
||||
GridFieldPageCount::class,
|
||||
GridFieldPaginator::class,
|
||||
GridFieldSortableHeader::class,
|
||||
GridFieldToolbarHeader::class,
|
||||
GridFieldViewButton::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Pattern used for looking up
|
||||
*/
|
||||
@ -193,6 +211,60 @@ class GridField extends FormField
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload the readonly components for this gridfield.
|
||||
*
|
||||
* @param array $components an array map of component class references to whitelist for a readonly version.
|
||||
*/
|
||||
public function setReadonlyComponents(array $components)
|
||||
{
|
||||
$this->readonlyComponents = $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the readonly components
|
||||
*
|
||||
* @return array a map of component classes.
|
||||
*/
|
||||
public function getReadonlyComponents()
|
||||
{
|
||||
return $this->readonlyComponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Readonly transformation to remove actions which shouldn't be present for a readonly state.
|
||||
*
|
||||
* @return GridField
|
||||
*/
|
||||
public function performReadonlyTransformation()
|
||||
{
|
||||
$copy = clone $this;
|
||||
$copy->setReadonly(true);
|
||||
|
||||
// get the whitelist for allowable readonly components
|
||||
$allowedComponents = $this->getReadonlyComponents();
|
||||
foreach ($this->getConfig()->getComponents() as $component) {
|
||||
// if a component doesn't exist, remove it from the readonly version.
|
||||
if (!in_array(get_class($component), $allowedComponents)) {
|
||||
$copy->getConfig()->removeComponent($component);
|
||||
}
|
||||
}
|
||||
|
||||
return $copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabling the gridfield should have the same affect as making it readonly (removing all action items).
|
||||
*
|
||||
* @return GridField
|
||||
*/
|
||||
public function performDisabledTransformation()
|
||||
{
|
||||
parent::performDisabledTransformation();
|
||||
|
||||
return $this->performReadonlyTransformation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GridFieldConfig
|
||||
*/
|
||||
|
@ -14,6 +14,7 @@ class GridFieldConfig_RecordViewer extends GridFieldConfig_Base
|
||||
|
||||
$this->addComponent(new GridFieldViewButton());
|
||||
$this->addComponent(new GridFieldDetailForm());
|
||||
$this->removeComponentsByType(GridFieldFilterHeader::class);
|
||||
|
||||
$this->extend('updateConfig');
|
||||
}
|
||||
|
@ -103,7 +103,12 @@ class TreeMultiselectField extends TreeDropdownField
|
||||
|
||||
// cannot rely on $this->value as this could be a many-many relationship
|
||||
$value = array_column($values, 'id');
|
||||
$data['value'] = ($value) ? $value : 'unchanged';
|
||||
if ($value) {
|
||||
sort($value);
|
||||
$data['value'] = $value;
|
||||
} else {
|
||||
$data['value'] = 'unchanged';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
@ -182,6 +187,7 @@ class TreeMultiselectField extends TreeDropdownField
|
||||
}
|
||||
|
||||
$title = implode(", ", $titleArray);
|
||||
sort($idArray);
|
||||
$value = implode(",", $idArray);
|
||||
} else {
|
||||
$title = $emptyTitle;
|
||||
|
@ -599,7 +599,7 @@ class DataQuery
|
||||
/**
|
||||
* Append a HAVING clause to this query.
|
||||
*
|
||||
* @param string $having Escaped SQL statement
|
||||
* @param mixed $having Predicate(s) to set, as escaped SQL statements or parameterised queries
|
||||
* @return $this
|
||||
*/
|
||||
public function having($having)
|
||||
|
@ -481,7 +481,7 @@ class SQLSelect extends SQLConditionalExpression
|
||||
*
|
||||
* @see SQLSelect::addWhere() for syntax examples
|
||||
*
|
||||
* @param mixed $having Predicate(s) to set, as escaped SQL statements or paramaterised queries
|
||||
* @param mixed $having Predicate(s) to set, as escaped SQL statements or parameterised queries
|
||||
* @param mixed $having,... Unlimited additional predicates
|
||||
* @return $this Self reference
|
||||
*/
|
||||
@ -497,7 +497,7 @@ class SQLSelect extends SQLConditionalExpression
|
||||
*
|
||||
* @see SQLSelect::addWhere() for syntax examples
|
||||
*
|
||||
* @param mixed $having Predicate(s) to set, as escaped SQL statements or paramaterised queries
|
||||
* @param mixed $having Predicate(s) to set, as escaped SQL statements or parameterised queries
|
||||
* @param mixed $having,... Unlimited additional predicates
|
||||
* @return $this Self reference
|
||||
*/
|
||||
|
@ -46,6 +46,10 @@ class LoginAttempt extends DataObject
|
||||
'Member' => Member::class, // only linked if the member actually exists
|
||||
);
|
||||
|
||||
private static $indexes = array(
|
||||
"EmailHashed" => true
|
||||
);
|
||||
|
||||
private static $table_name = "LoginAttempt";
|
||||
|
||||
/**
|
||||
@ -86,7 +90,6 @@ class LoginAttempt extends DataObject
|
||||
public static function getByEmail($email)
|
||||
{
|
||||
return static::get()->filterAny(array(
|
||||
'Email' => $email,
|
||||
'EmailHashed' => sha1($email),
|
||||
));
|
||||
}
|
||||
|
@ -268,7 +268,7 @@ class Member extends DataObject
|
||||
public function populateDefaults()
|
||||
{
|
||||
parent::populateDefaults();
|
||||
$this->Locale = i18n::get_locale();
|
||||
$this->Locale = i18n::config()->get('default_locale');
|
||||
}
|
||||
|
||||
public function requireDefaultRecords()
|
||||
@ -930,7 +930,7 @@ class Member extends DataObject
|
||||
|
||||
// save locale
|
||||
if (!$this->Locale) {
|
||||
$this->Locale = i18n::get_locale();
|
||||
$this->Locale = i18n::config()->get('default_locale');
|
||||
}
|
||||
|
||||
parent::onBeforeWrite();
|
||||
@ -1229,7 +1229,7 @@ class Member extends DataObject
|
||||
|
||||
/**
|
||||
* Return the date format based on the user's chosen locale,
|
||||
* falling back to the default format defined by the {@link i18n.get_locale()} setting.
|
||||
* falling back to the default format defined by the i18n::config()->get('default_locale') config setting.
|
||||
*
|
||||
* @return string ISO date format
|
||||
*/
|
||||
@ -1248,7 +1248,7 @@ class Member extends DataObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user locale
|
||||
* Get user locale, falling back to the configured default locale
|
||||
*/
|
||||
public function getLocale()
|
||||
{
|
||||
@ -1257,12 +1257,12 @@ class Member extends DataObject
|
||||
return $locale;
|
||||
}
|
||||
|
||||
return i18n::get_locale();
|
||||
return i18n::config()->get('default_locale');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time format based on the user's chosen locale,
|
||||
* falling back to the default format defined by the {@link i18n.get_locale()} setting.
|
||||
* falling back to the default format defined by the i18n::config()->get('default_locale') config setting.
|
||||
*
|
||||
* @return string ISO date format
|
||||
*/
|
||||
|
@ -8,7 +8,6 @@ use SilverStripe\Control\HTTPResponse_Exception;
|
||||
|
||||
class HTTPResponseTest extends SapphireTest
|
||||
{
|
||||
|
||||
public function testStatusDescriptionStripsNewlines()
|
||||
{
|
||||
$r = new HTTPResponse('my body', 200, "my description \nwith newlines \rand carriage returns");
|
||||
@ -30,7 +29,6 @@ class HTTPResponseTest extends SapphireTest
|
||||
|
||||
public function testExceptionContentPlainByDefault()
|
||||
{
|
||||
|
||||
// Confirm that the exception's statusCode and statusDescription take precedence
|
||||
$e = new HTTPResponse_Exception("Some content that may be from a hacker", 404, 'not even found');
|
||||
$this->assertEquals("text/plain", $e->getResponse()->getHeader("Content-Type"));
|
||||
@ -46,4 +44,26 @@ class HTTPResponseTest extends SapphireTest
|
||||
$response->removeHeader('X-Animal');
|
||||
$this->assertEmpty($response->getHeader('X-Animal'));
|
||||
}
|
||||
|
||||
public function providerTestValidStatusCodes()
|
||||
{
|
||||
return [
|
||||
[200, 'OK'],
|
||||
[226, 'IM Used'],
|
||||
[426, 'Upgrade Required'],
|
||||
[451, 'Unavailable For Legal Reasons'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestValidStatusCodes
|
||||
* @param int $code
|
||||
* @param string $status
|
||||
*/
|
||||
public function testValidStatusCodes($code, $status)
|
||||
{
|
||||
$response = new HTTPResponse();
|
||||
$response->setStatusCode($code);
|
||||
$this->assertEquals($status, $response->getStatusDescription());
|
||||
}
|
||||
}
|
||||
|
87
tests/php/Forms/GridField/GridFieldReadonlyTest.php
Normal file
87
tests/php/Forms/GridField/GridFieldReadonlyTest.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Forms\Tests\GridField;
|
||||
|
||||
use SilverStripe\Forms\GridField\GridField_ActionMenu;
|
||||
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
|
||||
use SilverStripe\Forms\GridField\GridFieldAddNewButton;
|
||||
use SilverStripe\Forms\GridField\GridFieldButtonRow;
|
||||
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
|
||||
use SilverStripe\Forms\GridField\GridFieldDataColumns;
|
||||
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
|
||||
use SilverStripe\Forms\GridField\GridFieldDetailForm;
|
||||
use SilverStripe\Forms\GridField\GridFieldEditButton;
|
||||
use SilverStripe\Forms\GridField\GridFieldFilterHeader;
|
||||
use SilverStripe\Forms\GridField\GridFieldPageCount;
|
||||
use SilverStripe\Forms\GridField\GridFieldPaginator;
|
||||
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
|
||||
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\GridField\GridField;
|
||||
use SilverStripe\Versioned\VersionedGridFieldState\VersionedGridFieldState;
|
||||
|
||||
class GridFieldReadonlyTest extends SapphireTest
|
||||
{
|
||||
protected static $fixture_file = 'GridFieldReadonlyTest.yml';
|
||||
|
||||
protected static $extra_dataobjects = array(
|
||||
Team::class,
|
||||
Cheerleader::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* The CMS can set the value of a GridField to be a hasMany relation, which needs a readonly state.
|
||||
* This test ensures GridField has a readonly transformation.
|
||||
*/
|
||||
public function testReadOnlyTransformation()
|
||||
{
|
||||
// Build a hasMany Relation via getComponents like ModelAdmin does.
|
||||
$components = Team::get_one(Team::class)
|
||||
->getComponents('Cheerleaders');
|
||||
|
||||
$gridConfig = GridFieldConfig_RelationEditor::create();
|
||||
|
||||
// Build some commonly used components to make sure we're only allowing the correct components
|
||||
$gridConfig->addComponent(new GridFieldButtonRow('before'));
|
||||
$gridConfig->addComponent(new GridFieldAddNewButton('buttons-before-left'));
|
||||
$gridConfig->addComponent(new GridFieldAddExistingAutocompleter('buttons-before-right'));
|
||||
$gridConfig->addComponent(new GridFieldToolbarHeader());
|
||||
$gridConfig->addComponent($sort = new GridFieldSortableHeader());
|
||||
$gridConfig->addComponent($filter = new GridFieldFilterHeader());
|
||||
$gridConfig->addComponent(new GridFieldDataColumns());
|
||||
$gridConfig->addComponent(new GridFieldEditButton());
|
||||
$gridConfig->addComponent(new GridFieldDeleteAction(true));
|
||||
$gridConfig->addComponent(new GridField_ActionMenu());
|
||||
$gridConfig->addComponent(new GridFieldPageCount('toolbar-header-right'));
|
||||
$gridConfig->addComponent($pagination = new GridFieldPaginator(2));
|
||||
$gridConfig->addComponent(new GridFieldDetailForm());
|
||||
$gridConfig->addComponent(new GridFieldDeleteAction());
|
||||
$gridConfig->addComponent(new VersionedGridFieldState());
|
||||
|
||||
$gridField = GridField::create(
|
||||
'Cheerleaders',
|
||||
'Cheerleaders',
|
||||
$components,
|
||||
$gridConfig
|
||||
);
|
||||
|
||||
// Model Admin sets the value of the GridField directly to the relation, which doesn't have a forTemplate()
|
||||
// function, if we rely on FormField to render into a ReadonlyField we'll get an error as HasManyRelation
|
||||
// doesn't have a forTemplate() function.
|
||||
$gridField->setValue($components);
|
||||
$gridField->setModelClass(Cheerleader::class);
|
||||
|
||||
// This function is called by $form->makeReadonly().
|
||||
$readonlyGridField = $gridField->performReadonlyTransformation();
|
||||
|
||||
// if we've made it this far, then the GridField is at least transforming correctly.
|
||||
$readonlyComponents = $readonlyGridField->getReadonlyComponents();
|
||||
|
||||
// assert that all the components in the readonly version are present in the whitelist.
|
||||
foreach ($readonlyGridField->getConfig()->getComponents() as $component) {
|
||||
$this->assertTrue(in_array(get_class($component), $readonlyComponents));
|
||||
}
|
||||
}
|
||||
}
|
27
tests/php/Forms/GridField/GridFieldReadonlyTest.yml
Normal file
27
tests/php/Forms/GridField/GridFieldReadonlyTest.yml
Normal file
@ -0,0 +1,27 @@
|
||||
SilverStripe\Forms\Tests\GridField\GridFieldTest\Team:
|
||||
team1:
|
||||
Name: Team 1
|
||||
City: Cologne
|
||||
team2:
|
||||
Name: Team 2
|
||||
City: Wellington
|
||||
team3:
|
||||
Name: Team 3
|
||||
City: Auckland
|
||||
team4:
|
||||
Name: Team 4
|
||||
City: Melbourne
|
||||
|
||||
SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader:
|
||||
cheerleader1:
|
||||
Name: Heather
|
||||
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1
|
||||
cheerleader2:
|
||||
Name: Bob
|
||||
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1
|
||||
cheerleader3:
|
||||
Name: Jenny
|
||||
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1
|
||||
cheerleader4:
|
||||
Name: Sam
|
||||
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1
|
@ -56,6 +56,8 @@ class MemberTest extends FunctionalTest
|
||||
|
||||
Member::config()->set('unique_identifier_field', 'Email');
|
||||
Member::set_password_validator(null);
|
||||
|
||||
i18n::set_locale('en_US');
|
||||
}
|
||||
|
||||
public function testPasswordEncryptionUpdatedOnChangedPassword()
|
||||
@ -1533,4 +1535,20 @@ class MemberTest extends FunctionalTest
|
||||
$this->assertInstanceOf(ValidationResult::class, $result);
|
||||
$this->assertFalse($result->isValid());
|
||||
}
|
||||
|
||||
public function testNewMembersReceiveTheDefaultLocale()
|
||||
{
|
||||
// Set a different current locale to the default
|
||||
i18n::set_locale('de_DE');
|
||||
|
||||
$newMember = Member::create();
|
||||
$newMember->update([
|
||||
'FirstName' => 'Leslie',
|
||||
'Surname' => 'Longly',
|
||||
'Email' => 'longly.leslie@example.com',
|
||||
]);
|
||||
$newMember->write();
|
||||
|
||||
$this->assertSame('en_US', $newMember->Locale, 'New members receive the default locale');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user