diff --git a/.htaccess b/.htaccess index a69bdd4..ca69678 100644 --- a/.htaccess +++ b/.htaccess @@ -95,7 +95,7 @@ DirectoryIndex index.php index.html index.htm # # If your site is running in a VirtualDocumentRoot at http://example.com/, # uncomment the following line: - # RewriteBase / + #RewriteBase / # Redirect common PHP files to their new locations. RewriteCond %{REQUEST_URI} ^(.*)?/(update.php) [OR] @@ -109,7 +109,7 @@ DirectoryIndex index.php index.html index.htm RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteRule ^ index.php [L] + RewriteRule ^(.*)$ index.php [L] # Rules to correctly serve gzip compressed CSS and JS files. # Requires both mod_rewrite and mod_headers to be enabled. diff --git a/core/CHANGELOG.txt b/core/CHANGELOG.txt index 19963ef..c5a8b5d 100644 --- a/core/CHANGELOG.txt +++ b/core/CHANGELOG.txt @@ -5,7 +5,7 @@ Drupal 8.0, xxxx-xx-xx (development version) - Included the following Symfony2 components: * ClassLoader - PSR-0-compatible autoload routines. * HttpFoundation - Abstraction objects for HTTP requests and responses. -- Removed modules from core +- Removed modules from core. * The following modules have been removed from core, because contributed modules with similar functionality are available: * Blog @@ -14,9 +14,41 @@ Drupal 8.0, xxxx-xx-xx (development version) - Removed the Garland theme from core. - Universally Unique IDentifier (UUID): * Support for generating and validating UUIDs. -- JavaScript changes +- JavaScript changes: * Updated to jQuery 1.7 - +- Tremendously improved language support all around. + * Great language improvements for users: + * Improved language selection with user preference detection in the + installer. + * Moved base language support to Language module. + * Greatly simplified the interface for setting up languages. + * Improved browser language detection considerably. + * Language domain and path prefix configuraton simplified and centralized; + path prefix detection is now default. + * Added HTML 5 language markup; language information added in markup in + several more places. + * Simplified and added new features in interface translation: + * Made interface translation directly accessible from language list. + * Centralized interface translation import to one directory. + * Drupal can now be translated to English and English can be deleted. + * Added support for singular/plural discovery and translation. + * Improved content language support: + * Freely orderable language selector in forms. + * Made it possible to assign language to taxonomy terms, vocabularies + and files. + * Much improved language APIs for developers: + * Added simple APIs and hooks to save/delete/update languages. + * Unified database schemas and APIs to make it easier to spot where + language codes are referenced. + * Made the language negotiation system APIs more consistent for + developers. + * Made it possible for users to have a preferred language separate from + their user entity language. + * The text formatter from t() is now available as format_string(). + * Added support for interface translation contexts in Drupal.t() and + Drupal.formatPlural() in JavaScript. + * Removed textgroups support from interface translation in favor of + native configuration language support. Drupal 7.0, 2011-01-05 ---------------------- diff --git a/core/INSTALL.sqlite.txt b/core/INSTALL.sqlite.txt index b4bfcef..4e64756 100644 --- a/core/INSTALL.sqlite.txt +++ b/core/INSTALL.sqlite.txt @@ -3,7 +3,7 @@ SQLITE REQUIREMENTS ------------------- To use SQLite with your Drupal installation, the following requirements must be -met: Server has PHP 5.3.2 or later with PDO, and the PDO SQLite driver must be +met: Server has PHP 5.3.3 or later with PDO, and the PDO SQLite driver must be enabled. SQLITE DATABASE CREATION diff --git a/core/INSTALL.txt b/core/INSTALL.txt index 245e934..5c0c7b2 100644 --- a/core/INSTALL.txt +++ b/core/INSTALL.txt @@ -15,7 +15,7 @@ REQUIREMENTS AND NOTES Drupal requires: - A web server. Apache (version 2.0 or greater) is recommended. -- PHP 5.3.2 (or greater) (http://www.php.net/). +- PHP 5.3.3 (or greater) (http://www.php.net/). - One of the following databases: - MySQL 5.0.15 (or greater) (http://www.mysql.com/). - MariaDB 5.1.44 (or greater) (http://mariadb.org/). MariaDB is a fully diff --git a/core/includes/batch.queue.inc b/core/includes/batch.queue.inc deleted file mode 100644 index ed290ee..0000000 --- a/core/includes/batch.queue.inc +++ /dev/null @@ -1,84 +0,0 @@ - $this->name))->fetchObject(); - if ($item) { - $item->data = unserialize($item->data); - return $item; - } - return FALSE; - } - - /** - * Retrieves all remaining items in the queue. - * - * This is specific to Batch API and is not part of the DrupalQueueInterface. - */ - public function getAllItems() { - $result = array(); - $items = db_query('SELECT data FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchAll(); - foreach ($items as $item) { - $result[] = unserialize($item->data); - } - return $result; - } -} - -/** - * Defines a batch queue for non-progressive batches. - */ -class BatchMemoryQueue extends MemoryQueue { - - /** - * Overrides MemoryQueue::claimItem(). - * - * Unlike MemoryQueue::claimItem(), this method provides a default lease - * time of 0 (no expiration) instead of 30. This allows the item to be - * claimed repeatedly until it is deleted. - */ - public function claimItem($lease_time = 0) { - if (!empty($this->queue)) { - reset($this->queue); - return current($this->queue); - } - return FALSE; - } - - /** - * Retrieves all remaining items in the queue. - * - * This is specific to Batch API and is not part of the DrupalQueueInterface. - */ - public function getAllItems() { - $result = array(); - foreach ($this->queue as $item) { - $result[] = $item->data; - } - return $result; - } -} diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 52a8cb6..7869daa 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -22,7 +22,7 @@ const DRUPAL_CORE_COMPATIBILITY = '8.x'; /** * Minimum supported version of PHP. */ -const DRUPAL_MINIMUM_PHP = '5.3.2'; +const DRUPAL_MINIMUM_PHP = '5.3.3'; /** * Minimum recommended value of PHP memory_limit. @@ -163,18 +163,47 @@ const DRUPAL_AUTHENTICATED_RID = 2; const DRUPAL_KILOBYTE = 1024; /** - * System language (only applicable to UI). + * Special system language code (only applicable to UI language). * - * Refers to the language used in Drupal and module/theme source code. + * Refers to the language used in Drupal and module/theme source code. Drupal + * uses the built-in text for English by default, but if configured to allow + * translation/customization of English, we need to differentiate between the + * built-in language and the English translation. */ const LANGUAGE_SYSTEM = 'system'; /** - * The language code used when no language is explicitly assigned. + * The language code used when no language is explicitly assigned (yet). * - * Defined by ISO639-2 for "Undetermined". + * Should be used when language information is not available or cannot be + * determined. This special language code is useful when we know the data + * might have linguistic information, but we don't know the language. + * + * See http://www.w3.org/International/questions/qa-no-language#undetermined. + */ +const LANGUAGE_NOT_SPECIFIED = 'und'; + +/** + * The language code used when the marked object has no linguistic content. + * + * Should be used when we explicitly know that the data referred has no + * linguistic content. + * + * See http://www.w3.org/International/questions/qa-no-language#nonlinguistic. */ -const LANGUAGE_NONE = 'und'; +const LANGUAGE_NOT_APPLICABLE = 'zxx'; + +/** + * The language code used when multiple languages could be applied. + * + * Should be used when individual parts of the data cannot be marked with + * language, but we know there are multiple languages involved. Such as a + * PDF file for an electronic appliance, which has usage manuals in 8 + * languages but is uploaded as one file in Drupal. + * + * Defined by ISO639-2 for "Multiple languages". + */ +const LANGUAGE_MULTIPLE = 'mul'; /** * The type of language used to define the content language. @@ -240,205 +269,7 @@ const REGISTRY_WRITE_LOOKUP_CACHE = 2; */ const DRUPAL_PHP_FUNCTION_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; -/** - * Provides a caching wrapper to be used in place of large array structures. - * - * This class should be extended by systems that need to cache large amounts - * of data and have it represented as an array to calling functions. These - * arrays can become very large, so ArrayAccess is used to allow different - * strategies to be used for caching internally (lazy loading, building caches - * over time etc.). This can dramatically reduce the amount of data that needs - * to be loaded from cache backends on each request, and memory usage from - * static caches of that same data. - * - * Note that array_* functions do not work with ArrayAccess. Systems using - * DrupalCacheArray should use this only internally. If providing API functions - * that return the full array, this can be cached separately or returned - * directly. However since DrupalCacheArray holds partial content by design, it - * should be a normal PHP array or otherwise contain the full structure. - * - * Note also that due to limitations in PHP prior to 5.3.4, it is impossible to - * write directly to the contents of nested arrays contained in this object. - * Only writes to the top-level array elements are possible. So if you - * previously had set $object['foo'] = array(1, 2, 'bar' => 'baz'), but later - * want to change the value of 'bar' from 'baz' to 'foobar', you cannot do so - * a targeted write like $object['foo']['bar'] = 'foobar'. Instead, you must - * overwrite the entire top-level 'foo' array with the entire set of new - * values: $object['foo'] = array(1, 2, 'bar' => 'foobar'). Due to this same - * limitation, attempts to create references to any contained data, nested or - * otherwise, will fail silently. So $var = &$object['foo'] will not throw an - * error, and $var will be populated with the contents of $object['foo'], but - * that data will be passed by value, not reference. For more information on - * the PHP limitation, see the note in the official PHP documentation at· - * http://php.net/manual/en/arrayaccess.offsetget.php on - * ArrayAccess::offsetGet(). - * - * By default, the class accounts for caches where calling functions might - * request keys in the array that won't exist even after a cache rebuild. This - * prevents situations where a cache rebuild would be triggered over and over - * due to a 'missing' item. These cases are stored internally as a value of - * NULL. This means that the offsetGet() and offsetExists() methods - * must be overridden if caching an array where the top level values can - * legitimately be NULL, and where $object->offsetExists() needs to correctly - * return (equivalent to array_key_exists() vs. isset()). This should not - * be necessary in the majority of cases. - * - * Classes extending this class must override at least the - * resolveCacheMiss() method to have a working implementation. - * - * offsetSet() is not overridden by this class by default. In practice this - * means that assigning an offset via arrayAccess will only apply while the - * object is in scope and will not be written back to the persistent cache. - * This follows a similar pattern to static vs. persistent caching in - * procedural code. Extending classes may wish to alter this behaviour, for - * example by overriding offsetSet() and adding an automatic call to persist(). - * - * @see SchemaCache - */ -abstract class DrupalCacheArray implements ArrayAccess { - - /** - * A cid to pass to cache()->set() and cache()->get(). - */ - protected $cid; - - /** - * A bin to pass to cache()->set() and cache()->get(). - */ - protected $bin; - - /** - * An array of keys to add to the cache at the end of the request. - */ - protected $keysToPersist = array(); - - /** - * Storage for the data itself. - */ - protected $storage = array(); - - /** - * Constructs a DrupalCacheArray object. - * - * @param $cid - * The cid for the array being cached. - * @param $bin - * The bin to cache the array. - */ - public function __construct($cid, $bin) { - $this->cid = $cid; - $this->bin = $bin; - - if ($cached = cache($bin)->get($this->cid)) { - $this->storage = $cached->data; - } - } - - /** - * Implements ArrayAccess::offsetExists(). - */ - public function offsetExists($offset) { - return $this->offsetGet($offset) !== NULL; - } - - /** - * Implements ArrayAccess::offsetGet(). - */ - public function offsetGet($offset) { - if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) { - return $this->storage[$offset]; - } - else { - return $this->resolveCacheMiss($offset); - } - } - - /** - * Implements ArrayAccess::offsetSet(). - */ - public function offsetSet($offset, $value) { - $this->storage[$offset] = $value; - } - - /** - * Implements ArrayAccess::offsetUnset(). - */ - public function offsetUnset($offset) { - unset($this->storage[$offset]); - } - - /** - * Flags an offset value to be written to the persistent cache. - * - * If a value is assigned to a cache object with offsetSet(), by default it - * will not be written to the persistent cache unless it is flagged with this - * method. This allows items to be cached for the duration of a request, - * without necessarily writing back to the persistent cache at the end. - * - * @param $offset - * The array offset that was request. - * @param $persist - * Optional boolean to specify whether the offset should be persisted or - * not, defaults to TRUE. When called with $persist = FALSE the offset will - * be unflagged so that it will not written at the end of the request. - */ - protected function persist($offset, $persist = TRUE) { - $this->keysToPersist[$offset] = $persist; - } - - /** - * Resolves a cache miss. - * - * When an offset is not found in the object, this is treated as a cache - * miss. This method allows classes implementing the interface to look up - * the actual value and allow it to be cached. - * - * @param $offset - * The offset that was requested. - * - * @return - * The value of the offset, or NULL if no value was found. - */ - abstract protected function resolveCacheMiss($offset); - - /** - * Writes a value to the persistent cache immediately. - * - * @param $data - * The data to write to the persistent cache. - * @param $lock - * Whether to acquire a lock before writing to cache. - */ - protected function set($data, $lock = TRUE) { - // Lock cache writes to help avoid stampedes. - // To implement locking for cache misses, override __construct(). - $lock_name = $this->cid . ':' . $this->bin; - if (!$lock || lock_acquire($lock_name)) { - if ($cached = cache($this->bin)->get($this->cid)) { - $data = $cached->data + $data; - } - cache($this->bin)->set($this->cid, $data); - if ($lock) { - lock_release($lock_name); - } - } - } - - /** - * Destructs the DrupalCacheArray object. - */ - public function __destruct() { - $data = array(); - foreach ($this->keysToPersist as $offset => $persist) { - if ($persist) { - $data[$offset] = $this->storage[$offset]; - } - } - if (!empty($data)) { - $this->set($data); - } - } -} +require_once DRUPAL_ROOT . '/core/includes/config.inc'; /** * Starts the timer with the specified name. @@ -767,7 +598,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 +1174,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 +1203,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 +2169,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 @@ -2416,7 +2250,7 @@ function _drupal_bootstrap_database() { // Initialize the database system. Note that the connection // won't be initialized until it is actually requested. - require_once DRUPAL_ROOT . '/core/includes/database/database.inc'; + require_once DRUPAL_ROOT . '/core/includes/database.inc'; // Register autoload functions so that we can access classes and interfaces. // The database autoload routine comes first so that we can load the database @@ -2728,7 +2562,7 @@ function language_load($langcode) { * The printed name of the language. */ function language_name($langcode) { - if ($langcode == LANGUAGE_NONE) { + if ($langcode == LANGUAGE_NOT_SPECIFIED) { return t('None'); } @@ -2912,133 +2746,6 @@ function ip_address() { } /** - * @ingroup schemaapi - * @{ - */ - -/** - * Gets the schema definition of a table, or the whole database schema. - * - * The returned schema will include any modifications made by any - * module that implements hook_schema_alter(). - * - * @param $table - * The name of the table. If not given, the schema of all tables is returned. - * @param $rebuild - * If true, the schema will be rebuilt instead of retrieved from the cache. - */ -function drupal_get_schema($table = NULL, $rebuild = FALSE) { - static $schema; - - if ($rebuild || !isset($table)) { - $schema = drupal_get_complete_schema($rebuild); - } - elseif (!isset($schema)) { - $schema = new SchemaCache(); - } - - if (!isset($table)) { - return $schema; - } - if (isset($schema[$table])) { - return $schema[$table]; - } - else { - return FALSE; - } -} - -/** - * Extends DrupalCacheArray to allow for dynamic building of the schema cache. - */ -class SchemaCache extends DrupalCacheArray { - - /** - * Constructs a SchemaCache object. - */ - public function __construct() { - // Cache by request method. - parent::__construct('schema:runtime:' . ($_SERVER['REQUEST_METHOD'] == 'GET'), 'cache'); - } - - /** - * Overrides DrupalCacheArray::resolveCacheMiss(). - */ - protected function resolveCacheMiss($offset) { - $complete_schema = drupal_get_complete_schema(); - $value = isset($complete_schema[$offset]) ? $complete_schema[$offset] : NULL; - $this->storage[$offset] = $value; - $this->persist($offset); - return $value; - } -} - -/** - * Gets the whole database schema. - * - * The returned schema will include any modifications made by any - * module that implements hook_schema_alter(). - * - * @param $rebuild - * If true, the schema will be rebuilt instead of retrieved from the cache. - */ -function drupal_get_complete_schema($rebuild = FALSE) { - static $schema = array(); - - if (empty($schema) || $rebuild) { - // Try to load the schema from cache. - if (!$rebuild && $cached = cache()->get('schema')) { - $schema = $cached->data; - } - // Otherwise, rebuild the schema cache. - else { - $schema = array(); - // Load the .install files to get hook_schema. - // On some databases this function may be called before bootstrap has - // been completed, so we force the functions we need to load just in case. - if (function_exists('module_load_all_includes')) { - // This function can be called very early in the bootstrap process, so - // we force the module_list() cache to be refreshed to ensure that it - // contains the complete list of modules before we go on to call - // module_load_all_includes(). - module_list(TRUE); - module_load_all_includes('install'); - } - - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - // Invoke hook_schema for all modules. - foreach (module_implements('schema') as $module) { - // Cast the result of hook_schema() to an array, as a NULL return value - // would cause array_merge() to set the $schema variable to NULL as well. - // That would break modules which use $schema further down the line. - $current = (array) module_invoke($module, 'schema'); - // Set 'module' and 'name' keys for each table, and remove descriptions, - // as they needlessly slow down cache()->get() for every single request. - _drupal_schema_initialize($current, $module); - $schema = array_merge($schema, $current); - } - - drupal_alter('schema', $schema); - // If the schema is empty, avoid saving it: some database engines require - // the schema to perform queries, and this could lead to infinite loops. - if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) { - cache()->set('schema', $schema); - } - if ($rebuild) { - cache()->deletePrefix('schema:'); - } - } - } - - return $schema; -} - -/** - * @} End of "ingroup schemaapi". - */ - - -/** * @ingroup registry * @{ */ @@ -3055,15 +2762,15 @@ function drupal_get_complete_schema($rebuild = FALSE) { * A UniversalClassLoader class instance (or extension thereof). */ function drupal_classloader() { - // Include the Symfony ClassLoader for loading PSR-0-compatible classes. - require_once DRUPAL_ROOT . '/core/vendor/Symfony/Component/ClassLoader/UniversalClassLoader.php'; - // By default, use the UniversalClassLoader which is best for development, // as it does not break when code is moved on the file system. However, as it // is slow, allow to use the APC class loader in production. static $loader; if (!isset($loader)) { + // Include the Symfony ClassLoader for loading PSR-0-compatible classes. + require_once DRUPAL_ROOT . '/core/vendor/Symfony/Component/ClassLoader/UniversalClassLoader.php'; + // @todo Use a cleaner way than variable_get() to switch autoloaders. switch (variable_get('autoloader_mode', 'default')) { case 'apc': @@ -3086,6 +2793,19 @@ function drupal_classloader() { } /** + * Registers an additional namespace. + * + * @param string $name + * The namespace component to register; e.g., 'node'. + * @param string $path + * The relative path to the Drupal component in the filesystem. + */ +function drupal_classloader_register($name, $path) { + $loader = drupal_classloader(); + $loader->registerNamespace('Drupal\\' . $name, DRUPAL_ROOT . '/' . $path . '/lib'); +} + +/** * Confirms that an interface is available. * * This function is rarely called directly. Instead, it is registered as an diff --git a/core/includes/common.inc b/core/includes/common.inc index 17f83ad..58332f0 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -163,6 +163,15 @@ const DRUPAL_CACHE_PER_PAGE = 0x0004; const DRUPAL_CACHE_GLOBAL = 0x0008; /** + * The delimiter used to split plural strings. + * + * This is the ETX (End of text) character and is used as a minimal means to + * separate singular and plural variants in source and translation text. It + * was found to be the most compatible delimiter for the supported databases. + */ +const LOCALE_PLURAL_DELIMITER = "\03"; + +/** * Adds content to a specified region. * * @param $region @@ -1719,27 +1728,34 @@ function format_xml_elements($array) { */ function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) { $args['@count'] = $count; + // Join both forms to search a translation. + $tranlatable_string = implode(LOCALE_PLURAL_DELIMITER, array($singular, $plural)); + // Translate as usual. + $translated_strings = t($tranlatable_string, $args, $options); + // Split joined translation strings into array. + $translated_array = explode(LOCALE_PLURAL_DELIMITER, $translated_strings); + if ($count == 1) { - return t($singular, $args, $options); + return $translated_array[0]; } // Get the plural index through the gettext formula. + // @todo implement static variable to minimize function_exists() usage. $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1; - // If the index cannot be computed, use the plural as a fallback (which - // allows for most flexiblity with the replaceable @count value). - if ($index < 0) { - return t($plural, $args, $options); + if ($index == 0) { + // Singular form. + return $translated_array[0]; } else { - switch ($index) { - case "0": - return t($singular, $args, $options); - case "1": - return t($plural, $args, $options); - default: - unset($args['@count']); - $args['@count[' . $index . ']'] = $count; - return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options); + if (isset($translated_array[$index])) { + // N-th plural form. + return $translated_array[$index]; + } + else { + // If the index cannot be computed or there's no translation, use + // the second plural form as a fallback (which allows for most flexiblity + // with the replaceable @count value). + return $translated_array[1]; } } } @@ -2184,31 +2200,20 @@ function url($path = NULL, array $options = array()) { $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; - // With Clean URLs. - if (!empty($GLOBALS['conf']['clean_url'])) { - $path = drupal_encode_path($prefix . $path); - if ($options['query']) { - return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; - } - else { - return $base . $path . $options['fragment']; - } + // If Clean URLs are not enabled, we need to prefix the script name onto + // the link. + // @todo: Make this dynamic based on the request object without using a global + // request object. + if (empty($GLOBALS['conf']['clean_url'])) { + $base .= 'index.php/'; + } + + $path = drupal_encode_path($prefix . $path); + if ($options['query']) { + return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; } - // Without Clean URLs. else { - $path = $prefix . $path; - $query = array(); - if (!empty($path)) { - $query['q'] = $path; - } - if ($options['query']) { - // We do not use array_merge() here to prevent overriding $path via query - // parameters. - $query += $options['query']; - } - $query = $query ? ('?' . drupal_http_build_query($query)) : ''; - $script = isset($options['script']) ? $options['script'] : ''; - return $base . $script . $query . $options['fragment']; + return $base . $path . $options['fragment']; } } @@ -2497,6 +2502,12 @@ function drupal_deliver_html_page($page_callback_result) { drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); } + // Send X-UA-Compatible HTTP header to force IE to use the most recent + // rendering engine or use Chrome's frame rendering engine if available. + if (is_null(drupal_get_http_header('X-UA-Compatible'))) { + drupal_add_http_header('X-UA-Compatible', 'IE=edge,chrome=1'); + } + // Send appropriate HTTP-Header for browsers and search engines. global $language_interface; drupal_add_http_header('Content-Language', $language_interface->langcode); @@ -2599,7 +2610,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 { @@ -2820,7 +2832,7 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { * - 'group': A number identifying the group in which to add the stylesheet. * Available constants are: * - CSS_SYSTEM: Any system-layer CSS. - * - CSS_DEFAULT: Any module-layer CSS. + * - CSS_DEFAULT: (default) Any module-layer CSS. * - CSS_THEME: Any theme-layer CSS. * The group number serves as a weight: the markup for loading a stylesheet * within a lower weight group is output to the page before the markup for @@ -3177,7 +3189,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 +4429,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']); @@ -5109,6 +5135,7 @@ function _drupal_bootstrap_full() { require_once DRUPAL_ROOT . '/core/includes/ajax.inc'; require_once DRUPAL_ROOT . '/core/includes/token.inc'; require_once DRUPAL_ROOT . '/core/includes/errors.inc'; + require_once DRUPAL_ROOT . '/core/includes/schema.inc'; // Detect string handling method unicode_check(); @@ -5187,7 +5214,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); @@ -5233,7 +5260,7 @@ function drupal_cron_run() { // Make sure every queue exists. There is no harm in trying to recreate an // existing queue. foreach ($queues as $queue_name => $info) { - DrupalQueue::get($queue_name)->createQueue(); + queue($queue_name)->createQueue(); } // Register shutdown callback. drupal_register_shutdown_function('drupal_cron_cleanup'); @@ -5263,7 +5290,7 @@ function drupal_cron_run() { foreach ($queues as $queue_name => $info) { $function = $info['worker callback']; $end = time() + (isset($info['time']) ? $info['time'] : 15); - $queue = DrupalQueue::get($queue_name); + $queue = queue($queue_name); while (time() < $end && ($item = $queue->claimItem())) { $function($item->data); $queue->deleteItem($item); @@ -6934,6 +6961,12 @@ function drupal_common_theme() { 'tel' => array( 'render element' => 'element', ), + 'email' => array( + 'render element' => 'element', + ), + 'url' => array( + 'render element' => 'element', + ), 'form' => array( 'render element' => 'element', ), @@ -6968,317 +7001,6 @@ function drupal_common_theme() { } /** - * @ingroup schemaapi - * @{ - */ - -/** - * Creates all tables defined in a module's hook_schema(). - * - * Note: This function does not pass the module's schema through - * hook_schema_alter(). The module's tables will be created exactly as the - * module defines them. - * - * @param $module - * The module for which the tables will be created. - */ -function drupal_install_schema($module) { - $schema = drupal_get_schema_unprocessed($module); - _drupal_schema_initialize($schema, $module, FALSE); - - foreach ($schema as $name => $table) { - db_create_table($name, $table); - } -} - -/** - * Removes all tables defined in a module's hook_schema(). - * - * Note: This function does not pass the module's schema through - * hook_schema_alter(). The module's tables will be created exactly as the - * module defines them. - * - * @param $module - * The module for which the tables will be removed. - * - * @return - * An array of arrays with the following key/value pairs: - * - success: a boolean indicating whether the query succeeded. - * - query: the SQL query(s) executed, passed through check_plain(). - */ -function drupal_uninstall_schema($module) { - $schema = drupal_get_schema_unprocessed($module); - _drupal_schema_initialize($schema, $module, FALSE); - - foreach ($schema as $table) { - if (db_table_exists($table['name'])) { - db_drop_table($table['name']); - } - } -} - -/** - * Returns the unprocessed and unaltered version of a module's schema. - * - * Use this function only if you explicitly need the original - * specification of a schema, as it was defined in a module's - * hook_schema(). No additional default values will be set, - * hook_schema_alter() is not invoked and these unprocessed - * definitions won't be cached. - * - * This function can be used to retrieve a schema specification in - * hook_schema(), so it allows you to derive your tables from existing - * specifications. - * - * It is also used by drupal_install_schema() and - * drupal_uninstall_schema() to ensure that a module's tables are - * created exactly as specified without any changes introduced by a - * module that implements hook_schema_alter(). - * - * @param $module - * The module to which the table belongs. - * @param $table - * The name of the table. If not given, the module's complete schema - * is returned. - */ -function drupal_get_schema_unprocessed($module, $table = NULL) { - // Load the .install file to get hook_schema. - module_load_install($module); - $schema = module_invoke($module, 'schema'); - - if (isset($table) && isset($schema[$table])) { - return $schema[$table]; - } - elseif (!empty($schema)) { - return $schema; - } - return array(); -} - -/** - * Fills in required default values for table definitions from hook_schema(). - * - * @param $schema - * The schema definition array as it was returned by the module's - * hook_schema(). - * @param $module - * The module for which hook_schema() was invoked. - * @param $remove_descriptions - * (optional) Whether to additionally remove 'description' keys of all tables - * and fields to improve performance of serialize() and unserialize(). - * Defaults to TRUE. - */ -function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) { - // Set the name and module key for all tables. - foreach ($schema as $name => &$table) { - if (empty($table['module'])) { - $table['module'] = $module; - } - if (!isset($table['name'])) { - $table['name'] = $name; - } - if ($remove_descriptions) { - unset($table['description']); - foreach ($table['fields'] as &$field) { - unset($field['description']); - } - } - } -} - -/** - * Retrieves a list of fields from a table schema. - * - * The returned list is suitable for use in an SQL query. - * - * @param $table - * The name of the table from which to retrieve fields. - * @param - * An optional prefix to to all fields. - * - * @return An array of fields. - */ -function drupal_schema_fields_sql($table, $prefix = NULL) { - $schema = drupal_get_schema($table); - $fields = array_keys($schema['fields']); - if ($prefix) { - $columns = array(); - foreach ($fields as $field) { - $columns[] = "$prefix.$field"; - } - return $columns; - } - else { - return $fields; - } -} - -/** - * Saves (inserts or updates) a record to the database based upon the schema. - * - * @param $table - * The name of the table; this must be defined by a hook_schema() - * implementation. - * @param $record - * An object or array representing the record to write, passed in by - * reference. If inserting a new record, values not provided in $record will - * be populated in $record and in the database with the default values from - * the schema, as well as a single serial (auto-increment) field (if present). - * If updating an existing record, only provided values are updated in the - * database, and $record is not modified. - * @param $primary_keys - * To indicate that this is a new record to be inserted, omit this argument. - * If this is an update, this argument specifies the primary keys' field - * names. If there is only 1 field in the key, you may pass in a string; if - * there are multiple fields in the key, pass in an array. - * - * @return - * If the record insert or update failed, returns FALSE. If it succeeded, - * returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed. - */ -function drupal_write_record($table, &$record, $primary_keys = array()) { - // Standardize $primary_keys to an array. - if (is_string($primary_keys)) { - $primary_keys = array($primary_keys); - } - - $schema = drupal_get_schema($table); - if (empty($schema)) { - return FALSE; - } - - $object = (object) $record; - $fields = array(); - $default_fields = array(); - - // Go through the schema to determine fields to write. - foreach ($schema['fields'] as $field => $info) { - if ($info['type'] == 'serial') { - // Skip serial types if we are updating. - if (!empty($primary_keys)) { - continue; - } - // Track serial field so we can helpfully populate them after the query. - // NOTE: Each table should come with one serial field only. - $serial = $field; - } - - // Skip field if it is in $primary_keys as it is unnecessary to update a - // field to the value it is already set to. - if (in_array($field, $primary_keys)) { - continue; - } - - if (!property_exists($object, $field)) { - // Skip fields that are not provided, default values are already known - // by the database. - $default_fields[] = $field; - continue; - } - - // Build array of fields to update or insert. - if (empty($info['serialize'])) { - $fields[$field] = $object->$field; - } - else { - $fields[$field] = serialize($object->$field); - } - - // Type cast to proper datatype, except when the value is NULL and the - // column allows this. - // - // MySQL PDO silently casts e.g. FALSE and '' to 0 when inserting the value - // into an integer column, but PostgreSQL PDO does not. Also type cast NULL - // when the column does not allow this. - if (isset($object->$field) || !empty($info['not null'])) { - if ($info['type'] == 'int' || $info['type'] == 'serial') { - $fields[$field] = (int) $fields[$field]; - } - elseif ($info['type'] == 'float') { - $fields[$field] = (float) $fields[$field]; - } - else { - $fields[$field] = (string) $fields[$field]; - } - } - } - - // Build the SQL. - if (empty($primary_keys)) { - // We are doing an insert. - $options = array('return' => Database::RETURN_INSERT_ID); - if (isset($serial) && isset($fields[$serial])) { - // If the serial column has been explicitly set with an ID, then we don't - // require the database to return the last insert id. - if ($fields[$serial]) { - $options['return'] = Database::RETURN_AFFECTED; - } - // If a serial column does exist with no value (i.e. 0) then remove it as - // the database will insert the correct value for us. - else { - unset($fields[$serial]); - } - } - // Create an INSERT query. useDefaults() is necessary for the SQL to be - // valid when $fields is empty. - $query = db_insert($table, $options) - ->fields($fields) - ->useDefaults($default_fields); - - $return = SAVED_NEW; - } - else { - // Create an UPDATE query. - $query = db_update($table)->fields($fields); - foreach ($primary_keys as $key) { - $query->condition($key, $object->$key); - } - $return = SAVED_UPDATED; - } - - // Execute the SQL. - if ($query_return = $query->execute()) { - if (isset($serial)) { - // If the database was not told to return the last insert id, it will be - // because we already know it. - if (isset($options) && $options['return'] != Database::RETURN_INSERT_ID) { - $object->$serial = $fields[$serial]; - } - else { - $object->$serial = $query_return; - } - } - } - // If we have a single-field primary key but got no insert ID, the - // query failed. Note that we explicitly check for FALSE, because - // a valid update query which doesn't change any values will return - // zero (0) affected rows. - elseif ($query_return === FALSE && count($primary_keys) == 1) { - $return = FALSE; - } - - // If we are inserting, populate empty fields with default values. - if (empty($primary_keys)) { - foreach ($schema['fields'] as $field => $info) { - if (isset($info['default']) && !property_exists($object, $field)) { - $object->$field = $info['default']; - } - } - } - - // If we began with an array, convert back. - if (is_array($record)) { - $record = (array) $object; - } - - return $return; -} - -/** - * @} End of "ingroup schemaapi". - */ - -/** * Parses Drupal module and theme .info files. * * Info files are NOT for placing arbitrary theme and module-specific settings. @@ -7776,9 +7498,10 @@ function archiver_get_archiver($file) { * file system, for example to update modules that have newer releases, or to * install a new theme. * - * @return + * @return array * The Drupal Updater class registry. * + * @see Drupal\Core\Updater\Updater * @see hook_updater_info() * @see hook_updater_info_alter() */ @@ -7825,3 +7548,97 @@ function drupal_get_filetransfer_info() { } return $info; } + +/** + * @defgroup queue Queue operations + * @{ + * Queue items to allow later processing. + * + * The queue system allows placing items in a queue and processing them later. + * The system tries to ensure that only one consumer can process an item. + * + * Before a queue can be used it needs to be created by + * Drupal\Core\Queue\QueueInterface::createQueue(). + * + * Items can be added to the queue by passing an arbitrary data object to + * Drupal\Core\Queue\QueueInterface::createItem(). + * + * To process an item, call Drupal\Core\Queue\QueueInterface::claimItem() and + * specify how long you want to have a lease for working on that item. + * When finished processing, the item needs to be deleted by calling + * Drupal\Core\Queue\QueueInterface::deleteItem(). If the consumer dies, the + * item will be made available again by the Drupal\Core\Queue\QueueInterface + * implementation once the lease expires. Another consumer will then be able to + * receive it when calling Drupal\Core\Queue\QueueInterface::claimItem(). + * Due to this, the processing code should be aware that an item might be handed + * over for processing more than once. + * + * The $item object used by the Drupal\Core\Queue\QueueInterface can contain + * arbitrary metadata depending on the implementation. Systems using the + * interface should only rely on the data property which will contain the + * information passed to Drupal\Core\Queue\QueueInterface::createItem(). + * The full queue item returned by Drupal\Core\Queue\QueueInterface::claimItem() + * needs to be passed to Drupal\Core\Queue\QueueInterface::deleteItem() once + * processing is completed. + * + * There are two kinds of queue backends available: reliable, which preserves + * the order of messages and guarantees that every item will be executed at + * least once. The non-reliable kind only does a best effort to preserve order + * in messages and to execute them at least once but there is a small chance + * that some items get lost. For example, some distributed back-ends like + * Amazon SQS will be managing jobs for a large set of producers and consumers + * where a strict FIFO ordering will likely not be preserved. Another example + * would be an in-memory queue backend which might lose items if it crashes. + * However, such a backend would be able to deal with significantly more writes + * than a reliable queue and for many tasks this is more important. See + * aggregator_cron() for an example of how to effectively utilize a + * non-reliable queue. Another example is doing Twitter statistics -- the small + * possibility of losing a few items is insignificant next to power of the + * queue being able to keep up with writes. As described in the processing + * section, regardless of the queue being reliable or not, the processing code + * should be aware that an item might be handed over for processing more than + * once (because the processing code might time out before it finishes). + */ + +/** + * Instantiates and statically caches the correct class for a queue. + * + * The following variables can be set by variable_set or $conf overrides: + * - queue_class_$name: the class to be used for the queue $name. + * - queue_default_class: the class to use when queue_class_$name is not + * defined. Defaults to Drupal\Core\Queue\System, a reliable backend using + * SQL. + * - queue_default_reliable_class: the class to use when queue_class_$name is + * not defined and the queue_default_class is not reliable. Defaults to + * Drupal\Core\Queue\System. + * + * @param string $name + * The name of the queue to work with. + * @param bool $reliable + * TRUE if the ordering of items and guaranteeing every item executes at + * least once is important, FALSE if scalability is the main concern. Defaults + * to FALSE. + * + * @return Drupal\Core\Queue\QueueInterface + * The queue object for a given name. + * + * @see Drupal\Core\Queue\QueueInterface + */ +function queue($name, $reliable = FALSE) { + static $queues; + if (!isset($queues[$name])) { + $class = variable_get('queue_class_' . $name, NULL); + if ($class && $reliable && in_array('Drupal\Core\Queue\ReliableQueueInterface', class_implements($class))) { + $class = variable_get('queue_default_reliable_class', 'Drupal\Core\Queue\System'); + } + elseif (!$class) { + $class = variable_get('queue_default_class', 'Drupal\Core\Queue\System'); + } + $queues[$name] = new $class($name); + } + return $queues[$name]; +} + +/** + * @} End of "defgroup queue". + */ diff --git a/core/includes/config.inc b/core/includes/config.inc new file mode 100644 index 0000000..8c2772b --- /dev/null +++ b/core/includes/config.inc @@ -0,0 +1,242 @@ + $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(""); + 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); + } + } +} diff --git a/core/includes/database/database.inc b/core/includes/database.inc similarity index 100% rename from core/includes/database/database.inc rename to core/includes/database.inc diff --git a/core/includes/file.inc b/core/includes/file.inc index 0e69bfe..2771f62 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -5,14 +5,78 @@ * API for handling file uploads and server file management. */ +use Drupal\Core\StreamWrapper\LocalStream; + /** - * Manually include stream wrapper code. + * Stream wrapper bit flags that are the basis for composite types. * - * Stream wrapper code is included here because there are cases where - * File API is needed before a bootstrap, or in an alternate order (e.g. - * maintenance theme). + * Note that 0x0002 is skipped, because it was the value of a constant that has + * since been removed. + */ + +/** + * Stream wrapper bit flag -- a filter that matches all wrappers. + */ +const STREAM_WRAPPERS_ALL = 0x0000; + +/** + * Stream wrapper bit flag -- refers to a local file system location. + */ +const STREAM_WRAPPERS_LOCAL = 0x0001; + +/** + * Stream wrapper bit flag -- wrapper is readable (almost always true). + */ +const STREAM_WRAPPERS_READ = 0x0004; + +/** + * Stream wrapper bit flag -- wrapper is writeable. + */ +const STREAM_WRAPPERS_WRITE = 0x0008; + +/** + * Stream wrapper bit flag -- exposed in the UI and potentially web accessible. + */ +const STREAM_WRAPPERS_VISIBLE = 0x0010; + +/** + * Composite stream wrapper bit flags that are usually used as the types. */ -require_once DRUPAL_ROOT . '/core/includes/stream_wrappers.inc'; + +/** + * Stream wrapper type flag -- not visible in the UI or accessible via web, + * but readable and writable. E.g. the temporary directory for uploads. + */ +define('STREAM_WRAPPERS_HIDDEN', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE); + +/** + * Stream wrapper type flag -- hidden, readable and writeable using local files. + */ +define('STREAM_WRAPPERS_LOCAL_HIDDEN', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_HIDDEN); + +/** + * Stream wrapper type flag -- visible, readable and writeable. + */ +define('STREAM_WRAPPERS_WRITE_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE | STREAM_WRAPPERS_VISIBLE); + +/** + * Stream wrapper type flag -- visible and read-only. + */ +define('STREAM_WRAPPERS_READ_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_VISIBLE); + +/** + * Stream wrapper type flag -- the default when 'type' is omitted from + * hook_stream_wrappers(). This does not include STREAM_WRAPPERS_LOCAL, + * because PHP grants a greater trust level to local files (for example, they + * can be used in an "include" statement, regardless of the "allow_url_include" + * setting), so stream wrappers need to explicitly opt-in to this. + */ +define('STREAM_WRAPPERS_NORMAL', STREAM_WRAPPERS_WRITE_VISIBLE); + +/** + * Stream wrapper type flag -- visible, readable and writeable using local files. + */ +define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_NORMAL); /** * @defgroup file File interface @@ -133,7 +197,7 @@ function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) { $existing = stream_get_wrappers(); foreach ($wrappers as $scheme => $info) { // We only register classes that implement our interface. - if (in_array('DrupalStreamWrapperInterface', class_implements($info['class']), TRUE)) { + if (in_array('Drupal\Core\StreamWrapper\StreamWrapperInterface', class_implements($info['class']), TRUE)) { // Record whether we are overriding an existing scheme. if (in_array($scheme, $existing, TRUE)) { $wrappers[$scheme]['override'] = TRUE; @@ -302,7 +366,7 @@ function file_stream_wrapper_uri_normalize($uri) { * Returns a new stream wrapper object appropriate for the given URI or FALSE * if no registered handler could be found. For example, a URI of * "private://example.txt" would return a new private stream wrapper object - * (DrupalPrivateStreamWrapper). + * (Drupal\Core\StreamWrapper\PrivateStream). */ function file_stream_wrapper_get_instance_by_uri($uri) { $scheme = file_uri_scheme($uri); @@ -334,7 +398,7 @@ function file_stream_wrapper_get_instance_by_uri($uri) { * @return * Returns a new stream wrapper object appropriate for the given $scheme. * For example, for the public scheme a stream wrapper object - * (DrupalPublicStreamWrapper). + * (Drupal\Core\StreamWrapper\PublicStream). * FALSE is returned if no registered handler could be found. */ function file_stream_wrapper_get_instance_by_scheme($scheme) { @@ -464,6 +528,7 @@ function file_ensure_htaccess() { file_save_htaccess('private://', TRUE); } file_save_htaccess('temporary://', TRUE); + file_save_htaccess(config_get_config_directory(), TRUE); } /** @@ -568,6 +633,13 @@ function file_load($fid) { function file_save(stdClass $file) { $file->timestamp = REQUEST_TIME; $file->filesize = filesize($file->uri); + if (!isset($file->langcode)) { + // Default the file's language code to none, because files are language + // neutral more often than language dependent. Until we have better flexible + // settings. + // @todo See http://drupal.org/node/258785 and followups. + $file->langcode = LANGUAGE_NOT_SPECIFIED; + } // Load the stored entity, if any. if (!empty($file->fid) && !isset($file->original)) { @@ -2114,7 +2186,7 @@ function file_get_mimetype($uri, $mapping = NULL) { else { // getMimeType() is not implementation specific, so we can directly // call it without an instance. - return DrupalLocalStreamWrapper::getMimeType($uri, $mapping); + return LocalStream::getMimeType($uri, $mapping); } } @@ -2227,7 +2299,7 @@ function drupal_unlink($uri, $context = NULL) { * * @todo This function is deprecated, and should be removed wherever possible. * - * @see DrupalStreamWrapperInterface::realpath() + * @see Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath() * @see http://php.net/manual/function.realpath.php * @ingroup php_wrappers */ diff --git a/core/includes/form.inc b/core/includes/form.inc index 853990b..6ebbac4 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -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']); } @@ -2298,6 +2299,48 @@ function form_type_checkboxes_value($element, $input = FALSE) { } /** + * Form value callback: Determines the value for a #type radios form element. + * + * @param $element + * The form element whose value is being populated. + * @param $input + * (optional) The incoming input to populate the form element. If FALSE, the + * element's default value is returned. Defaults to FALSE. + * + * @return + * The data that will appear in the $element_state['values'] collection for + * this element. + */ +function form_type_radios_value(&$element, $input = FALSE) { + if ($input !== FALSE) { + // There may not be a submitted value for multiple radio buttons, if none of + // the options was checked by default. If there is no submitted input value + // for this element (NULL), _form_builder_handle_input_element() + // automatically attempts to use the #default_value (if set) or an empty + // string (''). However, an empty string would fail validation in + // _form_validate(), in case it is not contained in the list of allowed + // values in #options. + if (!isset($input)) { + // Signify a garbage value to disable the #default_value handling and take + // over NULL as #value. + $element['#has_garbage_value'] = TRUE; + // There was a user submission so validation is a must. If this element is + // #required, then an appropriate error message will be output. While an + // optional #type 'radios' does not necessarily make sense from a user + // interaction perspective, there may be use-cases for that and it is not + // the job of Form API to artificially limit possibilities. + $element['#needs_validation'] = TRUE; + } + // The value stays the same, but the flags above will ensure it is + // processed properly. + return $input; + } + elseif (isset($element['#default_value'])) { + return $element['#default_value']; + } +} + +/** * Determines the value for a tableselect form element. * * @param $element @@ -2977,7 +3020,9 @@ function form_process_radios($element) { // The key is sanitized in drupal_attributes() during output from the // theme function. '#return_value' => $key, - '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL, + // Use default or FALSE. A value of FALSE means that the radio button is + // not 'checked'. + '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE, '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), @@ -3360,6 +3405,9 @@ function form_process_tableselect($element) { * different character, 'replace_pattern' needs to be set accordingly. * - error: (optional) A custom form error message string to show, if the * machine name contains disallowed characters. + * - standalone: (optional) Whether the live preview should stay in its own + * form element rather than in the suffix of the source element. Defaults + * to FALSE. * - #maxlength: (optional) Should be set to the maximum allowed length of the * machine name. Defaults to 64. * - #disabled: (optional) Should be set to TRUE in case an existing machine @@ -3371,6 +3419,9 @@ function form_process_machine_name($element, &$form_state) { '#title' => t('Machine-readable name'), '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), '#machine_name' => array(), + '#field_prefix' => '', + '#field_suffix' => '', + '#suffix' => '', ); // A form element that only wants to set one #machine_name property (usually // 'source' only) would leave all other properties undefined, if the defaults @@ -3381,6 +3432,9 @@ function form_process_machine_name($element, &$form_state) { 'label' => t('Machine name'), 'replace_pattern' => '[^a-z0-9_]+', 'replace' => '_', + 'standalone' => FALSE, + 'field_prefix' => $element['#field_prefix'], + 'field_suffix' => $element['#field_suffix'], ); // By default, machine names are restricted to Latin alphanumeric characters. @@ -3396,7 +3450,7 @@ function form_process_machine_name($element, &$form_state) { } // Retrieve the form element containing the human-readable name from the - // complete form in $form_state. By reference, because we need to append + // complete form in $form_state. By reference, because we may need to append // a #field_suffix that will hold the live preview. $key_exists = NULL; $source = drupal_array_get_nested_value($form_state['complete_form'], $element['#machine_name']['source'], $key_exists); @@ -3404,16 +3458,21 @@ function form_process_machine_name($element, &$form_state) { return $element; } - // Append a field suffix to the source form element, which will contain - // the live preview of the machine name. $suffix_id = $source['#id'] . '-machine-name-suffix'; - $source += array('#field_suffix' => ''); - $source['#field_suffix'] .= '  '; + $element['#machine_name']['suffix'] = '#' . $suffix_id; - $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); - drupal_array_set_nested_value($form_state['complete_form'], $parents, $source['#field_suffix']); + if ($element['#machine_name']['standalone']) { + $element['#suffix'] .= '  '; + } + else { + // Append a field suffix to the source form element, which will contain + // the live preview of the machine name. + $source += array('#field_suffix' => ''); + $source['#field_suffix'] .= '  '; - $element['#machine_name']['suffix'] = '#' . $suffix_id; + $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); + drupal_array_set_nested_value($form_state['complete_form'], $parents, $source['#field_suffix']); + } $js_settings = array( 'type' => 'setting', @@ -3750,6 +3809,56 @@ function theme_textfield($variables) { } /** + * Returns HTML for an email 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_email($variables) { + $element = $variables['element']; + $element['#attributes']['type'] = 'email'; + 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 = ''; + } + + $output = ''; + + return $output . $extra; +} + +/** + * Form element validation handler for #type 'email'. + * + * Note that #maxlength and #required is validated by _form_validate() already. + */ +function form_validate_email(&$element, &$form_state) { + $value = trim($element['#value']); + form_set_value($element, $value, $form_state); + + if ($value !== '' && !valid_email_address($value)) { + form_error($element, t('The e-mail address %mail is not valid.', array('%mail' => $value))); + } +} + +/** * Returns HTML for a tel form element. * * @param $variables @@ -3786,6 +3895,56 @@ function theme_tel($variables) { } /** + * Returns HTML for a url 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_url($variables) { + $element = $variables['element']; + $element['#attributes']['type'] = 'url'; + element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder')); + _form_set_class($element, array('form-url')); + + $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 = ''; + } + + $output = ''; + + return $output . $extra; +} + +/** + * Form element validation handler for #type 'url'. + * + * Note that #maxlength and #required is validated by _form_validate() already. + */ +function form_validate_url(&$element, &$form_state) { + $value = trim($element['#value']); + form_set_value($element, $value, $form_state); + + if ($value !== '' && !valid_url($value, TRUE)) { + form_error($element, t('The URL %url is not valid.', array('%url' => $value))); + } +} + +/** * Returns HTML for a form. * * @param $variables @@ -3815,7 +3974,7 @@ function theme_form($variables) { * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #value, #description, #rows, #cols, - * #placeholder, #required, #attributes + * #placeholder, #required, #attributes, #resizable * * @ingroup themeable */ @@ -3830,8 +3989,7 @@ function theme_textarea($variables) { // Add resizable behavior. if (!empty($element['#resizable'])) { - drupal_add_library('system', 'drupal.textarea'); - $wrapper_attributes['class'][] = 'resizable'; + $element['#attributes']['class'][] = 'resize-' . $element['#resizable']; } $output = ''; @@ -4483,8 +4641,9 @@ function &batch_get() { /** * Populates a job queue with the operations of a batch set. * - * Depending on whether the batch is progressive or not, the BatchQueue or - * BatchMemoryQueue handler classes will be used. + * Depending on whether the batch is progressive or not, the + * Drupal\Core\Queue\Batch or Drupal\Core\Queue\BatchMemory handler classes will + * be used. * * @param $batch * The batch array. @@ -4501,7 +4660,7 @@ function _batch_populate_queue(&$batch, $set_id) { $batch_set += array( 'queue' => array( 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id, - 'class' => $batch['progressive'] ? 'BatchQueue' : 'BatchMemoryQueue', + 'class' => $batch['progressive'] ? 'Drupal\Core\Queue\Batch' : 'Drupal\Core\Queue\BatchMemory', ), ); @@ -4527,12 +4686,8 @@ function _batch_populate_queue(&$batch, $set_id) { function _batch_queue($batch_set) { static $queues; - // The class autoloader is not available when running update.php, so make - // sure the files are manually included. if (!isset($queues)) { $queues = array(); - require_once DRUPAL_ROOT . '/core/modules/system/system.queue.inc'; - require_once DRUPAL_ROOT . '/core/includes/batch.queue.inc'; } if (isset($batch_set['queue'])) { diff --git a/core/includes/gettext.inc b/core/includes/gettext.inc index 95e84cf..18c27ea 100644 --- a/core/includes/gettext.inc +++ b/core/includes/gettext.inc @@ -173,7 +173,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) { } // Append the plural form to the current entry. - $current['msgid'] .= "\0" . $quoted; + $current['msgid'] .= LOCALE_PLURAL_DELIMITER . $quoted; $context = 'MSGID_PLURAL'; } @@ -390,8 +390,10 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL // Store the string we got in the database. case 'db-store': - // We got header information. + if ($value['msgid'] == '') { + // If 'msgid' is empty, it means we got values for the header of the + // file as per the structure of the Gettext format. $locale_plurals = variable_get('locale_translation_plurals', array()); if (($mode != LOCALE_IMPORT_KEEP) || empty($locale_plurals[$lang]['plurals'])) { // Since we only need to parse the header if we ought to update the @@ -413,32 +415,25 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL } else { - // Some real string to import. + // Found a string to store, clean up and prepare the data. $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']); - if (strpos($value['msgid'], "\0")) { - // This string has plural versions. - $english = explode("\0", $value['msgid'], 2); - $entries = array_keys($value['msgstr']); - for ($i = 3; $i <= count($entries); $i++) { - $english[] = $english[1]; - } - $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries); - $english = array_map('_locale_import_append_plural', $english, $entries); - foreach ($translation as $key => $trans) { - if ($key == 0) { - $plid = 0; - } - $plid = _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english[$key], $trans, $comments, $mode, $plid, $key); - } + if (is_array($value['msgstr'])) { + // Sort plural variants by their form index. + ksort($value['msgstr']); + // Serialize plural variants in one string by LOCALE_PLURAL_DELIMITER. + $value['msgstr'] = implode(LOCALE_PLURAL_DELIMITER, $value['msgstr']); } - else { - // A simple string to import. - $english = $value['msgid']; - $translation = $value['msgstr']; - _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $comments, $mode); - } + _locale_import_one_string_db( + $report, + $lang, + isset($value['msgctxt']) ? $value['msgctxt'] : '', + $value['msgid'], + $value['msgstr'], + $comments, + $mode + ); } } // end of db-store operation } @@ -461,15 +456,11 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL * Location value to save with source string. * @param $mode * Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE. - * @param $plid - * Optional plural ID to use. - * @param $plural - * Optional plural value to use. * * @return * The string ID of the existing string modified or the new string added. */ -function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $location, $mode, $plid = 0, $plural = 0) { +function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $location, $mode) { $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context", array(':source' => $source, ':context' => $context))->fetchField(); if (!empty($translation)) { @@ -497,8 +488,6 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t 'lid' => $lid, 'language' => $langcode, 'translation' => $translation, - 'plid' => $plid, - 'plural' => $plural, )) ->execute(); @@ -509,8 +498,6 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t db_update('locales_target') ->fields(array( 'translation' => $translation, - 'plid' => $plid, - 'plural' => $plural, )) ->condition('language', $langcode) ->condition('lid', $lid) @@ -534,8 +521,6 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t 'lid' => $lid, 'language' => $langcode, 'translation' => $translation, - 'plid' => $plid, - 'plural' => $plural )) ->execute(); @@ -547,8 +532,6 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t db_delete('locales_target') ->condition('language', $langcode) ->condition('lid', $lid) - ->condition('plid', $plid) - ->condition('plural', $plural) ->execute(); $report['deletes']++; @@ -791,27 +774,6 @@ function _locale_import_tokenize_formula($formula) { } /** - * Adds count indices to a string. - * - * Callback for array_map() within _locale_import_one_string(). - * - * @param $entry - * An array element. - * @param $key - * Index of the array element. - */ -function _locale_import_append_plural($entry, $key) { - // No modifications for 0, 1 - if ($key == 0 || $key == 1) { - return $entry; - } - - // First remove any possibly false indices, then add new ones - $entry = preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry); - return preg_replace('/(@count)/', "\\1[$key]", $entry); -} - -/** * Generates a short, one-string version of the passed comment array. * * @param $comment @@ -872,28 +834,19 @@ function _locale_import_parse_quoted($string) { */ function _locale_export_get_strings($language = NULL) { if (isset($language)) { - $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language ORDER BY t.plid, t.plural", array(':language' => $language->langcode)); + $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language", array(':language' => $language->langcode)); } else { - $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid ORDER BY t.plid, t.plural"); + $result = db_query("SELECT s.lid, s.source, s.context, s.location FROM {locales_source} s"); } $strings = array(); foreach ($result as $child) { - $string = array( + $strings[$child->lid] = array( 'comment' => $child->location, 'source' => $child->source, 'context' => $child->context, 'translation' => isset($child->translation) ? $child->translation : '', ); - if ($child->plid) { - // Has a parent lid. Since we process in the order of plids, - // we already have the parent in the array, so we can add the - // lid to the next plural version to it. This builds a linked - // list of plurals. - $string['child'] = TRUE; - $strings[$child->plid]['plural'] = $child->lid; - } - $strings[$child->lid] = $string; } return $strings; } @@ -933,6 +886,12 @@ function _locale_export_po_generate($language = NULL, $strings = array(), $heade $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; if (!empty($locale_plurals[$language->langcode]['formula'])) { $header .= "\"Plural-Forms: nplurals=" . $locale_plurals[$language->langcode]['plurals'] . "; plural=" . strtr($locale_plurals[$language->langcode]['formula'], array('$' => '')) . ";\\n\"\n"; + // Remember number of plural variants to optimize the export. + $nplurals = $locale_plurals[$language->langcode]['plurals']; + } + else { + // Remember we did not have a plural number for the export. + $nplurals = 0; } } else { @@ -956,41 +915,38 @@ function _locale_export_po_generate($language = NULL, $strings = array(), $heade $output = $header . "\n"; foreach ($strings as $lid => $string) { - // Only process non-children, children are output below their parent. - if (!isset($string['child'])) { - if ($string['comment']) { - $output .= '#: ' . $string['comment'] . "\n"; - } - if (!empty($string['context'])) { - $output .= 'msgctxt ' . _locale_export_string($string['context']); - } - $output .= 'msgid ' . _locale_export_string($string['source']); - if (!empty($string['plural'])) { - $plural = $string['plural']; - $output .= 'msgid_plural ' . _locale_export_string($strings[$plural]['source']); - if (isset($language)) { - $translation = $string['translation']; - for ($i = 0; $i < $locale_plurals[$language->langcode]['plurals']; $i++) { - $output .= 'msgstr[' . $i . '] ' . _locale_export_string($translation); - if ($plural) { - $translation = _locale_export_remove_plural($strings[$plural]['translation']); - $plural = isset($strings[$plural]['plural']) ? $strings[$plural]['plural'] : 0; - } - else { - $translation = ''; - } + if ($string['comment']) { + $output .= '#: ' . $string['comment'] . "\n"; + } + if (!empty($string['context'])) { + $output .= 'msgctxt ' . _locale_export_string($string['context']); + } + if (strpos($string['source'], LOCALE_PLURAL_DELIMITER) !== FALSE) { + // Export plural string. + $export_array = explode(LOCALE_PLURAL_DELIMITER, $string['source']); + $output .= 'msgid ' . _locale_export_string($export_array[0]); + $output .= 'msgid_plural ' . _locale_export_string($export_array[1]); + if (isset($language)) { + $export_array = explode(LOCALE_PLURAL_DELIMITER, $string['translation']); + for ($i = 0; $i < $nplurals; $i++) { + if (isset($export_array[$i])) { + $output .= 'msgstr[' . $i . '] ' . _locale_export_string($export_array[$i]); + } + else { + $output .= 'msgstr[' . $i . '] ""' . "\n"; } - } - else { - $output .= 'msgstr[0] ""' . "\n"; - $output .= 'msgstr[1] ""' . "\n"; } } else { - $output .= 'msgstr ' . _locale_export_string($string['translation']); + $output .= 'msgstr[0] ""' . "\n"; + $output .= 'msgstr[1] ""' . "\n"; } - $output .= "\n"; } + else { + $output .= 'msgid ' . _locale_export_string($string['source']); + $output .= 'msgstr ' . _locale_export_string($string['translation']); + } + $output .= "\n"; } return $output; } @@ -1087,12 +1043,5 @@ function _locale_export_wrap($str, $len) { } /** - * Removes plural index information from a string. - */ -function _locale_export_remove_plural($entry) { - return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry); -} - -/** * @} End of "locale-api-import-export" */ diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index f16e094..07e25a0 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -268,6 +268,7 @@ function install_begin_request(&$install_state) { require_once DRUPAL_ROOT . '/core/includes/common.inc'; require_once DRUPAL_ROOT . '/core/includes/file.inc'; require_once DRUPAL_ROOT . '/core/includes/install.inc'; + require_once DRUPAL_ROOT . '/core/includes/schema.inc'; require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'core/includes/path.inc'); // Load module basics (needed for hook invokes). @@ -311,7 +312,7 @@ function install_begin_request(&$install_state) { if ($install_state['settings_verified']) { // Initialize the database system. Note that the connection // won't be initialized until it is actually requested. - require_once DRUPAL_ROOT . '/core/includes/database/database.inc'; + require_once DRUPAL_ROOT . '/core/includes/database.inc'; // Verify the last completed task in the database, if there is one. $task = install_verify_completed_task(); @@ -1010,7 +1011,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 @@ -1747,7 +1777,7 @@ function _install_configure_form($form, &$form_state, &$install_state) { '#weight' => -20, ); $form['site_information']['site_mail'] = array( - '#type' => 'textfield', + '#type' => 'email', '#title' => st('Site e-mail address'), '#default_value' => ini_get('sendmail_from'), '#description' => st("Automated e-mails, such as registration information, will be sent from this address. Use an address ending in your site's domain to help prevent these e-mails from being flagged as spam."), @@ -1770,9 +1800,9 @@ function _install_configure_form($form, &$form_state, &$install_state) { '#attributes' => array('class' => array('username')), ); - $form['admin_account']['account']['mail'] = array('#type' => 'textfield', + $form['admin_account']['account']['mail'] = array( + '#type' => 'email', '#title' => st('E-mail address'), - '#maxlength' => EMAIL_MAX_LENGTH, '#required' => TRUE, '#weight' => -5, ); @@ -1849,12 +1879,6 @@ function install_configure_form_validate($form, &$form_state) { if ($error = user_validate_name($form_state['values']['account']['name'])) { form_error($form['admin_account']['account']['name'], $error); } - if ($error = user_validate_mail($form_state['values']['account']['mail'])) { - form_error($form['admin_account']['account']['mail'], $error); - } - if ($error = user_validate_mail($form_state['values']['site_mail'])) { - form_error($form['site_information']['site_mail'], $error); - } } /** diff --git a/core/includes/install.inc b/core/includes/install.inc index 8a25693..199ec1c 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -84,104 +84,6 @@ function drupal_load_updates() { } /** - * Returns an array of available schema versions for a module. - * - * @param $module - * A module name. - * @return - * If the module has updates, an array of available updates sorted by version. - * Otherwise, FALSE. - */ -function drupal_get_schema_versions($module) { - $updates = &drupal_static(__FUNCTION__, NULL); - if (!isset($updates[$module])) { - $updates = array(); - - foreach (module_list() as $loaded_module) { - $updates[$loaded_module] = array(); - } - - // Prepare regular expression to match all possible defined hook_update_N(). - $regexp = '/^(?P.+)_update_(?P\d+)$/'; - $functions = get_defined_functions(); - // Narrow this down to functions ending with an integer, since all - // hook_update_N() functions end this way, and there are other - // possible functions which match '_update_'. We use preg_grep() here - // instead of foreaching through all defined functions, since the loop - // through all PHP functions can take significant page execution time - // and this function is called on every administrative page via - // system_requirements(). - foreach (preg_grep('/_\d+$/', $functions['user']) as $function) { - // If this function is a module update function, add it to the list of - // module updates. - if (preg_match($regexp, $function, $matches)) { - $updates[$matches['module']][] = $matches['version']; - } - } - // Ensure that updates are applied in numerical order. - foreach ($updates as &$module_updates) { - sort($module_updates, SORT_NUMERIC); - } - } - return empty($updates[$module]) ? FALSE : $updates[$module]; -} - -/** - * Returns the currently installed schema version for a module. - * - * @param $module - * A module name. - * @param $reset - * Set to TRUE after modifying the system table. - * @param $array - * Set to TRUE if you want to get information about all modules in the - * system. - * @return - * The currently installed schema version, or SCHEMA_UNINSTALLED if the - * module is not installed. - */ -function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) { - static $versions = array(); - - if ($reset) { - $versions = array(); - } - - if (!$versions) { - $versions = array(); - $result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module')); - foreach ($result as $row) { - $versions[$row->name] = $row->schema_version; - } - } - - if ($array) { - return $versions; - } - else { - return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED; - } -} - -/** - * Update the installed version information for a module. - * - * @param $module - * A module name. - * @param $version - * The new schema version. - */ -function drupal_set_installed_schema_version($module, $version) { - db_update('system') - ->fields(array('schema_version' => $version)) - ->condition('name', $module) - ->execute(); - - // Reset the static cache of module schema versions. - drupal_get_installed_schema_version(NULL, TRUE); -} - -/** * Loads the install profile, extracting its defined distribution name. * * @return @@ -255,7 +157,7 @@ function drupal_get_database_types() { // without modifying the installer. // Because we have no registry yet, we need to also include the install.inc // file for the driver explicitly. - require_once DRUPAL_ROOT . '/core/includes/database/database.inc'; + require_once DRUPAL_ROOT . '/core/includes/database.inc'; foreach (file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) { if (file_exists($file->uri . '/Install/Tasks.php')) { $drivers[$file->filename] = $file->uri; @@ -434,6 +336,7 @@ function drupal_install_system() { )) ->execute(); system_rebuild_module_data(); + config_install_default_config('system'); } /** diff --git a/core/includes/language.inc b/core/includes/language.inc index f12f34a..76c4318 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -20,22 +20,22 @@ const LANGUAGE_NEGOTIATION_DEFAULT = 'language-default'; * The negotiated language object. */ function language_types_initialize($type) { - // Execute the language providers in the order they were set up and return the - // first valid language found. + // Execute the language negotiation methods in the order they were set up and + // return the first valid language found. $negotiation = variable_get("language_negotiation_$type", array()); - foreach ($negotiation as $provider_id => $provider) { - $language = language_provider_invoke($provider_id, $provider); + foreach ($negotiation as $method_id => $method) { + $language = language_negotiation_method_invoke($method_id, $method); if ($language) { - // Remember the provider key used to detect the language. - $language->provider = $provider_id; + // Remember the method ID used to detect the language. + $language->method_id = $method_id; return $language; } } // If no other language was found use the default one. $language = language_default(); - $language->provider = LANGUAGE_NEGOTIATION_DEFAULT; + $language->method_id = LANGUAGE_NEGOTIATION_DEFAULT; return $language; } @@ -68,11 +68,11 @@ function language_types_info() { * language type itself. * * @param $stored - * Optional. By default retrieves values from the 'language_types' variable to - * avoid unnecessary hook invocations. - * If set to FALSE retrieves values from the actual language type definitions. - * This allows to react to alterations performed on the definitions by modules - * installed after the 'language_types' variable is set. + * (optional) By default, retrieves values from the 'language_types' variable + * to avoid unnecessary hook invocations. If set to FALSE, retrieves values + * from the actual language type definitions. This allows reaction to + * alterations performed on the definitions by modules installed after the + * 'language_types' variable is set. * * @return * An array of language type names. @@ -99,7 +99,7 @@ function language_types_get_configurable($stored = TRUE) { } /** - * Disable the given language types. + * Disables the given language types. * * @param $types * An array of language types. @@ -128,17 +128,17 @@ function language_types_set() { // whether the 'fixed' key is defined. Non-configurable (fixed) language types // have their language negotiation settings stored there. $language_types = array(); - $defined_providers = language_negotiation_info(); + $negotiation_info = language_negotiation_info(); foreach (language_types_info() as $type => $info) { if (isset($info['fixed'])) { $language_types[$type] = FALSE; - $negotiation = array(); - foreach ($info['fixed'] as $weight => $id) { - if (isset($defined_providers[$id])) { - $negotiation[$id] = $weight; + $method_weights = array(); + foreach ($info['fixed'] as $weight => $method_id) { + if (isset($negotiation_info[$method_id])) { + $method_weights[$method_id] = $weight; } } - language_negotiation_set($type, $negotiation); + language_negotiation_set($type, $method_weights); } else { $language_types[$type] = TRUE; @@ -154,52 +154,35 @@ function language_types_set() { } /** - * Check if a language provider is enabled. - * - * This has two possible behaviors: - * - If $provider_id is given return its ID if enabled, FALSE otherwise. - * - If no ID is passed the first enabled language provider is returned. + * Returns the ID of the language type's first language negotiation method. * * @param $type - * The language negotiation type. - * @param $provider_id - * The language provider ID. - * - * @return - * The provider ID if it is enabled, FALSE otherwise. + * The language type. */ -function language_negotiation_get($type, $provider_id = NULL) { +function language_negotiation_method_get_first($type) { $negotiation = variable_get("language_negotiation_$type", array()); - - if (empty($negotiation)) { - return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE; - } - - if (empty($provider_id)) { - return key($negotiation); - } - - if (isset($negotiation[$provider_id])) { - return $provider_id; - } - - return FALSE; + return empty($negotiation) ? LANGUAGE_NEGOTIATION_DEFAULT : key($negotiation); } /** - * Check if the given language provider is enabled for any configurable language - * type. + * Checks if a language negotiation method is enabled for a language type. * - * @param $provider_id - * The language provider ID. + * @param $method_id + * The language negotiation method ID. + * @param $type + * (optional) The language type. If none is passed, all the configurable + * language types will be inspected. * * @return - * TRUE if there is at least one language type for which the give language - * provider is enabled, FALSE otherwise. + * TRUE if the method is enabled for at least one of the given language + * types, or FALSE otherwise. */ -function language_negotiation_get_any($provider_id) { - foreach (language_types_get_configurable() as $type) { - if (language_negotiation_get($type, $provider_id)) { +function language_negotiation_method_enabled($method_id, $type = NULL) { + $language_types = !empty($type) ? array($type) : language_types_get_configurable(); + + foreach ($language_types as $type) { + $negotiation = variable_get("language_negotiation_$type", array()); + if (isset($negotiation[$method_id])) { return TRUE; } } @@ -208,10 +191,10 @@ function language_negotiation_get_any($provider_id) { } /** - * Return the language switch links for the given language. + * Returns the language switch links for the given language type. * * @param $type - * The language negotiation type. + * The language type. * @param $path * The internal path the switch links will be relative to. * @@ -222,19 +205,19 @@ function language_negotiation_get_switch_links($type, $path) { $links = FALSE; $negotiation = variable_get("language_negotiation_$type", array()); - foreach ($negotiation as $id => $provider) { - if (isset($provider['callbacks']['switcher'])) { - if (isset($provider['file'])) { - require_once DRUPAL_ROOT . '/' . $provider['file']; + foreach ($negotiation as $method_id => $method) { + if (isset($method['callbacks']['language_switch'])) { + if (isset($method['file'])) { + require_once DRUPAL_ROOT . '/' . $method['file']; } - $callback = $provider['callbacks']['switcher']; + $callback = $method['callbacks']['language_switch']; $result = $callback($type, $path); if (!empty($result)) { // Allow modules to provide translations for specific links. drupal_alter('language_switch_links', $result, $type, $path); - $links = (object) array('links' => $result, 'provider' => $id); + $links = (object) array('links' => $result, 'method_id' => $method_id); break; } } @@ -244,7 +227,7 @@ function language_negotiation_get_switch_links($type, $path) { } /** - * Updates language configuration to remove any language provider that is no longer defined. + * Removes any language negotiation methods that are no longer defined. */ function language_negotiation_purge() { // Ensure that we are getting the defined language negotiation information. An @@ -253,60 +236,54 @@ function language_negotiation_purge() { drupal_static_reset('language_negotiation_info'); drupal_static_reset('language_types_info'); - $defined_providers = language_negotiation_info(); + $negotiation_info = language_negotiation_info(); foreach (language_types_info() as $type => $type_info) { $weight = 0; - $negotiation = array(); - foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) { - if (isset($defined_providers[$id])) { - $negotiation[$id] = $weight++; + $method_weights = array(); + foreach (variable_get("language_negotiation_$type", array()) as $method_id => $method) { + if (isset($negotiation_info[$method_id])) { + $method_weights[$method_id] = $weight++; } } - language_negotiation_set($type, $negotiation); + language_negotiation_set($type, $method_weights); } } /** - * Save a list of language providers. + * Saves a list of language negotiation methods for a language type. * * @param $type - * The language negotiation type. - * @param $language_providers - * An array of language provider weights keyed by id. - * @see language_provider_weight() + * The language type. + * @param $method_weights + * An array of language negotiation method weights keyed by method id. */ -function language_negotiation_set($type, $language_providers) { +function language_negotiation_set($type, $method_weights) { // Save only the necessary fields. - $provider_fields = array('callbacks', 'file', 'cache'); + $method_fields = array('callbacks', 'file', 'cache'); $negotiation = array(); - $providers_weight = array(); - $defined_providers = language_negotiation_info(); + $negotiation_info = language_negotiation_info(); $default_types = language_types_get_configurable(FALSE); - // Initialize the providers weight list. - foreach ($language_providers as $id => $provider) { - $providers_weight[$id] = language_provider_weight($provider); - } - - // Order providers list by weight. - asort($providers_weight); - - foreach ($providers_weight as $id => $weight) { - if (isset($defined_providers[$id])) { - $provider = $defined_providers[$id]; - // If the provider does not express any preference about types, make it - // available for any configurable type. - $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types); - // Check if the provider is defined and has the right type. + // Order the language negotiation method list by weight. + asort($method_weights); + + foreach ($method_weights as $method_id => $weight) { + if (isset($negotiation_info[$method_id])) { + $method = $negotiation_info[$method_id]; + // If the language negotiation method does not express any preference + // about types, make it available for any configurable type. + $types = array_flip(isset($method['types']) ? $method['types'] : $default_types); + // Check if the language negotiation method is defined and has the right + // type. if (isset($types[$type])) { - $provider_data = array(); - foreach ($provider_fields as $field) { - if (isset($provider[$field])) { - $provider_data[$field] = $provider[$field]; + $method_data = array(); + foreach ($method_fields as $field) { + if (isset($method[$field])) { + $method_data[$field] = $method[$field]; } } - $negotiation[$id] = $provider_data; + $negotiation[$method_id] = $method_data; } } } @@ -315,20 +292,20 @@ function language_negotiation_set($type, $language_providers) { } /** - * Return all the defined language providers. + * Returns all defined language negotiation methods. * * @return - * An array of language providers. + * An array of language negotiation methods. */ function language_negotiation_info() { - $language_providers = &drupal_static(__FUNCTION__); + $negotiation_info = &drupal_static(__FUNCTION__); - if (!isset($language_providers)) { - // Collect all the module-defined language negotiation providers. - $language_providers = module_invoke_all('language_negotiation_info'); + if (!isset($negotiation_info)) { + // Collect all the module-defined language negotiation methods. + $negotiation_info = module_invoke_all('language_negotiation_info'); - // Add the default language provider. - $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( + // Add the default language negotiation method. + $negotiation_info[LANGUAGE_NEGOTIATION_DEFAULT] = array( 'callbacks' => array('language' => 'language_from_default'), 'weight' => 10, 'name' => t('Default language'), @@ -336,73 +313,60 @@ function language_negotiation_info() { 'config' => 'admin/config/regional/language', ); - // Let other modules alter the list of language providers. - drupal_alter('language_negotiation_info', $language_providers); + // Let other modules alter the list of language negotiation methods. + drupal_alter('language_negotiation_info', $negotiation_info); } - return $language_providers; + return $negotiation_info; } /** - * Helper function used to cache the language providers results. + * Invokes a language negotiation method and caches the results. * - * @param $provider_id - * The language provider ID. - * @param $provider - * The language provider to be invoked. If not passed it will be explicitly - * loaded through language_negotiation_info(). + * @param $method_id + * The language negotiation method ID. + * @param $method + * (optional) The language negotiation method to be invoked. If not passed it + * will be explicitly loaded through language_negotiation_info(). * * @return - * The language provider's return value. + * The language negotiation method's return value. */ -function language_provider_invoke($provider_id, $provider = NULL) { +function language_negotiation_method_invoke($method_id, $method = NULL) { $results = &drupal_static(__FUNCTION__); - if (!isset($results[$provider_id])) { + if (!isset($results[$method_id])) { global $user; // Get the enabled languages only. $languages = language_list(TRUE); - if (!isset($provider)) { - $providers = language_negotiation_info(); - $provider = $providers[$provider_id]; + if (!isset($method)) { + $negotiation_info = language_negotiation_info(); + $method = $negotiation_info[$method_id]; } - if (isset($provider['file'])) { - require_once DRUPAL_ROOT . '/' . $provider['file']; + if (isset($method['file'])) { + require_once DRUPAL_ROOT . '/' . $method['file']; } - // 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); - $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE; + // If the language negotiation method has no cache preference or this is + // satisfied we can execute the callback. + $cache = !isset($method['cache']) || $user->uid || $method['cache'] == variable_get('cache', 0); + $callback = isset($method['callbacks']['negotiation']) ? $method['callbacks']['negotiation'] : FALSE; $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE; - $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; + $results[$method_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; } // Since objects are resources we need to return a clone to prevent the - // provider cache to be unintentionally altered. The same providers might be - // used with different language types based on configuration. - return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id]; -} - -/** - * Return the passed language provider weight or a default value. - * - * @param $provider - * A language provider data structure. - * - * @return - * A numeric weight. - */ -function language_provider_weight($provider) { - $default = is_numeric($provider) ? $provider : 0; - return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default; + // language negotiation method cache to be unintentionally altered. The same + // language negotiation methods might be used with different language types + // based on configuration. + return !empty($results[$method_id]) ? clone($results[$method_id]) : $results[$method_id]; } /** - * Default language provider. + * Returns the default language code. * * @return * The default language code. @@ -457,9 +421,9 @@ function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) { $fallback_candidates = &drupal_static(__FUNCTION__); if (!isset($fallback_candidates)) { - // Get languages ordered by weight, add LANGUAGE_NONE as the last one. + // Get languages ordered by weight, add LANGUAGE_NOT_SPECIFIED at the end. $fallback_candidates = array_keys(language_list()); - $fallback_candidates[] = LANGUAGE_NONE; + $fallback_candidates[] = LANGUAGE_NOT_SPECIFIED; // Let other modules hook in and add/change candidates. drupal_alter('language_fallback_candidates', $fallback_candidates); diff --git a/core/includes/locale.inc b/core/includes/locale.inc index 8a381ed..1f9567b 100644 --- a/core/includes/locale.inc +++ b/core/includes/locale.inc @@ -214,8 +214,8 @@ function locale_language_from_user($languages) { // User preference (only for logged users). global $user; - if ($user->uid) { - return $user->language; + if ($user->uid && !empty($user->preferred_langcode)) { + return $user->preferred_langcode; } // No language preference from the user. @@ -264,7 +264,7 @@ function locale_language_from_session($languages) { function locale_language_from_url($languages) { $language_url = FALSE; - if (!language_negotiation_get_any(LANGUAGE_NEGOTIATION_URL)) { + if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL)) { return $language_url; } @@ -303,8 +303,8 @@ function locale_language_from_url($languages) { * Determines the language to be assigned to URLs when none is detected. * * The language negotiation process has a fallback chain that ends with the - * default language provider. Each built-in language type has a separate - * initialization: + * default language negotiation method. Each built-in language type has a + * separate initialization: * - Interface language, which is the only configurable one, always gets a valid * value. If no request-specific language is detected, the default language * will be used. @@ -322,8 +322,8 @@ function locale_language_from_url($languages) { * * @param $languages * (optional) An array of valid language objects. This is passed by - * language_provider_invoke() to every language provider callback, but it is - * not actually needed here. Defaults to NULL. + * language_negotiation_method_invoke() to every language method callback, + * but it is not actually needed here. Defaults to NULL. * @param $language_type * (optional) The language type to fall back to. Defaults to the interface * language. @@ -406,7 +406,7 @@ function locale_language_switcher_session($type, $path) { } /** - * Rewrite URLs for the URL language provider. + * Rewrite URLs for the URL language negotiation method. */ function locale_language_url_rewrite_url(&$path, &$options) { static $drupal_static_fast; @@ -492,7 +492,7 @@ function locale_language_negotiation_url_domains_save(array $domains) { } /** - * Rewrite URLs for the Session language provider. + * Rewrite URLs for the Session language negotiation method. */ function locale_language_url_rewrite_session(&$path, &$options) { static $query_rewrite, $query_param, $query_value; @@ -506,16 +506,16 @@ function locale_language_url_rewrite_session(&$path, &$options) { $languages = language_list(TRUE); $query_param = check_plain(variable_get('locale_language_negotiation_session_param', 'language')); $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL; - $query_rewrite = isset($languages[$query_value]) && language_negotiation_get_any(LANGUAGE_NEGOTIATION_SESSION); + $query_rewrite = isset($languages[$query_value]) && language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_SESSION); } else { $query_rewrite = FALSE; } } - // If the user is anonymous, the user language provider is enabled, and the - // corresponding option has been set, we must preserve any explicit user - // language preference even with cookies disabled. + // If the user is anonymous, the user language negotiation method is enabled, + // and the corresponding option has been set, we must preserve any explicit + // user language preference even with cookies disabled. if ($query_rewrite) { if (is_string($options['query'])) { $options['query'] = drupal_get_query_array($options['query']); diff --git a/core/includes/mail.inc b/core/includes/mail.inc index cf45ca5..f58a76e 100644 --- a/core/includes/mail.inc +++ b/core/includes/mail.inc @@ -191,7 +191,8 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N } /** - * Returns an object that implements the MailSystemInterface. + * Returns an object that implements the Drupal\Core\Mail\MailInterface + * interface. * * Allows for one or more custom mail backends to format and send mail messages * composed using drupal_mail(). @@ -199,14 +200,15 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * An implementation needs to implement the following methods: * - format: Allows to preprocess, format, and postprocess a mail * message before it is passed to the sending system. By default, all messages - * may contain HTML and are converted to plain-text by the DefaultMailSystem - * implementation. For example, an alternative implementation could override - * the default implementation and additionally sanitize the HTML for usage in - * a MIME-encoded e-mail, but still invoking the DefaultMailSystem - * implementation to generate an alternate plain-text version for sending. + * may contain HTML and are converted to plain-text by the + * Drupal\Core\Mail\PhpMail implementation. For example, an alternative + * implementation could override the default implementation and additionally + * sanitize the HTML for usage in a MIME-encoded e-mail, but still invoking + * the Drupal\Core\Mail\PhpMail implementation to generate an alternate + * plain-text version for sending. * - mail: Sends a message through a custom mail sending engine. * By default, all messages are sent via PHP's mail() function by the - * DefaultMailSystem implementation. + * Drupal\Core\Mail\PhpMail implementation. * * The selection of a particular implementation is controlled via the variable * 'mail_system', which is a keyed array. The default implementation @@ -223,7 +225,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * * @code * array( - * 'default-system' => 'DefaultMailSystem', + * 'default-system' => 'Drupal\Core\Mail\PhpMail', * 'user' => 'DevelMailLog', * ); * @endcode @@ -233,7 +235,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * * @code * array( - * 'default-system' => 'DefaultMailSystem', + * 'default-system' => 'Drupal\Core\Mail\PhpMail', * 'user' => 'DevelMailLog', * 'contact_page_autoreply' => 'DrupalDevNullMailSend', * ); @@ -251,14 +253,14 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * A key to identify the e-mail sent. The final e-mail ID for the e-mail * alter hook in drupal_mail() would have been {$module}_{$key}. * - * @return MailSystemInterface + * @return Drupal\Core\Mail\MailInterface */ function drupal_mail_system($module, $key) { $instances = &drupal_static(__FUNCTION__, array()); $id = $module . '_' . $key; - $configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem')); + $configuration = variable_get('mail_system', array('default-system' => 'Drupal\Core\Mail\PhpMail')); // Look for overrides for the default class, starting from the most specific // id, and falling back to the module name. @@ -274,60 +276,17 @@ function drupal_mail_system($module, $key) { if (empty($instances[$class])) { $interfaces = class_implements($class); - if (isset($interfaces['MailSystemInterface'])) { + if (isset($interfaces['Drupal\Core\Mail\MailInterface'])) { $instances[$class] = new $class(); } else { - throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'MailSystemInterface'))); + throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'Drupal\Core\Mail\MailInterface'))); } } return $instances[$class]; } /** - * An interface for pluggable mail back-ends. - */ -interface MailSystemInterface { - /** - * Format a message composed by drupal_mail() prior sending. - * - * @param $message - * A message array, as described in hook_mail_alter(). - * - * @return - * The formatted $message. - */ - public function format(array $message); - - /** - * Send a message composed by drupal_mail(). - * - * @param $message - * Message array with at least the following elements: - * - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy', - * 'user_password_reset'. - * - to: The mail address or addresses where the message will be sent to. - * The formatting of this string must comply with RFC 2822. Some examples: - * - user@example.com - * - user@example.com, anotheruser@example.com - * - User - * - User , Another User - * - subject: Subject of the e-mail to be sent. This must not contain any - * newline characters, or the mail may not be sent properly. - * - body: Message to be sent. Accepts both CRLF and LF line-endings. - * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for - * smart plain text wrapping. - * - headers: Associative array containing all additional mail headers not - * defined by one of the other parameters. PHP's mail() looks for Cc - * and Bcc headers and sends the mail to addresses in these headers too. - * - * @return - * TRUE if the mail was successfully accepted for delivery, otherwise FALSE. - */ - public function mail(array $message); -} - -/** * Perform format=flowed soft wrapping for mail (RFC 3676). * * We use delsp=yes wrapping, but only break non-spaced languages when diff --git a/core/includes/module.inc b/core/includes/module.inc index d61aba9..60c4035 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -140,6 +140,7 @@ function system_list($type) { // drupal_get_filename() static cache for bootstrap modules only. // The rest is stored separately to keep the bootstrap module cache small. foreach ($bootstrap_list as $module) { + drupal_classloader_register($module->name, dirname($module->filename)); drupal_get_filename('module', $module->name, $module->filename); } // We only return the module names here since module_list() doesn't need @@ -175,6 +176,7 @@ function system_list($type) { } // Build a list of filenames so drupal_get_filename can use it. if ($record->status) { + drupal_classloader_register($record->name, dirname($record->filename)); $lists['filepaths'][] = array('type' => $record->type, 'name' => $record->name, 'filepath' => $record->filename); } } @@ -461,6 +463,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(). diff --git a/core/includes/path.inc b/core/includes/path.inc index b49d42b..223ab04 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -96,7 +96,7 @@ function drupal_lookup_path($action, $path = '', $langcode = NULL) { $args = array( ':system' => $cache['system_paths'], ':langcode' => $langcode, - ':language_none' => LANGUAGE_NONE, + ':langcode_undetermined' => LANGUAGE_NOT_SPECIFIED, ); // Always get the language-specific alias before the language-neutral // one. For example 'de' is less than 'und' so the order needs to be @@ -105,16 +105,16 @@ function drupal_lookup_path($action, $path = '', $langcode = NULL) { // the most recently created alias for each source. Subsequent queries // using fetchField() must use pid DESC to have the same effect. // For performance reasons, the query builder is not used here. - if ($langcode == LANGUAGE_NONE) { + if ($langcode == LANGUAGE_NOT_SPECIFIED) { // Prevent PDO from complaining about a token the query doesn't use. unset($args[':langcode']); - $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode = :language_none ORDER BY pid ASC', $args); + $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode = :langcode_undetermined ORDER BY pid ASC', $args); } - elseif ($langcode < LANGUAGE_NONE) { - $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode IN (:langcode, :language_none) ORDER BY langcode ASC, pid ASC', $args); + elseif ($langcode < LANGUAGE_NOT_SPECIFIED) { + $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid ASC', $args); } else { - $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode IN (:langcode, :language_none) ORDER BY langcode DESC, pid ASC', $args); + $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid ASC', $args); } $cache['map'][$langcode] = $result->fetchAllKeyed(); // Keep a record of paths with no alias to avoid querying twice. @@ -136,18 +136,18 @@ function drupal_lookup_path($action, $path = '', $langcode = NULL) { $args = array( ':source' => $path, ':langcode' => $langcode, - ':language_none' => LANGUAGE_NONE, + ':langcode_undetermined' => LANGUAGE_NOT_SPECIFIED, ); // See the queries above. - if ($langcode == LANGUAGE_NONE) { + if ($langcode == LANGUAGE_NOT_SPECIFIED) { unset($args[':langcode']); - $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode = :language_none ORDER BY pid DESC", $args)->fetchField(); + $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode = :langcode_undetermined ORDER BY pid DESC", $args)->fetchField(); } - elseif ($langcode > LANGUAGE_NONE) { - $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :language_none) ORDER BY langcode DESC, pid DESC", $args)->fetchField(); + elseif ($langcode > LANGUAGE_NOT_SPECIFIED) { + $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid DESC", $args)->fetchField(); } else { - $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :language_none) ORDER BY langcode ASC, pid DESC", $args)->fetchField(); + $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid DESC", $args)->fetchField(); } $cache['map'][$langcode][$path] = $alias; return $alias; @@ -162,18 +162,18 @@ function drupal_lookup_path($action, $path = '', $langcode = NULL) { $args = array( ':alias' => $path, ':langcode' => $langcode, - ':language_none' => LANGUAGE_NONE, + ':langcode_undetermined' => LANGUAGE_NOT_SPECIFIED, ); // See the queries above. - if ($langcode == LANGUAGE_NONE) { + if ($langcode == LANGUAGE_NOT_SPECIFIED) { unset($args[':langcode']); - $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode = :language_none ORDER BY pid DESC", $args); + $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode = :langcode_undetermined ORDER BY pid DESC", $args); } - elseif ($langcode > LANGUAGE_NONE) { - $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :language_none) ORDER BY langcode DESC, pid DESC", $args); + elseif ($langcode > LANGUAGE_NOT_SPECIFIED) { + $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid DESC", $args); } else { - $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :language_none) ORDER BY langcode ASC, pid DESC", $args); + $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid DESC", $args); } if ($source = $result->fetchField()) { $cache['map'][$langcode][$source] = $path; @@ -431,7 +431,7 @@ function path_load($conditions) { * - langcode: (optional) The language code of the alias. */ function path_save(&$path) { - $path += array('langcode' => LANGUAGE_NONE); + $path += array('langcode' => LANGUAGE_NOT_SPECIFIED); // Load the stored alias, if any. if (!empty($path['pid']) && !isset($path['original'])) { diff --git a/core/includes/schema.inc b/core/includes/schema.inc new file mode 100644 index 0000000..18fab75 --- /dev/null +++ b/core/includes/schema.inc @@ -0,0 +1,513 @@ +get('schema')) { + $schema = $cached->data; + } + // Otherwise, rebuild the schema cache. + else { + $schema = array(); + // Load the .install files to get hook_schema. + // On some databases this function may be called before bootstrap has + // been completed, so we force the functions we need to load just in case. + if (function_exists('module_load_all_includes')) { + // This function can be called very early in the bootstrap process, so + // we force the module_list() cache to be refreshed to ensure that it + // contains the complete list of modules before we go on to call + // module_load_all_includes(). + module_list(TRUE); + module_load_all_includes('install'); + } + + require_once DRUPAL_ROOT . '/core/includes/common.inc'; + // Invoke hook_schema for all modules. + foreach (module_implements('schema') as $module) { + // Cast the result of hook_schema() to an array, as a NULL return value + // would cause array_merge() to set the $schema variable to NULL as well. + // That would break modules which use $schema further down the line. + $current = (array) module_invoke($module, 'schema'); + // Set 'module' and 'name' keys for each table, and remove descriptions, + // as they needlessly slow down cache()->get() for every single request. + _drupal_schema_initialize($current, $module); + $schema = array_merge($schema, $current); + } + + drupal_alter('schema', $schema); + // If the schema is empty, avoid saving it: some database engines require + // the schema to perform queries, and this could lead to infinite loops. + if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) { + cache()->set('schema', $schema); + } + if ($rebuild) { + cache()->deletePrefix('schema:'); + } + } + } + + return $schema; +} + +/** + * Returns an array of available schema versions for a module. + * + * @param string $module + * A module name. + * + * @return array|bool + * If the module has updates, an array of available updates sorted by version. + * Otherwise, FALSE. + */ +function drupal_get_schema_versions($module) { + $updates = &drupal_static(__FUNCTION__, NULL); + if (!isset($updates[$module])) { + $updates = array(); + + foreach (module_list() as $loaded_module) { + $updates[$loaded_module] = array(); + } + + // Prepare regular expression to match all possible defined hook_update_N(). + $regexp = '/^(?P.+)_update_(?P\d+)$/'; + $functions = get_defined_functions(); + // Narrow this down to functions ending with an integer, since all + // hook_update_N() functions end this way, and there are other + // possible functions which match '_update_'. We use preg_grep() here + // instead of foreaching through all defined functions, since the loop + // through all PHP functions can take significant page execution time + // and this function is called on every administrative page via + // system_requirements(). + foreach (preg_grep('/_\d+$/', $functions['user']) as $function) { + // If this function is a module update function, add it to the list of + // module updates. + if (preg_match($regexp, $function, $matches)) { + $updates[$matches['module']][] = $matches['version']; + } + } + // Ensure that updates are applied in numerical order. + foreach ($updates as &$module_updates) { + sort($module_updates, SORT_NUMERIC); + } + } + return empty($updates[$module]) ? FALSE : $updates[$module]; +} + +/** + * Returns the currently installed schema version for a module. + * + * @param string $module + * A module name. + * @param bool $reset + * Set to TRUE after modifying the system table. + * @param bool $array + * Set to TRUE if you want to get information about all modules in the + * system. + * + * @return string|int + * The currently installed schema version, or SCHEMA_UNINSTALLED if the + * module is not installed. + */ +function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) { + static $versions = array(); + + if ($reset) { + $versions = array(); + } + + if (!$versions) { + $versions = array(); + $result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module')); + foreach ($result as $row) { + $versions[$row->name] = $row->schema_version; + } + } + + if ($array) { + return $versions; + } + else { + return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED; + } +} + +/** + * Updates the installed version information for a module. + * + * @param string $module + * A module name. + * @param string $version + * The new schema version. + */ +function drupal_set_installed_schema_version($module, $version) { + db_update('system') + ->fields(array('schema_version' => $version)) + ->condition('name', $module) + ->execute(); + + // Reset the static cache of module schema versions. + drupal_get_installed_schema_version(NULL, TRUE); +} + +/** + * Creates all tables defined in a module's hook_schema(). + * + * Note: This function does not pass the module's schema through + * hook_schema_alter(). The module's tables will be created exactly as the + * module defines them. + * + * @param string $module + * The module for which the tables will be created. + */ +function drupal_install_schema($module) { + $schema = drupal_get_schema_unprocessed($module); + _drupal_schema_initialize($schema, $module, FALSE); + + foreach ($schema as $name => $table) { + db_create_table($name, $table); + } +} + +/** + * Removes all tables defined in a module's hook_schema(). + * + * Note: This function does not pass the module's schema through + * hook_schema_alter(). The module's tables will be created exactly as the + * module defines them. + * + * @param string $module + * The module for which the tables will be removed. + * + * @return array + * An array of arrays with the following key/value pairs: + * - success: a boolean indicating whether the query succeeded. + * - query: the SQL query(s) executed, passed through check_plain(). + */ +function drupal_uninstall_schema($module) { + $schema = drupal_get_schema_unprocessed($module); + _drupal_schema_initialize($schema, $module, FALSE); + + foreach ($schema as $table) { + if (db_table_exists($table['name'])) { + db_drop_table($table['name']); + } + } +} + +/** + * Returns the unprocessed and unaltered version of a module's schema. + * + * Use this function only if you explicitly need the original + * specification of a schema, as it was defined in a module's + * hook_schema(). No additional default values will be set, + * hook_schema_alter() is not invoked and these unprocessed + * definitions won't be cached. + * + * This function can be used to retrieve a schema specification in + * hook_schema(), so it allows you to derive your tables from existing + * specifications. + * + * It is also used by drupal_install_schema() and + * drupal_uninstall_schema() to ensure that a module's tables are + * created exactly as specified without any changes introduced by a + * module that implements hook_schema_alter(). + * + * @param string $module + * The module to which the table belongs. + * @param string $table + * The name of the table. If not given, the module's complete schema + * is returned. + */ +function drupal_get_schema_unprocessed($module, $table = NULL) { + // Load the .install file to get hook_schema. + module_load_install($module); + $schema = module_invoke($module, 'schema'); + + if (isset($table) && isset($schema[$table])) { + return $schema[$table]; + } + elseif (!empty($schema)) { + return $schema; + } + return array(); +} + +/** + * Fills in required default values for table definitions from hook_schema(). + * + * @param array $schema + * The schema definition array as it was returned by the module's + * hook_schema(). + * @param string $module + * The module for which hook_schema() was invoked. + * @param bool $remove_descriptions + * (optional) Whether to additionally remove 'description' keys of all tables + * and fields to improve performance of serialize() and unserialize(). + * Defaults to TRUE. + */ +function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) { + // Set the name and module key for all tables. + foreach ($schema as $name => &$table) { + if (empty($table['module'])) { + $table['module'] = $module; + } + if (!isset($table['name'])) { + $table['name'] = $name; + } + if ($remove_descriptions) { + unset($table['description']); + foreach ($table['fields'] as &$field) { + unset($field['description']); + } + } + } +} + +/** + * Retrieves a list of fields from a table schema. + * + * The returned list is suitable for use in an SQL query. + * + * @param string $table + * The name of the table from which to retrieve fields. + * @param string $prefix + * An optional prefix to to all fields. + * + * @return array + * An array of fields. + */ +function drupal_schema_fields_sql($table, $prefix = NULL) { + $schema = drupal_get_schema($table); + $fields = array_keys($schema['fields']); + if ($prefix) { + $columns = array(); + foreach ($fields as $field) { + $columns[] = "$prefix.$field"; + } + return $columns; + } + else { + return $fields; + } +} + +/** + * Saves (inserts or updates) a record to the database based upon the schema. + * + * @param string $table + * The name of the table; this must be defined by a hook_schema() + * implementation. + * @param object|array $record + * An object or array representing the record to write, passed in by + * reference. If inserting a new record, values not provided in $record will + * be populated in $record and in the database with the default values from + * the schema, as well as a single serial (auto-increment) field (if present). + * If updating an existing record, only provided values are updated in the + * database, and $record is not modified. + * @param array $primary_keys + * To indicate that this is a new record to be inserted, omit this argument. + * If this is an update, this argument specifies the primary keys' field + * names. If there is only 1 field in the key, you may pass in a string; if + * there are multiple fields in the key, pass in an array. + * + * @return bool|int + * If the record insert or update failed, returns FALSE. If it succeeded, + * returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed. + */ +function drupal_write_record($table, &$record, $primary_keys = array()) { + // Standardize $primary_keys to an array. + if (is_string($primary_keys)) { + $primary_keys = array($primary_keys); + } + + $schema = drupal_get_schema($table); + if (empty($schema)) { + return FALSE; + } + + $object = (object) $record; + $fields = array(); + $default_fields = array(); + + // Go through the schema to determine fields to write. + foreach ($schema['fields'] as $field => $info) { + if ($info['type'] == 'serial') { + // Skip serial types if we are updating. + if (!empty($primary_keys)) { + continue; + } + // Track serial field so we can helpfully populate them after the query. + // NOTE: Each table should come with one serial field only. + $serial = $field; + } + + // Skip field if it is in $primary_keys as it is unnecessary to update a + // field to the value it is already set to. + if (in_array($field, $primary_keys)) { + continue; + } + + if (!property_exists($object, $field)) { + // Skip fields that are not provided, default values are already known + // by the database. + $default_fields[] = $field; + continue; + } + + // Build array of fields to update or insert. + if (empty($info['serialize'])) { + $fields[$field] = $object->$field; + } + else { + $fields[$field] = serialize($object->$field); + } + + // Type cast to proper datatype, except when the value is NULL and the + // column allows this. + // + // MySQL PDO silently casts e.g. FALSE and '' to 0 when inserting the value + // into an integer column, but PostgreSQL PDO does not. Also type cast NULL + // when the column does not allow this. + if (isset($object->$field) || !empty($info['not null'])) { + if ($info['type'] == 'int' || $info['type'] == 'serial') { + $fields[$field] = (int) $fields[$field]; + } + elseif ($info['type'] == 'float') { + $fields[$field] = (float) $fields[$field]; + } + else { + $fields[$field] = (string) $fields[$field]; + } + } + } + + // Build the SQL. + if (empty($primary_keys)) { + // We are doing an insert. + $options = array('return' => Database::RETURN_INSERT_ID); + if (isset($serial) && isset($fields[$serial])) { + // If the serial column has been explicitly set with an ID, then we don't + // require the database to return the last insert id. + if ($fields[$serial]) { + $options['return'] = Database::RETURN_AFFECTED; + } + // If a serial column does exist with no value (i.e. 0) then remove it as + // the database will insert the correct value for us. + else { + unset($fields[$serial]); + } + } + // Create an INSERT query. useDefaults() is necessary for the SQL to be + // valid when $fields is empty. + $query = db_insert($table, $options) + ->fields($fields) + ->useDefaults($default_fields); + + $return = SAVED_NEW; + } + else { + // Create an UPDATE query. + $query = db_update($table)->fields($fields); + foreach ($primary_keys as $key) { + $query->condition($key, $object->$key); + } + $return = SAVED_UPDATED; + } + + // Execute the SQL. + if ($query_return = $query->execute()) { + if (isset($serial)) { + // If the database was not told to return the last insert id, it will be + // because we already know it. + if (isset($options) && $options['return'] != Database::RETURN_INSERT_ID) { + $object->$serial = $fields[$serial]; + } + else { + $object->$serial = $query_return; + } + } + } + // If we have a single-field primary key but got no insert ID, the + // query failed. Note that we explicitly check for FALSE, because + // a valid update query which doesn't change any values will return + // zero (0) affected rows. + elseif ($query_return === FALSE && count($primary_keys) == 1) { + $return = FALSE; + } + + // If we are inserting, populate empty fields with default values. + if (empty($primary_keys)) { + foreach ($schema['fields'] as $field => $info) { + if (isset($info['default']) && !property_exists($object, $field)) { + $object->$field = $info['default']; + } + } + } + + // If we began with an array, convert back. + if (is_array($record)) { + $record = (array) $object; + } + + return $return; +} + +/** + * @} End of "ingroup schemaapi". + */ diff --git a/core/includes/theme.inc b/core/includes/theme.inc index fbb7804..d044f06 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1,5 +1,5 @@ &$provider) { - if (isset($negotiation[$id]['file']) && $negotiation[$id]['file'] == 'includes/locale.inc') { - $negotiation[$id]['file'] = 'core/includes/locale.inc'; + foreach ($negotiation as $method_id => &$method) { + if (isset($method['file']) && $method['file'] == 'includes/locale.inc') { + $method['file'] = 'core/includes/locale.inc'; } } variable_set("language_negotiation_$language_type", $negotiation); diff --git a/core/includes/utility.inc b/core/includes/utility.inc index 7d82f32..d44e4dd 100644 --- a/core/includes/utility.inc +++ b/core/includes/utility.inc @@ -51,7 +51,7 @@ function drupal_var_export($var, $prefix = '') { // magic method __set_state() leaving the export broken. This // workaround avoids this by casting the object as an array for // export and casting it back to an object when evaluated. - $output .= '(object) ' . drupal_var_export((array) $var, $prefix); + $output = '(object) ' . drupal_var_export((array) $var, $prefix); } else { $output = var_export($var, TRUE); diff --git a/core/includes/uuid.inc b/core/includes/uuid.inc deleted file mode 100644 index 57f2199..0000000 --- a/core/includes/uuid.inc +++ /dev/null @@ -1,154 +0,0 @@ -determinePlugin(); - $this->plugin = new $class(); - } - - /** - * Generates an universally unique identifier. - * - * @see UuidInterface::generate() - */ - public function generate() { - return $this->plugin->generate(); - } - - /** - * Check that a string appears to be in the format of a UUID. - * - * Plugins should not implement validation, since UUIDs should be in a - * consistent format across all plugins. - * - * @param $uuid - * The string to test. - * - * @return - * TRUE if the string is well formed. - */ - public function isValid($uuid) { - return preg_match("/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/", $uuid); - } - - /** - * Determines the optimal implementation to use for generating UUIDs. - * - * The selection is made based on the enabled PHP extensions with the - * most performant available option chosen. - * - * @return - * The class name for the optimal UUID generator. - */ - protected function determinePlugin() { - static $plugin; - if (!empty($plugin)) { - return $plugin; - } - - $plugin = 'UuidPhp'; - - // Debian/Ubuntu uses the (broken) OSSP extension as their UUID - // implementation. The OSSP implementation is not compatible with the - // PECL functions. - if (function_exists('uuid_create') && !function_exists('uuid_make')) { - $plugin = 'UuidPecl'; - } - // Try to use the COM implementation for Windows users. - elseif (function_exists('com_create_guid')) { - $plugin = 'UuidCom'; - } - return $plugin; - } -} - -/** - * UUID implementation using the PECL extension. - */ -class UuidPecl implements UuidInterface { - public function generate() { - return uuid_create(UUID_TYPE_DEFAULT); - } -} - -/** - * UUID implementation using the Windows internal GUID extension. - * - * @see http://php.net/com_create_guid - */ -class UuidCom implements UuidInterface { - public function generate() { - // Remove {} wrapper and make lower case to keep result consistent. - return drupal_strtolower(trim(com_create_guid(), '{}')); - } -} - -/** - * Generates an UUID v4 using PHP code. - * - * Loosely based on Ruby's UUIDTools generate_random logic. - * - * @see http://uuidtools.rubyforge.org/api/classes/UUIDTools/UUID.html - */ -class UuidPhp implements UuidInterface { - public function generate() { - $hex = substr(hash('sha256', drupal_random_bytes(16)), 0, 32); - - // The field names refer to RFC 4122 section 4.1.2. - $time_low = substr($hex, 0, 8); - $time_mid = substr($hex, 8, 4); - - $time_hi_and_version = base_convert(substr($hex, 12, 4), 16, 10); - $time_hi_and_version &= 0x0FFF; - $time_hi_and_version |= (4 << 12); - - $clock_seq_hi_and_reserved = base_convert(substr($hex, 16, 4), 16, 10); - $clock_seq_hi_and_reserved &= 0x3F; - $clock_seq_hi_and_reserved |= 0x80; - - $clock_seq_low = substr($hex, 20, 2); - $nodes = substr($hex, 20); - - $uuid = sprintf('%s-%s-%04x-%02x%02x-%s', - $time_low, $time_mid, - $time_hi_and_version, $clock_seq_hi_and_reserved, - $clock_seq_low, $nodes); - - return $uuid; - } -} diff --git a/core/install.php b/core/install.php index 7e908bd..3ea85ff 100644 --- a/core/install.php +++ b/core/install.php @@ -26,8 +26,8 @@ define('MAINTENANCE_MODE', 'install'); // The minimum version is specified explicitly, as DRUPAL_MINIMUM_PHP is not // yet available. It is defined in bootstrap.inc, but it is not possible to // load that file yet as it would cause a fatal error on older versions of PHP. -if (version_compare(PHP_VERSION, '5.3.2') < 0) { - print 'Your PHP installation is too old. Drupal requires at least PHP 5.3.2. See the system requirements page for more information.'; +if (version_compare(PHP_VERSION, '5.3.3') < 0) { + print 'Your PHP installation is too old. Drupal requires at least PHP 5.3.3. See the system requirements page for more information.'; exit; } diff --git a/core/lib/Drupal/Component/Uuid/Com.php b/core/lib/Drupal/Component/Uuid/Com.php new file mode 100644 index 0000000..41cfba4 --- /dev/null +++ b/core/lib/Drupal/Component/Uuid/Com.php @@ -0,0 +1,20 @@ +determinePlugin(); + $this->plugin = new $class(); + } + + /** + * Generates an universally unique identifier. + * + * @see Drupal\Component\Uuid\UuidInterface::generate() + */ + public function generate() { + return $this->plugin->generate(); + } + + /** + * Checks that a string appears to be in the format of a UUID. + * + * Plugins should not implement validation, since UUIDs should be in a + * consistent format across all plugins. + * + * @param string $uuid + * The string to test. + * + * @return bool + * TRUE if the string is well formed, FALSE otherwise. + */ + public function isValid($uuid) { + return preg_match("/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/", $uuid); + } + + /** + * Determines the optimal implementation to use for generating UUIDs. + * + * The selection is made based on the enabled PHP extensions with the + * most performant available option chosen. + * + * @return string + * The class name for the optimal UUID generator. + */ + protected function determinePlugin() { + static $plugin; + if (!empty($plugin)) { + return $plugin; + } + + $plugin = 'Drupal\Component\Uuid\Php'; + + // Debian/Ubuntu uses the (broken) OSSP extension as their UUID + // implementation. The OSSP implementation is not compatible with the + // PECL functions. + if (function_exists('uuid_create') && !function_exists('uuid_make')) { + $plugin = 'Drupal\Component\Uuid\Pecl'; + } + // Try to use the COM implementation for Windows users. + elseif (function_exists('com_create_guid')) { + $plugin = 'Drupal\Component\Uuid\Com'; + } + return $plugin; + } +} diff --git a/core/lib/Drupal/Component/Uuid/UuidInterface.php b/core/lib/Drupal/Component/Uuid/UuidInterface.php new file mode 100644 index 0000000..d1225ba --- /dev/null +++ b/core/lib/Drupal/Component/Uuid/UuidInterface.php @@ -0,0 +1,22 @@ +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; } diff --git a/core/lib/Drupal/Core/Cache/InstallBackend.php b/core/lib/Drupal/Core/Cache/InstallBackend.php index 8a1b177..53f9b23 100644 --- a/core/lib/Drupal/Core/Cache/InstallBackend.php +++ b/core/lib/Drupal/Core/Cache/InstallBackend.php @@ -2,7 +2,7 @@ /** * @file - * Definition of InstallBackend. + * Definition of Drupal\Core\Cache\InstallBackend. */ namespace Drupal\Core\Cache; diff --git a/core/lib/Drupal/Core/Cache/NullBackend.php b/core/lib/Drupal/Core/Cache/NullBackend.php index af83b96..c3da5d7 100644 --- a/core/lib/Drupal/Core/Cache/NullBackend.php +++ b/core/lib/Drupal/Core/Cache/NullBackend.php @@ -2,7 +2,7 @@ /** * @file - * Definition of NullBackend. + * Definition of Drupal\Core\Cache\NullBackend. */ namespace Drupal\Core\Cache; diff --git a/core/lib/Drupal/Core/Config/ConfigException.php b/core/lib/Drupal/Core/Config/ConfigException.php new file mode 100644 index 0000000..c60a449 --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigException.php @@ -0,0 +1,8 @@ +_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 + * + * baz + * + * @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(); + } +} diff --git a/core/lib/Drupal/Core/Config/DrupalConfigVerifiedStorage.php b/core/lib/Drupal/Core/Config/DrupalConfigVerifiedStorage.php new file mode 100644 index 0000000..ca37cdd --- /dev/null +++ b/core/lib/Drupal/Core/Config/DrupalConfigVerifiedStorage.php @@ -0,0 +1,102 @@ +name = $name; + } + + /** + * Instantiates a new signed file object or returns the existing one. + * + * @return SignedFileStorage + * The signed file object for this configuration object. + */ + protected function signedFileStorage() { + if (!isset($this->signedFile)) { + $this->signedFile = new SignedFileStorage($this->name); + } + return $this->signedFile; + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::copyToFile(). + */ + public function copyToFile() { + return $this->writeToFile($this->read()); + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::deleteFile(). + */ + public function deleteFile() { + return $this->signedFileStorage()->delete(); + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::copyFromFile(). + */ + public function copyFromFile() { + return $this->writeToActive($this->readFromFile()); + } + + /** + * @todo + * + * @return + * @todo + */ + public function readFromFile() { + return $this->signedFileStorage()->read($this->name); + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::isOutOfSync(). + */ + public function isOutOfSync() { + return $this->read() !== $this->readFromFile(); + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::write(). + */ + public function write($data) { + $this->writeToActive($data); + $this->writeToFile($data); + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::writeToFile(). + */ + public function writeToFile($data) { + return $this->signedFileStorage()->write($data); + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::delete(). + */ + public function delete() { + $this->deleteFromActive(); + $this->deleteFile(); + } +} diff --git a/core/lib/Drupal/Core/Config/DrupalConfigVerifiedStorageInterface.php b/core/lib/Drupal/Core/Config/DrupalConfigVerifiedStorageInterface.php new file mode 100644 index 0000000..2fdce76 --- /dev/null +++ b/core/lib/Drupal/Core/Config/DrupalConfigVerifiedStorageInterface.php @@ -0,0 +1,84 @@ + $this->name))->fetchField(); + } catch (Exception $e) { + return array(); + } + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::writeToActive(). + */ + public function writeToActive($data) { + return db_merge('config') + ->key(array('name' => $this->name)) + ->fields(array('data' => $data)) + ->execute(); + } + + /** + * @todo + */ + public function deleteFromActive() { + db_delete('config') + ->condition('name', $this->name) + ->execute(); + } + + /** + * Implements DrupalConfigVerifiedStorageInterface::getNamesWithPrefix(). + */ + static public function getNamesWithPrefix($prefix = '') { + return db_query('SELECT name FROM {config} WHERE name LIKE :name', array(':name' => db_like($prefix) . '%'))->fetchCol(); + } +} diff --git a/core/lib/Drupal/Core/Config/SignedFileStorage.php b/core/lib/Drupal/Core/Config/SignedFileStorage.php new file mode 100644 index 0000000..c669029 --- /dev/null +++ b/core/lib/Drupal/Core/Config/SignedFileStorage.php @@ -0,0 +1,143 @@ +name = $name; + } + + /** + * Reads and returns a signed file and its signature. + * + * @return + * An array with "signature" and "data" keys. + * + * @throws + * Exception + */ + protected function readWithSignature() { + $content = file_get_contents($this->getFilePath()); + if ($content === FALSE) { + throw new \Exception('Read file is invalid.'); + } + $signature = file_get_contents($this->getFilePath() . '.sig'); + if ($signature === FALSE) { + throw new \Exception('Signature file is invalid.'); + } + return array('data' => $content, 'signature' => $signature); + } + + /** + * Checks whether the XML configuration file already exists on disk. + * + * @return + * @todo + */ + protected function exists() { + return file_exists($this->getFilePath()); + } + + /** + * Returns the path to the XML configuration file. + * + * @return + * @todo + */ + public function getFilePath() { + return config_get_config_directory() . '/' . $this->name . '.xml'; + } + + /** + * Recreates the signature for the file. + */ + public function resign() { + if ($this->exists()) { + $parts = $this->readWithSignature(); + $this->write($parts['data']); + } + } + + /** + * Cryptographically verifies the integrity of the configuration file. + * + * @param $contentOnSuccess + * Whether or not to return the contents of the verified configuration file. + * + * @return mixed + * If $contentOnSuccess was TRUE, returns the contents of the verified + * configuration file; otherwise returns TRUE on success. Always returns + * FALSE if the configuration file was not successfully verified. + */ + public function verify($contentOnSuccess = FALSE) { + if ($this->exists()) { + $split = $this->readWithSignature(); + $expected_signature = config_sign_data($split['data']); + if ($expected_signature === $split['signature']) { + if ($contentOnSuccess) { + return $split['data']; + } + return TRUE; + } + } + return FALSE; + } + + /** + * Writes the contents of the configuration file to disk. + * + * @param $data + * The data to be written to the file. + * + * @throws + * Exception + */ + public function write($data) { + $signature = config_sign_data($data); + if (!file_put_contents($this->getFilePath(), $data)) { + throw new \Exception('Failed to write configuration file: ' . $this->getFilePath()); + } + if (!file_put_contents($this->getFilePath() . '.sig', $signature)) { + throw new \Exception('Failed to write signature file: ' . $this->getFilePath()); + } + } + + /** + * Returns the contents of the configuration file. + * + * @return + * @todo + */ + public function read() { + if ($this->exists()) { + $verification = $this->verify(TRUE); + if ($verification === FALSE) { + throw new \Exception('Invalid signature in file header.'); + } + return $verification; + } + } + + /** + * Deletes a configuration file. + */ + public function delete() { + // Needs error handling and etc. + @drupal_unlink($this->getFilePath()); + @drupal_unlink($this->getFilePath() . '.sig'); + } +} diff --git a/core/lib/Drupal/Core/Database/Query/Condition.php b/core/lib/Drupal/Core/Database/Query/Condition.php index 9faabc6..f7d2a2f 100644 --- a/core/lib/Drupal/Core/Database/Query/Condition.php +++ b/core/lib/Drupal/Core/Database/Query/Condition.php @@ -260,7 +260,7 @@ class Condition implements ConditionInterface, Countable { function __clone() { $this->changed = TRUE; foreach ($this->conditions as $key => $condition) { - if ($condition['field'] instanceOf ConditionInterface) { + if ($key !== '#conjunction' && $condition['field'] instanceOf ConditionInterface) { $this->conditions[$key]['field'] = clone($condition['field']); } } diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php new file mode 100644 index 0000000..32ba880 --- /dev/null +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -0,0 +1,110 @@ +getDispatcher(); + + $matcher = $this->getMatcher($request); + $dispatcher->addSubscriber(new RouterListener($matcher)); + $dispatcher->addSubscriber(new AccessSubscriber()); + $dispatcher->addSubscriber(new PathSubscriber()); + $dispatcher->addSubscriber(new LegacyControllerSubscriber()); + + $resolver = new ControllerResolver(); + + $kernel = new HttpKernel($dispatcher, $resolver); + $response = $kernel->handle($request); + } + catch (Exception $e) { + // Some other form of error occured that wasn't handled by another kernel + // listener. That could mean that it's a method/mime-type/error + // combination that is not accounted for, or some other type of error. + // Either way, treat it as a server-level error and return an HTTP 500. + // By default, this will be an HTML-type response because that's a decent + // best guess if we don't know otherwise. + $response = new Response('A fatal error occurred: ' . $e->getMessage(), 500); + } + + return $response; + } + + /** + * Returns an EventDispatcher for the Kernel to use. + * + * The EventDispatcher is pre-wired with some event listeners/subscribers. + * + * @todo Make the listeners that get attached extensible, but without using + * hooks. + * + * @return EventDispatcher + */ + protected function getDispatcher() { + $dispatcher = new EventDispatcher(); + + // @todo Make this extensible rather than just hard coding some. + // @todo Add a subscriber to handle other things, too, like our Ajax + // replacement system. + $dispatcher->addSubscriber(new HtmlSubscriber()); + $dispatcher->addSubscriber(new JsonSubscriber()); + + return $dispatcher; + } + + /** + * Returns a UrlMatcher object for the specified request. + * + * @param Request $request + * The request object for this matcher to use. + * @return UrlMatcher + */ + protected function getMatcher(Request $request) { + // Resolve a routing context(path, etc) using the routes object to a + // Set a routing context to translate. + $context = new RequestContext(); + $context->fromRequest($request); + $matcher = new UrlMatcher($context); + + return $matcher; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php new file mode 100644 index 0000000..88ce042 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php @@ -0,0 +1,54 @@ +getRequest()->attributes->get('drupal_menu_item'); + + if (!$router_item['access']) { + throw new AccessDeniedHttpException($message); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php new file mode 100644 index 0000000..aa57fe3 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php @@ -0,0 +1,109 @@ +getRequest()->getAcceptableContentTypes()); + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onAccessDeniedException(GetResponseEvent $event) { + if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof AccessDeniedHttpException) { + $event->setResponse(new Response('Access Denied', 403)); + } + } + + /** + * Processes a NotFound exception into an HTTP 404 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onNotFoundHttpException(GetResponseEvent $event) { + if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof NotFoundHttpException) { + $event->setResponse(new Response('Not Found', 404)); + } + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onMethodAllowedException(GetResponseEvent $event) { + if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof MethodNotAllowedException) { + $event->setResponse(new Response('Method Not Allowed', 405)); + } + } + + /** + * Processes a successful controller into an HTTP 200 response. + * + * Some controllers may not return a response object but simply the body of + * one. The VIEW event is called in that case, to allow us to mutate that + * body into a Response object. In particular we assume that the return + * from an HTML-type response is a render array from a legacy page callback + * and render it. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onView(GetResponseEvent $event) { + if ($this->isHtmlRequestEvent($event)) { + $page_callback_result = $event->getControllerResult(); + $event->setResponse(new Response(drupal_render_page($page_callback_result))); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + // Since we want HTML to be our default, catch-all response type, give its + // listeners a very low priority so that they always check last. + $events[KernelEvents::EXCEPTION][] = array('onNotFoundHttpException', -5); + $events[KernelEvents::EXCEPTION][] = array('onAccessDeniedException', -5); + $events[KernelEvents::EXCEPTION][] = array('onMethodAllowedException', -5); + + $events[KernelEvents::VIEW][] = array('onView', -5); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php new file mode 100644 index 0000000..92ce1e8 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php @@ -0,0 +1,124 @@ +getRequest()->getAcceptableContentTypes()); + } + + protected function createJsonResponse() { + $response = new Response(); + $response->headers->set('Content-Type', 'application/json; charset=utf-8'); + + return $response; + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onAccessDeniedException(GetResponseEvent $event) { + if ($this->isJsonRequestEvent($event) && $event->getException() instanceof AccessDeniedHttpException) { + $response = $this->createJsonResponse(); + $response->setStatusCode(403, 'Access Denied'); + $event->setResponse($response); + } + } + + /** + * Processes a NotFound exception into an HTTP 404 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onNotFoundHttpException(GetResponseEvent $event) { + if ($this->isJsonRequestEvent($event) && $event->getException() instanceof NotFoundHttpException) { + $response = $this->createJsonResponse(); + $response->setStatusCode(404, 'Not Found'); + $event->setResponse($response); + } + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onMethodAllowedException(GetResponseEvent $event) { + if ($this->isJsonRequestEvent($event) && $event->getException() instanceof MethodNotAllowedException) { + $response = $this->createJsonResponse(); + $response->setStatusCode(405, 'Method Not Allowed'); + $event->setResponse($response); + } + } + + /** + * Processes a successful controller into an HTTP 200 response. + * + * Some controllers may not return a response object but simply the body of + * one. The VIEW event is called in that case, to allow us to mutate that + * body into a Response object. In particular we assume that the return + * from an JSON-type response is a JSON string, so just wrap it into a + * Response object. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onView(GetResponseEvent $event) { + if ($this->isJsonRequestEvent($event)) { + $page_callback_result = $event->getControllerResult(); + + $response = $this->createJsonResponse(); + $response->setContent($page_callback_result); + + $event->setResponse($response); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::EXCEPTION][] = array('onNotFoundHttpException'); + $events[KernelEvents::EXCEPTION][] = array('onAccessDeniedException'); + $events[KernelEvents::EXCEPTION][] = array('onMethodAllowedException'); + + $events[KernelEvents::VIEW][] = array('onView'); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php new file mode 100644 index 0000000..e85d89f --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php @@ -0,0 +1,64 @@ +getRequest()->attributes->get('drupal_menu_item'); + $controller = $event->getController(); + + // This BC logic applies only to functions. Otherwise, skip it. + if (function_exists($controller)) { + $new_controller = function() use ($router_item) { + return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); + }; + $event->setController($new_controller); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php new file mode 100644 index 0000000..94bdbc4 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -0,0 +1,74 @@ +getRequest(); + + $path = ltrim($request->getPathInfo(), '/'); + + // Temporary BC shiv to support automated tests that still rely on old- + // style dirty URLs. + if (isset($_GET['q'])) { + $path = $_GET['q']; + } + + if (empty($path)) { + // @todo Temporary hack. Fix when configuration is injectable. + $path = variable_get('site_frontpage', 'user'); + } + $system_path = drupal_get_normal_path($path); + + $request->attributes->set('system_path', $system_path); + + // @todo Remove this line. + // Drupal uses $_GET['q'] directly in over 100 places at present, + // including writing back to it at times. Those are all critical bugs, + // even by Drupal 7 standards, but as many of the places that it does so + // are slated to be rewritten anyway we will save time and include this + // temporary hack. Removal of this line is a critical, Drupal-release + // blocking bug. + $_GET['q'] = $system_path; + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/Mail/MailInterface.php b/core/lib/Drupal/Core/Mail/MailInterface.php new file mode 100644 index 0000000..e18ba5a --- /dev/null +++ b/core/lib/Drupal/Core/Mail/MailInterface.php @@ -0,0 +1,52 @@ + + * - User , Another User + * - subject: Subject of the e-mail to be sent. This must not contain any + * newline characters, or the mail may not be sent properly. + * - body: Message to be sent. Accepts both CRLF and LF line-endings. + * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for + * smart plain text wrapping. + * - headers: Associative array containing all additional mail headers not + * defined by one of the other parameters. PHP's mail() looks for Cc + * and Bcc headers and sends the mail to addresses in these headers too. + * + * @return bool + * TRUE if the mail was successfully accepted for delivery, otherwise FALSE. + */ + public function mail(array $message); +} diff --git a/core/modules/system/system.mail.inc b/core/lib/Drupal/Core/Mail/PhpMail.php similarity index 80% rename from core/modules/system/system.mail.inc rename to core/lib/Drupal/Core/Mail/PhpMail.php index 1ce9fc0..668cc51 100644 --- a/core/modules/system/system.mail.inc +++ b/core/lib/Drupal/Core/Mail/PhpMail.php @@ -2,20 +2,23 @@ /** * @file - * Drupal core implementations of MailSystemInterface. + * Definition of Drupal\Core\Mail\PhpMail. */ +namespace Drupal\Core\Mail; + /** * The default Drupal mail backend using PHP's mail function. */ -class DefaultMailSystem implements MailSystemInterface { +class PhpMail implements MailInterface { + /** - * Concatenate and wrap the e-mail body for plain-text mails. + * Concatenates and wrap the e-mail body for plain-text mails. * - * @param $message + * @param array $message * A message array, as described in hook_mail_alter(). * - * @return + * @return array * The formatted $message. */ public function format(array $message) { @@ -25,19 +28,21 @@ class DefaultMailSystem implements MailSystemInterface { $message['body'] = drupal_html_to_text($message['body']); // Wrap the mail body for sending. $message['body'] = drupal_wrap_mail($message['body']); + return $message; } /** - * Send an e-mail message, using Drupal variables and default settings. - * - * @see http://php.net/manual/en/function.mail.php - * @see drupal_mail() + * Sends an e-mail message, using Drupal variables and default settings. * - * @param $message + * @param array $message * A message array, as described in hook_mail_alter(). - * @return + * + * @return bool * TRUE if the mail was successfully accepted, otherwise FALSE. + * + * @see http://php.net/manual/en/function.mail.php + * @see drupal_mail() */ public function mail(array $message) { // If 'Return-Path' isn't already set in php.ini, we pass it separately @@ -107,27 +112,7 @@ class DefaultMailSystem implements MailSystemInterface { ); ini_set('sendmail_from', $old_from); } - return $mail_result; - } -} -/** - * A mail sending implementation that captures sent messages to a variable. - * - * This class is for running tests or for development. - */ -class TestingMailSystem extends DefaultMailSystem implements MailSystemInterface { - /** - * Accept an e-mail message and store it in a variable. - * - * @param $message - * An e-mail message. - */ - public function mail(array $message) { - $captured_emails = variable_get('drupal_test_email_collector', array()); - $captured_emails[] = $message; - variable_set('drupal_test_email_collector', $captured_emails); - return TRUE; + return $mail_result; } } - diff --git a/core/lib/Drupal/Core/Mail/VariableLog.php b/core/lib/Drupal/Core/Mail/VariableLog.php new file mode 100644 index 0000000..3dfe5b6 --- /dev/null +++ b/core/lib/Drupal/Core/Mail/VariableLog.php @@ -0,0 +1,30 @@ + $this->name))->fetchObject(); + if ($item) { + $item->data = unserialize($item->data); + return $item; + } + return FALSE; + } + + /** + * Retrieves all remaining items in the queue. + * + * This is specific to Batch API and is not part of the + * Drupal\Core\Queue\QueueInterface. + * + * @return array + * An array of queue items. + */ + public function getAllItems() { + $result = array(); + $items = db_query('SELECT data FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchAll(); + foreach ($items as $item) { + $result[] = unserialize($item->data); + } + return $result; + } +} diff --git a/core/lib/Drupal/Core/Queue/BatchMemory.php b/core/lib/Drupal/Core/Queue/BatchMemory.php new file mode 100644 index 0000000..fcc49f5 --- /dev/null +++ b/core/lib/Drupal/Core/Queue/BatchMemory.php @@ -0,0 +1,52 @@ +queue)) { + reset($this->queue); + return current($this->queue); + } + return FALSE; + } + + /** + * Retrieves all remaining items in the queue. + * + * This is specific to Batch API and is not part of the + * Drupal\Core\Queue\QueueInterface. + * + * @return array + * An array of queue items. + */ + public function getAllItems() { + $result = array(); + foreach ($this->queue as $item) { + $result[] = $item->data; + } + return $result; + } +} diff --git a/core/lib/Drupal/Core/Queue/Memory.php b/core/lib/Drupal/Core/Queue/Memory.php new file mode 100644 index 0000000..d741bda --- /dev/null +++ b/core/lib/Drupal/Core/Queue/Memory.php @@ -0,0 +1,107 @@ +queue = array(); + $this->idSequence = 0; + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::createItem(). + */ + public function createItem($data) { + $item = new stdClass(); + $item->item_id = $this->idSequence++; + $item->data = $data; + $item->created = time(); + $item->expire = 0; + $this->queue[$item->item_id] = $item; + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::numberOfItems(). + */ + public function numberOfItems() { + return count($this->queue); + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::claimItem(). + */ + public function claimItem($lease_time = 30) { + foreach ($this->queue as $key => $item) { + if ($item->expire == 0) { + $item->expire = time() + $lease_time; + $this->queue[$key] = $item; + return $item; + } + } + return FALSE; + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::deleteItem(). + */ + public function deleteItem($item) { + unset($this->queue[$item->item_id]); + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::releaseItem(). + */ + public function releaseItem($item) { + if (isset($this->queue[$item->item_id]) && $this->queue[$item->item_id]->expire != 0) { + $this->queue[$item->item_id]->expire = 0; + return TRUE; + } + return FALSE; + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::createQueue(). + */ + public function createQueue() { + // Nothing needed here. + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::deleteQueue(). + */ + public function deleteQueue() { + $this->queue = array(); + $this->idSequence = 0; + } +} diff --git a/core/lib/Drupal/Core/Queue/QueueInterface.php b/core/lib/Drupal/Core/Queue/QueueInterface.php new file mode 100644 index 0000000..90d4b00 --- /dev/null +++ b/core/lib/Drupal/Core/Queue/QueueInterface.php @@ -0,0 +1,114 @@ +name = $name; + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::createItem(). + */ + public function createItem($data) { + // During a Drupal 6.x to 8.x update, drupal_get_schema() does not contain + // the queue table yet, so we cannot rely on drupal_write_record(). + $query = db_insert('queue') + ->fields(array( + 'name' => $this->name, + 'data' => serialize($data), + // We cannot rely on REQUEST_TIME because many items might be created + // by a single request which takes longer than 1 second. + 'created' => time(), + )); + return (bool) $query->execute(); + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::numberOfItems(). + */ + public function numberOfItems() { + return db_query('SELECT COUNT(item_id) FROM {queue} WHERE name = :name', array(':name' => $this->name))->fetchField(); + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::claimItem(). + */ + public function claimItem($lease_time = 30) { + // Claim an item by updating its expire fields. If claim is not successful + // another thread may have claimed the item in the meantime. Therefore loop + // until an item is successfully claimed or we are reasonably sure there + // are no unclaimed items left. + while (TRUE) { + $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created ASC', 0, 1, array(':name' => $this->name))->fetchObject(); + if ($item) { + // Try to update the item. Only one thread can succeed in UPDATEing the + // same row. We cannot rely on REQUEST_TIME because items might be + // claimed by a single consumer which runs longer than 1 second. If we + // continue to use REQUEST_TIME instead of the current time(), we steal + // time from the lease, and will tend to reset items before the lease + // should really expire. + $update = db_update('queue') + ->fields(array( + 'expire' => time() + $lease_time, + )) + ->condition('item_id', $item->item_id) + ->condition('expire', 0); + // If there are affected rows, this update succeeded. + if ($update->execute()) { + $item->data = unserialize($item->data); + return $item; + } + } + else { + // No items currently available to claim. + return FALSE; + } + } + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::releaseItem(). + */ + public function releaseItem($item) { + $update = db_update('queue') + ->fields(array( + 'expire' => 0, + )) + ->condition('item_id', $item->item_id); + return $update->execute(); + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::deleteItem(). + */ + public function deleteItem($item) { + db_delete('queue') + ->condition('item_id', $item->item_id) + ->execute(); + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::createQueue(). + */ + public function createQueue() { + // All tasks are stored in a single database table (which is created when + // Drupal is first installed) so there is nothing we need to do to create + // a new queue. + } + + /** + * Implements Drupal\Core\Queue\QueueInterface::deleteQueue(). + */ + public function deleteQueue() { + db_delete('queue') + ->condition('name', $this->name) + ->execute(); + } +} diff --git a/core/includes/stream_wrappers.inc b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php similarity index 55% rename from core/includes/stream_wrappers.inc rename to core/lib/Drupal/Core/StreamWrapper/LocalStream.php index 0edcee1..9f2b95d 100644 --- a/core/includes/stream_wrappers.inc +++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php @@ -2,247 +2,34 @@ /** * @file - * Drupal stream wrapper interface. - * - * Provides a Drupal interface and classes to implement PHP stream wrappers for - * public, private, and temporary files. - * - * A stream wrapper is an abstraction of a file system that allows Drupal to - * use the same set of methods to access both local files and remote resources. - * - * Note that PHP 5.2 fopen() only supports URIs of the form "scheme://target" - * despite the fact that according to RFC 3986 a URI's scheme component - * delimiter is in general just ":", not "://". Because of this PHP limitation - * and for consistency Drupal will only accept URIs of form "scheme://target". - * - * @see http://www.faqs.org/rfcs/rfc3986.html - * @see http://bugs.php.net/bug.php?id=47070 - */ - -/** - * Stream wrapper bit flags that are the basis for composite types. - * - * Note that 0x0002 is skipped, because it was the value of a constant that has - * since been removed. - */ - -/** - * Stream wrapper bit flag -- a filter that matches all wrappers. - */ -const STREAM_WRAPPERS_ALL = 0x0000; - -/** - * Stream wrapper bit flag -- refers to a local file system location. - */ -const STREAM_WRAPPERS_LOCAL = 0x0001; - -/** - * Stream wrapper bit flag -- wrapper is readable (almost always true). - */ -const STREAM_WRAPPERS_READ = 0x0004; - -/** - * Stream wrapper bit flag -- wrapper is writeable. - */ -const STREAM_WRAPPERS_WRITE = 0x0008; - -/** - * Stream wrapper bit flag -- exposed in the UI and potentially web accessible. - */ -const STREAM_WRAPPERS_VISIBLE = 0x0010; - -/** - * Composite stream wrapper bit flags that are usually used as the types. - */ - -/** - * Stream wrapper type flag -- not visible in the UI or accessible via web, - * but readable and writable. E.g. the temporary directory for uploads. + * Definition of Drupal\Core\StreamWrapper\LocalStream. */ -define('STREAM_WRAPPERS_HIDDEN', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE); -/** - * Stream wrapper type flag -- hidden, readable and writeable using local files. - */ -define('STREAM_WRAPPERS_LOCAL_HIDDEN', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_HIDDEN); +namespace Drupal\Core\StreamWrapper; /** - * Stream wrapper type flag -- visible, readable and writeable. - */ -define('STREAM_WRAPPERS_WRITE_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE | STREAM_WRAPPERS_VISIBLE); - -/** - * Stream wrapper type flag -- visible and read-only. - */ -define('STREAM_WRAPPERS_READ_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_VISIBLE); - -/** - * Stream wrapper type flag -- the default when 'type' is omitted from - * hook_stream_wrappers(). This does not include STREAM_WRAPPERS_LOCAL, - * because PHP grants a greater trust level to local files (for example, they - * can be used in an "include" statement, regardless of the "allow_url_include" - * setting), so stream wrappers need to explicitly opt-in to this. - */ -define('STREAM_WRAPPERS_NORMAL', STREAM_WRAPPERS_WRITE_VISIBLE); - -/** - * Stream wrapper type flag -- visible, readable and writeable using local files. - */ -define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_NORMAL); - -/** - * Generic PHP stream wrapper interface. - * - * @see http://www.php.net/manual/en/class.streamwrapper.php - */ -interface StreamWrapperInterface { - public function stream_open($uri, $mode, $options, &$opened_url); - public function stream_close(); - public function stream_lock($operation); - public function stream_read($count); - public function stream_write($data); - public function stream_eof(); - public function stream_seek($offset, $whence); - public function stream_flush(); - public function stream_tell(); - public function stream_stat(); - public function unlink($uri); - public function rename($from_uri, $to_uri); - public function mkdir($uri, $mode, $options); - public function rmdir($uri, $options); - public function url_stat($uri, $flags); - public function dir_opendir($uri, $options); - public function dir_readdir(); - public function dir_rewinddir(); - public function dir_closedir(); -} - -/** - * Drupal stream wrapper extension. - * - * Extend the StreamWrapperInterface with methods expected by Drupal stream - * wrapper classes. - */ -interface DrupalStreamWrapperInterface extends StreamWrapperInterface { - /** - * Set the absolute stream resource URI. - * - * This allows you to set the URI. Generally is only called by the factory - * method. - * - * @param $uri - * A string containing the URI that should be used for this instance. - */ - function setUri($uri); - - /** - * Returns the stream resource URI. - * - * @return - * Returns the current URI of the instance. - */ - public function getUri(); - - /** - * Returns a web accessible URL for the resource. - * - * This function should return a URL that can be embedded in a web page - * and accessed from a browser. For example, the external URL of - * "youtube://xIpLd0WQKCY" might be - * "http://www.youtube.com/watch?v=xIpLd0WQKCY". - * - * @return - * Returns a string containing a web accessible URL for the resource. - */ - public function getExternalUrl(); - - /** - * Returns the MIME type of the resource. - * - * @param $uri - * The URI, path, or filename. - * @param $mapping - * An optional map of extensions to their mimetypes, in the form: - * - 'mimetypes': a list of mimetypes, keyed by an identifier, - * - 'extensions': the mapping itself, an associative array in which - * the key is the extension and the value is the mimetype identifier. - * - * @return - * Returns a string containing the MIME type of the resource. - */ - public static function getMimeType($uri, $mapping = NULL); - - /** - * Changes permissions of the resource. - * - * PHP lacks this functionality and it is not part of the official stream - * wrapper interface. This is a custom implementation for Drupal. - * - * @param $mode - * Integer value for the permissions. Consult PHP chmod() documentation - * for more information. - * - * @return - * Returns TRUE on success or FALSE on failure. - */ - public function chmod($mode); - - /** - * Returns canonical, absolute path of the resource. - * - * Implementation placeholder. PHP's realpath() does not support stream - * wrappers. We provide this as a default so that individual wrappers may - * implement their own solutions. - * - * @return - * Returns a string with absolute pathname on success (implemented - * by core wrappers), or FALSE on failure or if the registered - * wrapper does not provide an implementation. - */ - public function realpath(); - - /** - * Gets the name of the directory from a given path. - * - * This method is usually accessed through drupal_dirname(), which wraps - * around the normal PHP dirname() function, which does not support stream - * wrappers. - * - * @param $uri - * An optional URI. - * - * @return - * A string containing the directory name, or FALSE if not applicable. - * - * @see drupal_dirname() - */ - public function dirname($uri = NULL); -} - - -/** - * Drupal stream wrapper base class for local files. + * Defines a Drupal stream wrapper base class for local files. * * This class provides a complete stream wrapper implementation. URIs such as * "public://example.txt" are expanded to a normal filesystem path such as * "sites/default/files/example.txt" and then PHP filesystem functions are * invoked. * - * DrupalLocalStreamWrapper implementations need to implement at least the + * Drupal\Core\StreamWrapper\LocalStream implementations need to implement at least the * getDirectoryPath() and getExternalUrl() methods. */ -abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface { +abstract class LocalStream implements StreamWrapperInterface { /** * Stream context resource. * - * @var Resource + * @var resource */ public $context; /** * A generic resource handle. * - * @var Resource + * @var resource */ public $handle = NULL; @@ -251,7 +38,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * A stream is referenced as "scheme://target". * - * @var String + * @var string */ protected $uri; @@ -259,20 +46,20 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * Gets the path that the wrapper is responsible for. * @TODO: Review this method name in D8 per http://drupal.org/node/701358 * - * @return + * @return string * String specifying the path. */ abstract function getDirectoryPath(); /** - * Base implementation of setUri(). + * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::setUri(). */ function setUri($uri) { $this->uri = $uri; } /** - * Base implementation of getUri(). + * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getUri(). */ function getUri() { return $this->uri; @@ -287,10 +74,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * method may return a URI or path suitable for writing that is completely * separate from the URI used for reading. * - * @param $uri + * @param string $uri * Optional URI. * - * @return + * @return string|bool * Returns a string representing a location suitable for writing of a file, * or FALSE if unable to write to the file such as with read-only streams. */ @@ -306,7 +93,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface } /** - * Base implementation of getMimeType(). + * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getMimeType(). */ static function getMimeType($uri, $mapping = NULL) { if (!isset($mapping)) { @@ -338,7 +125,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface } /** - * Base implementation of chmod(). + * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::chmod(). */ function chmod($mode) { $output = @chmod($this->getLocalPath(), $mode); @@ -349,7 +136,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface } /** - * Base implementation of realpath(). + * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath(). */ function realpath() { return $this->getLocalPath(); @@ -362,9 +149,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * (optional) The stream wrapper URI to be converted to a canonical * absolute path. This may point to a directory or another type of file. * - * @return string|false + * @return string|bool * If $uri is not set, returns the canonical absolute path of the URI - * previously set by the DrupalStreamWrapperInterface::setUri() function. + * previously set by the + * Drupal\Core\StreamWrapper\StreamWrapperInterface::setUri() function. * If $uri is set and valid for this class, returns its canonical absolute * path, as determined by the realpath() function. If $uri is set but not * valid, returns FALSE. @@ -389,16 +177,16 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for fopen(), file_get_contents(), file_put_contents() etc. * - * @param $uri + * @param string $uri * A string containing the URI to the file to open. - * @param $mode + * @param int $mode * The file mode ("r", "wb" etc.). - * @param $options + * @param int $options * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS. - * @param $opened_path + * @param string $opened_path * A string containing the path actually opened. * - * @return + * @return bool * Returns TRUE if file was opened successfully. * * @see http://php.net/manual/en/streamwrapper.stream-open.php @@ -418,7 +206,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for flock(). * - * @param $operation + * @param int $operation * One of the following: * - LOCK_SH to acquire a shared lock (reader). * - LOCK_EX to acquire an exclusive lock (writer). @@ -426,7 +214,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * - LOCK_NB if you don't want flock() to block while locking (not * supported on Windows). * - * @return + * @return bool * Always returns TRUE at the present time. * * @see http://php.net/manual/en/streamwrapper.stream-lock.php @@ -442,10 +230,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for fread(), file_get_contents() etc. * - * @param $count + * @param int $count * Maximum number of bytes to be read. * - * @return + * @return string|bool * The string that was read, or FALSE in case of an error. * * @see http://php.net/manual/en/streamwrapper.stream-read.php @@ -457,11 +245,11 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for fwrite(), file_put_contents() etc. * - * @param $data + * @param string $data * The string to be written. * - * @return - * The number of bytes written (integer). + * @return int + * The number of bytes written. * * @see http://php.net/manual/en/streamwrapper.stream-write.php */ @@ -472,7 +260,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for feof(). * - * @return + * @return bool * TRUE if end-of-file has been reached. * * @see http://php.net/manual/en/streamwrapper.stream-eof.php @@ -484,12 +272,12 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for fseek(). * - * @param $offset + * @param int $offset * The byte offset to got to. - * @param $whence + * @param int $whence * SEEK_SET, SEEK_CUR, or SEEK_END. * - * @return + * @return bool * TRUE on success. * * @see http://php.net/manual/en/streamwrapper.stream-seek.php @@ -503,7 +291,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for fflush(). * - * @return + * @return bool * TRUE if data was successfully stored (or there was no data to store). * * @see http://php.net/manual/en/streamwrapper.stream-flush.php @@ -515,7 +303,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for ftell(). * - * @return + * @return bool * The current offset in bytes from the beginning of file. * * @see http://php.net/manual/en/streamwrapper.stream-tell.php @@ -527,7 +315,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for fstat(). * - * @return + * @return bool * An array with file status, or FALSE in case of an error - see fstat() * for a description of this array. * @@ -540,7 +328,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for fclose(). * - * @return + * @return bool * TRUE if stream was successfully closed. * * @see http://php.net/manual/en/streamwrapper.stream-close.php @@ -552,10 +340,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for unlink(). * - * @param $uri + * @param string $uri * A string containing the uri to the resource to delete. * - * @return + * @return bool * TRUE if resource was successfully deleted. * * @see http://php.net/manual/en/streamwrapper.unlink.php @@ -568,12 +356,12 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for rename(). * - * @param $from_uri, + * @param string $from_uri, * The uri to the file to rename. - * @param $to_uri + * @param string $to_uri * The new uri for file. * - * @return + * @return bool * TRUE if file was successfully renamed. * * @see http://php.net/manual/en/streamwrapper.rename.php @@ -589,10 +377,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * around the PHP dirname() function because it does not support stream * wrappers. * - * @param $uri + * @param string $uri * A URI or path. * - * @return + * @return string * A string containing the directory name. * * @see drupal_dirname() @@ -612,14 +400,14 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for mkdir(). * - * @param $uri + * @param string $uri * A string containing the URI to the directory to create. - * @param $mode + * @param int $mode * Permission flags - see mkdir(). - * @param $options + * @param int $options * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE. * - * @return + * @return bool * TRUE if directory was successfully created. * * @see http://php.net/manual/en/streamwrapper.mkdir.php @@ -646,12 +434,12 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for rmdir(). * - * @param $uri + * @param string $uri * A string containing the URI to the directory to delete. - * @param $options + * @param int $options * A bit mask of STREAM_REPORT_ERRORS. * - * @return + * @return bool * TRUE if directory was successfully removed. * * @see http://php.net/manual/en/streamwrapper.rmdir.php @@ -669,12 +457,12 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for stat(). * - * @param $uri + * @param string $uri * A string containing the URI to get information about. - * @param $flags + * @param int $flags * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET. * - * @return + * @return array * An array with file status, or FALSE in case of an error - see fstat() * for a description of this array. * @@ -696,12 +484,12 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for opendir(). * - * @param $uri + * @param string $uri * A string containing the URI to the directory to open. - * @param $options + * @param int $options * Unknown (parameter is not documented in PHP Manual). * - * @return + * @return bool * TRUE on success. * * @see http://php.net/manual/en/streamwrapper.dir-opendir.php @@ -716,7 +504,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for readdir(). * - * @return + * @return string * The next filename, or FALSE if there are no more files in the directory. * * @see http://php.net/manual/en/streamwrapper.dir-readdir.php @@ -728,7 +516,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for rewinddir(). * - * @return + * @return bool * TRUE on success. * * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php @@ -744,7 +532,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Support for closedir(). * - * @return + * @return bool * TRUE on success. * * @see http://php.net/manual/en/streamwrapper.dir-closedir.php @@ -756,81 +544,3 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface return TRUE; } } - -/** - * Drupal public (public://) stream wrapper class. - * - * Provides support for storing publicly accessible files with the Drupal file - * interface. - */ -class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper { - /** - * Implements abstract public function getDirectoryPath() - */ - public function getDirectoryPath() { - return variable_get('file_public_path', conf_path() . '/files'); - } - - /** - * Overrides getExternalUrl(). - * - * Return the HTML URI of a public file. - */ - function getExternalUrl() { - $path = str_replace('\\', '/', $this->getTarget()); - return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path); - } -} - - -/** - * Drupal private (private://) stream wrapper class. - * - * Provides support for storing privately accessible files with the Drupal file - * interface. - * - * Extends DrupalPublicStreamWrapper. - */ -class DrupalPrivateStreamWrapper extends DrupalLocalStreamWrapper { - /** - * Implements abstract public function getDirectoryPath() - */ - public function getDirectoryPath() { - return variable_get('file_private_path', ''); - } - - /** - * Overrides getExternalUrl(). - * - * Return the HTML URI of a private file. - */ - function getExternalUrl() { - $path = str_replace('\\', '/', $this->getTarget()); - return url('system/files/' . $path, array('absolute' => TRUE)); - } -} - -/** - * Drupal temporary (temporary://) stream wrapper class. - * - * Provides support for storing temporarily accessible files with the Drupal - * file interface. - * - * Extends DrupalPublicStreamWrapper. - */ -class DrupalTemporaryStreamWrapper extends DrupalLocalStreamWrapper { - /** - * Implements abstract public function getDirectoryPath() - */ - public function getDirectoryPath() { - return variable_get('file_temporary_path', file_directory_temp()); - } - - /** - * Overrides getExternalUrl(). - */ - public function getExternalUrl() { - $path = str_replace('\\', '/', $this->getTarget()); - return url('system/temporary/' . $path, array('absolute' => TRUE)); - } -} diff --git a/core/lib/Drupal/Core/StreamWrapper/PhpStreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/PhpStreamWrapperInterface.php new file mode 100644 index 0000000..4cf41e0 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/PhpStreamWrapperInterface.php @@ -0,0 +1,35 @@ +getTarget()); + return url('system/files/' . $path, array('absolute' => TRUE)); + } +} diff --git a/core/lib/Drupal/Core/StreamWrapper/PublicStream.php b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php new file mode 100644 index 0000000..207b77a --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php @@ -0,0 +1,35 @@ +getTarget()); + return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path); + } +} diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php new file mode 100644 index 0000000..15ba2cf --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php @@ -0,0 +1,125 @@ +getTarget()); + return url('system/temporary/' . $path, array('absolute' => TRUE)); + } +} diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php new file mode 100644 index 0000000..eea11a6 --- /dev/null +++ b/core/lib/Drupal/Core/Updater/Module.php @@ -0,0 +1,113 @@ +name)) { + $relative_path = dirname($relative_path); + } + else { + $relative_path = 'sites/all/modules'; + } + return DRUPAL_ROOT . '/' . $relative_path; + } + + /** + * Implements Drupal\Core\Updater\UpdaterInterface::isInstalled(). + */ + public function isInstalled() { + return (bool) drupal_get_path('module', $this->name); + } + + /** + * Implements Drupal\Core\Updater\UpdaterInterface::canUpdateDirectory(). + */ + public static function canUpdateDirectory($directory) { + if (file_scan_directory($directory, '/.*\.module$/')) { + return TRUE; + } + return FALSE; + } + + /** + * Determines whether this class can update the specified project. + * + * @param string $project_name + * The project to check. + * + * @return bool + */ + public static function canUpdate($project_name) { + return (bool) drupal_get_path('module', $project_name); + } + + /** + * Returns available database schema updates once a new version is installed. + * + * @return array + */ + public function getSchemaUpdates() { + require_once DRUPAL_ROOT . '/core/includes/install.inc'; + require_once DRUPAL_ROOT . '/core/includes/update.inc'; + + if (!self::canUpdate($this->name)) { + return array(); + } + module_load_include('install', $this->name); + + if (!$updates = drupal_get_schema_versions($this->name)) { + return array(); + } + $modules_with_updates = update_get_update_list(); + if ($updates = $modules_with_updates[$this->name]) { + if ($updates['start']) { + return $updates['pending']; + } + } + return array(); + } + + /** + * Overrides Drupal\Core\Updater\Updater::postInstallTasks(). + */ + public function postInstallTasks() { + return array( + l(t('Enable newly added modules'), 'admin/modules'), + l(t('Administration pages'), 'admin'), + ); + } + + /** + * Overrides Drupal\Core\Updater\Updater::postUpdateTasks(). + */ + public function postUpdateTasks() { + // We don't want to check for DB updates here, we do that once for all + // updated modules on the landing page. + } +} diff --git a/core/lib/Drupal/Core/Updater/Theme.php b/core/lib/Drupal/Core/Updater/Theme.php new file mode 100644 index 0000000..697fae8 --- /dev/null +++ b/core/lib/Drupal/Core/Updater/Theme.php @@ -0,0 +1,89 @@ +name)) { + $relative_path = dirname($relative_path); + } + else { + $relative_path = 'sites/all/themes'; + } + return DRUPAL_ROOT . '/' . $relative_path; + } + + /** + * Implements Drupal\Core\Updater\UpdaterInterface::isInstalled(). + */ + public function isInstalled() { + return (bool) drupal_get_path('theme', $this->name); + } + + /** + * Implements Drupal\Core\Updater\UpdaterInterface::canUpdateDirectory(). + */ + static function canUpdateDirectory($directory) { + // This is a lousy test, but don't know how else to confirm it is a theme. + if (file_scan_directory($directory, '/.*\.module$/')) { + return FALSE; + } + return TRUE; + } + + /** + * Determines whether this class can update the specified project. + * + * @param string $project_name + * The project to check. + * + * @return bool + */ + public static function canUpdate($project_name) { + return (bool) drupal_get_path('theme', $project_name); + } + + /** + * Overrides Drupal\Core\Updater\Updater::postInstall(). + */ + public function postInstall() { + // Update the system table. + clearstatcache(); + system_rebuild_theme_data(); + } + + /** + * Overrides Drupal\Core\Updater\Updater::postInstallTasks(). + */ + public function postInstallTasks() { + return array( + l(t('Enable newly added themes'), 'admin/appearance'), + l(t('Administration pages'), 'admin'), + ); + } +} diff --git a/core/includes/updater.inc b/core/lib/Drupal/Core/Updater/Updater.php similarity index 74% rename from core/includes/updater.inc rename to core/lib/Drupal/Core/Updater/Updater.php index d4f99e9..b7e5915 100644 --- a/core/includes/updater.inc +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -2,78 +2,29 @@ /** * @file - * Classes used for updating various files in the Drupal webroot. These - * classes use a FileTransfer object to actually perform the operations. - * Normally, the FileTransfer is provided when the site owner is redirected to - * authorize.php as part of a multistep process. + * Definition of Drupal\Core\Updater\Updater. */ +namespace Drupal\Core\Updater; + /** - * Interface for a class which can update a Drupal project. - * - * An Updater currently serves the following purposes: - * - It can take a given directory, and determine if it can operate on it. - * - It can move the contents of that directory into the appropriate place - * on the system using FileTransfer classes. - * - It can return a list of "next steps" after an update or install. - * - In the future, it will most likely perform some of those steps as well. + * Defines the base class for Updaters used in Drupal. */ -interface DrupalUpdaterInterface { - - /** - * Checks if the project is installed. - * - * @return bool - */ - public function isInstalled(); +class Updater { /** - * Returns the system name of the project. + * Directory to install from. * - * @param string $directory - * A directory containing a project. - */ - public static function getProjectName($directory); - - /** - * @return string - * An absolute path to the default install location. + * @var string */ - public function getInstallDirectory(); + public $source; /** - * Determine if the Updater can handle the project provided in $directory. + * Constructs a new updater. * - * @todo: Provide something more rational here, like a project spec file. - * - * @param string $directory - * - * @return bool - * TRUE if the project is installed, FALSE if not. - */ - public static function canUpdateDirectory($directory); - - /** - * Actions to run after an install has occurred. - */ - public function postInstall(); - - /** - * Actions to run after an update has occurred. - */ - public function postUpdate(); -} - -/** - * Base class for Updaters used in Drupal. - */ -class Updater { - - /** - * @var string $source Directory to install from. + * @param string $source + * Directory to install from. */ - public $source; - public function __construct($source) { $this->source = $source; $this->name = self::getProjectName($source); @@ -81,7 +32,7 @@ class Updater { } /** - * Return an Updater of the appropriate type depending on the source. + * Returns an Updater of the appropriate type depending on the source. * * If a directory is provided which contains a module, will return a * ModuleUpdater. @@ -89,7 +40,10 @@ class Updater { * @param string $source * Directory of a Drupal project. * - * @return Updater + * @return Drupal\Core\Updater\Updater + * A new Drupal\Core\Updater\Updater object. + * + * @throws Drupal\Core\Updater\UpdaterException */ public static function factory($source) { if (is_dir($source)) { @@ -102,13 +56,15 @@ class Updater { } /** - * Determine which Updater class can operate on the given directory. + * Determines which Updater class can operate on the given directory. * * @param string $directory * Extracted Drupal project. * * @return string * The class name which can work with this project type. + * + * @throws Drupal\Core\Updater\UpdaterException */ public static function getUpdaterFromDirectory($directory) { // Gets a list of possible implementing classes. @@ -123,7 +79,7 @@ class Updater { } /** - * Figure out what the most important (or only) info file is in a directory. + * Determines what the most important (or only) info file is in a directory. * * Since there is no enforcement of which info file is the project's "main" * info file, this will get one with the same name as the directory, or the @@ -152,7 +108,7 @@ class Updater { } /** - * Get the name of the project directory (basename). + * Gets the name of the project directory (basename). * * @todo: It would be nice, if projects contained an info file which could * provide their canonical name. @@ -167,13 +123,15 @@ class Updater { } /** - * Return the project name from a Drupal info file. + * Returns the project name from a Drupal info file. * * @param string $directory * Directory to search for the info file. * * @return string * The title of the project. + * + * @throws Drupal\Core\Updater\UpdaterException */ public static function getProjectTitle($directory) { $info_file = self::findInfoFile($directory); @@ -188,7 +146,7 @@ class Updater { } /** - * Store the default parameters for the Updater. + * Stores the default parameters for the Updater. * * @param array $overrides * An array of overrides for the default parameters. @@ -206,9 +164,9 @@ class Updater { } /** - * Updates a Drupal project, returns a list of next actions. + * Updates a Drupal project and returns a list of next actions. * - * @param FileTransfer $filetransfer + * @param Drupal\Core\FileTransfer\FileTransferInterface $filetransfer * Object that is a child of FileTransfer. Used for moving files * to the server. * @param array $overrides @@ -216,6 +174,9 @@ class Updater { * * @return array * An array of links which the user may need to complete the update + * + * @throws Drupal\Core\Updater\UpdaterException + * @throws Drupal\Core\Updater\UpdaterFileTransferException */ public function update(&$filetransfer, $overrides = array()) { try { @@ -264,13 +225,15 @@ class Updater { /** * Installs a Drupal project, returns a list of next actions. * - * @param FileTransfer $filetransfer + * @param Drupal\Core\FileTransfer\FileTransferInterface $filetransfer * Object that is a child of FileTransfer. * @param array $overrides * An array of settings to override defaults; see self::getInstallArgs(). * * @return array * An array of links which the user may need to complete the install. + * + * @throws Drupal\Core\Updater\UpdaterFileTransferException */ public function install(&$filetransfer, $overrides = array()) { try { @@ -298,12 +261,14 @@ class Updater { } /** - * Make sure the installation parent directory exists and is writable. + * Makes sure the installation parent directory exists and is writable. * - * @param FileTransfer $filetransfer + * @param Drupal\Core\FileTransfer\FileTransferInterface $filetransfer * Object which is a child of FileTransfer. * @param string $directory * The installation directory to prepare. + * + * @throws Drupal\Core\Updater\UpdaterException */ public function prepareInstallDirectory(&$filetransfer, $directory) { // Make the parent dir writable if need be and create the dir. @@ -341,9 +306,9 @@ class Updater { } /** - * Ensure that a given directory is world readable. + * Ensures that a given directory is world readable. * - * @param FileTransfer $filetransfer + * @param Drupal\Core\FileTransfer\FileTransferInterface $filetransfer * Object which is a child of FileTransfer. * @param string $path * The file path to make world readable. @@ -359,7 +324,7 @@ class Updater { } /** - * Perform a backup. + * Performs a backup. * * @todo Not implemented. */ @@ -367,26 +332,26 @@ class Updater { } /** - * Return the full path to a directory where backups should be written. + * Returns the full path to a directory where backups should be written. */ public function getBackupDir() { return file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath(); } /** - * Perform actions after new code is updated. + * Performs actions after new code is updated. */ public function postUpdate() { } /** - * Perform actions after installation. + * Performs actions after installation. */ public function postInstall() { } /** - * Return an array of links to pages that should be visited post operation. + * Returns an array of links to pages that should be visited post operation. * * @return array * Links which provide actions to take after the install is finished. @@ -396,7 +361,7 @@ class Updater { } /** - * Return an array of links to pages that should be visited post operation. + * Returns an array of links to pages that should be visited post operation. * * @return array * Links which provide actions to take after the update is finished. @@ -405,23 +370,3 @@ class Updater { return array(); } } - -/** - * Exception class for the Updater class hierarchy. - * - * This is identical to the base Exception class, we just give it a more - * specific name so that call sites that want to tell the difference can - * specifically catch these exceptions and treat them differently. - */ -class UpdaterException extends Exception { -} - -/** - * Child class of UpdaterException that indicates a FileTransfer exception. - * - * We have to catch FileTransfer exceptions and wrap those in t(), since - * FileTransfer is so low-level that it doesn't use any Drupal APIs and none - * of the strings are translated. - */ -class UpdaterFileTransferException extends UpdaterException { -} diff --git a/core/lib/Drupal/Core/Updater/UpdaterException.php b/core/lib/Drupal/Core/Updater/UpdaterException.php new file mode 100644 index 0000000..fc9cb9d --- /dev/null +++ b/core/lib/Drupal/Core/Updater/UpdaterException.php @@ -0,0 +1,19 @@ +context = $context; + } + + /** + * {@inheritDoc} + * + * @api + */ + public function match($pathinfo) { + + $this->allow = array(); + + // Symfony uses a prefixing / but we don't yet. + $dpathinfo = ltrim($pathinfo, '/'); + + // Temporary BC shiv to support automated tests that still rely on old- + // style dirty URLs. + // @todo Remove this once testbot knows how to deal with Symfony-style + // dirty URLs. + if (isset($_GET['q'])) { + $dpathinfo = $_GET['q']; + $pathinfo = '/' . $dpathinfo; + } + + // Do our fancy frontpage logic. + if (empty($dpathinfo)) { + $dpathinfo = variable_get('site_frontpage', 'user'); + $pathinfo = '/' . $dpathinfo; + } + + if ($router_item = $this->matchDrupalItem($dpathinfo)) { + + $routes = new RouteCollection(); + $routes->add(hash('sha256', $router_item['path']), $this->convertDrupalItem($router_item)); + + if ($ret = $this->matchCollection($pathinfo, $routes)) { + //drupal_set_message('
' . var_export('test', TRUE) . '
'); + // Stash the router item in the attributes while we're transitioning. + $ret['drupal_menu_item'] = $router_item; + + // Most legacy controllers (aka page callbacks) are in a separate file, + // so we have to include that. + if ($router_item['include_file']) { + require_once DRUPAL_ROOT . '/' . $router_item['include_file']; + } + + return $ret; + } + } + + throw 0 < count($this->allow) + ? new MethodNotAllowedException(array_unique(array_map('strtoupper', $this->allow))) + : new ResourceNotFoundException(); + } + + /** + * Get a drupal menu item. + * + * @todo Make this return multiple possible candidates for the resolver to + * consider. + * + * @param string $path + * The path being looked up by + */ + protected function matchDrupalItem($path) { + // For now we can just proxy our procedural method. At some point this will + // become more complicated because we'll need to get back candidates for a + // path and them resolve them based on things like method and scheme which + // we currently can't do. + return menu_get_item($path); + } + + protected function convertDrupalItem($router_item) { + $route = array( + '_controller' => $router_item['page_callback'] + ); + // Place argument defaults on the route. + foreach ($router_item['page_arguments'] as $k => $v) { + $route[$k] = $v; + } + return new Route($router_item['href'], $route); + } +} diff --git a/core/lib/Drupal/Core/Utility/CacheArray.php b/core/lib/Drupal/Core/Utility/CacheArray.php new file mode 100644 index 0000000..8f98b72 --- /dev/null +++ b/core/lib/Drupal/Core/Utility/CacheArray.php @@ -0,0 +1,210 @@ + 'baz'), but later + * want to change the value of 'bar' from 'baz' to 'foobar', you cannot do so + * a targeted write like $object['foo']['bar'] = 'foobar'. Instead, you must + * overwrite the entire top-level 'foo' array with the entire set of new + * values: $object['foo'] = array(1, 2, 'bar' => 'foobar'). Due to this same + * limitation, attempts to create references to any contained data, nested or + * otherwise, will fail silently. So $var = &$object['foo'] will not throw an + * error, and $var will be populated with the contents of $object['foo'], but + * that data will be passed by value, not reference. For more information on + * the PHP limitation, see the note in the official PHP documentation atá + * http://php.net/manual/en/arrayaccess.offsetget.php on + * ArrayAccess::offsetGet(). + * + * By default, the class accounts for caches where calling functions might + * request keys in the array that won't exist even after a cache rebuild. This + * prevents situations where a cache rebuild would be triggered over and over + * due to a 'missing' item. These cases are stored internally as a value of + * NULL. This means that the offsetGet() and offsetExists() methods + * must be overridden if caching an array where the top level values can + * legitimately be NULL, and where $object->offsetExists() needs to correctly + * return (equivalent to array_key_exists() vs. isset()). This should not + * be necessary in the majority of cases. + * + * Classes extending this class must override at least the + * resolveCacheMiss() method to have a working implementation. + * + * offsetSet() is not overridden by this class by default. In practice this + * means that assigning an offset via arrayAccess will only apply while the + * object is in scope and will not be written back to the persistent cache. + * This follows a similar pattern to static vs. persistent caching in + * procedural code. Extending classes may wish to alter this behaviour, for + * example by overriding offsetSet() and adding an automatic call to persist(). + * + * @see SchemaCache + */ +abstract class CacheArray implements ArrayAccess { + + /** + * A cid to pass to cache()->set() and cache()->get(). + */ + protected $cid; + + /** + * A bin to pass to cache()->set() and cache()->get(). + */ + protected $bin; + + /** + * An array of keys to add to the cache at the end of the request. + */ + protected $keysToPersist = array(); + + /** + * Storage for the data itself. + */ + protected $storage = array(); + + /** + * Constructs a DrupalCacheArray object. + * + * @param $cid + * The cid for the array being cached. + * @param $bin + * The bin to cache the array. + */ + public function __construct($cid, $bin) { + $this->cid = $cid; + $this->bin = $bin; + + if ($cached = cache($bin)->get($this->cid)) { + $this->storage = $cached->data; + } + } + + /** + * Implements ArrayAccess::offsetExists(). + */ + public function offsetExists($offset) { + return $this->offsetGet($offset) !== NULL; + } + + /** + * Implements ArrayAccess::offsetGet(). + */ + public function offsetGet($offset) { + if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) { + return $this->storage[$offset]; + } + else { + return $this->resolveCacheMiss($offset); + } + } + + /** + * Implements ArrayAccess::offsetSet(). + */ + public function offsetSet($offset, $value) { + $this->storage[$offset] = $value; + } + + /** + * Implements ArrayAccess::offsetUnset(). + */ + public function offsetUnset($offset) { + unset($this->storage[$offset]); + } + + /** + * Flags an offset value to be written to the persistent cache. + * + * If a value is assigned to a cache object with offsetSet(), by default it + * will not be written to the persistent cache unless it is flagged with this + * method. This allows items to be cached for the duration of a request, + * without necessarily writing back to the persistent cache at the end. + * + * @param $offset + * The array offset that was request. + * @param $persist + * Optional boolean to specify whether the offset should be persisted or + * not, defaults to TRUE. When called with $persist = FALSE the offset will + * be unflagged so that it will not written at the end of the request. + */ + protected function persist($offset, $persist = TRUE) { + $this->keysToPersist[$offset] = $persist; + } + + /** + * Resolves a cache miss. + * + * When an offset is not found in the object, this is treated as a cache + * miss. This method allows classes implementing the interface to look up + * the actual value and allow it to be cached. + * + * @param $offset + * The offset that was requested. + * + * @return + * The value of the offset, or NULL if no value was found. + */ + abstract protected function resolveCacheMiss($offset); + + /** + * Writes a value to the persistent cache immediately. + * + * @param $data + * The data to write to the persistent cache. + * @param $lock + * Whether to acquire a lock before writing to cache. + */ + protected function set($data, $lock = TRUE) { + // Lock cache writes to help avoid stampedes. + // To implement locking for cache misses, override __construct(). + $lock_name = $this->cid . ':' . $this->bin; + if (!$lock || lock_acquire($lock_name)) { + if ($cached = cache($this->bin)->get($this->cid)) { + $data = $cached->data + $data; + } + cache($this->bin)->set($this->cid, $data); + if ($lock) { + lock_release($lock_name); + } + } + } + + /** + * Destructs the DrupalCacheArray object. + */ + public function __destruct() { + $data = array(); + foreach ($this->keysToPersist as $offset => $persist) { + if ($persist) { + $data[$offset] = $this->storage[$offset]; + } + } + if (!empty($data)) { + $this->set($data); + } + } +} diff --git a/core/lib/Drupal/Core/Utility/SchemaCache.php b/core/lib/Drupal/Core/Utility/SchemaCache.php new file mode 100644 index 0000000..d8c3a50 --- /dev/null +++ b/core/lib/Drupal/Core/Utility/SchemaCache.php @@ -0,0 +1,35 @@ +storage[$offset] = $value; + $this->persist($offset); + return $value; + } +} diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js index 2f4e5f6..122f05d 100644 --- a/core/misc/autocomplete.js +++ b/core/misc/autocomplete.js @@ -293,10 +293,11 @@ Drupal.ACDB.prototype.search = function (searchString) { this.timer = setTimeout(function () { db.owner.setStatus('begin'); - // Ajax GET request for autocompletion. + // Ajax GET request for autocompletion. We use Drupal.encodePath instead of + // encodeURIComponent to allow autocomplete search terms to contain slashes. $.ajax({ type: 'GET', - url: db.uri + '/' + encodeURIComponent(searchString), + url: db.uri + '/' + Drupal.encodePath(searchString), dataType: 'json', success: function (matches) { if (typeof matches.status === 'undefined' || matches.status !== 0) { diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js index 4f1be3b..ced8c4b 100644 --- a/core/misc/machine-name.js +++ b/core/misc/machine-name.js @@ -19,6 +19,10 @@ Drupal.behaviors.machineName = { * disallowed characters in the machine name; e.g., '[^a-z0-9]+'. * - replace: A character to replace disallowed characters with; e.g., '_' * or '-'. + * - standalone: Whether the preview should stay in its own element rather + * than the suffix of the source element. + * - field_prefix: The #field_prefix of the form element. + * - field_suffix: The #field_suffix of the form element. */ attach: function (context, settings) { var self = this; @@ -49,10 +53,12 @@ Drupal.behaviors.machineName = { var machine = self.transliterate($source.val(), options); } // Append the machine name preview to the source field. - var $preview = $('' + machine + ''); - $suffix.empty() - .append(' ').append('' + options.label + ':') - .append(' ').append($preview); + var $preview = $('' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + ''); + $suffix.empty(); + if (options.label) { + $suffix.append(' ').append('' + options.label + ':'); + } + $suffix.append(' ').append($preview); // If the machine name cannot be edited, stop further processing. if ($target.is(':disabled')) { @@ -80,7 +86,7 @@ Drupal.behaviors.machineName = { if (machine != '') { if (machine != options.replace) { $target.val(machine); - $preview.text(machine); + $preview.html(options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix); } $suffix.show(); } diff --git a/core/misc/powered-black-135x42.png b/core/misc/powered-black-135x42.png deleted file mode 100644 index 1ac2a87..0000000 Binary files a/core/misc/powered-black-135x42.png and /dev/null differ diff --git a/core/misc/powered-black-80x15.png b/core/misc/powered-black-80x15.png deleted file mode 100644 index e76fa03..0000000 Binary files a/core/misc/powered-black-80x15.png and /dev/null differ diff --git a/core/misc/powered-black-88x31.png b/core/misc/powered-black-88x31.png deleted file mode 100644 index 3adaa4f..0000000 Binary files a/core/misc/powered-black-88x31.png and /dev/null differ diff --git a/core/misc/powered-blue-135x42.png b/core/misc/powered-blue-135x42.png deleted file mode 100644 index 21e0521..0000000 Binary files a/core/misc/powered-blue-135x42.png and /dev/null differ diff --git a/core/misc/powered-blue-80x15.png b/core/misc/powered-blue-80x15.png deleted file mode 100644 index 28f8bc5..0000000 Binary files a/core/misc/powered-blue-80x15.png and /dev/null differ diff --git a/core/misc/powered-blue-88x31.png b/core/misc/powered-blue-88x31.png deleted file mode 100644 index e6e3086..0000000 Binary files a/core/misc/powered-blue-88x31.png and /dev/null differ diff --git a/core/misc/powered-gray-135x42.png b/core/misc/powered-gray-135x42.png deleted file mode 100644 index 7bbfcc6..0000000 Binary files a/core/misc/powered-gray-135x42.png and /dev/null differ diff --git a/core/misc/powered-gray-80x15.png b/core/misc/powered-gray-80x15.png deleted file mode 100644 index 80808ad..0000000 Binary files a/core/misc/powered-gray-80x15.png and /dev/null differ diff --git a/core/misc/powered-gray-88x31.png b/core/misc/powered-gray-88x31.png deleted file mode 100644 index a618d83..0000000 Binary files a/core/misc/powered-gray-88x31.png and /dev/null differ diff --git a/core/misc/textarea.js b/core/misc/textarea.js deleted file mode 100644 index 0ab5e71..0000000 --- a/core/misc/textarea.js +++ /dev/null @@ -1,32 +0,0 @@ -(function ($) { - -Drupal.behaviors.textarea = { - attach: function (context, settings) { - $('.form-textarea-wrapper.resizable', context).once('textarea', function () { - var staticOffset = null; - var textarea = $(this).addClass('resizable-textarea').find('textarea'); - var grippie = $('
').mousedown(startDrag); - - grippie.insertAfter(textarea); - - function startDrag(e) { - staticOffset = textarea.height() - e.pageY; - textarea.css('opacity', 0.25); - $(document).mousemove(performDrag).mouseup(endDrag); - return false; - } - - function performDrag(e) { - textarea.height(Math.max(32, staticOffset + e.pageY) + 'px'); - return false; - } - - function endDrag(e) { - $(document).unbind('mousemove', performDrag).unbind('mouseup', endDrag); - textarea.css('opacity', 1); - } - }); - } -}; - -})(jQuery); diff --git a/core/misc/vertical-tabs.js b/core/misc/vertical-tabs.js index 82dcd2c..14d0660 100644 --- a/core/misc/vertical-tabs.js +++ b/core/misc/vertical-tabs.js @@ -92,16 +92,6 @@ Drupal.verticalTab = function (settings) { } }); - // Pressing the Enter key lets you leave the tab again. - this.fieldset.keydown(function(event) { - // Enter key should not trigger inside