diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index bf59d6b..1d590c2 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -829,25 +829,21 @@ function drupal_settings_initialize() { * The filename of the item if it is to be set explicitly rather * than by consulting the database. * @param bool $trigger_error - * Whether to trigger an error when a file is missing / moved. Defaults to - * TRUE, but we may want to set this to FALSE if we merely want to check - * whether an item is installed. + * Whether to trigger an error when a file is missing or has unexpectedly + * moved. This defaults to TRUE, but can be set to FALSE by calling code that + * merely wants to check whether an item exists in the filesystem. * * @return * The filename of the requested item or NULL if the item is not found. */ function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) { - // The location of files will not change during the request, so do not use - // drupal_static(). // The $files static variable will hold the locations of all requested files. // We can be sure that any file listed in this static variable actually // exists as all additions have gone through a file_exists() check. + // The location of files will not change during the request, so do not use + // drupal_static(). static $files = array(); - // This flag may hold a string that indicates where the filename was retrieved - // from. - $filename_retrieved_from = ''; - // Profiles are a special case: they have a fixed location and naming. if ($type == 'profile') { $profile_filename = "profiles/$name/$name.profile"; @@ -860,34 +856,26 @@ function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TR if (!empty($filename) && file_exists($filename)) { // Prime the static cache with the provided filename. $files[$type][$name] = $filename; - $filename_retrieved_from = 'priming'; } elseif (isset($files[$type][$name])) { // This item had already been found earlier in the request, either through - // priming of the static cache (i.e. in system_list()), through a lookup - // in the {system} table, or through a file scan (cached or not). - // Do nothing. + // priming of the static cache (for example, in system_list()), through a + // lookup in the {system} table, or through a file scan (cached or not). Do + // nothing. } else { - // Look for the filename listed in the {system} table. - // Verify that we have an active database connection, before querying - // the database. This is required because this function is called both - // before we have a database connection (i.e. during installation) and - // when a database connection fails. + // Look for the filename listed in the {system} table. Verify that we have + // an active database connection before doing so, since this function is + // called both before we have a database connection (i.e. during + // installation) and when a database connection fails. + $database_unavailable = TRUE; try { - $system_query_success = FALSE; if (function_exists('db_query')) { - $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array( - ':name' => $name, - ':type' => $type - ))->fetchField(); - if ($file !== FALSE) { - if (file_exists(DRUPAL_ROOT . '/' . $file)) { - $files[$type][$name] = $file; - $filename_retrieved_from = 'system_table'; - } + $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField(); + if ($file !== FALSE && file_exists(DRUPAL_ROOT . '/' . $file)) { + $files[$type][$name] = $file; } - $system_query_success = TRUE; + $database_unavailable = FALSE; } } catch (Exception $e) { @@ -896,171 +884,125 @@ function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TR // flush while $conf['page_cache_without_database'] = TRUE and // $conf['page_cache_invoke_hooks'] = TRUE. We have a fallback for these // cases so we hide the error completely. - // We also hide the trigger_error() "moved or newly introduced" warning - // in this case as we can't be sure if it's correct. - } - if (!$system_query_success) { - // We can't tell if the error message will be valid if the query to the - // {system} table failed, so skip triggering of errors. - $trigger_error = FALSE; } // Fall back to searching the filesystem if the database could not find the // file or the file does not exist at the path returned by the database. if (!isset($files[$type][$name])) { - $files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error); + $files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable); } } if (isset($files[$type][$name])) { - // We found a file. - // Get the static file scan list. This list may be incomplete as there - // may be further files listed in persistent cache, but we skip loading that - // as a performance optimization, as the persistent cache record will - // be cleared on every system_rebuild_module_data() anyway. - $file_scans = &drupal_static('_drupal_get_filename_fallback'); - // If this file had previously been marked as missing, clean up the entry - // from the file scan cache. - $missing_reappeared = isset($file_scans[$type][$name]) && $file_scans[$type][$name] === FALSE; - // If this file had previously been marked as moved, and reappeared - // at the location listed in the system table or in the primed static, - // clean up the entry from the file scan cache. - $moved_reappeared = isset($file_scans[$type][$name]) && is_string($file_scans[$type][$name]) && ($filename_retrieved_from == 'system_table' || $filename_retrieved_from == 'priming'); - if ($missing_reappeared || $moved_reappeared) { - $file_scans[$type][$name] = NULL; - $file_scans['#write_cache'] = TRUE; - } return $files[$type][$name]; } } /** - * Helper function for drupal_get_filename(). + * Performs a cached file system scan as a fallback when searching for a file. * * This function looks for the requested file by triggering a file scan, * caching the new location if the file has moved and caching the miss - * if the file is missing. If a file had been marked as missing or moved - * in a previous file scan, no new file scan will be performed. + * if the file is missing. If a file had been marked as missing in a previous + * file scan, or if it has been marked as moved and is still in the last known + * location, no new file scan will be performed. * * @param string $type * The type of the item (theme, theme_engine, module, profile). * @param string $name * The name of the item for which the filename is requested. * @param bool $trigger_error - * Whether to create an error when a file is missing or moved. + * Whether to trigger an error when a file is missing or has unexpectedly + * moved. + * @param bool $database_unavailable + * Whether this function is being called because the Drupal database could + * not be queried for the file's location. * - * @return string + * @return * The filename of the requested item or NULL if the item is not found. + * + * @see drupal_get_filename() */ -function _drupal_get_filename_fallback($type, $name, $trigger_error) { - // This static variable will hold all missing and moved files, in order - // to prevent unnecessary file scans. It is an associative array with as - // keys the type and name of the item, and as value: - // - the boolean FALSE if the item is missing - // - a string with location found in a file scan if the module/theme has moved - // from the location listed in the {system} table. - $file_scans = &drupal_static('_drupal_get_filename_fallback'); - if (!isset($file_scans)) { - $file_scans = array(); - } - // Get the list of files marked as missing and moved during earlier file scans - // from the persistent cache, if available. - if (!isset($file_scans['#cache_merge_done'])) { - try { - if (function_exists('cache_get')) { - $cache = cache_get('_drupal_get_filename_fallback', 'cache_bootstrap'); - if (!empty($cache->data)) { - // Merge the changes to the file scan cache done in the current - // request (including the setting of previously set records to NULL) - // with the values that had been stored in the persistent cache. - $file_scans = drupal_array_merge_deep($cache->data, $file_scans); - // Set a flag so we remember that we've done a merge with the values - // stored in the persistent cache. - } - $file_scans['#cache_merge_done'] = TRUE; - } - } - catch (Exception $e) { - // Hide the error. - } - } +function _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable) { + $file_scans = &_drupal_file_scan_cache(); + $filename = NULL; - // Check whether this file had moved from the path listed in the {system} - // table during a previous file scan. - if (isset($file_scans[$type][$name]) && is_string($file_scans[$type][$name]) && file_exists($file_scans[$type][$name])) { - // The file exists at the location cached in a previous file scan. - $filename_from_file_scan = $file_scans[$type][$name]; + // If the cache indicates that the item is missing, or we can verify that the + // item exists in the location the cache says it exists in, use that. + if (isset($file_scans[$type][$name]) && ($file_scans[$type][$name] === FALSE || file_exists($file_scans[$type][$name]))) { + $filename = $file_scans[$type][$name]; } - // Unless the module had been marked as missing, perform a file scan. - elseif (!(isset($file_scans[$type][$name]) && $file_scans[$type][$name] === FALSE)) { - $filename_from_file_scan = _drupal_get_filename_perform_file_scan($type, $name); + // Otherwise, perform a new file scan to find the item. + else { + $filename = _drupal_get_filename_perform_file_scan($type, $name); + // Update the static cache, and mark the persistent cache for updating at + // the end of the page request. See drupal_file_scan_write_cache(). + $file_scans[$type][$name] = $filename; + $file_scans['#write_cache'] = TRUE; } - if (isset($filename_from_file_scan)) { - // The file has either moved from the location in the {system} table - // or is not yet listed in the {system} table. - // Make sure our change to the file scan cache will be written to the - // persistent cache. - if (!isset($file_scans[$type][$name]) || isset($file_scans[$type][$name]) && $file_scans[$type][$name] != $filename_from_file_scan) { - $file_scans[$type][$name] = $filename_from_file_scan; - if (!$trigger_error) { - // If we skip error triggering, do not write the found file location to - // persistent cache. - $file_scans['#write_cache'] = TRUE; - } + // If requested, trigger a user-level warning about the missing or + // unexpectedly moved file. If the database was unavailable, do not trigger a + // warning in the latter case, though, since if the {system} table could not + // be queried there is no way to know if the location found here was + // "unexpected" or not. + if ($trigger_error) { + $error_type = $filename === FALSE ? 'missing' : 'moved'; + if ($error_type == 'missing' || !$database_unavailable) { + _drupal_get_filename_fallback_trigger_error($type, $name, $error_type); } - // Create a user-level warning about the moved / recently introduced file. - if ($trigger_error) { - _drupal_get_filename_fallback_trigger_error($type, $name, 'moved'); - } - return $filename_from_file_scan; } - else { - // Make sure our change to the file scan cache will be written to - // the persistent cache. - if (!(isset($file_scans[$type][$name]) && $file_scans[$type][$name] === FALSE)) { - $file_scans['#write_cache'] = TRUE; - } - // Mark the file as missing. - $file_scans[$type][$name] = FALSE; - // Create a user-level warning about the missing file. - if ($trigger_error) { - _drupal_get_filename_fallback_trigger_error($type, $name, 'missing'); - } + + // The cache stores FALSE for files that aren't found (to be able to + // distinguish them from files that have not yet been searched for), but + // drupal_get_filename() expects NULL for these instead, so convert to NULL + // before returning. + if ($filename === FALSE) { + $filename = NULL; } + return $filename; } /** - * Helper function for _drupal_get_filename_fallback(). - * - * Creates a user-level warning when a missing or moved file is detected in - * _drupal_get_filename_fallback(). + * Returns the current list of cached file system scan results. * - * @param $type - * The type of the item (theme, theme_engine, module, profile). - * @param $name - * The name of the item for which the filename is requested. - * @param $error_type - * The type of the error ('missing' or 'moved'). - */ -function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) { - // Make sure we only show any missing / moved file error only once per - // request. - static $errors_triggered = array(); - if (empty($errors_triggered[$type][$name][$error_type])) { - if ($error_type == 'missing') { - trigger_error(format_string('The following @type is missing from the file system: %name. In order to fix this, put the file back in its original location or uninstall the module. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + * @return + * An associative array tracking the most recent file scan results for all + * files that have had scans performed. The keys are the type and name of the + * item that was searched for, and the values can be either: + * - Boolean FALSE if the item was not found in the file system. + * - A string pointing to the location where the item was found. + */ +function &_drupal_file_scan_cache() { + $file_scans = &drupal_static(__FUNCTION__, array()); + + // The file scan results are stored in a persistent cache (in addition to the + // static cache) but because this function can be called before the + // persistent cache is available, we must merge any items that were found + // earlier in the page request into the results from the persistent cache. + if (!isset($file_scans['#cache_merge_done'])) { + try { + if (function_exists('cache_get')) { + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + if (!empty($cache->data)) { + // File scan results from the current request should take precedence + // over the results from the persistent cache, since they are newer. + $file_scans = drupal_array_merge_deep($cache->data, $file_scans); + } + // Set a flag to indicate that the persistent cache does not need to be + // merged again. + $file_scans['#cache_merge_done'] = TRUE; + } } - elseif ($error_type == 'moved') { - trigger_error(format_string('The following @type has moved or has recently appeared within the file system: %name. In order to fix this, clear caches or put the file back in its original location. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + catch (Exception $e) { + // Hide the error. } - $errors_triggered[$type][$name][$error_type] = TRUE; } + + return $file_scans; } /** - * Helper function for drupal_get_filename(). - * - * Performs a file scan. + * Performs a file system scan to search for a system resource. * * @param $type * The type of the item (theme, theme_engine, module, profile). @@ -1068,9 +1010,14 @@ function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) * The name of the item for which the filename is requested. * * @return - * The filename of the requested item or NULL if the item is not found. + * The filename of the requested item or FALSE if the item is not found. + * + * @see drupal_get_filename() + * @see _drupal_get_filename_fallback() */ function _drupal_get_filename_perform_file_scan($type, $name) { + // The location of files will not change during the request, so do not use + // drupal_static(). static $dirs = array(), $files = array(); // We have a consistent directory naming: modules, themes... @@ -1105,10 +1052,36 @@ function _drupal_get_filename_perform_file_scan($type, $name) { } } - // If current file had been found in a file scan earlier on in this - // request, use the new location that had been found in the file scan. - if (isset($files[$type][$name])) { - return $files[$type][$name]; + // Return the results of the file system scan, or FALSE to indicate the file + // was not found. + return isset($files[$type][$name]) ? $files[$type][$name] : FALSE; +} + +/** + * Triggers a user-level warning for missing or unexpectedly moved files. + * + * @param $type + * The type of the item (theme, theme_engine, module, profile). + * @param $name + * The name of the item for which the filename is requested. + * @param $error_type + * The type of the error ('missing' or 'moved'). + * + * @see drupal_get_filename() + * @see _drupal_get_filename_fallback() + */ +function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) { + // Make sure we only show any missing or moved file errors only once per + // request. + static $errors_triggered = array(); + if (empty($errors_triggered[$type][$name][$error_type])) { + if ($error_type == 'missing') { + trigger_error(format_string('The following @type is missing from the file system: %name. In order to fix this, put the @type back in its original location. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + } + elseif ($error_type == 'moved') { + trigger_error(format_string('The following @type has moved within the file system: %name. In order to fix this, clear caches or put the @type back in its original location. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + } + $errors_triggered[$type][$name][$error_type] = TRUE; } } @@ -1124,18 +1097,11 @@ function drupal_file_scan_write_cache() { if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) { return; } - $file_scans = &drupal_static('_drupal_get_filename_fallback'); + $file_scans = &_drupal_file_scan_cache(); if (isset($file_scans['#write_cache'])) { - // Merge the newly found out missing and moved file data with the previously - // existing data, if we had not done so yet. - if (!isset($file_scans['#cache_merge_done'])) { - $cache = cache_get('_drupal_get_filename_fallback', 'cache_bootstrap'); - if (isset($cache->data)) { - $file_scans = drupal_array_merge_deep($cache->data, $file_scans); - } - } - $file_scans['#write_cache'] = NULL; - cache_set('_drupal_get_filename_fallback', $file_scans, 'cache_bootstrap'); + unset($file_scans['#write_cache']); + unset($file_scans['#cache_merge_done']); + cache_set('_drupal_file_scan_cache', $file_scans, 'cache_bootstrap'); } } diff --git a/includes/module.inc b/includes/module.inc index 101f5ca..48ea322 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -208,10 +208,9 @@ function system_list($type) { } cache_set('system_list', $lists, 'cache_bootstrap'); } + // To avoid a separate database lookup for the filepath, prime the + // drupal_get_filename() static cache with all enabled modules and themes. foreach ($lists['filepaths'] as $item) { - // To avoid a separate database lookup for the file path, prime the - // drupal_get_filename() static cache with all enabled modules and all - // themes. drupal_get_filename($item['type'], $item['name'], $item['filepath']); } } @@ -230,9 +229,8 @@ function system_list_reset() { cache_clear_all('system_list', 'cache_bootstrap'); // Clean up the bootstrap file scan cache. - drupal_static_reset('_drupal_get_filename_fallback'); - drupal_static_reset('system_filepaths'); - cache_clear_all('_drupal_get_filename_fallback', 'cache_bootstrap'); + drupal_static_reset('_drupal_file_scan_cache'); + cache_clear_all('_drupal_file_scan_cache', 'cache_bootstrap'); } /** diff --git a/includes/update.inc b/includes/update.inc index 4f12db8..ce9319f 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -797,7 +797,7 @@ function update_fix_d7_install_profile() { // 'Default' profile has been renamed to 'Standard' in D7. // We change the profile here to prevent a broken record in the system table. - // See system_update_7049() + // See system_update_7049(). if ($profile == 'default') { $profile = 'standard'; variable_set('install_profile', $profile); diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module index a2e6c6e..7c28fba 100644 --- a/modules/simpletest/simpletest.module +++ b/modules/simpletest/simpletest.module @@ -374,7 +374,6 @@ function simpletest_test_get_all() { // Pass FALSE as fourth argument so no error gets created for // the missing file. $found_module = drupal_get_filename('module', $module, NULL, FALSE); - if (!$found_module) { continue 2; } diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test index 9088023..bdd228c 100644 --- a/modules/simpletest/tests/bootstrap.test +++ b/modules/simpletest/tests/bootstrap.test @@ -371,24 +371,22 @@ class HookBootExitTestCase extends DrupalWebTestCase { /** * Test drupal_get_filename()'s availability. */ -class BootstrapGetFilenameTestCase extends DrupalWebTestCase { +class BootstrapGetFilenameTestCase extends DrupalUnitTestCase { public static function getInfo() { return array( - 'name' => 'Get filename test', - 'description' => 'Test that drupal_get_filename() works correctly when the file is not found in the database.', + 'name' => 'Get filename test (without the system table)', + 'description' => 'Test that drupal_get_filename() works correctly when the database is not available.', 'group' => 'Bootstrap', ); } /** - * Whether the filename test triggered the right error. + * The last file-related error message triggered by the filename test. * * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename(). - * - * @var boolean */ - protected $getFilenameTestTriggeredError = FALSE; + protected $getFilenameTestTriggeredError; /** * Test that drupal_get_filename() works correctly when the file is not found in the database. @@ -420,27 +418,135 @@ class BootstrapGetFilenameTestCase extends DrupalWebTestCase { // 'module' files in modules. $this->assertIdentical(drupal_get_filename('script', 'test'), 'scripts/test.script', t('Retrieve test script location.')); - // Generate a non-existing module name. + // When searching for a module that does not exist, drupal_get_filename() + // should return NULL and trigger an appropriate error message. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for an item that does not exist triggers the correct error.'); + restore_error_handler(); + + // Check that the result is stored in the file system scan cache. + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + + // Performing the search again in the same request still should not find + // the file, but the error message should not be repeated (therefore we do + // not override the error handler here). + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL during the second search.'); + } - // Searching for an item that does not exist returns NULL. - // Set a custom error handler so we can ignore the file not found error. + /** + * Skips handling of "file not found" errors. + */ + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + // Skip error handling if this is a "file not found" error. + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; + return; + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } +} + +/** + * Test drupal_get_filename() in the context of a full Drupal installation. + */ +class BootstrapGetFilenameWebTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Get filename test (full installation)', + 'description' => 'Test that drupal_get_filename() works correctly in the context of a full Drupal installation.', + 'group' => 'Bootstrap', + ); + } + + /** + * The last file-related error message triggered by the filename test. + * + * Used by BootstrapGetFilenameWebTestCase::testDrupalGetFilename(). + */ + protected $getFilenameTestTriggeredError; + + /** + * Test that drupal_get_filename() works correctly with a full Drupal site. + */ + function testDrupalGetFilename() { + // Search for a module that exists in the file system and the {system} + // table and make sure that it is found. + $this->assertIdentical(drupal_get_filename('module', 'node'), 'modules/node/node.module', 'Module found at expected location.'); + + // Search for a module that does not exist in either the file system or the + // {system} table. Make sure that an appropriate error is triggered and + // that the module winds up in the static and persistent cache. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that does not exist triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files persistent cache.'); + + // Simulate moving a module to a location that does not match the location + // in the {system} table and perform similar tests as above. + db_update('system') + ->fields(array('filename' => 'modules/simpletest/tests/fake_location/module_test.module')) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; set_error_handler(array($this, 'fileNotFoundErrorHandler')); - $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for an item that does not exist returns NULL.'); - $this->assertTrue($this->getFilenameTestTriggeredError, 'Searching for an item that does not exist triggers an error.'); - // Restore the original error handler. + $this->assertIdentical(drupal_get_filename('module', 'module_test'), 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved finds the module at its new location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'module_test'))) === 0, 'Searching for a module that has moved triggers the correct error.'); restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files persistent cache.'); - // Get the missing and moved files static variable. - $file_scans = &drupal_static('_drupal_get_filename_fallback'); - - // Searching for an item that does not exist creates a record in the static - // variable. - $this->assertTrue($file_scans['module'][$non_existing_module] === FALSE, 'Searching for an item that does not exist creates a record in the missing and moved files static variable.'); - drupal_install_schema('system'); + // Simulate a module that exists in the {system} table but does not exist + // in the file system and perform similar tests as above. + $non_existing_module = $this->randomName(); + db_update('system') + ->fields(array('name' => $non_existing_module)) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that exists in the system table but not in the file system returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that exists in the system table but not in the file system triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files persistent cache.'); + + // Simulate a module that exists in the file system but not in the {system} + // table and perform similar tests as above. + db_delete('system') + ->condition('name', 'common_test') + ->condition('type', 'module') + ->execute(); + system_list_reset(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertIdentical(drupal_get_filename('module', 'common_test'), 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table finds the module at its actual location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'common_test'))) === 0, 'Searching for a module that does not exist in the system table triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files static variable.'); drupal_file_scan_write_cache(); - $cache = cache_get('_drupal_get_filename_fallback', 'cache_bootstrap'); - $this->assertTrue($cache->data['module'][$non_existing_module] === FALSE, 'File scan results are correctly saved in persistent cache.'); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files persistent cache.'); } /** @@ -448,8 +554,8 @@ class BootstrapGetFilenameTestCase extends DrupalWebTestCase { */ public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { // Skip error handling if this is a "file not found" error. - if (strstr($message, 'is missing from the file system:')) { - $this->getFilenameTestTriggeredError = TRUE; + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; return; } _drupal_error_handler($error_level, $message, $filename, $line, $context);