From 13928d366fc474bf96cd32df203ac884e7e96ef8 Mon Sep 17 00:00:00 2001 From: webchick <webchick@24967.no-reply.drupal.org> Date: Sat, 9 Mar 2013 18:58:05 -0800 Subject: [PATCH] Issue #1921818 by chx: Modify drupal_rewrite_settings() to allow writing $settings values. --- core/includes/install.core.inc | 4 +- core/includes/install.inc | 292 +++++++++++++++--- .../Tests/System/SettingsRewriteTest.php | 113 +++++++ 3 files changed, 364 insertions(+), 45 deletions(-) create mode 100644 core/modules/system/lib/Drupal/system/Tests/System/SettingsRewriteTest.php diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index e2e8c2771ec9..d2e461ed99c6 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1102,11 +1102,11 @@ function install_settings_form_submit($form, &$form_state) { global $install_state; // Update global settings array and save. - $settings['databases'] = array( + $settings['databases'] = (object) array( 'value' => array('default' => array('default' => $form_state['storage']['database'])), 'required' => TRUE, ); - $settings['drupal_hash_salt'] = array( + $settings['drupal_hash_salt'] = (object) array( 'value' => drupal_hash_base64(drupal_random_bytes(55)), 'required' => TRUE, ); diff --git a/core/includes/install.inc b/core/includes/install.inc index 088413ae63b7..3fdc48c5f6ca 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -166,58 +166,144 @@ function drupal_get_database_types() { /** * Replaces values in settings.php with values in the submitted array. * + * This function replaces values in place if possible, even for + * multidimensional arrays. This way the old settings do not linger, + * overridden and also the doxygen on a value remains where it should be. + * * @param $settings - * An array of settings that need to be updated. + * An array of settings that need to be updated. Multidimensional arrays + * are dumped up to a stdClass object. The object can have value, required + * and comment properties. + * @code + * $settings['config_directories'] = array( + * CONFIG_ACTIVE_DIRECTORY => array( + * 'path' => (object) array( + * 'value' => 'config__hash/active' + * 'required' => TRUE, + * ), + * ), + * CONFIG_STAGING_DIRECTORY => array( + * 'path' => (object) array( + * 'value' => 'config_hash/staging', + * 'required' => TRUE, + * ), + * ), + * ); + * @endcode + * gets dumped as: + * @code + * $config_directories['active']['path'] = 'config__hash/active'; + * $config_directories['staging']['path'] = 'config__hash/staging' + * @endcode */ -function drupal_rewrite_settings($settings = array()) { - drupal_static_reset('conf_path'); - $settings_file = conf_path(FALSE) . '/settings.php'; - +function drupal_rewrite_settings($settings = array(), $settings_file = NULL) { + if (!isset($settings_file)) { + $settings_file = conf_path(FALSE) . '/settings.php'; + } // Build list of setting names and insert the values into the global namespace. - $keys = array(); + $variable_names = array(); foreach ($settings as $setting => $data) { - $GLOBALS[$setting] = $data['value']; - $keys[] = $setting; + _drupal_rewrite_settings_global($GLOBALS[$setting], $data); + $variable_names['$'. $setting] = $setting; } - - $buffer = NULL; $contents = file_get_contents(DRUPAL_ROOT . '/' . $settings_file); if ($contents !== FALSE) { // Step through each token in settings.php and replace any variables that // are in the passed-in array. - $replacing_variable = FALSE; + $buffer = ''; + $state = 'default'; foreach (token_get_all($contents) as $token) { - // Strip off the leading "$" before comparing the variable name. - if (is_array($token) && $token[0] == T_VARIABLE && ($variable_name = substr($token[1], 1)) && in_array($variable_name, $keys)) { - // Write the new value to settings.php in the following format: - // $[setting] = '[value]'; // [comment] - $setting = $settings[$variable_name]; - $buffer .= '$' . $variable_name . ' = ' . var_export($setting['value'], TRUE) . ';'; - if (!empty($setting['comment'])) { - $buffer .= ' // ' . $setting['comment']; - } - unset($settings[$variable_name]); - $replacing_variable = TRUE; + if (is_array($token)) { + list($type, $value) = $token; } else { - // Write a regular token (that is not part of a variable we're - // replacing) to settings.php directly. - if (!$replacing_variable) { - $buffer .= is_array($token) ? $token[1] : $token; - } - // When we hit a semicolon, we are done with the code that defines the - // variable that is being replaced. - if ($token == ';') { - $replacing_variable = FALSE; + $type = -1; + $value = $token; + } + // Do not operate on whitespace. + if (!in_array($type, array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) { + switch ($state) { + case 'default': + if ($type === T_VARIABLE && isset($variable_names[$value])) { + // This will be necessary to unset the dumped variable. + $parent = &$settings; + // This is the current index in parent. + $index = $variable_names[$value]; + // This will be necessary for descending into the array. + $current = &$parent[$index]; + $state = 'candidate_left'; + } + break; + case 'candidate_left': + if ($value == '[') { + $state = 'array_index'; + } + if ($value == '=') { + $state = 'candidate_right'; + } + break; + case 'array_index': + if (_drupal_rewrite_settings_is_array_index($type, $value)) { + $index = trim($value, '\'"'); + $state = 'right_bracket'; + } + else { + // $a[foo()] or $a[$bar] or something like that. + throw new Exception('invalid array index'); + } + break; + case 'right_bracket': + if ($value == ']') { + if (isset($current[$index])) { + // If the new settings has this index, descend into it. + $parent = &$current; + $current = &$parent[$index]; + $state = 'candidate_left'; + } + else { + // Otherwise, jump back to the default state. + $state = 'wait_for_semicolon'; + } + } + else { + // $a[1 + 2]. + throw new Exception('] expected'); + } + break; + case 'candidate_right': + if (_drupal_rewrite_settings_is_simple($type, $value)) { + $value = _drupal_rewrite_settings_dump_one($current); + // Unsetting $current would not affect $settings at all. + unset($parent[$index]); + // Skip the semicolon because _drupal_rewrite_settings_dump_one() added one. + $state = 'semicolon_skip'; + } + else { + $state = 'wait_for_semicolon'; + } + break; + case 'wait_for_semicolon': + if ($value == ';') { + $state = 'default'; + } + break; + case 'semicolon_skip': + if ($value == ';') { + $value = ''; + $state = 'default'; + } + else { + // If the expression was $a = 1 + 2; then we replaced 1 and + // the + is unexpected. + throw new Exception('Unepxected token after replacing value.'); + } + break; } } + $buffer .= $value; } - - // Add required settings that were missing from settings.php. - foreach ($settings as $setting => $data) { - if (!empty($data['required'])) { - $buffer .= "\$$setting = " . var_export($data['value'], TRUE) . ";\n"; - } + foreach ($settings as $name => $setting) { + $buffer .= _drupal_rewrite_settings_dump($setting, '$' . $name); } // Write the new settings file. @@ -230,6 +316,123 @@ function drupal_rewrite_settings($settings = array()) { } } +/** + * Helper for drupal_rewrite_settings(). + * + * Checks whether this token represents a scalar or NULL. + * + * @param int $type + * The token type + * @see token_name(). + * @param string $value + * The value of the token. + * + * @return bool + * TRUE if this token represents a scalar or NULL. + */ +function _drupal_rewrite_settings_is_simple($type, $value) { + $is_integer = $type == T_LNUMBER; + $is_float = $type == T_DNUMBER; + $is_string = $type == T_CONSTANT_ENCAPSED_STRING; + $is_boolean_or_null = $type == T_STRING && in_array(strtoupper($value), array('TRUE', 'FALSE', 'NULL')); + return $is_integer || $is_float || $is_string || $is_boolean_or_null; +} + + +/** + * Helper for drupal_rewrite_settings(). + * + * Checks whether this token represents a valid array index: a number or a + * stirng. + * + * @param int $type + * The token type + * @see token_name(). + * + * @return bool + * TRUE if this token represents a number or a string. + */ +function _drupal_rewrite_settings_is_array_index($type) { + $is_integer = $type == T_LNUMBER; + $is_float = $type == T_DNUMBER; + $is_string = $type == T_CONSTANT_ENCAPSED_STRING; + return $is_integer || $is_float || $is_string; +} + +/** + * Helper for drupal_rewrite_settings(). + * + * Makes the new settings global. + * + * @param array|NULL $ref + * A reference to a nested index in $GLOBALS. + * @param array|object $variable + * The nested value of the setting being copied. + */ +function _drupal_rewrite_settings_global(&$ref, $variable) { + if (is_object($variable)) { + $ref = $variable->value; + } + else { + foreach ($variable as $k => $v) { + _drupal_rewrite_settings_global($ref[$k], $v); + } + } +} + +/** + * Helper for drupal_rewrite_settings(). + * + * Dump the relevant value properties. + * + * @param array|object $variable + * The container for variable values. + * @param string $variable_name + * Name of variable. + * @return string + * A string containing valid PHP code of the variable suitable for placing + * into settings.php. + */ +function _drupal_rewrite_settings_dump($variable, $variable_name) { + $return = ''; + if (is_object($variable)) { + if (!empty($variable->required)) { + $return .= _drupal_rewrite_settings_dump_one($variable, "$variable_name = ", "\n"); + } + } + else { + foreach ($variable as $k => $v) { + $return .= _drupal_rewrite_settings_dump($v, $variable_name . "['" . $k . "']"); + } + } + return $return; +} + + +/** + * Helper for drupal_rewrite_settings(). + * + * Dump the value of a value property and adds the comment if it exists. + * + * @param stdClass $variable + * A stdClass object with at least a value property. + * @param string $prefix + * A string to prepend to the variable's value. + * @param string $suffix + * A string to append to the variable's value. + * @return string + * A string containing valid PHP code of the variable suitable for placing + * into settings.php. + */ +function _drupal_rewrite_settings_dump_one(\stdClass $variable, $prefix = '', $suffix = '') { + $return = $prefix . var_export($variable->value, TRUE) . ';'; + if (!empty($variable->comment)) { + $return .= ' // ' . $variable->comment; + } + $return .= $suffix; + return $return; +} + /** * Creates the config directory and ensures it is operational. * @@ -244,15 +447,18 @@ function drupal_install_config_directories() { if (empty($config_directories)) { $config_directories_hash = drupal_hash_base64(drupal_random_bytes(55)); $settings['config_directories'] = array( - 'value' => array( - CONFIG_ACTIVE_DIRECTORY => array( - 'path' => 'config_' . $config_directories_hash . '/active', + CONFIG_ACTIVE_DIRECTORY => array( + 'path' => (object) array( + 'value' => 'config_' . $config_directories_hash . '/active', + 'required' => TRUE, ), - CONFIG_STAGING_DIRECTORY => array( - 'path' => 'config_' . $config_directories_hash . '/staging', + ), + CONFIG_STAGING_DIRECTORY => array( + 'path' => (object) array( + 'value' => 'config_' . $config_directories_hash . '/staging', + 'required' => TRUE, ), ), - 'required' => TRUE, ); // Rewrite settings.php, which also sets the value as global variable. drupal_rewrite_settings($settings); diff --git a/core/modules/system/lib/Drupal/system/Tests/System/SettingsRewriteTest.php b/core/modules/system/lib/Drupal/system/Tests/System/SettingsRewriteTest.php new file mode 100644 index 000000000000..cd4af828d78f --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/SettingsRewriteTest.php @@ -0,0 +1,113 @@ +<?php + +/** + * @file + * Contains Drupal\system\Tests\System\SettingsRewriteTest. + */ + +namespace Drupal\system\Tests\System; + +use Drupal\simpletest\UnitTestBase; + +/** + * Tests the drupal_rewrite_settings() function. + */ +class SettingsRewriteTest extends UnitTestBase { + public static function getInfo() { + return array( + 'name' => 'drupal_rewrite_settings()', + 'description' => 'Tests the drupal_rewrite_settings() function.', + 'group' => 'System', + ); + } + + /** + * Tests the drupal_rewrite_settings() function. + */ + function testDrupalRewriteSettings() { + include_once DRUPAL_ROOT . '/core/includes/install.inc'; + $tests = array( + array( + 'original' => '$no_index_value_scalar = TRUE;', + 'settings' => array( + 'no_index_value_scalar' => (object) array( + 'value' => FALSE, + 'comment' => 'comment', + ), + ), + 'expected' => '$no_index_value_scalar = false; // comment', + ), + array( + 'original' => '$no_index_value_scalar = TRUE;', + 'settings' => array( + 'no_index_value_foo' => array( + 'foo' => array( + 'value' => (object) array( + 'value' => NULL, + 'required' => TRUE, + 'comment' => 'comment', + ), + ), + ), + ), + 'expected' => <<<'EXPECTED' +$no_index_value_scalar = TRUE; +$no_index_value_foo['foo']['value'] = NULL; // comment +EXPECTED + ), + array( + 'original' => '$no_index_value_array = array("old" => "value");', + 'settings' => array( + 'no_index_value_array' => (object) array( + 'value' => FALSE, + 'required' => TRUE, + 'comment' => 'comment', + ), + ), + 'expected' => '$no_index_value_array = array("old" => "value"); +$no_index_value_array = false; // comment', + ), + array( + 'original' => '$has_index_value_scalar["foo"]["bar"] = NULL;', + 'settings' => array( + 'has_index_value_scalar' => array( + 'foo' => array( + 'bar' => (object) array( + 'value' => FALSE, + 'required' => TRUE, + 'comment' => 'comment', + ), + ), + ), + ), + 'expected' => '$has_index_value_scalar["foo"]["bar"] = false; // comment', + ), + array( + 'original' => '$has_index_value_scalar["foo"]["bar"] = "foo";', + 'settings' => array( + 'has_index_value_scalar' => array( + 'foo' => array( + 'value' => (object) array( + 'value' => array('value' => 2), + 'required' => TRUE, + 'comment' => 'comment', + ), + ), + ), + ), + 'expected' => <<<'EXPECTED' +$has_index_value_scalar["foo"]["bar"] = "foo"; +$has_index_value_scalar['foo']['value'] = array ( + 'value' => 2, +); // comment +EXPECTED + ), + ); + foreach ($tests as $test) { + $filename = variable_get('file_public_path', conf_path() . '/files') . '/mock_settings.php'; + file_put_contents(DRUPAL_ROOT . '/' . $filename, "<?php\n" . $test['original'] . "\n"); + drupal_rewrite_settings($test['settings'], $filename); + $this->assertEqual(file_get_contents(DRUPAL_ROOT . '/' . $filename), "<?php\n" . $test['expected'] . "\n"); + } + } +} -- GitLab