Skip to content
Snippets Groups Projects
Commit 7917ee7b authored by Tim Plunkett's avatar Tim Plunkett
Browse files

Issue #3501590 by tim.plunkett: Increase PHPStan level and ensure issues exist...

Issue #3501590 by tim.plunkett: Increase PHPStan level and ensure issues exist for each entry in the baseline
parents 9cb5058f e329f55a
No related branches found
No related tags found
1 merge request!678Resolve #3501590 "Increase phpstan level"
Pipeline #404158 failed
Showing
with 179 additions and 109 deletions
......@@ -10,7 +10,7 @@ use Drupal\project_browser\Plugin\ProjectBrowserSourceManager;
/**
* Implements hook_install().
*/
function project_browser_devel_install() {
function project_browser_devel_install(): void {
// Set the new random data generator as plugin and keep the current one.
$configFactory = \Drupal::configFactory();
$current_source_plugin = $configFactory->getEditable('project_browser.admin_settings')
......@@ -27,7 +27,7 @@ function project_browser_devel_install() {
/**
* Implements hook_uninstall().
*/
function project_browser_devel_uninstall() {
function project_browser_devel_uninstall(): void {
// Set the previous plugin.
$admin_settings = \Drupal::configFactory()->getEditable('project_browser.admin_settings');
$enabled_sources = $admin_settings->get('enabled_sources');
......
......@@ -25,21 +25,21 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* local_task = {}
* )
*/
class RandomDataPlugin extends ProjectBrowserSourceBase {
final class RandomDataPlugin extends ProjectBrowserSourceBase {
/**
* Utility to create random data.
*
* @var \Drupal\Component\Utility\Random
*/
protected $randomGenerator;
protected Random $randomGenerator;
/**
* ProjectBrowser cache bin.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheBin;
protected CacheBackendInterface $cacheBin;
/**
* Constructs a MockDrupalDotOrg object.
......@@ -62,7 +62,7 @@ class RandomDataPlugin extends ProjectBrowserSourceBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
......@@ -80,7 +80,7 @@ class RandomDataPlugin extends ProjectBrowserSourceBase {
* @return array
* Array of random IDs and names.
*/
protected function getRandomIdsAndNames($array_length = 4): array {
protected function getRandomIdsAndNames(int $array_length = 4): array {
$data = [];
for ($i = 0; $i < $array_length; $i++) {
$data[] = [
......@@ -98,7 +98,7 @@ class RandomDataPlugin extends ProjectBrowserSourceBase {
* @return int
* Random timestamp.
*/
protected function getRandomDate() {
protected function getRandomDate(): int {
return rand(strtotime('2 years ago'), strtotime('today'));
}
......@@ -163,17 +163,17 @@ class RandomDataPlugin extends ProjectBrowserSourceBase {
// Filter by project machine name.
if (!empty($query['machine_name'])) {
$projects = array_filter($projects, fn(Project $project) => $project->machineName === $query['machine_name']);
$projects = array_filter($projects, fn(Project $project): bool => $project->machineName === $query['machine_name']);
}
// Filter by categories.
if (!empty($query['categories'])) {
$projects = array_filter($projects, fn(Project $project) => array_intersect(array_column($project->categories, 'id'), explode(',', $query['categories'])));
$projects = array_filter($projects, fn(Project $project): bool => empty(array_intersect(array_column($project->categories, 'id'), explode(',', $query['categories']))));
}
// Filter by search text.
if (!empty($query['search'])) {
$projects = array_filter($projects, fn(Project $project) => stripos($project->title, $query['search']) !== FALSE);
$projects = array_filter($projects, fn(Project $project): bool => stripos($project->title, $query['search']) !== FALSE);
}
return $this->createResultsPage($projects);
......@@ -181,6 +181,9 @@ class RandomDataPlugin extends ProjectBrowserSourceBase {
/**
* Gets the project data from cache if available, or builds it if not.
*
* @return \Drupal\project_browser\ProjectBrowser\Project[]
* An array of projects.
*/
private function getProjectData(): array {
$stored_projects = $this->cacheBin->get('RandomData:projects');
......@@ -199,14 +202,14 @@ class RandomDataPlugin extends ProjectBrowserSourceBase {
if ($i !== 0) {
$project_images[] = [
'file' => [
'uri' => str_replace(4, 5, $good_image),
'uri' => str_replace('4', '5', $good_image),
'resource' => 'image',
],
'alt' => $machine_name . ' something',
];
$project_images[] = [
'file' => [
'uri' => str_replace(4, 6, $good_image),
'uri' => str_replace('4', '6', $good_image),
'resource' => 'image',
],
'alt' => $machine_name . ' another thing',
......
......@@ -10,7 +10,7 @@
/**
* Implements hook_install().
*/
function project_browser_source_example_install() {
function project_browser_source_example_install(): void {
$configFactory = \Drupal::configFactory();
$current_source_plugin = $configFactory->getEditable('project_browser.admin_settings')
->get('enabled_sources');
......@@ -23,7 +23,7 @@ function project_browser_source_example_install() {
/**
* Implements hook_uninstall().
*/
function project_browser_source_example_uninstall() {
function project_browser_source_example_uninstall(): void {
$admin_settings = \Drupal::configFactory()->getEditable('project_browser.admin_settings');
$enabled_sources = $admin_settings->get('enabled_sources');
if (($key = array_search('project_browser_source_example', $enabled_sources)) !== FALSE) {
......
......@@ -2,7 +2,6 @@
namespace Drupal\project_browser_source_example\Plugin\ProjectBrowserSource;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\project_browser\Plugin\ProjectBrowserSourceBase;
use Drupal\project_browser\ProjectBrowser\Project;
use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage;
......@@ -18,7 +17,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
* description = @Translation("Example source plugin for Project Browser."),
* )
*/
class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {
final class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {
/**
* Constructor for example plugin.
......@@ -31,15 +30,12 @@ class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {
* The plugin implementation definition.
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
* The request from the browser.
* @param \Drupal\Core\Extension\ModuleExtensionList $moduleExtensionList
* The module extension list.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected readonly RequestStack $requestStack,
protected ModuleExtensionList $moduleExtensionList,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
......@@ -47,13 +43,12 @@ class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get(RequestStack::class),
$container->get(ModuleExtensionList::class),
);
}
......@@ -85,7 +80,7 @@ class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {
'short_description' => 'Quick summary to show in the cards.',
'long_description' => 'Extended project information to show in the detail page',
'author' => 'Jane Doe',
'logo' => $request->getSchemeAndHttpHost() . '/core/misc/logo/drupal-logo.svg',
'logo' => $request?->getSchemeAndHttpHost() . '/core/misc/logo/drupal-logo.svg',
'created_at' => strtotime('1 year ago'),
'updated_at' => strtotime('1 month ago'),
'categories' => ['cat_1:Category 1'],
......@@ -152,7 +147,6 @@ class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {
// Images: Array of images using the same structure as $logo, above.
images: [],
);
$pb_path = $this->moduleExtensionList->getPath('project_browser');
$projects[] = new Project(
logo: $logo,
// Maybe the source won't have all fields, but we still need to
......
parameters:
level: 1
level: 8
universalObjectCratesClasses:
- Drupal\Core\Extension\Extension
reportUnmatchedIgnoredErrors: true
excludePaths:
# The scripts directory does not contain runtime code.
- scripts
# The node_modules contains some PHP to ignore.
- sveltejs
# The recipe form contains a couple of errors that cannot be ignored.
......@@ -9,9 +13,60 @@ parameters:
- src/Form/RecipeForm.php
# Every ignore should be documented.
ignoreErrors:
# Ignore errors when using `array` as a return type.
-
# @see https://www.drupal.org/docs/develop/development-tools/phpstan/handling-unsafe-usage-of-new-static#s-ignoring-the-issue
identifier: new.static
identifier: missingType.iterableValue
reportUnmatched: false
### Core testing suite
# Caused by missing return type on \Drupal\FunctionalJavascriptTests\WebDriverTestBase::assertSession().
-
message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:assert[a-zA-Z]+\\(\\)\\.$#"
paths:
- tests/src/FunctionalJavascript
reportUnmatched: false
# Caused by missing return type on \Drupal\FunctionalJavascriptTests\WebDriverTestBase::assertSession().
-
message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:wait[a-zA-Z]+\\(\\)\\.$#"
paths:
- tests/src/FunctionalJavascript
reportUnmatched: false
# Caused by \Drupal\KernelTests\KernelTestBase::$container having the wrong type.
-
message: "#^Property Drupal\\\\KernelTests\\\\KernelTestBase\\:\\:\\$container \\(Drupal\\\\Core\\\\DependencyInjection\\\\ContainerBuilder\\) does not accept Drupal\\\\Component\\\\DependencyInjection\\\\ContainerInterface\\.$#"
paths:
- tests/src/Kernel/DatabaseTablesTest.php
reportUnmatched: false
# Caused by \Drupal\Tests\user\Traits\UserCreationTrait::createUser() returning FALSE instead of throwing an exception.
-
message: "#^Parameter \\#1 \\$account of method Drupal\\\\Tests\\\\BrowserTestBase\\:\\:drupalLogin\\(\\) expects Drupal\\\\Core\\\\Session\\\\AccountInterface, Drupal\\\\user\\\\Entity\\\\User\\|false given\\.$#"
paths:
- tests/src/Functional
- tests/src/FunctionalJavascript
reportUnmatched: false
### Package Manager
# @todo Remove after resolving https://www.drupal.org/i/3501836.
# Caused by using self instead of static as a return type in \Drupal\fixture_manipulator\FixtureManipulator.
-
message: "#^Method Drupal\\\\fixture_manipulator\\\\FixtureManipulator\\:\\:commitChanges\\(\\) invoked with 0 parameters, 1 required\\.$#"
paths:
- tests/src/Kernel/InstallerTest.php
- tests/src/Kernel/CoreNotUpdatedValidatorTest.php
- tests/src/Kernel/PackageNotInstalledValidatorTest.php
reportUnmatched: false
# Caused by missing return type on \Drupal\Tests\package_manager\Traits\FixtureManipulatorTrait::getStageFixtureManipulator().
-
message: "#^Call to an undefined method object\\:\\:setCorePackageVersion\\(\\)\\.$#"
paths:
- tests/src/Kernel/CoreNotUpdatedValidatorTest.php
reportUnmatched: false
# Caused by missing @throws on \Drupal\package_manager\StageBase::apply().
-
message: "#^Dead catch \\- Drupal\\\\package_manager\\\\Exception\\\\StageEventException is never thrown in the try block\\.$#"
paths:
- tests/src/Kernel/CoreNotUpdatedValidatorTest.php
reportUnmatched: false
# @todo: Remove the following rules when support is dropped for Drupal 10.2, which does not have recipes.
-
......
......@@ -13,7 +13,7 @@ use Drupal\Core\Recipe\Recipe;
* Populates the project_browser_projects using a fixture with PHP serialized
* items.
*/
function project_browser_install() {
function project_browser_install(): void {
if (class_exists(Recipe::class)) {
$config = \Drupal::configFactory()
->getEditable('project_browser.admin_settings');
......@@ -26,7 +26,7 @@ function project_browser_install() {
/**
* Implements hook_update_last_removed().
*/
function project_browser_update_last_removed() {
function project_browser_update_last_removed(): int {
return 9017;
}
......@@ -35,7 +35,7 @@ function project_browser_update_last_removed() {
*
* Remove disable_add_new_module setting.
*/
function project_browser_update_9018() {
function project_browser_update_9018(): void {
$config_factory = \Drupal::configFactory();
$config_factory->getEditable('project_browser.admin_settings')
->clear('disable_add_new_module')
......@@ -47,7 +47,7 @@ function project_browser_update_9018() {
*
* Remove the Drupal.org(Mock API) setting and tables.
*/
function project_browser_update_9019() {
function project_browser_update_9019(): void {
// Remove the mock from the enabled_sources, if present.
$config = \Drupal::configFactory()->getEditable('project_browser.admin_settings');
$enabled_sources = $config->get('enabled_sources');
......
......@@ -13,10 +13,10 @@ use Drupal\project_browser\Plugin\ProjectBrowserSource\Recipes;
/**
* Implements hook_help().
*/
function project_browser_help($route_name, RouteMatchInterface $route_match) {
function project_browser_help(string $route_name, RouteMatchInterface $route_match): string {
$output = '';
switch ($route_name) {
case 'help.page.project_browser':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t("The Project Browser module allows users to easily search for available Drupal modules from your site. Enhanced filtering is provided so you can find what you need.") . '</p>';
$output .= '<p>' . t('For more information, see the <a href=":project_browser">online documentation for the Project Browser module</a>.', [':project_browser' => 'https://www.drupal.org/docs/contributed-modules/project-browser']) . '</p>';
......@@ -28,14 +28,14 @@ function project_browser_help($route_name, RouteMatchInterface $route_match) {
$output .= '<dd>' . t('Users who have the <em>Administer site configuration</em> permission can select where to search for modules from the <a href=":project_browser_settings">Project Browser settings page</a>. This can include the modules already on your site as well as contributed modules on Drupal.org', [':project_browser_settings' => Url::fromRoute('project_browser.settings')->toString()]) . '</dd>';
$output .= '</dl>';
return $output;
}
return $output;
}
/**
* Implements hook_theme().
*/
function project_browser_theme() {
function project_browser_theme(): array {
return [
'project_browser_main_app' => [
'variables' => [],
......
......@@ -69,7 +69,8 @@ final class Activator implements ActivatorInterface {
*/
public function supports(Project $project): bool {
try {
return $this->getActivatorForProject($project) instanceof ActivatorInterface;
$this->getActivatorForProject($project);
return TRUE;
}
catch (\InvalidArgumentException) {
return FALSE;
......
......@@ -13,16 +13,18 @@ use Drupal\project_browser\ComposerInstaller\Installer;
use Drupal\project_browser\EnabledSourceHandler;
use Drupal\project_browser\InstallState;
use Drupal\project_browser\ProjectBrowser\Project;
use Drupal\project_browser_test\Plugin\ProjectBrowserSource\ProjectBrowserTestMock;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Defines a controller to install projects via UI.
*/
class InstallerController extends ControllerBase {
final class InstallerController extends ControllerBase {
/**
* No require or install in progress for a given module.
......@@ -64,7 +66,11 @@ class InstallerController extends ControllerBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
public static function create(ContainerInterface $container): static {
// @see \Drupal\project_browser\ProjectBrowserServiceProvider.
assert($container->get(Installer::class) instanceof Installer);
assert($container->get(InstallState::class) instanceof InstallState);
return new static(
$container->get(Installer::class),
$container->get(EnabledSourceHandler::class),
......@@ -78,7 +84,7 @@ class InstallerController extends ControllerBase {
/**
* Checks if UI install is enabled on the site.
*/
public function access() :AccessResult {
public function access(): AccessResult {
$ui_install = $this->config('project_browser.admin_settings')->get('allow_ui_install');
return AccessResult::allowedIf((bool) $ui_install);
}
......@@ -131,7 +137,7 @@ class InstallerController extends ControllerBase {
/**
* Provides a JSON response for a given error.
*
* @param \Exception $e
* @param \Throwable $e
* The error that occurred.
* @param string $phase
* The phase the error occurred in.
......@@ -139,7 +145,7 @@ class InstallerController extends ControllerBase {
* @return \Symfony\Component\HttpFoundation\JsonResponse
* Provides an error message to be displayed by the Project Browser UI.
*/
private function errorResponse(\Exception $e, string $phase = ''): JsonResponse {
private function errorResponse(\Throwable $e, string $phase = ''): JsonResponse {
$exception_type_short = (new \ReflectionClass($e))->getShortName();
$exception_message = $e->getMessage();
$response_body = ['message' => "$exception_type_short: $exception_message"];
......@@ -213,7 +219,7 @@ class InstallerController extends ControllerBase {
// accessed after. This final check ensures a destroy is not attempted
// during apply.
if ($this->installer->isApplying()) {
throw new StageException('Another project is being added. Try again in a few minutes.');
throw new StageException($this->installer, 'Another project is being added. Try again in a few minutes.');
}
// Adding the TRUE parameter to destroy is dangerous, but we provide it
......@@ -351,6 +357,7 @@ class InstallerController extends ControllerBase {
if ($source === NULL) {
return new JsonResponse(['message' => "Cannot download $project->id from any available source"], 500);
}
assert($source instanceof ProjectBrowserTestMock);
if (!$source->isProjectSafe($project)) {
return new JsonResponse(['message' => "$project->machineName is not safe to add because its security coverage has been revoked"], 500);
}
......@@ -439,10 +446,10 @@ class InstallerController extends ControllerBase {
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* @return \Symfony\Component\HttpFoundation\Response
* Status message.
*/
public function activate(Request $request): JsonResponse {
public function activate(Request $request): Response {
foreach ($request->toArray() as $project) {
$project = $this->enabledSourceHandler->getStoredProject($project);
$this->installState->setState($project, 'activating');
......
......@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response;
/**
* Controller for the proxy layer.
*/
class ProjectBrowserEndpointController extends ControllerBase {
final class ProjectBrowserEndpointController extends ControllerBase {
/**
* Constructor for endpoint controller.
......@@ -27,7 +27,7 @@ class ProjectBrowserEndpointController extends ControllerBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
public static function create(ContainerInterface $container): static {
return new static(
$container->get(EnabledSourceHandler::class),
);
......@@ -44,9 +44,10 @@ class ProjectBrowserEndpointController extends ControllerBase {
* @return \Symfony\Component\HttpFoundation\JsonResponse
* Typically a project listing.
*/
public function getAllProjects(Request $request) {
public function getAllProjects(Request $request): JsonResponse {
$id = $request->query->get('id');
if ($id) {
assert(is_string($id));
return new JsonResponse($this->enabledSource->getStoredProject($id));
}
......@@ -125,7 +126,7 @@ class ProjectBrowserEndpointController extends ControllerBase {
/**
* Returns a list of categories.
*/
public function getAllCategories() {
public function getAllCategories(): JsonResponse {
$current_sources = $this->enabledSource->getCurrentSources();
if (!$current_sources) {
return new JsonResponse([], Response::HTTP_ACCEPTED);
......
......@@ -28,7 +28,7 @@ final class ProjectBrowserCommands extends DrushCommands {
#[Usage(name: 'project-browser:storage-clear', description: 'Clear stored Project Browser data')]
public function storageClear(): void {
$this->enabledSourceHandler->clearStorage();
$this->logger()->success(dt('Stored data from Project Browser sources have been cleared.'));
$this->logger()?->success(dt('Stored data from Project Browser sources have been cleared.'));
}
}
......@@ -51,10 +51,13 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$install_readiness = $container->get(InstallReadiness::class, ContainerInterface::NULL_ON_INVALID_REFERENCE);
assert(is_null($install_readiness) || $install_readiness instanceof InstallReadiness);
return new static(
$plugin_id,
$plugin_definition,
$container->get(InstallReadiness::class, ContainerInterface::NULL_ON_INVALID_REFERENCE),
$install_readiness,
$container->get(ModuleHandlerInterface::class),
$container->get(ConfigFactoryInterface::class),
);
......@@ -123,7 +126,7 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn
];
// @todo Fix https://www.drupal.org/node/3494512 to avoid adding
// hard-coded values. #techdebt
if ($source->getPluginId() !== 'recipes' && $package_manager['available']) {
if ($source->getPluginId() !== 'recipes' && $package_manager['available'] && is_object($this->installReadiness)) {
$package_manager = array_merge($package_manager, $this->installReadiness->validatePackageManager());
$package_manager['status_checked'] = TRUE;
}
......
......@@ -16,7 +16,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Settings form for Project Browser.
*/
class SettingsForm extends ConfigFormBase {
final class SettingsForm extends ConfigFormBase {
public function __construct(
ConfigFactoryInterface $config_factory,
......@@ -32,7 +32,7 @@ class SettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
public static function create(ContainerInterface $container): static {
return new static(
$container->get(ConfigFactoryInterface::class),
$container->get(TypedConfigManagerInterface::class),
......@@ -109,6 +109,7 @@ class SettingsForm extends ConfigFormBase {
'#attributes' => [
'id' => 'project_browser',
],
'#tabledrag' => [],
];
$options = [
'enabled' => $this->t('Enabled'),
......@@ -117,6 +118,7 @@ class SettingsForm extends ConfigFormBase {
if (count($source_plugins) > 1) {
$form['#attached']['library'][] = 'project_browser/tabledrag';
foreach ($options as $status => $title) {
assert(is_array($table['#tabledrag']));
$table['#tabledrag'][] = [
'action' => 'match',
'relationship' => 'sibling',
......@@ -135,11 +137,11 @@ class SettingsForm extends ConfigFormBase {
'class' => ['status-title', 'status-title-' . $status],
'no_striping' => TRUE,
],
];
$table['status-' . $status]['title'] = [
'#plain_text' => $title,
'#wrapper_attributes' => [
'colspan' => 4,
'title' => [
'#plain_text' => $title,
'#wrapper_attributes' => [
'colspan' => 4,
],
],
];
......@@ -204,7 +206,7 @@ class SettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
public function validateForm(array &$form, FormStateInterface $form_state): void {
$all_plugins = $form_state->getValue('enabled_sources');
if (!array_key_exists('enabled', array_count_values(array_column($all_plugins, 'status')))) {
$form_state->setErrorByName('enabled_sources', $this->t('At least one source plugin must be enabled.'));
......@@ -214,10 +216,10 @@ class SettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
public function submitForm(array &$form, FormStateInterface $form_state): void {
$settings = $this->config('project_browser.admin_settings');
$all_plugins = $form_state->getValue('enabled_sources');
$enabled_plugins = array_filter($all_plugins, fn($source) => $source['status'] === 'enabled');
$enabled_plugins = array_filter($all_plugins, fn($source): bool => $source['status'] === 'enabled');
$settings
->set('enabled_sources', array_keys($enabled_plugins))
->set('allow_ui_install', $form_state->getValue('allow_ui_install'))
......
......@@ -17,16 +17,13 @@ use Symfony\Component\HttpFoundation\Response;
*/
final class ModuleActivator implements ActivatorInterface {
use ActivationInstructionsTrait {
__construct as traitConstruct;
}
use ActivationInstructionsTrait;
public function __construct(
private readonly ModuleInstallerInterface $moduleInstaller,
ModuleExtensionList $moduleList,
FileUrlGeneratorInterface $fileUrlGenerator,
protected readonly ModuleExtensionList $moduleList,
protected readonly FileUrlGeneratorInterface $fileUrlGenerator,
) {
$this->traitConstruct($moduleList, $fileUrlGenerator);
}
/**
......@@ -60,7 +57,7 @@ final class ModuleActivator implements ActivatorInterface {
/**
* {@inheritdoc}
*/
public function getInstructions(Project $project): string|Url|null {
public function getInstructions(Project $project): string|Url {
if ($this->getStatus($project) === ActivationStatus::Present) {
return Url::fromRoute('system.modules_list', options: [
'fragment' => 'module-' . str_replace('_', '-', $project->machineName),
......
......@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\project_browser\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\project_browser\EnabledSourceHandler;
......@@ -24,7 +25,7 @@ final class LocalTaskDeriver extends DeriverBase implements ContainerDeriverInte
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
public static function create(ContainerInterface $container, $base_plugin_id): static {
return new static(
$container->get(EnabledSourceHandler::class),
);
......@@ -50,7 +51,7 @@ final class LocalTaskDeriver extends DeriverBase implements ContainerDeriverInte
$local_task['route_parameters'] = [
'source' => $source_id,
];
$derivative_id = str_replace($source::DERIVATIVE_SEPARATOR, '__', $source_id);
$derivative_id = str_replace(PluginBase::DERIVATIVE_SEPARATOR, '__', $source_id);
$this->derivatives[$derivative_id] = $local_task;
}
}
......
......@@ -21,7 +21,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
* description = @Translation("Modules included in Drupal core"),
* )
*/
class DrupalCore extends ProjectBrowserSourceBase {
final class DrupalCore extends ProjectBrowserSourceBase {
/**
* All core modules are covered under security policy.
......@@ -74,7 +74,7 @@ class DrupalCore extends ProjectBrowserSourceBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
......@@ -92,10 +92,10 @@ class DrupalCore extends ProjectBrowserSourceBase {
* The array containing core modules, keyed by module machine name.
*/
protected function getCoreModules() {
$projects = array_filter($this->moduleExtensionList->reset()->getList(), fn(Extension $project) => $project->origin === 'core');
$projects = array_filter($this->moduleExtensionList->reset()->getList(), fn(Extension $project): bool => $project->origin === 'core');
$include_tests = Settings::get('extension_discovery_scan_tests') || drupal_valid_test_ua();
if (!$include_tests) {
$projects = array_filter($projects, fn(Extension $project) => empty($project->info['hidden']) && $project->info['package'] !== 'Testing');
$projects = array_filter($projects, fn(Extension $project): bool => empty($project->info['hidden']) && $project->info['package'] !== 'Testing');
}
return $projects;
}
......@@ -123,22 +123,22 @@ class DrupalCore extends ProjectBrowserSourceBase {
// Filter by project machine name.
if (!empty($query['machine_name'])) {
$projects = array_filter($projects, fn(Project $project) => $project->machineName === $query['machine_name']);
$projects = array_filter($projects, fn(Project $project): bool => $project->machineName === $query['machine_name']);
}
// Filter by coverage.
if (!empty($query['security_advisory_coverage'])) {
$projects = array_filter($projects, fn(Project $project) => $project->isCovered);
$projects = array_filter($projects, fn(Project $project): bool => $project->isCovered ?? FALSE);
}
// Filter by categories.
if (!empty($query['categories'])) {
$projects = array_filter($projects, fn(Project $project) => array_intersect(array_column($project->categories, 'id'), explode(',', $query['categories'])));
$projects = array_filter($projects, fn(Project $project): bool => empty(array_intersect(array_column($project->categories, 'id'), explode(',', $query['categories']))));
}
// Filter by search text.
if (!empty($query['search'])) {
$projects = array_filter($projects, fn(Project $project) => stripos($project->title, $query['search']) !== FALSE);
$projects = array_filter($projects, fn(Project $project): bool => stripos($project->title, $query['search']) !== FALSE);
}
// Filter by sorting criterion.
......@@ -181,7 +181,7 @@ class DrupalCore extends ProjectBrowserSourceBase {
$returned_list[] = new Project(
logo: [
'file' => [
'uri' => $request->getSchemeAndHttpHost() . '/core/misc/logo/drupal-logo.svg',
'uri' => $request?->getSchemeAndHttpHost() . '/core/misc/logo/drupal-logo.svg',
'resource' => 'image',
],
'alt' => '',
......
......@@ -33,7 +33,7 @@ use Symfony\Component\HttpFoundation\Response;
* }
* )
*/
class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
final class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
use StringTranslationTrait;
......@@ -98,7 +98,7 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
......@@ -159,7 +159,7 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
$response_data = Json::decode($response->getBody()->getContents());
$result['data'] = array_merge($result['data'], $response_data['data']);
if (!empty($response_data['included'])) {
if (!empty($response_data['included']) && !empty($result['included'])) {
$result['included'] = array_merge($result['included'], $response_data['included']);
}
$iterations++;
......@@ -296,13 +296,13 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
}
$api_response = $this->fetchProjects($query);
if (!is_array($api_response) || $api_response['code'] !== Response::HTTP_OK) {
if ($api_response['code'] !== Response::HTTP_OK) {
$error_message = $api_response['message'] ?? $this->t('Error querying data.');
return $this->createResultsPage([], 0, $error_message);
}
$returned_list = [];
if (is_array($api_response) && !empty($api_response['list'])) {
if (!empty($api_response['list'])) {
$related = !empty($api_response['related']) ? $api_response['related'] : NULL;
$current_drupal_version = $this->getNumericSemverVersion(\Drupal::VERSION);
$maintained_values = $filter_values['maintained'] ?? [];
......@@ -313,7 +313,7 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
$uid_info = $project['relationships']['uid']['data'];
$maintenance_status = $project['relationships']['field_maintenance_status']['data'] ?? [];
if (!empty($maintenance_status)) {
if (is_array($maintenance_status)) {
$maintenance_status = [
'id' => $maintenance_status['id'],
'name' => $related[$maintenance_status['type']][$maintenance_status['id']]['name'],
......@@ -328,8 +328,8 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
];
}
$module_categories = $project['relationships']['field_module_categories']['data'] ?? [];
if (!empty($module_categories)) {
$module_categories = $project['relationships']['field_module_categories']['data'] ?? NULL;
if (is_array($module_categories)) {
$categories = [];
foreach ($module_categories as $module_category) {
$categories[] = [
......@@ -340,8 +340,8 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
$module_categories = $categories;
}
$project_images = $project['relationships']['field_project_images']['data'] ?? [];
if (!empty($project_images)) {
$project_images = $project['relationships']['field_project_images']['data'] ?? NULL;
if (is_array($project_images)) {
$images = [];
foreach ($project_images as $image) {
$uri = self::DRUPAL_ORG_ENDPOINT . $related[$image['type']][$image['id']]['uri']['url'];
......@@ -400,8 +400,8 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
'name' => $related[$uid_info['type']][$uid_info['id']]['name'],
],
packageName: $project['attributes']['field_composer_namespace'] ?? 'drupal/' . $machine_name,
categories: $module_categories,
images: $project_images,
categories: $module_categories ?? [],
images: $project_images ?? [],
url: Url::fromUri('https://www.drupal.org/project/' . $machine_name),
);
$returned_list[] = $project_object;
......@@ -565,7 +565,7 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
if ($extra = $version_object->getVersionExtra()) {
$version = str_replace("-$extra", '', $version);
}
$minor_version = $version_object->getMinorVersion() ?? 0;
$minor_version = $version_object->getMinorVersion() ?? '0';
$patch_version = explode('.', $version)[2] ?? '0';
return (int) (
......
......@@ -25,7 +25,7 @@ use Symfony\Component\Finder\Finder;
/**
* A source plugin that exposes recipes installed locally.
*/
class Recipes extends ProjectBrowserSourceBase {
final class Recipes extends ProjectBrowserSourceBase {
public function __construct(
private readonly FileSystemInterface $fileSystem,
......@@ -43,6 +43,7 @@ class Recipes extends ProjectBrowserSourceBase {
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
assert(is_string($container->getParameter('app.root')));
return new static(
$container->get(FileSystemInterface::class),
$container->get('cache.project_browser'),
......@@ -89,6 +90,7 @@ class Recipes extends ProjectBrowserSourceBase {
}
else {
$package = file_get_contents($path . '/composer.json');
assert(is_string($package));
$package = Json::decode($package);
$package_name = $package['name'];
......@@ -125,22 +127,22 @@ class Recipes extends ProjectBrowserSourceBase {
// Filter by project machine name.
if (!empty($query['machine_name'])) {
$projects = array_filter($projects, fn(Project $project) => $project->machineName === $query['machine_name']);
$projects = array_filter($projects, fn(Project $project): bool => $project->machineName === $query['machine_name']);
}
// Filter by coverage.
if (!empty($query['security_advisory_coverage'])) {
$projects = array_filter($projects, fn(Project $project) => $project->isCovered);
$projects = array_filter($projects, fn(Project $project): bool => $project->isCovered ?? FALSE);
}
// Filter by categories.
if (!empty($query['categories'])) {
$projects = array_filter($projects, fn(Project $project) => array_intersect(array_column($project->categories, 'id'), explode(',', $query['categories'])));
$projects = array_filter($projects, fn(Project $project): bool => empty(array_intersect(array_column($project->categories, 'id'), explode(',', $query['categories']))));
}
// Filter by search text.
if (!empty($query['search'])) {
$projects = array_filter($projects, fn(Project $project) => stripos($project->title, $query['search']) !== FALSE);
$projects = array_filter($projects, fn(Project $project): bool => stripos($project->title, $query['search']) !== FALSE);
}
$total = count($projects);
......@@ -181,7 +183,9 @@ class Recipes extends ProjectBrowserSourceBase {
$contrib_recipe_names = InstalledVersions::getInstalledPackagesByType(Recipe::COMPOSER_PROJECT_TYPE);
if ($contrib_recipe_names) {
$path = InstalledVersions::getInstallPath($contrib_recipe_names[0]);
assert(is_string($path));
$path = $this->fileSystem->realpath($path);
assert(is_string($path));
$search_in[] = dirname($path);
}
......
......@@ -7,7 +7,6 @@ use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter;
use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines an abstract base class for a Project Browser source.
......@@ -20,17 +19,6 @@ abstract class ProjectBrowserSourceBase extends PluginBase implements ProjectBro
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
);
}
/**
* {@inheritdoc}
*/
......@@ -102,4 +90,13 @@ abstract class ProjectBrowserSourceBase extends PluginBase implements ProjectBro
);
}
/**
* {@inheritdoc}
*/
public function getPluginDefinition(): array {
$definition = parent::getPluginDefinition();
assert(is_array($definition));
return $definition;
}
}
......@@ -62,4 +62,9 @@ interface ProjectBrowserSourceInterface extends PluginInspectionInterface {
*/
public function getSortOptions(): array;
/**
* {@inheritdoc}
*/
public function getPluginDefinition(): array;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment