diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 26a75f6..1bcec37 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -208,10 +208,23 @@ function config_get_config_directory($type = CONFIG_ACTIVE_DIRECTORY) { * The filename of the requested item or NULL if the item is not found. */ function drupal_get_filename($type, $name, $filename = NULL) { + // Return NULL right away if $type or $name is empty. + if (empty($type) || empty($name)) { + return NULL; + } + // The location of files will not change during the request, so do not use // drupal_static(). static $files = array(); + // We use drupal static for the missing records so we can test it. + // Drupal static fast pattern is used as this function may be called often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['missing'] = &drupal_static(__FUNCTION__ . ':missing'); + } + $missing = &$drupal_static_fast['missing']; + // Type 'core' only exists to simplify application-level logic; it always maps // to the /core directory, whereas $name is ignored. It is only requested via // drupal_get_path(). /core/core.info.yml does not exist, but is required @@ -251,13 +264,24 @@ function drupal_get_filename($type, $name, $filename = NULL) { } // If still unknown, perform a filesystem scan. if (!isset($files[$type][$name])) { - $listing = new ExtensionDiscovery(DRUPAL_ROOT); - // Prevent an infinite recursion by this legacy function. - if ($original_type == 'profile') { - $listing->setProfileDirectories(array()); + if (!isset($missing)) { + $missing = array(); + if (\Drupal::hasService('cache.bootstrap')) { + $cache = \Drupal::cache('bootstrap')->get('drupal_get_filename:missing'); + if ($cache && $cache->data) { + $missing = $cache->data; + } + } } - foreach ($listing->scan($original_type) as $extension_name => $file) { - $files[$type][$extension_name] = $file->getPathname(); + if (!isset($missing[$type][$name])) { + $listing = new ExtensionDiscovery(DRUPAL_ROOT); + // Prevent an infinite recursion by this legacy function. + if ($original_type == 'profile') { + $listing->setProfileDirectories(array()); + } + foreach ($listing->scan($original_type) as $extension_name => $file) { + $files[$type][$extension_name] = $file->getPathname(); + } } } } @@ -265,6 +289,16 @@ function drupal_get_filename($type, $name, $filename = NULL) { if (isset($files[$type][$name])) { return $files[$type][$name]; } + elseif (!isset($missing[$type][$name])) { + // Add the missing file to a temporary cache and throw an alert. This cache + // will be cleared on cron runs as well as when visiting the module and + // theme list pages. + $missing[$type][$name] = TRUE; + if (\Drupal::hasService('cache.bootstrap')) { + \Drupal::cache('bootstrap')->set('drupal_get_filename:missing', $missing, REQUEST_TIME + 24 * 60 * 60); + } + trigger_error(SafeMarkup::format('The following @type is missing from the file system: @name', array('@type' => $type, '@name' => $name)), E_USER_WARNING); + } } /** diff --git a/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php b/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php index 3cb8ab0..0285432 100644 --- a/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php +++ b/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php @@ -27,7 +27,6 @@ class CommentStringIdEntitiesTest extends KernelTestBase { 'user', 'field', 'field_ui', - 'entity', 'entity_test', 'text', ); diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php index 73180a0..34e0b48 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -186,6 +186,10 @@ public function systemAdminMenuBlockPage() { * @todo Move into ThemeController. */ public function themesPage() { + // Clean up the bootstrap "missing files" cache when listing themes. + \Drupal::cache('bootstrap')->delete('drupal_get_filename:missing'); + drupal_static_reset('drupal_get_filename:missing'); + $config = $this->config('system.theme'); // Get all available themes. $themes = $this->themeHandler->rebuildThemeData(); diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index eb894f6..f07a0ed 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -174,6 +174,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Include system.admin.inc so we can use the sort callbacks. $this->moduleHandler->loadInclude('system', 'inc', 'system.admin'); + // Clean up the "missing files" cache when listing modules. + \Drupal::cache('bootstrap')->delete('drupal_get_filename:missing'); + drupal_static_reset('drupal_get_filename:missing'); + $form['filters'] = array( '#type' => 'container', '#attributes' => array( diff --git a/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php b/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php index f867045..7dcabe5 100644 --- a/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php +++ b/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php @@ -70,7 +70,26 @@ function testDrupalGetFilename() { // a fixed location and naming. $this->assertIdentical(drupal_get_filename('profile', 'standard'), 'core/profiles/standard/standard.info.yml'); + // Generate a non-existing module name. + $non_existing_module = uniqid("", TRUE); + + // Set a custom error handler so we can ignore the file not found error. + set_error_handler(function($severity, $message, $file, $line) { + // Skip error handling if this is a "file not found" error. + if (!(error_reporting() & $severity) || strstr($message, 'is missing from the file system:')) { + return; + } + throw new ErrorException($message, 0, $severity, $file, $line); + } // Searching for an item that does not exist returns NULL. - $this->assertNull(drupal_get_filename('module', uniqid("", TRUE)), 'Searching for an item that does not exist returns NULL.'); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for an item that does not exist returns NULL.'); + // 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.'); } } diff --git a/core/modules/system/src/Tests/Common/AttachedAssetsTest.php b/core/modules/system/src/Tests/Common/AttachedAssetsTest.php index a52db9b..6e62d06 100644 --- a/core/modules/system/src/Tests/Common/AttachedAssetsTest.php +++ b/core/modules/system/src/Tests/Common/AttachedAssetsTest.php @@ -74,10 +74,20 @@ function testDefault() { * Tests non-existing libraries. */ function testLibraryUnknown() { + // Set a custom error handler so we can ignore the file not found error. + set_error_handler(function($severity, $message, $file, $line) { + // Skip error handling if this is a "file not found" error. + if (!(error_reporting() & $severity) || strstr($message, 'is missing from the file system:')) { + return; + } + throw new ErrorException($message, 0, $severity, $file, $line); + } $build['#attached']['library'][] = 'unknown/unknown'; $assets = AttachedAssets::createFromRenderArray($build); $this->assertIdentical([], $this->assetResolver->getJsAssets($assets, FALSE)[0], 'Unknown library was not added to the page.'); + // Restore the original error handler. + restore_error_handler(); } /** diff --git a/core/modules/system/src/Tests/Entity/EntitySchemaTest.php b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php index 6f6f39d..25ff4f5 100644 --- a/core/modules/system/src/Tests/Entity/EntitySchemaTest.php +++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php @@ -28,7 +28,7 @@ class EntitySchemaTest extends EntityUnitTestBase { * * @var array */ - public static $modules = array('menu_link'); + public static $modules = array('menu_link_content'); /** * {@inheritdoc} diff --git a/core/modules/taxonomy/src/Tests/TermKernelTest.php b/core/modules/taxonomy/src/Tests/TermKernelTest.php index 6692775..3d08826 100644 --- a/core/modules/taxonomy/src/Tests/TermKernelTest.php +++ b/core/modules/taxonomy/src/Tests/TermKernelTest.php @@ -23,7 +23,7 @@ class TermKernelTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = array( 'filter', 'taxonomy', 'taxonomy_term', 'text', 'user' ); + public static $modules = array( 'filter', 'taxonomy', 'text', 'user' ); /** * {@inheritdoc} diff --git a/core/modules/views/src/Tests/Plugin/RowEntityTest.php b/core/modules/views/src/Tests/Plugin/RowEntityTest.php index 35bd1e2..7de0305 100644 --- a/core/modules/views/src/Tests/Plugin/RowEntityTest.php +++ b/core/modules/views/src/Tests/Plugin/RowEntityTest.php @@ -24,7 +24,7 @@ class RowEntityTest extends ViewUnitTestBase { * * @var array */ - public static $modules = ['taxonomy', 'text', 'filter', 'field', 'entity', 'system', 'node', 'user']; + public static $modules = ['taxonomy', 'text', 'filter', 'field', 'system', 'node', 'user']; /** * Views used by this test.