Commit 44963050 authored by Dries's avatar Dries

Merge remote-tracking branch 'heyrocker/8.x-file-config' into 8.x

parents 2cc2aa25 1bc549de
......@@ -240,6 +240,8 @@
*/
const DRUPAL_PHP_FUNCTION_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
require_once DRUPAL_ROOT . '/core/includes/config.inc';
/**
* Provides a caching wrapper to be used in place of large array structures.
*
......@@ -767,7 +769,7 @@ function drupal_settings_initialize() {
global $base_url, $base_path, $base_root;
// Export the following settings.php variables to the global namespace
global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url;
global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url, $config_directory_name, $config_signature_key;
$conf = array();
if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) {
......@@ -1343,8 +1345,10 @@ function drupal_page_header() {
* response is sent.
*/
function drupal_serve_page_from_cache(stdClass $cache) {
$config = config('system.performance');
// Negotiate whether to use compression.
$page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib');
$page_compression = $config->get('page_compression') && extension_loaded('zlib');
$return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
// Get headers set in hook_boot(). Keys are lower-case.
......@@ -1370,7 +1374,7 @@ function drupal_serve_page_from_cache(stdClass $cache) {
// max-age > 0, allowing the page to be cached by external proxies, when a
// session cookie is present unless the Vary header has been replaced or
// unset in hook_boot().
$max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? variable_get('page_cache_maximum_age', 0) : 0;
$max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? $config->get('page_cache_maximum_age') : 0;
$default_headers['Cache-Control'] = 'public, max-age=' . $max_age;
// Entity tag should change if the output changes.
......@@ -2336,7 +2340,8 @@ function _drupal_bootstrap_page_cache() {
}
else {
drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE);
$cache_enabled = variable_get('cache');
$config = config('system.performance');
$cache_enabled = $config->get('cache');
}
drupal_block_denied(ip_address());
// If there is no session cookie and cache is enabled (or forced), try
......
......@@ -2599,7 +2599,8 @@ function drupal_page_footer() {
// Commit the user session, if needed.
drupal_session_commit();
if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) {
$config = config('system.performance');
if ($config->get('cache') && ($cache = drupal_page_set_cache())) {
drupal_serve_page_from_cache($cache);
}
else {
......@@ -3177,7 +3178,14 @@ function drupal_group_css($css) {
* @see system_element_info()
*/
function drupal_aggregate_css(&$css_groups) {
$preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
// Only aggregate during normal site operation.
if (defined('MAINTENANCE_MODE')) {
$preprocess_css = FALSE;
}
else {
$config = config('system.performance');
$preprocess_css = $config->get('preprocess_css');
}
// For each group that needs aggregation, aggregate its items.
foreach ($css_groups as $key => $group) {
......@@ -4410,9 +4418,16 @@ function drupal_group_js($javascript) {
* @see drupal_pre_render_scripts()
*/
function drupal_aggregate_js(&$js_groups) {
// Only aggregate when the site is configured to do so, and not during an
// update.
if (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) {
// Only aggregate during normal site operation.
if (defined('MAINTENANCE_MODE')) {
$preprocess_js = FALSE;
}
else {
$config = config('system.performance');
$preprocess_js = $config->get('preprocess_js');
}
if ($preprocess_js) {
foreach ($js_groups as $key => $group) {
if ($group['type'] == 'file' && $group['preprocess']) {
$js_groups[$key]['data'] = drupal_build_js_cache($group['items']);
......@@ -5187,7 +5202,7 @@ function drupal_page_set_cache() {
}
if ($cache->data['body']) {
if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) {
if (config('system.performance')->get('page_compression') && extension_loaded('zlib')) {
$cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
}
cache('page')->set($cache->cid, $cache->data, $cache->expire);
......
<?php
use Drupal\Core\Config\DrupalVerifiedStorageSQL;
/**
* @file
* This is the API for configuration storage.
*/
/**
* Gets the randomly generated config directory name.
*
* @return
* The directory name.
*/
function config_get_config_directory() {
global $config_directory_name;
if ($test_prefix = drupal_valid_test_ua()) {
$path = conf_path() . '/files/simpletest/config_' . $test_prefix;
}
else {
$path = conf_path() . '/files/' . $config_directory_name;
}
return $path;
}
/**
* Moves the default config supplied by a module to the live config directory.
*
* @param
* The name of the module we are installing.
*
* @todo Make this acknowledge other storage engines rather than having
* SQL be hardcoded.
*/
function config_install_default_config($module) {
$module_config_dir = drupal_get_path('module', $module) . '/config';
$drupal_config_dir = config_get_config_directory();
if (is_dir(drupal_get_path('module', $module) . '/config')) {
$files = glob($module_config_dir . '/' . '*.xml');
foreach ($files as $key => $file) {
// Load config data into the active store and write it out to the
// file system in the drupal config directory. Note the config name
// needs to be the same as the file name WITHOUT the extension.
$parts = explode('/', $file);
$file = array_pop($parts);
$config_name = str_replace('.xml', '', $file);
$verified_storage = new DrupalVerifiedStorageSQL($config_name);
$verified_storage->write(file_get_contents($module_config_dir . '/' . $file));
}
}
}
/**
* Retrieves an iterable array which lists the children under a config 'branch'.
*
* Given the following configuration files:
* - core.entity.node_type.article.xml
* - core.entity.node_type.page.xml
*
* You can pass a prefix 'core.entity.node_type' and get back an array of the
* filenames that match. This allows you to iterate through all files in a
* branch.
*
* @param $prefix
* The prefix of the files we are searching for.
*
* @return
* An array of file names under a branch.
*/
function config_get_signed_file_storage_names_with_prefix($prefix = '') {
$files = glob(config_get_config_directory() . '/' . $prefix . '*.xml');
$clean_name = function ($value) {
return basename($value, '.xml');
};
return array_map($clean_name, $files);
}
/**
* Generates a hash of a config file's contents using our encryption key.
*
* @param $data
* The contents of a configuration file.
*
* @return
* A hash of the data.
*/
function config_sign_data($data) {
// The configuration key is loaded from settings.php and imported into the global namespace
global $config_signature_key;
// SHA-512 is both secure and very fast on 64 bit CPUs.
return hash_hmac('sha512', $data, $config_signature_key);
}
/**
* @todo
*
* @param $prefix
* @todo
*
* @return
* @todo
*/
function config_get_verified_storage_names_with_prefix($prefix = '') {
return DrupalVerifiedStorageSQL::getNamesWithPrefix($prefix);
}
/**
* Retrieves a configuration object.
*
* This is the main entry point to the configuration API. Calling
* @code config(book.admin) @endcode will return a configuration object in which
* the book module can store its administrative settings.
*
* @param $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 $class
* The class name of the config object to be returned. Defaults to
* DrupalConfig.
*
* @return
* An instance of the class specified in the $class parameter.
*
* @todo Replace this with an appropriate factory / ability to inject in
* alternate storage engines..
*/
function config($name, $class = 'Drupal\Core\Config\DrupalConfig') {
return new $class(new DrupalVerifiedStorageSQL($name));
}
/**
* Decodes configuration data from its native format to an associative array.
*
* @param $data
* Configuration data.
*
* @return
* An associative array representation of the data.
*/
function config_decode($data) {
if (empty($data)) {
return array();
}
// This is the fastest and easiest way to get from a string of XML to a PHP
// array since SimpleXML and json_decode()/encode() are native to PHP. Our
// only other choice would be a custom userspace implementation which would
// be a lot less performant and more complex.
$xml = new SimpleXMLElement($data);
$json = json_encode($xml);
return json_decode($json, TRUE);
}
/**
* Standardizes SimpleXML object output into simple arrays for easier use.
*
* @param $xmlObject
* A valid XML string.
*
* @return
* An array representation of A SimpleXML object.
*/
function config_xml_to_array($data) {
$out = array();
$xmlObject = simplexml_load_string($data);
if (is_object($xmlObject)) {
$attributes = (array) $xmlObject->attributes();
if (isset($attributes['@attributes'])) {
$out['#attributes'] = $attributes['@attributes'];
}
}
if (trim((string) $xmlObject)) {
return trim((string) $xmlObject);
}
foreach ($xmlObject as $index => $content) {
if (is_object($content)) {
$out[$index] = config_xml_to_array($content);
}
}
return $out;
}
/**
* Encodes an array into the native configuration format.
*
* @param $data
* An associative array or an object
*
* @return
* A representation of this array or object in the native configuration
* format.
*
* @todo The loaded XML can be invalid; throwing plenty of PHP warnings but no
* catchable error.
*/
function config_encode($data) {
// Convert the supplied array into a SimpleXMLElement.
$xml_object = new SimpleXMLElement("<?xml version=\"1.0\"?><config></config>");
config_array_to_xml($data, $xml_object);
// Pretty print the result.
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml_object->asXML());
return $dom->saveXML();
}
/**
* Encodes an array into XML
*
* @param $data
* An associative array or an object
*
* @return
* A representation of this array or object in the native configuration
* format.
*/
function config_array_to_xml($array, &$xml_object) {
foreach ($array as $key => $value) {
if (is_array($value)) {
if (!is_numeric($key)){
$subnode = $xml_object->addChild("$key");
config_array_to_xml($value, $subnode);
}
else {
config_array_to_xml($value, $xml_object);
}
}
else {
$xml_object->addChild($key, $value);
}
}
}
......@@ -464,6 +464,7 @@ function file_ensure_htaccess() {
file_save_htaccess('private://', TRUE);
}
file_save_htaccess('temporary://', TRUE);
file_save_htaccess(config_get_config_directory(), TRUE);
}
/**
......
......@@ -865,7 +865,8 @@ function drupal_process_form($form_id, &$form, &$form_state) {
// We'll clear out the cached copies of the form and its stored data
// here, as we've finished with them. The in-memory copies are still
// here, though.
if (!variable_get('cache', 0) && !empty($form_state['values']['form_build_id'])) {
$config = config('system.performance');
if (!$config->get('cache') && !empty($form_state['values']['form_build_id'])) {
cache('form')->delete('form_' . $form_state['values']['form_build_id']);
cache('form')->delete('form_state_' . $form_state['values']['form_build_id']);
}
......
......@@ -1010,7 +1010,36 @@ function install_settings_form_submit($form, &$form_state) {
'value' => drupal_hash_base64(drupal_random_bytes(55)),
'required' => TRUE,
);
$settings['config_signature_key'] = array(
'value' => drupal_hash_base64(drupal_random_bytes(55)),
'required' => TRUE,
);
// This duplicates drupal_get_token() because that function can't work yet.
// Wondering if it makes sense to move this later in the process, but its
// nice having all the settings stuff here.
//
// @todo This is actually causing a bug right now, because you can install
// without hitting install_settings_form_submit() if your settings.php
// already has the db stuff in it, and right now in that case your
// config directory never gets created. So this needs to be moved elsewhere.
$settings['config_directory_name'] = array(
'value' => 'config_' . drupal_hmac_base64('', session_id() . $settings['config_signature_key']['value'] . $settings['drupal_hash_salt']['value']),
'required' => TRUE,
);
drupal_rewrite_settings($settings);
// Actually create the config directory named above.
$config_path = conf_path() . '/files/' . $settings['config_directory_name']['value'];
if (!file_prepare_directory($config_path, FILE_CREATE_DIRECTORY)) {
// How best to handle errors here?
};
// Write out a .htaccess file that will protect the config directory from
// prying eyes.
file_save_htaccess($config_path, TRUE);
// Indicate that the settings file has been verified, and check the database
// for the last completed task, now that we have a valid connection. This
// last step is important since we want to trigger an error if the new
......
......@@ -434,6 +434,7 @@ function drupal_install_system() {
))
->execute();
system_rebuild_module_data();
config_install_default_config('system');
}
/**
......
......@@ -375,7 +375,8 @@ function language_provider_invoke($provider_id, $provider = NULL) {
// If the language provider has no cache preference or this is satisfied
// we can execute the callback.
$cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0);
$config = config('system.performance');
$cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == $config->get('cache');
$callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE;
$langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE;
$results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
......
......@@ -461,6 +461,9 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
$versions = drupal_get_schema_versions($module);
$version = $versions ? max($versions) : SCHEMA_INSTALLED;
// Copy any default configuration data to the system config directory/
config_install_default_config($module);
// If the module has no current updates, but has some that were
// previously removed, set the version to the value of
// hook_update_last_removed().
......
......@@ -101,7 +101,8 @@ protected function prepareItem($cache) {
// timer. The cache variable is loaded into the $user object by
// _drupal_session_read() in session.inc. If the data is permanent or we're
// not enforcing a minimum cache lifetime always return the cached data.
if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && $user->cache > $cache->created) {
$config = config('system.performance');
if ($cache->expire != CACHE_PERMANENT && $config->get('cache_lifetime') && $user->cache > $cache->created) {
// This cache data is too old and thus not valid for us, ignore it.
return FALSE;
}
......
<?php
namespace Drupal\Core\Config;
/**
* @todo
*/
class ConfigException extends \Exception {}
<?php
namespace Drupal\Core\Config;
use Drupal\Core\Config\ConfigException;
/**
* @todo
*/
class ConfigFileStorageException extends ConfigException {}
<?php
namespace Drupal\Core\Config;
use Drupal\Core\Config\ConfigFileStorageException;
/**
* @todo
*/
class ConfigFileStorageReadException extends ConfigFileStorageException {}
<?php
namespace Drupal\Core\Config;
use Drupal\Core\Config\ConfigFileStorageException;
/**
* @todo
*/
class ConfigFileStorageSignatureException extends ConfigFileStorageException {}
<?php
namespace Drupal\Core\Config;
use Drupal\Core\Config\DrupalConfigVerifiedStorageInterface;
use Drupal\Core\Config\ConfigException;
/**
* Represents the default configuration storage object.
*/
class DrupalConfig {
/**
* The storage engine to save this config object to.
*
* @var DrupalConfigVerifiedStorageInterface
*/
protected $_verifiedStorage;
protected $data = array();
/**
* Constructs a DrupalConfig object.
*
* @param DrupalConfigVerifiedStorageInterface $verified_storage
* The storage engine where this config object should be saved.
*
* @todo $this should really know about $name and make it publicly accessible.
*/
public function __construct(DrupalConfigVerifiedStorageInterface $verified_storage) {
$this->_verifiedStorage = $verified_storage;
$this->read();
}
/**
* Reads config data from the active store into our object.
*/
public function read() {
$active = (array) config_decode($this->_verifiedStorage->read());
foreach ($active as $key => $value) {
$this->set($key, $value);
}
}
/**
* Checks whether a particular value is overridden.
*
* @param $key
* @todo
*
* @return
* @todo
*/
public function isOverridden($key) {
return isset($this->_overrides[$key]);
}
/**
* Gets data from this config object.
*
* @param $key
* A string that maps to a key within the configuration data.
* For instance in the following XML:
* @code
* <foo>
* <bar>baz</bar>
* </foo>
* @endcode
* A key of 'foo.bar' would return the string 'baz'. However, a key of 'foo'
* would return array('bar' => 'baz').
* If no key is specified, then the entire data array is returned.
*
* The configuration system does not retain data types. Every saved value is
* casted to a string. In most cases this is not an issue; however, it can
* 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.
*
* @return
* The data that was requested.
*/
public function get($key = '') {
if (empty($key)) {
return $this->data;
}
else {
$parts = explode('.', $key);
if (count($parts) == 1) {
return isset($this->data[$key]) ? $this->data[$key] : NULL;
}
else {
$key_exists = NULL;
$value = drupal_array_get_nested_value($this->data, $parts, $key_exists);
return $key_exists ? $value : NULL;
}
}
}
/**
* Sets value in this config object.
*
* @param $key
* @todo
* @param $value
* @todo
*/
public function set($key, $value) {
// Remove all non-alphanumeric characters from the key.
// @todo Reverse this and throw an exception when encountering a key with
// invalid name. The identical validation also needs to happen in get().
// Furthermore, the dot/period is a reserved character; it may appear
// between keys, but not within keys.
$key = preg_replace('@[^a-zA-Z0-9_.-]@', '', $key);
// Type-cast value into a string.
$value = $this->castValue($value);
$parts = explode('.', $key);
if (count($parts) == 1) {
$this->data[$key] = $value;
}
else {
drupal_array_set_nested_value($this->data, $parts, $value);
}
return $this;
}
/**
* Casts a saved value to a string.
*
* The configuration system only saves strings or arrays. Any scalar
* non-string value is cast to a string. The one exception is boolean FALSE
* which would normally become '' when cast to a string, but is manually
* cast to '0' here for convenience and consistency.
*
* Any non-scalar value that is not an array (aka objects) gets cast
* to an array.
*
* @param $value
* A value being saved into the configuration system.
* @param $value
* The value cast to a string or array.
*/
public function castValue($value) {
if (is_scalar($value)) {
// Handle special case of FALSE, which should be '0' instead of ''.
if ($value === FALSE) {
$value = '0';
}
else {
$value = (string) $value;
}
}
else {
// Any non-scalar value must be an array.
if (!is_array($value)) {
$value = (array) $value;
}
// Recurse into any nested keys.
foreach ($value as $key => $nested_value) {
$value[$key] = $this->castValue($nested_value);
}
}
return $value;
}
/**
* Unsets value in this config object.
*
* @param $key
* Name of the key whose value should be unset.
*/
public function clear($key) {
$parts = explode('.', $key);
if (count($parts) == 1) {
unset($this->data[$key]);
}
else {
drupal_array_unset_nested_value($this->data, $parts);
}
}
/**
* Saves the configuration object to disk as XML.
*/
public function save() {
$this->_verifiedStorage->write(config_encode($this->data));
}
/**
* Deletes the configuration object on disk.
*/
public function delete() {
$this->data = array();
$this->_verifiedStorage->delete();
}
}
<?php
namespace Drupal\Core\Config;
use Drupal\Core\Config\DrupalConfigVerifiedStorageInterface;
use Drupal\Core\Config\SignedFileStorage;
/**
* @todo
*/
abstract class DrupalConfigVerifiedStorage implements DrupalConfigVerifiedStorageInterface {
protected $name;
/**
* The local signed file object to read from and write to.
*
* @var SignedFileStorage
*/
protected $signedFile;
/**
* Implements DrupalConfigVerifiedStorageInterface::__construct().
*/
function __construct($name) {
$this->name = $name;
}
/**
* Instantiates a new signed file object or returns the existing one.
*
* @return SignedFileStorage
* The signed file object for this configuration object.