Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drupal 11 readiness. #1407

Merged
merged 18 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,24 @@ on:
jobs:
drupal:
name: Drupal ${{ matrix.drupal-core }} (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
# We cannot use ubuntu-latest right now as it still points to 22.04 and we need a newer database driver."
apathak18 marked this conversation as resolved.
Show resolved Hide resolved
runs-on: ubuntu-24.04
apathak18 marked this conversation as resolved.
Show resolved Hide resolved
env:
extensions: mbstring, xml, pdo_sqlite, gd, opcache
strategy:
fail-fast: false
matrix:
php-versions: ['8.1', '8.2']
php-versions: ['8.1', '8.2', '8.3']
drupal-core: ['10.3.x']
phpstan: ['0']
include:
# Extra run to test older supported Drupal 10.1.x.
- php-versions: '8.1'
drupal-core: '10.1.x'
phpstan: '0'
# Extra run to test older supported Drupal 10.2.x.
- php-versions: '8.1'
drupal-core: '10.2.x'
phpstan: '0'
# We only need to run PHPStan once on the latest PHP version.
- php-versions: '8.3'
drupal-core: '10.3.x'
drupal-core: '11.0.x'
apathak18 marked this conversation as resolved.
Show resolved Hide resolved
phpstan: '1'
steps:
- name: Checkout Drupal core
Expand Down Expand Up @@ -86,12 +83,13 @@ jobs:
composer --no-interaction run-script drupal-phpunit-upgrade
composer config --no-plugins allow-plugins.phpstan/extension-installer true

# Revisit - check for latest release of dependent modules.
- name: Install GraphQL dependencies
run: |
composer --no-interaction --no-progress require \
webonyx/graphql-php:^14.8 \
drupal/typed_data:^1.0 \
drupal/redirect:^1.0
drupal/typed_data:^2.0 \
drupal/redirect:dev-1.x

- name: Run PHPUnit
run: |
Expand All @@ -109,8 +107,8 @@ jobs:
mglaman/phpstan-drupal:^1.1.2 \
phpstan/phpstan-deprecation-rules:^1.0.0 \
jangregor/phpstan-prophecy:^1.0.0 \
phpstan/phpstan-phpunit:^1.0.0 \
phpstan/extension-installer:^1.0
phpstan/phpstan-phpunit:^1.4 \
phpstan/extension-installer:^1.4
composer --no-interaction --no-progress --with-all-dependencies upgrade drupal/coder:8.3.24

- name: Run PHPStan
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"homepage": "http://drupal.org/project/graphql",
"license": "GPL-2.0+",
"require": {
"php": ">=7.3",
"drupal/typed_data": "^1.0 || ^2.0",
"php": ">=8.1",
"webonyx/graphql-php": "^14.8.0"
},
"minimum-stability": "dev"
Expand Down
2 changes: 1 addition & 1 deletion examples/graphql_composable/graphql_composable.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ package: GraphQL
dependencies:
- graphql:graphql
- node:node
core_version_requirement: ^10.1
core_version_requirement: ^10.2 || ^11
2 changes: 1 addition & 1 deletion examples/graphql_example/graphql_examples.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ package: GraphQL
dependencies:
- graphql:graphql
- node:node
core_version_requirement: ^10.1
core_version_requirement: ^10.2 || ^11
2 changes: 1 addition & 1 deletion graphql.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ type: module
description: 'Base module for integrating GraphQL with Drupal.'
package: GraphQL
configure: graphql.config_page
core_version_requirement: ^10.1
core_version_requirement: ^10.2 || ^11
dependencies:
- typed_data:typed_data
1 change: 1 addition & 0 deletions graphql.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ services:
- '@renderer'
- '@event_dispatcher'
- '@image.factory'
- '@file.validator'

plugin.manager.graphql.persisted_query:
class: Drupal\graphql\Plugin\PersistedQueryPluginManager
Expand Down
2 changes: 1 addition & 1 deletion src/EventSubscriber/ApqSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function onBeforeOperation(OperationEvent $event): void {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
public static function getSubscribedEvents(): array {
return [
OperationEvent::GRAPHQL_OPERATION_BEFORE => 'onBeforeOperation',
];
Expand Down
2 changes: 1 addition & 1 deletion src/EventSubscriber/OperationSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function onAfterOperation(OperationEvent $event): void {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
public static function getSubscribedEvents(): array {
return [
OperationEvent::GRAPHQL_OPERATION_BEFORE => 'onBeforeOperation',
OperationEvent::GRAPHQL_OPERATION_AFTER => 'onAfterOperation',
Expand Down
2 changes: 1 addition & 1 deletion src/EventSubscriber/SubrequestSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function onKernelRequestFinished(FinishRequestEvent $event): void {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
public static function getSubscribedEvents(): array {
return [
KernelEvents::REQUEST => 'onKernelRequest',
KernelEvents::FINISH_REQUEST => 'onKernelRequestFinished',
Expand Down
79 changes: 44 additions & 35 deletions src/GraphQL/Utility/FileUpload.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\ByteSizeMarkup;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Utility\Token;
use Drupal\file\FileInterface;
use Drupal\file\Validation\FileValidatorInterface;
use Drupal\graphql\GraphQL\Response\FileUploadResponse;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
Expand Down Expand Up @@ -111,6 +113,13 @@ class FileUpload {
*/
protected $imageFactory;

/**
* The file validator service.
*
* @var \Drupal\file\Validation\FileValidatorInterface
*/
protected FileValidatorInterface $fileValidator;

/**
* Constructor.
*/
Expand All @@ -126,6 +135,7 @@ public function __construct(
RendererInterface $renderer,
EventDispatcherInterface $eventDispatcher,
ImageFactory $image_factory,
FileValidatorInterface $file_validator,
) {
/** @var \Drupal\file\FileStorageInterface $file_storage */
$file_storage = $entityTypeManager->getStorage('file');
Expand All @@ -140,6 +150,7 @@ public function __construct(
$this->renderer = $renderer;
$this->eventDispatcher = $eventDispatcher;
$this->imageFactory = $image_factory;
$this->fileValidator = $file_validator;
}

/**
Expand Down Expand Up @@ -193,10 +204,7 @@ public function saveFileUpload(UploadedFile $uploaded_file, array $settings): Fi
switch ($uploaded_file->getError()) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
// @todo Drupal 10.1 compatibility, needs to be converted to
// ByteSizeMarkup later.
// @phpstan-ignore-next-line
$maxUploadSize = format_size($this->getMaxUploadSize($settings));
$maxUploadSize = ByteSizeMarkup::create($this->getMaxUploadSize($settings));
$response->addViolation($this->t('The file @file could not be saved because it exceeds @maxsize, the maximum allowed size for uploads.', [
'@file' => $uploaded_file->getClientOriginalName(),
'@maxsize' => $maxUploadSize,
Expand Down Expand Up @@ -248,8 +256,8 @@ public function saveFileUpload(UploadedFile $uploaded_file, array $settings): Fi

$temp_file_path = $uploaded_file->getRealPath();

// Drupal 10.2 compatibility: use the deprecated constant for now.
// @phpstan-ignore-next-line
// Drupal 10.3 compatibility: use the deprecated constant for now.
// @phpstan-ignore-next-line as it is deprecated in D12.
$file_uri = $this->fileSystem->getDestinationFilename($file_uri, FileSystemInterface::EXISTS_RENAME);

// Lock based on the prepared file URI.
Expand All @@ -272,11 +280,17 @@ public function saveFileUpload(UploadedFile $uploaded_file, array $settings): Fi
// before it is saved.
$file->setSize(@filesize($temp_file_path));

// Validate against file_validate() first with the temporary path.
// @todo Drupal 10.1 compatibility, needs to be converted to file validate
// service later.
// @phpstan-ignore-next-line
$errors = file_validate($file, $validators);
// Validate against fileValidator first with the temporary path.
/** @var \Symfony\Component\Validator\ConstraintViolationListInterface $file_validate_errors */
$file_validate_errors = $this->fileValidator->validate($file, $validators);
$errors = [];
if (count($file_validate_errors) > 0) {
foreach ($file_validate_errors as $violation) {
$errors[] = $violation->getMessage();
}
}

// Validate Image resolution.
$maxResolution = $settings['max_resolution'] ?? 0;
$minResolution = $settings['min_resolution'] ?? 0;
if (!empty($maxResolution) || !empty($minResolution)) {
Expand All @@ -287,14 +301,13 @@ public function saveFileUpload(UploadedFile $uploaded_file, array $settings): Fi
$response->addViolations($errors);
return $response;
}

$file->setFileUri($file_uri);
// Move the file to the correct location after validation. Use
// FileSystemInterface::EXISTS_ERROR as the file location has already been
// determined above in FileSystem::getDestinationFilename().
try {
// Drupal 10.2 compatibility: use the deprecated constant for now.
// @phpstan-ignore-next-line
// Drupal 10.3 compatibility: use the deprecated constant for now.
// @phpstan-ignore-next-line as it is deprecated in D12.
$this->fileSystem->move($temp_file_path, $file_uri, FileSystemInterface::EXISTS_ERROR);
}
catch (FileException $e) {
Expand All @@ -315,7 +328,6 @@ public function saveFileUpload(UploadedFile $uploaded_file, array $settings): Fi
}

$file->save();

$response->setFileEntity($file);
return $response;
}
Expand Down Expand Up @@ -487,12 +499,12 @@ protected function validateFileImageResolution(FileInterface $file, $maximum_dim
protected function prepareFilename(string $filename, array &$validators): string {
// Don't rename if 'allow_insecure_uploads' evaluates to TRUE.
if (!$this->systemFileConfig->get('allow_insecure_uploads')) {
if (!empty($validators['file_validate_extensions'][0])) {
// If there is a file_validate_extensions validator and a list of
// valid extensions, munge the filename to protect against possible
// malicious extension hiding within an unknown file type. For example,
// "filename.html.foo".
$event = new FileUploadSanitizeNameEvent($filename, $validators['file_validate_extensions'][0]);
if (!empty($validators['FileExtension']['extensions'])) {
// If there is a fileValidator service to validate FileExtension and
// a list of valid extensions, munge the filename to protect against
// possible malicious extension hiding within an unknown file type.
// For example, "filename.html.foo".
$event = new FileUploadSanitizeNameEvent($filename, $validators['FileExtension']['extensions']);
$this->eventDispatcher->dispatch($event);
$filename = $event->getFilename();
}
Expand All @@ -502,33 +514,30 @@ protected function prepareFilename(string $filename, array &$validators): string
// and filename._php.txt, respectively).
if (preg_match(FileSystemInterface::INSECURE_EXTENSION_REGEX, $filename)) {
// If the file will be rejected anyway due to a disallowed extension, it
// should not be renamed; rather, we'll let file_validate_extensions()
// reject it below.
// should not be renamed; rather, we'll let fileValidator service
// to validate FileExtension reject it below.
$passes_validation = FALSE;
if (!empty($validators['file_validate_extensions'][0])) {
if (!empty($validators['FileExtension']['extensions'])) {
/** @var \Drupal\file\FileInterface $file */
$file = $this->fileStorage->create([]);
$file->setFilename($filename);
// @todo Drupal 10.1 compatibility, needs to be converted to file
// validator service later.
// @phpstan-ignore-next-line
$passes_validation = empty(file_validate_extensions($file, $validators['file_validate_extensions'][0]));
$passes_validation = count($this->fileValidator->validate($file, $validators['FileExtension']['extensions']));
}
if (empty($validators['file_validate_extensions'][0]) || $passes_validation) {
if (empty($validators['FileExtension']['extensions']) || ($passes_validation > 0)) {
if ((substr($filename, -4) != '.txt')) {
// The destination filename will also later be used to create the
// URI.
$filename .= '.txt';
}

$event = new FileUploadSanitizeNameEvent($filename, $validators['file_validate_extensions'][0] ?? '');
$event = new FileUploadSanitizeNameEvent($filename, $validators['FileExtension']['extensions'] ?? '');
$this->eventDispatcher->dispatch($event);
$filename = $event->getFilename();

// The .txt extension may not be in the allowed list of extensions. We
// have to add it here or else the file upload will fail.
if (!empty($validators['file_validate_extensions'][0])) {
$validators['file_validate_extensions'][0] .= ' txt';
if (!empty($validators['FileExtension']['extensions'])) {
$validators['FileExtension']['extensions'] .= ' txt';
}
}
}
Expand Down Expand Up @@ -579,7 +588,7 @@ protected function getUploadLocation(array $settings): string {
protected function getUploadValidators(array $settings): array {
$validators = [
// Add in our check of the file name length.
'file_validate_name_length' => [],
'FileNameLength' => [],
];

// Cap the upload size according to the PHP limit.
Expand All @@ -589,11 +598,11 @@ protected function getUploadValidators(array $settings): array {
}

// There is always a file size limit due to the PHP server limit.
$validators['file_validate_size'] = [$max_filesize];
$validators['FileSizeLimit'] = ['fileLimit' => $max_filesize];

// Add the extension check if necessary.
if (!empty($settings['file_extensions'])) {
$validators['file_validate_extensions'] = [$settings['file_extensions']];
$validators['FileExtension'] = ['extensions' => $settings['file_extensions']];
}

return $validators;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ public function resolve(FileInterface $entity = NULL, $style, RefinableCacheable
$access = $entity->access('view', NULL, TRUE);
$metadata->addCacheableDependency($access);
if ($access->isAllowed() && $image_style = ImageStyle::load($style)) {

// @phpstan-ignore-next-line
$width = $entity->width;
// @phpstan-ignore-next-line
$height = $entity->height;

// @phpstan-ignore-next-line
if (empty($width) || empty($height)) {
if ($width == NULL || $height == NULL) {
/** @var \Drupal\Core\Image\ImageInterface $image */
$image = \Drupal::service('image.factory')->get($entity->getFileUri());
if ($image->isValid()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type: module
name: GraphQL File Validate Test
description: Tests hook_file_validate() on uploads.
description: Tests file validate on uploads.
package: Testing
hidden: TRUE
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
use Drupal\file\FileInterface;

/**
* Implements hook_file_validate().
* Test to validate file exists.
*/
function graphql_file_validate_file_validate(FileInterface $file): void {
function graphql_file_validate_test_file(FileInterface $file): void {
apathak18 marked this conversation as resolved.
Show resolved Hide resolved
if (!file_exists($file->getFileUri())) {
throw new \Exception('File does not exist during validation: ' . $file->getFileUri());
apathak18 marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
graphql_file_validate_test_subscriber:
class: Drupal\graphql_file_validate\EventSubscriber\GraphqlFileValidationTestSubscriber
tags:
- { name: event_subscriber }
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Drupal\graphql_file_validate\EventSubscriber;

use Drupal\file\Validation\FileValidationEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Provides a file validation listener for graphql file validate test.
*/
class GraphqlFileValidationTestSubscriber implements EventSubscriberInterface {

/**
* Handles the file validation event.
*
* @param \Drupal\file\Validation\FileValidationEvent $event
* The event.
*/
public function onFileValidation(FileValidationEvent $event): void {
graphql_file_validate_test_file($event->file);
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [FileValidationEvent::class => 'onFileValidation'];
}

}
Loading