Issue #2188661 by sun, Berdir: Extension System, Part II: ExtensionDiscovery.
parent
438494d0f1
commit
ae8be3cc96
core
lib/Drupal/Core
Config
Utility
modules
block/tests/modules/block_test
breakpoint
lib/Drupal/breakpoint/Tests
config/tests/config_test/lib/Drupal/config_test
config_translation/tests/modules/config_translation_test
locale
simpletest
lib/Drupal/simpletest
system
lib/Drupal/system/Tests
Bootstrap
Common
Extension
System
tests
modules
ajax_test
serialization_test
theme_page_test
theme_test
update
tests/Drupal/Tests/Core/Extension
themes/engines
phptemplate
twig
|
@ -10,6 +10,7 @@ use Drupal\Component\Utility\Url;
|
|||
use Drupal\Core\DrupalKernel;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Utility\Title;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Symfony\Component\ClassLoader\ApcClassLoader;
|
||||
|
@ -655,7 +656,7 @@ function _drupal_request_initialize() {
|
|||
function drupal_get_filename($type, $name, $filename = NULL) {
|
||||
// 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 converted into modules in system_rebuild_module_data().
|
||||
// @todo Remove false-exposure of profiles as modules.
|
||||
|
@ -667,72 +668,31 @@ function drupal_get_filename($type, $name, $filename = NULL) {
|
|||
$files[$type] = array();
|
||||
}
|
||||
|
||||
if (!empty($filename)) {
|
||||
if (isset($filename)) {
|
||||
$files[$type][$name] = $filename;
|
||||
}
|
||||
elseif (isset($files[$type][$name])) {
|
||||
// nothing
|
||||
}
|
||||
else {
|
||||
// Verify that we have an keyvalue service before using it. This is required
|
||||
// because this function is called during installation.
|
||||
// @todo Inject database connection into KeyValueStore\DatabaseStorage.
|
||||
if (\Drupal::hasService('keyvalue') && function_exists('db_query')) {
|
||||
if ($type == 'module') {
|
||||
if (empty($files[$type])) {
|
||||
$files[$type] = \Drupal::moduleHandler()->getModuleList();
|
||||
}
|
||||
if (isset($files[$type][$name])) {
|
||||
return $files[$type][$name];
|
||||
}
|
||||
}
|
||||
try {
|
||||
$file_list = \Drupal::state()->get('system.' . $type . '.files');
|
||||
if ($file_list && isset($file_list[$name]) && file_exists(DRUPAL_ROOT . '/' . $file_list[$name])) {
|
||||
$files[$type][$name] = $file_list[$name];
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// The keyvalue service raised an exception because the backend might
|
||||
// be down. We have a fallback for this case so we hide the error
|
||||
// completely.
|
||||
}
|
||||
elseif (!isset($files[$type][$name])) {
|
||||
// If the pathname of the requested extension is not known, try to retrieve
|
||||
// the list of extension pathnames from various providers, checking faster
|
||||
// providers first.
|
||||
// Retrieve the current module list (derived from the service container).
|
||||
if ($type == 'module' && \Drupal::hasService('module_handler')) {
|
||||
$files[$type] += \Drupal::moduleHandler()->getModuleList();
|
||||
}
|
||||
// Fallback to searching the filesystem if the database could not find the
|
||||
// file or the file returned by the database is not found.
|
||||
// If still unknown, retrieve the file list prepared in state by
|
||||
// system_rebuild_module_data() and system_rebuild_theme_data().
|
||||
if (!isset($files[$type][$name]) && \Drupal::hasService('state')) {
|
||||
$files[$type] += \Drupal::state()->get('system.' . $type . '.files', array());
|
||||
}
|
||||
// If still unknown, perform a filesystem scan.
|
||||
if (!isset($files[$type][$name])) {
|
||||
// We have consistent directory naming: modules, themes...
|
||||
$dir = $type . 's';
|
||||
if ($type == 'theme_engine') {
|
||||
$dir = 'themes/engines';
|
||||
$extension = 'engine';
|
||||
$listing = new ExtensionDiscovery();
|
||||
// Prevent an infinite recursion by this legacy function.
|
||||
if ($original_type == 'profile') {
|
||||
$listing->setProfileDirectories(array());
|
||||
}
|
||||
elseif ($type == 'theme') {
|
||||
$extension = 'info.yml';
|
||||
}
|
||||
// Profiles are converted into modules in system_rebuild_module_data().
|
||||
// @todo Remove false-exposure of profiles as modules.
|
||||
elseif ($original_type == 'profile') {
|
||||
$dir = 'profiles';
|
||||
$extension = 'profile';
|
||||
}
|
||||
else {
|
||||
$extension = $type;
|
||||
}
|
||||
|
||||
if (!isset($dirs[$dir][$extension])) {
|
||||
$dirs[$dir][$extension] = TRUE;
|
||||
if (!function_exists('drupal_system_listing')) {
|
||||
require_once __DIR__ . '/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);
|
||||
foreach ($matches as $matched_name => $file) {
|
||||
$files[$type][$matched_name] = $file->uri;
|
||||
}
|
||||
foreach ($listing->scan($original_type) as $extension_name => $file) {
|
||||
$files[$type][$extension_name] = $file->uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ use Drupal\Component\Utility\MapArray;
|
|||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\Core\Routing\GeneratorNotInitializedException;
|
||||
use Drupal\Core\SystemListingInfo;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
|
@ -3303,19 +3302,6 @@ function drupal_page_set_cache(Response $response, Request $request) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is kept only for backward compatibility.
|
||||
*
|
||||
* @see \Drupal\Core\SystemListing::scan().
|
||||
*/
|
||||
function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) {
|
||||
// As SystemListing is required to build a dependency injection container
|
||||
// from scratch and SystemListingInfo only extends SystemLising, this
|
||||
// class needs to be hardwired.
|
||||
$listing = new SystemListingInfo();
|
||||
return $listing->scan($mask, $directory, $key, $min_depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main page content value for later use.
|
||||
*
|
||||
|
|
|
@ -13,7 +13,7 @@ use Drupal\Core\Database\Install\TaskException;
|
|||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Language\LanguageManager;
|
||||
use Drupal\Core\StringTranslation\Translator\FileTranslation;
|
||||
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
@ -494,7 +494,21 @@ function install_begin_request(&$install_state) {
|
|||
// Override the module list with a minimal set of modules.
|
||||
$module_handler->setModuleList(array('system' => 'core/modules/system/system.module'));
|
||||
}
|
||||
$module_handler->load('system');
|
||||
// After setting up a custom and finite module list in a custom low-level
|
||||
// bootstrap like here, ensure to use ModuleHandler::loadAll() so that
|
||||
// ModuleHandler::isLoaded() returns TRUE, since that is a condition being
|
||||
// checked by other subsystems (e.g., the theme system).
|
||||
$module_handler->loadAll();
|
||||
|
||||
// Add list of all available profiles to the installation state.
|
||||
$listing = new ExtensionDiscovery();
|
||||
$listing->setProfileDirectories(array());
|
||||
$install_state['profiles'] += $listing->scan('profile');
|
||||
|
||||
// Prime drupal_get_filename()'s static cache.
|
||||
foreach ($install_state['profiles'] as $name => $profile) {
|
||||
drupal_get_filename('profile', $name, $profile->uri);
|
||||
}
|
||||
|
||||
// Prepare for themed output. We need to run this at the beginning of the
|
||||
// page request to avoid a different theme accidentally getting set. (We also
|
||||
|
@ -528,9 +542,6 @@ function install_begin_request(&$install_state) {
|
|||
|
||||
// Modify the installation state as appropriate.
|
||||
$install_state['completed_task'] = $task;
|
||||
|
||||
// Add the list of available profiles to the installation state.
|
||||
$install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,7 @@ use Drupal\Component\Utility\Crypt;
|
|||
use Drupal\Component\Utility\Settings;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Requirement severity -- Informational message only.
|
||||
|
@ -123,22 +124,17 @@ function drupal_detect_database_types() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns all supported database installer objects that are compiled into PHP.
|
||||
* Returns all supported database driver installer objects.
|
||||
*
|
||||
* @return
|
||||
* An array of database installer objects compiled into PHP.
|
||||
* @return \Drupal\Core\Database\Install\Tasks[]
|
||||
* An array of available database driver installer objects.
|
||||
*/
|
||||
function drupal_get_database_types() {
|
||||
$databases = array();
|
||||
$drivers = array();
|
||||
|
||||
// We define a driver as a directory in /core/includes/database that in turn
|
||||
// contains a database.inc file. That allows us to drop in additional drivers
|
||||
// without modifying the installer.
|
||||
require_once __DIR__ . '/database.inc';
|
||||
// Allow any valid PHP identifier.
|
||||
// @see http://www.php.net/manual/en/language.variables.basics.php.
|
||||
$mask = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';
|
||||
// The internal database driver name is any valid PHP identifier.
|
||||
$mask = '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '$/';
|
||||
$files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, array('recurse' => FALSE));
|
||||
if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) {
|
||||
$files += file_scan_directory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, array('recurse' => FALSE));
|
||||
|
@ -584,15 +580,16 @@ function drupal_verify_profile($install_state) {
|
|||
}
|
||||
$info = $install_state['profile_info'];
|
||||
|
||||
// Get a list of modules that exist in Drupal's assorted subdirectories.
|
||||
// Get the list of available modules for the selected installation profile.
|
||||
$listing = new ExtensionDiscovery();
|
||||
$present_modules = array();
|
||||
foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules') as $present_module) {
|
||||
foreach ($listing->scan('module') as $present_module) {
|
||||
$present_modules[] = $present_module->name;
|
||||
}
|
||||
|
||||
// The installation profile is also a module, which needs to be installed
|
||||
// after all the other dependencies have been installed.
|
||||
$present_modules[] = drupal_get_profile();
|
||||
$present_modules[] = $profile;
|
||||
|
||||
// Verify that all of the profile's required modules are present.
|
||||
$missing_modules = array_diff($info['dependencies'], $present_modules);
|
||||
|
@ -976,9 +973,7 @@ function drupal_requirements_url($severity) {
|
|||
function drupal_check_profile($profile, array $install_state) {
|
||||
include_once __DIR__ . '/file.inc';
|
||||
|
||||
$profile_file = $install_state['profiles'][$profile]->uri;
|
||||
|
||||
if (!isset($profile) || !file_exists($profile_file)) {
|
||||
if (!isset($profile) || !isset($install_state['profiles'][$profile])) {
|
||||
throw new Exception(install_no_profile_error());
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Builds a list of bootstrap modules and enabled modules and themes.
|
||||
|
@ -298,14 +299,19 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE)
|
|||
* Returns an array of modules required by core.
|
||||
*/
|
||||
function drupal_required_modules() {
|
||||
$files = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'modules');
|
||||
$listing = new ExtensionDiscovery();
|
||||
$files = $listing->scan('module');
|
||||
$required = array();
|
||||
|
||||
// An installation profile is required and one must always be loaded.
|
||||
$required[] = drupal_get_profile();
|
||||
// Unless called by the installer, an installation profile is required and
|
||||
// must always be loaded. drupal_get_profile() also returns the installation
|
||||
// profile in the installer, but only after it has been selected.
|
||||
if ($profile = drupal_get_profile()) {
|
||||
$required[] = $profile;
|
||||
}
|
||||
|
||||
foreach ($files as $name => $file) {
|
||||
$info = \Drupal::service('info_parser')->parse($file->uri);
|
||||
$info = \Drupal::service('info_parser')->parse($file->getPathname());
|
||||
if (!empty($info) && !empty($info['required']) && $info['required']) {
|
||||
$required[] = $name;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use Drupal\Component\Utility\String;
|
|||
use Drupal\Component\Utility\Url;
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
use Drupal\Core\Extension\ExtensionNameLengthException;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Template\RenderWrapper;
|
||||
|
@ -65,10 +66,10 @@ const RESPONSIVE_PRIORITY_LOW = 'priority-low';
|
|||
/**
|
||||
* Determines if a theme is available to use.
|
||||
*
|
||||
* @param $theme
|
||||
* @param string|\Drupal\Core\Extension\Extension $theme
|
||||
* Either the name of a theme or a full theme object.
|
||||
*
|
||||
* @return
|
||||
* @return bool
|
||||
* Boolean TRUE if the theme is enabled or is the site administration theme;
|
||||
* FALSE otherwise.
|
||||
*
|
||||
|
@ -78,7 +79,7 @@ const RESPONSIVE_PRIORITY_LOW = 'priority-low';
|
|||
* @see \Drupal\Core\Theme\ThemeAccessCheck::checkAccess().
|
||||
*/
|
||||
function drupal_theme_access($theme) {
|
||||
if (is_object($theme)) {
|
||||
if ($theme instanceof Extension) {
|
||||
$theme = $theme->name;
|
||||
}
|
||||
return \Drupal::service('access_check.theme')->checkAccess($theme);
|
||||
|
@ -121,19 +122,9 @@ function drupal_theme_initialize() {
|
|||
*
|
||||
* This function is useful to initialize a theme when no database is present.
|
||||
*
|
||||
* @param $theme
|
||||
* An object with the following information:
|
||||
* filename
|
||||
* The .info.yml file for this theme. The 'path' to
|
||||
* the theme will be in this file's directory. (Required)
|
||||
* owner
|
||||
* The path to the .theme file or the .engine file to load for
|
||||
* the theme. (Required)
|
||||
* stylesheet
|
||||
* The primary stylesheet for the theme. (Optional)
|
||||
* engine
|
||||
* The name of theme engine to use. (Optional)
|
||||
* @param $base_theme
|
||||
* @param \Drupal\Core\Extension\Extension $theme
|
||||
* The theme extension object.
|
||||
* @param \Drupal\Core\Extension\Extension[] $base_theme
|
||||
* An optional array of objects that represent the 'base theme' if the
|
||||
* theme is meant to be derivative of another theme. It requires
|
||||
* the same information as the $theme object. It should be in
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Storage controller used by the Drupal installer.
|
||||
*
|
||||
|
@ -110,9 +112,14 @@ class InstallStorage extends FileStorage {
|
|||
*/
|
||||
protected function getAllFolders() {
|
||||
if (!isset($this->folders)) {
|
||||
$this->folders = $this->getComponentNames('profile', array(drupal_get_profile()));
|
||||
$this->folders += $this->getComponentNames('module', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0)));
|
||||
$this->folders += $this->getComponentNames('theme', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes')));
|
||||
$this->folders = array();
|
||||
// @todo Refactor getComponentNames() to use the extension list directly.
|
||||
if ($profile = drupal_get_profile()) {
|
||||
$this->folders += $this->getComponentNames('profile', array($profile));
|
||||
}
|
||||
$listing = new ExtensionDiscovery();
|
||||
$this->folders += $this->getComponentNames('module', array_keys($listing->scan('module')));
|
||||
$this->folders += $this->getComponentNames('theme', array_keys($listing->scan('theme')));
|
||||
}
|
||||
return $this->folders;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use Drupal\Core\CoreServiceProvider;
|
|||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
|
||||
use Drupal\Core\DependencyInjection\YamlFileLoader;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
|
@ -83,7 +84,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
|
|||
* An array of module data objects.
|
||||
*
|
||||
* The data objects have the same data structure as returned by
|
||||
* file_scan_directory() but only the uri property is used.
|
||||
* ExtensionDiscovery but only the uri property is used.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
@ -297,19 +298,28 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
|
|||
protected function moduleData($module) {
|
||||
if (!$this->moduleData) {
|
||||
// First, find profiles.
|
||||
$profiles_scanner = new SystemListing();
|
||||
$all_profiles = $profiles_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
|
||||
$profiles = array_keys(array_intersect_key($this->moduleList, $all_profiles));
|
||||
$listing = new ExtensionDiscovery();
|
||||
$listing->setProfileDirectories(array());
|
||||
$all_profiles = $listing->scan('profile');
|
||||
$profiles = array_intersect_key($all_profiles, $this->moduleList);
|
||||
|
||||
// If a module is within a profile directory but specifies another
|
||||
// profile for testing, it needs to be found in the parent profile.
|
||||
if (($parent_profile_config = $this->configStorage->read('simpletest.settings')) && isset($parent_profile_config['parent_profile']) && $parent_profile_config['parent_profile'] != $profiles[0]) {
|
||||
$settings = $this->configStorage->read('simpletest.settings');
|
||||
$parent_profile = !empty($settings['parent_profile']) ? $settings['parent_profile'] : NULL;
|
||||
if ($parent_profile && !isset($profiles[$parent_profile])) {
|
||||
// In case both profile directories contain the same extension, the
|
||||
// actual profile always has precedence.
|
||||
array_unshift($profiles, $parent_profile_config['parent_profile']);
|
||||
$profiles = array($parent_profile => $all_profiles[$parent_profile]) + $profiles;
|
||||
}
|
||||
|
||||
$profile_directories = array_map(function ($profile) {
|
||||
return $profile->getPath();
|
||||
}, $profiles);
|
||||
$listing->setProfileDirectories($profile_directories);
|
||||
|
||||
// Now find modules.
|
||||
$modules_scanner = new SystemListing($profiles);
|
||||
$this->moduleData = $all_profiles + $modules_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules');
|
||||
$this->moduleData = $profiles + $listing->scan('module');
|
||||
}
|
||||
return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Extension\Discovery;
|
||||
|
||||
/**
|
||||
* Filters a RecursiveDirectoryIterator to discover extensions.
|
||||
*
|
||||
* To ensure the best possible performance for extension discovery, this
|
||||
* filter implementation hard-codes a range of assumptions about directories
|
||||
* in which Drupal extensions may appear and in which not. Every unnecessary
|
||||
* subdirectory tree recursion is avoided.
|
||||
*
|
||||
* The list of globally ignored directory names is defined in the
|
||||
* RecursiveExtensionFilterIterator::$blacklist property.
|
||||
*
|
||||
* In addition, all 'config' directories are skipped, unless the directory path
|
||||
* ends with 'modules/config', so as to still find the config module provided by
|
||||
* Drupal core and still allow that module to be overridden with a custom config
|
||||
* module.
|
||||
*
|
||||
* Lastly, ExtensionDiscovery instructs this filter to additionally skip all
|
||||
* 'tests' directories at regular runtime, since just with Drupal core only, the
|
||||
* discovery process yields 4x more extensions when tests are not ignored.
|
||||
*
|
||||
* @see ExtensionDiscovery::scan()
|
||||
* @see ExtensionDiscovery::scanDirectory()
|
||||
*
|
||||
* @todo Use RecursiveCallbackFilterIterator instead of the $acceptTests
|
||||
* parameter forwarding once PHP 5.4 is available.
|
||||
*/
|
||||
class RecursiveExtensionFilterIterator extends \RecursiveFilterIterator {
|
||||
|
||||
/**
|
||||
* List of base extension type directory names to scan.
|
||||
*
|
||||
* Only these directory names are considered when starting a filesystem
|
||||
* recursion in a search path.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $whitelist = array(
|
||||
'profiles',
|
||||
'modules',
|
||||
'themes',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of directory names to skip when recursing.
|
||||
*
|
||||
* These directories are globally ignored in the recursive filesystem scan;
|
||||
* i.e., extensions (of all types) are not able to use any of these names,
|
||||
* because their directory names will be skipped.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $blacklist = array(
|
||||
// Object-oriented code subdirectories.
|
||||
'src',
|
||||
'lib',
|
||||
'vendor',
|
||||
// Front-end.
|
||||
'assets',
|
||||
'css',
|
||||
'files',
|
||||
'images',
|
||||
'js',
|
||||
'misc',
|
||||
'templates',
|
||||
// Legacy subdirectories.
|
||||
'includes',
|
||||
// Test subdirectories.
|
||||
'fixtures',
|
||||
// @todo ./tests/Drupal should be ./tests/src/Drupal
|
||||
'Drupal',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether to include test directories when recursing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $acceptTests = FALSE;
|
||||
|
||||
/**
|
||||
* Controls whether test directories will be scanned.
|
||||
*
|
||||
* @param bool $flag
|
||||
* Pass FALSE to skip all test directories in the discovery. If TRUE,
|
||||
* extensions in test directories will be discovered and only the global
|
||||
* directory blacklist in RecursiveExtensionFilterIterator::$blacklist is
|
||||
* applied.
|
||||
*/
|
||||
public function acceptTests($flag = FALSE) {
|
||||
$this->acceptTests = $flag;
|
||||
if (!$this->acceptTests) {
|
||||
$this->blacklist[] = 'tests';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \RecursiveFilterIterator::getChildren().
|
||||
*/
|
||||
public function getChildren() {
|
||||
$filter = parent::getChildren();
|
||||
// Pass the $acceptTests flag forward to child iterators.
|
||||
$filter->acceptTests($this->acceptTests);
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \FilterIterator::accept().
|
||||
*/
|
||||
public function accept() {
|
||||
$name = $this->current()->getFilename();
|
||||
// FilesystemIterator::SKIP_DOTS only skips '.' and '..', but not hidden
|
||||
// directories (like '.git').
|
||||
if ($name[0] == '.') {
|
||||
return FALSE;
|
||||
}
|
||||
if ($this->isDir()) {
|
||||
// If this is a subdirectory of a base search path, only recurse into the
|
||||
// fixed list of expected extension type directory names. Required for
|
||||
// scanning the top-level/root directory; without this condition, we would
|
||||
// recurse into the whole filesystem tree that possibly contains other
|
||||
// files aside from Drupal.
|
||||
if ($this->current()->getSubPath() == '') {
|
||||
return in_array($name, $this->whitelist, TRUE);
|
||||
}
|
||||
// 'config' directories are special-cased here, because every extension
|
||||
// contains one. However, those default configuration directories cannot
|
||||
// contain extensions. The directory name cannot be globally skipped,
|
||||
// because core happens to have a directory of an actual module that is
|
||||
// named 'config'. By explicitly testing for that case, we can skip all
|
||||
// other config directories, and at the same time, still allow the core
|
||||
// config module to be overridden/replaced in a profile/site directory
|
||||
// (whereas it must be located directly in a modules directory).
|
||||
if ($name == 'config') {
|
||||
return substr($this->current()->getPathname(), -14) == 'modules/config';
|
||||
}
|
||||
// Accept the directory unless the name is blacklisted.
|
||||
return !in_array($name, $this->blacklist, TRUE);
|
||||
}
|
||||
else {
|
||||
// Only accept extension info files.
|
||||
return substr($name, -9) == '.info.yml';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Extension\Extension.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Extension;
|
||||
|
||||
/**
|
||||
* Defines an extension (file) object.
|
||||
*/
|
||||
class Extension implements \Serializable {
|
||||
|
||||
/**
|
||||
* The type of the extension (e.g., 'module').
|
||||
*
|
||||
* @todo Replace all uses of $type with getType() method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* The relative pathname of the extension (e.g., 'core/modules/node/node.info.yml').
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pathname;
|
||||
|
||||
/**
|
||||
* The internal name of the extension (e.g., 'node').
|
||||
*
|
||||
* @todo Replace all uses of $name with getName() method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The relative pathname of the main extension file (e.g., 'core/modules/node/node.module').
|
||||
*
|
||||
* @todo Remove this property and do not require .module/.profile files.
|
||||
* @see https://drupal.org/node/340723
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $uri;
|
||||
|
||||
/**
|
||||
* The filename of the main extension file (e.g., 'node.module').
|
||||
*
|
||||
* Note that this is not necessarily a filename but a pathname and also not
|
||||
* necessarily the filename of the info file. Due to legacy code and property
|
||||
* value overloading, it is either the filename of the main extension file or
|
||||
* the relative pathname of the main extension file (== $uri), depending on
|
||||
* whether the object has been post-processed or not.
|
||||
*
|
||||
* @see _system_rebuild_module_data()
|
||||
* @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData()
|
||||
*
|
||||
* @todo Remove this property and do not require .module/.profile files.
|
||||
* @see https://drupal.org/node/340723
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $filename;
|
||||
|
||||
/**
|
||||
* An SplFileInfo instance for the extension's info file.
|
||||
*
|
||||
* Note that SplFileInfo is a PHP resource and resources cannot be serialized.
|
||||
*
|
||||
* @var \SplFileInfo
|
||||
*/
|
||||
protected $splFileInfo;
|
||||
|
||||
/**
|
||||
* Constructs a new Extension object.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of the extension; e.g., 'module'.
|
||||
* @param string $pathname
|
||||
* The relative path and filename of the extension's info file; e.g.,
|
||||
* 'core/modules/node/node.info.yml'.
|
||||
* @param string $filename
|
||||
* The filename of the main extension file; e.g., 'node.module'.
|
||||
*/
|
||||
public function __construct($type, $pathname, $filename) {
|
||||
$this->type = $type;
|
||||
$this->pathname = $pathname;
|
||||
// Set legacy public properties.
|
||||
$this->name = basename($pathname, '.info.yml');
|
||||
$this->filename = $filename;
|
||||
$this->uri = dirname($pathname) . '/' . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the extension.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal name of the extension.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
return basename($this->pathname, '.info.yml');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative path of the extension.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() {
|
||||
return dirname($this->pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative path and filename of the extension's info file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPathname() {
|
||||
return $this->pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename of the extension's info file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename() {
|
||||
return basename($this->pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-routes method calls to SplFileInfo.
|
||||
*
|
||||
* Offers all SplFileInfo methods to consumers; e.g., $extension->getMTime().
|
||||
*/
|
||||
public function __call($method, array $args) {
|
||||
if (!isset($this->splFileInfo)) {
|
||||
$this->splFileInfo = new \SplFileInfo($this->pathname);
|
||||
}
|
||||
return call_user_func_array(array($this->splFileInfo, $method), $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an explicit SplFileInfo object for the extension's info file.
|
||||
*
|
||||
* Used by ExtensionDiscovery::scanDirectory() to avoid creating additional
|
||||
* PHP resources.
|
||||
*
|
||||
* @param \SplFileInfo $fileinfo
|
||||
* A file info instance to set.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSplFileInfo(\SplFileInfo $fileinfo) {
|
||||
$this->splFileInfo = $fileinfo;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Serializable::serialize().
|
||||
*
|
||||
* Serializes the Extension object in the most optimized way.
|
||||
*/
|
||||
public function serialize() {
|
||||
$data = array(
|
||||
'type' => $this->type,
|
||||
'pathname' => $this->pathname,
|
||||
);
|
||||
|
||||
// Include legacy public properties.
|
||||
// @todo Remove this property and do not require .module/.profile files.
|
||||
// @see https://drupal.org/node/340723
|
||||
// @see Extension::$filename
|
||||
$data['filename'] = basename($this->uri);
|
||||
|
||||
// @todo ThemeHandler::listInfo(), ThemeHandler::rebuildThemeData(), and
|
||||
// system_list() are adding custom properties to the Extension object.
|
||||
$info = new \ReflectionObject($this);
|
||||
foreach ($info->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
|
||||
$data[$property->getName()] = $property->getValue($this);
|
||||
}
|
||||
|
||||
return serialize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Serializable::unserialize().
|
||||
*/
|
||||
public function unserialize($data) {
|
||||
$data = unserialize($data);
|
||||
$this->type = $data['type'];
|
||||
$this->pathname = $data['pathname'];
|
||||
|
||||
// Restore legacy public properties.
|
||||
// @todo Remove these properties and do not require .module/.profile files.
|
||||
// @see https://drupal.org/node/340723
|
||||
// @see Extension::$filename
|
||||
$this->name = basename($data['pathname'], '.info.yml');
|
||||
$this->uri = dirname($data['pathname']) . '/' . $data['filename'];
|
||||
$this->filename = $data['filename'];
|
||||
|
||||
// @todo ThemeHandler::listInfo(), ThemeHandler::rebuildThemeData(), and
|
||||
// system_list() are adding custom properties to the Extension object.
|
||||
foreach ($data as $property => $value) {
|
||||
if (!isset($this->$property)) {
|
||||
$this->$property = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Extension\ExtensionDiscovery.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Extension;
|
||||
|
||||
use Drupal\Component\Utility\Settings;
|
||||
use Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator;
|
||||
|
||||
/**
|
||||
* Discovers available extensions in the filesystem.
|
||||
*/
|
||||
class ExtensionDiscovery {
|
||||
|
||||
/**
|
||||
* Origin directory weight: Core.
|
||||
*/
|
||||
const ORIGIN_CORE = 0;
|
||||
|
||||
/**
|
||||
* Origin directory weight: Installation profile.
|
||||
*/
|
||||
const ORIGIN_PROFILE = 1;
|
||||
|
||||
/**
|
||||
* Origin directory weight: sites/all.
|
||||
*/
|
||||
const ORIGIN_SITES_ALL = 2;
|
||||
|
||||
/**
|
||||
* Origin directory weight: Site-wide directory.
|
||||
*/
|
||||
const ORIGIN_ROOT = 3;
|
||||
|
||||
/**
|
||||
* Origin directory weight: Parent site directory of a test site environment.
|
||||
*/
|
||||
const ORIGIN_PARENT_SITE = 4;
|
||||
|
||||
/**
|
||||
* Origin directory weight: Site-specific directory.
|
||||
*/
|
||||
const ORIGIN_SITE = 5;
|
||||
|
||||
/**
|
||||
* Regular expression to match PHP function names.
|
||||
*
|
||||
* @see http://php.net/manual/functions.user-defined.php
|
||||
*/
|
||||
const PHP_FUNCTION_PATTERN = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';
|
||||
|
||||
/**
|
||||
* InfoParser instance for parsing .info.yml files.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\InfoParser
|
||||
*/
|
||||
protected $infoParser;
|
||||
|
||||
/**
|
||||
* Previously discovered files keyed by origin directory and extension type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $files = array();
|
||||
|
||||
/**
|
||||
* List of installation profile directories to additionally scan.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $profileDirectories;
|
||||
|
||||
/**
|
||||
* Discovers available extensions of a given type.
|
||||
*
|
||||
* Finds all extensions (modules, themes, etc) that exist on the site. It
|
||||
* searches in several locations. For instance, to discover all available
|
||||
* modules:
|
||||
* @code
|
||||
* $listing = new ExtensionDiscovery();
|
||||
* $modules = $listing->scan('module');
|
||||
* @endcode
|
||||
*
|
||||
* The following directories will be searched (in the order stated):
|
||||
* - the core directory; i.e., /core
|
||||
* - the installation profile directory; e.g., /core/profiles/standard
|
||||
* - the legacy site-wide directory; i.e., /sites/all
|
||||
* - the site-wide directory; i.e., /
|
||||
* - the site-specific directory; e.g., /sites/example.com
|
||||
*
|
||||
* The information is returned in an associative array, keyed by the extension
|
||||
* name (without .info.yml extension). Extensions found later in the search
|
||||
* will take precedence over extensions found earlier - unless they are not
|
||||
* compatible with the current version of Drupal core.
|
||||
*
|
||||
* @param string $type
|
||||
* The extension type to search for. One of 'profile', 'module', 'theme', or
|
||||
* 'theme_engine'.
|
||||
* @param bool $include_tests
|
||||
* (optional) Whether to explicitly include or exclude test extensions. By
|
||||
* default, test extensions are only discovered when in a test environment.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* An associative array of Extension objects, keyed by extension name.
|
||||
*/
|
||||
public function scan($type, $include_tests = NULL) {
|
||||
// Determine the installation profile directories to scan for extensions,
|
||||
// unless explicit profile directories have been set.
|
||||
if (!isset($this->profileDirectories)) {
|
||||
$this->setProfileDirectoriesFromSettings();
|
||||
}
|
||||
|
||||
// Search the core directory.
|
||||
$searchdirs[static::ORIGIN_CORE] = 'core';
|
||||
|
||||
// Search the legacy sites/all directory.
|
||||
$searchdirs[static::ORIGIN_SITES_ALL] = 'sites/all';
|
||||
|
||||
// Search for contributed and custom extensions in top-level directories.
|
||||
// The scan uses a whitelist to limit recursion to the expected extension
|
||||
// type specific directory names only.
|
||||
$searchdirs[static::ORIGIN_ROOT] = '';
|
||||
|
||||
// Simpletest uses the regular built-in multi-site functionality of Drupal
|
||||
// for running web tests. As a consequence, extensions of the parent site
|
||||
// located in a different site-specific directory are not discovered in a
|
||||
// test site environment, because the site directories are not the same.
|
||||
// Therefore, add the site directory of the parent site to the search paths,
|
||||
// so that contained extensions are still discovered.
|
||||
// @see \Drupal\simpletest\WebTestBase::setUp()
|
||||
if ($parent_site = Settings::getSingleton()->get('test_parent_site')) {
|
||||
$searchdirs[static::ORIGIN_PARENT_SITE] = $parent_site;
|
||||
}
|
||||
|
||||
// Search the site-specific directory.
|
||||
$searchdirs[static::ORIGIN_SITE] = conf_path();
|
||||
|
||||
// Unless an explicit value has been passed, manually check whether we are
|
||||
// in a test environment, in which case test extensions must be included.
|
||||
if (!isset($include_tests)) {
|
||||
$include_tests = (bool) drupal_valid_test_ua();
|
||||
}
|
||||
|
||||
$files = array();
|
||||
foreach ($searchdirs as $dir) {
|
||||
// Discover all extensions in the directory, unless we did already.
|
||||
if (!isset(static::$files[$dir][$include_tests])) {
|
||||
static::$files[$dir][$include_tests] = $this->scanDirectory($dir, $include_tests);
|
||||
}
|
||||
// Only return extensions of the requested type.
|
||||
if (isset(static::$files[$dir][$include_tests][$type])) {
|
||||
$files += static::$files[$dir][$include_tests][$type];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the discovered extensions by their originating directories and,
|
||||
// if applicable, filter out extensions that do not belong to the current
|
||||
// installation profiles.
|
||||
$origin_weights = array_flip($searchdirs);
|
||||
$origins = array();
|
||||
$profiles = array();
|
||||
foreach ($files as $key => $file) {
|
||||
// If the extension does not belong to a profile, just apply the weight
|
||||
// of the originating directory.
|
||||
if (strpos($file->getSubPath(), 'profiles') !== 0) {
|
||||
$origins[$key] = $origin_weights[$file->origin];
|
||||
$profiles[$key] = NULL;
|
||||
}
|
||||
// If the extension belongs to a profile but no profile directories are
|
||||
// defined, then we are scanning for installation profiles themselves.
|
||||
// In this case, profiles are sorted by origin only.
|
||||
elseif (empty($this->profileDirectories)) {
|
||||
$origins[$key] = static::ORIGIN_PROFILE;
|
||||
$profiles[$key] = NULL;
|
||||
}
|
||||
else {
|
||||
// Apply the weight of the originating profile directory.
|
||||
foreach ($this->profileDirectories as $weight => $profile_path) {
|
||||
if (strpos($file->getPath(), $profile_path) === 0) {
|
||||
$origins[$key] = static::ORIGIN_PROFILE;
|
||||
$profiles[$key] = $weight;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
// If we end up here, then the extension does not belong to any of the
|
||||
// current installation profile directories, so remove it.
|
||||
unset($files[$key]);
|
||||
}
|
||||
}
|
||||
// Now sort the extensions by origin and installation profile(s).
|
||||
// The result of this multisort can be depicted like the following matrix,
|
||||
// whereas the first integer is the weight of the originating directory and
|
||||
// the second is the weight of the originating installation profile:
|
||||
// 0 core/modules/node/node.module
|
||||
// 1 0 profiles/parent_profile/modules/parent_module/parent_module.module
|
||||
// 1 1 core/profiles/testing/modules/compatible_test/compatible_test.module
|
||||
// 2 sites/all/modules/common/common.module
|
||||
// 3 modules/devel/devel.module
|
||||
// 4 sites/default/modules/custom/custom.module
|
||||
array_multisort($origins, SORT_ASC, $profiles, SORT_ASC, $files);
|
||||
|
||||
// Process and return the sorted and filtered list of extensions keyed by
|
||||
// extension name.
|
||||
return $this->process($files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets installation profile directories based on current site settings.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProfileDirectoriesFromSettings() {
|
||||
$this->profileDirectories = array();
|
||||
$profile = drupal_get_profile();
|
||||
// For SimpleTest to be able to test modules packaged together with a
|
||||
// distribution we need to include the profile of the parent site (in
|
||||
// which test runs are triggered).
|
||||
if (drupal_valid_test_ua() && !drupal_installation_attempted()) {
|
||||
$testing_profile = \Drupal::config('simpletest.settings')->get('parent_profile');
|
||||
if ($testing_profile && $testing_profile != $profile) {
|
||||
$this->profileDirectories[] = drupal_get_path('profile', $testing_profile);
|
||||
}
|
||||
}
|
||||
// In case both profile directories contain the same extension, the actual
|
||||
// profile always has precedence.
|
||||
if ($profile) {
|
||||
$this->profileDirectories[] = drupal_get_path('profile', $profile);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the installation profile directories to be scanned.
|
||||
*
|
||||
* @return array
|
||||
* A list of installation profile directory paths relative to the system
|
||||
* root directory.
|
||||
*/
|
||||
public function getProfileDirectories() {
|
||||
return $this->profileDirectories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets explicit profile directories to scan.
|
||||
*
|
||||
* @param array $paths
|
||||
* A list of installation profile directory paths relative to the system
|
||||
* root directory (without trailing slash) to search for extensions.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProfileDirectories(array $paths = NULL) {
|
||||
$this->profileDirectories = $paths;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the filtered and sorted list of extensions.
|
||||
*
|
||||
* Extensions discovered in later search paths override earlier, unless they
|
||||
* are not compatible with the current version of Drupal core.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension[] $all_files
|
||||
* The sorted list of all extensions that were found.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* The filtered list of extensions, keyed by extension name.
|
||||
*/
|
||||
protected function process(array $all_files) {
|
||||
$files = array();
|
||||
// Duplicate files found in later search directories take precedence over
|
||||
// earlier ones; they replace the extension in the existing $files array.
|
||||
// The exception to this is if the later extension is not compatible with
|
||||
// the current version of Drupal core, which may occur during upgrades when
|
||||
// e.g. new modules were introduced in core while older contrib modules with
|
||||
// the same name still exist in a later search path.
|
||||
foreach ($all_files as $file) {
|
||||
if (isset($files[$file->name])) {
|
||||
// Skip the extension if it is incompatible with Drupal core.
|
||||
$info = $this->getInfoParser()->parse($file->getPathname());
|
||||
if (!isset($info['core']) || $info['core'] != \Drupal::CORE_COMPATIBILITY) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$files[$file->name] = $file;
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scans a base directory for the requested extension type.
|
||||
*
|
||||
* @param string $dir
|
||||
* A relative base directory path to scan, without trailing slash.
|
||||
* @param bool $include_tests
|
||||
* Whether to include test extensions. If FALSE, all 'tests' directories are
|
||||
* excluded in the search.
|
||||
*
|
||||
* @return array
|
||||
* An associative array whose keys are extension type names and whose values
|
||||
* are associative arrays of \Drupal\Core\Extension\Extension objects, keyed
|
||||
* by absolute path name.
|
||||
*
|
||||
* @see \Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator
|
||||
*/
|
||||
protected function scanDirectory($dir, $include_tests) {
|
||||
$files = array();
|
||||
|
||||
// In order to scan top-level directories, absolute directory paths have to
|
||||
// be used (which also improves performance, since any configured PHP
|
||||
// include_paths will not be consulted). Retain the relative originating
|
||||
// directory being scanned, so relative paths can be reconstructed below
|
||||
// (all paths are expected to be relative to DRUPAL_ROOT).
|
||||
$dir_prefix = ($dir == '' ? '' : "$dir/");
|
||||
$absolute_dir = ($dir == '' ? DRUPAL_ROOT : DRUPAL_ROOT . "/$dir");
|
||||
|
||||
if (!is_dir($absolute_dir)) {
|
||||
return $files;
|
||||
}
|
||||
// Use Unix paths regardless of platform, skip dot directories, and return
|
||||
// the RecursiveDirectoryIterator instance to have access to getSubPath(),
|
||||
// since SplFileInfo does not support relative paths.
|
||||
$flags = \FilesystemIterator::UNIX_PATHS;
|
||||
$flags |= \FilesystemIterator::SKIP_DOTS;
|
||||
$flags |= \FilesystemIterator::CURRENT_AS_SELF;
|
||||
$directory_iterator = new \RecursiveDirectoryIterator($absolute_dir, $flags);
|
||||
|
||||
// Filter the recursive scan to discover extensions only.
|
||||
// Important: Without a RecursiveFilterIterator, RecursiveDirectoryIterator
|
||||
// would recurse into the entire filesystem directory tree without any kind
|
||||
// of limitations.
|
||||
$filter = new RecursiveExtensionFilterIterator($directory_iterator);
|
||||
$filter->acceptTests($include_tests);
|
||||
|
||||
// The actual recursive filesystem scan is only invoked by instantiating the
|
||||
// RecursiveIteratorIterator.
|
||||
$iterator = new \RecursiveIteratorIterator($filter,
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY,
|
||||
// Suppress filesystem errors in case a directory cannot be accessed.
|
||||
\RecursiveIteratorIterator::CATCH_GET_CHILD
|
||||
);
|
||||
|
||||
foreach ($iterator as $key => $fileinfo) {
|
||||
// All extension names in Drupal have to be valid PHP function names due
|
||||
// to the module hook architecture.
|
||||
if (!preg_match(static::PHP_FUNCTION_PATTERN, $fileinfo->getBasename('.info.yml'))) {
|
||||
continue;
|
||||
}
|
||||
// Determine extension type from info file.
|
||||
$type = FALSE;
|
||||
$file = $fileinfo->openFile('r');
|
||||
while (!$type && !$file->eof()) {
|
||||
preg_match('@^type:\s*(\w+)\s*$@', $file->fgets(), $matches);
|
||||
if (isset($matches[1])) {
|
||||
$type = $matches[1];
|
||||
}
|
||||
}
|
||||
if (empty($type)) {
|
||||
continue;
|
||||
}
|
||||
$name = $fileinfo->getBasename('.info.yml');
|
||||
$pathname = $dir_prefix . $fileinfo->getSubPathname();
|
||||
|
||||
// Supply main extension filename being used throughout Drupal.
|
||||
// For themes, the filename is the info file itself.
|
||||
if ($type == 'theme') {
|
||||
$filename = $fileinfo->getFilename();
|
||||
}
|
||||
// For theme engines, the file extension is .engine.
|
||||
elseif ($type == 'theme_engine') {
|
||||
$filename = $name . '.engine';
|
||||
}
|
||||
// Otherwise, it is .module/.profile; i.e., the extension type.
|
||||
else {
|
||||
$filename = $name . '.' . $type;
|
||||
}
|
||||
|
||||
$extension = new Extension($type, $pathname, $filename);
|
||||
// Inject the existing RecursiveDirectoryIterator object to avoid
|
||||
// unnecessary creation of additional SplFileInfo resources.
|
||||
$extension->setSplFileInfo($fileinfo);
|
||||
// Track the originating directory for sorting purposes.
|
||||
$extension->origin = $dir;
|
||||
|
||||
$files[$type][$key] = $extension;
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a parser for .info.yml files.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\InfoParser
|
||||
* The InfoParser instance.
|
||||
*/
|
||||
protected function getInfoParser() {
|
||||
if (!isset($this->infoParser)) {
|
||||
$this->infoParser = new InfoParser();
|
||||
}
|
||||
return $this->infoParser;
|
||||
}
|
||||
|
||||
}
|
|
@ -11,9 +11,8 @@ use Drupal\Component\Utility\String;
|
|||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
|
||||
|
||||
/**
|
||||
* Class that parses Drupal module's, theme's and profile's .info.yml files.
|
||||
* Parses extension .info.yml files.
|
||||
*/
|
||||
class InfoParser implements InfoParserInterface {
|
||||
|
||||
|
@ -22,7 +21,7 @@ class InfoParser implements InfoParserInterface {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parsedInfos = array();
|
||||
protected static $parsedInfos = array();
|
||||
|
||||
/**
|
||||
* Symfony YAML parser object.
|
||||
|
@ -35,29 +34,29 @@ class InfoParser implements InfoParserInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse($filename) {
|
||||
if (!isset($this->parsedInfos[$filename])) {
|
||||
if (!isset(static::$parsedInfos[$filename])) {
|
||||
if (!file_exists($filename)) {
|
||||
$this->parsedInfos[$filename] = array();
|
||||
static::$parsedInfos[$filename] = array();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$this->parsedInfos[$filename] = $this->getParser()->parse(file_get_contents($filename));
|
||||
static::$parsedInfos[$filename] = $this->getParser()->parse(file_get_contents($filename));
|
||||
}
|
||||
catch (ParseException $e) {
|
||||
$message = String::format("Unable to parse !file. Parser error !error.", array('!file' => $filename, '!error' => $e->getMessage()));
|
||||
throw new InfoParserException($message, $filename);
|
||||
}
|
||||
$missing_keys = array_diff($this->getRequiredKeys(), array_keys($this->parsedInfos[$filename]));
|
||||
$missing_keys = array_diff($this->getRequiredKeys(), array_keys(static::$parsedInfos[$filename]));
|
||||
if (!empty($missing_keys)) {
|
||||
$message = format_plural(count($missing_keys), 'Missing required key (!missing_keys) in !file.', 'Missing required keys (!missing_keys) in !file.', array('!missing_keys' => implode(', ', $missing_keys), '!file' => $filename));
|
||||
throw new InfoParserException($message, $filename);
|
||||
}
|
||||
if (isset($this->parsedInfos[$filename]['version']) && $this->parsedInfos[$filename]['version'] === 'VERSION') {
|
||||
$this->parsedInfos[$filename]['version'] = \Drupal::VERSION;
|
||||
if (isset(static::$parsedInfos[$filename]['version']) && static::$parsedInfos[$filename]['version'] === 'VERSION') {
|
||||
static::$parsedInfos[$filename]['version'] = \Drupal::VERSION;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->parsedInfos[$filename];
|
||||
return static::$parsedInfos[$filename];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +79,7 @@ class InfoParser implements InfoParserInterface {
|
|||
* An array of required keys.
|
||||
*/
|
||||
protected function getRequiredKeys() {
|
||||
return array('name', 'type');
|
||||
return array('type', 'core', 'name');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -599,9 +599,10 @@ class ModuleHandler implements ModuleHandlerInterface {
|
|||
$this->load($module);
|
||||
module_load_install($module);
|
||||
|
||||
// Flush theme info caches, since (testing) modules can implement
|
||||
// hook_system_theme_info() to register additional themes.
|
||||
system_list_reset();
|
||||
// Clear the static cache of system_rebuild_module_data() to pick up the
|
||||
// new module, since it merges the installation status of modules into
|
||||
// its statically cached list.
|
||||
drupal_static_reset('system_rebuild_module_data');
|
||||
|
||||
// Update the kernel to include it.
|
||||
// This reboots the kernel to register the module's bundle and its
|
||||
|
@ -742,10 +743,10 @@ class ModuleHandler implements ModuleHandlerInterface {
|
|||
// Remove any potential cache bins provided by the module.
|
||||
$this->removeCacheBins($module);
|
||||
|
||||
// Refresh the system list to exclude the uninstalled modules.
|
||||
// @todo Only needed to rebuild theme info.
|
||||
// @see system_list_reset()
|
||||
system_list_reset();
|
||||
// Clear the static cache of system_rebuild_module_data() to pick up the
|
||||
// new module, since it merges the installation status of modules into
|
||||
// its statically cached list.
|
||||
drupal_static_reset('system_rebuild_module_data');
|
||||
|
||||
// Clear the entity info cache.
|
||||
entity_info_cache_clear();
|
||||
|
|
|
@ -71,7 +71,8 @@ interface ModuleHandlerInterface {
|
|||
*
|
||||
* @param array $modules
|
||||
* An array of module objects keyed by module name. Each object contains
|
||||
* information discovered during a Drupal\Core\SystemListing scan.
|
||||
* information discovered during a Drupal\Core\Extension\ExtensionDiscovery
|
||||
* scan.
|
||||
*
|
||||
* @return
|
||||
* The same array with the new keys for each module:
|
||||
|
@ -80,7 +81,7 @@ interface ModuleHandlerInterface {
|
|||
* - required_by: An array with the keys being the modules that will not work
|
||||
* without this module.
|
||||
*
|
||||
* @see \Drupal\Core\SystemListing
|
||||
* @see \Drupal\Core\Extension\ExtensionDiscovery
|
||||
*/
|
||||
public function buildModuleDependencies(array $modules);
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ use Drupal\Core\Cache\CacheBackendInterface;
|
|||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\ConfigInstallerInterface;
|
||||
use Drupal\Core\Routing\RouteBuilder;
|
||||
use Drupal\Core\SystemListingInfo;
|
||||
|
||||
/**
|
||||
* Default theme handler using the config system for enabled/disabled themes.
|
||||
|
@ -87,11 +86,11 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
protected $routeBuilder;
|
||||
|
||||
/**
|
||||
* The system listing info
|
||||
* An extension discovery instance.
|
||||
*
|
||||
* @var \Drupal\Core\SystemListingInfo
|
||||
* @var \Drupal\Core\Extension\ExtensionDiscovery
|
||||
*/
|
||||
protected $systemListingInfo;
|
||||
protected $extensionDiscovery;
|
||||
|
||||
/**
|
||||
* Constructs a new ThemeHandler.
|
||||
|
@ -110,17 +109,17 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
* database.
|
||||
* @param \Drupal\Core\Routing\RouteBuilder $route_builder
|
||||
* (optional) The route builder to rebuild the routes if a theme is enabled.
|
||||
* @param \Drupal\Core\SystemListingInfo $system_list_info
|
||||
* (optional) The system listing info.
|
||||
* @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery
|
||||
* (optional) A extension discovery instance (for unit tests).
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, SystemListingInfo $system_list_info = NULL) {
|
||||
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->cacheBackend = $cache_backend;
|
||||
$this->infoParser = $info_parser;
|
||||
$this->configInstaller = $config_installer;
|
||||
$this->routeBuilder = $route_builder;
|
||||
$this->systemListingInfo = $system_list_info;
|
||||
$this->extensionDiscovery = $extension_discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,24 +242,9 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function rebuildThemeData() {
|
||||
// Find themes.
|
||||
$listing = $this->getSystemListingInfo();
|
||||
$themes = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes', 'name', 1);
|
||||
// Allow modules to add further themes.
|
||||
if ($module_themes = $this->moduleHandler->invokeAll('system_theme_info')) {
|
||||
foreach ($module_themes as $name => $uri) {
|
||||
// @see file_scan_directory()
|
||||
$themes[$name] = (object) array(
|
||||
'uri' => $uri,
|
||||
'filename' => pathinfo($uri, PATHINFO_FILENAME),
|
||||
'name' => $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Find theme engines.
|
||||
$listing = $this->getSystemListingInfo();
|
||||
$engines = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines', 'name', 1);
|
||||
$listing = $this->getExtensionDiscovery();
|
||||
$themes = $listing->scan('theme');
|
||||
$engines = $listing->scan('theme_engine');
|
||||
|
||||
// Set defaults for theme info.
|
||||
$defaults = array(
|
||||
|
@ -288,20 +272,15 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
// Read info files for each theme.
|
||||
foreach ($themes as $key => $theme) {
|
||||
$themes[$key]->filename = $theme->uri;
|
||||
$themes[$key]->info = $this->infoParser->parse($theme->uri) + $defaults;
|
||||
|
||||
// Skip this extension if its type is not theme.
|
||||
if (!isset($themes[$key]->info['type']) || $themes[$key]->info['type'] != 'theme') {
|
||||
unset($themes[$key]);
|
||||
continue;
|
||||
}
|
||||
$themes[$key]->info = $this->infoParser->parse($theme->getPathname()) + $defaults;
|
||||
|
||||
// Add the info file modification time, so it becomes available for
|
||||
// contributed modules to use for ordering theme lists.
|
||||
$themes[$key]->info['mtime'] = filemtime($theme->uri);
|
||||
$themes[$key]->info['mtime'] = $theme->getMTime();
|
||||
|
||||
// Invoke hook_system_info_alter() to give installed modules a chance to
|
||||
// modify the data in the .info.yml files if necessary.
|
||||
// @todo Remove $type argument, obsolete with $theme->getType().
|
||||
$type = 'theme';
|
||||
$this->moduleHandler->alter('system_info', $themes[$key]->info, $themes[$key], $type);
|
||||
|
||||
|
@ -316,12 +295,10 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
$themes[$key]->template = TRUE;
|
||||
}
|
||||
|
||||
// Prefix stylesheets and scripts with module path.
|
||||
$path = dirname($theme->uri);
|
||||
// Prefix stylesheets, scripts, and screenshot with theme path.
|
||||
$path = $theme->getPath();
|
||||
$theme->info['stylesheets'] = $this->themeInfoPrefixPath($theme->info['stylesheets'], $path);
|
||||
$theme->info['scripts'] = $this->themeInfoPrefixPath($theme->info['scripts'], $path);
|
||||
|
||||
// Give the screenshot proper path information.
|
||||
if (!empty($themes[$key]->info['screenshot'])) {
|
||||
$themes[$key]->info['screenshot'] = $path . '/' . $themes[$key]->info['screenshot'];
|
||||
}
|
||||
|
@ -330,7 +307,7 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
// Now that we've established all our master themes, go back and fill in
|
||||
// data for sub-themes.
|
||||
foreach ($sub_themes as $key) {
|
||||
$themes[$key]->base_themes = $this->doGetBaseThemes($themes, $key);
|
||||
$themes[$key]->base_themes = $this->getBaseThemes($themes, $key);
|
||||
// Don't proceed if there was a problem with the root base theme.
|
||||
if (!current($themes[$key]->base_themes)) {
|
||||
continue;
|
||||
|
@ -436,23 +413,23 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
return array($base_key => NULL);
|
||||
}
|
||||
$used_themes[$base_key] = TRUE;
|
||||
return $this->getBaseThemes($themes, $base_key, $used_themes) + $current_base_theme;
|
||||
return $this->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme;
|
||||
}
|
||||
// If we get here, then this is our parent theme.
|
||||
return $current_base_theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a system listing info object.
|
||||
* Returns an extension discovery object.
|
||||
*
|
||||
* @return \Drupal\Core\SystemListingInfo
|
||||
* The system listing object.
|
||||
* @return \Drupal\Core\Extension\ExtensionDiscovery
|
||||
* The extension discovery object.
|
||||
*/
|
||||
protected function getSystemListingInfo() {
|
||||
if (!isset($this->systemListingInfo)) {
|
||||
$this->systemListingInfo = new SystemListingInfo();
|
||||
protected function getExtensionDiscovery() {
|
||||
if (!isset($this->extensionDiscovery)) {
|
||||
$this->extensionDiscovery = new ExtensionDiscovery();
|
||||
}
|
||||
return $this->systemListingInfo;
|
||||
return $this->extensionDiscovery;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,7 +37,7 @@ interface ThemeHandlerInterface {
|
|||
* Retrieved from the database, if available and the site is not in
|
||||
* maintenance mode; otherwise compiled freshly from the filesystem.
|
||||
*
|
||||
* @return array
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* An associative array of the currently available themes. The keys are the
|
||||
* themes' machine names and the values are objects having the following
|
||||
* properties:
|
||||
|
@ -81,10 +81,10 @@ interface ThemeHandlerInterface {
|
|||
public function reset();
|
||||
|
||||
/**
|
||||
* Helper function to scan and collect theme .info.yml data and their engines.
|
||||
* Scans and collects theme extension data and their engines.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of themes information.
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* An associative array of theme extensions.
|
||||
*/
|
||||
public function rebuildThemeData();
|
||||
|
||||
|
@ -94,7 +94,7 @@ interface ThemeHandlerInterface {
|
|||
* Themes can inherit templates and function implementations from earlier
|
||||
* themes.
|
||||
*
|
||||
* @param array $themes
|
||||
* @param \Drupal\Core\Extension\Extension[] $themes
|
||||
* An array of available themes.
|
||||
* @param string $theme
|
||||
* The name of the theme whose base we are looking for.
|
||||
|
|
|
@ -46,8 +46,6 @@ class UpdateModuleHandler extends ModuleHandler {
|
|||
case 'stream_wrappers':
|
||||
return array('system');
|
||||
|
||||
// This is called during rebuild to find testing themes.
|
||||
case 'system_theme_info':
|
||||
// Those are needed by user_access() to check access on update.php.
|
||||
case 'entity_type_build':
|
||||
case 'entity_load':
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\SystemListing.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core;
|
||||
|
||||
use Drupal\Component\Utility\Settings;
|
||||
|
||||
/**
|
||||
* Returns information about system object files (modules, themes, etc.).
|
||||
*
|
||||
* This class requires the list of profiles to be scanned (see
|
||||
* \Drupal\Core\SystemListing::scan) to be passed into the constructor. Also,
|
||||
* info files are not parsed.
|
||||
*/
|
||||
class SystemListing {
|
||||
|
||||
/**
|
||||
* Construct this listing object.
|
||||
*
|
||||
* @param array $profiles
|
||||
* A list of profiles to search their directories for in addition to the
|
||||
* default directories.
|
||||
*/
|
||||
function __construct($profiles = array()) {
|
||||
$this->profiles = $profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about system object files (modules, themes, etc.).
|
||||
*
|
||||
* This function is used to find all or some system object files (module
|
||||
* files, theme files, etc.) that exist on the site. It searches in several
|
||||
* locations, depending on what type of object you are looking for. For
|
||||
* instance, if you are looking for modules and call:
|
||||
* @code
|
||||
* $scanner = new SystemListing();
|
||||
* $all_modules = $scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules');
|
||||
* @endcode
|
||||
* this function will search:
|
||||
* - the core modules directory; i.e., /core/modules
|
||||
* - the profiles directories as defined by the profiles() method.
|
||||
* - the site-wide modules directory; i.e., /modules
|
||||
* - the all-sites directory; i.e., /sites/all/modules
|
||||
* - the site-specific directory; i.e., /sites/example.com/modules
|
||||
* in that order, and return information about all of the files ending in
|
||||
* .module in those directories.
|
||||
*
|
||||
* The information is returned in an associative array, which can be keyed
|
||||
* on the file name ($key = 'filename'), the file name without the extension
|
||||
* ($key = 'name'), or the full file stream URI ($key = 'uri'). If you use a
|
||||
* key of 'filename' or 'name', files found later in the search will take
|
||||
* precedence over files found earlier (unless they belong to a module or
|
||||
* theme not compatible with Drupal core); if you choose a key of 'uri',
|
||||
* you will get all files found.
|
||||
*
|
||||
* @param string $mask
|
||||
* The preg_match() regular expression for the files to find. The
|
||||
* expression must be anchored and use DRUPAL_PHP_FUNCTION_PATTERN for the
|
||||
* file name part before the extension, since the results could contain
|
||||
* matches that do not present valid Drupal extensions otherwise.
|
||||
* @param string $directory
|
||||
* The subdirectory name in which the files are found. For example,
|
||||
* 'modules' will search all 'modules' directories and their
|
||||
* sub-directories as explained above.
|
||||
* @param string $key
|
||||
* (optional) The key to be used for the associative array returned.
|
||||
* Possible values are:
|
||||
* - 'uri' for the file's URI.
|
||||
* - 'filename' for the basename of the file.
|
||||
* - 'name' for the name of the file without the extension.
|
||||
* For 'name' and 'filename' only the highest-precedence file is returned.
|
||||
* Defaults to 'name'.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of file objects, keyed on the chosen key. Each
|
||||
* element in the array is an object containing file information, with
|
||||
* properties:
|
||||
* - 'uri': Full URI of the file.
|
||||
* - 'filename': File name.
|
||||
* - 'name': Name of file without the extension.
|
||||
*/
|
||||
function scan($mask, $directory, $key = 'name') {
|
||||
if (!in_array($key, array('uri', 'filename', 'name'))) {
|
||||
$key = 'uri';
|
||||
}
|
||||
$config = conf_path();
|
||||
|
||||
// Search for the directory in core.
|
||||
$searchdir = array('core/' . $directory);
|
||||
foreach ($this->profiles($directory) as $profile) {
|
||||
$searchdir[] = $profile;
|
||||
}
|
||||
|
||||
// Always search for contributed and custom extensions in top-level
|
||||
// directories as well as sites/all/* directories. If the same extension is
|
||||
// located in both directories, then the latter wins for legacy/historical
|
||||
// reasons.
|
||||
$searchdir[] = $directory;
|
||||
$searchdir[] = 'sites/all/' . $directory;
|
||||
|
||||
// Simpletest uses the regular built-in multi-site functionality of Drupal
|
||||
// for running web tests. As a consequence, extensions of the parent site
|
||||
// located in a different site-specific directory are not discovered in a
|
||||
// test site environment, because the site directories are not the same.
|
||||
// Therefore, add the site directory of the parent site to the search paths,
|
||||
// so that contained extensions are still discovered.
|
||||
// @see \Drupal\simpletest\WebTestBase::setUp()
|
||||
if ($parent_site = Settings::getSingleton()->get('test_parent_site')) {
|
||||
$searchdir[] = $parent_site;
|
||||
}
|
||||
if (file_exists("$config/$directory")) {
|
||||
$searchdir[] = "$config/$directory";
|
||||
}
|
||||
// @todo Find a way to skip ./config directories (but not modules/config).
|
||||
$nomask = '/^(CVS|lib|templates|css|js)$/';
|
||||
$files = array();
|
||||
// Get current list of items.
|
||||
foreach ($searchdir as $dir) {
|
||||
$files = array_merge($files, $this->process($files, $this->scanDirectory($dir, $key, $mask, $nomask)));
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* List the profiles for this directory.
|
||||
*
|
||||
* This version only returns those passed to the constructor.
|
||||
*
|
||||
* @param string $directory
|
||||
* The current search directory like 'modules' or 'themes'.
|
||||
*
|
||||
* @return array
|
||||
* A list of profiles.
|
||||
*/
|
||||
protected function profiles($directory) {
|
||||
return $this->profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the files to add before adding them.
|
||||
*
|
||||
* @param array $files
|
||||
* Every file found so far.
|
||||
* @param array $files_to_add
|
||||
* The files found in a single directory.
|
||||
*
|
||||
* @return array
|
||||
* The processed list of file objects. For example, the SystemListingInfo
|
||||
* class removes files not compatible with the current core version.
|
||||
*/
|
||||
protected function process(array $files, array $files_to_add) {
|
||||
return $files_to_add;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abbreviated version of file_scan_directory().
|
||||
*
|
||||
* @param $dir
|
||||
* The base directory or URI to scan, without trailing slash.
|
||||
* @param $key
|
||||
* The key to be used for the returned associative array of files.
|
||||
* Possible values are 'uri', for the file's URI; 'filename', for the
|
||||
* basename of the file; and 'name' for the name of the file without the
|
||||
* extension.
|
||||
* @param $mask
|
||||
* The preg_match() regular expression of the files to find.
|
||||
* @param $nomask
|
||||
* The preg_match() regular expression of the files to ignore.
|
||||
*
|
||||
* @return array
|
||||
* An associative array (keyed on the chosen key) of objects with 'uri',
|
||||
* 'filename', and 'name' members corresponding to the matching files.
|
||||
*/
|
||||
protected function scanDirectory($dir, $key, $mask, $nomask) {
|
||||
$files = array();
|
||||
if (is_dir($dir)) {
|
||||
// Avoid warnings when opendir does not have the permissions to open a
|
||||
// directory.
|
||||
if ($handle = @opendir($dir)) {
|
||||
while (FALSE !== ($filename = readdir($handle))) {
|
||||
// Skip this file if it matches the nomask or starts with a dot.
|
||||
if ($filename[0] != '.' && !preg_match($nomask, $filename)) {
|
||||
$uri = "$dir/$filename";
|
||||
if (is_dir($uri)) {
|
||||
// Give priority to files in this folder by merging them in after
|
||||
// any subdirectory files.
|
||||
$files = array_merge($this->scanDirectory($uri, $key, $mask, $nomask), $files);
|
||||
}
|
||||
elseif (preg_match($mask, $filename)) {
|
||||
// Always use this match over anything already set in $files with
|
||||
// the same $options['key'].
|
||||
$file = new \stdClass();
|
||||
$file->uri = $uri;
|
||||
$file->filename = $filename;
|
||||
$file->name = pathinfo($filename, PATHINFO_FILENAME);
|
||||
$this->processFile($file);
|
||||
$files[$file->$key] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process each file object as it is found by scanDirectory().
|
||||
*
|
||||
* @param $file
|
||||
* A file object.
|
||||
*/
|
||||
protected function processFile($file) {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\SystemListingInfo.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core;
|
||||
|
||||
/**
|
||||
* Returns information about system object files (modules, themes, etc.).
|
||||
*
|
||||
* This class finds the profile directories itself and also parses info files.
|
||||
*/
|
||||
class SystemListingInfo extends SystemListing {
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\SystemListing::profiles().
|
||||
*/
|
||||
protected function profiles($directory) {
|
||||
$searchdir = array();
|
||||
// The 'core/profiles' directory contains pristine collections of modules
|
||||
// and themes as provided by a distribution. It is pristine in the same
|
||||
// way that the 'core/modules' directory is pristine for core; users
|
||||
// should avoid any modification by using the top-level or sites/<domain>
|
||||
// directories.
|
||||
$profile = drupal_get_profile();
|
||||
// For SimpleTest to be able to test modules packaged together with a
|
||||
// distribution we need to include the profile of the parent site (in
|
||||
// which test runs are triggered).
|
||||
if (drupal_valid_test_ua() && !drupal_installation_attempted()) {
|
||||
$testing_profile = \Drupal::config('simpletest.settings')->get('parent_profile');
|
||||
if ($testing_profile && $testing_profile != $profile) {
|
||||
$searchdir[] = drupal_get_path('profile', $testing_profile) . '/' . $directory;
|
||||
}
|
||||
}
|
||||
// In case both profile directories contain the same extension, the actual
|
||||
// profile always has precedence.
|
||||
$searchdir[] = drupal_get_path('profile', $profile) . '/' . $directory;
|
||||
return $searchdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\SystemListing::process().
|
||||
*/
|
||||
protected function process(array $files, array $files_to_add) {
|
||||
// Duplicate files found in later search directories take precedence over
|
||||
// earlier ones, so we want them to overwrite keys in our resulting
|
||||
// $files array.
|
||||
// The exception to this is if the later file is from a module or theme not
|
||||
// compatible with Drupal core. This may occur during upgrades of Drupal
|
||||
// core when new modules exist in core while older contrib modules with the
|
||||
// same name exist in a directory such as /modules.
|
||||
foreach (array_intersect_key($files_to_add, $files) as $file_key => $file) {
|
||||
// If it has no info file, then we just behave liberally and accept the
|
||||
// new resource on the list for merging.
|
||||
if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info.yml')) {
|
||||
// Get the .info.yml file for the module or theme this file belongs to.
|
||||
$info = \Drupal::service('info_parser')->parse($info_file);
|
||||
|
||||
// If the module or theme is incompatible with Drupal core, remove it
|
||||
// from the array for the current search directory, so it is not
|
||||
// overwritten when merged with the $files array.
|
||||
if (isset($info['core']) && $info['core'] != \Drupal::CORE_COMPATIBILITY) {
|
||||
unset($files_to_add[$file_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $files_to_add;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\SystemListing::processFile().
|
||||
*/
|
||||
protected function processFile($file) {
|
||||
$file->name = basename($file->name, '.info');
|
||||
}
|
||||
|
||||
}
|
|
@ -29,20 +29,20 @@ class ProjectInfo {
|
|||
* files for each module or theme, which is important data which is used when
|
||||
* deciding if the available update data should be invalidated.
|
||||
*
|
||||
* @param $projects
|
||||
* @param array $projects
|
||||
* Reference to the array of project data of what's installed on this site.
|
||||
* @param $list
|
||||
* @param \Drupal\Core\Extension\Extension[] $list
|
||||
* Array of data to process to add the relevant info to the $projects array.
|
||||
* @param $project_type
|
||||
* @param string $project_type
|
||||
* The kind of data in the list. Can be 'module' or 'theme'.
|
||||
* @param $status
|
||||
* @param bool $status
|
||||
* Boolean that controls what status (enabled or disabled) to process out of
|
||||
* the $list and add to the $projects array.
|
||||
* @param $additional_whitelist
|
||||
* @param array $additional_whitelist
|
||||
* (optional) Array of additional elements to be collected from the .info.yml
|
||||
* file. Defaults to array().
|
||||
*/
|
||||
function processInfoList(&$projects, $list, $project_type, $status, $additional_whitelist = array()) {
|
||||
function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_whitelist = array()) {
|
||||
foreach ($list as $file) {
|
||||
// A disabled or hidden base theme of an enabled sub-theme still has all
|
||||
// of its code run by the sub-theme, so we include it in our "enabled"
|
||||
|
@ -178,13 +178,11 @@ class ProjectInfo {
|
|||
/**
|
||||
* Determines what project a given file object belongs to.
|
||||
*
|
||||
* @param $file
|
||||
* A file object as returned by system_get_files_database().
|
||||
* @param \Drupal\Core\Extension\Extension $file
|
||||
* An extension object.
|
||||
*
|
||||
* @return
|
||||
* @return string
|
||||
* The canonical project short name.
|
||||
*
|
||||
* @see system_get_files_database()
|
||||
*/
|
||||
function getProjectName($file) {
|
||||
$project_name = '';
|
||||
|
|
|
@ -5,14 +5,6 @@
|
|||
* Provide test blocks.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function block_test_system_theme_info() {
|
||||
$themes['block_test_theme'] = drupal_get_path('module', 'block_test') . '/themes/block_test_theme/block_test_theme.info.yml';
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_alter().
|
||||
*/
|
||||
|
|
|
@ -15,13 +15,6 @@ use Drupal\breakpoint\Entity\Breakpoint;
|
|||
*/
|
||||
class BreakpointThemeTest extends BreakpointGroupTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('breakpoint_theme_test');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Breakpoint theme functionality',
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
name: 'Breakpoint theme test'
|
||||
type: module
|
||||
description: 'Test breakpoints provided by themes'
|
||||
package: Other
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
hidden: true
|
||||
dependencies:
|
||||
- breakpoint
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Test breakpoint functionality for breakpoints provided by themes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function breakpoint_theme_test_system_theme_info() {
|
||||
$themes['breakpoint_test_theme'] = drupal_get_path('module', 'breakpoint_theme_test') . '/themes/breakpoint_test_theme/breakpoint_test_theme.info.yml';
|
||||
return $themes;
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\config_test;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Tests configuration of profiles, modules and themes.
|
||||
|
@ -22,9 +23,11 @@ class TestInstallStorage extends InstallStorage {
|
|||
*/
|
||||
protected function getAllFolders() {
|
||||
if (!isset($this->folders)) {
|
||||
$this->folders = $this->getComponentNames('profile', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles')));
|
||||
$this->folders += $this->getComponentNames('module', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0)));
|
||||
$this->folders += $this->getComponentNames('theme', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes')));
|
||||
// @todo Refactor getComponentNames() to use the extension list directly.
|
||||
$listing = new ExtensionDiscovery();
|
||||
$this->folders = $this->getComponentNames('profile', array_keys($listing->scan('profile')));
|
||||
$this->folders += $this->getComponentNames('module', array_keys($listing->scan('module')));
|
||||
$this->folders += $this->getComponentNames('theme', array_keys($listing->scan('theme')));
|
||||
}
|
||||
return $this->folders;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\config_test;
|
||||
|
||||
use Drupal\Core\Config\Schema\SchemaStorage;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Tests configuration schemas of profiles, modules and themes.
|
||||
|
@ -29,10 +30,12 @@ class TestSchemaStorage extends SchemaStorage {
|
|||
*/
|
||||
protected function getAllFolders() {
|
||||
if (!isset($this->folders)) {
|
||||
// @todo Refactor getComponentNames() to use the extension list directly.
|
||||
$listing = new ExtensionDiscovery();
|
||||
$this->folders = $this->getBaseDataTypeSchema();
|
||||
$this->folders += $this->getComponentNames('profile', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles')));
|
||||
$this->folders += $this->getComponentNames('module', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0)));
|
||||
$this->folders += $this->getComponentNames('theme', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes')));
|
||||
$this->folders += $this->getComponentNames('profile', array_keys($listing->scan('profile')));
|
||||
$this->folders += $this->getComponentNames('module', array_keys($listing->scan('module')));
|
||||
$this->folders += $this->getComponentNames('theme', array_keys($listing->scan('theme')));
|
||||
}
|
||||
return $this->folders;
|
||||
}
|
||||
|
|
|
@ -70,11 +70,3 @@ function config_translation_test_form_config_translation_edit_form_alter(&$form,
|
|||
$form['#altered'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function config_translation_test_system_theme_info() {
|
||||
$themes['config_translation_test_theme'] = drupal_get_path('module', 'config_translation') . '/tests/themes/config_translation_test_theme/config_translation_test_theme.info.yml';
|
||||
return $themes;
|
||||
}
|
||||
|
|
|
@ -162,7 +162,7 @@ function locale_translation_project_list() {
|
|||
* project = myproject" in its .info.yml file. This function will add a project
|
||||
* "myproject" to the info data.
|
||||
*
|
||||
* @param array $data
|
||||
* @param \Drupal\Core\Extension\Extension[] $data
|
||||
* Array of .info.yml file data.
|
||||
* @param string $type
|
||||
* The project type. i.e. module, theme.
|
||||
|
|
|
@ -792,7 +792,7 @@ abstract class WebTestBase extends TestBase {
|
|||
'required' => TRUE,
|
||||
);
|
||||
// Add the parent profile's search path to the child site's search paths.
|
||||
// @see drupal_system_listing()
|
||||
// @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories()
|
||||
$settings['conf']['simpletest.settings']['parent_profile'] = (object) array(
|
||||
'value' => $this->originalProfile,
|
||||
'required' => TRUE,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Page\HtmlPage;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\simpletest\TestBase;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
|
@ -422,14 +423,13 @@ function simpletest_log_read($test_id, $database_prefix, $test_class) {
|
|||
* @endcode
|
||||
*/
|
||||
function simpletest_test_get_all($module = NULL) {
|
||||
$all_groups = &drupal_static(__FUNCTION__);
|
||||
static $all_groups = array();
|
||||
$cid = "simpletest:$module";
|
||||
|
||||
if (!isset($all_groups[$cid])) {
|
||||
$all_groups[$cid] = array();
|
||||
$groups = &$all_groups[$cid];
|
||||
// Make sure that namespaces for disabled modules are registered so that the
|
||||
// checks below will find them.
|
||||
// Register namespaces (extensions are not necessarily enabled).
|
||||
simpletest_classloader_register();
|
||||
|
||||
// Load test information from cache if available, otherwise retrieve the
|
||||
|
@ -439,24 +439,27 @@ function simpletest_test_get_all($module = NULL) {
|
|||
}
|
||||
else {
|
||||
// Select all PSR-0 classes in the Tests namespace of all modules.
|
||||
$classes = array();
|
||||
$module_data = system_rebuild_module_data();
|
||||
$all_data = $module_data + system_rebuild_theme_data();
|
||||
$all_data += drupal_system_listing('/\.profile$/', 'profiles', 'name');
|
||||
$listing = new ExtensionDiscovery();
|
||||
$all_data = $listing->scan('module', TRUE);
|
||||
// If module is set then we keep only that one module.
|
||||
if (isset($module)) {
|
||||
$all_data = array(
|
||||
$module => $all_data[$module],
|
||||
);
|
||||
}
|
||||
else {
|
||||
$all_data += $listing->scan('profile', TRUE);
|
||||
$all_data += $listing->scan('theme', TRUE);
|
||||
}
|
||||
$classes = array();
|
||||
foreach ($all_data as $name => $data) {
|
||||
// Build directory in which the test files would reside.
|
||||
$tests_dir = DRUPAL_ROOT . '/' . dirname($data->uri) . '/lib/Drupal/' . $name . '/Tests';
|
||||
$tests_dir = DRUPAL_ROOT . '/' . $data->getPath() . '/lib/Drupal/' . $name . '/Tests';
|
||||
// Scan it for test files if it exists.
|
||||
if (is_dir($tests_dir)) {
|
||||
$files = file_scan_directory($tests_dir, '/.*\.php/');
|
||||
$files = file_scan_directory($tests_dir, '/\.php$/');
|
||||
if (!empty($files)) {
|
||||
$basedir = DRUPAL_ROOT . '/' . dirname($data->uri) . '/lib/';
|
||||
$basedir = DRUPAL_ROOT . '/' . $data->getPath() . '/lib/';
|
||||
foreach ($files as $file) {
|
||||
// Convert the file name into the namespaced class name.
|
||||
$replacements = array(
|
||||
|
@ -524,23 +527,36 @@ function simpletest_test_get_all($module = NULL) {
|
|||
* Registers namespaces for disabled modules.
|
||||
*/
|
||||
function simpletest_classloader_register() {
|
||||
// @see drupal_get_filename()
|
||||
// Use the same cache prefix as simpletest_test_get_all().
|
||||
$cid = "simpletest::all";
|
||||
$types = array(
|
||||
'theme_engine' => array('dir' => 'themes/engines', 'extension' => 'engine'),
|
||||
'module' => array('dir' => 'modules', 'extension' => 'module'),
|
||||
'theme' => array('dir' => 'themes', 'extension' => 'info'),
|
||||
'profile' => array('dir' => 'profiles', 'extension' => 'profile'),
|
||||
'theme_engine',
|
||||
'module',
|
||||
'theme',
|
||||
'profile',
|
||||
);
|
||||
|
||||
$classloader = drupal_classloader();
|
||||
if ($cache = \Drupal::cache()->get($cid)) {
|
||||
$extensions = $cache->data;
|
||||
}
|
||||
else {
|
||||
$listing = new ExtensionDiscovery();
|
||||
$extensions = array();
|
||||
foreach ($types as $type) {
|
||||
foreach ($listing->scan($type, TRUE) as $name => $file) {
|
||||
$extensions[$type][$name] = $file->uri;
|
||||
}
|
||||
}
|
||||
\Drupal::cache()->set($cid, $extensions);
|
||||
}
|
||||
|
||||
foreach ($types as $type => $info) {
|
||||
$matches = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.' . $info['extension'] . '$/', $info['dir']);
|
||||
foreach ($matches as $name => $file) {
|
||||
drupal_classloader_register($name, dirname($file->uri));
|
||||
$classloader->add('Drupal\\' . $name . '\\Tests', DRUPAL_ROOT . '/' . dirname($file->uri) . '/tests');
|
||||
$classloader = drupal_classloader();
|
||||
foreach ($types as $type) {
|
||||
foreach ($extensions[$type] as $name => $uri) {
|
||||
drupal_classloader_register($name, dirname($uri));
|
||||
$classloader->add('Drupal\\' . $name . '\\Tests', DRUPAL_ROOT . '/' . dirname($uri) . '/tests');
|
||||
// While being there, prime drupal_get_filename().
|
||||
drupal_get_filename($type, $name, $file->uri);
|
||||
drupal_get_filename($type, $name, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,15 +47,6 @@ class GetFilenameUnitTest extends UnitTestBase {
|
|||
// a fixed location and naming.
|
||||
$this->assertIdentical(drupal_get_filename('profile', 'standard'), 'core/profiles/standard/standard.profile', 'Retrieve installation profile location.');
|
||||
|
||||
// When a file is not found in the database cache, drupal_get_filename()
|
||||
// searches several locations on the filesystem, including the core/
|
||||
// directory. We use the '.script' extension below because this is a
|
||||
// non-existent filetype that will definitely not exist in the database.
|
||||
// Since there is already a core/scripts directory, drupal_get_filename()
|
||||
// will automatically check there for 'script' files, just as it does
|
||||
// for (e.g.) 'module' files in core/modules.
|
||||
$this->assertIdentical(drupal_get_filename('script', 'test'), 'core/scripts/test/test.script');
|
||||
|
||||
// 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.');
|
||||
}
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
|
||||
/**
|
||||
* Tests scanning system directories in drupal_system_listing().
|
||||
*/
|
||||
class SystemListingTest extends WebTestBase {
|
||||
class SystemListingTest extends DrupalUnitTestBase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Drupal system listing',
|
||||
|
@ -55,11 +56,16 @@ class SystemListingTest extends WebTestBase {
|
|||
|
||||
// Now scan the directories and check that the files take precedence as
|
||||
// expected.
|
||||
$files = drupal_system_listing('/\.module$/', 'modules');
|
||||
$listing = new ExtensionDiscovery();
|
||||
$listing->setProfileDirectories(array('core/profiles/testing'));
|
||||
$files = $listing->scan('module');
|
||||
foreach ($expected_directories as $module => $directories) {
|
||||
$expected_directory = array_shift($directories);
|
||||
$expected_filename = "$expected_directory/$module/$module.module";
|
||||
$this->assertEqual($files[$module]->uri, $expected_filename, format_string('Module @module was found at @filename.', array('@module' => $module, '@filename' => $expected_filename)));
|
||||
$expected_uri = "$expected_directory/$module/$module.module";
|
||||
$this->assertEqual($files[$module]->uri, $expected_uri, format_string('Module @actual was found at @expected.', array(
|
||||
'@actual' => $files[$module]->uri,
|
||||
'@expected' => $expected_uri,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ class InfoParserUnitTest extends DrupalUnitTestBase {
|
|||
$this->fail('Expected InfoParserException not thrown when reading missing_keys.info.txt');
|
||||
}
|
||||
catch (InfoParserException $e) {
|
||||
$expected_message = 'Missing required keys (name, type) in core/modules/system/tests/fixtures/missing_keys.info.txt.';
|
||||
$expected_message = "Missing required keys (type, core, name) in $filename.";
|
||||
$this->assertEqual($e->getMessage(), $expected_message);
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ class InfoParserUnitTest extends DrupalUnitTestBase {
|
|||
$this->fail('Expected InfoParserException not thrown when reading missing_key.info.txt');
|
||||
}
|
||||
catch (InfoParserException $e) {
|
||||
$expected_message = 'Missing required key (type) in core/modules/system/tests/fixtures/missing_key.info.txt.';
|
||||
$expected_message = "Missing required key (type) in $filename.";
|
||||
$this->assertEqual($e->getMessage(), $expected_message);
|
||||
}
|
||||
|
||||
|
|
|
@ -254,9 +254,12 @@ class ThemeTest extends WebTestBase {
|
|||
* Test that themes can't be enabled when the base theme or engine is missing.
|
||||
*/
|
||||
function testInvalidTheme() {
|
||||
\Drupal::moduleHandler()->install(array('theme_page_test'));
|
||||
// theme_page_test_system_info_alter() un-hides all hidden themes.
|
||||
$this->container->get('module_handler')->install(array('theme_page_test'));
|
||||
// Clear the system_list() and theme listing cache to pick up the change.
|
||||
$this->container->get('theme_handler')->reset();
|
||||
$this->drupalGet('admin/appearance');
|
||||
$this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', array('@base_theme' => 'not_real_test_basetheme')), 'Invalid base theme check succeeded.');
|
||||
$this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', array('@theme_engine' => 'not_real_engine')), 'Invalid theme engine check succeeded.');
|
||||
$this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', array('@base_theme' => 'not_real_test_basetheme')));
|
||||
$this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', array('@theme_engine' => 'not_real_engine')));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\system\Tests\Theme;
|
||||
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\comment\CommentInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
|
@ -28,7 +29,7 @@ class EntityFilteringThemeTest extends WebTestBase {
|
|||
/**
|
||||
* A list of all available themes.
|
||||
*
|
||||
* @var array
|
||||
* @var \Drupal\Core\Extension\Extension[]
|
||||
*/
|
||||
protected $themes;
|
||||
|
||||
|
@ -81,9 +82,10 @@ class EntityFilteringThemeTest extends WebTestBase {
|
|||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Enable all available themes for testing.
|
||||
$this->themes = array_keys(list_themes());
|
||||
theme_enable($this->themes);
|
||||
// Enable all available non-testing themes.
|
||||
$listing = new ExtensionDiscovery();
|
||||
$this->themes = $listing->scan('theme', FALSE);
|
||||
theme_enable(array_keys($this->themes));
|
||||
|
||||
// Create a test user.
|
||||
$this->user = $this->drupalCreateUser(array('access content', 'access user profiles'));
|
||||
|
@ -133,9 +135,9 @@ class EntityFilteringThemeTest extends WebTestBase {
|
|||
);
|
||||
|
||||
// Check each path in all available themes.
|
||||
foreach ($this->themes as $theme) {
|
||||
foreach ($this->themes as $name => $theme) {
|
||||
\Drupal::config('system.theme')
|
||||
->set('default', $theme)
|
||||
->set('default', $name)
|
||||
->save();
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
|
|
|
@ -1052,25 +1052,6 @@ function hook_system_breadcrumb_alter(array &$breadcrumb, array $attributes, arr
|
|||
$breadcrumb[] = Drupal::l(t('Text'), 'example_route_name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return additional themes provided by modules.
|
||||
*
|
||||
* Only use this hook for testing purposes. Use a hidden MYMODULE_test.module
|
||||
* to implement this hook. Testing themes should be hidden, too.
|
||||
*
|
||||
* This hook is invoked from _system_rebuild_theme_data() and allows modules to
|
||||
* register additional themes outside of the regular 'themes' directories of a
|
||||
* Drupal installation.
|
||||
*
|
||||
* @return
|
||||
* An associative array. Each key is the system name of a theme and each value
|
||||
* is the corresponding path to the theme's .info.yml file.
|
||||
*/
|
||||
function hook_system_theme_info() {
|
||||
$themes['mymodule_test_theme'] = drupal_get_path('module', 'mymodule') . '/mymodule_test_theme/mymodule_test_theme.info.yml';
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the information parsed from module and theme .info.yml files
|
||||
*
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Utility\ModuleInfo;
|
||||
use Drupal\block\BlockPluginInterface;
|
||||
use Drupal\menu_link\MenuLinkInterface;
|
||||
|
@ -1520,22 +1521,23 @@ function system_get_info($type, $name = NULL) {
|
|||
/**
|
||||
* Helper function to scan and collect module .info.yml data.
|
||||
*
|
||||
* @return
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* An associative array of module information.
|
||||
*/
|
||||
function _system_rebuild_module_data() {
|
||||
$listing = new ExtensionDiscovery();
|
||||
// Find modules
|
||||
$modules = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0);
|
||||
$modules = $listing->scan('module');
|
||||
|
||||
// Find installation profiles.
|
||||
$profiles = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles', 'name', 0);
|
||||
$profiles = $listing->scan('profile');
|
||||
|
||||
// Include the installation profile in modules that are loaded.
|
||||
$profile = drupal_get_profile();
|
||||
$modules[$profile] = $profiles[$profile];
|
||||
|
||||
// Installation profile hooks are always executed last.
|
||||
$modules[$profile]->weight = 1000;
|
||||
if ($profile = drupal_get_profile()) {
|
||||
$modules[$profile] = $profiles[$profile];
|
||||
// Installation profile hooks are always executed last.
|
||||
$modules[$profile]->weight = 1000;
|
||||
}
|
||||
|
||||
// Set defaults for module info.
|
||||
$defaults = array(
|
||||
|
@ -1553,17 +1555,11 @@ function _system_rebuild_module_data() {
|
|||
$modules[$key]->filename = $module->uri;
|
||||
|
||||
// Look for the info file.
|
||||
$module->info = \Drupal::service('info_parser')->parse(dirname($module->uri) . '/' . $module->name . '.info.yml');
|
||||
|
||||
// Skip modules/profiles that don't provide info or have the wrong type.
|
||||
if (empty($module->info) || !isset($module->info['type']) || !in_array($module->info['type'], array('module', 'profile'))) {
|
||||
unset($modules[$key]);
|
||||
continue;
|
||||
}
|
||||
$module->info = \Drupal::service('info_parser')->parse($module->getPathname());
|
||||
|
||||
// Add the info file modification time, so it becomes available for
|
||||
// contributed modules to use for ordering module lists.
|
||||
$module->info['mtime'] = filemtime(dirname($module->uri) . '/' . $module->name . '.info.yml');
|
||||
$module->info['mtime'] = $module->getMTime();
|
||||
|
||||
// Merge in defaults and save.
|
||||
$modules[$key]->info = $module->info + $defaults;
|
||||
|
@ -1576,6 +1572,7 @@ function _system_rebuild_module_data() {
|
|||
|
||||
// Invoke hook_system_info_alter() to give installed modules a chance to
|
||||
// modify the data in the .info.yml files if necessary.
|
||||
// @todo Remove $type argument, obsolete with $module->getType().
|
||||
$type = 'module';
|
||||
\Drupal::moduleHandler()->alter('system_info', $modules[$key]->info, $modules[$key], $type);
|
||||
}
|
||||
|
@ -1587,7 +1584,7 @@ function _system_rebuild_module_data() {
|
|||
}
|
||||
|
||||
|
||||
if (isset($modules[$profile])) {
|
||||
if ($profile && isset($modules[$profile])) {
|
||||
// The installation profile is required, if it's a valid module.
|
||||
$modules[$profile]->info['required'] = TRUE;
|
||||
// Add a default distribution name if the profile did not provide one. This
|
||||
|
@ -1603,9 +1600,9 @@ function _system_rebuild_module_data() {
|
|||
/**
|
||||
* Ensures that dependencies of required modules are also required.
|
||||
*
|
||||
* @param \stdClass $module
|
||||
* @param \Drupal\Core\Extension\Extension $module
|
||||
* The module info.
|
||||
* @param array $modules
|
||||
* @param \Drupal\Core\Extension\Extension[] $modules
|
||||
* The array of all module info.
|
||||
*/
|
||||
function _system_rebuild_module_data_ensure_required($module, &$modules) {
|
||||
|
@ -1624,7 +1621,7 @@ function _system_rebuild_module_data_ensure_required($module, &$modules) {
|
|||
/**
|
||||
* Rebuild, save, and return data about all currently available modules.
|
||||
*
|
||||
* @return
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* Array of all available modules and their data.
|
||||
*/
|
||||
function system_rebuild_module_data() {
|
||||
|
@ -1636,14 +1633,13 @@ function system_rebuild_module_data() {
|
|||
$modules = _system_rebuild_module_data();
|
||||
$files = array();
|
||||
ksort($modules);
|
||||
// Add name, status, weight, and schema version.
|
||||
// Add status, weight, and schema version.
|
||||
$installed_modules = (array) \Drupal::config('system.module')->get('enabled');
|
||||
foreach ($modules as $module => $record) {
|
||||
$record->name = $module;
|
||||
$record->weight = isset($installed_modules[$module]) ? $installed_modules[$module] : 0;
|
||||
$record->status = (int) isset($installed_modules[$module]);
|
||||
$record->schema_version = SCHEMA_UNINSTALLED;
|
||||
$files[$module] = $record->filename;
|
||||
foreach ($modules as $name => $module) {
|
||||
$module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0;
|
||||
$module->status = (int) isset($installed_modules[$name]);
|
||||
$module->schema_version = SCHEMA_UNINSTALLED;
|
||||
$files[$name] = $module->uri;
|
||||
}
|
||||
$modules = \Drupal::moduleHandler()->buildModuleDependencies($modules);
|
||||
$modules_cache = $modules;
|
||||
|
@ -1658,7 +1654,7 @@ function system_rebuild_module_data() {
|
|||
/**
|
||||
* Helper function to scan and collect theme .info.yml data and their engines.
|
||||
*
|
||||
* @return
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* An associative array of themes information.
|
||||
*
|
||||
* @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData()
|
||||
|
@ -1673,7 +1669,7 @@ function _system_rebuild_theme_data() {
|
|||
/**
|
||||
* Rebuild, save, and return data about all currently available themes.
|
||||
*
|
||||
* @return
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
* Array of all available themes and their data.
|
||||
*/
|
||||
function system_rebuild_theme_data() {
|
||||
|
@ -1690,7 +1686,7 @@ function system_rebuild_theme_data() {
|
|||
$files = array();
|
||||
foreach ($themes as $name => $theme) {
|
||||
$theme->status = (int) isset($enabled_themes[$name]);
|
||||
$files[$name] = $theme->filename;
|
||||
$files[$name] = $theme->uri;
|
||||
}
|
||||
// Replace last known theme data state.
|
||||
// @todo Obsolete with proper installation status for themes.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
core: 8.x
|
||||
name: common_test
|
||||
type: module
|
||||
description: 'testing info file parsing'
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# info.yml for testing missing name, description, and type keys.
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- field
|
||||
|
|
|
@ -12,14 +12,6 @@ use Drupal\Core\Ajax\OpenModalDialogCommand;
|
|||
use Drupal\Core\Ajax\CloseDialogCommand;
|
||||
use Drupal\Core\Ajax\HtmlCommand;
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function ajax_test_system_theme_info() {
|
||||
$themes['test_theme'] = drupal_get_path('module', 'system') . '/tests/themes/test_theme/test_theme.info.yml';
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback: Returns an element suitable for use by
|
||||
* \Drupal\Core\Ajax\AjaxResponse::ajaxRender().
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
name: 'Serialization test module'
|
||||
type: module
|
||||
description: 'Support module for serialization tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
hidden: true
|
|
@ -9,13 +9,3 @@ function theme_page_test_system_info_alter(&$info, $file, $type) {
|
|||
unset($info['hidden']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function theme_page_test_system_theme_info() {
|
||||
$themes['test_invalid_basetheme'] = drupal_get_path('module', 'system') . '/tests/themes/test_invalid_basetheme/test_invalid_basetheme.info.yml';
|
||||
$themes['test_invalid_engine'] = drupal_get_path('module', 'system') . '/tests/themes/test_invalid_engine/test_invalid_engine.info.yml';
|
||||
return $themes;
|
||||
}
|
||||
|
|
|
@ -52,17 +52,6 @@ function theme_test_theme($existing, $type, $theme, $path) {
|
|||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function theme_test_system_theme_info() {
|
||||
$themes['test_theme'] = drupal_get_path('module', 'system') . '/tests/themes/test_theme/test_theme.info.yml';
|
||||
$themes['test_basetheme'] = drupal_get_path('module', 'system') . '/tests/themes/test_basetheme/test_basetheme.info.yml';
|
||||
$themes['test_subtheme'] = drupal_get_path('module', 'system') . '/tests/themes/test_subtheme/test_subtheme.info.yml';
|
||||
$themes['test_theme_phptemplate'] = drupal_get_path('module', 'system') . '/tests/themes/test_theme_phptemplate/test_theme_phptemplate.info.yml';
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for HTML document templates.
|
||||
*/
|
||||
|
|
|
@ -166,7 +166,7 @@ class UpdateManager implements UpdateManagerInterface {
|
|||
$this->keyValueStore->delete($key);
|
||||
}
|
||||
else {
|
||||
$projects = $this->keyValueStore->get($key);
|
||||
$projects = $this->keyValueStore->get($key, array());
|
||||
}
|
||||
return $projects;
|
||||
}
|
||||
|
|
|
@ -8,15 +8,6 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|||
* Module for testing Update Manager functionality.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function update_test_system_theme_info() {
|
||||
$themes['update_test_basetheme'] = drupal_get_path('module', 'update') . '/tests/themes/update_test_basetheme/update_test_basetheme.info.yml';
|
||||
$themes['update_test_subtheme'] = drupal_get_path('module', 'update') . '/tests/themes/update_test_subtheme/update_test_subtheme.info.yml';
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_system_info_alter().
|
||||
*
|
||||
|
|
|
@ -596,7 +596,7 @@ function theme_update_last_check($variables) {
|
|||
* an .info.yml file which claims that the code is compatible with the current
|
||||
* version of Drupal core.
|
||||
*
|
||||
* @see drupal_system_listing()
|
||||
* @see \Drupal\Core\Extension\ExtensionDiscovery
|
||||
* @see _system_rebuild_module_data()
|
||||
*/
|
||||
function update_verify_update_archive($project, $archive_file, $directory) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Tests\Core\Extension;
|
||||
|
||||
use Drupal\Core\Extension\Extension;
|
||||
use Drupal\Core\Extension\InfoParser;
|
||||
use Drupal\Core\Extension\ThemeHandler;
|
||||
use Drupal\Core\Config\ConfigInstaller;
|
||||
|
@ -65,11 +66,11 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
protected $configInstaller;
|
||||
|
||||
/**
|
||||
* The system listing info.
|
||||
* The extension discovery.
|
||||
*
|
||||
* @var \Drupal\Core\SystemListingInfo|\PHPUnit_Framework_MockObject_MockObject
|
||||
* @var \Drupal\Core\Extension\ExtensionDiscovery|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $systemListingInfo;
|
||||
protected $extensionDiscovery;
|
||||
|
||||
/**
|
||||
* The tested theme handler.
|
||||
|
@ -101,10 +102,10 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
$this->routeBuilder = $this->getMockBuilder('Drupal\Core\Routing\RouteBuilder')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->systemListingInfo = $this->getMockBuilder('Drupal\Core\SystemListingInfo')
|
||||
$this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->systemListingInfo);
|
||||
$this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery);
|
||||
|
||||
$this->getContainerWithCacheBins($this->cacheBackend);
|
||||
}
|
||||
|
@ -145,17 +146,12 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
->expects($this->once())
|
||||
->method('save');
|
||||
|
||||
$this->systemListingInfo->expects($this->any())
|
||||
$this->extensionDiscovery->expects($this->any())
|
||||
->method('scan')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
// Ensure that the themes_enabled hook is fired.
|
||||
$this->moduleHandler->expects($this->at(0))
|
||||
->method('invokeAll')
|
||||
->with('system_theme_info')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$this->moduleHandler->expects($this->at(1))
|
||||
->method('invokeAll')
|
||||
->with('themes_enabled', array($theme_list));
|
||||
|
||||
|
@ -186,27 +182,25 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
->method('clear')
|
||||
->will($this->returnSelf());
|
||||
|
||||
$this->systemListingInfo->expects($this->any())
|
||||
$this->extensionDiscovery->expects($this->any())
|
||||
->method('scan')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$this->themeHandler->enable(array('bartik'));
|
||||
$this->themeHandler->systemList['bartik'] = (object) array(
|
||||
'name' => 'bartik',
|
||||
'info' => array(
|
||||
'stylesheets' => array(
|
||||
'all' => array(
|
||||
'css/layout.css',
|
||||
'css/style.css',
|
||||
'css/colors.css',
|
||||
),
|
||||
$this->themeHandler->systemList['bartik'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/bartik/bartik.info.yml', 'bartik.info.yml');
|
||||
$this->themeHandler->systemList['bartik']->info = array(
|
||||
'stylesheets' => array(
|
||||
'all' => array(
|
||||
'css/layout.css',
|
||||
'css/style.css',
|
||||
'css/colors.css',
|
||||
),
|
||||
'scripts' => array(
|
||||
'example' => 'theme.js',
|
||||
),
|
||||
'engine' => 'twig',
|
||||
'base theme' => 'stark',
|
||||
),
|
||||
'scripts' => array(
|
||||
'example' => 'theme.js',
|
||||
),
|
||||
'engine' => 'twig',
|
||||
'base theme' => 'stark',
|
||||
);
|
||||
|
||||
$list_info = $this->themeHandler->listInfo();
|
||||
|
@ -218,18 +212,16 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
$this->assertEquals('stark', $list_info['bartik']->base_theme);
|
||||
$this->assertEquals(0, $list_info['bartik']->status);
|
||||
|
||||
$this->themeHandler->systemList['seven'] = (object) array(
|
||||
'name' => 'seven',
|
||||
'info' => array(
|
||||
'stylesheets' => array(
|
||||
'screen' => array(
|
||||
'style.css',
|
||||
),
|
||||
$this->themeHandler->systemList['seven'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', 'seven.info.yml');
|
||||
$this->themeHandler->systemList['seven']->info = array(
|
||||
'stylesheets' => array(
|
||||
'screen' => array(
|
||||
'style.css',
|
||||
),
|
||||
'scripts' => array(),
|
||||
),
|
||||
'status' => 1,
|
||||
'scripts' => array(),
|
||||
);
|
||||
$this->themeHandler->systemList['seven']->status = 1;
|
||||
|
||||
$this->themeHandler->enable(array('seven'));
|
||||
|
||||
|
@ -246,14 +238,11 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
* @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData()
|
||||
*/
|
||||
public function testRebuildThemeData() {
|
||||
$this->systemListingInfo->expects($this->at(0))
|
||||
$this->extensionDiscovery->expects($this->at(0))
|
||||
->method('scan')
|
||||
->with($this->anything(), 'themes', 'name', 1)
|
||||
->with('theme')
|
||||
->will($this->returnValue(array(
|
||||
'seven' => (object) array(
|
||||
'name' => 'seven',
|
||||
'uri' => DRUPAL_ROOT . '/core/themes/seven/seven.info.yml',
|
||||
),
|
||||
'seven' => new Extension('theme', DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', 'seven.info.yml'),
|
||||
)));
|
||||
$this->infoParser->expects($this->once())
|
||||
->method('parse')
|
||||
|
@ -271,7 +260,7 @@ class ThemeHandlerTest extends UnitTestCase {
|
|||
$info = $theme_data['seven'];
|
||||
|
||||
// Ensure some basic properties.
|
||||
$this->assertInstanceOf('stdClass', $info);
|
||||
$this->assertInstanceOf('Drupal\Core\Extension\Extension', $info);
|
||||
$this->assertEquals('seven', $info->name);
|
||||
$this->assertEquals(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', $info->uri);
|
||||
$this->assertEquals(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', $info->filename);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
type: theme_engine
|
||||
name: PHPTemplate
|
||||
core: 8.x
|
||||
version: VERSION
|
||||
package: Core
|
|
@ -0,0 +1,5 @@
|
|||
type: theme_engine
|
||||
name: Twig
|
||||
core: 8.x
|
||||
version: VERSION
|
||||
package: Core
|
Loading…
Reference in New Issue