diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 1484e48151d..53e6bc75ef2 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -22,7 +22,6 @@ maintainer. Current component maintainers for Drupal 8: Ajax system - Alex Bronstein 'effulgentsia' -- Randy Fay 'rfay' - Earl Miles 'merlinofchaos' Base system diff --git a/core/includes/form.inc b/core/includes/form.inc index 6e0e2c66df6..c94bc62c98f 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -1108,7 +1108,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) { * A keyed array containing the current state of the form. The current * user-submitted data is stored in $form_state['values'], though * form validation functions are passed an explicit copy of the - * values for the sake of simplicity. Validation handlers can also + * values for the sake of simplicity. Validation handlers can also use * $form_state to pass information on to submit handlers. For example: * $form_state['data_for_submission'] = $data; * This technique is useful when validation requires file parsing, diff --git a/core/includes/language.inc b/core/includes/language.inc index 8e5f1ac6912..0892cc4b882 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -2,7 +2,9 @@ /** * @file - * Multiple language handling functionality. + * Language Negotiation API. + * + * @see http://drupal.org/node/1497272 */ /** @@ -11,10 +13,99 @@ const LANGUAGE_NEGOTIATION_DEFAULT = 'language-default'; /** - * Chooses a language for the given type based on language negotiation settings. + * @defgroup language_negotiation Language Negotiation API functionality + * @{ + * Functions to customize the language types and the negotiation process. + * + * The language negotiation API is based on two major concepts: + * - Language types: types of translatable data (the types of data that a user + * can view or request). + * - Language negotiation methods: functions for determining which language to + * use to present a particular piece of data to the user. + * Both language types and language negotiation methods are customizable. + * + * Drupal defines three built-in language types: + * - Interface language: The page's main language, used to present translated + * user interface elements such as titles, labels, help text, and messages. + * - Content language: The language used to present content that is available + * in more than one language (see + * @link field_language Field Language API @endlink for details). + * - URL language: The language associated with URLs. When generating a URL, + * this value will be used by url() as a default if no explicit preference is + * provided. + * Modules can define additional language types through + * hook_language_types_info(), and alter existing language type definitions + * through hook_language_types_info_alter(). + * + * Language types may be configurable or fixed. The language negotiation + * methods associated with a configurable language type can be explicitly + * set through the user interface. A fixed language type has predetermined + * (module-defined) language negotiation settings and, thus, does not appear in + * the configuration page. Here is a code snippet that makes the content + * language (which by default inherits the interface language's values) + * configurable: + * @code + * function mymodule_language_types_info_alter(&$language_types) { + * unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); + * } + * @endcode + * + * Every language type can have a different set of language negotiation methods + * assigned to it. Different language types often share the same language + * negotiation settings, but they can have independent settings if needed. If + * two language types are configured the same way, their language switcher + * configuration will be functionally identical and the same settings will act + * on both language types. + * + * Drupal defines the following built-in language negotiation methods: + * - URL: Determine the language from the URL (path prefix or domain). + * - Session: Determine the language from a request/session parameter. + * - User: Follow the user's language preference. + * - Browser: Determine the language from the browser's language settings. + * - Default language: Use the default site language. + * Language negotiation methods are simple callback functions that implement a + * particular logic to return a language code. For instance, the URL method + * searches for a valid path prefix or domain name in the current request URL. + * If a language negotiation method does not return a valid language code, the + * next method associated to the language type (based on method weight) is + * invoked. + * + * Modules can define additional language negotiation methods through + * hook_language_negotiation_info(), and alter existing methods through + * hook_language_negotiation_info_alter(). Here is an example snippet that lets + * path prefixes be ignored for administrative paths: + * @code + * function mymodule_language_negotiation_info_alter(&$negotiation_info) { + * // Replace the core function with our own function. + * module_load_include('language', 'inc', 'language.negotiation'); + * $negotiation_info[LANGUAGE_NEGOTIATION_URL]['callbacks']['negotiation'] = 'mymodule_from_url'; + * $negotiation_info[LANGUAGE_NEGOTIATION_URL]['file'] = drupal_get_path('module', 'mymodule') . '/mymodule.module'; + * } + * + * function mymodule_from_url($languages) { + * // Use the core URL language negotiation method to get a valid language + * // code. + * module_load_include('language', 'inc', 'language.negotiation'); + * $langcode = language_from_url($languages); + * + * // If we are on an administrative path, override with the default language. + * if (isset($_GET['q']) && strtok($_GET['q'], '/') == 'admin') { + * return language_default()->langcode; + * } + * return $langcode; + * } + * ?> + * @endcode + * + * For more information, see + * @link http://drupal.org/node/1497272 Language Negotiation API @endlink + */ + +/** + * Chooses a language based on language negotiation method settings. * * @param $type - * The language type key. + * The language type key to find the language for. * * @return * The negotiated language object. @@ -68,8 +159,8 @@ function language_types_info() { * Returns only the configurable language types. * * A language type maybe configurable or fixed. A fixed language type is a type - * whose negotiation values are unchangeable and defined while defining the - * language type itself. + * whose language negotiation methods are module-defined and not altered through + * the user interface. * * @param $stored * (optional) By default, retrieves values from the 'language_types' variable @@ -162,6 +253,10 @@ function language_types_set() { * * @param $type * The language type. + * + * @return + * The identifier of the first language negotiation method for the given + * language type, or the default method if none exists. */ function language_negotiation_method_get_first($type) { $negotiation = variable_get("language_negotiation_$type", array()); @@ -169,7 +264,7 @@ function language_negotiation_method_get_first($type) { } /** - * Checks if a language negotiation method is enabled for a language type. + * Checks whether a language negotiation method is enabled for a language type. * * @param $method_id * The language negotiation method ID. @@ -259,7 +354,7 @@ function language_negotiation_purge() { * @param $type * The language type. * @param $method_weights - * An array of language negotiation method weights keyed by method id. + * An array of language negotiation method weights keyed by method ID. */ function language_negotiation_set($type, $method_weights) { // Save only the necessary fields. @@ -278,8 +373,7 @@ function language_negotiation_set($type, $method_weights) { // 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. + // Check whether the method is defined and has the right type. if (isset($types[$type])) { $method_data = array(); foreach ($method_fields as $field) { @@ -328,13 +422,14 @@ function language_negotiation_info() { * Invokes a language negotiation method and caches the results. * * @param $method_id - * The language negotiation method ID. + * The language negotiation method's identifier. * @param $method - * (optional) The language negotiation method to be invoked. If not passed it - * will be explicitly loaded through language_negotiation_info(). + * (optional) An associative array of information about the method to be + * invoked (see hook_language_negotiation_info() for details). If not passed + * in, it will be loaded through language_negotiation_info(). * * @return - * The language negotiation method's return value. + * A language object representing the language chosen by the method. */ function language_negotiation_method_invoke($method_id, $method = NULL) { $results = &drupal_static(__FUNCTION__); @@ -361,10 +456,10 @@ function language_negotiation_method_invoke($method_id, $method = NULL) { $results[$method_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; } - // Since objects are resources we need to return a clone to prevent the - // language negotiation method cache to be unintentionally altered. The same - // language negotiation methods might be used with different language types - // based on configuration. + // Since objects are resources, we need to return a clone to prevent the + // language negotiation method cache from being unintentionally altered. The + // same methods might be used with different language types based on + // configuration. return !empty($results[$method_id]) ? clone($results[$method_id]) : $results[$method_id]; } @@ -381,8 +476,8 @@ function language_from_default() { /** * Split the given path into prefix and actual path. * - * Parse the given path and return the language object identified by the - * prefix and the actual path. + * Parse the given path and return the language object identified by the prefix + * and the actual path. * * @param $path * The path to split. @@ -434,3 +529,7 @@ function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) { return $fallback_candidates; } + +/** + * @} End of "language_negotiation" + */ diff --git a/core/includes/module.inc b/core/includes/module.inc index 6b4604a7b21..928abc93642 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -79,16 +79,19 @@ function module_list($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { - $drupal_static_fast['list'] = &drupal_static(__FUNCTION__ . ':list', array()); + $drupal_static_fast['list'] = &drupal_static(__FUNCTION__ . ':list'); $drupal_static_fast['sorted_list'] = &drupal_static(__FUNCTION__ . ':sorted_list'); } $list = &$drupal_static_fast['list']; $sorted_list = &$drupal_static_fast['sorted_list']; - if (empty($list) || $refresh || $fixed_list) { + if (!isset($list) || $refresh || isset($fixed_list)) { $list = array(); $sorted_list = NULL; - if ($fixed_list) { + // The fixed list may be a completely empty array, thus check for isset(). + // Calling code may use this to empty out the module list entirely. For + // example, unit tests need to ensure that no modules are invoked. + if (isset($fixed_list)) { foreach ($fixed_list as $name => $module) { drupal_get_filename('module', $name, $module['filename']); $list[$name] = $name; diff --git a/core/lib/Drupal/Core/Cache/NullBackend.php b/core/lib/Drupal/Core/Cache/NullBackend.php index c3da5d70a12..0408fc3efad 100644 --- a/core/lib/Drupal/Core/Cache/NullBackend.php +++ b/core/lib/Drupal/Core/Cache/NullBackend.php @@ -42,7 +42,7 @@ class NullBackend implements CacheBackendInterface { /** * Implements Drupal\Core\Cache\CacheBackendInterface::set(). */ - function set($cid, $data, $expire = CACHE_PERMANENT) {} + function set($cid, $data, $expire = CACHE_PERMANENT, array $tags = array()) {} /** * Implements Drupal\Core\Cache\CacheBackendInterface::delete(). @@ -74,6 +74,11 @@ class NullBackend implements CacheBackendInterface { */ function garbageCollection() {} + /** + * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags(). + */ + public function invalidateTags(array $tags) {} + /** * Implements Drupal\Core\Cache\CacheBackendInterface::isEmpty(). */ diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index dbf7c368fc2..94a23fb05fc 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -819,6 +819,9 @@ abstract class Connection extends PDO { * @param $name * Optional name of the savepoint. * + * @return Drupal\Core\Database\Transaction + * A DatabaseTransaction object. + * * @see Drupal\Core\Database\Transaction */ public function startTransaction($name = '') { diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 4859aba086f..d2d4bad5149 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -676,7 +676,7 @@ function node_type_update_nodes($old_type, $type) { * TRUE to rebuild node types. Equivalent to calling node_types_rebuild(). * * @return - * Associative array with two components: + * An object with two properties: * - names: Associative array of the names of node types, keyed by the type. * - types: Associative array of node type objects, keyed by the type. * Both of these arrays will include new types that have been defined by diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 552b4e35c4c..aa5f233790a 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -539,6 +539,181 @@ abstract class TestBase { restore_error_handler(); } + /** + * Generates a database prefix for running tests. + * + * The database prefix is used by prepareEnvironment() to setup a public files + * directory for the test to be run, which also contains the PHP error log, + * which is written to in case of a fatal error. Since that directory is based + * on the database prefix, all tests (even unit tests) need to have one, in + * order to access and read the error log. + * + * @see TestBase::prepareEnvironment() + * + * The generated database table prefix is used for the Drupal installation + * being performed for the test. It is also used as user agent HTTP header + * value by the cURL-based browser of DrupalWebTestCase, which is sent to the + * Drupal installation of the test. During early Drupal bootstrap, the user + * agent HTTP header is parsed, and if it matches, all database queries use + * the database table prefix that has been generated here. + * + * @see WebTestBase::curlInitialize() + * @see drupal_valid_test_ua() + * @see WebTestBase::setUp() + */ + protected function prepareDatabasePrefix() { + $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000); + + // As soon as the database prefix is set, the test might start to execute. + // All assertions as well as the SimpleTest batch operations are associated + // with the testId, so the database prefix has to be associated with it. + db_update('simpletest_test_id') + ->fields(array('last_prefix' => $this->databasePrefix)) + ->condition('test_id', $this->testId) + ->execute(); + } + + /** + * Changes the database connection to the prefixed one. + * + * @see WebTestBase::setUp() + */ + protected function changeDatabasePrefix() { + if (empty($this->databasePrefix)) { + $this->prepareDatabasePrefix(); + } + + // Clone the current connection and replace the current prefix. + $connection_info = Database::getConnectionInfo('default'); + Database::renameConnection('default', 'simpletest_original_default'); + foreach ($connection_info as $target => $value) { + $connection_info[$target]['prefix'] = array( + 'default' => $value['prefix']['default'] . $this->databasePrefix, + ); + } + Database::addConnectionInfo('default', 'default', $connection_info['default']); + } + + /** + * Prepares the current environment for running the test. + * + * Backups various current environment variables and resets them, so they do + * not interfere with the Drupal site installation in which tests are executed + * and can be restored in TestBase::tearDown(). + * + * Also sets up new resources for the testing environment, such as the public + * filesystem and configuration directories. + * + * @see TestBase::tearDown() + */ + protected function prepareEnvironment() { + global $user, $language_interface, $conf; + + // Backup current in-memory configuration. + $this->originalConf = $conf; + + // Backup statics and globals. + $this->originalContainer = clone drupal_container(); + $this->originalLanguage = $language_interface; + $this->originalConfigDirectory = $GLOBALS['config_directory_name']; + + // Save further contextual information. + $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->originalProfile = drupal_get_profile(); + $this->originalUser = $user; + + // Save and clean the shutdown callbacks array because it is static cached + // and will be changed by the test run. Otherwise it will contain callbacks + // from both environments and the testing environment will try to call the + // handlers defined by the original one. + $callbacks = &drupal_register_shutdown_function(); + $this->originalShutdownCallbacks = $callbacks; + $callbacks = array(); + + // Create test directory ahead of installation so fatal errors and debug + // information can be logged during installation process. + // Use temporary files directory with the same prefix as the database. + $this->public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); + $this->private_files_directory = $this->public_files_directory . '/private'; + $this->temp_files_directory = $this->private_files_directory . '/temp'; + + // Create the directories + file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY); + file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY); + $this->generatedTestFiles = FALSE; + + // Create and set a new configuration directory and signature key. + // The child site automatically adjusts the global $config_directory_name to + // a test-prefix-specific directory within the public files directory. + // @see config_get_config_directory() + $GLOBALS['config_directory_name'] = 'simpletest/' . substr($this->databasePrefix, 10) . '/config'; + $this->configFileDirectory = $this->originalFileDirectory . '/' . $GLOBALS['config_directory_name']; + file_prepare_directory($this->configFileDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + + // Log fatal errors. + ini_set('log_errors', 1); + ini_set('error_log', $this->public_files_directory . '/error.log'); + + // Set the test information for use in other parts of Drupal. + $test_info = &$GLOBALS['drupal_test_info']; + $test_info['test_run_id'] = $this->databasePrefix; + $test_info['in_child_site'] = FALSE; + } + + /** + * Deletes created files, database tables, and reverts all environment changes. + * + * This method needs to be invoked for both unit and integration tests. + * + * @see TestBase::prepareDatabasePrefix() + * @see TestBase::changeDatabasePrefix() + * @see TestBase::prepareEnvironment() + */ + protected function tearDown() { + global $user, $language_interface, $conf; + + // In case a fatal error occurred that was not in the test process read the + // log to pick up any fatal errors. + simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE); + + $emailCount = count(variable_get('drupal_test_email_collector', array())); + if ($emailCount) { + $message = format_plural($emailCount, '1 e-mail was sent during this test.', '@count e-mails were sent during this test.'); + $this->pass($message, t('E-mail')); + } + + // Delete temporary files directory. + file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); + + // Restore original database connection. + Database::removeConnection('default'); + Database::renameConnection('simpletest_original_default', 'default'); + + // Reset all static variables. + drupal_static_reset(); + + // Restore has_run state. + $has_run = &drupal_static('module_load_all'); + $has_run = TRUE; + + // Restore original in-memory configuration. + $conf = $this->originalConf; + + // Restore original statics and globals. + drupal_container($this->originalContainer); + $language_interface = $this->originalLanguage; + $GLOBALS['config_directory_name'] = $this->originalConfigDirectory; + + // Restore original shutdown callbacks. + $callbacks = &drupal_register_shutdown_function(); + $callbacks = $this->originalShutdownCallbacks; + + // Restore original user session. + $user = $this->originalUser; + drupal_save_session(TRUE); + } + /** * Handle errors during test runs. * diff --git a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php index 5b5850be228..b5314cecb6d 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php @@ -38,55 +38,40 @@ abstract class UnitTestBase extends TestBase { protected function setUp() { global $conf; - // Store necessary current values before switching to the test environment. - $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + // Create the database prefix for this test. + $this->prepareDatabasePrefix(); - // Reset all statics so that test is performed with a clean environment. + // Prepare the environment for running tests. + $this->prepareEnvironment(); + $this->originalThemeRegistry = theme_get_registry(FALSE); + + // Reset all statics and variables to perform tests in a clean environment. + $conf = array(); drupal_static_reset(); - // Generate temporary prefixed database to ensure that tests have a clean starting point. - $this->databasePrefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); + // Empty out module list. + module_list(TRUE, FALSE, FALSE, array()); + // Prevent module_load_all() from attempting to refresh it. + $has_run = &drupal_static('module_load_all'); + $has_run = TRUE; - // Create test directory. - $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); - file_prepare_directory($public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); - $conf['file_public_path'] = $public_files_directory; + // Re-implant theme registry. + // Required for l() and other functions to work correctly and not trigger + // database lookups. + $theme_get_registry = &drupal_static('theme_get_registry'); + $theme_get_registry[FALSE] = $this->originalThemeRegistry; - // Clone the current connection and replace the current prefix. - $connection_info = Database::getConnectionInfo('default'); - Database::renameConnection('default', 'simpletest_original_default'); - foreach ($connection_info as $target => $value) { - $connection_info[$target]['prefix'] = array( - 'default' => $value['prefix']['default'] . $this->databasePrefix, - ); - } - Database::addConnectionInfo('default', 'default', $connection_info['default']); + $conf['file_public_path'] = $this->public_files_directory; - // Set user agent to be consistent with web test case. + // Change the database prefix. + // All static variables need to be reset before the database prefix is + // changed, since Drupal\Core\Utility\CacheArray implementations attempt to + // write back to persistent caches when they are destructed. + $this->changeDatabasePrefix(); + + // Set user agent to be consistent with WebTestBase. $_SERVER['HTTP_USER_AGENT'] = $this->databasePrefix; - // If locale is enabled then t() will try to access the database and - // subsequently will fail as the database is not accessible. - $module_list = module_list(); - if (isset($module_list['locale'])) { - $this->originalModuleList = $module_list; - unset($module_list['locale']); - module_list(TRUE, FALSE, FALSE, $module_list); - } $this->setup = TRUE; } - - protected function tearDown() { - global $conf; - - // Get back to the original connection. - Database::removeConnection('default'); - Database::renameConnection('simpletest_original_default', 'default'); - - $conf['file_public_path'] = $this->originalFileDirectory; - // Restore modules if necessary. - if (isset($this->originalModuleList)) { - module_list(TRUE, FALSE, FALSE, $this->originalModuleList); - } - } } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 14e218af8c6..3bb1e0ce77e 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -534,117 +534,6 @@ abstract class WebTestBase extends TestBase { } } - /** - * Generates a database prefix for running tests. - * - * The generated database table prefix is used for the Drupal installation - * being performed for the test. It is also used as user agent HTTP header - * value by the cURL-based browser of Drupal\simpletest\WebTestBase, which is sent - * to the Drupal installation of the test. During early Drupal bootstrap, the - * user agent HTTP header is parsed, and if it matches, all database queries - * use the database table prefix that has been generated here. - * - * @see Drupal\simpletest\WebTestBase::curlInitialize() - * @see drupal_valid_test_ua() - * @see Drupal\simpletest\WebTestBase::setUp() - */ - protected function prepareDatabasePrefix() { - $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000); - - // As soon as the database prefix is set, the test might start to execute. - // All assertions as well as the SimpleTest batch operations are associated - // with the testId, so the database prefix has to be associated with it. - db_update('simpletest_test_id') - ->fields(array('last_prefix' => $this->databasePrefix)) - ->condition('test_id', $this->testId) - ->execute(); - } - - /** - * Changes the database connection to the prefixed one. - * - * @see Drupal\simpletest\WebTestBase::setUp() - */ - protected function changeDatabasePrefix() { - if (empty($this->databasePrefix)) { - $this->prepareDatabasePrefix(); - } - - // Clone the current connection and replace the current prefix. - $connection_info = Database::getConnectionInfo('default'); - Database::renameConnection('default', 'simpletest_original_default'); - foreach ($connection_info as $target => $value) { - $connection_info[$target]['prefix'] = array( - 'default' => $value['prefix']['default'] . $this->databasePrefix, - ); - } - Database::addConnectionInfo('default', 'default', $connection_info['default']); - } - - /** - * Prepares the current environment for running the test. - * - * Backups various current environment variables and resets them, so they do - * not interfere with the Drupal site installation in which tests are executed - * and can be restored in tearDown(). - * - * Also sets up new resources for the testing environment, such as the public - * filesystem and configuration directories. - * - * @see Drupal\simpletest\WebTestBase::setUp() - * @see Drupal\simpletest\WebTestBase::tearDown() - */ - protected function prepareEnvironment() { - global $user, $language_interface, $conf; - - // Store necessary current values before switching to prefixed database. - $this->originalContainer = clone drupal_container(); - $this->originalLanguage = $language_interface; - $this->originalLanguageDefault = variable_get('language_default'); - $this->originalConfigDirectory = $GLOBALS['config_directory_name']; - $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); - $this->originalProfile = drupal_get_profile(); - $this->originalUser = $user; - - // Save and clean the shutdown callbacks array because it is static cached - // and will be changed by the test run. Otherwise it will contain callbacks - // from both environments and the testing environment will try to call the - // handlers defined by the original one. - $callbacks = &drupal_register_shutdown_function(); - $this->originalShutdownCallbacks = $callbacks; - $callbacks = array(); - - // Create test directory ahead of installation so fatal errors and debug - // information can be logged during installation process. - // Use temporary files directory with the same prefix as the database. - $this->public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); - $this->private_files_directory = $this->public_files_directory . '/private'; - $this->temp_files_directory = $this->private_files_directory . '/temp'; - - // Create the directories - file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); - file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY); - file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY); - $this->generatedTestFiles = FALSE; - - // Create and set a new configuration directory and signature key. - // The child site automatically adjusts the global $config_directory_name to - // a test-prefix-specific directory within the public files directory. - // @see config_get_config_directory() - $GLOBALS['config_directory_name'] = 'simpletest/' . substr($this->databasePrefix, 10) . '/config'; - $this->configFileDirectory = $this->originalFileDirectory . '/' . $GLOBALS['config_directory_name']; - file_prepare_directory($this->configFileDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); - - // Log fatal errors. - ini_set('log_errors', 1); - ini_set('error_log', $this->public_files_directory . '/error.log'); - - // Set the test information for use in other parts of Drupal. - $test_info = &$GLOBALS['drupal_test_info']; - $test_info['test_run_id'] = $this->databasePrefix; - $test_info['in_child_site'] = FALSE; - } - /** * Sets up a Drupal site for running functional and integration tests. * @@ -841,21 +730,6 @@ abstract class WebTestBase extends TestBase { * and reset the database prefix. */ protected function tearDown() { - global $user, $language_interface; - - // In case a fatal error occurred that was not in the test process read the - // log to pick up any fatal errors. - simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE); - - $emailCount = count(variable_get('drupal_test_email_collector', array())); - if ($emailCount) { - $message = format_plural($emailCount, '1 e-mail was sent during this test.', '@count e-mails were sent during this test.'); - $this->pass($message, t('E-mail')); - } - - // Delete temporary files directory. - file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); - // Remove all prefixed tables. $connection_info = Database::getConnectionInfo('default'); $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%'); @@ -872,21 +746,7 @@ abstract class WebTestBase extends TestBase { $this->fail('Failed to drop all prefixed tables.'); } - // Get back to the original connection. - Database::removeConnection('default'); - Database::renameConnection('simpletest_original_default', 'default'); - - // Restore the original dependency injection container. - drupal_container($this->originalContainer); - - // Restore original shutdown callbacks array to prevent original - // environment of calling handlers from test run. - $callbacks = &drupal_register_shutdown_function(); - $callbacks = $this->originalShutdownCallbacks; - - // Return the user to the original one. - $user = $this->originalUser; - drupal_save_session(TRUE); + parent::tearDown(); // Ensure that internal logged in variable and cURL options are reset. $this->loggedInUser = FALSE; @@ -903,18 +763,6 @@ abstract class WebTestBase extends TestBase { // Rebuild caches. $this->refreshVariables(); - // Reset public files directory. - $GLOBALS['conf']['file_public_path'] = $this->originalFileDirectory; - - // Reset configuration globals. - $GLOBALS['config_directory_name'] = $this->originalConfigDirectory; - - // Reset language. - $language_interface = $this->originalLanguage; - if ($this->originalLanguageDefault) { - $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; - } - // Close the CURL handler. $this->curlClose(); } diff --git a/core/modules/system/language.api.php b/core/modules/system/language.api.php index 9f28619a2d3..f1cd519d08c 100644 --- a/core/modules/system/language.api.php +++ b/core/modules/system/language.api.php @@ -62,22 +62,22 @@ function hook_language_switch_links_alter(array &$links, $type, $path) { } /** - * Allow modules to define their own language types. + * Define language types. * * @return - * An associative array of language type definitions. + * An associative array of language type definitions. The keys are the + * identifiers, which are also used as names for global variables representing + * the types in the bootstrap phase. The values are associative arrays that + * may contain the following elements: + * - name: The human-readable language type identifier. + * - description: A description of the language type. + * - fixed: A fixed array of language negotiation method identifiers to use to + * initialize this language. Defining this key makes the language type + * non-configurable, so it will always use the specified methods in the + * given priority order. Omit to make the language type configurable. * - * Each language type has an identifier key which is used as the name for the - * global variable corresponding to the language type in the bootstrap phase. - * - * The language type definition is an associative array that may contain the - * following key-value pairs: - * - "name": The human-readable language type identifier. - * - "description": A description of the language type. - * - "fixed": A fixed array of language negotiation method identifiers to use - * to initialize this language. Defining this key makes the language type - * non-configurable and will always use the specified methods in the given - * priority order. + * @see hook_language_types_info_alter() + * @ingroup language_negotiation */ function hook_language_types_info() { return array( @@ -94,10 +94,11 @@ function hook_language_types_info() { /** * Perform alterations on language types. * - * @see hook_language_types_info(). - * * @param $language_types * Array of language type definitions. + * + * @see hook_language_types_info() + * @ingroup language_negotiation */ function hook_language_types_info_alter(array &$language_types) { if (isset($language_types['custom_language_type'])) { @@ -106,31 +107,35 @@ function hook_language_types_info_alter(array &$language_types) { } /** - * Allow modules to define their own language negotiation methods. + * Define language negotiation methods. * * @return - * An array of language negotiation method definitions. Each method has an - * identifier key. The language negotiation method definition is an indexed - * array that may contain the following key-value pairs: - * - "types": An array of allowed language types. If a language negotiation + * An associative array of language negotiation method definitions. The keys + * are method identifiers, and the values are associative arrays definining + * each method, with the following elements: + * - types: An array of allowed language types. If a language negotiation * method does not specify which language types it should be used with, it * will be available for all the configurable language types. - * - "callbacks": An array of functions that will be called to perform various - * tasks. Possible key-value pairs are: - * - "negotiation": Required. The callback that will determine the language - * value. - * - "language_switch": The callback that will determine the language - * switch links associated to the current language method. - * - "url_rewrite": The callback that will provide URL rewriting. - * - "file": A file that will be included before the callback is invoked; this - * allows callback functions to be in separate files. - * - "weight": The default weight the language negotiation method has. - * - "name": A human-readable identifier. - * - "description": A description of the language negotiation method. - * - "config": An internal path pointing to the language negotiation method - * configuration page. - * - "cache": The value Drupal's page cache should be set to for the current - * language negotiation method to be invoked. + * - callbacks: An associative array of functions that will be called to + * perform various tasks. Possible elements are: + * - negotiation: (required) Name of the callback function that determines + * the language value. + * - language_switch: (optional) Name of the callback function that + * determines links for a language switcher block associated with this + * method. See language_switcher_url() for an example. + * - url_rewrite: (optional) Name of the callback function that provides URL + * rewriting, if needed by this method. + * - file: The file where callback functions are defined (this file will be + * included before the callbacks are invoked). + * - weight: The default weight of the method. + * - name: The translated human-readable name for the method. + * - description: A translated longer description of the method. + * - config: An internal path pointing to the method's configuration page. + * - cache: The value Drupal's page cache should be set to for the current + * method to be invoked. + * + * @see hook_language_negotiation_info_alter() + * @ingroup language_negotiation */ function hook_language_negotiation_info() { return array( @@ -155,6 +160,9 @@ function hook_language_negotiation_info() { * * @param $negotiation_info * Array of language negotiation method definitions. + * + * @see hook_language_negotiation_info() + * @ingroup language_negotiation */ function hook_language_negotiation_info_alter(array &$negotiation_info) { if (isset($negotiation_info['custom_language_method'])) { diff --git a/core/modules/system/tests/actions.test b/core/modules/system/lib/Drupal/system/Tests/Actions/ConfigurationTest.php similarity index 56% rename from core/modules/system/tests/actions.test rename to core/modules/system/lib/Drupal/system/Tests/Actions/ConfigurationTest.php index 469b16e13d5..1a31fb4ff1c 100644 --- a/core/modules/system/tests/actions.test +++ b/core/modules/system/lib/Drupal/system/Tests/Actions/ConfigurationTest.php @@ -1,8 +1,18 @@ 'Actions configuration', @@ -64,66 +74,3 @@ class ActionsConfigurationTestCase extends WebTestBase { $this->assertFalse($exists, t('Make sure the action is gone from the database after being deleted.')); } } - -/** - * Test actions executing in a potential loop, and make sure they abort properly. - */ -class ActionLoopTestCase extends WebTestBase { - protected $aid; - - public static function getInfo() { - return array( - 'name' => 'Actions executing in a potentially infinite loop', - 'description' => 'Tests actions executing in a loop, and makes sure they abort properly.', - 'group' => 'Actions', - ); - } - - function setUp() { - parent::setUp('dblog', 'actions_loop_test'); - } - - /** - * Set up a loop with 3 - 12 recursions, and see if it aborts properly. - */ - function testActionLoop() { - $user = $this->drupalCreateUser(array('administer actions')); - $this->drupalLogin($user); - - $info = actions_loop_test_action_info(); - $this->aid = actions_save('actions_loop_test_log', $info['actions_loop_test_log']['type'], array(), $info['actions_loop_test_log']['label']); - - // Delete any existing watchdog messages to clear the plethora of - // "Action added" messages from when Drupal was installed. - db_delete('watchdog')->execute(); - // To prevent this test from failing when xdebug is enabled, the maximum - // recursion level should be kept low enough to prevent the xdebug - // infinite recursion protection mechanism from aborting the request. - // See http://drupal.org/node/587634. - variable_set('actions_max_stack', mt_rand(3, 12)); - $this->triggerActions(); - } - - /** - * Create an infinite loop by causing a watchdog message to be set, - * which causes the actions to be triggered again, up to actions_max_stack - * times. - */ - protected function triggerActions() { - $this->drupalGet('', array('query' => array('trigger_actions_on_watchdog' => $this->aid))); - $expected = array(); - $expected[] = 'Triggering action loop'; - for ($i = 1; $i <= variable_get('actions_max_stack', 35); $i++) { - $expected[] = "Test log #$i"; - } - $expected[] = 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.'; - - $result = db_query("SELECT message FROM {watchdog} WHERE type = 'actions_loop_test' OR type = 'actions' ORDER BY wid"); - $loop_started = FALSE; - foreach ($result as $row) { - $expected_message = array_shift($expected); - $this->assertEqual($row->message, $expected_message, t('Expected message %expected, got %message.', array('%expected' => $expected_message, '%message' => $row->message))); - } - $this->assertTrue(empty($expected), t('All expected messages found.')); - } -} diff --git a/core/modules/system/lib/Drupal/system/Tests/Actions/LoopTest.php b/core/modules/system/lib/Drupal/system/Tests/Actions/LoopTest.php new file mode 100644 index 00000000000..97667c0cea5 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Actions/LoopTest.php @@ -0,0 +1,73 @@ + 'Actions executing in a potentially infinite loop', + 'description' => 'Tests actions executing in a loop, and makes sure they abort properly.', + 'group' => 'Actions', + ); + } + + function setUp() { + parent::setUp('dblog', 'actions_loop_test'); + } + + /** + * Set up a loop with 3 - 12 recursions, and see if it aborts properly. + */ + function testActionLoop() { + $user = $this->drupalCreateUser(array('administer actions')); + $this->drupalLogin($user); + + $info = actions_loop_test_action_info(); + $this->aid = actions_save('actions_loop_test_log', $info['actions_loop_test_log']['type'], array(), $info['actions_loop_test_log']['label']); + + // Delete any existing watchdog messages to clear the plethora of + // "Action added" messages from when Drupal was installed. + db_delete('watchdog')->execute(); + // To prevent this test from failing when xdebug is enabled, the maximum + // recursion level should be kept low enough to prevent the xdebug + // infinite recursion protection mechanism from aborting the request. + // See http://drupal.org/node/587634. + variable_set('actions_max_stack', mt_rand(3, 12)); + $this->triggerActions(); + } + + /** + * Create an infinite loop by causing a watchdog message to be set, + * which causes the actions to be triggered again, up to actions_max_stack + * times. + */ + protected function triggerActions() { + $this->drupalGet('', array('query' => array('trigger_actions_on_watchdog' => $this->aid))); + $expected = array(); + $expected[] = 'Triggering action loop'; + for ($i = 1; $i <= variable_get('actions_max_stack', 35); $i++) { + $expected[] = "Test log #$i"; + } + $expected[] = 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.'; + + $result = db_query("SELECT message FROM {watchdog} WHERE type = 'actions_loop_test' OR type = 'actions' ORDER BY wid"); + $loop_started = FALSE; + foreach ($result as $row) { + $expected_message = array_shift($expected); + $this->assertEqual($row->message, $expected_message, t('Expected message %expected, got %message.', array('%expected' => $expected_message, '%message' => $row->message))); + } + $this->assertTrue(empty($expected), t('All expected messages found.')); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/NullBackendTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/NullBackendTest.php new file mode 100644 index 00000000000..5a42e5defb6 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/NullBackendTest.php @@ -0,0 +1,38 @@ + 'Cache NullBackend test', + 'description' => 'Tests the cache NullBackend.', + 'group' => 'Cache', + ); + } + + /** + * Tests that the NullBackend does not actually store variables. + */ + function testNullBackend() { + $null_cache = new NullBackend('test'); + + $key = $this->randomName(); + $value = $this->randomName(); + + $null_cache->set($key, $value); + $this->assertTrue($null_cache->isEmpty()); + $this->assertFalse($null_cache->get($key)); + } +} diff --git a/core/modules/system/tests/mail.test b/core/modules/system/lib/Drupal/system/Tests/Common/HtmlToTextTest.php similarity index 86% rename from core/modules/system/tests/mail.test rename to core/modules/system/lib/Drupal/system/Tests/Common/HtmlToTextTest.php index 38c6dc8c0af..a30c3c106dc 100644 --- a/core/modules/system/tests/mail.test +++ b/core/modules/system/lib/Drupal/system/Tests/Common/HtmlToTextTest.php @@ -2,97 +2,17 @@ /** * @file - * Test the Drupal mailing system. + * Definition of Drupal\system\Tests\Common\HtmlToTextTest. */ -use Drupal\Core\Mail\MailInterface; +namespace Drupal\system\Tests\Common; + use Drupal\simpletest\WebTestBase; /** - * Defines a mail class used for testing. + * Tests for drupal_html_to_text(). */ -class MailTestCase extends WebTestBase implements MailInterface { - /** - * The most recent message that was sent through the test case. - * - * We take advantage here of the fact that static variables are shared among - * all instance of the same class. - */ - private static $sent_message; - - public static function getInfo() { - return array( - 'name' => 'Mail system', - 'description' => 'Performs tests on the pluggable mailing framework.', - 'group' => 'Mail', - ); - } - - function setUp() { - parent::setUp(array('simpletest')); - - // Set MailTestCase (i.e. this class) as the SMTP library - variable_set('mail_system', array('default-system' => 'MailTestCase')); - } - - /** - * Assert that the pluggable mail system is functional. - */ - public function testPluggableFramework() { - global $language_interface; - - // Use MailTestCase for sending a message. - $message = drupal_mail('simpletest', 'mail_test', 'testing@example.com', $language_interface); - - // Assert whether the message was sent through the send function. - $this->assertEqual(self::$sent_message['to'], 'testing@example.com', t('Pluggable mail system is extendable.')); - } - - /** - * Test that message sending may be canceled. - * - * @see simpletest_mail_alter() - */ - public function testCancelMessage() { - global $language; - - // Reset the class variable holding a copy of the last sent message. - self::$sent_message = NULL; - - // Send a test message that simpletest_mail_alter should cancel. - $message = drupal_mail('simpletest', 'cancel_test', 'cancel@example.com', $language); - - // Assert that the message was not actually sent. - $this->assertNull(self::$sent_message, 'Message was canceled.'); - } - - /** - * Concatenate and wrap the e-mail body for plain-text mails. - * - * @see Drupal\Core\Mail\PhpMail - */ - public function format(array $message) { - // Join the body array into one string. - $message['body'] = implode("\n\n", $message['body']); - // Convert any HTML to plain-text. - $message['body'] = drupal_html_to_text($message['body']); - // Wrap the mail body for sending. - $message['body'] = drupal_wrap_mail($message['body']); - return $message; - } - - /** - * Send function that is called through the mail system. - */ - public function mail(array $message) { - self::$sent_message = $message; - } -} - -/** - * Unit tests for drupal_html_to_text(). - */ -class DrupalHtmlToTextTestCase extends WebTestBase { +class HtmlToTextTest extends WebTestBase { public static function getInfo() { return array( 'name' => 'HTML to text conversion', diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/MailTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/MailTest.php new file mode 100644 index 00000000000..cf33f487e77 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Common/MailTest.php @@ -0,0 +1,92 @@ + 'Mail system', + 'description' => 'Performs tests on the pluggable mailing framework.', + 'group' => 'Mail', + ); + } + + function setUp() { + parent::setUp(array('simpletest')); + + // Set MailTestCase (i.e. this class) as the SMTP library + variable_set('mail_system', array('default-system' => 'Drupal\system\Tests\Common\MailTest')); + } + + /** + * Assert that the pluggable mail system is functional. + */ + public function testPluggableFramework() { + global $language_interface; + + // Use MailTestCase for sending a message. + $message = drupal_mail('simpletest', 'mail_test', 'testing@example.com', $language_interface); + + // Assert whether the message was sent through the send function. + $this->assertEqual(self::$sent_message['to'], 'testing@example.com', t('Pluggable mail system is extendable.')); + } + + /** + * Test that message sending may be canceled. + * + * @see simpletest_mail_alter() + */ + public function testCancelMessage() { + global $language; + + // Reset the class variable holding a copy of the last sent message. + self::$sent_message = NULL; + + // Send a test message that simpletest_mail_alter should cancel. + $message = drupal_mail('simpletest', 'cancel_test', 'cancel@example.com', $language); + + // Assert that the message was not actually sent. + $this->assertNull(self::$sent_message, 'Message was canceled.'); + } + + /** + * Concatenate and wrap the e-mail body for plain-text mails. + * + * @see Drupal\Core\Mail\PhpMail + */ + public function format(array $message) { + // Join the body array into one string. + $message['body'] = implode("\n\n", $message['body']); + // Convert any HTML to plain-text. + $message['body'] = drupal_html_to_text($message['body']); + // Wrap the mail body for sending. + $message['body'] = drupal_wrap_mail($message['body']); + return $message; + } + + /** + * Send function that is called through the mail system. + */ + public function mail(array $message) { + self::$sent_message = $message; + } +} diff --git a/core/modules/system/system.info b/core/modules/system/system.info index 8a11985572e..1b87b854f23 100644 --- a/core/modules/system/system.info +++ b/core/modules/system/system.info @@ -8,7 +8,6 @@ required = TRUE configure = admin/config/system ; Tests in tests directory. -files[] = tests/actions.test files[] = tests/ajax.test files[] = tests/batch.test files[] = tests/bootstrap.test @@ -22,7 +21,6 @@ files[] = tests/form.test files[] = tests/image.test files[] = tests/installer.test files[] = tests/lock.test -files[] = tests/mail.test files[] = tests/menu.test files[] = tests/module.test files[] = tests/pager.test