Commit dc530c14 authored by Sergey Shadrin's avatar Sergey Shadrin

[#124455] Update to core 11.2.3

- Updated dockerfile and docker compose files
- Updated all modules and core
- quickedit, pet, drush_language and paragraphs_browser_previewer just downloaded and patched because no d11 published versions exists. Later need to add in composer.json
parent 740ec626
...@@ -7,6 +7,7 @@ app/sites/*/settings.php ...@@ -7,6 +7,7 @@ app/sites/*/settings.php
# Exclude update module # Exclude update module
app/core/modules/update app/core/modules/update
!app/core/modules/update/update.info.yml !app/core/modules/update/update.info.yml
app/modules/contrib/upgrade_status
# Exclude IDE specific directories. # Exclude IDE specific directories.
.idea .idea
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a883f65614d1e1faeacd42ed12105266", "content-hash": "ce257c13e8b88933505ca1a84be9a1a5",
"packages": [ "packages": [
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
......
analyse
analysing
checkstyle
codeclimate
dekor
deprecatedfilter
endspaceless
extdata
Gábor
Hojtsy
inspectable
linecount
NOSORT
ONLYDIR
rescan
rupal
sandboxing
subcomponent
subextension
sublist
updatev
\ No newline at end of file
#
# DrupalCI includes.
#
include:
- project: $_GITLAB_TEMPLATES_REPO
ref: $_GITLAB_TEMPLATES_REF
file:
- '/includes/include.drupalci.main.yml'
- '/includes/include.drupalci.variables.yml'
- '/includes/include.drupalci.workflows.yml'
#
# Start custom overrides.
#
variables:
OPT_IN_TEST_CURRENT: 1
OPT_IN_TEST_PREVIOUS_MAJOR: 1
OPT_IN_TEST_NEXT_MAJOR: 1
This diff is collapsed.
# Upgrade Status
Review Drupal major upgrade readiness of the environment and components of the site.
The module provides the following key features:
- Checks if you are using a version of Drupal that supports an upgrade.
- Checks if your system meets the next major version's system requirements.
- Integrates with the Update Status core module to inform you to update your contributed projects. Projects can be compatible with multiple major Drupal versions, so most projects can be updated on your existing site before doing the core major update.
- Runs phpstan checks and a whole set of other checks to find any compatibility issues with the next Drupal major version that may remain.
- Integrates with drush for command line usage and to plug into CI systems.
For a full description of the module, visit the
[project page](https://www.drupal.org/project/upgrade_status).
Submit bug reports and feature suggestions, or track changes in the
[issue queue](https://www.drupal.org/project/issues/upgrade_status).
## Requirements
This module requires no modules outside of Drupal core.
## Installation
You must use Composer to install all the required third party dependencies,
for example `composer require drupal/upgrade_status` then install as you would
normally install a contributed Drupal module. For further information, see:
[Installing Drupal Modules](https://www.drupal.org/docs/extending-drupal/installing-drupal-modules).
While the module takes an effort to categorize projects properly, installing
[Composer Deploy](https://www.drupal.org/project/composer_deploy) or
[Git Deploy](https://www.drupal.org/project/git_deploy) as appropriate to your
Drupal setup is suggested to identify custom vs. contributed projects more
accurately and gather version information leading to useful available update
information.
## Configuration
There are no configuration options. Go to Administration » Reports » Upgrade
status to use the module.
## Maintainers
- Gábor Hojtsy - [Gábor Hojtsy](https://www.drupal.org/u/g%C3%A1bor-hojtsy)
{
"name": "drupal/upgrade_status",
"type": "drupal-module",
"description": "Review Drupal major upgrade readiness of the environment and components of the site.",
"homepage": "http://drupal.org/project/upgrade_status",
"license": "GPL-2.0-or-later",
"require": {
"mglaman/phpstan-drupal": "^1.2.11|^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0.0|^2.0",
"dekor/php-array-table": "^2.0",
"nikic/php-parser": "^4.0.0|^5.0.0",
"webflo/drupal-finder": "^1.2",
"symfony/process": "^3.4|^4.0|^5.0|^6.0|^7.0"
},
"require-dev": {
"drush/drush": "^11|^12|^13"
},
"minimum-stability": "dev",
"extra": {
"drush": {
"services": {
"drush.services.yml": "^9 || ^10"
}
}
}
}
upgrade_status.settings:
type: config_object
label: 'Upgrade status settings'
mapping:
paths_per_scan:
type: integer
label: 'Paths per iteration to scan'
/**
* @file
* Styles used by the Upgrade Status module.
*/
/* Upgrade Status summary of the whole site */
.upgrade-status-of-site tr:hover {
color: inherit;
background-color: inherit;
}
.upgrade-status-of-site th {
width: 33%;
}
.upgrade-status-of-site td {
vertical-align: top;
}
/* Make space for SVG circle */
.upgrade-status-of-site td:nth-child(3) .item-list ul li {
margin-right: 6em;
}
/* Upgrade Status environment table layout */
.upgrade-status-of-environment th.requirement-label {
width: 70%;
}
.upgrade-status-of-environment th.status-info {
width: 30%;
}
/* Upgrade Status next step table layout */
.upgrade-status-next-step th {
width: 10%;
}
.upgrade-status-next-step th.project-label {
width: 20%;
}
.upgrade-status-next-step th.select-all {
width: 1px;
}
/* Remove Gin borders on these tables. TH and TR don't line up. */
.upgrade-status-of-environment .gin-layer-wrapper tr,
.upgrade-status-next-step .gin-layer-wrapper tr,
.upgrade-status-project-result-group .gin-layer-wrapper tr {
border-left: 0;
}
/* Project specific results layout */
.upgrade-status-project-result-group h3 {
margin: 30px 0 0 0;
}
.upgrade-status-project-result-group tr td:nth-child(1),
.upgrade-status-project-result-group tr td:nth-child(3) {
width: 40%;
}
.upgrade-status-project-result-group tr td:nth-child(2) {
width: 10%;
}
.upgrade-status-of-environment td.requirement-label,
.upgrade-status-next-step td.project-label {
font-weight: bold;
}
.upgrade-status-project-result-group tr > td.status-info,
.upgrade-status-next-step tr > td.status-info,
.upgrade-status-of-environment tr > td.status-info {
padding-left: 35px; /* LTR */
background-repeat: no-repeat;
background-position-x: 10px; /* LTR */
background-position-y: center;
}
[dir="rtl"] .upgrade-status-project-result-group tr > td.status-info,
[dir="rtl"] .upgrade-status-next-step tr > td.status-info,
[dir="rtl"] .upgrade-status-of-environment tr > td.status-info {
padding-right: 35px; /* LTR */
padding-left: 0;
/* @todo x background position for RTL */
}
.upgrade-status-project-result-group tr.color-error > td.status-info,
.upgrade-status-of-environment tr.color-error > td.status-info {
background-image: url(../icons/error.svg);
}
.upgrade-status-project-result-group tr.color-warning > td.status-info,
.upgrade-status-next-step td.status-info-incompatible,
.upgrade-status-of-environment tr.color-warning > td.status-info {
background-image: url(../icons/warning.svg);
}
.upgrade-status-project-result-group
tr.color-warning.known-later
> td.status-info,
.upgrade-status-next-step td.status-info-na {
background-image: url(../icons/ex.svg);
}
.upgrade-status-project-result-group tr.color-success > td.status-info,
.upgrade-status-next-step td.status-info-compatible,
.upgrade-status-of-environment tr.color-success > td.status-info {
background-image: url(../icons/check.svg);
}
.upgrade-status-project-result-group
tr.color-warning.rector-covered
> td.status-info {
background-image: url(../icons/wrench.svg);
}
.upgrade-status-next-step td.status-info-unchecked {
background-image: url(../icons/questionmark-disc.svg);
}
html.gin--dark-mode .upgrade-status-next-step td.status-info-unchecked {
background-image: url(../icons/questionmark-disc-white.svg);
}
/* Result circle styling in the status of site summary */
.upgrade-status-of-site-circle {
display: block;
float: right;
height: 5em;
margin: 0 0 1em 1em;
}
.upgrade-status-of-site-circle .circle-bg {
fill: none;
stroke: #f5f5f2;
stroke-width: 3.8;
}
.upgrade-status-of-site-circle .circle {
fill: none;
stroke-width: 2.8;
stroke-linecap: round;
animation-duration: 1s;
animation-timing-function: ease-out;
animation-direction: forwards;
stroke: #325e1c;
}
.upgrade-status-of-site-circle .percentage {
font-size: 0.5em;
text-anchor: middle;
font-weight: bold;
}
html.gin--dark-mode .upgrade-status-of-site-circle .percentage {
fill: #fff;
}
/* Gin dialog does not have top and bottom padding by default */
.ui-dialog .ui-widget-content.ui-dialog-content {
padding-top: 1rem;
padding-bottom: 1rem;
}
# FROM mglaman/drupal-check/phpstan/deprecation_testing.neon
parameters:
drupal:
rules:
classExtendsInternalClassRule: false
parallel:
maximumNumberOfProcesses: 0
customRulesetUsed: true
ignoreErrors:
- '#\Drupal calls should be avoided in classes, use dependency injection instead#'
- '#Plugin definitions cannot be altered.#'
- '#Missing cache backend declaration for performance.#'
- '#Plugin manager has cache backend specified but does not declare cache tags.#'
# FROM mglaman/drupal-check/phpstan/base_config.neon
reportUnmatchedIgnoredErrors: false
excludePaths:
- */tests/Drupal/Tests/Listeners/Legacy/*
- */tests/fixtures/*.php
- */settings*.php
- */bower_components/*
- */node_modules/*
services:
upgrade_status.commands:
class: \Drupal\upgrade_status\Drush\Commands\UpgradeStatusCommands
arguments:
- '@upgrade_status.result_formatter'
- '@upgrade_status.project_collector'
- '@upgrade_status.deprecation_analyzer'
- '@date.formatter'
tags:
- { name: drush.command }
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#73b355"><path d="M6.464 13.676c-.194.194-.513.194-.707 0l-4.96-4.955c-.194-.193-.194-.513 0-.707l1.405-1.407c.194-.195.512-.195.707 0l2.849 2.848c.194.193.513.193.707 0l6.629-6.626c.195-.194.514-.194.707 0l1.404 1.404c.193.194.193.513 0 .707l-8.741 8.736z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#e32700"><path d="M8.002 1c-3.868 0-7.002 3.134-7.002 7s3.134 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm4.025 9.284c.062.063.1.149.1.239 0 .091-.037.177-.1.24l-1.262 1.262c-.064.062-.15.1-.24.1s-.176-.036-.24-.1l-2.283-2.283-2.286 2.283c-.064.062-.15.1-.24.1s-.176-.036-.24-.1l-1.261-1.262c-.063-.062-.1-.148-.1-.24 0-.088.036-.176.1-.238l2.283-2.285-2.283-2.284c-.063-.064-.1-.15-.1-.24s.036-.176.1-.24l1.262-1.262c.063-.063.149-.1.24-.1.089 0 .176.036.24.1l2.285 2.284 2.283-2.284c.064-.063.15-.1.24-.1s.176.036.24.1l1.262 1.262c.062.063.1.149.1.24 0 .089-.037.176-.1.24l-2.283 2.284 2.283 2.284z"/></svg>
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.51 13.925c.194.194.512.195.706.001l3.432-3.431c.194-.194.514-.194.708 0l3.432 3.431c.192.194.514.193.707-.001l1.405-1.417c.191-.195.189-.514-.002-.709l-3.397-3.4c-.192-.193-.192-.514-.002-.708l3.401-3.43c.189-.195.189-.515 0-.709l-1.407-1.418c-.195-.195-.513-.195-.707-.001l-3.43 3.431c-.195.194-.516.194-.708 0l-3.432-3.431c-.195-.195-.512-.194-.706.001l-1.407 1.417c-.194.195-.194.515 0 .71l3.403 3.429c.193.195.193.514-.001.708l-3.4 3.399c-.194.195-.195.516-.001.709l1.406 1.419z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fff" d="M8.002 1c-3.868 0-7.002 3.134-7.002 7s3.134 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm3 5c0 .551-.16 1.085-.477 1.586l-.158.22c-.07.093-.189.241-.361.393-.168.148-.35.299-.545.447l-.203.189-.141.129-.096.17-.021.235v.63h-2.001v-.704c.026-.396.078-.73.204-.999.125-.269.271-.498.439-.688l.225-.21-.01-.015.176-.14.137-.128c.186-.139.357-.277.516-.417l.148-.18c.098-.152.168-.323.168-.518 0-.552-.447-1-1-1s-1.002.448-1.002 1h-2c0-1.657 1.343-3 3.002-3 1.656 0 3 1.343 3 3zm-1.75 6.619c0 .344-.281.625-.625.625h-1.25c-.345 0-.626-.281-.626-.625v-1.238c0-.344.281-.625.626-.625h1.25c.344 0 .625.281.625.625v1.238z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M8.002 1c-3.868 0-7.002 3.134-7.002 7s3.134 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm3 5c0 .551-.16 1.085-.477 1.586l-.158.22c-.07.093-.189.241-.361.393-.168.148-.35.299-.545.447l-.203.189-.141.129-.096.17-.021.235v.63h-2.001v-.704c.026-.396.078-.73.204-.999.125-.269.271-.498.439-.688l.225-.21-.01-.015.176-.14.137-.128c.186-.139.357-.277.516-.417l.148-.18c.098-.152.168-.323.168-.518 0-.552-.447-1-1-1s-1.002.448-1.002 1h-2c0-1.657 1.343-3 3.002-3 1.656 0 3 1.343 3 3zm-1.75 6.619c0 .344-.281.625-.625.625h-1.25c-.345 0-.626-.281-.626-.625v-1.238c0-.344.281-.625.626-.625h1.25c.344 0 .625.281.625.625v1.238z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#e29700"><path d="M14.66 12.316l-5.316-10.633c-.738-1.476-1.946-1.476-2.685 0l-5.317 10.633c-.738 1.477.008 2.684 1.658 2.684h10.002c1.65 0 2.396-1.207 1.658-2.684zm-7.66-8.316h2.002v5h-2.002v-5zm2.252 8.615c0 .344-.281.625-.625.625h-1.25c-.345 0-.626-.281-.626-.625v-1.239c0-.344.281-.625.626-.625h1.25c.344 0 .625.281.625.625v1.239z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M14.416 11.586l-.01-.008v-.001l-5.656-5.656c.15-.449.252-.921.252-1.421 0-2.485-2.016-4.5-4.502-4.5-.505 0-.981.102-1.434.255l2.431 2.431-.588 2.196-2.196.588-2.445-2.445c-.162.464-.268.956-.268 1.475 0 2.486 2.014 4.5 4.5 4.5.5 0 .972-.102 1.421-.251l5.667 5.665c.781.781 2.047.781 2.828 0s.781-2.047 0-2.828z"/></svg>
<?php
declare(strict_types=1);
namespace Drupal\upgrade_status;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Site\Settings;
/**
* The route deprecation analyzer.
*/
final class CSSDeprecationAnalyzer {
/**
* Analyzes usages of deprecated CSS selectors 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 = [];
$css_files = $this->getAllCSSFiles(DRUPAL_ROOT . '/' . $extension->getPath());
foreach ($css_files as $css_file) {
$content = file_get_contents($css_file);
// Remove valid selectors for this check.
$content = str_replace('#drupal-off-canvas:not(.drupal-off-canvas-reset)', 'removed', $content);
$content = str_replace('#drupal-off-canvas-wrapper', 'removed', $content);
if (strpos($content, '#drupal-off-canvas')) {
$deprecations[] = new DeprecationMessage('The #drupal-off-canvas selector is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. See https://www.drupal.org/node/3305664.', $css_file, 0, 'CSSDeprecationAnalyzer');
}
}
return $deprecations;
}
/**
* Finds all .css files for non-test extensions under a path.
*
* @param string $path
* Base path to find all .css files in.
*
* @return array
* A list of paths to .css files found under the base path.
*/
private function getAllCSSFiles(string $path) {
$files = [];
$ignore_directories = Settings::get('file_scan_ignore_directories', ['bower_components', 'node_modules']);
foreach(array_filter(glob($path . '/*.css'), 'is_file') as $file) {
foreach ($ignore_directories as $ignore_directory) {
if (strpos($file, '/' . $ignore_directory . '/')) {
continue 2;
}
}
$files[] = $file;
}
foreach (glob($path . '/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
foreach ($ignore_directories as $ignore_directory) {
if (strpos($dir, '/' . $ignore_directory . '/')) {
continue 2;
}
}
$files = array_merge($files, $this->getAllCSSFiles($dir));
}
return $files;
}
}
<?php
declare(strict_types=1);
namespace Drupal\upgrade_status;
use Drupal\Core\Extension\Extension;
/**
* Config schema deprecation analyzer.
*/
final class ConfigSchemaDeprecationAnalyzer {
/**
* Analyzes usages of deprecated config schema elements 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 = [];
$project_dir = DRUPAL_ROOT . '/' . $extension->getPath();
$config_files = $this->getViewsConfigFiles($project_dir);
foreach ($config_files as $config_file) {
$error_path = str_replace(DRUPAL_ROOT . '/', '', $config_file);
$file_contents = file_get_contents($config_file);
if (($line = $this->findKeyLine('default_argument_skip_url:', $file_contents)) !== 1) {
$deprecations[] = new DeprecationMessage("Support from all Views contextual filter settings for the default_argument_skip_url setting is removed from drupal:11.0.0. No replacement is provided. See https://www.drupal.org/node/3382316.", $error_path, $line, 'ConfigSchemaDeprecationAnalyzer');
}
}
return $deprecations;
}
/**
* Finds all views config files for extensions under a path.
*
* @param string $path
* Base path to find all views config files in.
*
* @return array
* A list of paths to views config files found under the base path.
*/
private function getViewsConfigFiles(string $path) {
$files = [];
foreach(glob($path . '/views.view.*.yml') as $file) {
$files[] = $file;
}
foreach (glob($path . '/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files, $this->getViewsConfigFiles($dir));
}
return $files;
}
/**
* Finds the line that contains the substring.
*
* @param string $substring
* The string to find.
* @param string $file_contents
* String contents of a file.
* @return
* Line number if found, 1 otherwise.
*/
private function findKeyLine($substring, $file_contents) {
$lines = explode("\n", $file_contents);
foreach ($lines as $num => $line) {
if (strpos($line, $substring) !== FALSE) {
return $num + 1;
}
}
return 1;
}
}
<?php
namespace Drupal\upgrade_status\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\RendererInterface;
use Drupal\upgrade_status\ProjectCollector;
use Drupal\upgrade_status\ScanResultFormatter;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class ScanResultController extends ControllerBase {
/**
* The scan result formatter service.
*
* @var \Drupal\upgrade_status\ScanResultFormatter
*/
protected $resultFormatter;
/**
* The project collector service.
*
* @var \Drupal\upgrade_status\ProjectCollector
*/
protected $projectCollector;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a \Drupal\upgrade_status\Controller\ScanResultController.
*
* @param \Drupal\upgrade_status\ScanResultFormatter $result_formatter
* The scan result formatter service.
* @param \Drupal\upgrade_status\ProjectCollector $project_collector
* The project collector service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct(
ScanResultFormatter $result_formatter,
ProjectCollector $project_collector,
RendererInterface $renderer
) {
$this->resultFormatter = $result_formatter;
$this->projectCollector = $project_collector;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('upgrade_status.result_formatter'),
$container->get('upgrade_status.project_collector'),
$container->get('renderer')
);
}
/**
* Builds content for the error list page/popup.
*
* @param string $project_machine_name
* The machine name of the project.
*
* @return array
* Build array.
*/
public function resultPage(string $project_machine_name) {
$extension = $this->projectCollector->loadProject($project_machine_name);
return $this->resultFormatter->formatResult($extension);
}
/**
* Generates single project export.
*
* @param string $project_machine_name
* The machine name of the project.
* @param string $format
* The format to use when exporting the data: html or ascii.
*
* @return \Symfony\Component\HttpFoundation\Response
* Response object.
*/
public function resultExport(string $project_machine_name, string $format) {
$extension = $this->projectCollector->loadProject($project_machine_name);
$result = $this->resultFormatter->getRawResult($extension);
// Sanitize user input.
if (!in_array($format, ['html', 'ascii'])) {
$format = 'html';
}
$build = ['#theme' => 'upgrade_status_' . $format . '_export' ];
$build['#projects'][$extension->info['upgrade_status_type'] == ProjectCollector::TYPE_CUSTOM ? 'custom' : 'contrib'] = [
$project_machine_name =>
$format == 'html' ?
$this->resultFormatter->formatResult($extension) :
$this->resultFormatter->formatAsciiResult($extension) ,
];
$fileDate = $this->resultFormatter->formatDateTime($result['date'], 'html_datetime');
$extension = $format == 'html' ? '.html' : '.txt';
$filename = 'single-export-' . $project_machine_name . '-' . $fileDate . $extension;
$response = new Response($this->renderer->renderRoot($build));
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
return $response;
}
/**
* Analyze a specific project in its own HTTP request.
*
* @param string $project_machine_name
* The machine name of the project.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* Response object.
*/
public function analyze(string $project_machine_name) {
if ($project_machine_name == 'upgrade_status_request_test') {
// Handle the special case of a request test which is testing the
// HTTP sandboxing capability.
return new JsonResponse(
['message' => 'Request test success']
);
}
else {
// Dealing with a real project.
$extension = $this->projectCollector->loadProject($project_machine_name);
\Drupal::service('upgrade_status.deprecation_analyzer')->analyze($extension);
return new JsonResponse(
['message' => $this->t('Scanned @project', ['@project' => $extension->getName()])]
);
}
}
}
<?php
// @phpcs:ignoreFile
namespace Drupal\upgrade_status;
use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Cookie\SetCookie;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Cookie jar that stores cookies as an array.
*
* Note: Copied from Guzzle 6 but adjusted to not trigger deprecations in
* PHP 8.1 during upgrade checks. Can be removed after Core allows Guzzle 7.
*/
class CookieJar implements CookieJarInterface
{
/** @var SetCookie[] Loaded cookie data */
private $cookies = [];
/** @var bool */
private $strictMode;
/**
* @param bool $strictMode Set to true to throw exceptions when invalid
* cookies are added to the cookie jar.
* @param array $cookieArray Array of SetCookie objects or a hash of
* arrays that can be used with the SetCookie
* constructor
*/
public function __construct($strictMode = false, $cookieArray = [])
{
$this->strictMode = $strictMode;
foreach ($cookieArray as $cookie) {
if (!($cookie instanceof SetCookie)) {
$cookie = new SetCookie($cookie);
}
$this->setCookie($cookie);
}
}
/**
* Create a new Cookie jar from an associative array and domain.
*
* @param array $cookies Cookies to create the jar from
* @param string $domain Domain to set the cookies to
*
* @return self
*/
public static function fromArray(array $cookies, $domain)
{
$cookieJar = new self();
foreach ($cookies as $name => $value) {
$cookieJar->setCookie(new SetCookie([
'Domain' => $domain,
'Name' => $name,
'Value' => $value,
'Discard' => true
]));
}
return $cookieJar;
}
/**
* @deprecated
*/
public static function getCookieValue($value)
{
return $value;
}
/**
* Evaluate if this cookie should be persisted to storage
* that survives between requests.
*
* @param SetCookie $cookie Being evaluated.
* @param bool $allowSessionCookies If we should persist session cookies
* @return bool
*/
public static function shouldPersist(
SetCookie $cookie,
$allowSessionCookies = false
) {
if ($cookie->getExpires() || $allowSessionCookies) {
if (!$cookie->getDiscard()) {
return true;
}
}
return false;
}
/**
* Finds and returns the cookie based on the name
*
* @param string $name cookie name to search for
* @return SetCookie|null cookie that was found or null if not found
*/
public function getCookieByName($name)
{
// don't allow a non string name
if ($name === null || !is_scalar($name)) {
return null;
}
foreach ($this->cookies as $cookie) {
if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
return $cookie;
}
}
return null;
}
public function toArray(): array
{
return array_map(function (SetCookie $cookie) {
return $cookie->toArray();
}, $this->getIterator()->getArrayCopy());
}
public function clear($domain = null, $path = null, $name = null) :void
{
if (!$domain) {
$this->cookies = [];
return;
} elseif (!$path) {
$this->cookies = array_filter(
$this->cookies,
function (SetCookie $cookie) use ($domain) {
return !$cookie->matchesDomain($domain);
}
);
} elseif (!$name) {
$this->cookies = array_filter(
$this->cookies,
function (SetCookie $cookie) use ($path, $domain) {
return !($cookie->matchesPath($path) &&
$cookie->matchesDomain($domain));
}
);
} else {
$this->cookies = array_filter(
$this->cookies,
function (SetCookie $cookie) use ($path, $domain, $name) {
return !($cookie->getName() == $name &&
$cookie->matchesPath($path) &&
$cookie->matchesDomain($domain));
}
);
}
}
public function clearSessionCookies(): void
{
$this->cookies = array_filter(
$this->cookies,
function (SetCookie $cookie) {
return !$cookie->getDiscard() && $cookie->getExpires();
}
);
}
public function setCookie(SetCookie $cookie): bool
{
// If the name string is empty (but not 0), ignore the set-cookie
// string entirely.
$name = $cookie->getName();
if (!$name && $name !== '0') {
return false;
}
// Only allow cookies with set and valid domain, name, value
$result = $cookie->validate();
if ($result !== true) {
if ($this->strictMode) {
throw new \RuntimeException('Invalid cookie: ' . $result);
} else {
$this->removeCookieIfEmpty($cookie);
return false;
}
}
// Resolve conflicts with previously set cookies
foreach ($this->cookies as $i => $c) {
// Two cookies are identical, when their path, and domain are
// identical.
if ($c->getPath() != $cookie->getPath() ||
$c->getDomain() != $cookie->getDomain() ||
$c->getName() != $cookie->getName()
) {
continue;
}
// The previously set cookie is a discard cookie and this one is
// not so allow the new cookie to be set
if (!$cookie->getDiscard() && $c->getDiscard()) {
unset($this->cookies[$i]);
continue;
}
// If the new cookie's expiration is further into the future, then
// replace the old cookie
if ($cookie->getExpires() > $c->getExpires()) {
unset($this->cookies[$i]);
continue;
}
// If the value has changed, we better change it
if ($cookie->getValue() !== $c->getValue()) {
unset($this->cookies[$i]);
continue;
}
// The cookie exists, so no need to continue
return false;
}
$this->cookies[] = $cookie;
return true;
}
#[\ReturnTypeWillChange]
public function count()
{
return count($this->cookies);
}
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator(array_values($this->cookies));
}
public function extractCookies(
RequestInterface $request,
ResponseInterface $response
): void {
if ($cookieHeader = $response->getHeader('Set-Cookie')) {
foreach ($cookieHeader as $cookie) {
$sc = SetCookie::fromString($cookie);
if (!$sc->getDomain()) {
$sc->setDomain($request->getUri()->getHost());
}
if (0 !== strpos($sc->getPath(), '/')) {
$sc->setPath($this->getCookiePathFromRequest($request));
}
$this->setCookie($sc);
}
}
}
/**
* Computes cookie path following RFC 6265 section 5.1.4
*
* @link https://tools.ietf.org/html/rfc6265#section-5.1.4
*
* @param RequestInterface $request
* @return string
*/
private function getCookiePathFromRequest(RequestInterface $request)
{
$uriPath = $request->getUri()->getPath();
if ('' === $uriPath) {
return '/';
}
if (0 !== strpos($uriPath, '/')) {
return '/';
}
if ('/' === $uriPath) {
return '/';
}
if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
return '/';
}
return substr($uriPath, 0, $lastSlashPos);
}
public function withCookieHeader(RequestInterface $request): RequestInterface
{
$values = [];
$uri = $request->getUri();
$scheme = $uri->getScheme();
$host = $uri->getHost();
$path = $uri->getPath() ?: '/';
foreach ($this->cookies as $cookie) {
if ($cookie->matchesPath($path) &&
$cookie->matchesDomain($host) &&
!$cookie->isExpired() &&
(!$cookie->getSecure() || $scheme === 'https')
) {
$values[] = $cookie->getName() . '='
. $cookie->getValue();
}
}
return $values
? $request->withHeader('Cookie', implode('; ', $values))
: $request;
}
/**
* If a cookie already exists and the server asks to set it again with a
* null value, the cookie must be deleted.
*
* @param SetCookie $cookie
*/
private function removeCookieIfEmpty(SetCookie $cookie)
{
$cookieValue = $cookie->getValue();
if ($cookieValue === null || $cookieValue === '') {
$this->clear(
$cookie->getDomain(),
$cookie->getPath(),
$cookie->getName()
);
}
}
}
<?php
declare(strict_types=1);
namespace Drupal\upgrade_status;
/**
* A value object containing a deprecation message with some metadata.
*/
class DeprecationMessage {
/**
* The message.
*
* @var string
*/
protected $message;
/**
* The line associated to the deprecation message.
*
* @var int
*/
protected $line;
/**
* The file related to the deprecation message.
*
* @var string
*/
protected $file;
/**
* The analyzer providing the message.
*
* @var string
*/
protected string $analyzer;
/**
* Constructs a new deprecation message.
*
* @param string $message
* The message.
* @param string $file
* The file related to the deprecation message.
* @param int $line
* The line associated to the deprecation message.
*/
public function __construct(string $message, string $file = '', int $line = 0, string $analyzer = '') {
$this->message = $message;
$this->file = $file;
$this->line = $line;
$this->analyzer = $analyzer;
}
/**
* Gets the message.
*
* @return string
*/
public function getMessage(): string {
return $this->message;
}
/**
* Gets the file.
*
* @return string
*/
public function getFile(): string {
return $this->file;
}
/**
* Gets the line.
*
* @return int
*/
public function getLine(): int {
return $this->line;
}
/**
* Sets the line value.
*
* @param int $line
* The line associated to the deprecation message.
*/
public function setLine(int $line) {
$this->line = $line;
}
/**
* Sets the file value.
*
* @param string $file
* The file related to the deprecation message.
*/
public function setFile(string $file) {
$this->file = $file;
}
/**
* Get analyzer providing the message.
*
* @return string
*/
public function getAnalyzer(): string {
return $this->analyzer;
}
}
<?php
declare(strict_types=1);
namespace Drupal\upgrade_status;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Core\Extension\Extension;
/**
* The info.yml and composer deprecation analyzer.
*/
final class ExtensionMetadataDeprecationAnalyzer {
/**
* Analyzes usages of deprecated extension metadata 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 = [];
$project_dir = DRUPAL_ROOT . '/' . $extension->getPath();
$info_files = $this->getSubExtensionInfoFiles($project_dir);
foreach ($info_files as $info_file) {
try {
// Manually add on info file incompatibility to results. Reading
// .info.yml files directly, not from extension discovery because that
// is cached.
$file_contents = file_get_contents($info_file);
$info = Yaml::decode($file_contents) ?: [];
if (!empty($info['package']) && $info['package'] == 'Testing' && !strpos($info_file, '/upgrade_status_test')) {
// If this info file was for a testing project other than our own
// testing projects, ignore it.
continue;
}
$error_path = str_replace(DRUPAL_ROOT . '/', '', $info_file);
// Check for missing base theme key.
if ($info['type'] === 'theme') {
if (!isset($info['base theme'])) {
$deprecations[] = new DeprecationMessage("The now required 'base theme' key is missing. See https://www.drupal.org/node/3066038.", $error_path, 1, 'ExtensionMetadataDeprecationAnalyzer');
}
}
if (!isset($info['core_version_requirement'])) {
$deprecations[] = new DeprecationMessage("Add core_version_requirement to designate which Drupal versions is the extension compatible with. See https://drupal.org/node/3070687.", $error_path, 1, 'ExtensionMetadataDeprecationAnalyzer');
}
elseif (!ProjectCollector::isCompatibleWithNextMajorDrupal($info['core_version_requirement'])) {
$line = $this->findKeyLine('core_version_requirement:', $file_contents);
$deprecations[] = new DeprecationMessage("Value of core_version_requirement: {$info['core_version_requirement']} is not compatible with the next major version of Drupal core. See https://drupal.org/node/3070687.", $error_path, $line, 'ExtensionMetadataDeprecationAnalyzer');
}
// @todo
// Change values to ExtensionLifecycle class constants once at least
// Drupal 9.3 is required.
if (!empty($info['lifecycle'])) {
$line = $this->findKeyLine('lifecycle:', $file_contents);
$link = !empty($info['lifecycle_link']) ? $info['lifecycle_link'] : 'https://www.drupal.org/node/3215042';
if ($info['lifecycle'] == 'deprecated') {
$deprecations[] = new DeprecationMessage("This extension is deprecated. Don't use it. See $link.", $error_path, $line, 'ExtensionMetadataDeprecationAnalyzer');
}
elseif ($info['lifecycle'] == 'obsolete') {
$deprecations[] = new DeprecationMessage("This extension is obsolete. Obsolete extensions are usually uninstalled automatically when not needed anymore. You only need to do something about this if the uninstallation was unsuccessful. See $link.", $error_path, $line, 'ExtensionMetadataDeprecationAnalyzer');
}
}
} catch (InvalidDataTypeException $e) {
$deprecations[] = new DeprecationMessage('Parse error. ' . $e->getMessage(), $error_path, 1, 'ExtensionMetadataDeprecationAnalyzer');
}
// No need to check info files for PHP 8 compatibility information because
// they can only define minimal PHP versions not maximum or excluded PHP
// versions.
}
// Manually add on composer.json file incompatibility to results.
if (file_exists($project_dir . '/composer.json')) {
$error_path = $extension->getPath() . '/composer.json';
$composer_json = json_decode(file_get_contents($project_dir . '/composer.json'));
if (empty($composer_json) || !is_object($composer_json)) {
$deprecations[] = new DeprecationMessage("Parse error in composer.json. Having a composer.json is not a requirement in general, but if there is one, it should be valid. See https://drupal.org/node/2514612.", $error_path, 1, 'ExtensionMetadataDeprecationAnalyzer');
}
elseif (!empty($composer_json->require->{'drupal/core'}) && !projectCollector::isCompatibleWithNextMajorDrupal($composer_json->require->{'drupal/core'})) {
$deprecations[] = new DeprecationMessage("The drupal/core requirement is not compatible with the next major version of Drupal. Either remove it or update it to be compatible. See https://www.drupal.org/docs/develop/using-composer/add-a-composerjson-file#core-compatibility.", $error_path, 1, 'ExtensionMetadataDeprecationAnalyzer');
}
elseif (!empty($composer_json->require->{'php'}) && !projectCollector::isCompatibleWithPHP($composer_json->require->{'php'}, '8.1.0')) {
$deprecations[] = new DeprecationMessage("The PHP requirement is not compatible with PHP 8.1. Once the codebase is actually compatible, either remove this limitation or update it to be compatible.", $error_path, 1, 'ExtensionMetadataDeprecationAnalyzer');
}
}
return $deprecations;
}
/**
* Finds all .info.yml files for extensions under a path.
*
* @param string $path
* Base path to find all info.yml files in.
*
* @return array
* A list of paths to .info.yml files found under the base path.
*/
private function getSubExtensionInfoFiles(string $path) {
$files = [];
foreach(glob($path . '/*.info.yml') as $file) {
// Make sure the filename matches rules for an extension. There may be
// info.yml files in shipped configuration which would have more parts.
$parts = explode('.', basename($file));
if (count($parts) == 3) {
$files[] = $file;
}
}
foreach (glob($path . '/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files, $this->getSubExtensionInfoFiles($dir));
}
return $files;
}
/**
* Finds the line that contains the substring.
*
* @param string $substring
* The string to find.
* @param string $file_contents
* String contents of a file.
* @return
* Line number if found, 1 otherwise.
*/
private function findKeyLine($substring, $file_contents) {
$lines = explode("\n", $file_contents);
foreach ($lines as $num => $line) {
if (strpos($line, $substring) !== FALSE) {
return $num + 1;
}
}
return 1;
}
}
<?php
declare(strict_types=1);
namespace Drupal\upgrade_status;
use Drupal\Core\Extension\Extension;
/**
* The route deprecation analyzer.
*/
final class RouteDeprecationAnalyzer {
/**
* Analyzes usages of deprecated route elements 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 = [];
$routing_files = $this->getAllRoutingFiles(DRUPAL_ROOT . '/' . $extension->getPath());
foreach ($routing_files as $routing_file) {
$content = file_get_contents($routing_file);
if (strpos($content, '_access_node_revision')) {
$deprecations[] = new DeprecationMessage('The _access_node_revision routing requirement is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use _entity_access instead. See https://www.drupal.org/node/3161210.', $routing_file, 0, 'RouteDeprecationAnalyzer');
}
if (strpos($content, '_access_media_revision')) {
$deprecations[] = new DeprecationMessage('The _access_media_revision routing requirement is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use _entity_access instead. See https://www.drupal.org/node/3161210.', $routing_file, 0, 'RouteDeprecationAnalyzer');
}
}
return $deprecations;
}
/**
* Finds all .routing.yml files for non-test extensions under a path.
*
* @param string $path
* Base path to find all .routing.yml files in.
*
* @return array
* A list of paths to .routing.yml files found under the base path.
*/
private function getAllRoutingFiles(string $path) {
$files = [];
foreach(glob($path . '/*.routing.yml') as $file) {
// Make sure the filename matches rules for an extension. There may be
// routing.yml files in shipped configuration which would have more parts.
$parts = explode('.', basename($file));
if (count($parts) == 3) {
$files[] = $file;
}
}
foreach (glob($path . '/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files, $this->getAllRoutingFiles($dir));
}
return $files;
}
}
<?php
declare(strict_types=1);
namespace Drupal\upgrade_status;
use Drupal\Core\Cache\NullBackend;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Theme\Registry;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\NodeFinder;
use PhpParser\ParserFactory;
use PhpParser\PhpVersion;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Function_;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A theme function deprecation analyzer.
*
* @todo Remove once Drupal 8 to 9 deprecations are not a focus anymore.
* This is not dependent on Drupal 8 core itself though, so we can keep
* it in Drupal 9 to 10 for the sake of exposing extremely outdated code.
*/
final class ThemeFunctionDeprecationAnalyzer {
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
/**
* Constructs a new theme function deprecation analyzer.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $this->container
* The service container.
*/
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
/**
* Analyzes theme functions in an extension.
*
* @param \Drupal\Core\Extension\Extension $extension
* The extension to be analyzed.
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
*/
public function analyze(Extension $extension): array {
$deprecation_messages = [];
// Analyze hook_theme and hook_theme_registry_alter functions.
$deprecation_messages = array_merge($deprecation_messages, $this->analyzeFunction($extension->getName() . '_' . 'theme', $extension));
$deprecation_messages = array_merge($deprecation_messages, $this->analyzeFunction($extension->getName() . '_' . 'theme_registry_alter', $extension));
// If a theme is being analyzed, theme function overrides need to be
// analyzed.
if ($extension->getType() === 'theme') {
// Create new instance of theme registry to ensure that we have the most
// recent data without having to make changes to the production theme
// registry.
$theme_registry = new Registry($this->container->get('app.root'), new NullBackend('null'), $this->container->get('lock'), $this->container->get('module_handler'), $this->container->get('theme_handler'), $this->container->get('theme.initialization'), $extension->getName());
$theme_registry->setThemeManager($this->container->get('theme.manager'));
$theme_hooks = $theme_registry->get();
$theme_function_overrides = drupal_find_theme_functions($theme_hooks, [$extension->getName()]);
foreach ($theme_function_overrides as $machine_name => $theme_function_override) {
try {
$function = new \ReflectionFunction($extension->getName() . '_' . $machine_name);
$file = $function->getFileName();
$line = $function->getStartLine();
$deprecation_messages[$extension->getName() . '_' . $machine_name] = new DeprecationMessage(sprintf('The theme is overriding the "%s" theme function. Theme functions are deprecated. For more info, see https://www.drupal.org/node/2575445.', $machine_name), $file, $line, 'ThemeFunctionDeprecationAnalyzer');
} catch (\ReflectionException $e) {
// This should never happen because drupal_find_theme_functions()
// ensures that the function exists.
}
}
}
return $deprecation_messages;
}
/**
* Analyzes function for definition of theme functions.
*
* This doesn't recognize functions in all edge cases. For example, theme
* functions could be generated dynamically in a number of different ways.
* However, this will be useful in most use cases.
*
* @param $function
* The function to be analyzed.
* @param \Drupal\Core\Extension\Extension $extension
* The extension that is being tested.
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
*/
private function analyzeFunction(string $function, Extension $extension): array {
$deprecation_messages = [];
try {
$function_reflection = new \ReflectionFunction($function);
} catch (\ReflectionException $e) {
// Not all extensions implement theme hooks.
return [];
}
$parser_factory = new ParserFactory();
if (method_exists($parser_factory, 'create')) {
$parser = $parser_factory->create(ParserFactory::PREFER_PHP7);
}
else {
$parser = $parser_factory->createForVersion(PhpVersion::fromString("7.4"));
}
try {
$ast = $parser->parse(file_get_contents($function_reflection->getFileName()));
} catch (Error $error) {
// The function cannot be evaluated because of a syntax error.
$deprecation_messages[] = new DeprecationMessage(sprintf('Parse error while processing the %s hook implementation.', $function), $function_reflection->getFileName(), $function_reflection->getStartLine(), 'ThemeFunctionDeprecationAnalyzer');
}
if (!is_iterable($ast)) {
return [];
}
$finder = new NodeFinder();
// Find the node for the function that is being analyzed.
$function_node = $finder->findFirst($ast, function (Node $node) use ($function) {
return ($node instanceof Function_ && isset($node->name) && $node->name->name === $function);
});
if (!$function_node) {
// This should never happen because the file has been loaded based on the
// existence of the function.
return [];
}
// Find theme functions that have been defined using the array syntax.
// @code
// function hook_theme() {
// return [
// 'theme_hook' => ['function' => theme_function'],
// ];
// }
// @endcode
$theme_function_nodes = $finder->find([$function_node], function(Node $node) {
return (isset($node->key) && $node->key instanceof String_ && $node->key->value === 'function');
});
foreach ($theme_function_nodes as $node) {
$theme_function = $node->value instanceof String_ ? sprintf('"%s"', $node->value->value) : 'an unknown';
$deprecation_messages[] = new DeprecationMessage(sprintf('The %s is defining %s theme function. Theme functions are deprecated. For more info, see https://www.drupal.org/node/2575445.', $extension->getType(), $theme_function), $function_reflection->getFileName(), $node->getStartLine(), 'ThemeFunctionDeprecationAnalyzer');
}
// Find theme functions that are being added to an existing array using
// the array square bracket syntax.
// @code
// function hook_theme_registry_alter(&$theme_registry) {
// $theme_registry['theme_hook']['function'] = 'another_theme_function';
// }
// @endcode
$theme_function_dim_nodes = $finder->find([$function_node], function(Node $node) {
return $node instanceof Assign && $node->var instanceof ArrayDimFetch && $node->var->dim instanceof String_ && $node->var->dim->value === 'function';
});
foreach ($theme_function_dim_nodes as $node) {
$theme_function = $node->expr instanceof String_ ? sprintf('"%s"', $node->expr->value) : 'an unknown';
$deprecation_messages[] = new DeprecationMessage(sprintf('The %s is defining %s theme function. Theme functions are deprecated. For more info, see https://www.drupal.org/node/2575445.', $extension->getType(), $theme_function), $function_reflection->getFileName(), $node->getStartLine(), 'ThemeFunctionDeprecationAnalyzer');
}
return $deprecation_messages;
}
}
<?php
namespace Drupal\upgrade_status;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Template\TwigEnvironment;
use Twig\Error\SyntaxError;
use Twig\Source;
use Twig\Util\TemplateDirIterator;
class TwigDeprecationAnalyzer {
/**
* The Twig environment.
*
* @var \Drupal\Core\Template\TwigEnvironment
*/
protected $twigEnvironment;
public function __construct(TwigEnvironment $twig_environment) {
$this->twigEnvironment = $twig_environment;
}
/**
* Analyzes theme functions in an extension.
*
* This is based on Twig\Util\DeprecationCollector which is a final class
* and thus cannot be extended. While it did find non-twig runtime deprecated
* errors, it did not gave us the file/line information, so we needed to copy
* and modify that behavior. We folded in our twig file/line parsing inline
* then to make it simpler.
*
* @param \Drupal\Core\Extension\Extension $extension
* The extension to be analyzed.
*
* @return \Drupal\upgrade_status\DeprecationMessage[]
*/
public function analyze(Extension $extension): array {
$deprecations = [];
set_error_handler(function ($type, $msg, $file, $line) use (&$deprecations) {
if (\E_USER_DEPRECATED === $type) {
if (preg_match('!([a-zA-Z0-9\_\-\/]+.html\.twig)!', $msg, $file_matches)) {
// Caught a Twig syntax based deprecation, record file name and line
// number from the message we caught.
preg_match('/(\d+).?$/', $msg, $line_matches);
$msg = preg_replace('! in (.+)\.twig at line \d+\.!', '.', $msg);
$msg .= ' See https://drupal.org/node/3071078.';
$deprecations[] = new DeprecationMessage(
$msg,
$file_matches[1],
$line_matches[1] ?? 0,
'TwigDeprecationAnalyzer'
);
}
else {
// Otherwise record the deprecation from the original caught error.
$deprecations[] = new DeprecationMessage(
$msg,
$file,
$line,
'TwigDeprecationAnalyzer'
);
}
}
});
$iterator = new TemplateDirIterator(
new TwigRecursiveIterator($extension->getPath())
);
foreach ($iterator as $name => $contents) {
try {
$this->twigEnvironment->parse($this->twigEnvironment->tokenize(new Source($contents, $name)));
} catch (SyntaxError $e) {
// Report twig syntax error which stops us from parsing it.
$deprecations[] = new DeprecationMessage(
'Twig template ' . $name . ' contains a syntax error and cannot be parsed.',
$name,
$e->getTemplateLine(),
'TwigDeprecationAnalyzer'
);
}
}
restore_error_handler();
// Ensure files are sorted properly.
usort($deprecations, static function (DeprecationMessage $a, DeprecationMessage $b) {
return strcmp($a->getFile(), $b->getFile());
});
return $deprecations;
}
}
<?php
namespace Drupal\upgrade_status;
use Drupal\Core\Site\Settings;
/**
* Filters a RecursiveDirectoryIterator to discover Drupal twig template files.
*/
class TwigRecursiveIterator extends \RecursiveIteratorIterator {
/**
* TwigRecursiveIteratorIterator constructor.
*
* @param string $directory
* Directory to search files.
*/
public function __construct(string $directory) {
$exclude = Settings::get('file_scan_ignore_directories', []);
parent::__construct(new \RecursiveCallbackFilterIterator(
new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS),
function ($current) use ($exclude) {
$name = $current->getFilename();
// RecursiveDirectoryIterator::SKIP_DOTS only skips '.' and '..', but
// not hidden directories (like '.git').
return $name[0] !== '.' &&
(($current->isDir() && !in_array($name, $exclude, TRUE)) ||
($current->isFile() && substr($name, -10) === '.html.twig'));
}
), \RecursiveIteratorIterator::LEAVES_ONLY);
}
}
{% if projects.custom %}
{{ "CUSTOM PROJECTS"|t }}
{% for project in projects.custom %}
--------------------------------------------------------------------------------
{{ project.name }}
{{ project.date }}
{% if project.summary %}
{{ project.summary }}
{% endif %}
{{ project.data|raw }}
{% endfor %}
{% endif %}
{% if projects.contrib %}
{{ "CONTRIBUTED PROJECTS"|t }}
{% for project in projects.contrib %}
--------------------------------------------------------------------------------
{{ project.name }}
{{ project.date }}
{% if project.summary %}
{{ project.summary }}
{% endif %}
{{ project.data|raw }}
{% endfor %}
{% endif %}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment