diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 847bfc3..c45b47f 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -835,7 +835,7 @@ function drupal_settings_initialize() {
function drupal_get_filename($type, $name, $filename = NULL) {
// The location of files will not change during the request, so do not use
// drupal_static().
- static $files = array(), $dirs = array();
+ static $files = array(), $dirs = array(), $files_scanned = array();
// Profiles are a special case: they have a fixed location and naming.
if ($type == 'profile') {
@@ -860,8 +860,13 @@ function drupal_get_filename($type, $name, $filename = NULL) {
try {
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 && file_exists(DRUPAL_ROOT . '/' . $file)) {
- $files[$type][$name] = $file;
+ if ($file !== FALSE) {
+ if (file_exists(DRUPAL_ROOT . '/' . $file)) {
+ $files[$type][$name] = $file;
+ }
+ else {
+ $database_file_exists = FALSE;
+ }
}
}
}
@@ -873,6 +878,9 @@ function drupal_get_filename($type, $name, $filename = NULL) {
// Fallback to searching the filesystem if the database could not find the
// file or the file returned by the database is not found.
if (!isset($files[$type][$name])) {
+ // We use drupal_static() for the missing records so we can test it.
+ $missing = &drupal_static('drupal_get_filename:missing');
+
// We have a consistent directory naming: modules, themes...
$dir = $type . 's';
if ($type == 'theme_engine') {
@@ -886,7 +894,23 @@ function drupal_get_filename($type, $name, $filename = NULL) {
$extension = $type;
}
- if (!isset($dirs[$dir][$extension])) {
+ // See if we have a cached list of files missing from the file system.
+ if (!isset($missing)) {
+ $missing = array();
+ try {
+ if (function_exists('cache_get')) {
+ $cache = cache_get('drupal_get_filename:missing');
+ if (!empty($cache->data)) {
+ $missing = $cache->data;
+ }
+ }
+ }
+ catch (Exception $e) {
+ // Hide the error.
+ }
+ }
+
+ if (!isset($dirs[$dir][$extension]) && !isset($missing[$type][$name])) {
$dirs[$dir][$extension] = TRUE;
if (!function_exists('drupal_system_listing')) {
require_once DRUPAL_ROOT . '/includes/common.inc';
@@ -897,7 +921,17 @@ function drupal_get_filename($type, $name, $filename = NULL) {
// called more than once in the same page request.
$matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
foreach ($matches as $matched_name => $file) {
- $files[$type][$matched_name] = $file->uri;
+ $files_scanned[$type][$matched_name] = $file->uri;
+ }
+ }
+
+ if (isset($files_scanned[$type][$name])) {
+ $files[$type][$name] = $files_scanned[$type][$name];
+ if (isset($database_file_exists) && $database_file_exists === FALSE) {
+ // This file has moved. Cache its new location into the missing files cache.
+ $missing[$type][$name] = $files_scanned[$type][$name];
+ $missing['#write_cache'] = TRUE;
+ trigger_error(format_string('The following @type has moved on the file system: @name. Clearing caches may help fix this. For more information, see the documentation page.', array('@type' => $type, '@name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
}
}
}
@@ -906,6 +940,32 @@ function drupal_get_filename($type, $name, $filename = NULL) {
if (isset($files[$type][$name])) {
return $files[$type][$name];
}
+ else {
+ if (!isset($missing[$type][$name])) {
+ // Add the missing file to a temporary cache and throw an alert.
+ // This cache will be cleared on module/theme rebuild.
+ $missing[$type][$name] = FALSE;
+ $missing['#write_cache'] = TRUE;
+ }
+ trigger_error(format_string('The following @type is missing from the file system: @name. For more information, see the documentation page.', array('@type' => $type, '@name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
+ }
+}
+
+/**
+ * Writes the missing and moved files to persistent cache.
+ *
+ * @param array $missing
+ * The array of missing and moved files.
+ */
+function drupal_missing_write_cache() {
+ if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) {
+ return;
+ }
+ $missing = &drupal_static('drupal_get_filename:missing');
+ if (isset($missing['#write_cache'])) {
+ unset($missing['#write_cache']);
+ cache_set('drupal_get_filename:missing', $missing, 'cache_bootstrap');
+ }
}
/**
@@ -2464,6 +2524,10 @@ function _drupal_bootstrap_database() {
// the install or upgrade process.
spl_autoload_register('drupal_autoload_class');
spl_autoload_register('drupal_autoload_interface');
+
+ // Reset the drupal_get_filename() static missing filenames cache as it
+ // may have previously been initialized without the database being available.
+ drupal_static_reset('drupal_get_filename:missing');
}
/**
diff --git a/includes/common.inc b/includes/common.inc
index cd30145..ffb35a4 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -2751,6 +2751,7 @@ function drupal_page_footer() {
_registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
drupal_cache_system_paths();
module_implements_write_cache();
+ drupal_missing_write_cache();
system_run_automated_cron();
}
diff --git a/includes/update.inc b/includes/update.inc
index a17161c..c23519d 100644
--- a/includes/update.inc
+++ b/includes/update.inc
@@ -795,6 +795,14 @@ function update_fix_d7_requirements() {
function update_fix_d7_install_profile() {
$profile = drupal_get_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()
+ if ($profile == 'default') {
+ $profile = 'standard';
+ variable_set('install_profile', $profile);
+ }
+
$results = db_select('system', 's')
->fields('s', array('name', 'schema_version'))
->condition('name', $profile)
diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module
index 91f0f90..73cd14f 100644
--- a/modules/simpletest/simpletest.module
+++ b/modules/simpletest/simpletest.module
@@ -371,7 +371,18 @@ function simpletest_test_get_all() {
// If this test class requires a non-existing module, skip it.
if (!empty($info['dependencies'])) {
foreach ($info['dependencies'] as $module) {
- if (!drupal_get_filename('module', $module)) {
+
+ // Searching for an item that does not exist triggers an PHP
+ // error. Set a custom error handler so we can ignore the file
+ // not found error.
+ set_error_handler('_simpletest_file_not_found_error_handler');
+
+ $found_module = drupal_get_filename('module', $module);
+
+ // Restore the original error handler.
+ restore_error_handler();
+
+ if (!$found_module) {
continue 2;
}
}
@@ -395,6 +406,16 @@ function simpletest_test_get_all() {
return $groups;
}
+/**
+ * Skips handling of "file not found" errors.
+ */
+function _simpletest_file_not_found_error_handler($error_level, $message, $filename, $line, $context) {
+ if (strstr($message, 'is missing from the file system:')) {
+ return;
+ }
+ _drupal_error_handler($error_level, $message, $filename, $line, $context);
+}
+
/*
* Register a simple class loader that can find D8-style PSR-0 test classes.
*
diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test
index ece1cd9..aeb134c 100644
--- a/modules/simpletest/tests/bootstrap.test
+++ b/modules/simpletest/tests/bootstrap.test
@@ -382,6 +382,15 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
}
/**
+ * Whether the filename test triggered the right error.
+ *
+ * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename().
+ *
+ * @var boolean
+ */
+ protected $getFilenameTestTriggeredError = FALSE;
+
+ /**
* Test that drupal_get_filename() works correctly when the file is not found in the database.
*/
function testDrupalGetFilename() {
@@ -410,6 +419,36 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
// automatically check there for 'script' files, just as it does for (e.g.)
// '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.
+ $non_existing_module = $this->randomName();
+
+ // Searching for an item that does not exist returns NULL.
+ // Set a custom error handler so we can ignore the file not found error.
+ 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.
+ restore_error_handler();
+
+ // Get the missing records static from drupal_get_filename().
+ $missing = &drupal_static('drupal_get_filename:missing');
+
+ // Searching for an item that does not exist creates a static record in
+ // drupal_get_filename().
+ $this->assertTrue($missing['module'][$non_existing_module], 'Searching for an item that does not exist creates a static record in drupal_get_filename().');
+ }
+
+ /**
+ * 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 (strstr($message, 'is missing from the file system:')) {
+ $this->getFilenameTestTriggeredError = TRUE;
+ return;
+ }
+ _drupal_error_handler($error_level, $message, $filename, $line, $context);
}
}
diff --git a/modules/system/system.module b/modules/system/system.module
index 8fc517f..10e672e 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -2365,6 +2365,10 @@ function system_get_info($type, $name = NULL) {
* An associative array of module information.
*/
function _system_rebuild_module_data() {
+ // Clean up the bootstrap "missing files" cache when rebuilding module data.
+ drupal_static_reset('drupal_get_filename:missing');
+ cache_clear_all('drupal_get_filename:missing', 'cache');
+
// Find modules
$modules = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0);
@@ -2501,6 +2505,10 @@ function _system_update_bootstrap_status() {
* An associative array of themes information.
*/
function _system_rebuild_theme_data() {
+ // Clean up the bootstrap "missing files" cache when rebuilding theme data.
+ drupal_static_reset('drupal_get_filename:missing');
+ cache_clear_all('drupal_get_filename:missing', 'cache');
+
// Find themes
$themes = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'themes');
// Allow modules to add further themes.