migration is cleaning textes, added pdf migration
This commit is contained in:
@@ -6,6 +6,7 @@ dependencies:
|
|||||||
- field.field.node.projet.body
|
- field.field.node.projet.body
|
||||||
- field.field.node.projet.field_artiste_s_invite_s
|
- field.field.node.projet.field_artiste_s_invite_s
|
||||||
- field.field.node.projet.field_dates
|
- field.field.node.projet.field_dates
|
||||||
|
- field.field.node.projet.field_fichiers
|
||||||
- field.field.node.projet.field_images
|
- field.field.node.projet.field_images
|
||||||
- field.field.node.projet.field_invite_accompagne_curate_p
|
- field.field.node.projet.field_invite_accompagne_curate_p
|
||||||
- field.field.node.projet.field_partenaire_s
|
- field.field.node.projet.field_partenaire_s
|
||||||
@@ -18,6 +19,7 @@ dependencies:
|
|||||||
- autocomplete_deluxe
|
- autocomplete_deluxe
|
||||||
- datetime_range
|
- datetime_range
|
||||||
- field_group
|
- field_group
|
||||||
|
- file
|
||||||
- image
|
- image
|
||||||
- path
|
- path
|
||||||
- text
|
- text
|
||||||
@@ -101,6 +103,7 @@ third_party_settings:
|
|||||||
group_media:
|
group_media:
|
||||||
children:
|
children:
|
||||||
- field_images
|
- field_images
|
||||||
|
- field_fichiers
|
||||||
label: Media
|
label: Media
|
||||||
region: content
|
region: content
|
||||||
parent_name: group_tabs
|
parent_name: group_tabs
|
||||||
@@ -161,6 +164,13 @@ content:
|
|||||||
region: content
|
region: content
|
||||||
settings: { }
|
settings: { }
|
||||||
third_party_settings: { }
|
third_party_settings: { }
|
||||||
|
field_fichiers:
|
||||||
|
type: file_generic
|
||||||
|
weight: 8
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
progress_indicator: throbber
|
||||||
|
third_party_settings: { }
|
||||||
field_images:
|
field_images:
|
||||||
type: image_image
|
type: image_image
|
||||||
weight: 7
|
weight: 7
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ dependencies:
|
|||||||
- field.field.node.projet.body
|
- field.field.node.projet.body
|
||||||
- field.field.node.projet.field_artiste_s_invite_s
|
- field.field.node.projet.field_artiste_s_invite_s
|
||||||
- field.field.node.projet.field_dates
|
- field.field.node.projet.field_dates
|
||||||
|
- field.field.node.projet.field_fichiers
|
||||||
- field.field.node.projet.field_images
|
- field.field.node.projet.field_images
|
||||||
- field.field.node.projet.field_invite_accompagne_curate_p
|
- field.field.node.projet.field_invite_accompagne_curate_p
|
||||||
- field.field.node.projet.field_partenaire_s
|
- field.field.node.projet.field_partenaire_s
|
||||||
@@ -15,6 +16,7 @@ dependencies:
|
|||||||
- node.type.projet
|
- node.type.projet
|
||||||
module:
|
module:
|
||||||
- datetime_range
|
- datetime_range
|
||||||
|
- file
|
||||||
- image
|
- image
|
||||||
- options
|
- options
|
||||||
- text
|
- text
|
||||||
@@ -50,6 +52,14 @@ content:
|
|||||||
third_party_settings: { }
|
third_party_settings: { }
|
||||||
weight: 102
|
weight: 102
|
||||||
region: content
|
region: content
|
||||||
|
field_fichiers:
|
||||||
|
type: file_default
|
||||||
|
label: above
|
||||||
|
settings:
|
||||||
|
use_description_as_link_text: true
|
||||||
|
third_party_settings: { }
|
||||||
|
weight: 110
|
||||||
|
region: content
|
||||||
field_images:
|
field_images:
|
||||||
type: image
|
type: image
|
||||||
label: above
|
label: above
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ dependencies:
|
|||||||
- field.field.node.projet.body
|
- field.field.node.projet.body
|
||||||
- field.field.node.projet.field_artiste_s_invite_s
|
- field.field.node.projet.field_artiste_s_invite_s
|
||||||
- field.field.node.projet.field_dates
|
- field.field.node.projet.field_dates
|
||||||
|
- field.field.node.projet.field_fichiers
|
||||||
- field.field.node.projet.field_images
|
- field.field.node.projet.field_images
|
||||||
- field.field.node.projet.field_invite_accompagne_curate_p
|
- field.field.node.projet.field_invite_accompagne_curate_p
|
||||||
- field.field.node.projet.field_partenaire_s
|
- field.field.node.projet.field_partenaire_s
|
||||||
@@ -56,6 +57,7 @@ content:
|
|||||||
region: content
|
region: content
|
||||||
hidden:
|
hidden:
|
||||||
field_dates: true
|
field_dates: true
|
||||||
|
field_fichiers: true
|
||||||
field_invite_accompagne_curate_p: true
|
field_invite_accompagne_curate_p: true
|
||||||
field_partenaire_s: true
|
field_partenaire_s: true
|
||||||
field_portage: true
|
field_portage: true
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
uuid: 01cf5142-1617-4b0a-afbe-46bed19f3a75
|
||||||
|
langcode: fr
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.node.field_fichiers
|
||||||
|
- node.type.projet
|
||||||
|
module:
|
||||||
|
- file
|
||||||
|
id: node.projet.field_fichiers
|
||||||
|
field_name: field_fichiers
|
||||||
|
entity_type: node
|
||||||
|
bundle: projet
|
||||||
|
label: Fichiers
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
translatable: false
|
||||||
|
default_value: { }
|
||||||
|
default_value_callback: ''
|
||||||
|
settings:
|
||||||
|
handler: 'default:file'
|
||||||
|
handler_settings: { }
|
||||||
|
file_directory: '[date:custom:Y]-[date:custom:m]'
|
||||||
|
file_extensions: 'pdf doc docx odt zip txt'
|
||||||
|
max_filesize: ''
|
||||||
|
description_field: true
|
||||||
|
field_type: file
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
uuid: 9a9ebb54-2e2f-46bd-9586-60ebedfc95d8
|
||||||
|
langcode: fr
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- file
|
||||||
|
- node
|
||||||
|
id: node.field_fichiers
|
||||||
|
field_name: field_fichiers
|
||||||
|
entity_type: node
|
||||||
|
type: file
|
||||||
|
settings:
|
||||||
|
target_type: file
|
||||||
|
display_field: true
|
||||||
|
display_default: false
|
||||||
|
uri_scheme: public
|
||||||
|
module: file
|
||||||
|
locked: false
|
||||||
|
cardinality: -1
|
||||||
|
translatable: true
|
||||||
|
indexes: { }
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
||||||
@@ -84,6 +84,17 @@ process:
|
|||||||
image_field_caption/format:
|
image_field_caption/format:
|
||||||
plugin: default_value
|
plugin: default_value
|
||||||
default_value: wysiwyg
|
default_value: wysiwyg
|
||||||
|
field_fichiers:
|
||||||
|
plugin: sub_process
|
||||||
|
source: pdfs
|
||||||
|
process:
|
||||||
|
target_id:
|
||||||
|
plugin: file_import
|
||||||
|
source: uri
|
||||||
|
destination: destination
|
||||||
|
reuse: true
|
||||||
|
skip_on_missing_source: true
|
||||||
|
description: description
|
||||||
field_dates/value:
|
field_dates/value:
|
||||||
plugin: leshed_date
|
plugin: leshed_date
|
||||||
source: date_start
|
source: date_start
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ langcode: fr
|
|||||||
uuid: e53d1b41-902a-4030-a476-ad2d5f3e5070
|
uuid: e53d1b41-902a-4030-a476-ad2d5f3e5070
|
||||||
name: 'Le Shed'
|
name: 'Le Shed'
|
||||||
mail: dev@figureslibres.io
|
mail: dev@figureslibres.io
|
||||||
slogan: ''
|
slogan: "Centre d'art en Normandie"
|
||||||
page:
|
page:
|
||||||
403: ''
|
403: ''
|
||||||
404: ''
|
404: ''
|
||||||
|
|||||||
+11
@@ -98,6 +98,17 @@ process:
|
|||||||
image_field_caption/format:
|
image_field_caption/format:
|
||||||
plugin: default_value
|
plugin: default_value
|
||||||
default_value: wysiwyg
|
default_value: wysiwyg
|
||||||
|
field_fichiers:
|
||||||
|
plugin: sub_process
|
||||||
|
source: pdfs
|
||||||
|
process:
|
||||||
|
target_id:
|
||||||
|
plugin: file_import
|
||||||
|
source: uri
|
||||||
|
destination: destination
|
||||||
|
reuse: true
|
||||||
|
skip_on_missing_source: true
|
||||||
|
description: description
|
||||||
field_dates/value:
|
field_dates/value:
|
||||||
plugin: leshed_date
|
plugin: leshed_date
|
||||||
source: date_start
|
source: date_start
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\migrate_leshed\Plugin\migrate\process;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||||
|
use Drupal\Core\File\FileExists;
|
||||||
|
use Drupal\Core\File\FileSystemInterface;
|
||||||
|
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||||
|
use Drupal\migrate\Attribute\MigrateProcess;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drupal\migrate\MigrateSkipRowException;
|
||||||
|
use Drupal\migrate\ProcessPluginBase;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies a local file into the file system as a managed File entity.
|
||||||
|
*
|
||||||
|
* The source value is a readable file path/URI. The file is copied into the
|
||||||
|
* destination directory and registered as a managed file; the file id is
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* Configuration:
|
||||||
|
* - destination: (required) destination directory URI, e.g. 'public://2026-08/'.
|
||||||
|
* - uid: owner uid of the created file (default 0).
|
||||||
|
* - reuse: reuse an existing managed file at the destination URI (default TRUE).
|
||||||
|
* - skip_on_missing_source: skip the value when the source is missing
|
||||||
|
* (default TRUE) instead of failing.
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* field_fichiers/target_id:
|
||||||
|
* plugin: file_import
|
||||||
|
* source: uri
|
||||||
|
* destination: destination
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
#[MigrateProcess(id: 'file_import')]
|
||||||
|
final class FileImport extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-run cache of already resolved destination URIs to file ids.
|
||||||
|
*
|
||||||
|
* @var array<string, int>
|
||||||
|
*/
|
||||||
|
private array $fidCache = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
array $configuration,
|
||||||
|
$plugin_id,
|
||||||
|
$plugin_definition,
|
||||||
|
private readonly FileSystemInterface $fileSystem,
|
||||||
|
private readonly EntityTypeManagerInterface $entityTypeManager,
|
||||||
|
) {
|
||||||
|
$configuration += [
|
||||||
|
'uid' => 0,
|
||||||
|
'reuse' => TRUE,
|
||||||
|
'skip_on_missing_source' => TRUE,
|
||||||
|
];
|
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
|
||||||
|
return new self(
|
||||||
|
$configuration,
|
||||||
|
$plugin_id,
|
||||||
|
$plugin_definition,
|
||||||
|
$container->get('file_system'),
|
||||||
|
$container->get('entity_type.manager'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property): ?int {
|
||||||
|
$source = is_string($value) ? trim($value) : '';
|
||||||
|
if ($source === '') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_file($source) && !is_file($this->fileSystem->realpath($source) ?: $source)) {
|
||||||
|
return $this->skipFile($migrate_executable, "Source file '$source' is missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$directory = $this->getPropertyValue($this->configuration['destination'], $row);
|
||||||
|
if (!is_string($directory) || $directory === '') {
|
||||||
|
return $this->skipFile($migrate_executable, "Missing 'destination' for file '$source'.");
|
||||||
|
}
|
||||||
|
$directory = rtrim($directory, '/') . '/';
|
||||||
|
$destination = $directory . $this->fileSystem->basename($source);
|
||||||
|
|
||||||
|
// Reuse an already imported file to keep the migration idempotent.
|
||||||
|
if ($this->configuration['reuse']) {
|
||||||
|
if (isset($this->fidCache[$destination])) {
|
||||||
|
return $this->fidCache[$destination];
|
||||||
|
}
|
||||||
|
$existing = $this->entityTypeManager->getStorage('file')
|
||||||
|
->loadByProperties(['uri' => $destination]);
|
||||||
|
if ($existing) {
|
||||||
|
return $this->fidCache[$destination] = (int) reset($existing)->id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
|
||||||
|
return $this->skipFile($migrate_executable, "Cannot prepare directory '$directory'.");
|
||||||
|
}
|
||||||
|
$this->fileSystem->copy($source, $destination, FileExists::Replace);
|
||||||
|
|
||||||
|
$file = $this->entityTypeManager->getStorage('file')->create([
|
||||||
|
'uri' => $destination,
|
||||||
|
'uid' => (int) $this->getPropertyValue($this->configuration['uid'], $row),
|
||||||
|
'status' => 1,
|
||||||
|
]);
|
||||||
|
$file->save();
|
||||||
|
}
|
||||||
|
catch (\Throwable $e) {
|
||||||
|
return $this->skipFile($migrate_executable, "Import of file '$source' failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fidCache[$destination] = (int) $file->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a message and skips the current file item.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||||
|
*/
|
||||||
|
private function skipFile(MigrateExecutableInterface $migrate_executable, string $message): never {
|
||||||
|
$migrate_executable->saveMessage($message);
|
||||||
|
throw new MigrateSkipRowException($message, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a configuration value, supporting destination (@) references.
|
||||||
|
*/
|
||||||
|
private function getPropertyValue(mixed $key, Row $row): mixed {
|
||||||
|
if (is_string($key) && str_starts_with($key, '@')) {
|
||||||
|
return $row->get(substr($key, 1));
|
||||||
|
}
|
||||||
|
if (is_string($key) && $row->hasSourceProperty($key)) {
|
||||||
|
return $row->getSourceProperty($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Drupal\migrate_leshed\Plugin\migrate\source;
|
namespace Drupal\migrate_leshed\Plugin\migrate\source;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||||
use Drupal\Core\File\FileSystemInterface;
|
use Drupal\Core\File\FileSystemInterface;
|
||||||
|
use Drupal\Core\Utility\Token;
|
||||||
use Drupal\migrate\Attribute\MigrateSource;
|
use Drupal\migrate\Attribute\MigrateSource;
|
||||||
use Drupal\migrate\Plugin\MigrationInterface;
|
use Drupal\migrate\Plugin\MigrationInterface;
|
||||||
use Drupal\migrate\Row;
|
use Drupal\migrate\Row;
|
||||||
@@ -46,6 +48,11 @@ final class ProjetNode extends Url {
|
|||||||
*/
|
*/
|
||||||
private const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg', 'webp'];
|
private const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg', 'webp'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File extensions to import into field_fichiers.
|
||||||
|
*/
|
||||||
|
private const FILE_EXTENSIONS = ['pdf'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default destination directory when no constant is configured.
|
* Default destination directory when no constant is configured.
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +70,11 @@ final class ProjetNode extends Url {
|
|||||||
*/
|
*/
|
||||||
private ?array $folderMap = NULL;
|
private ?array $folderMap = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memoized destination directory for field_fichiers (from field settings).
|
||||||
|
*/
|
||||||
|
private ?string $filesDestination = NULL;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
array $configuration,
|
array $configuration,
|
||||||
$plugin_id,
|
$plugin_id,
|
||||||
@@ -70,6 +82,8 @@ final class ProjetNode extends Url {
|
|||||||
MigrationInterface $migration,
|
MigrationInterface $migration,
|
||||||
DataParserPluginManager $parserPluginManager,
|
DataParserPluginManager $parserPluginManager,
|
||||||
private readonly FileSystemInterface $fileSystem,
|
private readonly FileSystemInterface $fileSystem,
|
||||||
|
private readonly EntityFieldManagerInterface $entityFieldManager,
|
||||||
|
private readonly Token $token,
|
||||||
) {
|
) {
|
||||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $parserPluginManager);
|
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $parserPluginManager);
|
||||||
}
|
}
|
||||||
@@ -85,6 +99,8 @@ final class ProjetNode extends Url {
|
|||||||
$migration,
|
$migration,
|
||||||
$container->get('plugin.manager.migrate_plus.data_parser'),
|
$container->get('plugin.manager.migrate_plus.data_parser'),
|
||||||
$container->get('file_system'),
|
$container->get('file_system'),
|
||||||
|
$container->get('entity_field.manager'),
|
||||||
|
$container->get('token'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +111,7 @@ final class ProjetNode extends Url {
|
|||||||
return parent::fields() + [
|
return parent::fields() + [
|
||||||
'body_html' => $this->t('Project body (HTML converted from Markdown)'),
|
'body_html' => $this->t('Project body (HTML converted from Markdown)'),
|
||||||
'images' => $this->t('Project images with captions'),
|
'images' => $this->t('Project images with captions'),
|
||||||
|
'pdfs' => $this->t('Project PDF files'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +140,14 @@ final class ProjetNode extends Url {
|
|||||||
// an undefined source key, even when no drive folder exists yet.
|
// an undefined source key, even when no drive folder exists yet.
|
||||||
$row->setSourceProperty('body_html', NULL);
|
$row->setSourceProperty('body_html', NULL);
|
||||||
$row->setSourceProperty('images', []);
|
$row->setSourceProperty('images', []);
|
||||||
|
$row->setSourceProperty('pdfs', []);
|
||||||
|
|
||||||
$uid = trim((string) $row->getSourceProperty('id'));
|
$uid = trim((string) $row->getSourceProperty('id'));
|
||||||
$folder = $uid !== '' ? $this->findProjectFolder($uid) : NULL;
|
$folder = $uid !== '' ? $this->findProjectFolder($uid) : NULL;
|
||||||
if ($folder !== NULL) {
|
if ($folder !== NULL) {
|
||||||
$row->setSourceProperty('body_html', $this->readBody($folder, $uid));
|
$row->setSourceProperty('body_html', $this->readBody($folder, $uid));
|
||||||
$row->setSourceProperty('images', $this->readImages($folder, $uid, (string) $row->getSourceProperty('title')));
|
$row->setSourceProperty('images', $this->readImages($folder, $uid, (string) $row->getSourceProperty('title')));
|
||||||
|
$row->setSourceProperty('pdfs', $this->readPdfs($folder, $uid));
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -188,8 +207,7 @@ final class ProjetNode extends Url {
|
|||||||
sort($entries, SORT_NATURAL | SORT_FLAG_CASE);
|
sort($entries, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
$markdown = NULL;
|
$markdown = NULL;
|
||||||
foreach ($entries as $entry) {
|
foreach ($entries as $entry) {
|
||||||
// Ignore ".back.md" backups of the pre-clean source text.
|
if (!is_file($dir . '/' . $entry) || !preg_match('/\.md$/i', $entry)) {
|
||||||
if (!is_file($dir . '/' . $entry) || !preg_match('/\.md$/i', $entry) || preg_match('/\.back\.md$/i', $entry)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (preg_match($preferred, $entry)) {
|
if (preg_match($preferred, $entry)) {
|
||||||
@@ -206,10 +224,44 @@ final class ProjetNode extends Url {
|
|||||||
if ($contents === FALSE || trim($contents) === '') {
|
if ($contents === FALSE || trim($contents) === '') {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
$contents = $this->cleanBodyMarkdown($contents);
|
||||||
|
if ($contents === '') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return (string) $this->getMarkdownConverter()->convert($contents);
|
return (string) $this->getMarkdownConverter()->convert($contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans the raw source Markdown before conversion.
|
||||||
|
*
|
||||||
|
* Removes redundant content already present elsewhere: raw HTML wrappers,
|
||||||
|
* invisible characters, and the leading title block (H1 title + H2/H3
|
||||||
|
* artists/subtitle, redundant with the sheet). Headings located *inside* the
|
||||||
|
* body (e.g. artist/curator bios) are preserved. Idempotent: running it on an
|
||||||
|
* already-cleaned text has no effect.
|
||||||
|
*/
|
||||||
|
private function cleanBodyMarkdown(string $md): string {
|
||||||
|
// 1. Strip HTML tags (<div>, <br>, </div>, ...).
|
||||||
|
$md = (string) preg_replace('/<[^>]+>/', '', $md);
|
||||||
|
// 2. Strip zero-width/BOM characters and turn NBSP into a normal space.
|
||||||
|
$md = (string) preg_replace('/[\x{200B}\x{200E}\x{200F}\x{FEFF}]/u', '', $md);
|
||||||
|
$md = (string) preg_replace('/\x{00A0}/u', ' ', $md);
|
||||||
|
// 3. Drop only the leading heading block: skip leading blank and heading
|
||||||
|
// lines until the first body line.
|
||||||
|
$lines = preg_split('/\R/u', $md) ?: [];
|
||||||
|
$i = 0;
|
||||||
|
$count = count($lines);
|
||||||
|
while ($i < $count && (trim($lines[$i]) === '' || preg_match('/^\s*#{1,6}\s/u', $lines[$i]))) {
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
$md = implode("\n", array_slice($lines, $i));
|
||||||
|
// 4. Collapse blank lines and trim.
|
||||||
|
$md = (string) preg_replace('/\n{3,}/', "\n\n", $md);
|
||||||
|
|
||||||
|
return trim($md);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the list of images (URI + alt) for the project.
|
* Builds the list of images (URI + alt) for the project.
|
||||||
*
|
*
|
||||||
@@ -272,6 +324,90 @@ final class ProjetNode extends Url {
|
|||||||
return $images;
|
return $images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the list of PDF files for the project.
|
||||||
|
*
|
||||||
|
* Collects PDFs both at the project folder root and inside the "{UID}_pdf"
|
||||||
|
* subfolder. Each item carries the destination directory (from the
|
||||||
|
* field_fichiers settings) and a human-readable description.
|
||||||
|
*
|
||||||
|
* @return array<int, array{uri: string, destination: string, description: string}>
|
||||||
|
* The files to import.
|
||||||
|
*/
|
||||||
|
private function readPdfs(string $folderUri, string $uid): array {
|
||||||
|
$dir = $this->fileSystem->realpath($folderUri);
|
||||||
|
if ($dir === FALSE) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directories to scan: the project folder root and its "{UID}_pdf" subfolder.
|
||||||
|
$sources = [$folderUri];
|
||||||
|
$pattern = '/^' . preg_quote($uid, '/') . '_pdf$/i';
|
||||||
|
foreach (scandir($dir) ?: [] as $entry) {
|
||||||
|
if (preg_match($pattern, $entry) && is_dir($dir . '/' . $entry)) {
|
||||||
|
$sources[] = $folderUri . '/' . $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$destination = $this->filesDestination();
|
||||||
|
$pdfs = [];
|
||||||
|
foreach ($sources as $sourceUri) {
|
||||||
|
$sourceDir = $this->fileSystem->realpath($sourceUri);
|
||||||
|
if ($sourceDir === FALSE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$files = scandir($sourceDir) ?: [];
|
||||||
|
sort($files, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (!is_file($sourceDir . '/' . $file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), self::FILE_EXTENSIONS, TRUE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$pdfs[] = [
|
||||||
|
'uri' => $sourceUri . '/' . $file,
|
||||||
|
'destination' => $destination,
|
||||||
|
'description' => $this->humanizeFilename($file),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pdfs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves (once) the field_fichiers destination directory from its settings.
|
||||||
|
*/
|
||||||
|
private function filesDestination(): string {
|
||||||
|
if ($this->filesDestination !== NULL) {
|
||||||
|
return $this->filesDestination;
|
||||||
|
}
|
||||||
|
$scheme = 'public';
|
||||||
|
$directory = '';
|
||||||
|
$definitions = $this->entityFieldManager->getFieldDefinitions('node', 'projet');
|
||||||
|
$field = $definitions['field_fichiers'] ?? NULL;
|
||||||
|
if ($field !== NULL) {
|
||||||
|
$scheme = $field->getFieldStorageDefinition()->getSetting('uri_scheme') ?: 'public';
|
||||||
|
$directory = (string) $this->token->replace((string) $field->getSetting('file_directory'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->filesDestination = $scheme . '://' . trim($directory, '/') . '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a file name into a readable label (drops UID prefix and extension).
|
||||||
|
*/
|
||||||
|
private function humanizeFilename(string $file): string {
|
||||||
|
$name = pathinfo($file, PATHINFO_FILENAME);
|
||||||
|
// Drop a leading "{UID}_" numeric prefix.
|
||||||
|
$name = preg_replace('/^\d+[_-]/', '', $name) ?? $name;
|
||||||
|
// Separators to spaces, collapse, trim.
|
||||||
|
$name = (string) preg_replace('/[_\-]+/u', ' ', $name);
|
||||||
|
|
||||||
|
return trim((string) preg_replace('/\s+/u', ' ', $name));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the raw contents of a caption (legende) file, or '' when missing.
|
* Returns the raw contents of a caption (legende) file, or '' when missing.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user