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;
}
}
