Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"drupal/pathauto": "*",
"drupal/token": "*",
"drupal/redirect": "*",
"drush/drush": ">=12.5.0"
"drush/drush": ">=12.5.0",
"rector/rector": "<2.6.5"
},
"conflict": {
"drush/drush": "<12.5.0"
Expand Down
6 changes: 6 additions & 0 deletions filefield_paths.module
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ function filefield_paths_file_presave(FileInterface $file): void {// phpcs:ignor
\Drupal::service(File::class)->filePresave($file);
}

// @phpstan-ignore-next-line
#[LegacyHook]
function filefield_paths_file_delete(FileInterface $file): void {// phpcs:ignore Drupal.Commenting.FunctionComment.Missing, Squiz.WhiteSpace.FunctionSpacing.Before
\Drupal::service(File::class)->fileDelete($file);
}

// @phpstan-ignore-next-line
#[LegacyHook]
function filefield_paths_file_url_alter(string &$uri): void {// phpcs:ignore Drupal.Commenting.FunctionComment.Missing, Squiz.WhiteSpace.FunctionSpacing.Before
Expand Down
2 changes: 1 addition & 1 deletion renovate.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": ["config:recommended"],
"automerge": true,
"automerge": false,
"rangeStrategy": "bump",
"dependencyDashboard": true,
"pinDigests": true,
Expand Down
8 changes: 3 additions & 5 deletions src/Drush/Commands/Commands.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,9 @@ protected function buildInfo(): array {
];
}

if (!isset($info[$entity_type_name][$bundle])) {
$info[$entity_type_name][$bundle] = [
'#label' => sprintf('%s (%s)', $bundles_info[$bundle]['label'], $bundle),
];
}
$info[$entity_type_name][$bundle] ??= [
'#label' => sprintf('%s (%s)', $bundles_info[$bundle]['label'], $bundle),
];
$field_instance = $this->entityFieldManager->getFieldDefinitions($entity_type_name, $bundle)[$field->getName()] ?? NULL;
if ($field_instance && is_array($info[$entity_type_name][$bundle])) {
$info[$entity_type_name][$bundle][$field_instance->getName()] = sprintf('%s (%s)', $field_instance->getLabel(), $field_instance->getName());
Expand Down
10 changes: 8 additions & 2 deletions src/Hook/FieldWidgetSingleElementForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Drupal\filefield_paths\Hook;

use Drupal\Component\Utility\Crypt;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Form\FormStateInterface;
Expand Down Expand Up @@ -39,8 +40,13 @@ public function formAlter(array &$element, FormStateInterface $form_state, array
$settings = $context['items']->getFieldDefinition()
->getThirdPartySettings('filefield_paths');
$temp_location = $settings['temp_location'] ?? NULL;
$element['#upload_location'] = $temp_location ?:
$this->getSettings()->get('temp_location');
$temp_location = $temp_location ?: $this->getSettings()->get('temp_location');
// Stage each upload in a directory of its own. Two files with the same
// name would otherwise take the same staged path in turn, and the image
// style preview URL is built from that path. A browser or a CDN then
// shows the first image in place of the second.
// See https://www.drupal.org/i/3277844.
$element['#upload_location'] = sprintf('%s/ffp-%s', rtrim((string) $temp_location, '/'), Crypt::randomBytesBase64(8));
}
}

Expand Down
56 changes: 56 additions & 0 deletions src/Hook/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

namespace Drupal\filefield_paths\Hook;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\file\FileInterface;
Expand All @@ -17,6 +20,22 @@ final class File {

use StringTranslationTrait;

/**
* Constructor.
*
* @param \Drupal\Core\File\FileSystemInterface $fileSystem
* The file system service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
*/
public function __construct(
private readonly FileSystemInterface $fileSystem,
private readonly ConfigFactoryInterface $configFactory,
private readonly EntityTypeManagerInterface $entityTypeManager,
) {}

/**
* Implements hook_entity_base_field_info().
*/
Expand Down Expand Up @@ -49,4 +68,41 @@ public function filePresave(FileInterface $file): void {// phpcs:ignore Squiz.Wh
}
}

/**
* Implements hook_file_delete().
*/
#[Hook('file_delete')]
public function fileDelete(FileInterface $file): void {
// Each upload is staged in a directory of its own, inside a staging
// location. When the file is deleted before it was saved to an entity,
// remove the directory too. The name alone does not prove this module
// made the directory, so it must also sit in a configured staging
// location. rmdir() fails on a directory that still holds files.
$directory = $this->fileSystem->dirname($file->getFileUri());
if (!preg_match('#^(.+)/ffp-[A-Za-z0-9_-]+$#', $directory, $matches)) {
return;
}
if (in_array($matches[1], $this->stagingLocations(), TRUE)) {
@$this->fileSystem->rmdir($directory);
}
}

/**
* Returns every configured staging location.
*
* @return string[]
* The global location and every field level override, without a
* trailing slash.
*/
private function stagingLocations(): array {
$locations = [
(string) $this->configFactory->get('filefield_paths.settings')->get('temp_location'),
];
foreach ($this->entityTypeManager->getStorage('field_config')->loadMultiple() as $field) {
$locations[] = (string) $field->getThirdPartySetting('filefield_paths', 'temp_location');
}
$locations = array_map(static fn (string $location): string => rtrim($location, '/'), array_filter($locations));
return array_values(array_unique($locations));
}

}
13 changes: 8 additions & 5 deletions tests/src/Functional/FileFieldPathsGeneralTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ public function testUploadFile(): void {
$edit[sprintf('files[%s_0]', $field_name)] = $file_system->realpath($test_file->getFileUri());
$this->submitForm($edit, 'Upload');

// Ensure that the file was put into the Temporary file location.
// Ensure that the file was put into the Temporary file location. Each
// upload is staged in a directory of its own under that location.
$config = $this->config('filefield_paths.settings');
$session->responseContains(\Drupal::service('file_url_generator')->generateString(sprintf('%s/%s', $config->get('temp_location'), $test_file->getFilename())));
$temp_location_url = \Drupal::service('file_url_generator')->generateString($config->get('temp_location'));
$session->responseMatches(sprintf('#%s/ffp-[A-Za-z0-9_-]+/%s#', preg_quote($temp_location_url, '#'), preg_quote($test_file->getFilename(), '#')));

// Save the node.
$this->submitForm([], 'Save');
Expand Down Expand Up @@ -139,9 +141,10 @@ public function testUploadFileWithCustomTempLocation(): void {
$this->submitForm($edit, 'Upload');

// Ensure that the file was put into the custom Temporary file location
// defined on the field configuration (not the global setting).
$generated_url = \Drupal::service('file_url_generator')->generateString($custom_dir . '/' . $test_file->getFilename());
$session->responseContains($generated_url);
// defined on the field configuration (not the global setting). Each
// upload is staged in a directory of its own under that location.
$custom_dir_url = \Drupal::service('file_url_generator')->generateString($custom_dir);
$session->responseMatches(sprintf('#%s/ffp-[A-Za-z0-9_-]+/%s#', preg_quote($custom_dir_url, '#'), preg_quote($test_file->getFilename(), '#')));

// Save the node.
$this->submitForm([], 'Save');
Expand Down
Loading