diff -u b/includes/bootstrap.inc b/includes/bootstrap.inc
--- b/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -835,6 +835,9 @@
function drupal_get_filename($type, $name, $filename = NULL) {
// 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,
+ // and any file that gets added during the request will have been checked
+ // with a file_exists() at some point.
static $files = array(), $dirs = array(), $files_scanned = array();
// Profiles are a special case: they have a fixed location and naming.
@@ -850,13 +853,14 @@
$files[$type][$name] = $filename;
}
elseif (isset($files[$type][$name])) {
- // nothing
+ // This item had already been found earlier in the request. Do nothing.
}
- // 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.
else {
+ // Look for the location 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.
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();
@@ -865,6 +869,8 @@
$files[$type][$name] = $file;
}
else {
+ // Flag that the filename in the database does not exist for the
+ // requested item, so we can figure out if it has moved.
$database_file_exists = FALSE;
}
}
@@ -876,11 +882,9 @@
// hide the error completely.
}
// Fallback to searching the filesystem if the database could not find the
- // file or the file returned by the database is not found.
+ // file or the filename returned by the database is not found. We cache
+ // missing and moved files in order to prevent unnecessary file scans.
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') {
@@ -894,19 +898,28 @@
$extension = $type;
}
- // See if we have a cached list of files missing from the file system.
+ // 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 file type and name, and as value the boolean FALSE (if the
+ // module is missing) or a string (if the module has moved from the
+ // location listed in the {system} table) with the location that was
+ // found in a file scan.
+ $missing = &drupal_static('drupal_get_filename:missing');
if (!isset($missing)) {
$missing = array();
+ }
+ // Get the missing and moved files from persistent cache, if available.
+ if (!isset($missing['#cache_merge_done'])) {
try {
if (function_exists('cache_get')) {
$cache = cache_get('drupal_get_filename:missing', 'cache_bootstrap');
if (!empty($cache->data)) {
- // Merge the changes already done in the current request (including
- // the setting of missing records to NULL) into the values saved
- // in persistent cache.
- $missing = $cache->data + $missing;
- // Set a flag so we know that we've already done a merge with values
- // in cache_get().
+ // Merge the changes already done in the current request
+ // (including the setting of missing records to NULL) into the
+ // values saved in persistent cache.
+ $missing = drupal_array_merge_deep($cache->data, $missing);
+ // Set a flag so we know that we've already done a merge with
+ // the values in persistent cache.
$missing['#cache_merge_done'] = TRUE;
}
}
@@ -916,19 +929,27 @@
}
}
+ // Check whether this file had previously moved from its location in
+ // the {system} table, and the result of a file scan had been cached.
if (isset($missing[$type][$name]) && is_string($missing[$type][$name])) {
// This file has moved and the result of a file scan had been cached.
if (file_exists($missing[$type][$name])) {
+ // We found the file at the cached location.
$files[$type][$name] = $missing[$type][$name];
}
else {
- // File is not available anymore at its new location. Re-do file scan.
+ // File is not available anymore at the cached location. Remove the
+ // missing/moved file record so a new file scan will be performed.
$missing[$type][$name] = NULL;
$missing['#write_cache'] = TRUE;
}
}
+ // Check if we had already scanned this directory/extension combination,
+ // or if this file had been marked as missing/moved.
if (!isset($dirs[$dir][$extension]) && !isset($missing[$type][$name])) {
+ // Log that we have now scanned this directory/extension combination
+ // into a static variable to prevent unnecessary scans.
$dirs[$dir][$extension] = TRUE;
if (!function_exists('drupal_system_listing')) {
require_once DRUPAL_ROOT . '/includes/common.inc';
@@ -939,16 +960,24 @@
// 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) {
+ // Log the locations found in the file scan into a static variable.
$files_scanned[$type][$matched_name] = $file->uri;
}
}
+ // 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_scanned[$type][$name])) {
+ // Use the location from the file scan earlier on in this request.
$files[$type][$name] = $files_scanned[$type][$name];
+ // Check whether the current file was listed as being in another
+ // location in the {system} table.
if (isset($database_file_exists) && $database_file_exists === FALSE) {
- // This file has moved. Cache its new location into the missing files
- // cache.
+ // This file has moved. Cache its new location into the missing and
+ // moved files list.
$missing[$type][$name] = $files_scanned[$type][$name];
+ // Make sure our change to the missing and moved files list will be
+ // written to persistent cache.
$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);
}
@@ -957,15 +986,20 @@
}
if (isset($files[$type][$name])) {
+ // The requested file exists, return its filename.
return $files[$type][$name];
}
else {
+ // The requested file does not exist in any location.
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.
+ // Mark the file as missing.
$missing[$type][$name] = TRUE;
+ // Make sure our change to the missing files list will be written to
+ // persistent cache.
+ // This cache will be cleared on module/theme rebuild.
$missing['#write_cache'] = TRUE;
}
+ // Trigger a user-level warning on every request for the missing file.
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);
}
}
@@ -977,15 +1011,18 @@
* The array of missing and moved files.
*/
function drupal_missing_write_cache() {
+ // Only write to cache if we are fully bootstrapped.
if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) {
return;
}
$missing = &drupal_static('drupal_get_filename:missing');
if (isset($missing['#write_cache'])) {
+ // Merge the newly found out missing and moved file data with
+ // the previously existing data, if we hadn't done so yet.
if (!isset($missing['#cache_merge_done'])) {
$cache = cache_get('drupal_get_filename:missing', 'cache_bootstrap');
if (isset($cache->data)) {
- $missing = $cache->data + $missing;
+ $missing = drupal_array_merge_deep($cache->data, $missing);
}
}
$missing['#write_cache'] = NULL;