diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 34a9940..95dff27 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1103,12 +1103,12 @@ function install_settings_form_submit($form, &$form_state) { // Update global settings array and save. $settings['databases'] = array( - 'value' => array('default' => array('default' => $form_state['storage']['database'])), - 'required' => TRUE, + '.value' => array('default' => array('default' => $form_state['storage']['database'])), + '.required' => TRUE, ); $settings['drupal_hash_salt'] = array( - 'value' => drupal_hash_base64(drupal_random_bytes(55)), - 'required' => TRUE, + '.value' => drupal_hash_base64(drupal_random_bytes(55)), + '.required' => TRUE, ); drupal_rewrite_settings($settings); diff --git a/core/includes/install.inc b/core/includes/install.inc index d33bb0a..c7c6ac6 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -167,57 +167,133 @@ function drupal_get_database_types() { * Replaces values in settings.php with values in the submitted array. * * @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 .value key: + * @code + * $settings['config_directories'] = array( + * '.value' => array( + * CONFIG_ACTIVE_DIRECTORY => array( + * 'path' => 'config_' . $config_directories_hash . '/active', + * ), + * CONFIG_STAGING_DIRECTORY => array( + * 'path' => 'config_' . $config_directories_hash . '/staging', + * ), + * ), + * '.required' => TRUE, + * ); + * @endcode + * gets dumped as: + * @code + * @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. @@ -231,6 +307,112 @@ 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(). + * + * Sets the relevant .value values global. + * + * @param $ref + * @param $variable + */ +function _drupal_rewrite_settings_global(&$ref, $variable) { + if (array_key_exists('.value', $variable)) { + $ref = $variable['.value']; + } + else { + foreach ($variable as $k => $v) { + _drupal_rewrite_settings_global($ref[$k], $v); + } + } +} + +/** + * Helper for drupal_rewrite_settings(). + * + * Write the relevant .value values into a buffer. + * + * @param $variable + * @param $prefix + */ +function _drupal_rewrite_settings_dump(array $variable, $prefix) { + $return = ''; + if (array_key_exists('.value', $variable)) { + if (!empty($variable['.required'])) { + $return .= _drupal_rewrite_settings_dump_one($variable, "$prefix = ", "\n"); + } + } + else { + foreach ($variable as $k => $v) { + $return .= _drupal_rewrite_settings_dump($v, $prefix . "['" . $k . "']"); + } + } + return $return; +} + + +/** + * Helper for drupal_rewrite_settings(). + * + * Dump the value of a .value and adds .comments. + * + * @param array $variable + * An array with a required .value key and an optional .comment key. + * @param string $prefix + * @param string $suffix + * @return string + */ +function _drupal_rewrite_settings_dump_one(array $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. * * @see install_settings_form_submit() @@ -244,7 +426,7 @@ 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( + '.value' => array( CONFIG_ACTIVE_DIRECTORY => array( 'path' => 'config_' . $config_directories_hash . '/active', ), @@ -252,7 +434,7 @@ function drupal_install_config_directories() { 'path' => '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 0000000..919c000 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/SettingsRewriteTest.php @@ -0,0 +1,107 @@ + 'drupal_rewrite_settings()', + 'description' => 'Tests the drupal_rewrite_settings() function.', + 'group' => 'System', + ); + } + + 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' => 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' => 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' => 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' => 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' => 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'; + $return = file_put_contents(DRUPAL_ROOT . '/' . $filename, "assertEqual(file_get_contents(DRUPAL_ROOT . '/' . $filename), "