diff -u b/includes/bootstrap.inc b/includes/bootstrap.inc
--- b/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -838,7 +838,7 @@
// 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();
+ static $files = array();
// Profiles are a special case: they have a fixed location and naming.
if ($type == 'profile') {
@@ -850,192 +850,222 @@
}
if (!empty($filename) && file_exists($filename)) {
+ // Prime the static cache with the provided filename.
$files[$type][$name] = $filename;
}
elseif (isset($files[$type][$name])) {
- // This item had already been found earlier in the request. Do nothing.
+ // This item had already been found earlier in the request, either in this
+ // same function or by the static cache having been primed elsewhere, such
+ // as in system_list(). Do nothing.
}
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 {
- $database_file_exists = NULL;
- 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;
- $database_file_exists = TRUE;
- }
- 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;
+ $system_filepaths = &drupal_static('system_filepaths');
+ if (!isset($system_filepaths[$type][$name])) {
+ // Look for the file name 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();
+ if ($file !== FALSE) {
+ $system_filepaths[$type][$name] = $file;
+ if (file_exists(DRUPAL_ROOT . '/' . $file)) {
+ $files[$type][$name] = $file;
+ }
}
}
}
- }
- catch (Exception $e) {
- // The database table may not exist because Drupal is not yet installed,
- // or the database might be down. We have a fallback for this case so we
- // hide the error completely.
+ catch (Exception $e) {
+ // The database table may not exist because Drupal is not yet installed,
+ // or the database might be down. We have a fallback for this case so we
+ // hide the error completely.
+ }
}
// Fallback to searching the filesystem if the database could not find the
- // file or the filename returned by the database is not found. We cache
- // missing and moved files in order to prevent unnecessary file scans.
+ // file or the filename returned by the database is not found.
if (!isset($files[$type][$name])) {
- // We have a consistent directory naming: modules, themes...
- $dir = $type . 's';
- if ($type == 'theme_engine') {
- $dir = 'themes/engines';
- $extension = 'engine';
- }
- elseif ($type == 'theme') {
- $extension = 'info';
- }
- else {
- $extension = $type;
- }
-
- // 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 TRUE (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 = 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;
- }
- }
- }
- catch (Exception $e) {
- // Hide the error.
- }
- }
-
- // Check whether this file had previously moved from its location in
- // the {system} table, and the new location found in 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 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;
- }
- }
+ $files[$type][$name] = _drupal_get_filename_fallback($type, $name);
+ }
+ }
- // 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';
- }
- // Scan the appropriate directories for all files with the requested
- // extension, not just the file we are currently looking for. This
- // prevents unnecessary scans from being repeated when this function is
- // 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 (isset($files[$type][$name])) {
+ // We found a file.
+ // Clean up the item from the file scan cache, in case the item had been
+ // marked as missing or moved.
+ $file_scans = &drupal_static('_drupal_get_filename_fallback');
+ if (isset($file_scans[$type][$name])) {
+ $file_scans[$type][$name] = NULL;
+ $file_scans['#write_cache'] = TRUE;
+ }
+ return $files[$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_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 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);
+/**
+ * Helper function for drupal_get_filename().
+ *
+ * This triggers a file scan, caching any missing or moved files. If an item
+ * had been marked as missing or moved in a previous file scan, no new file
+ * scans 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.
+ *
+ * @return string
+ * The filename of the requested item or NULL if the item is not found.
+ */
+function _drupal_get_filename_fallback($type, $name) {
+ // 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 with location found in a file scan if
+ // the module 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 missing and moved files from 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 already done in the current request
+ // (including the setting of missing records to NULL) into the
+ // values saved in persistent cache.
+ $file_scans = drupal_array_merge_deep($cache->data, $file_scans);
+ // Set a flag so we know that we've already done a merge with
+ // the values in persistent cache.
+ $file_scans['#cache_merge_done'] = TRUE;
}
}
}
+ catch (Exception $e) {
+ // Hide the error.
+ }
}
- if (isset($files[$type][$name])) {
- // The requested file exists.
- // Check if the file had previously been marked as missing or moved.
- if (isset($missing[$type][$name]) && ($missing[$type][$name] === TRUE || (is_string($missing[$type][$name]) && $missing[$type][$name] != $files[$type][$name]))) {
- // Previously missing/moved file has reappeared.
- $missing[$type][$name] = NULL;
- $missing['#write_cache'] = TRUE;
+ // Check whether this file had previously moved from its location in the
+ // {system} table.
+ if (isset($file_scans[$type][$name]) && is_string($file_scans[$type][$name]) && file_exists($file_scans[$type][$name])) {
+ // We found the file at the cached location.
+ return $file_scans[$type][$name];
+ }
+
+ // Unless the module had been marked as missing, perform a file scan.
+ if (!(isset($file_scans[$type][$name]) && $file_scans[$type][$name] === FALSE)) {
+ $filename_from_file_scan = _drupal_get_filename_perform_file_scan($type, $name);
+ }
+ if (isset($filename_from_file_scan)) {
+ // Check whether the current file was listed as being in another
+ // location in the {system} table.
+ $system_filepaths = &drupal_static('system_filepaths');
+ if (isset($system_filepaths[$type][$name]) && $system_filepaths[$type][$name] != $filename_from_file_scan) {
+ // This file has moved. Cache its new location into the missing and
+ // moved files list.
+ $file_scans[$type][$name] = $filename_from_file_scan[$type][$name];
+ // Make sure our change to the missing and moved files list will be
+ // written to persistent cache.
+ $file_scans['#write_cache'] = TRUE;
+ trigger_error(format_string('The following %type has moved on 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);
}
- return $files[$type][$name];
+ return $filename_from_file_scan;
}
else {
- // The requested file does not exist in any location.
- if (!isset($missing[$type][$name])) {
- // 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_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);
+ // Make sure our change to the missing files list will be written to
+ // persistent cache.
+ if (!isset($file_scans[$type][$name])) {
+ $file_scans['#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);
+ // Mark the file as missing.
+ $file_scans[$type][$name] = FALSE;
}
}
/**
- * Writes the missing and moved files to persistent cache.
+ * Helper function for drupal_get_filename().
+ *
+ * Performs a file scan.
*
- * @param array $missing
- * The array of missing and 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.
+ *
+ * @return
+ * The filename of the requested item or NULL if the item is not found.
+ */
+function _drupal_get_filename_perform_file_scan($type, $name) {
+ static $dirs = array(), $files = array();
+
+ // We have a consistent directory naming: modules, themes...
+ $dir = $type . 's';
+ if ($type == 'theme_engine') {
+ $dir = 'themes/engines';
+ $extension = 'engine';
+ }
+ elseif ($type == 'theme') {
+ $extension = 'info';
+ }
+ else {
+ $extension = $type;
+ }
+
+ // 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])) {
+ // 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';
+ }
+ // Scan the appropriate directories for all files with the requested
+ // extension, not just the file we are currently looking for. This
+ // prevents unnecessary scans from being repeated when this function is
+ // 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[$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[$type][$name])) {
+ return $files[$type][$name];
+ }
+}
+
+/**
+ * Writes the missing and moved files to persistent cache.
*/
-function drupal_missing_write_cache() {
+function drupal_file_scan_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'])) {
+ $file_scans = &drupal_static('_drupal_get_filename_fallback');
+ if (isset($file_scans['#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($file_scans['#cache_merge_done'])) {
+ $cache = cache_get('_drupal_get_filename_fallback', 'cache_bootstrap');
if (isset($cache->data)) {
- $missing = drupal_array_merge_deep($cache->data, $missing);
+ $file_scans = drupal_array_merge_deep($cache->data, $file_scans);
}
}
- $missing['#write_cache'] = NULL;
- cache_set('drupal_get_filename:missing', $missing, 'cache_bootstrap');
+ $file_scans['#write_cache'] = NULL;
+ cache_set('_drupal_get_filename_fallback', $file_scans, 'cache_bootstrap');
}
}
diff -u b/includes/common.inc b/includes/common.inc
--- b/includes/common.inc
+++ b/includes/common.inc
@@ -2751,7 +2751,7 @@
_registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
drupal_cache_system_paths();
module_implements_write_cache();
- drupal_missing_write_cache();
+ drupal_file_scan_write_cache();
system_run_automated_cron();
}
diff -u b/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test
--- b/modules/simpletest/tests/bootstrap.test
+++ b/modules/simpletest/tests/bootstrap.test
@@ -432,11 +432,15 @@
restore_error_handler();
// Get the missing records static from drupal_get_filename().
- $missing = &drupal_static('drupal_get_filename:missing');
+ $file_scans = &drupal_static('_drupal_get_filename_fallback');
// 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().');
+ $this->assertTrue($file_scans['module'][$non_existing_module] === FALSE, 'Searching for an item that does not exist creates a static record in drupal_get_filename().');
+ drupal_install_schema('system');
+ 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.');
}
/**
diff -u b/modules/system/system.module b/modules/system/system.module
--- b/modules/system/system.module
+++ b/modules/system/system.module
@@ -2366,8 +2366,8 @@
*/
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');
+ drupal_static_reset('_drupal_get_filename_fallback');
+ cache_clear_all('_drupal_get_filename_fallback', 'cache_bootstrap');
// Find modules
$modules = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0);
@@ -2506,8 +2506,8 @@
*/
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');
+ drupal_static_reset('_drupal_get_filename_fallback');
+ cache_clear_all('_drupal_get_filename_fallback', 'cache_bootstrap');
// Find themes
$themes = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'themes');
only in patch2:
unchanged:
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -121,6 +121,7 @@ function module_list($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE
*/
function system_list($type) {
$lists = &drupal_static(__FUNCTION__);
+ $system_filepaths = &drupal_static('system_filepaths');
// For bootstrap modules, attempt to fetch the list from cache if possible.
// if not fetch only the required information to fire bootstrap hooks
@@ -208,9 +209,12 @@ 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) {
+ // Save the file paths from the database into a static variable so we
+ // can determine later if the files have moved.
+ $system_filepaths[$item['type']][$item['name']] = $item['filepath'];
+ // To avoid a separate database lookup for the filepath, prime the
+ // drupal_get_filename() static cache with all enabled modules and themes.
drupal_get_filename($item['type'], $item['name'], $item['filepath']);
}
}