migration projets: body markdown, images HD, légendes, personnes prénom/nom
- ProjetNode: body Markdown->HTML (strip HTML brut), images + légendes lues
depuis public://drive_shared_leshed/Page{UID}
- ImageResizeImport: nouveau process plugin, redimensionne les images en web HD
(max 1920px) et crée le fichier managé (idempotent)
- field_images: caption via image_field_caption, alt depuis les légendes
- PersonneGenerate: nom dans field_nom (titre auto-généré via auto_entitylabel),
dédup sur field_nom + field_prenom
- title: skip_on_empty (ligne ignorée si "Nom du projet" vide)
- CER: relation curateur sur field_projets_curates (distinct de field_projet_s_lie_s)
- deps: + league/commonmark, image_field_caption, auto_entitylabel ; - migrate_file
This commit is contained in:
@@ -16,15 +16,19 @@
|
||||
],
|
||||
"require": {
|
||||
"composer/installers": "^2.0",
|
||||
"drupal/auto_entitylabel": "^3.4",
|
||||
"drupal/cer": "^5.0@beta",
|
||||
"drupal/config_ignore": "^3.4",
|
||||
"drupal/core-composer-scaffold": "^10.6",
|
||||
"drupal/core-project-message": "^10.6",
|
||||
"drupal/core-recommended": "^10.6",
|
||||
"drupal/image_delta_formatter": "^1.3",
|
||||
"drupal/image_field_caption": "^3.0",
|
||||
"drupal/migrate_google_sheets": "^2.3",
|
||||
"drupal/migrate_tools": "^6.1",
|
||||
"drupal/page_manager": "^4.0@RC",
|
||||
"drupal/shield": "^1.8",
|
||||
"league/commonmark": "^2.8",
|
||||
"wikimedia/composer-merge-plugin": "^2.1"
|
||||
},
|
||||
"conflict": {
|
||||
|
||||
Generated
+638
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0b0b3210b893eaaa833b27f1e6fae4c9",
|
||||
"content-hash": "5bd9a2694b1628f45febf28ca3f995d7",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
@@ -1868,6 +1868,87 @@
|
||||
"issues": "https://www.drupal.org/project/issues/audiofield"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/auto_entitylabel",
|
||||
"version": "3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.drupalcode.org/project/auto_entitylabel.git",
|
||||
"reference": "8.x-3.4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://ftp.drupal.org/files/projects/auto_entitylabel-8.x-3.4.zip",
|
||||
"reference": "8.x-3.4",
|
||||
"shasum": "9b3d7bc8e450ae008b3f48fed0dd9dace03ddbb5"
|
||||
},
|
||||
"require": {
|
||||
"drupal/core": "^10.1 || ^11"
|
||||
},
|
||||
"require-dev": {
|
||||
"drupal/book": "^1.0",
|
||||
"drupal/token": "^1.0"
|
||||
},
|
||||
"type": "drupal-module",
|
||||
"extra": {
|
||||
"drupal": {
|
||||
"version": "8.x-3.4",
|
||||
"datestamp": "1736308389",
|
||||
"security-coverage": {
|
||||
"status": "covered",
|
||||
"message": "Covered by Drupal's security advisory policy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packages.drupal.org/8/downloads",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "bforchhammer",
|
||||
"homepage": "https://www.drupal.org/user/216396"
|
||||
},
|
||||
{
|
||||
"name": "colan",
|
||||
"homepage": "https://www.drupal.org/user/58704"
|
||||
},
|
||||
{
|
||||
"name": "dqd",
|
||||
"homepage": "https://www.drupal.org/user/1001934"
|
||||
},
|
||||
{
|
||||
"name": "japerry",
|
||||
"homepage": "https://www.drupal.org/user/45640"
|
||||
},
|
||||
{
|
||||
"name": "mandclu",
|
||||
"homepage": "https://www.drupal.org/user/52136"
|
||||
},
|
||||
{
|
||||
"name": "pravin ajaaz",
|
||||
"homepage": "https://www.drupal.org/user/2910049"
|
||||
},
|
||||
{
|
||||
"name": "purushotam.rai",
|
||||
"homepage": "https://www.drupal.org/user/3193859"
|
||||
},
|
||||
{
|
||||
"name": "renatog",
|
||||
"homepage": "https://www.drupal.org/user/3326031"
|
||||
},
|
||||
{
|
||||
"name": "vladimiraus",
|
||||
"homepage": "https://www.drupal.org/user/673120"
|
||||
}
|
||||
],
|
||||
"description": "Allows hiding of entity label fields and automatic label creation.",
|
||||
"homepage": "https://www.drupal.org/project/auto_entitylabel",
|
||||
"support": {
|
||||
"source": "https://git.drupalcode.org/project/auto_entitylabel",
|
||||
"issues": "https://www.drupal.org/project/issues/auto_entitylabel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/autocomplete_deluxe",
|
||||
"version": "2.1.2",
|
||||
@@ -4243,6 +4324,131 @@
|
||||
"issues": "https://www.drupal.org/project/issues/honeypot"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/image_delta_formatter",
|
||||
"version": "1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.drupalcode.org/project/image_delta_formatter.git",
|
||||
"reference": "8.x-1.3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://ftp.drupal.org/files/projects/image_delta_formatter-8.x-1.3.zip",
|
||||
"reference": "8.x-1.3",
|
||||
"shasum": "6ec48166cb04eda57915dbe28f97fa32c1e77b75"
|
||||
},
|
||||
"require": {
|
||||
"drupal/core": "^10 || ^11"
|
||||
},
|
||||
"type": "drupal-module",
|
||||
"extra": {
|
||||
"drupal": {
|
||||
"version": "8.x-1.3",
|
||||
"datestamp": "1738516937",
|
||||
"security-coverage": {
|
||||
"status": "covered",
|
||||
"message": "Covered by Drupal's security advisory policy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packages.drupal.org/8/downloads",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "bojanz",
|
||||
"homepage": "https://www.drupal.org/user/86106"
|
||||
},
|
||||
{
|
||||
"name": "fgm",
|
||||
"homepage": "https://www.drupal.org/user/27985"
|
||||
},
|
||||
{
|
||||
"name": "jsacksick",
|
||||
"homepage": "https://www.drupal.org/user/972218"
|
||||
}
|
||||
],
|
||||
"description": "Provides a formatter that displays a specific delta of a multi-value image or media field.",
|
||||
"homepage": "https://www.drupal.org/project/image_delta_formatter",
|
||||
"support": {
|
||||
"source": "https://git.drupalcode.org/project/image_delta_formatter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/image_field_caption",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.drupalcode.org/project/image_field_caption.git",
|
||||
"reference": "3.0.1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://ftp.drupal.org/files/projects/image_field_caption-3.0.1.zip",
|
||||
"reference": "3.0.1",
|
||||
"shasum": "e84c3b9534d5824b6a67885187b1c24d4eccaf60"
|
||||
},
|
||||
"require": {
|
||||
"drupal/core": "^10 || ^11"
|
||||
},
|
||||
"type": "drupal-module",
|
||||
"extra": {
|
||||
"drupal": {
|
||||
"version": "3.0.1",
|
||||
"datestamp": "1739400507",
|
||||
"security-coverage": {
|
||||
"status": "covered",
|
||||
"message": "Covered by Drupal's security advisory policy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packages.drupal.org/8/downloads",
|
||||
"license": [
|
||||
"GPL-2.0+"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "awm",
|
||||
"homepage": "https://www.drupal.org/user/1059398"
|
||||
},
|
||||
{
|
||||
"name": "foxy-vikvik",
|
||||
"homepage": "https://www.drupal.org/user/3706169"
|
||||
},
|
||||
{
|
||||
"name": "hanoii",
|
||||
"homepage": "https://www.drupal.org/user/23157"
|
||||
},
|
||||
{
|
||||
"name": "ironsizide",
|
||||
"homepage": "https://www.drupal.org/user/787980"
|
||||
},
|
||||
{
|
||||
"name": "istryker",
|
||||
"homepage": "https://www.drupal.org/user/303676"
|
||||
},
|
||||
{
|
||||
"name": "kksandr",
|
||||
"homepage": "https://www.drupal.org/user/3710922"
|
||||
},
|
||||
{
|
||||
"name": "robloach",
|
||||
"homepage": "https://www.drupal.org/user/61114"
|
||||
},
|
||||
{
|
||||
"name": "tyler.frankenstein",
|
||||
"homepage": "https://www.drupal.org/user/150680"
|
||||
}
|
||||
],
|
||||
"description": "Add caption to image field.",
|
||||
"homepage": "https://www.drupal.org/project/image_field_caption",
|
||||
"support": {
|
||||
"source": "https://git.drupalcode.org/project/image_field_caption",
|
||||
"issues": "https://www.drupal.org/project/issues/image_field_caption"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/imagemagick",
|
||||
"version": "3.7.0",
|
||||
@@ -7864,6 +8070,195 @@
|
||||
},
|
||||
"time": "2026-06-26T00:11:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.8.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "1902f60f984235023acbe03db6ad614a37b3c3e7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7",
|
||||
"reference": "1902f60f984235023acbe03db6ad614a37b3c3e7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"league/config": "^1.1.1",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/event-dispatcher": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.1 || ^3.0",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"cebe/markdown": "^1.0",
|
||||
"commonmark/cmark": "0.31.1",
|
||||
"commonmark/commonmark.js": "0.31.1",
|
||||
"composer/package-versions-deprecated": "^1.8",
|
||||
"embed/embed": "^4.4",
|
||||
"erusev/parsedown": "^1.0",
|
||||
"ext-json": "*",
|
||||
"github/gfm": "0.29.0",
|
||||
"michelf/php-markdown": "^1.4 || ^2.0",
|
||||
"nyholm/psr7": "^1.5",
|
||||
"phpstan/phpstan": "^2.0.0",
|
||||
"phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0",
|
||||
"symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0",
|
||||
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0",
|
||||
"unleashedtech/php-coding-standard": "^3.1.1",
|
||||
"vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\CommonMark\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
|
||||
"homepage": "https://commonmark.thephpleague.com",
|
||||
"keywords": [
|
||||
"commonmark",
|
||||
"flavored",
|
||||
"gfm",
|
||||
"github",
|
||||
"github-flavored",
|
||||
"markdown",
|
||||
"md",
|
||||
"parser"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://commonmark.thephpleague.com/",
|
||||
"forum": "https://github.com/thephpleague/commonmark/discussions",
|
||||
"issues": "https://github.com/thephpleague/commonmark/issues",
|
||||
"rss": "https://github.com/thephpleague/commonmark/releases.atom",
|
||||
"source": "https://github.com/thephpleague/commonmark"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.colinodell.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/colinodell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/league/commonmark",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-12T15:29:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
"version": "v1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/config.git",
|
||||
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
|
||||
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dflydev/dot-access-data": "^3.0.1",
|
||||
"nette/schema": "^1.2",
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.8.2",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"unleashedtech/php-coding-standard": "^3.1",
|
||||
"vimeo/psalm": "^4.7.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Config\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"description": "Define configuration arrays with strict schemas and access values with dot notation",
|
||||
"homepage": "https://config.thephpleague.com",
|
||||
"keywords": [
|
||||
"array",
|
||||
"config",
|
||||
"configuration",
|
||||
"dot",
|
||||
"dot-access",
|
||||
"nested",
|
||||
"schema"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://config.thephpleague.com/",
|
||||
"issues": "https://github.com/thephpleague/config/issues",
|
||||
"rss": "https://github.com/thephpleague/config/releases.atom",
|
||||
"source": "https://github.com/thephpleague/config"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.colinodell.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/colinodell",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-11T20:36:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/container",
|
||||
"version": "4.2.5",
|
||||
@@ -8062,6 +8457,164 @@
|
||||
},
|
||||
"time": "2026-04-24T08:04:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
"version": "v1.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/schema.git",
|
||||
"reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002",
|
||||
"reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"nette/utils": "^4.0",
|
||||
"php": "8.1 - 8.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"nette/tester": "^2.6",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"phpstan/phpstan": "^2.1.39@stable",
|
||||
"tracy/tracy": "^2.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nette\\": "src"
|
||||
},
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause",
|
||||
"GPL-2.0-only",
|
||||
"GPL-3.0-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"description": "📐 Nette Schema: validating data structures against a given Schema.",
|
||||
"homepage": "https://nette.org",
|
||||
"keywords": [
|
||||
"config",
|
||||
"nette"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/schema/issues",
|
||||
"source": "https://github.com/nette/schema/tree/v1.3.5"
|
||||
},
|
||||
"time": "2026-02-23T03:47:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v4.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0",
|
||||
"reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "8.2 - 8.5"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/finder": "<3",
|
||||
"nette/schema": "<1.2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"jetbrains/phpstorm-attributes": "^1.2",
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"nette/tester": "^2.5",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"phpstan/phpstan": "^2.1@stable",
|
||||
"tracy/tracy": "^2.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "to use Image",
|
||||
"ext-iconv": "to use Strings::chr(), ord() and reverse()",
|
||||
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
|
||||
"ext-json": "to use Nette\\Utils\\Json",
|
||||
"ext-mbstring": "to use Strings::lower() etc...",
|
||||
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nette\\": "src"
|
||||
},
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause",
|
||||
"GPL-2.0-only",
|
||||
"GPL-3.0-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
|
||||
"homepage": "https://nette.org",
|
||||
"keywords": [
|
||||
"array",
|
||||
"core",
|
||||
"datetime",
|
||||
"images",
|
||||
"json",
|
||||
"nette",
|
||||
"paginator",
|
||||
"password",
|
||||
"slugify",
|
||||
"string",
|
||||
"unicode",
|
||||
"utf-8",
|
||||
"utility",
|
||||
"validation"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v4.1.5"
|
||||
},
|
||||
"time": "2026-07-17T23:02:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.8.0",
|
||||
@@ -10687,6 +11240,90 @@
|
||||
],
|
||||
"time": "2026-04-10T17:25:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.37.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.38.1",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
status: 1
|
||||
pattern: '[node:field_prenom] [node:field_nom]'
|
||||
escape: false
|
||||
preserve_titles: false
|
||||
save: true
|
||||
chunk: 50
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.personne
|
||||
new_content_behavior: 0
|
||||
@@ -6,7 +6,7 @@ id: projet_curateur
|
||||
label: 'Projet - Curateur'
|
||||
enabled: true
|
||||
first_field: field_invite_accompagne_curate_p
|
||||
second_field: field_projet_s_lie_s
|
||||
second_field: field_projets_curates
|
||||
add_direction: append
|
||||
bundles:
|
||||
node:
|
||||
|
||||
@@ -8,7 +8,7 @@ id: node.personne.title
|
||||
field_name: title
|
||||
entity_type: node
|
||||
bundle: personne
|
||||
label: Nom
|
||||
label: Title
|
||||
description: ''
|
||||
required: true
|
||||
translatable: true
|
||||
|
||||
@@ -162,4 +162,9 @@ content:
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
url_redirects:
|
||||
weight: 50
|
||||
region: content
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
hidden: { }
|
||||
|
||||
@@ -89,4 +89,9 @@ content:
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
url_redirects:
|
||||
weight: 50
|
||||
region: content
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
hidden: { }
|
||||
|
||||
@@ -4,8 +4,10 @@ status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.field.node.personne.body
|
||||
- field.field.node.personne.field_nom
|
||||
- field.field.node.personne.field_prenom
|
||||
- field.field.node.personne.field_projet_s_lie_s
|
||||
- field.field.node.personne.field_projets_curates
|
||||
- field.field.node.personne.field_type_de_personne
|
||||
- node.type.personne
|
||||
module:
|
||||
@@ -23,7 +25,7 @@ third_party_settings:
|
||||
label: Details
|
||||
region: content
|
||||
parent_name: ''
|
||||
weight: 4
|
||||
weight: 5
|
||||
format_type: details_sidebar
|
||||
format_settings:
|
||||
classes: ''
|
||||
@@ -41,7 +43,7 @@ mode: default
|
||||
content:
|
||||
body:
|
||||
type: text_textarea_with_summary
|
||||
weight: 2
|
||||
weight: 3
|
||||
region: content
|
||||
settings:
|
||||
rows: 9
|
||||
@@ -49,6 +51,14 @@ content:
|
||||
placeholder: ''
|
||||
show_summary: false
|
||||
third_party_settings: { }
|
||||
field_nom:
|
||||
type: string_textfield
|
||||
weight: 2
|
||||
region: content
|
||||
settings:
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
field_prenom:
|
||||
type: string_textfield
|
||||
weight: 1
|
||||
@@ -59,7 +69,17 @@ content:
|
||||
third_party_settings: { }
|
||||
field_projet_s_lie_s:
|
||||
type: entity_reference_autocomplete
|
||||
weight: 3
|
||||
weight: 4
|
||||
region: content
|
||||
settings:
|
||||
match_operator: CONTAINS
|
||||
match_limit: 10
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
field_projets_curates:
|
||||
type: entity_reference_autocomplete
|
||||
weight: 26
|
||||
region: content
|
||||
settings:
|
||||
match_operator: CONTAINS
|
||||
|
||||
@@ -257,6 +257,11 @@ content:
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
url_redirects:
|
||||
weight: 50
|
||||
region: content
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
hidden:
|
||||
created: true
|
||||
sticky: true
|
||||
|
||||
@@ -4,8 +4,10 @@ status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.field.node.personne.body
|
||||
- field.field.node.personne.field_nom
|
||||
- field.field.node.personne.field_prenom
|
||||
- field.field.node.personne.field_projet_s_lie_s
|
||||
- field.field.node.personne.field_projets_curates
|
||||
- field.field.node.personne.field_type_de_personne
|
||||
- node.type.personne
|
||||
module:
|
||||
@@ -23,6 +25,14 @@ content:
|
||||
third_party_settings: { }
|
||||
weight: 101
|
||||
region: content
|
||||
field_nom:
|
||||
type: string
|
||||
label: above
|
||||
settings:
|
||||
link_to_entity: false
|
||||
third_party_settings: { }
|
||||
weight: 109
|
||||
region: content
|
||||
field_prenom:
|
||||
type: string
|
||||
label: above
|
||||
@@ -39,6 +49,14 @@ content:
|
||||
third_party_settings: { }
|
||||
weight: 108
|
||||
region: content
|
||||
field_projets_curates:
|
||||
type: entity_reference_label
|
||||
label: above
|
||||
settings:
|
||||
link: true
|
||||
third_party_settings: { }
|
||||
weight: 110
|
||||
region: content
|
||||
field_type_de_personne:
|
||||
type: entity_reference_label
|
||||
label: above
|
||||
|
||||
@@ -5,34 +5,26 @@ dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.node.teaser
|
||||
- field.field.node.personne.body
|
||||
- field.field.node.personne.field_nom
|
||||
- field.field.node.personne.field_prenom
|
||||
- field.field.node.personne.field_projet_s_lie_s
|
||||
- field.field.node.personne.field_projets_curates
|
||||
- field.field.node.personne.field_type_de_personne
|
||||
- node.type.personne
|
||||
module:
|
||||
- text
|
||||
- user
|
||||
id: node.personne.teaser
|
||||
targetEntityType: node
|
||||
bundle: personne
|
||||
mode: teaser
|
||||
content:
|
||||
body:
|
||||
type: text_summary_or_trimmed
|
||||
label: hidden
|
||||
settings:
|
||||
trim_length: 600
|
||||
third_party_settings: { }
|
||||
weight: 101
|
||||
region: content
|
||||
links:
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
weight: 100
|
||||
region: content
|
||||
content: { }
|
||||
hidden:
|
||||
body: true
|
||||
field_nom: true
|
||||
field_prenom: true
|
||||
field_projet_s_lie_s: true
|
||||
field_projets_curates: true
|
||||
field_type_de_personne: true
|
||||
langcode: true
|
||||
links: true
|
||||
search_api_excerpt: true
|
||||
|
||||
@@ -13,8 +13,10 @@ dependencies:
|
||||
- field.field.node.projet.field_portage
|
||||
- field.field.node.projet.field_projet_s_lie_s
|
||||
- field.field.node.projet.field_type_de_projet
|
||||
- image.style.medium
|
||||
- node.type.projet
|
||||
module:
|
||||
- image_delta_formatter
|
||||
- text
|
||||
- user
|
||||
id: node.projet.teaser
|
||||
@@ -26,23 +28,40 @@ content:
|
||||
type: text_summary_or_trimmed
|
||||
label: hidden
|
||||
settings:
|
||||
trim_length: 600
|
||||
trim_length: 100
|
||||
third_party_settings: { }
|
||||
weight: 101
|
||||
weight: 2
|
||||
region: content
|
||||
links:
|
||||
settings: { }
|
||||
field_artiste_s_invite_s:
|
||||
type: entity_reference_entity_view
|
||||
label: hidden
|
||||
settings:
|
||||
view_mode: teaser
|
||||
link: false
|
||||
third_party_settings: { }
|
||||
weight: 100
|
||||
weight: 1
|
||||
region: content
|
||||
field_images:
|
||||
type: image_delta_formatter
|
||||
label: hidden
|
||||
settings:
|
||||
deltas:
|
||||
- 1
|
||||
deltas_reversed: false
|
||||
image_link: content
|
||||
image_style: medium
|
||||
image_loading:
|
||||
attribute: lazy
|
||||
third_party_settings: { }
|
||||
weight: 0
|
||||
region: content
|
||||
hidden:
|
||||
field_artiste_s_invite_s: true
|
||||
field_dates: true
|
||||
field_images: true
|
||||
field_invite_accompagne_curate_p: true
|
||||
field_partenaire_s: true
|
||||
field_portage: true
|
||||
field_projet_s_lie_s: true
|
||||
field_type_de_projet: true
|
||||
langcode: true
|
||||
links: true
|
||||
search_api_excerpt: true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
_core:
|
||||
default_config_hash: 4GIX5Esnc_umpXUBj4IIocRX7Mt5fPhm4AgXfE3E56E
|
||||
module:
|
||||
auto_entitylabel: -100
|
||||
actions_permissions: 0
|
||||
address: 0
|
||||
admin_toolbar: 0
|
||||
@@ -52,6 +53,8 @@ module:
|
||||
help: 0
|
||||
honeypot: 0
|
||||
image: 0
|
||||
image_delta_formatter: 0
|
||||
image_field_caption: 0
|
||||
inline_entity_form: 0
|
||||
language: 0
|
||||
layout_builder: 0
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
uuid: 7f1a16d3-57ea-4861-ad4f-1312ceadd1e7
|
||||
langcode: fr
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_nom
|
||||
- node.type.personne
|
||||
id: node.personne.field_nom
|
||||
field_name: field_nom
|
||||
entity_type: node
|
||||
bundle: personne
|
||||
label: Nom
|
||||
description: ''
|
||||
required: true
|
||||
translatable: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings: { }
|
||||
field_type: string
|
||||
@@ -11,7 +11,7 @@ entity_type: node
|
||||
bundle: personne
|
||||
label: Prenom
|
||||
description: ''
|
||||
required: false
|
||||
required: true
|
||||
translatable: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
uuid: 75b15206-7946-48cb-b7b9-17c27ad00c25
|
||||
langcode: fr
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_projets_curates
|
||||
- node.type.personne
|
||||
- node.type.projet
|
||||
id: node.personne.field_projets_curates
|
||||
field_name: field_projets_curates
|
||||
entity_type: node
|
||||
bundle: personne
|
||||
label: projets_curates
|
||||
description: ''
|
||||
required: false
|
||||
translatable: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
handler: 'default:node'
|
||||
handler_settings:
|
||||
target_bundles:
|
||||
projet: projet
|
||||
sort:
|
||||
field: _none
|
||||
direction: ASC
|
||||
auto_create: false
|
||||
auto_create_bundle: ''
|
||||
field_type: entity_reference
|
||||
@@ -35,4 +35,7 @@ settings:
|
||||
title: ''
|
||||
width: null
|
||||
height: null
|
||||
caption: ''
|
||||
caption_field: 1
|
||||
caption_field_required: 0
|
||||
field_type: image
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
uuid: 14aa5aef-0840-4e3d-a78a-027e2c61844f
|
||||
langcode: fr
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.field_nom
|
||||
field_name: field_nom
|
||||
entity_type: node
|
||||
type: string
|
||||
settings:
|
||||
max_length: 255
|
||||
case_sensitive: false
|
||||
is_ascii: false
|
||||
module: core
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@@ -0,0 +1,19 @@
|
||||
uuid: c5d97546-10d2-46e0-9324-1164bdd04cbb
|
||||
langcode: fr
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.field_projets_curates
|
||||
field_name: field_projets_curates
|
||||
entity_type: node
|
||||
type: entity_reference
|
||||
settings:
|
||||
target_type: node
|
||||
module: core
|
||||
locked: false
|
||||
cardinality: -1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@@ -15,6 +15,8 @@ source:
|
||||
plugin: projet_node
|
||||
data_fetcher_plugin: http
|
||||
data_parser_plugin: google_sheets
|
||||
constants:
|
||||
images_destination: 'public://projets/'
|
||||
urls: 'https://sheets.googleapis.com/v4/spreadsheets/1561Gv1C-IlCTmg64Z8HnznvOeLbvwhA_k1l6aq95zFA/values/Projets'
|
||||
fields:
|
||||
-
|
||||
@@ -56,7 +58,32 @@ process:
|
||||
type:
|
||||
plugin: default_value
|
||||
default_value: projet
|
||||
title: title
|
||||
title:
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
source: title
|
||||
message: 'Projet ignoré : « Nom du projet » vide.'
|
||||
body/value: body_html
|
||||
body/format:
|
||||
plugin: default_value
|
||||
default_value: wysiwyg
|
||||
field_images:
|
||||
plugin: sub_process
|
||||
source: images
|
||||
process:
|
||||
target_id:
|
||||
plugin: image_resize_import
|
||||
source: uri
|
||||
destination: destination
|
||||
max_width: 1920
|
||||
max_height: 1920
|
||||
reuse: true
|
||||
skip_on_missing_source: true
|
||||
alt: alt
|
||||
image_field_caption/value: caption
|
||||
image_field_caption/format:
|
||||
plugin: default_value
|
||||
default_value: wysiwyg
|
||||
field_dates/value:
|
||||
plugin: leshed_date
|
||||
source: date_start
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
langcode: fr
|
||||
_core:
|
||||
default_config_hash: R7HmKRbuDsDQNpqEWt7wWcf0ygaE3u-NQQr-w_LhHJk
|
||||
auto_redirect: true
|
||||
@@ -7,4 +8,3 @@ warning: false
|
||||
ignore_admin_path: false
|
||||
access_check: false
|
||||
route_normalizer_enabled: true
|
||||
langcode: fr
|
||||
|
||||
@@ -3,7 +3,8 @@ langcode: fr
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_prenom
|
||||
- field.storage.node.field_projet_s_lie_s
|
||||
- field.storage.node.field_projets_curates
|
||||
- field.storage.node.field_type_de_projet
|
||||
module:
|
||||
- node
|
||||
@@ -107,69 +108,6 @@ display:
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
field_prenom:
|
||||
id: field_prenom
|
||||
table: node__field_prenom
|
||||
field: field_prenom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: field
|
||||
label: ''
|
||||
exclude: true
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
@@ -183,7 +121,7 @@ display:
|
||||
label: Titre
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: true
|
||||
alter_text: false
|
||||
text: '{{ field_prenom }} {{ title }}'
|
||||
make_link: false
|
||||
path: ''
|
||||
@@ -298,25 +236,132 @@ display:
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
relationship: uid
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
field_projet_s_lie_s:
|
||||
id: field_projet_s_lie_s
|
||||
table: node__field_projet_s_lie_s
|
||||
field: field_projet_s_lie_s
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: field
|
||||
label: Auteur
|
||||
label: 'Projet(s) lié(s)'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: user_name
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
field_projets_curates:
|
||||
id: field_projets_curates
|
||||
table: node__field_projets_curates
|
||||
field: field_projets_curates
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: field
|
||||
label: projets_curates
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
status:
|
||||
id: status
|
||||
table: node_field_data
|
||||
@@ -658,9 +703,10 @@ display:
|
||||
columns:
|
||||
node_bulk_form: node_bulk_form
|
||||
type: title
|
||||
field_prenom: title
|
||||
title: title
|
||||
name: name
|
||||
field_type_de_projet: field_type_de_projet
|
||||
field_projets_curates: field_projet_s_lie_s
|
||||
field_projet_s_lie_s: field_projet_s_lie_s
|
||||
status: status
|
||||
changed: changed
|
||||
operations: operations
|
||||
@@ -678,13 +724,6 @@ display:
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
field_prenom:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
title:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
@@ -692,13 +731,21 @@ display:
|
||||
separator: '<br/>'
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
name:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
field_type_de_projet:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: priority-low
|
||||
responsive: ''
|
||||
field_projets_curates:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
field_projet_s_lie_s:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
status:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
@@ -737,6 +784,17 @@ display:
|
||||
plugin_id: standard
|
||||
required: true
|
||||
show_admin_links: false
|
||||
header:
|
||||
result:
|
||||
id: result
|
||||
table: views
|
||||
field: result
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: result
|
||||
empty: false
|
||||
content: 'Affichage de @start à @end sur @total'
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
@@ -749,7 +807,8 @@ display:
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:field.storage.node.field_prenom'
|
||||
- 'config:field.storage.node.field_projet_s_lie_s'
|
||||
- 'config:field.storage.node.field_projets_curates'
|
||||
- 'config:field.storage.node.field_type_de_projet'
|
||||
page_1:
|
||||
id: page_1
|
||||
@@ -783,5 +842,6 @@ display:
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:field.storage.node.field_prenom'
|
||||
- 'config:field.storage.node.field_projet_s_lie_s'
|
||||
- 'config:field.storage.node.field_projets_curates'
|
||||
- 'config:field.storage.node.field_type_de_projet'
|
||||
|
||||
+30
-1
@@ -10,6 +10,10 @@ source:
|
||||
plugin: projet_node
|
||||
data_fetcher_plugin: http
|
||||
data_parser_plugin: google_sheets
|
||||
constants:
|
||||
# Trailing slash: file_import keeps the original filename and copies the
|
||||
# image into this directory.
|
||||
images_destination: 'public://projets/'
|
||||
# The feed file for the spreadsheet. The Google Spreadsheet should be either “Public” or set to “Anyone with link can
|
||||
# view” in order for the feed to work.
|
||||
# Template: 'https://sheets.googleapis.com/v4/spreadsheets/<SHEET>/values/<TAB>?key=<KEY>'
|
||||
@@ -68,7 +72,32 @@ process:
|
||||
type:
|
||||
plugin: default_value
|
||||
default_value: projet
|
||||
title: title
|
||||
title:
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
source: title
|
||||
message: 'Projet ignoré : « Nom du projet » vide.'
|
||||
body/value: body_html
|
||||
body/format:
|
||||
plugin: default_value
|
||||
default_value: wysiwyg
|
||||
field_images:
|
||||
plugin: sub_process
|
||||
source: images
|
||||
process:
|
||||
target_id:
|
||||
plugin: image_resize_import
|
||||
source: uri
|
||||
destination: destination
|
||||
max_width: 1920
|
||||
max_height: 1920
|
||||
reuse: true
|
||||
skip_on_missing_source: true
|
||||
alt: alt
|
||||
image_field_caption/value: caption
|
||||
image_field_caption/format:
|
||||
plugin: default_value
|
||||
default_value: wysiwyg
|
||||
field_dates/value:
|
||||
plugin: leshed_date
|
||||
source: date_start
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate_leshed\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Image\ImageFactory;
|
||||
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 image, downscaling it to a web-friendly size, as a File.
|
||||
*
|
||||
* The source value is a readable file path/URI. The image is scaled down
|
||||
* (aspect ratio preserved, never upscaled) to fit within max_width x
|
||||
* max_height, saved into the destination directory and registered as a
|
||||
* managed file. Returns the file id.
|
||||
*
|
||||
* Configuration:
|
||||
* - destination: (required) destination directory URI, e.g. 'public://projets/1/'.
|
||||
* - max_width: maximum width in pixels (default 1920).
|
||||
* - max_height: maximum height in pixels (default 1920).
|
||||
* - 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_images/target_id:
|
||||
* plugin: image_resize_import
|
||||
* source: uri
|
||||
* destination: destination
|
||||
* max_width: 1920
|
||||
* max_height: 1920
|
||||
* @endcode
|
||||
*/
|
||||
#[MigrateProcess(id: 'image_resize_import')]
|
||||
final class ImageResizeImport 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 ImageFactory $imageFactory,
|
||||
private readonly EntityTypeManagerInterface $entityTypeManager,
|
||||
) {
|
||||
$configuration += [
|
||||
'max_width' => 1920,
|
||||
'max_height' => 1920,
|
||||
'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('image.factory'),
|
||||
$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;
|
||||
}
|
||||
|
||||
// Stream wrappers support is_file() directly; realpath() is only needed as
|
||||
// a fallback for schemes it cannot resolve.
|
||||
if (!is_file($source) && !is_file($this->fileSystem->realpath($source) ?: $source)) {
|
||||
// Skipping throws MigrateSkipRowException because, inside a sub_process,
|
||||
// only that exception drops the whole image item cleanly (SubProcess
|
||||
// catches it and `continue`s); MigrateSkipProcessException would instead
|
||||
// leave a broken delta with alt/caption but no target_id.
|
||||
return $this->skipImage($migrate_executable, "Source image '$source' is missing.");
|
||||
}
|
||||
|
||||
$directory = $this->getPropertyValue($this->configuration['destination'], $row);
|
||||
if (!is_string($directory) || $directory === '') {
|
||||
return $this->skipImage($migrate_executable, "Missing 'destination' for image '$source'.");
|
||||
}
|
||||
$directory = rtrim($directory, '/') . '/';
|
||||
$destination = $directory . $this->fileSystem->basename($source);
|
||||
|
||||
// Reuse an already imported file (idempotency), memoized for the run.
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Any infrastructure failure only drops the current image (logged), so a
|
||||
// single unreadable/oversized file never aborts the whole batch.
|
||||
try {
|
||||
if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
|
||||
return $this->skipImage($migrate_executable, "Cannot prepare directory '$directory'.");
|
||||
}
|
||||
|
||||
$image = $this->imageFactory->get($source);
|
||||
if (!$image->isValid()) {
|
||||
return $this->skipImage($migrate_executable, "Source '$source' is not a valid image.");
|
||||
}
|
||||
// Downscale only: scale() never upscales when upscale is FALSE.
|
||||
$image->scale((int) $this->configuration['max_width'], (int) $this->configuration['max_height']);
|
||||
if (!$image->save($destination)) {
|
||||
return $this->skipImage($migrate_executable, "Failed to write resized image to '$destination'.");
|
||||
}
|
||||
|
||||
$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->skipImage($migrate_executable, "Import of image '$source' failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $this->fidCache[$destination] = (int) $file->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message and skips the current image item.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||
*/
|
||||
private function skipImage(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,8 +18,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
* Input: a string such as "Julie Faitot et Jonathan Loppin" or
|
||||
* "Sonja Beaudouin, Alexandre Delabrière". Names are split on "et" and
|
||||
* commas. For each name, the first word is used as 'field_prenom' and the
|
||||
* rest as the node title. An existing node with the same title and prenom
|
||||
* is reused, otherwise it is created. Returns an array of node IDs.
|
||||
* rest as 'field_nom'. The node title is generated from those two fields by
|
||||
* the auto_entitylabel module on save. An existing node with the same nom and
|
||||
* prenom is reused, otherwise it is created. Returns an array of node IDs.
|
||||
*
|
||||
* When 'type_de_personne' is set in the configuration, a taxonomy term with
|
||||
* that name is found or created (without duplicates) in the
|
||||
@@ -103,7 +104,7 @@ final class PersonneGenerate extends ProcessPluginBase implements ContainerFacto
|
||||
|
||||
$query = $storage->getQuery()
|
||||
->condition('type', 'personne')
|
||||
->condition('title', $nom)
|
||||
->condition('field_nom', $nom)
|
||||
->accessCheck(FALSE)
|
||||
->range(0, 1);
|
||||
if ($prenom !== '') {
|
||||
@@ -121,9 +122,11 @@ final class PersonneGenerate extends ProcessPluginBase implements ContainerFacto
|
||||
return $nid;
|
||||
}
|
||||
|
||||
// The node title is generated from field_prenom + field_nom by
|
||||
// auto_entitylabel on save; it is intentionally not set here.
|
||||
$values = [
|
||||
'type' => 'personne',
|
||||
'title' => $nom,
|
||||
'field_nom' => $nom,
|
||||
'field_prenom' => $prenom === '' ? NULL : $prenom,
|
||||
'status' => 1,
|
||||
'uid' => $this->configuration['author_uid'] ?? 3,
|
||||
|
||||
@@ -4,11 +4,31 @@ declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate_leshed\Plugin\migrate\source;
|
||||
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\migrate\Attribute\MigrateSource;
|
||||
use Drupal\migrate_plus\Plugin\migrate\source\Url;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_plus\DataParserPluginManager;
|
||||
use Drupal\migrate_plus\Plugin\migrate\source\Url;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Source plugin for projects, enriched with body and images from the drive.
|
||||
*
|
||||
* On top of the Google Sheets columns, this source reads the local files
|
||||
* synced from the shared Google Drive (see the `sync_leshed_drive` Makefile
|
||||
* target) that live under public://drive_shared_leshed. Files are grouped in
|
||||
* per-project folders named "Page{UID}" (optionally suffixed, e.g.
|
||||
* "Page1_Complet"), where {UID} matches the sheet UID column:
|
||||
* - "{UID}_texte_*.md": the body, written in Markdown, converted to HTML.
|
||||
* - "{UID}_images|Images/": one folder of images, each "{name}.jpg" paired
|
||||
* with a "{name}_legende.md" caption used as the (required) alt text.
|
||||
*
|
||||
* These are exposed as two extra source properties consumed by the migration:
|
||||
* - body_html: the converted HTML string (or NULL).
|
||||
* - images: a list of ['uri' => string, 'alt' => string] items.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "projet_node"
|
||||
* )
|
||||
@@ -16,18 +36,65 @@ use Drupal\migrate\Row;
|
||||
#[MigrateSource(id: 'projet_node')]
|
||||
final class ProjetNode extends Url {
|
||||
|
||||
/**
|
||||
* Stream wrapper URI of the synced drive folder.
|
||||
*/
|
||||
private const DRIVE_URI = 'public://drive_shared_leshed';
|
||||
|
||||
/**
|
||||
* Image file extensions to import into field_images.
|
||||
*/
|
||||
private const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg', 'webp'];
|
||||
|
||||
/**
|
||||
* Default destination directory when no constant is configured.
|
||||
*/
|
||||
private const DEFAULT_IMAGE_DESTINATION = 'public://projets/';
|
||||
|
||||
/**
|
||||
* Lazily instantiated Markdown converter.
|
||||
*/
|
||||
private ?CommonMarkConverter $markdownConverter = NULL;
|
||||
|
||||
/**
|
||||
* Memoized map of UID => project folder URI, built once from the drive root.
|
||||
*
|
||||
* @var array<string, string>|null
|
||||
*/
|
||||
private ?array $folderMap = NULL;
|
||||
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
MigrationInterface $migration,
|
||||
DataParserPluginManager $parserPluginManager,
|
||||
private readonly FileSystemInterface $fileSystem,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $parserPluginManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create($container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL): self {
|
||||
return new self(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('plugin.manager.migrate_plus.data_parser'),
|
||||
$container->get('file_system'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(): array {
|
||||
// theese are source fields
|
||||
return [
|
||||
'id' => $this->t('Project ID'),
|
||||
'title' => $this->t('Project Title'),
|
||||
'date_start' => $this->t('Project date start'),
|
||||
'date_end' => $this->t('Project date end'),
|
||||
'projet_type' => $this->t('Type de projet'),
|
||||
'curator' => $this->t('Commissaria')
|
||||
return parent::fields() + [
|
||||
'body_html' => $this->t('Project body (HTML converted from Markdown)'),
|
||||
'images' => $this->t('Project images with captions'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -45,16 +112,211 @@ final class ProjetNode extends Url {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// $nid = $row->getSourceProperty('entity_id');
|
||||
// $vid = $row->getSourceProperty('revision_id');
|
||||
// $type = $row->getSourceProperty('type');
|
||||
// $language = $row->getSourceProperty('language');
|
||||
// $title = $row->getSourceProperty('title');
|
||||
// // drush_print('-- '.$nid."\t".$title."\t".$language);
|
||||
public function prepareRow(Row $row): bool {
|
||||
// Let the parent (and prepare-row event subscribers) decide first: no need
|
||||
// to hit the filesystem for a row that will be skipped.
|
||||
if (!parent::prepareRow($row)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Always define both properties so the process pipeline never references
|
||||
// an undefined source key, even when no drive folder exists yet.
|
||||
$row->setSourceProperty('body_html', NULL);
|
||||
$row->setSourceProperty('images', []);
|
||||
|
||||
$uid = trim((string) $row->getSourceProperty('id'));
|
||||
$folder = $uid !== '' ? $this->findProjectFolder($uid) : NULL;
|
||||
if ($folder !== NULL) {
|
||||
$row->setSourceProperty('body_html', $this->readBody($folder, $uid));
|
||||
$row->setSourceProperty('images', $this->readImages($folder, $uid, (string) $row->getSourceProperty('title')));
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public:// URI of the project folder, or NULL when missing.
|
||||
*/
|
||||
private function findProjectFolder(string $uid): ?string {
|
||||
return $this->getFolderMap()[$uid] ?? NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds (once) the UID => project folder URI map from the drive root.
|
||||
*
|
||||
* @return array<string, string>
|
||||
* Map keyed by the numeric part of each "Page{UID}[_suffix]" folder.
|
||||
*/
|
||||
private function getFolderMap(): array {
|
||||
if ($this->folderMap !== NULL) {
|
||||
return $this->folderMap;
|
||||
}
|
||||
|
||||
$this->folderMap = [];
|
||||
$base = $this->fileSystem->realpath(self::DRIVE_URI);
|
||||
if ($base === FALSE || !is_dir($base)) {
|
||||
return $this->folderMap;
|
||||
}
|
||||
// Match "Page{N}" exactly or with a suffix ("_Complet", ...). Sorting keeps
|
||||
// the resolution deterministic when e.g. "Page5" and "Page5_Complet"
|
||||
// coexist (the plain, longer-suffixed folder wins consistently).
|
||||
$entries = scandir($base) ?: [];
|
||||
sort($entries, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry === '.' || $entry === '..' || !is_dir($base . '/' . $entry)) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^Page(\d+)(_.*)?$/', $entry, $matches)) {
|
||||
$this->folderMap[$matches[1]] = self::DRIVE_URI . '/' . $entry;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->folderMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Markdown body file and converts it to HTML.
|
||||
*/
|
||||
private function readBody(string $folderUri, string $uid): ?string {
|
||||
$dir = $this->fileSystem->realpath($folderUri);
|
||||
if ($dir === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
// Prefer "{UID}_texte*.md", fall back to any Markdown file at the root.
|
||||
$preferred = '/^' . preg_quote($uid, '/') . '_texte.*\.md$/i';
|
||||
$entries = scandir($dir) ?: [];
|
||||
sort($entries, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
$markdown = NULL;
|
||||
foreach ($entries as $entry) {
|
||||
if (!is_file($dir . '/' . $entry) || !preg_match('/\.md$/i', $entry)) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match($preferred, $entry)) {
|
||||
$markdown = $dir . '/' . $entry;
|
||||
break;
|
||||
}
|
||||
$markdown ??= $dir . '/' . $entry;
|
||||
}
|
||||
if ($markdown === NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($markdown);
|
||||
if ($contents === FALSE || trim($contents) === '') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (string) $this->getMarkdownConverter()->convert($contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the list of images (URI + alt) for the project.
|
||||
*
|
||||
* @return array<int, array{uri: string, alt: string, caption: string, destination: string}>
|
||||
* The images to import.
|
||||
*/
|
||||
private function readImages(string $folderUri, string $uid, string $title): array {
|
||||
$dir = $this->fileSystem->realpath($folderUri);
|
||||
if ($dir === FALSE) {
|
||||
return [];
|
||||
}
|
||||
// The destination is passed through each item because a sub_process row
|
||||
// cannot read the parent migration's `constants`. It is namespaced per
|
||||
// project (UID) so identically-named files from different projects never
|
||||
// collide when file_import reuses existing files (reuse: true).
|
||||
$base = $this->configuration['constants']['images_destination']
|
||||
?? self::DEFAULT_IMAGE_DESTINATION;
|
||||
$destination = rtrim($base, '/') . '/' . $uid . '/';
|
||||
// Locate the images subfolder ("{UID}_images", case-insensitive).
|
||||
$imagesDirUri = NULL;
|
||||
$pattern = '/^' . preg_quote($uid, '/') . '_images$/i';
|
||||
foreach (scandir($dir) ?: [] as $entry) {
|
||||
if (preg_match($pattern, $entry) && is_dir($dir . '/' . $entry)) {
|
||||
$imagesDirUri = $folderUri . '/' . $entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($imagesDirUri === NULL) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$imagesDir = $this->fileSystem->realpath($imagesDirUri);
|
||||
if ($imagesDir === FALSE) {
|
||||
return [];
|
||||
}
|
||||
$images = [];
|
||||
$files = scandir($imagesDir) ?: [];
|
||||
sort($files, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
foreach ($files as $file) {
|
||||
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
if (!in_array($extension, self::IMAGE_EXTENSIONS, TRUE)) {
|
||||
continue;
|
||||
}
|
||||
$name = pathinfo($file, PATHINFO_FILENAME);
|
||||
$legende = $this->readCaptionFile($imagesDir . '/' . $name . '_legende.md');
|
||||
// Plain text (required alt), falling back to the project title. Capped to
|
||||
// the image field's alt column length (varchar 512).
|
||||
$alt = $legende !== '' ? $this->toPlainText($legende) : $title;
|
||||
$alt = mb_substr($alt, 0, 512);
|
||||
// Rich caption (image_field_caption): Markdown converted to HTML.
|
||||
$caption = $legende !== '' ? (string) $this->getMarkdownConverter()->convert($legende) : '';
|
||||
$images[] = [
|
||||
'uri' => $imagesDirUri . '/' . $file,
|
||||
'alt' => $alt,
|
||||
'caption' => $caption,
|
||||
'destination' => $destination,
|
||||
];
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw contents of a caption (legende) file, or '' when missing.
|
||||
*/
|
||||
private function readCaptionFile(string $captionPath): string {
|
||||
if (!is_file($captionPath)) {
|
||||
return '';
|
||||
}
|
||||
$contents = file_get_contents($captionPath);
|
||||
if ($contents === FALSE) {
|
||||
return '';
|
||||
}
|
||||
// Drive exports are occasionally Windows-1252 rather than UTF-8.
|
||||
if (!mb_check_encoding($contents, 'UTF-8')) {
|
||||
$contents = mb_convert_encoding($contents, 'UTF-8', 'Windows-1252');
|
||||
}
|
||||
|
||||
return trim($contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces Markdown/HTML text to a single trimmed plain-text line (for alt).
|
||||
*/
|
||||
private function toPlainText(string $text): string {
|
||||
$text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
// Drop Markdown emphasis/heading markers left after tag stripping.
|
||||
$text = preg_replace('/[*_#`>]+/u', '', $text) ?? $text;
|
||||
|
||||
return trim((string) preg_replace('/\s+/u', ' ', $text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shared Markdown converter.
|
||||
*
|
||||
* Raw HTML embedded in the source Markdown (alignment <div>, <br>, ...) is
|
||||
* stripped: only the Markdown itself is converted to HTML, keeping the body
|
||||
* clean and safe for the wysiwyg format.
|
||||
*/
|
||||
private function getMarkdownConverter(): CommonMarkConverter {
|
||||
if ($this->markdownConverter === NULL) {
|
||||
$this->markdownConverter = new CommonMarkConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => FALSE,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->markdownConverter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to display a node.
|
||||
*
|
||||
* Available variables:
|
||||
* - node: The node entity with limited access to object properties and methods.
|
||||
* Only method names starting with "get", "has", or "is" and a few common
|
||||
* methods such as "id", "label", and "bundle" are available. For example:
|
||||
* - node.getCreatedTime() will return the node creation timestamp.
|
||||
* - node.hasField('field_example') returns TRUE if the node bundle includes
|
||||
* field_example. (This does not indicate the presence of a value in this
|
||||
* field.)
|
||||
* - node.isPublished() will return whether the node is published or not.
|
||||
* Calling other methods, such as node.delete(), will result in an exception.
|
||||
* See \Drupal\node\Entity\Node for a full list of public properties and
|
||||
* methods for the node object.
|
||||
* - label: (optional) The title of the node.
|
||||
* - content: All node items. Use {{ content }} to print them all,
|
||||
* or print a subset such as {{ content.field_example }}. Use
|
||||
* {{ content|without('field_example') }} to temporarily suppress the printing
|
||||
* of a given child element.
|
||||
* - author_picture: The node author user entity, rendered using the "compact"
|
||||
* view mode.
|
||||
* - metadata: Metadata for this node.
|
||||
* - date: (optional) Themed creation date field.
|
||||
* - author_name: (optional) Themed author name field.
|
||||
* - url: Direct URL of the current node.
|
||||
* - display_submitted: Whether submission information should be displayed.
|
||||
* - attributes: HTML attributes for the containing element.
|
||||
* The attributes.class element may contain one or more of the following
|
||||
* classes:
|
||||
* - node: The current template type (also known as a "theming hook").
|
||||
* - node--type-[type]: The current node type. For example, if the node is an
|
||||
* "Article" it would result in "node--type-article". Note that the machine
|
||||
* name will often be in a short form of the human readable label.
|
||||
* - node--view-mode-[view_mode]: The View Mode of the node; for example, a
|
||||
* teaser would result in: "node--view-mode-teaser", and
|
||||
* full: "node--view-mode-full".
|
||||
* The following are controlled through the node publishing options.
|
||||
* - node--promoted: Appears on nodes promoted to the front page.
|
||||
* - node--sticky: Appears on nodes ordered above other non-sticky nodes in
|
||||
* teaser listings.
|
||||
* - node--unpublished: Appears on unpublished nodes visible only to site
|
||||
* admins.
|
||||
* - title_attributes: Same as attributes, except applied to the main title
|
||||
* tag that appears in the template.
|
||||
* - content_attributes: Same as attributes, except applied to the main
|
||||
* content tag that appears in the template.
|
||||
* - author_attributes: Same as attributes, except applied to the author of
|
||||
* the node tag that appears in the template.
|
||||
* - title_prefix: Additional output populated by modules, intended to be
|
||||
* displayed in front of the main title tag that appears in the template.
|
||||
* - title_suffix: Additional output populated by modules, intended to be
|
||||
* displayed after the main title tag that appears in the template.
|
||||
* - view_mode: View mode; for example, "teaser" or "full".
|
||||
* - teaser: Flag for the teaser state. Will be true if view_mode is 'teaser'.
|
||||
* - page: Flag for the full page state. Will be true if view_mode is 'full'.
|
||||
*
|
||||
* @see template_preprocess_node()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<article{{ attributes }}>
|
||||
|
||||
{{ title_prefix }}
|
||||
{% if label and not page %}
|
||||
<h2{{ title_attributes }}>
|
||||
<a href="{{ url }}" rel="bookmark">{{ label }}</a>
|
||||
</h2>
|
||||
{% endif %}
|
||||
{{ title_suffix }}
|
||||
|
||||
{% if display_submitted %}
|
||||
<footer>
|
||||
{{ author_picture }}
|
||||
<div{{ author_attributes }}>
|
||||
{% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}
|
||||
{{ metadata }}
|
||||
</div>
|
||||
</footer>
|
||||
{% endif %}
|
||||
|
||||
<div{{ content_attributes }}>
|
||||
{{ content }}
|
||||
</div>
|
||||
|
||||
</article>
|
||||
Reference in New Issue
Block a user