Commit 020d1869 authored by Crell's avatar Crell

Merge remote-tracking branch 'upstream/8.x' into kernel

parents d6f33f14 99269244
......@@ -58,7 +58,7 @@ Database system
- Károly Négyesi 'chx' <http://drupal.org/user/9446>
Database update system
- ?
- Ashok Modi 'btmash' <http://drupal.org/user/60422>
Entity system
- Wolfgang Ziegler 'fago' <http://drupal.org/user/16747>
......
......@@ -265,7 +265,7 @@
/**
* Regular expression to match PHP function names.
*
* @see http://php.net/manual/en/language.functions.php
* @see http://php.net/manual/language.functions.php
*/
const DRUPAL_PHP_FUNCTION_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
......@@ -3029,8 +3029,8 @@ function registry_update() {
* However, the above line of code does not work, because PHP only allows static
* variables to be initializied by literal values, and does not allow static
* variables to be assigned to references.
* - http://php.net/manual/en/language.variables.scope.php#language.variables.scope.static
* - http://php.net/manual/en/language.variables.scope.php#language.variables.scope.references
* - http://php.net/manual/language.variables.scope.php#language.variables.scope.static
* - http://php.net/manual/language.variables.scope.php#language.variables.scope.references
* The example below shows the syntax needed to work around both limitations.
* For benchmarks and more information, see http://drupal.org/node/619666.
*
......
......@@ -533,8 +533,8 @@ function drupal_get_destination() {
* Parses a system URL string into an associative array suitable for url().
*
* This function should only be used for URLs that have been generated by the
* system, resp. url(). It should not be used for URLs that come from external
* sources, or URLs that link to external resources.
* system, such as via url(). It should not be used for URLs that come from
* external sources, or URLs that link to external resources.
*
* The returned array contains a 'path' that may be passed separately to url().
* For example:
......@@ -1069,7 +1069,7 @@ function _fix_gpc_magic(&$item) {
* @param $key
* The key for the item within $_FILES.
*
* @see http://php.net/manual/en/features.file-upload.php#42280
* @see http://php.net/manual/features.file-upload.php#42280
*/
function _fix_gpc_magic_files(&$item, $key) {
if ($key != 'tmp_name') {
......@@ -1893,7 +1893,7 @@ function format_interval($interval, $granularity = 2, $langcode = NULL) {
* get interpreted as date format characters.
* @param $timezone
* (optional) Time zone identifier, as described at
* http://php.net/manual/en/timezones.php Defaults to the time zone used to
* http://php.net/manual/timezones.php Defaults to the time zone used to
* display the page.
* @param $langcode
* (optional) Language code to translate to. Defaults to the language used to
......@@ -3643,7 +3643,7 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
// Remove certain whitespace.
// There are different conditions for removing leading and trailing
// whitespace.
// @see http://php.net/manual/en/regexp.reference.subpatterns.php
// @see http://php.net/manual/regexp.reference.subpatterns.php
$contents = preg_replace('<
# Strip leading and trailing whitespace.
\s*([@{};,])\s*
......@@ -5192,7 +5192,7 @@ function _drupal_bootstrap_full() {
* client without gzip support.
*
* Page compression requires the PHP zlib extension
* (http://php.net/manual/en/ref.zlib.php).
* (http://php.net/manual/ref.zlib.php).
*
* @see drupal_page_header()
*/
......@@ -6983,6 +6983,9 @@ function drupal_common_theme() {
'textarea' => array(
'render element' => 'element',
),
'search' => array(
'render element' => 'element',
),
'password' => array(
'render element' => 'element',
),
......
......@@ -24,7 +24,7 @@
* Maps PHP error constants to watchdog severity levels.
*
* The error constants are documented at
* http://php.net/manual/en/errorfunc.constants.php
* http://php.net/manual/errorfunc.constants.php
*
* @ingroup logging_severity_levels
*/
......
......@@ -1494,7 +1494,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
// Check for file upload errors and return FALSE if a lower level system
// error occurred. For a complete list of errors:
// See http://php.net/manual/en/features.file-upload.errors.php.
// See http://php.net/manual/features.file-upload.errors.php.
switch ($_FILES['files']['error'][$source]) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
......@@ -2253,7 +2253,7 @@ function drupal_chmod($uri, $mode = NULL) {
* @param $uri
* A URI or pathname.
* @param $context
* Refer to http://php.net/manual/en/ref.stream.php
* Refer to http://php.net/manual/ref.stream.php
*
* @return
* Boolean TRUE on success, or FALSE on failure.
......@@ -2388,7 +2388,7 @@ function drupal_basename($uri, $suffix = NULL) {
* @param $recursive
* Default to FALSE.
* @param $context
* Refer to http://php.net/manual/en/ref.stream.php
* Refer to http://php.net/manual/ref.stream.php
*
* @return
* Boolean TRUE on success, or FALSE on failure.
......@@ -2419,7 +2419,7 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
* @param $uri
* A URI or pathname.
* @param $context
* Refer to http://php.net/manual/en/ref.stream.php
* Refer to http://php.net/manual/ref.stream.php
*
* @return
* Boolean TRUE on success, or FALSE on failure.
......
......@@ -3094,7 +3094,7 @@ function form_pre_render_conditional_form_element($element) {
$element['#attributes']['title'] = $element['#title'];
if (!empty($element['#required'])) {
// Append an indication that this field is required.
$element['#attributes']['title'] .= ' (' . t('Required') . ')';
$element['#attributes']['title'] .= ' (' . $t('Required') . ')';
}
}
......@@ -3104,6 +3104,19 @@ function form_pre_render_conditional_form_element($element) {
return $element;
}
/**
* Processes a form button element.
*/
function form_process_button($element, $form_state) {
// If this is a button intentionally allowing incomplete form submission
// (e.g., a "Previous" or "Add another item" button), then also skip
// client-side validation.
if (isset($element['#limit_validation_errors']) && $element['#limit_validation_errors'] !== FALSE) {
$element['#attributes']['formnovalidate'] = 'formnovalidate';
}
return $element;
}
/**
* Sets the #checked property of a checkbox element.
*/
......@@ -3689,6 +3702,36 @@ function theme_vertical_tabs($variables) {
return $output;
}
/**
* Adds autocomplete functionality to elements with a valid #autocomplete_path.
*
* @param $element
* The form element to process. Properties used:
* - #autocomplete_path: A system path to be used as callback URL by the
* autocomplete JavaScript library.
*/
function form_process_autocomplete($element, &$form_state) {
if (!empty($element['#autocomplete_path']) && drupal_valid_path($element['#autocomplete_path'])) {
$element['#attributes']['class'][] = 'form-autocomplete';
$element['#attached']['library'][] = array('system', 'drupal.autocomplete');
// Provide a hidden element for the JavaScript behavior to bind to. Since
// this element is for client-side functionality only, and we don't want to
// collect any input from it, use #theme='hidden' instead of #type='hidden'.
// @todo Refactor autocomplete.js to accept Drupal.settings instead of
// requiring extraneous markup.
$element['autocomplete'] = array(
'#theme' => 'hidden',
'#attributes' => array(
'id' => $element['#id'] . '-autocomplete',
'value' => url($element['#autocomplete_path'], array('absolute' => TRUE)),
'class' => array('autocomplete'),
'disabled' => 'disabled',
),
);
}
return $element;
}
/**
* Returns HTML for a submit button form element.
*
......@@ -3779,7 +3822,7 @@ function theme_hidden($variables) {
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #description, #size, #maxlength,
* #placeholder, #required, #attributes, #autocomplete_path.
* #placeholder, #required, #attributes.
*
* @ingroup themeable
*/
......@@ -3789,23 +3832,7 @@ function theme_textfield($variables) {
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
_form_set_class($element, array('form-text'));
$extra = '';
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
drupal_add_library('system', 'drupal.autocomplete');
$element['#attributes']['class'][] = 'form-autocomplete';
$attributes = array();
$attributes['type'] = 'hidden';
$attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
$attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
$attributes['disabled'] = 'disabled';
$attributes['class'][] = 'autocomplete';
$extra = '<input' . drupal_attributes($attributes) . ' />';
}
$output = '<input' . drupal_attributes($element['#attributes']) . ' />';
return $output . $extra;
return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
}
/**
......@@ -3815,7 +3842,7 @@ function theme_textfield($variables) {
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #description, #size, #maxlength,
* #placeholder, #required, #attributes, #autocomplete_path.
* #placeholder, #required, #attributes.
*
* @ingroup themeable
*/
......@@ -3825,23 +3852,7 @@ function theme_email($variables) {
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
_form_set_class($element, array('form-email'));
$extra = '';
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
drupal_add_library('system', 'drupal.autocomplete');
$element['#attributes']['class'][] = 'form-autocomplete';
$attributes = array();
$attributes['type'] = 'hidden';
$attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
$attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
$attributes['disabled'] = 'disabled';
$attributes['class'][] = 'autocomplete';
$extra = '<input' . drupal_attributes($attributes) . ' />';
}
$output = '<input' . drupal_attributes($element['#attributes']) . ' />';
return $output . $extra;
return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
}
/**
......@@ -3865,7 +3876,7 @@ function form_validate_email(&$element, &$form_state) {
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #description, #size, #maxlength,
* #placeholder, #required, #attributes, #autocomplete_path.
* #placeholder, #required, #attributes.
*
* @ingroup themeable
*/
......@@ -3875,23 +3886,7 @@ function theme_tel($variables) {
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
_form_set_class($element, array('form-tel'));
$extra = '';
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
drupal_add_library('system', 'drupal.autocomplete');
$element['#attributes']['class'][] = 'form-autocomplete';
$attributes = array();
$attributes['type'] = 'hidden';
$attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
$attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
$attributes['disabled'] = 'disabled';
$attributes['class'][] = 'autocomplete';
$extra = '<input' . drupal_attributes($attributes) . ' />';
}
$output = '<input' . drupal_attributes($element['#attributes']) . ' />';
return $output . $extra;
return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
}
/**
......@@ -3901,7 +3896,7 @@ function theme_tel($variables) {
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #description, #size, #maxlength,
* #placeholder, #required, #attributes, #autocomplete_path.
* #placeholder, #required, #attributes.
*
* @ingroup themeable
*/
......@@ -3911,6 +3906,26 @@ function theme_url($variables) {
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
_form_set_class($element, array('form-url'));
return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
}
/**
* Returns HTML for a search form element.
*
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #description, #size, #maxlength,
* #placeholder, #required, #attributes, #autocomplete_path.
*
* @ingroup themeable
*/
function theme_search($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'search';
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
_form_set_class($element, array('form-search'));
$extra = '';
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
drupal_add_library('system', 'drupal.autocomplete');
......@@ -4276,6 +4291,10 @@ function _form_set_class(&$element, $class = array()) {
// form_builder().
if (!empty($element['#required'])) {
$element['#attributes']['class'][] = 'required';
// @todo Rename the _form_set_class() function to reflect that we're setting
// non-class attributes too.
$element['#attributes']['required'] = 'required';
$element['#attributes']['aria-required'] = 'true';
}
if (isset($element['#parents']) && form_get_error($element)) {
$element['#attributes']['class'][] = 'error';
......
......@@ -2292,6 +2292,9 @@ function menu_get_active_menu_names() {
*/
function menu_set_active_item($path) {
$_GET['q'] = $path;
// Since the active item has changed, the active menu trail may also be out
// of date.
drupal_static_reset('menu_set_active_trail');
}
/**
......@@ -2377,7 +2380,7 @@ function menu_set_active_trail($new_trail = NULL) {
// appending either the preferred link or the menu router item for the
// current page. Exclude it if we are on the front page.
$last = end($trail);
if ($last['href'] != $preferred_link['href'] && !drupal_is_front_page()) {
if ($preferred_link && $last['href'] != $preferred_link['href'] && !drupal_is_front_page()) {
$trail[] = $preferred_link;
}
}
......@@ -3102,10 +3105,10 @@ function menu_link_save(&$item, $existing_item = array(), $parent_candidates = a
}
// If every value in $existing_item is the same in the $item, there is no
// reason to run the update queries or clear the caches. We use
// array_intersect_assoc() with the $item as the first parameter because
// array_intersect_key() with the $item as the first parameter because
// $item may have additional keys left over from building a router entry.
// The intersect removes the extra keys, allowing a meaningful comparison.
if (!$existing_item || (array_intersect_assoc($item, $existing_item)) != $existing_item) {
if (!$existing_item || (array_intersect_key($item, $existing_item)) != $existing_item) {
db_update('menu_links')
->fields(array(
'menu_name' => $item['menu_name'],
......
......@@ -849,6 +849,72 @@ function update_retrieve_dependencies() {
return $return;
}
/**
* Updates config with values set on Drupal 7.x
*
* Provide a generalised method to migrate variables from Drupal 7 to Drupal 8's
* configuration management system.
*
* @param $config_name
* The name of the configuration object to retrieve. The name corresponds to
* an XML configuration file. For @code config(book.admin) @endcode, the
* config object returned will contain the contents of book.admin.xml.
* @param $variable_map
* An array to map new to old configuration naming conventions. Example:
* @code
* array('new_config' => 'old_config')
* @endcode
* This would update the value for new_config to the value old_config has in
* the variable table.
*/
function update_variables_to_config($config_name, $variable_map = array()) {
$config = config($config_name);
$config_data = array_keys($config->get());
if (!empty($config_data)) {
// Build a list of variables to select from the database and build a mapping
// of variable names to config keys.
foreach ($config_data as $config_key) {
if (isset($variable_map[$config_key])) {
$variables[] = $variable_map[$config_key];
$config_keys[$variable_map[$config_key]] = $config_key;
}
else {
$variables[] = $config_key;
$config_keys[$config_key] = $config_key;
}
}
// Get any variables currently defined that match the new setting names in
// the config file.
$query = db_select('variable', 'v')
->fields('v')
->condition('name', $variables, 'IN');
$var_values = $query->execute()->fetchAllKeyed(0);
if (!empty($var_values)) {
// Update the config system settings to use the values previously stored in
// the variable table.
try {
foreach($var_values as $name => $val) {
$config->set($config_keys[$name], unserialize($val));
}
$config->save();
// Delete the old variables. The config system will throw an exception if a
// value cannot be saved, so this code will not run if there is a problem
// running the update.
$del = db_delete('variable')->condition('name', $variables, 'IN');
$del->execute();
}
// @TODO We may want to do different error handling for different
// exception types, but for now we'll just log the exception.
catch (Exception $e) {
watchdog_exception('update', $e);
}
}
}
}
/**
* @defgroup update-api-7.x-to-8.x Update versions of API functions
* @{
......
......@@ -38,6 +38,12 @@ public function __construct(DrupalConfigVerifiedStorageInterface $verified_stora
public function read() {
$active = (array) config_decode($this->_verifiedStorage->read());
foreach ($active as $key => $value) {
// If the setting is empty, return an empty string rather than an array.
// This is necessary because SimpleXML's default behavior is to return
// an empty array instead of a string.
if (is_array($value) && empty($value)) {
$value = '';
}
$this->set($key, $value);
}
}
......@@ -75,23 +81,33 @@ public function isOverridden($key) {
* cause issues with Booleans, which are casted to "1" (TRUE) or "0" (FALSE).
* In particular, code relying on === or !== will no longer function properly.
*
* @see http://php.net/manual/en/language.operators.comparison.php.
* @see http://php.net/manual/language.operators.comparison.php.
*
* @return
* The data that was requested.
*/
public function get($key = '') {
global $conf;
$name = $this->_verifiedStorage->getName();
if (isset($conf[$name])) {
$merged_data = drupal_array_merge_deep($this->data, $conf[$name]);
}
else {
$merged_data = $this->data;
}
if (empty($key)) {
return $this->data;
return $merged_data;
}
else {
$parts = explode('.', $key);
if (count($parts) == 1) {
return isset($this->data[$key]) ? $this->data[$key] : NULL;
return isset($merged_data[$key]) ? $merged_data[$key] : NULL;
}
else {
$key_exists = NULL;
$value = drupal_array_get_nested_value($this->data, $parts, $key_exists);
$value = drupal_array_get_nested_value($merged_data, $parts, $key_exists);
return $key_exists ? $value : NULL;
}
}
......
......@@ -99,4 +99,11 @@ public function delete() {
$this->deleteFromActive();
$this->deleteFile();
}
/**
* Implements DrupalConfigVerifiedStorageInterface::getName().
*/
public function getName() {
return $this->name;
}
}
......@@ -81,4 +81,9 @@ function writeToFile($data);
* @todo
*/
static function getNamesWithPrefix($prefix);
/**
* Gets the name of this object.
*/
public function getName();
}
......@@ -21,7 +21,7 @@
* concrete implementation of it to support special handling required by that
* database.
*
* @see http://php.net/manual/en/book.pdo.php
* @see http://php.net/manual/book.pdo.php
*/
abstract class Connection extends PDO {
......
......@@ -55,8 +55,6 @@ public function __construct(array $connection_options = array()) {
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
// Because MySQL's prepared statements skip the query cache, because it's dumb.
PDO::ATTR_EMULATE_PREPARES => TRUE,
// Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER,
);
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
......
......@@ -390,7 +390,7 @@ public function indexExists($table, $name) {
// Returns one row for each column in the index. Result is string or FALSE.
// Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
$row = $this->connection->query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'")->fetchAssoc();
return isset($row['key_name']);
return isset($row['Key_name']);
}
public function addPrimaryKey($table, $fields) {
......
......@@ -71,8 +71,6 @@ public function __construct(array $connection_options = array()) {
PDO::ATTR_EMULATE_PREPARES => TRUE,
// Convert numeric values to strings when fetching.
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
// Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER,
);
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
......
......@@ -73,8 +73,6 @@ public function __construct(array $connection_options = array()) {
'pdo' => array(),
);
$connection_options['pdo'] += array(
// Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER,
// Convert numeric values to strings when fetching.
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
);
......
......@@ -19,7 +19,7 @@
* driver needs to set a custom statement class, it may do so in its
* constructor.
*
* @see http://us.php.net/pdostatement
* @see http://php.net/pdostatement
*/
class Statement extends PDOStatement implements StatementInterface {
......
......@@ -64,7 +64,7 @@ public function rowCount();
/**
* Sets the default fetch mode for this statement.
*
* See http://php.net/manual/en/pdo.constants.php for the definition of the
* See http://php.net/manual/pdo.constants.php for the definition of the
* constants used.
*
* @param $mode
......@@ -83,7 +83,7 @@ public function rowCount();
/**
* Fetches the next row from a result set.
*
* See http://php.net/manual/en/pdo.constants.php for the definition of the
* See http://php.net/manual/pdo.constants.php for the definition of the
* constants used.
*
* @param $mode
......
......@@ -291,7 +291,7 @@ public function current() {
$class_name = $this->fetchOptions['class'];
}
if (count($this->fetchOptions['constructor_args'])) {
$reflector = new ReflectionClass($class_name);
$reflector = new \ReflectionClass($class_name);
$result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);