View source
<?php
declare (strict_types=1);
namespace Drupal\upgrade_status;
use Drupal\Core\Asset\LibraryDiscoveryParser;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ExtensionList;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ProfileExtensionList;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Core\Template\TwigEnvironment;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Node;
use Twig\Source;
use Twig\Util\TemplateDirIterator;
final class LibraryDeprecationAnalyzer {
protected $libraryDiscoveryParser;
protected $twigEnvironment;
protected $moduleExtensionList;
protected $themeExtensionList;
protected $profileExtensionList;
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;
}
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;
}
private function analyzeLibraryDependencies(Extension $extension) : array {
$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;
}
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;
}, []);
}
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;
}, []);
}
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) {
}
}
return $deprecations;
}
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;
}
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) {
continue;
}
$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,
];
$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;
}
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);
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;
}
}