Focus touche/pince dépince rectangle conserve ratio et traitement d'image comme facebook

Webform 6.3.x

Drupal 10.4.x

CSS:

span.ajax-throbber.icon {
  min-height: 0px;
  min-width: 0px;
  padding-left: 0px;
  position: relative;
}
.focus-box {
  position: absolute;
  border: 2px dashed red;
  width: 65%; /* sera redéfini via JS */
  height: auto;
  aspect-ratio: 390 / 280;
  resize: none;
  cursor: move;
  z-index: 11;
  box-shadow: rgba(0, 0, 0, 0.5) 0px 0px 0px 9999px;
}

.focus-box::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  bottom: 0;
  right: 0;
  background: red;
  pointer-events: none;
  z-index: 12;
  clip-path: polygon(100% 0, 0 100%, 100% 100%);
}

#photo-preview {
  position: relative;
  overflow: hidden;
}

.focus-overlay {
  position: absolute;
  pointer-events: none;
  z-index: 10;
  transform: translate(-50%, -50%);
  visibility: hidden;
}

.focus-marker {
  width: 40px;
  height: 40px;
  border: 2px solid black;
  border-radius: 50%;
  background: white;
  mix-blend-mode: difference;
  color: black;
  text-align: center;
  line-height: 40px;
  font-size: 24px;
  font-weight: bold;
  user-select: none;
  visibility: hidden;
}

#edit-focus-point--wrapper {
  visibility: hidden;
}

Javascript

(function (Drupal, drupalSettings) {
  // Attend que le DOM soit prêt
  document.addEventListener('DOMContentLoaded', function () {
    // Stocker les observers pour les réinitialiser
    var observer = null;
    var globalObserver = null;

    // Fonction pour activer/désactiver le throbber et le bouton "Next".
    function toggleThrobber(show) {
      var $throbber = jQuery('.ajax-throbber');
      var $buttonNext = jQuery('button.webform-button--next');

      if (show) {
        $buttonNext.each(function () {
          jQuery(this).prop('disabled', true);
        });
        $throbber.each(function () {
          jQuery(this).show();
        });
      } else {
        $buttonNext.each(function () {
          jQuery(this).prop('disabled', false);
        });
        $throbber.each(function () {
          jQuery(this).hide();
        });
      }
    }

    // Ajouter dynamiquement le throbber aux wrappers AJAX.
    function addThrobberToAjaxWrappers() {
      jQuery('div[id^="ajax-wrapper"]').each(function () {
        if (!jQuery('body').find('.ajax-throbber').length) {
          var $throbber = jQuery('<span class="ajax-throbber glyphicon-spin icon glyphicon glyphicon-refresh" aria-hidden="true"></span>');
          $throbber.hide();
          jQuery(this).append($throbber);
        }
      });
    }

    // Fonction pour vérifier si le bouton "Attachment_upload_button" est présent.
    function waitForUploadButton() {
      var intervalId = setInterval(function () {
        if (jQuery('button[name="Attachment_upload_button"]').length) {
          clearInterval(intervalId);
          addThrobberToAjaxWrappers();
        }
      }, 100);

      setTimeout(function () {
        clearInterval(intervalId);
      }, 300000); // 5 minutes max
    }

    // Observer les changements pour détecter les fichiers téléchargés.
    function observeFileUploads() {
      // Nettoyer l'ancien observer s'il existe
      if (observer) {
        observer.disconnect();
      }
      if (globalObserver) {
        globalObserver.disconnect();
      }

      observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
          if (mutation.type === 'childList') {
            var hasFiles = jQuery('div.form-item.form-type-checkbox').find('input').length > 0;
            // toggleThrobber(!hasFiles);
          }
        });
      });

      jQuery('div[id^="ajax-wrapper--"]').each(function () {
        observer.observe(this, { childList: true, subtree: true });
      });

      // Observer tout le document pour détecter les nouveaux ajax-wrapper--
      globalObserver = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
          mutation.addedNodes.forEach(function (node) {
            if (node.nodeType === 1 && node.id && node.id.startsWith('ajax-wrapper--')) {
              toggleThrobber(false);
              observer.observe(node, { childList: true, subtree: true });
            }
          });
        });
      });

      globalObserver.observe(document.body, { childList: true, subtree: true });
    }

    // Suivre AJAX pour démarrer et arrêter le throbber.
    jQuery(document).ajaxSend(function (event, xhr, settings) {
      toggleThrobber(true);
      let input_coords = document.querySelector('input[name="focus_box_coords"]');
      if (input_coords) input_coords.value = '';
    });

    jQuery(document).ajaxComplete(function (event, xhr, settings) {
      waitForUploadButton();
      addThrobberToAjaxWrappers();
      observeFileUploads(); // Réinitialiser le MutationObserver après chaque AJAX.
    });

    // Initialisation.
    waitForUploadButton();
    addThrobberToAjaxWrappers();
    observeFileUploads();
  });

  Drupal.behaviors.photoPreview = {
    attach: function (context, settings) {
      const previewContainer = document.getElementById('photo-preview');
      if (!previewContainer) {
        // Skip if this page/context doesn't have a preview target.
        return;
      }
      // Affiche le preview quand un fichier est présent
      once('photoPreview', '.file-link', context).forEach(function (fileLinkWrapper) {
        const link = fileLinkWrapper.querySelector('a');
        if (link && previewContainer) {
          const originalUrl = link.getAttribute('href');
          let candidateUrl = originalUrl;

          // Tenter de construire une version avec /orig/
          if (originalUrl.includes('_sid_')) {
            candidateUrl = originalUrl.replace(/_sid_(\/[^\/]+\.(?:jpg|jpeg|png|gif))/i, '_sid_/orig$1');
          }
          else {
            candidateUrl = originalUrl.replace(/\/(\d+)(\/[^\/]+\.(?:jpg|jpeg|png|gif))/i, '/$1/orig$2');
          }

          // Tester si l'image modifiée existe
          const testImage = new Image();
          testImage.onload = function () {
            // L'image "orig" existe, l'utiliser
            previewContainer.innerHTML = '';
            previewContainer.innerHTML =
              '<img src="' + candidateUrl + '" style="max-width: 585px; max-width: 390px; object-fit: contain;" />';
            if (typeof window.photoFocusOverlay === 'function') {
              window.photoFocusOverlay();
            }
          };
          testImage.onerror = function () {
            // Sinon, utiliser l'original
            previewContainer.innerHTML = '';
            previewContainer.innerHTML =
              '<img src="' + originalUrl + '" style="max-width: 585px; max-width: 390px; object-fit: contain;" />';
            if (typeof window.photoFocusOverlay === 'function') {
              window.photoFocusOverlay();
            }
          };
          testImage.src = candidateUrl;

        }
      });

      // Une seule fois, enregistrer une fonction globale qui se déclenche après CHAQUE AJAX
      if (!window.__photoPreviewAjaxPatched) {
        window.__photoPreviewAjaxPatched = true;

        // Ceci capture toutes les réponses AJAX Drupal
        jQuery(document).ajaxComplete(function (event, xhr, settings) {
          const previewContainer = document.getElementById('photo-preview');
          const fileLink = document.querySelector('.file-link a');
          if (previewContainer && !fileLink) {
            previewContainer.innerHTML = '';
            previewContainer.removeAttribute('style');
          }
        });
      }
    }
  };

  window.photoFocusOverlay = function () {
    const previewContainer = document.getElementById('photo-preview');
    const image = previewContainer?.querySelector('img');

    if (!image || previewContainer.querySelector('.focus-overlay')) return;

    function syncPreviewSize() {
      const rect = image.getBoundingClientRect();
      previewContainer.style.width = rect.width + 'px';
      previewContainer.style.height = rect.height + 'px';
    }

    function updateCoordsInput(x, y, w, h, containerW, containerH) {
      const naturalW = image.naturalWidth;
      const naturalH = image.naturalHeight;
      const scaleX = naturalW / containerW;
      const scaleY = naturalH / containerH;
      const x_abs = x * scaleX;
      const y_abs = y * scaleY;
      const w_abs = w * scaleX;
      const h_abs = h * scaleY;
      const json = JSON.stringify({ x: x_abs, y: y_abs, w: w_abs, h: h_abs });
      const input = document.querySelector('input[name="focus_box_coords"]');
      if (input) input.value = json;
    }

    function enforceAspectRatio(box, container) {
      const containerRect = container.getBoundingClientRect();
      const aspectRatio = 280 / 390;

      let maxWidth = containerRect.width;
      let maxHeight = containerRect.height;

      // Limit width to container width
      let width = Math.min(box.offsetWidth, maxWidth);
      let height = width * aspectRatio;

      // If height would overflow, scale both down proportionally
      if (height > maxHeight) {
        height = maxHeight;
        width = height / aspectRatio;
      }

      // Clamp position so box stays fully within container
      let left = parseFloat(box.style.left) || 0;
      let top = parseFloat(box.style.top) || 0;

      left = Math.min(left, containerRect.width - width);
      top = Math.min(top, containerRect.height - height);

      box.style.width = width + 'px';
      box.style.height = height + 'px';
      box.style.left = left + 'px';
      box.style.top = top + 'px';

      updateCoordsInput(left, top, width, height, containerRect.width, containerRect.height);
    }

    function initOverlay() {
      syncPreviewSize();
      previewContainer.style.position = 'relative';

      previewContainer.querySelectorAll('.focus-overlay, .focus-box').forEach(el => el.remove());

      const overlay = document.createElement('div');
      overlay.className = 'focus-overlay';
      overlay.innerHTML = '<div class="focus-marker">+</div>';
      previewContainer.appendChild(overlay);

      const focusBox = document.createElement('div');
      focusBox.className = 'focus-box';
      focusBox.innerHTML = '<div class="resize-handle"></div>';
      previewContainer.appendChild(focusBox);

      const imgRect = image.getBoundingClientRect();
      const containerWidth = imgRect.width;
      const containerHeight = imgRect.height;

      const input = document.querySelector('input[name="focus_box_coords"]');
      let boxWidth, boxHeight, left, top;
      if (input && input.value) {
        try {
          const prev = JSON.parse(input.value);
          const scaleX = containerWidth / image.naturalWidth;
          const scaleY = containerHeight / image.naturalHeight;
          boxWidth = prev.w * scaleX;
          boxHeight = prev.h * scaleY;
          left = prev.x * scaleX;
          top = prev.y * scaleY;
          if (top <= 0 && boxHeight < containerHeight) {
            top = Math.max((containerHeight - boxHeight) / 2, 1);
          }
        } catch (e) {
          boxWidth = containerWidth * 0.6;
          boxHeight = boxWidth * (280 / 390);
          left = (containerWidth - boxWidth) / 2;
          top = (containerHeight - boxHeight) / 2;
        }
      } else {
        boxWidth = containerWidth * 0.6;
        boxHeight = boxWidth * (280 / 390);
        left = (containerWidth - boxWidth) / 2;
        top = (containerHeight - boxHeight) / 2;
      }

      focusBox.style.cssText = `
        position: absolute;
        border: 2px dashed red;
        z-index: 11;
        overflow: hidden;
        touch-action: none;
        left: ${left}px;
        top: ${top}px;
        width: ${boxWidth}px;
        height: ${boxHeight}px;
      `;

      const resizeHandle = focusBox.querySelector('.resize-handle');
      resizeHandle.style.cssText = `
        position: absolute;
        right: 0;
        bottom: 0;
        width: 20px;
        height: 20px;
        z-index: 12;
        cursor: nwse-resize;
        touch-action: none;
      `;

      let isDragging = false;
      let dragOffsetX, dragOffsetY;

      focusBox.addEventListener('mousedown', (e) => {
        if (e.target === resizeHandle) return;
        e.preventDefault();
        const rect = focusBox.getBoundingClientRect();
        isDragging = 'move';
        dragOffsetX = e.clientX - rect.left;
        dragOffsetY = e.clientY - rect.top;
      });

      window.addEventListener('mousemove', (e) => {
        if (isDragging !== 'move') return;
        e.preventDefault();
        const containerRect = previewContainer.getBoundingClientRect();
        let newLeft = e.clientX - containerRect.left - dragOffsetX;
        let newTop = e.clientY - containerRect.top - dragOffsetY;
        newLeft = Math.max(0, Math.min(newLeft, containerRect.width - focusBox.offsetWidth));
        newTop = Math.max(0, Math.min(newTop, containerRect.height - focusBox.offsetHeight));
        focusBox.style.left = newLeft + 'px';
        focusBox.style.top = newTop + 'px';
        enforceAspectRatio(focusBox, previewContainer);
      });

      let resizeStartX;
      resizeHandle.addEventListener('mousedown', (e) => {
        e.preventDefault();
        e.stopPropagation();
        resizeStartX = e.clientX;
        isDragging = 'resize';
        focusBox.dataset.startWidth = focusBox.offsetWidth;
      });

      window.addEventListener('mousemove', (e) => {
        if (isDragging !== 'resize') return;
        e.preventDefault();
        const dx = e.clientX - resizeStartX;
        const newWidth = parseFloat(focusBox.dataset.startWidth) + dx;
        focusBox.style.width = newWidth + 'px';
        enforceAspectRatio(focusBox, previewContainer);
      });

      window.addEventListener('mouseup', () => {
        isDragging = false;
      });

      previewContainer.addEventListener('touchstart', (e) => {
        if (e.touches.length === 2) {
          const dx = e.touches[1].clientX - e.touches[0].clientX;
          const dy = e.touches[1].clientY - e.touches[0].clientY;
          previewContainer.dataset.pinchStartDistance = Math.hypot(dx, dy);
          previewContainer.dataset.startWidth = focusBox.offsetWidth;
        } else if (e.touches.length === 1) {
          const touch = e.touches[0];
          const rect = focusBox.getBoundingClientRect();
          const containerRect = previewContainer.getBoundingClientRect();
          focusBox.dataset.dragging = 'true';
          focusBox.dataset.offsetX = touch.clientX - rect.left;
          focusBox.dataset.offsetY = touch.clientY - rect.top;
          focusBox.dataset.containerLeft = containerRect.left;
          focusBox.dataset.containerTop = containerRect.top;
        }
      }, { passive: false });

      previewContainer.addEventListener('touchmove', (e) => {
        const containerRect = previewContainer.getBoundingClientRect();
        if (e.touches.length === 2) {
          e.preventDefault();
          const dx = e.touches[1].clientX - e.touches[0].clientX;
          const dy = e.touches[1].clientY - e.touches[0].clientY;
          const distance = Math.hypot(dx, dy);
          const scale = distance / parseFloat(previewContainer.dataset.pinchStartDistance);
          let newWidth = parseFloat(previewContainer.dataset.startWidth) * scale;
          focusBox.style.width = newWidth + 'px';
          enforceAspectRatio(focusBox, previewContainer);
        } else if (e.touches.length === 1 && focusBox.dataset.dragging === 'true') {
          e.preventDefault();
          const touch = e.touches[0];
          const offsetX = parseFloat(focusBox.dataset.offsetX);
          const offsetY = parseFloat(focusBox.dataset.offsetY);
          const containerLeft = parseFloat(focusBox.dataset.containerLeft);
          const containerTop = parseFloat(focusBox.dataset.containerTop);
          let newLeft = touch.clientX - containerLeft - offsetX;
          let newTop = touch.clientY - containerTop - offsetY;
          newLeft = Math.max(0, Math.min(newLeft, containerRect.width - focusBox.offsetWidth));
          newTop = Math.max(0, Math.min(newTop, containerRect.height - focusBox.offsetHeight));
          focusBox.style.left = newLeft + 'px';
          focusBox.style.top = newTop + 'px';
          enforceAspectRatio(focusBox, previewContainer);
        }
      }, { passive: false });

      previewContainer.addEventListener('touchend', () => {
        focusBox.dataset.dragging = 'false';
      });

      enforceAspectRatio(focusBox, previewContainer);
      window.addEventListener('resize', syncPreviewSize);
    }

    if (image.complete) {
      initOverlay();
    } else {
      image.addEventListener('load', initOverlay);
    }
  };



})(Drupal, drupalSettings);

 

Webform gestionnnaire mon_module/src/Plugin/WebformHandler/CompagnonGestionnaire.php

 

<?php

namespace Drupal\mon_module\Plugin\WebformHandler;

use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\webform\WebformSubmissionInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\file\Entity\File;
use Drupal\Core\File\FileSystemInterface;

/**
 * Applique le style d’image et calcule l’année pour "Compagnon du mois".
 *
 * @WebformHandler(
 *   id = "compagnon_gestionnaire",
 *   label = @Translation("Compagnon - Gestionnaire"),
 *   category = @Translation("Traitement personnalisé"),
 *   description = @Translation("Applique le style d'image et calcule l'année pour le compagnon du mois."),
 *   cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_SINGLE,
 *   results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED,
 * )
 */
class CompagnonGestionnaire extends WebformHandlerBase {

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {
    $data = $webform_submission->getData();

    // Appliquer le style d'image "compagnon" si le champ photo est rempli.
    if (!empty($data['photo'])) {
      $fid = $data['photo'];
      $file = File::load($fid);

      if ($file) {
        $do_image_style = TRUE;
        $uri = $file->getFileUri();
        $realpath = \Drupal::service('file_system')->realpath($uri);
        $image_info = @getimagesize(\Drupal::service('file_system')->realpath($uri));
        if ($image_info) {
          $width = $image_info[0];
          $height = $image_info[1];
          $webform_submission = $form_state->getFormObject()->getEntity();
          $fs = \Drupal::service('file_system');

          // Example - public://webform/compagnon_du_mois/_sid_/filename.jpg.
          $source_uri = $file->getFileUri();

          // Parse filename.
          $filename = basename($source_uri);

          $sid = $webform_submission->id();
          if (!is_numeric($sid)) {
            $sid = '_sid_';
          }
          // Destination path.
          $original_copy_dir = "public://webform/compagnon_du_mois/$sid/orig";
          $original_copy_uri = "{$original_copy_dir}/{$filename}";

          if ($width == 390 && $height == 280) {
            // Image was already styled, copy original back and re-style it.
            $do_image_style = FALSE;
            if (!file_exists($original_copy_uri) && is_numeric($sid)) {
              $unprocessed_original_copy_dir = "public://webform/compagnon_du_mois/_sid_/orig";
              $unprocessed_original_copy_uri = "{$unprocessed_original_copy_dir}/{$filename}";
              if (file_exists($unprocessed_original_copy_uri) && is_numeric($sid)) {
                if (!file_exists($original_copy_dir)) {
                  $fs->prepareDirectory($original_copy_dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
                }
                \Drupal::logger('mon_module')->notice('File exists at @path', ['@path' => $unprocessed_original_copy_uri]);
                $fs->copy($unprocessed_original_copy_uri, $original_copy_uri , FileSystemInterface::EXISTS_RENAME);
              }
            }
            if (file_exists($original_copy_uri)) {
              $fs->copy($original_copy_uri, $source_uri, FileSystemInterface::EXISTS_REPLACE);
              // Invalidate the file entity's cache tag.
              \Drupal::service('cache_tags.invalidator')->invalidateTags(["file:$fid"]);
              // Reload the image.
              $file = File::load($fid);
              $do_image_style = TRUE;
            }
            else {
              \Drupal::logger('mon_module')->notice('File does not exist at @path', ['@path' => $original_copy_uri]);
            }
          }
          else if (!file_exists($original_copy_uri)) {
            if (!file_exists($original_copy_dir)) {
              $fs->prepareDirectory($original_copy_dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
            }
            try {
              $copy_dir = "public://webform/compagnon_du_mois/_sid_/orig";
              $copy_uri = "{$copy_dir}/{$filename}";
              // Next line required for preview that uses js.
              $fs->copy($source_uri, $copy_uri, FileSystemInterface::EXISTS_REPLACE);
              // This copy required by everytyhing else.
              $fs->copy($source_uri, $original_copy_uri, FileSystemInterface::EXISTS_REPLACE);
              // Invalidate the file entity's cache tag.
              \Drupal::service('cache_tags.invalidator')->invalidateTags(["file:$fid"]);
              // Reload the image.
              $file = File::load($fid);
              $do_image_style = TRUE;
              \Drupal::logger('mon_module')->notice('Copied file to @dest', ['@dest' => $original_copy_uri]);
            }
            catch (\Exception $e) {
              \Drupal::logger('mon_module')->error('File copy failed: @msg', ['@msg' => $e->getMessage()]);
            }
          }

          if (is_null($sid)) {
            if (file_exists($original_copy_uri)) {
              $fs->copy($original_copy_uri, $source_uri, FileSystemInterface::EXISTS_REPLACE);
              \Drupal::logger('mon_module')->notice('File already exists at @path', ['@path' => $original_copy_uri]);
              // Invalidate the file entity's cache tag.
              \Drupal::service('cache_tags.invalidator')->invalidateTags(["file:$fid"]);
              // Reload the image.
              $file = File::load($fid);
              $do_image_style = TRUE;
            }
            else {
              \Drupal::logger('mon_module')->notice('File does not exist at @path', ['@path' => $original_copy_uri]);
            }
          }
          else if (is_numeric($sid)) {
            $original_copy_dir = "public://webform/compagnon_du_mois/$sid/orig";
            $original_copy_uri = "{$original_copy_dir}/{$filename}";
            if (!file_exists($original_copy_uri)) {
              $temp_copy_dir = "public://webform/compagnon_du_mois/_sid_/orig";
              $temp_copy_uri = "{$original_copy_dir}/{$filename}";
              if (file_exists($original_copy_uri)) {
                $fs->copy($temp_copy_uri, $original_copy_uri, FileSystemInterface::EXISTS_REPLACE);
              }
            }
            if (file_exists($original_copy_uri)) {
              $fs->copy($original_copy_uri, $source_uri, FileSystemInterface::EXISTS_REPLACE);
              \Drupal::logger('mon_module')->notice('Copied file to @dest', ['@dest' => $original_copy_uri]);
              // Invalidate the file entity's cache tag.
              \Drupal::service('cache_tags.invalidator')->invalidateTags(["file:$fid"]);
              // Reload the image.
              $file = File::load($fid);
              $do_image_style = TRUE;
            }
          }
        }

	// Appliquer le traitement de l'image à ratio fixe.
        if ($do_image_style) {
          // Rotation basée sur EXIF AVANT d'appliquer le style.
          if (function_exists('exif_read_data') && @exif_imagetype($realpath) === IMAGETYPE_JPEG) {
            $exif = @exif_read_data($realpath);
            if (!empty($exif['Orientation'])) {
              $orientation = (int) $exif['Orientation'];
              \Drupal::logger('mon_module')->notice("exif orientation @orientation", ['@orientation' => $orientation]);

              if (in_array($orientation, [3, 6, 8])) {
                $image = imagecreatefromjpeg($realpath);
                if ($image) {
                  switch ($orientation) {
                    case 3:
                      $image = imagerotate($image, 180, 0);
                      break;
                    case 6:
                      $image = imagerotate($image, -90, 0);
                      break;
                    case 8:
                      $image = imagerotate($image, 90, 0);
                      break;
                  }
                  imagejpeg($image, $realpath, 90);
                }
              }
            }
          }
          $coords_raw = $data['focus_box_coords'] ?? null;
          if ($coords_raw && $file) {
            $coords = json_decode($coords_raw, TRUE);
            if (isset($coords['x'], $coords['y'], $coords['w'], $coords['h'])) {
              $x = (int) round($coords['x']);
              $y = (int) round($coords['y']);
              $w = (int) round($coords['w']);
              $h = (int) round($coords['h']);

              if ($w > 0 && $h > 0) {
                $image = imagecreatefromjpeg($realpath); // adapt if png
                $cropped = imagecrop($image, [
                  'x' => $x,
                  'y' => $y,
                  'width' => $w,
                  'height' => $h,
                ]);
                if ($cropped) {
                  $resized = imagecreatetruecolor(390, 280);
                  imagecopyresampled($resized, $cropped, 0, 0, 0, 0, 390, 280, $w, $h          );
                  imagejpeg($resized, $realpath, 90);
                  imagedestroy($image);
                  imagedestroy($cropped);
                  imagedestroy($resized);
                  clearstatcache(TRUE, $realpath);
                  $file->setSize(filesize($realpath));
                  $file->save();
                }
              }

            }
          }
        }
      }
    }

    // Calcul du champ "year".
    $annee_option = $data['annee'] ?? 'this_year';
    $soumission_timestamp = $webform_submission->getCompletedTime() ?? \Drupal::time()->getCurrentTime();
    $year = (int) \Drupal::service('date.formatter')->format($soumission_timestamp, 'custom', 'Y');
    if ($annee_option === 'next_year') {
      $year += 1;
    }
    // Appliquer la valeur au champ 'year'
    $webform_submission->setElementData('year', $year);

    // Calcul du champ "month".
    $mois_option = $data['mois'] ?? 'Janvier';
    $month = $this->getNumericMonth($mois_option);
    // Appliquer la valeur au champ 'month'
    $webform_submission->setElementData('month', $month);

  }

  /**
   * Return the numeric month value after passing a string.
   *
   * parameter (string) $mois.
   * returns (string) $month.
   */
  public function getNumericMonth(string $mois) {
    switch ($mois) {
      case 'Janvier':
      case 'January':
        $month = 1;
        break;
      case 'Février':
      case 'February':
        $month = 2;
        break;
      case 'Mars':
      case 'March':
        $month = 3;
        break;
      case 'Avril':
      case 'April':
        $month = 4;
        break;
      case 'Mai':
      case 'May':
        $month = 5;
        break    ;
      case 'Juin':
      case 'June':
        $month = 6;
        break;
      case 'Juillet':
      case 'July':
        $month = 7;
        break;
      case 'Août':
      case 'August':
        $month = 8;
        break;
      case 'Septembre':
      case 'September':
        $month = 9;
        break;
      case 'Octobre':
      case 'October':
        $month = 10;
        break;
      case 'Novembre':
      case 'November':
        $month = 11;
        break;
      case 'Décembre':
      case 'December':
        $month = 12;
        break;
      default:
        $month = 1;
        break;
    }

    return $month;
  }

}

 

yaml

debut:
  '#type': wizard_page
  '#title': Début
  choix:
    '#type': fieldset
    '#title': 'Compagnon de la semaine'
    '#open': true
    '#required': true
    photo:
      '#type': image_file
      '#title': Photo
      '#required': true
      '#attributes':
        class:
          - custom-photo-upload
      '#uri_scheme': public
      '#min_resolution': 250x200
    processed_text_01:
      '#type': processed_text
      '#text': '<div id="photo-preview"><!-- Placeholder for photo preview -->&nbsp;</div>'
      '#format': rich_text_ckeditor5
    nom_du_parent:
      '#type': textfield
      '#title': 'Nom du parent'
      '#required': true
    nom_du_compagnon:
      '#type': textfield
      '#title': 'Nom du compagnon'
      '#required': true
    focus_box_coords:
      '#type': hidden
      '#default_value': ''
apercu:
  '#type': wizard_page
  '#title': Aperçu
  '#open': true
  processed_text:
    '#type': processed_text
    '#text': '<div class="compagnon-fond"><div class="intro"><p class="intro"><strong>Compagnon de la semaine</strong></p><p class="text-center">Présentez-nous votre adorable boule de poils!</p><div class="align-center wxt-media-w400" style="max-height:285px;max-width:390px;"><div class="field field--name-field-media-image field--type-image field--label-hidden"><img class="img-responsive" loading="lazy" src="[webform_submission:values:photo:value:clear]?[current-date:raw]" width="390" height="280" alt=""></div></div><p class="nom">Nom : <strong>[webform_submission:values:nom_du_compagnon:value:clear]</strong></p><p class="parent">Parent : <strong>[webform_submission:values:nom_du_parent:value:clear]</strong></p><p class="bouton"><a class="btn btn-default btn-white btn-inverse" href="/fr/form/compagnon-du-mois">Soumettre une photo</a></p></div></div>'
    '#format': rich_text_ckeditor5
  actions:
    '#type': webform_actions
    '#title': "Bouton(s) d'envoi"
    '#submit__attributes':
      class:
        - btn
        - btn-default
        - btn-inverse
    '#update__label': 'Mise à jour'
    '#update__attributes':
      class:
        - btn
        - btn-default
        - btn-inverse
    '#delete_hide': false
    '#delete__attributes':
      class:
        - btn
        - btn-default
        - btn-inverse
    '#delete__dialog': true