final class LibraryDeprecationAnalyzer in Upgrade Status 8.3
Same name and namespace in other branches
- 8.2 src/LibraryDeprecationAnalyzer.php \Drupal\upgrade_status\LibraryDeprecationAnalyzer
A library deprecation analyzer.
Hierarchy
- class \Drupal\upgrade_status\LibraryDeprecationAnalyzer
Expanded class hierarchy of LibraryDeprecationAnalyzer
1 string reference to 'LibraryDeprecationAnalyzer'
1 service uses LibraryDeprecationAnalyzer
File
- src/
LibraryDeprecationAnalyzer.php, line 24
Namespace
Drupal\upgrade_statusView source
final class LibraryDeprecationAnalyzer {
/**
* The library discovery parser.
*
* @var \Drupal\Core\Asset\LibraryDiscoveryParser
*/
protected $libraryDiscoveryParser;
/**
* The Twig environment.
*
* @var \Drupal\Core\Template\TwigEnvironment
*/
protected $twigEnvironment;
/**
* The list of available modules.
*
* @var \Drupal\Core\Extension\ModuleExtensionList
*/
protected $moduleExtensionList;
/**
* The list of available themes.
*
* @var \Drupal\Core\Extension\ThemeExtensionList
*/
protected $themeExtensionList;
/**
* The list of available profiles.
*
* @var \Drupal\Core\Extension\ProfileExtensionList
*/
protected $profileExtensionList;
/**
* Constructs a new library deprecation analyzer
*
* @param \Drupal\Core\Asset\LibraryDiscoveryParser $library_discovery_parser
* The library discovery parser.
* @param \Drupal\Core\Template\TwigEnvironment $twig_environment
* The Twig environment.
* @param \Drupal\Core\Extension\ModuleExtensionList $module_extension_list
* The module extension list service.
* @param \Drupal\Core\Extension\ThemeExtensionList $theme_extension_list
* The theme extension handler service.
* @param \Drupal\Core\Extension\ProfileExtensionList $profile_extension_list
* The profile extension handler service.
*/
public function __construct(LibraryDiscoveryParser $library_discovery_parser, TwigEnvironment $twig_environment, ModuleExtensionList $module_extension_list, ThemeExtensionList $theme_extension_list, ProfileExtensionList $profile_extension_list) {
$this->libraryDiscoveryParser = $library_discovery_parser;
$this->twigEnvironment = $twig_environment;
$this->moduleExtensionList = $module_extension_list;
$this->themeExtensionList = $theme_extension_list;
$this->profileExtensionList = $profile_extension_list;
}
/**
* Analyzes usages of deprecated libraries in an extension.
*
* @param \Drupal\Core\Extension\Extension $extension
* The extension to be analyzed.
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
* A list of deprecation messages.
*
* @throws \Exception
*/
public function analyze(Extension $extension) : array {
$deprecations = [];
$deprecations = array_merge($deprecations, $this
->analyzeLibraryDependencies($extension));
if ($extension
->getType() === 'theme') {
$deprecations = array_merge($deprecations, $this
->analyzeThemeLibraryOverrides($extension));
$deprecations = array_merge($deprecations, $this
->analyzeThemeLibraryExtends($extension));
}
$deprecations = array_merge($deprecations, $this
->analyzeTwigLibraryDependencies($extension));
$deprecations = array_merge($deprecations, $this
->analyzePhpLibraryReferences($extension));
return $deprecations;
}
/**
* Analyzes libraries for dependencies on deprecated libraries.
*
* @param \Drupal\Core\Extension\Extension $extension
* The extension to be analyzed.
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
*/
private function analyzeLibraryDependencies(Extension $extension) : array {
// Drupal\Core\Asset\LibraryDiscoveryParser::buildByExtension() assumes a
// disabled module is a theme and fails not finding it then, so check if
// the extension identified he is an installed module or theme. The library
// info will not be available otherwise.
$installed_modules = array_keys($this->moduleExtensionList
->getAllInstalledInfo());
$installed_themes = array_keys($this->themeExtensionList
->getAllInstalledInfo());
if (!in_array($extension
->getName(), $installed_modules) && !in_array($extension
->getName(), $installed_themes)) {
$message = sprintf("The '%s' extension is not installed. Cannot check deprecated library use.", $extension
->getName());
return [
new DeprecationMessage($message, $extension
->getPath(), 0),
];
}
try {
$libraries = $this->libraryDiscoveryParser
->buildByExtension($extension
->getName());
} catch (\Exception $e) {
return [
new DeprecationMessage($e
->getMessage(), $extension
->getPath(), 0),
];
}
$libraries_with_dependencies = array_filter($libraries, function ($library) {
return isset($library['dependencies']);
});
$deprecations = [];
$file = sprintf('%s/%s.libraries.yml', $extension
->getPath(), $extension
->getName());
foreach ($libraries_with_dependencies as $key => $library_with_dependency) {
foreach ($library_with_dependency['dependencies'] as $dependency) {
$is_deprecated = $this
->isLibraryDeprecated($dependency);
if (is_null($is_deprecated)) {
$message = sprintf("The '%s' library (a dependency of '%s') is not defined because the defining extension is not installed. Cannot decide if it is deprecated or not.", $dependency, $key);
$deprecations[] = new DeprecationMessage($message, $file, 0);
}
elseif (!empty($is_deprecated)) {
if ($is_deprecated instanceof DeprecationMessage) {
$is_deprecated
->setFile($file);
$deprecations[] = $is_deprecated;
}
else {
$message = sprintf("The '%s' library is depending on a deprecated library. %s", $key, $is_deprecated);
$deprecations[] = new DeprecationMessage($message, $file, 0);
}
}
}
}
return $deprecations;
}
/**
* Analyze library overrides in a theme.
*
* @param \Drupal\Core\Extension\Extension $extension
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
* @throws \Exception
*/
private function analyzeThemeLibraryOverrides(Extension $extension) : array {
if ($extension
->getType() !== 'theme') {
throw new \Exception('Library overrides are only available in themes.');
}
if (!isset($extension->info['libraries-override'])) {
return [];
}
return array_reduce(array_keys($extension->info['libraries-override']), function ($deprecated_libraries, $library) use ($extension) {
$is_deprecated = $this
->isLibraryDeprecated($library);
if (is_null($is_deprecated)) {
$message = sprintf("The '%s' library is not defined because the defining extension is not installed. Cannot decide if it is deprecated or not.", $library);
$deprecated_libraries[] = new DeprecationMessage($message, $extension
->getFilename(), 0);
}
elseif (!empty($is_deprecated)) {
if ($is_deprecated instanceof DeprecationMessage) {
$is_deprecated
->setFile($extension
->getFilename());
$deprecated_libraries[] = $is_deprecated;
}
else {
$message = "Theme is overriding a deprecated library. {$is_deprecated}";
$deprecated_libraries[] = new DeprecationMessage($message, $extension
->getFilename(), 0);
}
}
return $deprecated_libraries;
}, []);
}
/**
* Analyze library extends in a theme.
*
* @param \Drupal\Core\Extension\Extension $extension
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
* @throws \Exception
*/
private function analyzeThemeLibraryExtends(Extension $extension) : array {
if ($extension
->getType() !== 'theme') {
throw new \Exception('Library extends are only available in themes.');
}
if (!isset($extension->info['libraries-extend'])) {
return [];
}
return array_reduce(array_keys($extension->info['libraries-extend']), function ($deprecated_libraries, $library) use ($extension) {
$is_deprecated = $this
->isLibraryDeprecated($library);
if (is_null($is_deprecated)) {
$message = sprintf("The '%s' library is not defined because the defining extension is not installed. Cannot decide if it is deprecated or not.", $library);
$deprecated_libraries[] = new DeprecationMessage($message, $extension
->getFilename(), 0);
}
elseif (!empty($is_deprecated)) {
if ($is_deprecated instanceof DeprecationMessage) {
$is_deprecated
->setFile($extension
->getFilename());
$deprecated_libraries[] = $is_deprecated;
}
else {
$message = "Theme is extending a deprecated library. {$is_deprecated}";
$deprecated_libraries[] = new DeprecationMessage($message, $extension
->getFilename(), 0);
}
}
return $deprecated_libraries;
}, []);
}
/**
* Analyzes Twig library dependencies.
*
* At the moment we analyze only libraries attached using `library_attach()`.
* However, there could be other ways to attache a library in a template, such
* as generating and rendering a render array with `#attached`.
*
* @param \Drupal\Core\Extension\Extension $extension
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
*/
private function analyzeTwigLibraryDependencies(Extension $extension) : array {
$iterator = new TemplateDirIterator(new TwigRecursiveIterator($extension
->getPath()));
$deprecations = [];
foreach ($iterator as $name => $contents) {
try {
$libraries = $this
->findLibrariesAttachedInTemplate($this->twigEnvironment
->parse($this->twigEnvironment
->tokenize(new Source($contents, $name))));
foreach ($libraries as $library) {
$is_deprecated = $this
->isLibraryDeprecated($library['library']);
if (is_null($is_deprecated)) {
$message = sprintf("The '%s' library is not defined because the defining extension is not installed. Cannot decide if it is deprecated or not.", $library['library']);
$deprecations[] = new DeprecationMessage($message, $name, $library['line']);
}
elseif (!empty($is_deprecated)) {
if ($is_deprecated instanceof DeprecationMessage) {
$is_deprecated
->setFile($name);
$deprecations[] = $is_deprecated;
}
else {
$message = 'Template is attaching a deprecated library. ' . $is_deprecated;
$deprecations[] = new DeprecationMessage($message, $name, $library['line']);
}
}
}
} catch (SyntaxError $e) {
// Ignore templates containing syntax errors.
}
}
return $deprecations;
}
/**
* Finds libraries attached using `libraries_attach()` in a Twig template.
*
* @param \Twig\Node\Node $node
*
* @return string[]
*/
private function findLibrariesAttachedInTemplate(Node $node) : array {
if (!is_iterable($node)) {
return [];
}
$libraries = [];
foreach ($node as $item) {
if ($item instanceof FunctionExpression) {
if ($item
->getAttribute('name') === 'attach_library') {
foreach ($item
->getNode('arguments') as $argument) {
if ($argument instanceof ConstantExpression && $argument
->hasAttribute('value')) {
$libraries[] = [
'library' => $argument
->getAttribute('value'),
'line' => $item
->getTemplateLine(),
];
}
}
}
}
else {
$libraries = array_merge($libraries, $this
->findLibrariesAttachedInTemplate($item));
}
}
return $libraries;
}
/**
* Analyzes libraries referenced in PHP.
*
* This can only analyze statically attached libraries. We are not checking
* the context where the library is being referenced, so in some cases this
* could lead into false negatives. Testing the context would be possible, but
* could lead into not detecting all references to deprecated libraries.
*
* @param \Drupal\Core\Extension\Extension $extension
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
*/
private function analyzePhpLibraryReferences(Extension $extension) : array {
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($extension
->getPath()), \RecursiveIteratorIterator::LEAVES_ONLY), '/\\.(php|module|theme|profile|inc)$/');
$deprecations = [];
foreach ($iterator as $file) {
try {
$tokens = token_get_all(file_get_contents($file
->getPathName()));
} catch (\ParseError $error) {
// Ignore syntax errors.
continue;
}
// Find nodes that look like attaching a library.
$potential_libraries = array_values(array_map(function ($token) {
list($type, $value, $line) = $token;
return [
'value' => substr($value, 1, -1),
'line' => $line,
];
}, array_filter($tokens, function ($token) {
if (is_array($token)) {
list($type, $value) = $token;
return $type === T_CONSTANT_ENCAPSED_STRING && preg_match('/^[\\"\'][a-zA-Z0-9\\.\\-\\_]+\\/[a-zA-Z0-9\\.\\-\\_]+[\\"\']$/', $value);
}
return FALSE;
})));
foreach ($potential_libraries as $potential_library) {
list($extension_name) = explode('/', $potential_library['value'], 2);
$extension_lists = [
$this->moduleExtensionList,
$this->themeExtensionList,
$this->profileExtensionList,
];
// Iterate through all extension lists to see if we have found a valid
// extension.
$valid_extension = array_reduce($extension_lists, function ($valid_extension, ExtensionList $extension_list) use ($extension_name) {
if ($valid_extension || $extension_list
->exists($extension_name)) {
return TRUE;
}
return FALSE;
}, FALSE);
if ($valid_extension) {
$is_deprecated = $this
->isLibraryDeprecated($potential_library['value']);
if (is_null($is_deprecated)) {
$message = sprintf("The '%s' library is not defined because the defining extension is not installed. Cannot decide if it is deprecated or not.", $potential_library['value']);
$deprecations[] = new DeprecationMessage($message, $file
->getPathName(), $potential_library['line']);
}
elseif (!empty($is_deprecated)) {
if ($is_deprecated instanceof DeprecationMessage) {
$is_deprecated
->setFile($file
->getPathName());
$deprecations[] = $is_deprecated;
}
else {
$message = "The referenced library is deprecated. {$is_deprecated}";
$deprecations[] = new DeprecationMessage($message, $file
->getPathName(), $potential_library['line']);
}
}
}
}
}
return $deprecations;
}
/**
* Tests if library is deprecated.
*
* @param string $library
* A string representing library. For example, 'node/drupal.node'.
*
* @return bool|string|NULL|DeprecationMessage
* FALSE if the library is not deprecated. NULL if the library's module
* is not enabled. Deprecation message string otherwise.
*/
private function isLibraryDeprecated($library) {
if (!strpos($library, '/')) {
return new DeprecationMessage('The ' . $library . ' library does not have an extension name and is therefore not valid.');
}
list($extension_name, $library_name) = explode('/', $library, 2);
// Drupal\Core\Asset\LibraryDiscoveryParser::buildByExtension() assumes a
// disabled module is a theme and fails not finding it then, so check if
// the extension identified he is an installed module or theme. The library
// info will not be available otherwise.
if ($extension_name != 'core') {
$installed_modules = array_keys($this->moduleExtensionList
->getAllInstalledInfo());
$installed_themes = array_keys($this->themeExtensionList
->getAllInstalledInfo());
if (!in_array($extension_name, $installed_modules) && !in_array($extension_name, $installed_themes)) {
return NULL;
}
}
try {
$dependency_libraries = $this->libraryDiscoveryParser
->buildByExtension($extension_name);
} catch (\Exception $e) {
return new DeprecationMessage($e
->getMessage());
}
if (isset($dependency_libraries[$library_name]) && isset($dependency_libraries[$library_name]['deprecated'])) {
return str_replace('%library_id%', "{$extension_name}/{$library_name}", $dependency_libraries[$library_name]['deprecated']);
}
return FALSE;
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
LibraryDeprecationAnalyzer:: |
protected | property | The library discovery parser. | |
LibraryDeprecationAnalyzer:: |
protected | property | The list of available modules. | |
LibraryDeprecationAnalyzer:: |
protected | property | The list of available profiles. | |
LibraryDeprecationAnalyzer:: |
protected | property | The list of available themes. | |
LibraryDeprecationAnalyzer:: |
protected | property | The Twig environment. | |
LibraryDeprecationAnalyzer:: |
public | function | Analyzes usages of deprecated libraries in an extension. | |
LibraryDeprecationAnalyzer:: |
private | function | Analyzes libraries for dependencies on deprecated libraries. | |
LibraryDeprecationAnalyzer:: |
private | function | Analyzes libraries referenced in PHP. | |
LibraryDeprecationAnalyzer:: |
private | function | Analyze library extends in a theme. | |
LibraryDeprecationAnalyzer:: |
private | function | Analyze library overrides in a theme. | |
LibraryDeprecationAnalyzer:: |
private | function | Analyzes Twig library dependencies. | |
LibraryDeprecationAnalyzer:: |
private | function | Finds libraries attached using `libraries_attach()` in a Twig template. | |
LibraryDeprecationAnalyzer:: |
private | function | Tests if library is deprecated. | |
LibraryDeprecationAnalyzer:: |
public | function | Constructs a new library deprecation analyzer |