Intégrations
phil/block-editor-bundleTéléversements d'images
L'application conserve les images ; le bundle les lui transporte depuis l'éditeur. Écrivez un ImageUploader :
use Phil\BlockEditorBundle\Application\Exception\UploadRefused;
use Phil\BlockEditorBundle\Application\Upload\ImageUpload;
use Phil\BlockEditorBundle\Application\Upload\ImageUploader;
use Phil\BlockEditorBundle\Application\Upload\UploadedImage;
final readonly class MediaUploader implements ImageUploader
{
public function __construct(private MediaLibrary $library, private Urls $urls) {}
public function upload(ImageUpload $image): UploadedImage
{
$type = mime_content_type($image->path);
if (!\in_array($type, ['image/jpeg', 'image/png', 'image/webp', 'image/avif'], true)) {
throw new UploadRefused('Only JPEG, PNG, WebP and AVIF images are accepted.');
}
$media = $this->library->upload($image->path, $image->clientName);
return new UploadedImage($this->urls->of($media), $media->width, $media->height, ['media' => $media->id]);
}
}
phil_block_editor:
image_uploader: App\Media\MediaUploader
ImageUpload transporte le fichier temporaire et ce que le navigateur en a dit — nom, type, taille —, qui n'est jamais
une preuve : le téléverseur lit lui-même le fichier. Le message de UploadRefused est présenté au rédacteur.
UploadedImage devient le file du bloc : url, width et height si elles sont connues, et les champs
supplémentaires que l'application garde — un identifiant de sa médiathèque, un crédit. Sur phil/media-bundle, le
téléverseur appelle sa bibliothèque et retourne l'URL signée et l'identifiant du média.
Le point de terminaison accepte le champ image d'un POST multipart, refuse un fichier au-delà de max_upload_bytes,
et répond comme l'attend l'outil d'image d'Editor.js :
{"success": 1, "file": {"url": "/media/7/cat.webp", "width": 1200, "height": 800, "media": 7}}
Mentions et liens de page
Écrivez un ReferenceSearch par menu :
use Phil\BlockEditorBundle\Application\Reference\ReferenceSearch;
use Phil\BlockEditorBundle\Application\Reference\Suggestion;
final readonly class PeopleSearch implements ReferenceSearch
{
public function __construct(private People $people) {}
public function search(string $query, int $limit): iterable
{
foreach ($this->people->startingWith($query, $limit) as $person) {
yield new Suggestion('user:'.$person->id, $person->name, '/people/'.$person->slug, $person->handle);
}
}
}
phil_block_editor:
mention_search: App\Search\PeopleSearch
link_search: App\Search\PageSearch
La référence d'une suggestion est kind:identifier — un genre en minuscules, puis jusqu'à 128 lettres, chiffres, _,
., / ou - —, la forme que phil/ujo conserve dans data-mention et data-ref. Son libellé est ce que le menu
affiche et ce que le texte insère. La recherche reçoit ce qui a été tapé après le déclencheur, épuré et coupé à 100
caractères, possiblement vide, et le point de terminaison répond avec ses 8 premières suggestions :
{"items": [{"reference": "user:42", "label": "Ada Lovelace", "url": "/people/ada", "hint": "@ada"}]}
Les recherches s'exécutent sous la session du rédacteur : une recherche retourne seulement ce que cette personne peut voir.
Protéger les points de terminaison
Chaque point de terminaison demande d'abord au vérificateur d'autorisations de l'application l'attribut access,
IS_AUTHENTICATED par défaut, et refuse quiconque ne l'a pas, avant d'appeler le téléverseur ou la recherche ; sans
SecurityBundle, il refuse tout le monde. Un pare-feu ne le fait pas seul : un pare-feu paresseux, ou qui laisse passer
les visiteurs anonymes, ouvre les routes à quiconque envoie depuis un script les en-têtes ci-dessous. Avec
access: ~, l'application doit protéger le préfixe par une entrée access_control
(installation.md).
Les services ImageUploader et ReferenceSearch ne reçoivent pas l'utilisateur : quand les fichiers gardés, ou les
suggestions retournées, dépendent de qui demande, lisez-y l'utilisateur courant dans le service Security de Symfony :
public function search(string $query, int $limit): iterable
{
$member = $this->security->getUser();
if (null === $member) {
return [];
}
// only what $member may see
}
Chaque point de terminaison exige aussi, sous le contrôle de EditorRequest, pour qu'un autre site ne puisse pas
l'appeler au nom du rédacteur :
- l'en-tête propre à l'éditeur,
X-Phil-Block-Editor: 1, qu'un formulaire ou un lien d'un autre site ne peut pas ajouter sans que le navigateur demande d'abord ; - aucun
Sec-Fetch-Site: cross-sitevenu du navigateur ; - pour le téléversement d'image, un
POST, la vérification CSRF sans état de Symfony pour le jetonphil_block_editor. Les recherchesGETdes menus@et[[ne changent rien et ne portent aucun jeton. À chaque téléversement, l'éditeur tire un nouveau jeton aléatoire, le place dans un témoin nommé d'après la valeur que le champ lui donne (csrf-tokenpar défaut,csrf_protection.cookie_namede FrameworkBundle ; préfixé de__Host-etsecureen HTTPS, même site seulement) et l'envoie dans un en-tête du même nom.SameOriginCsrfTokenManageraccepte la requête quandSec-Fetch-Site,OriginouRefererdu navigateur nomme ce site, ou quand l'en-tête et le témoin portent le même jeton, et la refuse quand l'un ou l'autre dit le contraire. FrameworkBundle efface le témoin dans la réponse.
Une requête refusée reçoit 403, une route sans son service 404.
Savoir où les choses sont utilisées
DocumentReferences lit un document pour trouver ce vers quoi il pointe :
use Phil\BlockEditorBundle\Application\Reference\DocumentReferences;
DocumentReferences::mentions($document); // ['user:42', …]
DocumentReferences::links($document); // ['page:7', …]
DocumentReferences::files($document); // ['/media/7/cat.webp', …] — images, attachments, galleries
Avec phil/references-bundle, un collecteur de références propre à l'application transforme ces résultats en liens, de
sorte qu'une page liée ou une image affichée ne peut pas être supprimée tant qu'un document l'utilise. Les documents
sont lus dans la forme canonique qu'écrit phil/ujo : décodez-les ou normalisez-les d'abord.
Rendu pour les lecteurs
L'éditeur ne produit pas le rendu des documents pour les lecteurs. ujo_html de phil/ujo-bundle s'en charge, avec les
classes qu'écrivent les outils en ligne de l'éditeur. La feuille de style du site les met en forme et donne sa couleur à
chaque jeton de couleur :
.text-color[data-color='brand'] { color: var(--brand); }