Issue #1081266 by stefan.r, jeroen.b, mikeytown2, David_Rothstein, tsphethean, mfb, joseph.olstad, marcelovani, Kars-T, joelpittet, Fabianx, catch, fgm, das-peter, alexpott, emcniece, oriol_e9g, sun, corbacho, klausi, mgifford, onelittleant, Peter Bex, Spleshka, beejeebus, Berdir, pwaterz, SocialNicheGuru, sylus, Wim Leers, heyyo, joshtaylor, swentel, alanburke, dagmar, alexmoreno, kenorb, EvanSchisler, Mark Theunissen, bmateus, andypost, Lukas von Blarer, ChristophWeber, nicholas.alipaz, arosboro, askibinski, dawehner, DerekL, ExTexan: Avoid re-scanning module directory when a filename or a module is missing
parent
f68a354ff8
commit
206c7c1b40
|
@ -52,6 +52,7 @@ Drupal 7.50, xxxx-xx-xx (development version)
|
|||
data structure change).
|
||||
- Increased maxlength of menu link title input fields in the node form and
|
||||
menu link form from 128 to 255 characters.
|
||||
- Avoid re-scanning of module directory when a filename or a module is missing.
|
||||
|
||||
Drupal 7.44, 2016-06-15
|
||||
-----------------------
|
||||
|
|
|
@ -828,14 +828,21 @@ function drupal_settings_initialize() {
|
|||
* @param $filename
|
||||
* 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 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) {
|
||||
function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) {
|
||||
// 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(), $dirs = array();
|
||||
static $files = array();
|
||||
|
||||
// Profiles are a special case: they have a fixed location and naming.
|
||||
if ($type == 'profile') {
|
||||
|
@ -847,59 +854,41 @@ function drupal_get_filename($type, $name, $filename = NULL) {
|
|||
}
|
||||
|
||||
if (!empty($filename) && file_exists($filename)) {
|
||||
// Prime the static cache with the provided filename.
|
||||
$files[$type][$name] = $filename;
|
||||
}
|
||||
elseif (isset($files[$type][$name])) {
|
||||
// nothing
|
||||
// This item had already been found earlier in the request, either through
|
||||
// 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.
|
||||
}
|
||||
// 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 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 {
|
||||
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;
|
||||
}
|
||||
$database_unavailable = FALSE;
|
||||
}
|
||||
}
|
||||
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.
|
||||
// the database might be down, or we may have done a non-database cache
|
||||
// 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.
|
||||
}
|
||||
// Fallback to searching the filesystem if the database could not find the
|
||||
// file or the file returned by the database is not found.
|
||||
// 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])) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (!isset($dirs[$dir][$extension])) {
|
||||
$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) {
|
||||
$files[$type][$matched_name] = $file->uri;
|
||||
}
|
||||
}
|
||||
$files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -908,6 +897,250 @@ function drupal_get_filename($type, $name, $filename = NULL) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 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
|
||||
* 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, $database_unavailable) {
|
||||
$file_scans = &_drupal_file_scan_cache();
|
||||
$filename = NULL;
|
||||
|
||||
// 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];
|
||||
}
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current list of cached file system scan results.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Hide the error.
|
||||
}
|
||||
}
|
||||
|
||||
return $file_scans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a file system scan to search for a system resource.
|
||||
*
|
||||
* @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 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...
|
||||
$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.
|
||||
if (!isset($dirs[$dir][$extension])) {
|
||||
// Log that we have now scanned this directory/extension combination
|
||||
// into a static variable so as to prevent unnecessary file 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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])) {
|
||||
// Use _drupal_trigger_error_with_delayed_logging() here since these are
|
||||
// triggered during low-level operations that cannot necessarily be
|
||||
// interrupted by a watchdog() call.
|
||||
if ($error_type == 'missing') {
|
||||
_drupal_trigger_error_with_delayed_logging(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 <a href="@documentation">the documentation page</a>.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
|
||||
}
|
||||
elseif ($error_type == 'moved') {
|
||||
_drupal_trigger_error_with_delayed_logging(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 <a href="@documentation">the documentation page</a>.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
|
||||
}
|
||||
$errors_triggered[$type][$name][$error_type] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes trigger_error() with logging delayed until the end of the request.
|
||||
*
|
||||
* This is an alternative to PHP's trigger_error() function which can be used
|
||||
* during low-level Drupal core operations that need to avoid being interrupted
|
||||
* by a watchdog() call.
|
||||
*
|
||||
* Normally, Drupal's error handler calls watchdog() in response to a
|
||||
* trigger_error() call. However, this invokes hook_watchdog() which can run
|
||||
* arbitrary code. If the trigger_error() happens in the middle of an
|
||||
* operation such as a rebuild operation which should not be interrupted by
|
||||
* arbitrary code, that could potentially break or trigger the rebuild again.
|
||||
* This function protects against that by delaying the watchdog() call until
|
||||
* the end of the current page request.
|
||||
*
|
||||
* This is an internal function which should only be called by low-level Drupal
|
||||
* core functions. It may be removed in a future Drupal 7 release.
|
||||
*
|
||||
* @param string $error_msg
|
||||
* The error message to trigger. As with trigger_error() itself, this is
|
||||
* limited to 1024 bytes; additional characters beyond that will be removed.
|
||||
* @param int $error_type
|
||||
* (optional) The type of error. This should be one of the E_USER family of
|
||||
* constants. As with trigger_error() itself, this defaults to E_USER_NOTICE
|
||||
* if not provided.
|
||||
*
|
||||
* @see _drupal_log_error()
|
||||
*/
|
||||
function _drupal_trigger_error_with_delayed_logging($error_msg, $error_type = E_USER_NOTICE) {
|
||||
$delay_logging = &drupal_static(__FUNCTION__, FALSE);
|
||||
$delay_logging = TRUE;
|
||||
trigger_error($error_msg, $error_type);
|
||||
$delay_logging = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the file scan cache to the persistent cache.
|
||||
*
|
||||
* This cache stores all files marked as missing or moved after a file scan
|
||||
* to prevent unnecessary file scans in subsequent requests. This cache is
|
||||
* cleared in system_list_reset() (i.e. after a module/theme rebuild).
|
||||
*/
|
||||
function drupal_file_scan_write_cache() {
|
||||
// Only write to the persistent cache if requested, and if we know that any
|
||||
// data previously in the cache was successfully loaded and merged in by
|
||||
// _drupal_file_scan_cache().
|
||||
$file_scans = &_drupal_file_scan_cache();
|
||||
if (isset($file_scans['#write_cache']) && isset($file_scans['#cache_merge_done'])) {
|
||||
unset($file_scans['#write_cache']);
|
||||
cache_set('_drupal_file_scan_cache', $file_scans, 'cache_bootstrap');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the persistent variable table.
|
||||
*
|
||||
|
|
|
@ -2776,6 +2776,7 @@ function drupal_page_footer() {
|
|||
_registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
|
||||
drupal_cache_system_paths();
|
||||
module_implements_write_cache();
|
||||
drupal_file_scan_write_cache();
|
||||
system_run_automated_cron();
|
||||
}
|
||||
|
||||
|
|
|
@ -199,7 +199,16 @@ function _drupal_log_error($error, $fatal = FALSE) {
|
|||
$number++;
|
||||
}
|
||||
|
||||
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
|
||||
// Log the error immediately, unless this is a non-fatal error which has been
|
||||
// triggered via drupal_trigger_error_with_delayed_logging(); in that case
|
||||
// trigger it in a shutdown function. Fatal errors are always triggered
|
||||
// immediately since for a fatal error the page request will end here anyway.
|
||||
if (!$fatal && drupal_static('_drupal_trigger_error_with_delayed_logging')) {
|
||||
drupal_register_shutdown_function('watchdog', 'php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
|
||||
}
|
||||
else {
|
||||
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
|
||||
}
|
||||
|
||||
if ($fatal) {
|
||||
drupal_add_http_header('Status', '500 Service unavailable (with message)');
|
||||
|
|
|
@ -227,6 +227,10 @@ function system_list_reset() {
|
|||
drupal_static_reset('list_themes');
|
||||
cache_clear_all('bootstrap_modules', 'cache_bootstrap');
|
||||
cache_clear_all('system_list', 'cache_bootstrap');
|
||||
|
||||
// Clean up the bootstrap file scan cache.
|
||||
drupal_static_reset('_drupal_file_scan_cache');
|
||||
cache_clear_all('_drupal_file_scan_cache', 'cache_bootstrap');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -374,7 +374,10 @@ 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)) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -388,12 +388,19 @@ 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',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The last file-related error message triggered by the filename test.
|
||||
*
|
||||
* Used by BootstrapGetFilenameTestCase::testDrupalGetFilename().
|
||||
*/
|
||||
protected $getFilenameTestTriggeredError;
|
||||
|
||||
/**
|
||||
* Test that drupal_get_filename() works correctly when the file is not found in the database.
|
||||
*/
|
||||
|
@ -423,6 +430,203 @@ 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.'));
|
||||
|
||||
// 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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('system_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->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.');
|
||||
|
||||
// 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_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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 that watchdog messages about missing files are correctly recorded.
|
||||
*/
|
||||
public function testWatchdog() {
|
||||
// Search for a module that does not exist in either the file system or the
|
||||
// {system} table. Make sure that an appropriate warning is recorded in the
|
||||
// logs.
|
||||
$non_existing_module = $this->randomName();
|
||||
$query_parameters = array(
|
||||
':type' => 'php',
|
||||
':severity' => WATCHDOG_WARNING,
|
||||
);
|
||||
$this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs before searching for a module that does not exist.');
|
||||
// Trigger the drupal_get_filename() call. This must be done via a request
|
||||
// to a separate URL since the watchdog() will happen in a shutdown
|
||||
// function, and so that SimpleTest can be told to ignore (and not fail as
|
||||
// a result of) the expected PHP warnings generated during this process.
|
||||
variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module);
|
||||
$this->drupalGet('system-test/drupal-get-filename');
|
||||
$message_variables = db_query('SELECT variables FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchCol();
|
||||
$this->assertEqual(count($message_variables), 1, 'A single warning message appears in the logs after searching for a module that does not exist.');
|
||||
$variables = reset($message_variables);
|
||||
$variables = unserialize($variables);
|
||||
$this->assertTrue(isset($variables['!message']) && strpos($variables['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) !== FALSE, 'The warning message that appears in the logs after searching for a module that does not exist contains the expected text.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that drupal_get_filename() does not break recursive rebuilds.
|
||||
*/
|
||||
public function testRecursiveRebuilds() {
|
||||
// Ensure that the drupal_get_filename() call due to a missing module does
|
||||
// not break the data returned by an attempted recursive rebuild. The code
|
||||
// path which is tested is as follows:
|
||||
// - Call drupal_get_schema().
|
||||
// - Within a hook_schema() implementation, trigger a drupal_get_filename()
|
||||
// search for a nonexistent module.
|
||||
// - In the watchdog() call that results from that, trigger
|
||||
// drupal_get_schema() again.
|
||||
// Without some kind of recursion protection, this could cause the second
|
||||
// drupal_get_schema() call to return incomplete results. This test ensures
|
||||
// that does not happen.
|
||||
$non_existing_module = $this->randomName();
|
||||
variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module);
|
||||
$this->drupalGet('system-test/drupal-get-filename-with-schema-rebuild');
|
||||
$original_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_original_tables');
|
||||
$final_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_final_tables');
|
||||
$this->assertTrue(!empty($original_drupal_get_schema_tables));
|
||||
$this->assertTrue(!empty($final_drupal_get_schema_tables));
|
||||
$this->assertEqual($original_drupal_get_schema_tables, $final_drupal_get_schema_tables);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the system_test module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function system_test_schema() {
|
||||
// Trigger a search for a module in the filesystem when requested by
|
||||
// system_test_drupal_get_filename_with_schema_rebuild().
|
||||
if (variable_get('system_test_drupal_get_filename_attempt_recursive_rebuild')) {
|
||||
$module_name = variable_get('system_test_drupal_get_filename_test_module_name');
|
||||
drupal_get_filename('module', $module_name);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
|
@ -127,6 +127,20 @@ function system_test_menu() {
|
|||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
$items['system-test/drupal-get-filename'] = array(
|
||||
'title' => 'Test drupal_get_filename()',
|
||||
'page callback' => 'system_test_drupal_get_filename',
|
||||
'access callback' => TRUE,
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
$items['system-test/drupal-get-filename-with-schema-rebuild'] = array(
|
||||
'title' => 'Test drupal_get_filename() with a schema rebuild',
|
||||
'page callback' => 'system_test_drupal_get_filename_with_schema_rebuild',
|
||||
'access callback' => TRUE,
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
@ -482,3 +496,76 @@ function system_test_request_destination() {
|
|||
// information.
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page callback to run drupal_get_filename() on a particular module.
|
||||
*/
|
||||
function system_test_drupal_get_filename() {
|
||||
// Prevent SimpleTest from failing as a result of the expected PHP warnings
|
||||
// this function causes. Any warnings will be recorded in the database logs
|
||||
// for examination by the tests.
|
||||
define('SIMPLETEST_COLLECT_ERRORS', FALSE);
|
||||
|
||||
$module_name = variable_get('system_test_drupal_get_filename_test_module_name');
|
||||
drupal_get_filename('module', $module_name);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Page callback to run drupal_get_filename() and do a schema rebuild.
|
||||
*/
|
||||
function system_test_drupal_get_filename_with_schema_rebuild() {
|
||||
// Prevent SimpleTest from failing as a result of the expected PHP warnings
|
||||
// this function causes.
|
||||
define('SIMPLETEST_COLLECT_ERRORS', FALSE);
|
||||
|
||||
// Record the original database tables from drupal_get_schema().
|
||||
variable_set('system_test_drupal_get_filename_with_schema_rebuild_original_tables', array_keys(drupal_get_schema(NULL, TRUE)));
|
||||
|
||||
// Trigger system_test_schema() and system_test_watchdog() to perform an
|
||||
// attempted recursive rebuild when drupal_get_schema() is called. See
|
||||
// BootstrapGetFilenameWebTestCase::testRecursiveRebuilds().
|
||||
variable_set('system_test_drupal_get_filename_attempt_recursive_rebuild', TRUE);
|
||||
drupal_get_schema(NULL, TRUE);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_watchdog().
|
||||
*/
|
||||
function system_test_watchdog($log_entry) {
|
||||
// If an attempted recursive schema rebuild has been triggered by
|
||||
// system_test_drupal_get_filename_with_schema_rebuild(), perform the rebuild
|
||||
// in response to the missing file message triggered by system_test_schema().
|
||||
if (!variable_get('system_test_drupal_get_filename_attempt_recursive_rebuild')) {
|
||||
return;
|
||||
}
|
||||
if ($log_entry['type'] != 'php' || $log_entry['severity'] != WATCHDOG_WARNING) {
|
||||
return;
|
||||
}
|
||||
$module_name = variable_get('system_test_drupal_get_filename_test_module_name');
|
||||
if (!isset($log_entry['variables']['!message']) || strpos($log_entry['variables']['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $module_name))) === FALSE) {
|
||||
return;
|
||||
}
|
||||
variable_set('system_test_drupal_get_filename_with_schema_rebuild_final_tables', array_keys(drupal_get_schema()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_module_implements_alter().
|
||||
*/
|
||||
function system_test_module_implements_alter(&$implementations, $hook) {
|
||||
// For BootstrapGetFilenameWebTestCase::testRecursiveRebuilds() to work
|
||||
// correctly, this module's hook_schema() implementation cannot be either the
|
||||
// first implementation (since that would trigger a potential recursive
|
||||
// rebuild before anything is in the drupal_get_schema() cache) or the last
|
||||
// implementation (since that would trigger a potential recursive rebuild
|
||||
// after the cache is already complete). So put it somewhere in the middle.
|
||||
if ($hook == 'schema') {
|
||||
$group = $implementations['system_test'];
|
||||
unset($implementations['system_test']);
|
||||
$count = count($implementations);
|
||||
$implementations = array_merge(array_slice($implementations, 0, $count / 2, TRUE), array('system_test' => $group), array_slice($implementations, $count / 2, NULL, TRUE));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface {
|
|||
* found on your system, and if there was a copy in sites/all, we'd see it.
|
||||
*/
|
||||
public function getInstallDirectory() {
|
||||
if ($relative_path = drupal_get_path('module', $this->name)) {
|
||||
if ($this->isInstalled() && ($relative_path = drupal_get_path('module', $this->name))) {
|
||||
$relative_path = dirname($relative_path);
|
||||
}
|
||||
else {
|
||||
|
@ -34,7 +34,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface {
|
|||
}
|
||||
|
||||
public function isInstalled() {
|
||||
return (bool) drupal_get_path('module', $this->name);
|
||||
return (bool) drupal_get_filename('module', $this->name, NULL, FALSE);
|
||||
}
|
||||
|
||||
public static function canUpdateDirectory($directory) {
|
||||
|
@ -109,7 +109,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface {
|
|||
* found on your system, and if there was a copy in sites/all, we'd see it.
|
||||
*/
|
||||
public function getInstallDirectory() {
|
||||
if ($relative_path = drupal_get_path('theme', $this->name)) {
|
||||
if ($this->isInstalled() && ($relative_path = drupal_get_path('theme', $this->name))) {
|
||||
$relative_path = dirname($relative_path);
|
||||
}
|
||||
else {
|
||||
|
@ -119,7 +119,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface {
|
|||
}
|
||||
|
||||
public function isInstalled() {
|
||||
return (bool) drupal_get_path('theme', $this->name);
|
||||
return (bool) drupal_get_filename('theme', $this->name, NULL, FALSE);
|
||||
}
|
||||
|
||||
static function canUpdateDirectory($directory) {
|
||||
|
|
Loading…
Reference in New Issue