diff --git a/core/core.services.yml b/core/core.services.yml index 90c40a9..395729b 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -118,15 +118,19 @@ services: arguments: ['@service_container', '@settings'] keyvalue.database: class: Drupal\Core\KeyValueStore\KeyValueDatabaseFactory - arguments: ['@database'] + arguments: ['@serialization.phpserialize', '@database'] keyvalue.expirable: class: Drupal\Core\KeyValueStore\KeyValueExpirableFactory arguments: ['@service_container', '@settings'] keyvalue.expirable.database: class: Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory + arguments: ['@serialization.phpserialize', '@database'] tags: - { name: needs_destruction } - arguments: ['@database'] + + serialization.phpserialize: + class: Drupal\Core\Serialization\PhpSerialize + settings: class: Drupal\Component\Utility\Settings factory_class: Drupal\Component\Utility\Settings diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php index 63964de..c8b1575 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php @@ -30,8 +30,6 @@ public static function schema(FieldDefinitionInterface $field_definition) { 'value' => array( 'type' => 'text', 'size' => 'big', - 'not null' => TRUE, - 'default' => '', ), ), ); diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 424b464..aa8369a 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -304,6 +304,7 @@ public function getFormStateDefaults() { 'submitted' => FALSE, 'executed' => FALSE, 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, 'cache'=> FALSE, 'method' => 'post', 'groups' => array(), @@ -1511,7 +1512,7 @@ protected function handleInputElement($form_id, &$element, &$form_state) { // #access=FALSE on an element usually allow access for some users, so forms // submitted with self::submitForm() may bypass access restriction and be // treated as high-privilege users instead. - $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); + $process_input = empty($element['#disabled']) && (($form_state['programmed'] && $form_state['programmed_bypass_access_check']) || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); // Set the element's #value property. if (!isset($element['#value']) && !array_key_exists('#value', $element)) { diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php index 04ebcca..aa34f3f 100644 --- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php +++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php @@ -164,6 +164,12 @@ public function getForm($form_arg); * likely to occur during Ajax operations. * - programmed: If TRUE, the form was submitted programmatically, usually * invoked via self::submitForm(). Defaults to FALSE. + * - programmed_bypass_access_check: If TRUE, programmatic form submissions + * are processed without taking #access into account. Set this to FALSE + * when submitting a form programmatically with values that may have been + * input by the user executing the current request; this will cause + * #access to be respected as it would on a normal form submission. + * Defaults to TRUE. * - process_input: Boolean flag. TRUE signifies correct form submission. * This is always TRUE for programmed forms coming from self::submitForm() * (see 'programmed' key), or if the form_id coming from the diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php index 1464186..d25d2f3 100644 --- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php @@ -9,6 +9,7 @@ use Drupal\Core\Database\Query\Merge; use Drupal\Core\Database\Connection; +use Drupal\Core\Serialization\SerializationInterface; /** * Defines a default key/value store implementation. @@ -19,6 +20,13 @@ class DatabaseStorage extends StorageBase { /** + * The serialization class to use. + * + * @var \Drupal\Core\Serialization\SerializationInterface + */ + protected $serializer; + + /** * The database connection. * * @var \Drupal\Core\Database\Connection @@ -37,11 +45,16 @@ class DatabaseStorage extends StorageBase { * * @param string $collection * The name of the collection holding key and value pairs. + * @param \Drupal\Core\Serialization\SerializationInterface $serializer + * The serialization class to use. + * @param \Drupal\Core\Database\Connection $connection + * The database connection to use. * @param string $table * The name of the SQL table to use, defaults to key_value. */ - public function __construct($collection, Connection $connection, $table = 'key_value') { + public function __construct($collection, SerializationInterface $serializer, Connection $connection, $table = 'key_value') { parent::__construct($collection); + $this->serializer = $serializer; $this->connection = $connection; $this->table = $table; } @@ -65,7 +78,7 @@ public function getMultiple(array $keys) { $result = $this->connection->query('SELECT name, value FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name IN (:keys) AND collection = :collection', array(':keys' => $keys, ':collection' => $this->collection))->fetchAllAssoc('name'); foreach ($keys as $key) { if (isset($result[$key])) { - $values[$key] = unserialize($result[$key]->value); + $values[$key] = $this->serializer->decode($result[$key]->value); } } } @@ -86,7 +99,7 @@ public function getAll() { foreach ($result as $item) { if ($item) { - $values[$item->name] = unserialize($item->value); + $values[$item->name] = $this->serializer->decode($item->value); } } return $values; @@ -101,7 +114,7 @@ public function set($key, $value) { 'name' => $key, 'collection' => $this->collection, )) - ->fields(array('value' => serialize($value))) + ->fields(array('value' => $this->serializer->encode($value))) ->execute(); } @@ -113,7 +126,7 @@ public function setIfNotExists($key, $value) { ->insertFields(array( 'collection' => $this->collection, 'name' => $key, - 'value' => serialize($value), + 'value' => $this->serializer->encode($value), )) ->condition('collection', $this->collection) ->condition('name', $key) diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php index fb0b8b0..f4923cf 100644 --- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php @@ -10,6 +10,7 @@ use Drupal\Core\DestructableInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\Merge; +use Drupal\Core\Serialization\SerializationInterface; /** * Defines a default key/value store implementation for expiring items. @@ -20,13 +21,6 @@ class DatabaseStorageExpirable extends DatabaseStorage implements KeyValueStoreExpirableInterface, DestructableInterface { /** - * The connection object for this storage. - * - * @var \Drupal\Core\Database\Connection - */ - protected $connection; - - /** * Flag indicating whether garbage collection should be performed. * * When this flag is TRUE, garbage collection happens at the end of the @@ -44,16 +38,15 @@ class DatabaseStorageExpirable extends DatabaseStorage implements KeyValueStoreE * * @param string $collection * The name of the collection holding key and value pairs. - * @param array $options - * An associative array of options for the key/value storage collection. - * Keys used: - * - connection: (optional) The database connection to use for storing the - * data. Defaults to the current connection. - * - table: (optional) The name of the SQL table to use. Defaults to - * key_value_expire. + * @param \Drupal\Core\Serialization\SerializationInterface $serializer + * The serialization class to use. + * @param \Drupal\Core\Database\Connection $connection + * The database connection to use. + * @param string $table + * The name of the SQL table to use, defaults to key_value_expire. */ - public function __construct($collection, Connection $connection, $table = 'key_value_expire') { - parent::__construct($collection, $connection, $table); + public function __construct($collection, SerializationInterface $serializer, Connection $connection, $table = 'key_value_expire') { + parent::__construct($collection, $serializer, $connection, $table); } /** @@ -78,7 +71,7 @@ public function getMultiple(array $keys) { ':keys' => $keys, ':collection' => $this->collection, ))->fetchAllKeyed(); - return array_map('unserialize', $values); + return array_map(array($this->serializer, 'decode'), $values); } /** @@ -91,7 +84,7 @@ public function getAll() { ':collection' => $this->collection, ':now' => REQUEST_TIME ))->fetchAllKeyed(); - return array_map('unserialize', $values); + return array_map(array($this->serializer, 'decode'), $values); } /** @@ -107,7 +100,7 @@ function setWithExpire($key, $value, $expire) { 'collection' => $this->collection, )) ->fields(array( - 'value' => serialize($value), + 'value' => $this->serializer->encode($value), 'expire' => REQUEST_TIME + $expire, )) ->execute(); @@ -124,7 +117,7 @@ function setWithExpireIfNotExists($key, $value, $expire) { ->insertFields(array( 'collection' => $this->collection, 'name' => $key, - 'value' => serialize($value), + 'value' => $this->serializer->encode($value), 'expire' => REQUEST_TIME + $expire, )) ->condition('collection', $this->collection) diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php index 9000e20..895d7ed 100644 --- a/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php +++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php @@ -9,6 +9,7 @@ use Drupal\Core\DestructableInterface; use Drupal\Core\Database\Connection; +use Drupal\Core\Serialization\SerializationInterface; /** * Defines the key/value store factory for the database backend. @@ -23,6 +24,13 @@ class KeyValueDatabaseExpirableFactory implements KeyValueExpirableFactoryInterf protected $storages = array(); /** + * The serialization class to use. + * + * @var \Drupal\Core\Serialization\SerializationInterface + */ + protected $serializer; + + /** * The database connection. * * @var \Drupal\Core\Database\Connection @@ -32,11 +40,13 @@ class KeyValueDatabaseExpirableFactory implements KeyValueExpirableFactoryInterf /** * Constructs this factory object. * - * + * @param \Drupal\Core\Serialization\SerializationInterface $serializer + * The serialization class to use. * @param \Drupal\Core\Database\Connection $connection * The Connection object containing the key-value tables. */ - function __construct(Connection $connection) { + function __construct(SerializationInterface $serializer, Connection $connection) { + $this->serializer = $serializer; $this->connection = $connection; } @@ -45,7 +55,7 @@ function __construct(Connection $connection) { */ public function get($collection) { if (!isset($this->storages[$collection])) { - $this->storages[$collection] = new DatabaseStorageExpirable($collection, $this->connection); + $this->storages[$collection] = new DatabaseStorageExpirable($collection, $this->serializer, $this->connection); } return $this->storages[$collection]; } diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseFactory.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseFactory.php index 1a840b3..43063ab 100644 --- a/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseFactory.php +++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseFactory.php @@ -6,8 +6,10 @@ */ namespace Drupal\Core\KeyValueStore; + use Drupal\Core\Database\Connection; use Drupal\Core\Database\Database; +use Drupal\Core\Serialization\SerializationInterface; /** * Defines the key/value store factory for the database backend. @@ -15,13 +17,29 @@ class KeyValueDatabaseFactory implements KeyValueFactoryInterface { /** - * Constructs this factory object. + * The serialization class to use. + * + * @var \Drupal\Core\Serialization\SerializationInterface + */ + protected $serializer; + + /** + * The database connection to use. * + * @var \Drupal\Core\Database\Connection + */ + protected $connection; + + /** + * Constructs this factory object. * + * @param \Drupal\Core\Serialization\SerializationInterface $serializer + * The serialization class to use. * @param \Drupal\Core\Database\Connection $connection * The Connection object containing the key-value tables. */ - function __construct(Connection $connection) { + function __construct(SerializationInterface $serializer, Connection $connection) { + $this->serializer = $serializer; $this->connection = $connection; } @@ -29,6 +47,6 @@ function __construct(Connection $connection) { * {@inheritdoc} */ public function get($collection) { - return new DatabaseStorage($collection, $this->connection); + return new DatabaseStorage($collection, $this->serializer, $this->connection); } } diff --git a/core/lib/Drupal/Core/Serialization/Exception/InvalidDataTypeException.php b/core/lib/Drupal/Core/Serialization/Exception/InvalidDataTypeException.php new file mode 100644 index 0000000..e6b8ce6 --- /dev/null +++ b/core/lib/Drupal/Core/Serialization/Exception/InvalidDataTypeException.php @@ -0,0 +1,14 @@ + $value) { - $this->assertTrue(isset($stored_values[$key]) && $stored_values[$key] == $value, format_string('Submission handler correctly executed: %stored_key is %stored_value', array('%stored_key' => $key, '%stored_value' => print_r($value, TRUE)))); + $this->assertEqual($stored_values[$key], $value, format_string('Submission handler correctly executed: %stored_key is %stored_value', array('%stored_key' => $key, '%stored_value' => print_r($value, TRUE)))); } } } + + /** + * Test the programmed_bypass_access_check flag. + */ + public function testProgrammaticAccessBypass() { + $form_state['values'] = array( + 'textfield' => 'dummy value', + 'field_restricted' => 'dummy value' + ); + + // Programmatically submit the form with a value for the restricted field. + // Since programmed_bypass_access_check is set to TRUE by default, the + // field is accessible and can be set. + \Drupal::formBuilder()->submitForm('form_test_programmatic_form', $form_state); + $values = $form_state['storage']['programmatic_form_submit']; + $this->assertEqual($values['field_restricted'], 'dummy value', 'The value for the restricted field is stored correctly.'); + + // Programmatically submit the form with a value for the restricted field + // with programmed_bypass_access_check set to FALSE. Since access + // restrictions apply, the restricted field is inaccessible, and the value + // should not be stored. + $form_state['programmed_bypass_access_check'] = FALSE; + \Drupal::formBuilder()->submitForm('form_test_programmatic_form', $form_state); + $values = $form_state['storage']['programmatic_form_submit']; + $this->assertNotEqual($values['field_restricted'], 'dummy value', 'The value for the restricted field is not stored.'); + + } } diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php index 5f7365d..d53b914 100644 --- a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php @@ -35,7 +35,10 @@ protected function setUp() { ->addArgument('default'); $this->container ->register('keyvalue.expirable.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory') + ->addArgument(new Reference('serialization.phpserialize')) ->addArgument(new Reference('database')); + $this->container + ->register('serialization.phpserialize', 'Drupal\Core\Serialization\PhpSerialize'); $this->settingsSet('keyvalue_expirable_default', 'keyvalue.expirable.database'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageTest.php index ad286d6..3442530 100644 --- a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageTest.php @@ -34,7 +34,10 @@ protected function setUp() { ->addArgument('default'); $this->container ->register('keyvalue.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseFactory') + ->addArgument(new Reference('serialization.phpserialize')) ->addArgument(new Reference('database')); + $this->container + ->register('serialization.phpserialize', 'Drupal\Core\Serialization\PhpSerialize'); $this->settingsSet('keyvalue_default', 'keyvalue.database'); } diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index 43d5d25..939dc83 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -1768,6 +1768,12 @@ function form_test_programmatic_form($form, &$form_state) { '#default_value' => 'all', ); + $form['field_restricted'] = array( + '#type' => 'textfield', + '#title' => 'Textfield (no access)', + '#access' => FALSE, + ); + // The main submit button for the form. $form['submit'] = array( '#type' => 'submit',