Commit 97e30b96 authored by Dries's avatar Dries

Merge branch '8.x' of git.drupal.org:project/drupal into 8.x

parents ef658a6b 0f9489f9
......@@ -5,8 +5,6 @@
* Functions for use with Drupal's Ajax framework.
*/
use Drupal\Component\Utility\NestedArray;
/**
* @defgroup ajax Ajax framework
* @{
......@@ -291,8 +289,8 @@ function ajax_render($commands = array()) {
// Now add a command to merge changes and additions to Drupal.settings.
$scripts = drupal_add_js();
if (!empty($scripts['settings'])) {
$settings = $scripts['settings'];
array_unshift($commands, ajax_command_settings(NestedArray::mergeDeepArray($settings['data']), TRUE));
$settings = drupal_merge_js_settings($scripts['settings']['data']);
array_unshift($commands, ajax_command_settings($settings, TRUE));
}
// Allow modules to alter any Ajax response.
......
......@@ -3868,6 +3868,63 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
return drupal_render($elements);
}
/**
* Merges an array of settings arrays into a single settings array.
*
* This function merges the items in the same way that
*
* @code
* jQuery.extend(true, {}, $settings_items[0], $settings_items[1], ...)
* @endcode
*
* would. This means integer indeces are preserved just like string indeces are,
* rather than re-indexed as is common in PHP array merging.
*
* Example:
* @code
* function module1_page_build(&$page) {
* $page['#attached']['js'][] = array(
* 'type' => 'setting',
* 'data' => array('foo' => array('a', 'b', 'c')),
* );
* }
* function module2_page_build(&$page) {
* $page['#attached']['js'][] = array(
* 'type' => 'setting',
* 'data' => array('foo' => array('d')),
* );
* }
* // When the page is rendered after the above code, and the browser runs the
* // resulting <SCRIPT> tags, the value of drupalSettings.foo is
* // ['d', 'b', 'c'], not ['a', 'b', 'c', 'd'].
* @endcode
*
* By following jQuery.extend() merge logic rather than common PHP array merge
* logic, the following are ensured:
* - drupal_add_js() is idempotent: calling it twice with the same parameters
* does not change the output sent to the browser.
* - If pieces of the page are rendered in separate PHP requests and the
* returned settings are merged by JavaScript, the resulting settings are the
* same as if rendered in one PHP request and merged by PHP.
*
* @param $settings_items
* An array of settings arrays, as returned by:
* @code
* $js = drupal_add_js();
* $settings_items = $js['settings']['data'];
* @endcode
*
* @return
* A merged $settings array, suitable for JSON encoding and returning to the
* browser.
*
* @see drupal_add_js()
* @see drupal_pre_render_scripts()
*/
function drupal_merge_js_settings($settings_items) {
return NestedArray::mergeDeepArray($settings_items, TRUE);
}
/**
* #pre_render callback to add the elements needed for JavaScript tags to be rendered.
*
......@@ -3949,7 +4006,7 @@ function drupal_pre_render_scripts($elements) {
switch ($item['type']) {
case 'setting':
$element['#value_prefix'] = $embed_prefix;
$element['#value'] = 'var drupalSettings = ' . drupal_json_encode(NestedArray::mergeDeepArray($item['data'], TRUE)) . ";";
$element['#value'] = 'var drupalSettings = ' . drupal_json_encode(drupal_merge_js_settings($item['data'])) . ";";
$element['#value_suffix'] = $embed_suffix;
break;
......@@ -4574,6 +4631,9 @@ function drupal_get_library($module, $name = NULL) {
*/
function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
$js_added = &drupal_static(__FUNCTION__, FALSE);
$tabledrag_id = &drupal_static(__FUNCTION__ . '_setting', FALSE);
$tabledrag_id = (!isset($tabledrag_id)) ? 0 : $tabledrag_id + 1;
if (!$js_added) {
// Add the table drag JavaScript to the page before the module JavaScript
// to ensure that table drag behaviors are registered before any module
......@@ -4585,7 +4645,7 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro
// If a subgroup or source isn't set, assume it is the same as the group.
$target = isset($subgroup) ? $subgroup : $group;
$source = isset($source) ? $source : $target;
$settings['tableDrag'][$table_id][$group][] = array(
$settings['tableDrag'][$table_id][$group][$tabledrag_id] = array(
'target' => $target,
'source' => $source,
'relationship' => $relationship,
......
......@@ -9,7 +9,6 @@
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Utility\NestedArray;
/**
* JSON response object for AJAX requests.
......@@ -134,8 +133,8 @@ protected function ajaxRender(Request $request) {
// Prepend a command to merge changes and additions to Drupal.settings.
$scripts = drupal_add_js();
if (!empty($scripts['settings'])) {
$settings = $scripts['settings'];
$this->addCommand(new SettingsCommand(NestedArray::mergeDeepArray($settings['data']), TRUE), TRUE);
$settings = drupal_merge_js_settings($scripts['settings']['data']);
$this->addCommand(new SettingsCommand($settings, TRUE));
}
$commands = $this->commands;
......
......@@ -232,7 +232,6 @@ public function build(ContainerBuilder $container) {
$container->register('serializer.normalizer.list', 'Drupal\Core\Serialization\ListNormalizer')->addTag('normalizer');
$container->register('serializer.normalizer.typed_data', 'Drupal\Core\Serialization\TypedDataNormalizer')->addTag('normalizer');
$container->register('serializer.encoder.json', 'Drupal\Core\Serialization\JsonEncoder')->addTag('encoder');
$container->register('serializer.encoder.xml', 'Drupal\Core\Serialization\XmlEncoder')->addTag('encoder');
$container->register('flood', 'Drupal\Core\Flood\DatabaseBackend')
->addArgument(new Reference('database'));
......
<?php
/**
* @file
* Contains \Drupal\Core\Serialization\XmlEncoder.
*/
namespace Drupal\Core\Serialization;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\SerializerAwareEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder as BaseXmlEncoder;
/**
* Adds XML support for serializer.
*
* This acts as a wrapper class for Symfony's XmlEncoder so that it is not
* implementing NormalizationAwareInterface, and can be normalized externally.
*/
class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, DecoderInterface {
/**
* The formats that this Encoder supports.
*
* @var array
*/
static protected $format = array('xml');
/**
* An instance of the Symfony XmlEncoder to perform the actual encoding.
*
* @var \Symfony\Component\Serializer\Encoder\XmlEncoder
*/
protected $baseEncoder;
/**
* Constucts the XmlEncoder object, creating a BaseXmlEncoder class also.
*/
public function __construct() {
$this->baseEncoder = new BaseXmlEncoder();
}
/**
* Implements \Symfony\Component\Serializer\Encoder\EncoderInterface::encode().
*/
public function encode($data, $format){
$normalized = $this->serializer->normalize($data, $format);
return $this->baseEncoder->encode($normalized, $format);
}
/**
* Implements \Symfony\Component\Serializer\Encoder\JsonEncoder::supportsEncoding().
*/
public function supportsEncoding($format) {
return in_array($format, static::$format);
}
/**
* Implements \Symfony\Component\Serializer\Encoder\EncoderInterface::decode().
*/
public function decode($data, $format){
return $this->baseEncoder->decode($data, $format);
}
/**
* Implements \Symfony\Component\Serializer\Encoder\JsonEncoder::supportsDecoding().
*/
public function supportsDecoding($format) {
return in_array($format, static::$format);
}
}
......@@ -36,12 +36,11 @@ function setUp() {
config('system.site')->set('page.front', 'test-page')->save();
// Create Full HTML text format.
$full_html_format = array(
$full_html_format = entity_create('filter_format', array(
'format' => 'full_html',
'name' => 'Full HTML',
);
$full_html_format = (object) $full_html_format;
filter_format_save($full_html_format);
));
$full_html_format->save();
$this->checkPermissions(array(), TRUE);
// Create and log in an administrative user having access to the Full HTML
......
......@@ -125,7 +125,8 @@ function testSimpleEntityType() {
}
function testEditorWithCustomMetadata() {
$this->enableModules(array('filter'));
$this->installSchema('system', 'url_alias');
$this->enableModules(array('user', 'filter'));
// Enable edit_test module so that the WYSIWYG Create.js PropertyEditor
// widget becomes available.
......@@ -147,16 +148,15 @@ function testEditorWithCustomMetadata() {
);
// Create a text format.
$full_html_format = array(
$full_html_format = entity_create('filter_format', array(
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 1,
'filters' => array(
'filter_htmlcorrector' => array('status' => 1),
),
);
$full_html_format = (object) $full_html_format;
filter_format_save($full_html_format);
));
$full_html_format->save();
// Create an entity with values for this rich text field.
$this->entity = field_test_create_entity();
......
......@@ -32,15 +32,14 @@ public static function getInfo() {
function setUp() {
parent::setUp();
// Add text format.
$filtered_html_format = array(
// Add text format.
$filtered_html_format = entity_create('filter_format', array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => array(),
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
));
$filtered_html_format->save();
// Create admin user.
$this->admin_user = $this->drupalCreateUser(array('administer filters'));
......
......@@ -32,23 +32,21 @@ public static function getInfo() {
function setUp() {
parent::setUp();
// Add text formats.
$filtered_html_format = array(
// Add text formats.
$filtered_html_format = entity_create('filter_format', array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => array(),
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
$full_html_format = array(
));
$filtered_html_format->save();
$full_html_format = entity_create('filter_format', array(
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 1,
'filters' => array(),
);
$full_html_format = (object) $full_html_format;
filter_format_save($full_html_format);
));
$full_html_format->save();
// Create node type.
$this->drupalCreateContentType(array(
......
......@@ -21,7 +21,7 @@ class EditorManagerTest extends DrupalUnitTestBase {
*
* @var array
*/
public static $modules = array('filter', 'editor');
public static $modules = array('system', 'editor');
/**
* The manager for text editor plugins.
......@@ -42,25 +42,24 @@ function setUp() {
parent::setUp();
// Install the Filter module.
$this->enableModules(array('filter'));
$this->installSchema('system', 'url_alias');
$this->enableModules(array('user', 'filter'));
// Add text formats.
$filtered_html_format = array(
$filtered_html_format = entity_create('filter_format', array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => array(),
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
$full_html_format = array(
));
$filtered_html_format->save();
$full_html_format = entity_create('filter_format', array(
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 1,
'filters' => array(),
);
$full_html_format = (object) $full_html_format;
filter_format_save($full_html_format);
));
$full_html_format->save();
}
/**
......
......@@ -582,11 +582,6 @@ function field_ui_field_settings_form($form, &$form_state, $instance) {
if (is_array($additions)) {
$form['field']['settings'] += $additions;
}
if (!element_children($form['field']['settings'])) {
$form['field']['settings'] += array(
'#markup' => t('%field has no field settings.', array('%field' => $instance['label'])),
);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save field settings'));
......
......@@ -776,7 +776,7 @@ function file_ajax_upload() {
$output = theme('status_messages') . drupal_render($form);
$js = drupal_add_js();
$settings = NestedArray::mergeDeepArray($js['settings']['data']);
$settings = drupal_merge_js_settings($js['settings']['data']);
$response = new AjaxResponse();
return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
......
# Every site requires at least one text format as fallback format that
# - is accessible to all users.
# - is secure, using very basic formatting only.
# - may be modified by installation profiles to have other properties.
format: plain_text
name: 'Plain text'
status: '1'
weight: '10'
roles:
- anonymous
- authenticated
cache: '1'
filters:
# Escape all HTML.
filter_html_escape:
module: filter
status: '1'
# Convert URLs into links.
filter_url:
module: filter
status: '1'
# Convert linebreaks into paragraphs.
filter_autop:
module: filter
status: '1'
langcode: und
......@@ -75,13 +75,12 @@ function filter_admin_overview($form) {
* Form submission handler for filter_admin_overview().
*/
function filter_admin_overview_submit($form, &$form_state) {
$filter_formats = filter_formats();
foreach ($form_state['values']['formats'] as $id => $data) {
// Only update if this is a form element with weight.
if (is_array($data) && isset($data['weight'])) {
// Only update if this is a form element with weight.
db_update('filter_format')
->fields(array('weight' => $data['weight']))
->condition('format', $id)
->execute();
$filter_formats[$id]->set('weight', $data['weight']);
$filter_formats[$id]->save();
}
}
filter_formats_reset();
......@@ -113,10 +112,8 @@ function filter_admin_overview_submit($form, &$form_state) {
function filter_admin_format_page($format = NULL) {
if (!isset($format->name)) {
drupal_set_title(t('Add text format'));
$format = (object) array(
'format' => NULL,
'name' => '',
);
$format = entity_create('filter_format', array());
}
return drupal_get_form('filter_admin_format_form', $format);
}
......@@ -216,6 +213,10 @@ function filter_admin_format_form($form, &$form_state, $format) {
'#title' => t('Enabled filters'),
'#prefix' => '<div id="filters-status-wrapper">',
'#suffix' => '</div>',
// This item is used as a pure wrapping container with heading. Ignore its
// value, since 'filters' should only contain filter definitions.
// @see http://drupal.org/node/1829202
'#input' => FALSE,
);
foreach ($filter_info as $name => $filter) {
$form['filters']['status'][$name] = array(
......@@ -233,6 +234,10 @@ function filter_admin_format_form($form, &$form_state, $format) {
'#type' => 'item',
'#title' => t('Filter processing order'),
'#theme' => 'filter_admin_format_filter_order',
// This item is used as a pure wrapping container with heading. Ignore its
// value, since 'filters' should only contain filter definitions.
// @see http://drupal.org/node/1829202
'#input' => FALSE,
);
foreach ($filter_info as $name => $filter) {
$form['filters']['order'][$name]['filter'] = array(
......@@ -260,8 +265,7 @@ function filter_admin_format_form($form, &$form_state, $format) {
$function = $filter['settings callback'];
// Pass along stored filter settings and default settings, but also the
// format object and all filters to allow for complex implementations.
$defaults = (isset($filter['default settings']) ? $filter['default settings'] : array());
$settings_form = $function($form, $form_state, $filters[$name], $format, $defaults, $filters);
$settings_form = $function($form, $form_state, $filters[$name], $format, $filter['default settings'], $filters);
if (!empty($settings_form)) {
$form['filters']['settings'][$name] = array(
'#type' => 'details',
......@@ -325,9 +329,12 @@ function filter_admin_format_form_validate($form, &$form_state) {
form_set_value($form['format'], $format_format, $form_state);
form_set_value($form['name'], $format_name, $form_state);
$result = db_query("SELECT format FROM {filter_format} WHERE name = :name AND format <> :format", array(':name' => $format_name, ':format' => $format_format))->fetchField();
if ($result) {
form_set_error('name', t('Text format names must be unique. A format named %name already exists.', array('%name' => $format_name)));
$filter_formats = entity_load_multiple('filter_format');
foreach ($filter_formats as $format) {
if ($format->name == $format_name && $format->format != $format_format) {
form_set_error('name', t('Text format names must be unique. A format named %name already exists.', array('%name' => $format_name)));
break;
}
}
}
......@@ -343,13 +350,13 @@ function filter_admin_format_form_submit($form, &$form_state) {
// Add the submitted form values to the text format, and save it.
$format = $form['#format'];
foreach ($form_state['values'] as $key => $value) {
$format->$key = $value;
$format->set($key, $value);
}
$status = filter_format_save($format);
$status = $format->save();
// Save user permissions.
if ($permission = filter_permission_name($format)) {
foreach ($format->roles as $rid => $enabled) {
foreach ($form_state['values']['roles'] as $rid => $enabled) {
user_role_change_permissions($rid, array($permission => $enabled));
}
}
......
......@@ -276,44 +276,11 @@ function hook_filter_FILTER_tips($filter, $format, $long) {
* @{
*/
/**
* Perform actions when a new text format has been created.
*
* @param $format
* The format object of the format being updated.
*
* @see hook_filter_format_update()
* @see hook_filter_format_disable()
*/
function hook_filter_format_insert($format) {
mymodule_cache_rebuild();
}
/**
* Perform actions when a text format has been updated.
*
* This hook allows modules to act when a text format has been updated in any
* way. For example, when filters have been reconfigured, disabled, or
* re-arranged in the text format.
*
* @param $format
* The format object of the format being updated.
*
* @see hook_filter_format_insert()
* @see hook_filter_format_disable()
*/
function hook_filter_format_update($format) {
mymodule_cache_rebuild();
}
/**
* Perform actions when a text format has been disabled.
*
* @param $format
* The format object of the format being disabled.
*
* @see hook_filter_format_insert()
* @see hook_filter_format_update()
*/
function hook_filter_format_disable($format) {
mymodule_cache_rebuild();
......
......@@ -5,146 +5,18 @@
* Install, update, and uninstall functions for the Filter module.
*/
use Drupal\Component\Uuid\Uuid;
/**
* Implements hook_schema().
*/
function filter_schema() {
$schema['filter'] = array(
'description' => 'Table that maps filters (HTML corrector) to text formats (Filtered HTML).',
'fields' => array(
'format' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => 'Foreign key: The {filter_format}.format to which this filter is assigned.',
),
'module' => array(
'type' => 'varchar',
'length' => 64,
'not null' => TRUE,
'default' => '',
'description' => 'The origin module of the filter.',
),
'name' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the filter being referenced.',
),
'weight' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Weight of filter within format.',
),
'status' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Filter enabled status. (1 = enabled, 0 = disabled)',
),
'settings' => array(
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of name value pairs that store the filter settings for the specific format.',
),
),
'primary key' => array('format', 'name'),
'indexes' => array(
'list' => array('weight', 'module', 'name'),
),
);
$schema['filter_format'] = array(
'description' => 'Stores text formats: custom groupings of filters, such as Filtered HTML.',
'fields' => array(
'format' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => 'Primary Key: Unique machine name of the format.',
),
'name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the text format (Filtered HTML).',
'translatable' => TRUE,
),
'cache' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
'description' => 'Flag to indicate whether format is cacheable. (1 = cacheable, 0 = not cacheable)',
),
'status' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 1,
'size' => 'tiny',
'description' => 'The status of the text format. (1 = enabled, 0 = disabled)',
),
'weight' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Weight of text format to use when listing.',
),
),
'primary key' => array('format'),
'unique keys' => array(
'name' => array('name'),
),
'indexes' => array(
'status_weight' => array('status', 'weight'),
),
);
$schema['cache_filter'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['cache_filter']['description'] = 'Cache table for the Filter module to store already filtered pieces of text, identified by text format and hash of the text.';
return $schema;
}
/**
* Implements hook_install().
*/
function filter_install() {
// All sites require at least one text format (the fallback format) that all
// users have access to, so add it here. We initialize it as a simple, safe
// plain text format with very basic formatting, but it can be modified by
// installation profiles to have other properties.
$plain_text_format = array(
'format' => 'plain_text',
'name' => 'Plain text',
'weight' => 10,
'filters' => array(
// Escape all HTML.
'filter_html_escape' => array(
'weight' => 0,
'status' => 1,
),
// URL filter.
'filter_url' => array(
'weight' => 1,
'status' => 1,
),
// Line break filter.
'filter_autop' => array(
'weight' => 2,
'status' => 1,
),
),
);
$plain_text_format = (object) $plain_text_format;
filter_format_save($plain_text_format);
}
/**
* @addtogroup updates-7.x-to-8.x
* @{
......@@ -161,6 +33,42 @@ function filter_update_8000() {
));
}
/**
* Migrate filter formats into configuration.