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;