cloudinary-8.x-1.x-dev/modules/cloudinary_media_library_widget/src/Element/CloudinaryMediaLibrary.php

modules/cloudinary_media_library_widget/src/Element/CloudinaryMediaLibrary.php
<?php

namespace Drupal\cloudinary_media_library_widget\Element;

use Cloudinary\Asset\AssetType;
use Cloudinary\Configuration\Configuration;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Element\FormElement;

/**
 * Provides a form element to display a textfield integrated with cloudinary.
 *
 * Properties:
 * - #resource_type: Limits uploaded files to the specified type.
 *    By default, all resource types are allowed.
 * - #extended: Set TRUE to extend the widget value and store array of data.
 *    As results, a referenced file based on the value will be created.
 * - #preview: Whether to display preview image above the field.
 *    By default, the preview is not displayed to have maximum performance.
 * - #multiple: Whether to allow users to select multiple images from
 *    the Media Library asset grid.
 * - #cardinality: Max number of media assets that can be added during
 *    a single session.
 *
 * Usage Example:
 * @code
 * $form['image'] = [
 *   '#type' => 'cloudinary_media_library',
 *   '#resource_type' => 'image',
 *   '#title' => t('Image'),
 *   '#preview' => TRUE,
 *   '#description' => t('Choose an image from cloudinary media library'),
 * ];
 * @endcode
 *
 * @FormElement("cloudinary_media_library")
 */
class CloudinaryMediaLibrary extends FormElement {

  /**
   * Max number of media assets that can be added during a single session.
   */
  const MAX_FILES_LIMIT = 50;

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    return [
      '#input' => TRUE,
      '#theme_wrappers' => ['fieldset'],
      '#extended' => FALSE,
      '#extended_element_type' => 'hidden',
      '#resource_type' => 'auto',
      '#multiple' => FALSE,
      '#cardinality' => 1,
      '#preview' => FALSE,
      '#process' => [
        [static::class, 'processCloudinary'],
      ],
      '#element_validate' => [
        [static::class, 'validateCloudinary'],
      ],
      '#pre_render' => [
        [static::class, 'preRenderCloudinary'],
      ],
    ];
  }

  /**
   * Render API callback: Validates the cloudinary_media_library element.
   */
  public static function validateCloudinary(&$element, FormStateInterface $form_state, &$complete_form) {
    if ($element['#multiple']) {
      $values = $element['#value'];

      if ($element['#extended']) {
        $values = array_column($values, 'value');
      }
    }
    else {
      $values = [$element['#extended'] ? $element['#value']['value'] : $element['#value']];
    }

    /** @var \Drupal\cloudinary_sdk\Service\AssetHelperInterface $asset_generator */
    $asset_generator = \Drupal::service('cloudinary_sdk.asset_helper');

    foreach ($values as $value) {
      if ($value && !$asset_generator->isApplicable($value)) {
        $form_state->setError($element['value'], t('The value is not valid.'));
        return;
      }
    }

    // Set value as string for not extended widget version.
    if (!$element['#extended'] && !$element['#multiple']) {
      $form_state->setValueForElement($element, reset($values));
    }
  }

  /**
   * Validate cloudinary connection.
   */
  public static function validateConnection() {
    try {
      Configuration::instance()->validate();
    }
    catch (\Exception $e) {
      \Drupal::messenger()->addWarning(t('You have not provided a valid connection to your Cloudinary instance. Follow @link to set up a connection.', [
        '@link' => Link::createFromRoute(t('this link'), 'cloudinary_sdk.settings')->toString(),
      ]));
    }
  }

  /**
   * Render API callback: Manages preview of the inserted asset.
   */
  public static function preRenderCloudinary($element) {
    static::validateConnection();

    $settings = [
      'multiple' => $element['#multiple'],
      'max_files' => $element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
      ? self::MAX_FILES_LIMIT
      : $element['#cardinality'],
    ];

    $element['value']['#attached']['library'][] = 'cloudinary_media_library_widget/form';
    $element['value']['#attached']['drupalSettings']['cloudinary_media_library']['base'] = self::baseDrupalSettings();
    $element['value']['#attached']['drupalSettings']['cloudinary_media_library']['elements'][$element['#id']] = $settings;
    $element['value']['#attributes']['class'][] = 'form-cloudinary';
    $element['value']['#attributes']['data-resource-type'] = $element['#resource_type'];

    // Mark input with preview.
    if ($element['#preview']) {
      /** @var \Drupal\cloudinary_sdk\Service\AssetHelperInterface $generator */
      $generator = \Drupal::service('cloudinary_sdk.asset_helper');
      $element['value']['#attributes']['class'][] = 'has-preview';

      // Get small preview of each item.
      if ($element['#multiple']) {
        $preview = [];
        $items = $element['#value'] ?: [];
        foreach ($items as $delta => $item) {
          $value = $generator->generateStringValue($item);
          $preview[$delta] = $generator->generatePreviewImageUrl($value, 200);
        }

        $element['value']['#attributes']['data-preview'] = Json::encode($preview);
      }
      else {
        $value = $element['#extended'] ? $element['value']['#value'] : $element['#value'];

        if ($value && ($preview = $generator->generatePreviewImageUrl($value))) {
          $element['value']['#attributes']['data-preview-image'] = $preview;
        }
      }
    }

    return $element;
  }

  /**
   * Render API callback: Expands the cloudinary_media_library element type.
   *
   * Allows this field to return an array instead of a single value.
   */
  public static function processCloudinary(&$element, FormStateInterface $form_state, &$complete_form) {
    // Use #tree option to return value as an associative array.
    $element['#tree'] = TRUE;

    $element['value'] = [
      '#title' => $element['#title'],
      '#required' => $element['#required'],
    ];

    $default_value = $element['#value'];

    // Support multiple items.
    if ($element['#multiple']) {
      // Convert default value to string as json.
      if ($default_value && is_array($default_value)) {
        $default_value = Json::encode($default_value);
      }

      $element['value'] += [
        '#type' => 'hidden',
        '#default_value' => $default_value,
      ];
    }
    else {
      // Use a string value for textfield based on associate data array.
      if ($element['#extended']) {
        $default_value = $element['#value']['value'] ?? '';
      }

      $element['value'] += [
        '#type' => 'textfield',
        '#maxlength' => 1024,
        '#title_display' => 'invisible',
        '#placeholder' => 'cloudinary:<resource_type>:<delivery_type>:<public_id>:<transformation>',
        '#default_value' => $default_value,
      ];

      // Build extra fields (only available for single cardinality so far).
      switch ($element['#resource_type']) {
        case AssetType::IMAGE:
          static::buildImageExtraFields($element);
          break;

        case AssetType::VIDEO:
        case AssetType::RAW:
          static::buildRawExtraFields($element);
          break;
      }
    }

    return $element;
  }

  /**
   * Build extra fields for raw resource type.
   */
  public static function buildRawExtraFields(array &$element) {
    switch ($element['#extended_element_type']) {
      case 'hidden':
        $element['description'] = [
          '#type' => 'hidden',
          '#access' => $element['#extended'],
          '#attributes' => ['class' => ['form-asset-item--title']],
          '#value' => $element['#value']['description'] ?? '',
        ];
        break;

      case 'textfield':
        $states = [
          'visible' => [
            ':input[name="' . $element['#name'] . '[value]"]' => ['!value' => ''],
          ],
        ];

        $element['description'] = [
          '#type' => 'textfield',
          '#title' => t('Description'),
          '#default_value' => $element['#value']['description'] ?? '',
          '#description' => t('Enter a description about the inserted file.'),
          '#maxlength' => 1024,
          '#attributes' => [
            'class' => ['form-asset-item--title'],
          ],
          '#access' => $element['#extended'],
          '#states' => $states,
        ];
        break;
    }
  }

  /**
   * Build extra fields for image resource type.
   */
  public static function buildImageExtraFields(array &$element) {
    switch ($element['#extended_element_type']) {
      case 'hidden':
        static::buildAltAndTitleHiddenFields($element);
        break;

      case 'textfield':
        static::buildAltAndTitleTextFields($element);
        break;
    }
  }

  /**
   * Build hidden extra fields.
   */
  public static function buildAltAndTitleHiddenFields(array &$element) {
    $element['alt'] = [
      '#type' => 'hidden',
      '#access' => $element['#extended'],
      '#attributes' => ['class' => ['form-asset-item--alt']],
      '#value' => $element['#value']['alt'] ?? '',
    ];

    $element['title'] = [
      '#type' => 'hidden',
      '#attributes' => ['class' => ['form-asset-item--title']],
      '#access' => $element['#extended'],
      '#value' => $element['#value']['title'] ?? '',
    ];
  }

  /**
   * Build extra text fields.
   */
  public static function buildAltAndTitleTextFields(array &$element) {
    $states = [
      'visible' => [
        ':input[name="' . $element['#name'] . '[value]"]' => ['!value' => ''],
      ],
    ];

    $element['alt'] = [
      '#type' => 'textfield',
      '#title' => t('Alternative text'),
      '#default_value' => $element['#value']['alt'] ?? '',
      '#description' => t('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility.'),
      '#maxlength' => 512,
      '#attributes' => [
        'class' => ['form-asset-item--alt'],
      ],
      '#access' => $element['#alt_field'] && $element['#extended'],
      '#required' => $element['#alt_field_required'],
      '#states' => $states,
    ];
    $element['title'] = [
      '#type' => 'textfield',
      '#title' => t('Title'),
      '#default_value' => $element['#value']['title'] ?? '',
      '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
      '#maxlength' => 1024,
      '#attributes' => [
        'class' => ['form-asset-item--title'],
      ],
      '#access' => $element['#title_field'] && $element['#extended'],
      '#required' => $element['#title_field_required'],
      '#states' => $states,
    ];
  }

  /**
   * Build base drupalSettings required for the cloudinary media widget.
   *
   * @return array
   *   List of Drupal settings.
   */
  public static function baseDrupalSettings() {
    $sdk_config = \Drupal::config('cloudinary_sdk.settings');
    $widget_config = \Drupal::config('cloudinary_media_library_widget.settings');

    $settings = [
      'cloud_name' => $sdk_config->get('cloudinary_sdk_cloud_name'),
      'api_key' => $sdk_config->get('cloudinary_sdk_api_key'),
      'use_saml' => $widget_config->get('cloudinary_saml_auth'),
      'default_title_attribute' => $widget_config->get('cloudinary_attribute_default_title'),
      'default_description_attribute' => $widget_config->get('cloudinary_attribute_default_description'),
    ];

    $starting_folder = $widget_config->get('cloudinary_starting_folder');

    if ($starting_folder !== '/') {
      $settings['starting_folder'] = $starting_folder;
    }

    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
    // Handle default value when no input done.
    if (!$input) {
      if ($element['#multiple']) {
        return $element['#default_value'] ?? [];
      }

      // Generate a string value for textfield element if it does not exist.
      if ($element['#extended'] && !isset($element['#default_value']['value']) && isset($element['#default_value']['public_id'])) {
        /** @var \Drupal\cloudinary_sdk\Service\AssetHelperInterface $asset_generator */
        $asset_generator = \Drupal::service('cloudinary_sdk.asset_helper');
        $element['#default_value']['value'] = $asset_generator->generateStringValue($element['#default_value']);
      }

      return $element['#default_value'] ?? '';
    }

    // Convert multiple value into array.
    if ($element['#multiple']) {
      $values = Json::decode($input['value']);

      foreach ($values as $delta => $value) {
        $values[$delta] = static::prepareValue($value, $element['#extended']);
      }

      return $values;
    }

    return static::prepareValue($input, $element['#extended']);
  }

  /**
   * Prepare input value to be used for storage.
   *
   * @param array $value
   *   The input.
   * @param bool $extended
   *   Whether the element is extended.
   *
   * @return array|string|null
   *   The extended value or NULL.
   */
  protected static function prepareValue(array $value, bool $extended) {
    if ($extended) {
      /** @var \Drupal\cloudinary_sdk\Service\AssetHelperInterface $asset_generator */
      $asset_generator = \Drupal::service('cloudinary_sdk.asset_helper');

      /** @var \Drupal\cloudinary_media_library_widget\Service\CloudinaryFileHelperInterface $file_helper */
      $file_helper = \Drupal::service('cloudinary_media_library_widget.file_helper');

      try {
        $info = $asset_generator->parseValue($value['value']);

        // Don't create a file for video.
        if ($info['resource_type'] !== AssetType::VIDEO) {
          if ($file = $file_helper->createFile($value['value'])) {
            $info['target_id'] = $file->id();
          }
        }
      }
      catch (\Exception $e) {
        // Let a validate callback display a proper error.
        return ['value' => $value['value']];
      }

      return $info + $value;
    }

    return $value['value'] ?? NULL;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc