Skip to content
Snippets Groups Projects
Forked from project / cloudinary
10 commits ahead of the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
cloudinary.module 15.49 KiB
<?php

/**
 * @file
 * File for the Cloudinary module.
 */

/**
 * Sample file on cloudinary for image effect preview.
 */

use Cloudinary\Transformation\Transformation;
use Drupal\image\Entity\ImageStyle;

/**
 * Url prefix of cloudinary preview image.
 */
define('CLOUDINARY_PREVIEW_IMAGE_PREFIX', 'https://res.cloudinary.com/demo/image/upload/');

/**
 * Effects for effects param visible states.
 */
define('CLOUDINARY_VISIBLE_STATES_EFFECT', 'sepia,brightness,saturation,hue,oil_paint,vignette,pixelate,pixelate_faces,gradient_fade,blur,tilt_shift,sharpen,unsharp_mask,pixelate_region,red,blue,green,contrast,vibrance,fill_light,blur_region,blur_faces,make_transparent,trim,shadow,colorize');

/**
 * Crop mode for gravity visible states.
 */
define('CLOUDINARY_VISIBLE_STATES_CROP', 'fill,crop,thumb,pad,lfill,lpad,mpad');

/**
 * Implements hook_theme().
 */
function cloudinary_theme() {
  return [
    'cloudinary_crop_summary' => [
      'variables' => [
        'data' => NULL,
      ],
    ],
  ];
}

/**
 * Theme function for Cloudinary effect.
 */
function theme_cloudinary_crop_summary($variables) {
  $attributes = 'None';
  $data = cloudinary_preprocess_transformation($variables['data']);
  if (!empty($data)) {
    $attribute = [];
    foreach ($data as $key => $value) {
      if (is_array($value)) {
        $value = implode('.', array_filter($value));
      }
      $attribute[] = "$key: $value";
    }
    $attributes = implode(', ', $attribute);
  }

  return t('Image will have cloudinary effects applied with attributes : @attributes', ['@attributes' => $attributes]);
}

/**
 * Implements hook_theme_registry_alter().
 */
function cloudinary_theme_registry_alter(&$theme_registry) {
  foreach ($theme_registry['image_style_preview']['preprocess functions'] as &$function) {
    if ($function == 'template_preprocess_image_style_preview') {
      $function = 'cloudinary_preprocess_image_style_preview';
      break;
    }
  }
}

/**
 * Returns HTML for a cloudinary preview of an image style.
 *
 * @ingroup themeable
 */
function cloudinary_preprocess_image_style_preview(&$variables) {
  // Style information.
  $style = $variables['style'];
  $variables['style_id'] = $style->id();
  $variables['style_name'] = $style->label();

  // Cache bypass token.
  $variables['cache_bypass'] = \Drupal::time()->getRequestTime();
  // Get sample image path from Cloudinary demo sandbox.
  $original = CLOUDINARY_PREVIEW_IMAGE_PREFIX . 'sample.jpg';
  // Generate image style for provided sample image.
  $data = cloudinary_stream_wrapper_transformation($variables['style_id'], NULL);
  $derivative_width = !empty($data['width']) && ($data['width'] > 0) ? $data['width'] : 160;
  $derivative_height = !empty($data['height']) && ($data['height'] > 0) ? $data['height'] : 160;
  if (empty($data)) {
    $data = [];
  }

  // Unset named transformations since they are specific to Cloudinary accounts,
  // and will not work with a demo sandbox.
  if (isset($data['transformation'])) {
    unset($data['transformation']);
  }

  $transformation = Transformation::fromParams($data);
  $transformation_string = $transformation->toUrl();

  if ($transformation_string) {
    $transformation_string .= '/';
  }

  $preview = CLOUDINARY_PREVIEW_IMAGE_PREFIX . $transformation_string . 'sample.jpg';

  $variables['derivative']['rendered']['#uri'] = $preview;
  $variables['derivative']['url'] = \Drupal::service('file_url_generator')->generateAbsoluteString($preview);

  // Sample image info.
  $sample_width = 160;
  $sample_height = 160;

  // Set up original file information.
  $variables['original'] = [
    'url' => \Drupal::service('file_url_generator')->generateAbsoluteString($original),
    'width' => 864,
    'height' => 576,
  ];
  if ($variables['original']['width'] > $variables['original']['height']) {
    $variables['preview']['original']['width'] = min($variables['original']['width'], $sample_width);
    $variables['preview']['original']['height'] = round($variables['preview']['original']['width'] / $variables['original']['width'] * $variables['original']['height']);
  }
  else {
    $variables['preview']['original']['height'] = min($variables['original']['height'], $sample_height);
    $variables['preview']['original']['width'] = round($variables['preview']['original']['height'] / $variables['original']['height'] * $variables['original']['width']);
  }

  // Set up derivative file information.
  $variables['derivative'] = [
    'url' => \Drupal::service('file_url_generator')->generateAbsoluteString($preview),
    'width' => $derivative_width,
    'height' => $derivative_height,
  ];
  if ($variables['derivative']['width'] > $variables['derivative']['height']) {
    $variables['preview']['derivative']['width'] = min($variables['derivative']['width'], $sample_width);
    $variables['preview']['derivative']['height'] = round($variables['preview']['derivative']['width'] / $variables['derivative']['width'] * $variables['derivative']['height']);
  }
  else {
    $variables['preview']['derivative']['height'] = min($variables['derivative']['height'], $sample_height);
    $variables['preview']['derivative']['width'] = round($variables['preview']['derivative']['height'] / $variables['derivative']['height'] * $variables['derivative']['width']);
  }

  // Build the preview of the original image.
  $variables['original']['rendered'] = [
    '#theme' => 'image',
    '#uri' => $original,
    '#alt' => t('Sample original image'),
    '#title' => '',
    '#attributes' => [
      'width' => $variables['original']['width'],
      'height' => $variables['original']['height'],
      'style' => 'width: ' . $variables['preview']['original']['width'] . 'px; height: ' . $variables['preview']['original']['height'] . 'px;',
    ],
  ];

  // Build the preview of the image style derivative. Timestamps are added
  // to prevent caching of images on the client side.
  $variables['derivative']['rendered'] = [
    '#theme' => 'image',
    '#uri' => $variables['derivative']['url'] . '?cache_bypass=' . $variables['cache_bypass'],
    '#alt' => t('Sample modified image'),
    '#title' => '',
    '#attributes' => [
      'width' => 'auto',
      'height' => 'auto',
      'style' => 'width: ' . $variables['preview']['derivative']['width'] . 'px; height: ' . $variables['preview']['derivative']['height'] . 'px;',
    ],
  ];

}

/**
 * Implements hook_cloudinary_stream_wrapper_transformation().
 */
function cloudinary_cloudinary_stream_wrapper_transformation() {
  $path = \Drupal::service('extension.list.module')->getPath('cloudinary');

  return [
    'cloudinary_crop' => [
      'title' => t('Cloudinary crop'),
      'callback' => 'cloudinary_transformation_cloudinary_crop',
      'file' => $path . '/includes/cloudinary.transformation.cloudinary.inc',
    ],
    'cloudinary_named_transformation' => [
      'title' => t('Cloudinary named transformation'),
      'callback' => 'cloudinary_transformation_cloudinary_named_transformation',
      'file' => $path . '/includes/cloudinary.transformation.cloudinary.inc',
    ],
    'image_crop' => [
      'title' => t('Crop'),
      'callback' => 'cloudinary_transformation_image_crop',
      'file' => $path . '/includes/cloudinary.transformation.drupal.inc',
    ],
    'image_desaturate' => [
      'title' => t('Desaturate'),
      'callback' => 'cloudinary_transformation_image_desaturate',
    ],
    'image_resize' => [
      'title' => t('Resize'),
      'callback' => 'cloudinary_transformation_image_resize',
    ],
    'image_rotate' => [
      'title' => t('Rotate'),
      'callback' => 'cloudinary_transformation_image_rotate',
    ],
    'image_scale' => [
      'title' => t('Scale'),
      'callback' => 'cloudinary_transformation_image_scale',
    ],
    'image_scale_and_crop' => [
      'title' => t('Scale and crop'),
      'callback' => 'cloudinary_transformation_image_scale_and_crop',
    ],
    'focal_point_scale_and_crop' => [
      'title' => t('Focal Point Scale and Crop'),
      'callback' => 'cloudinary_transformation_image_scale_and_crop',
      'file' => $path . '/includes/cloudinary.transformation.drupal.inc',
    ],
  ];
}

/**
 * Convert image effect to special structure for cloudinary style.
 */
function cloudinary_transformation_image($data, $extra = NULL) {
  if ($extra) {
    if (is_array($extra)) {
      $data = array_merge($data, $extra);
    }
    else {
      $data['crop'] = $extra;
    }
  }

  $type = CLOUDINARY_STREAM_WRAPPER_TRANSFORMATION_NEW;
  if (isset($data['type'])) {
    $type = $data['type'];
    unset($data['type']);
  }

  return ['type' => $type, 'data' => $data];
}

/**
 * Ajax callback for cloudinary image preview on effect edit form.
 */
function cloudinary_crop_form_preview_callback($form, $form_state) {
  $data = cloudinary_from_image_effect_prepare($form_state['values']['data']);
  $form['data']['cloudinary']['preview']['thumbnail']['#markup'] = cloudinary_crop_form_preview($data);

  return $form['data']['cloudinary']['preview']['thumbnail'];
}

/**
 * Generate cloudinary image preview for effect edit form.
 */
function cloudinary_crop_form_preview($data) {
  $filename = 'sample.jpg';

  if (isset($data['gravity'])) {
    switch ($data['gravity']) {
      case 'face':
      case 'face:center':
      case 'rek_face':
        $filename = 'bike.jpg';
        break;

      case 'faces':
      case 'faces:center':
      case 'rek_faces':
        $filename = 'couple.jpg';
        break;
    }
  }

  if (isset($data['effect'])) {
    switch ($data['effect']) {
      case 'redeye':
      case 'rek_redeye':
        $filename = 'itaib_redeye_msjmif.jpg';
        break;

      case 'pixelate_faces':
      case 'blur_faces':
        $filename = 'couple.jpg';
        break;
    }
  }

  $original = CLOUDINARY_PREVIEW_IMAGE_PREFIX . $filename;
  $data = cloudinary_prepare_transformation($data);
  $transformation = Transformation::fromParams($data);
  $transformation_string = $transformation->toUrl();

  if ($transformation_string) {
    $transformation_string .= '/';
  }

  $preview = CLOUDINARY_PREVIEW_IMAGE_PREFIX . $transformation_string . $filename;

  return [
    '#theme' => 'image_style_preview',
    '#style' => ImageStyle::load('thumbnail'),
    '#original' => $original,
    '#preview' => $preview,
  ];
}

/**
 * Prepare cloudinary transformation with attributes.
 *
 * Return multiple transformations if angle set.
 */
function cloudinary_prepare_transformation($data, $only = TRUE) {
  $data = cloudinary_preprocess_transformation($data);
  // Rotation Angle.
  if (isset($data['angle']) || isset($data['angles'])) {
    if (isset($data['crop']) && $data['crop'] != 'crop') {
      unset($data['crop']);
    }
    if (isset($data['angle']) && count($data) > 1) {
      $angle = $data['angle'];
      unset($data['angle']);
      if (isset($data['angles'])) {
        $data['angle'] = array_values(array_filter($data['angles']));
        unset($data['angles']);
      }

      $new_data = [$data];
      $new_data[] = ['angle' => $angle];

      if ($only !== TRUE) {
        $new_data['multiple'] = TRUE;
      }

      return $new_data;
    }
  }

  return $data;
}

/**
 * Preprocess cloudinary transformation.
 *
 * Unset empty value fields.
 *
 * Merge field value to cloudinary format.
 */
function cloudinary_preprocess_transformation($data) {
  $data = array_filter($data);
  // Merge border with and border color.
  if (isset($data['border_width'])) {
    $border = $data['border_width'] . 'px_solid_rgb:';
    $border_color = '000';
    // Check and convert short #FFFFFF syntax to full #FFF syntax.
    if (isset($data['border_color'])) {
      $bc = $data['border_color'];
      $len = strlen($bc);
      if ($len == 7 || $len == 4) {
        $border_color = substr($bc, 1);
        if ($len == 7 && $bc[0] == $bc[1] && $bc[2] == $bc[3] && $bc[4] == $bc[5]) {
          $border_color = $bc[0] . $bc[2] . $bc[4];
        }
      }
    }
    // Append border color to border.
    $border .= $border_color;
    $data['border'] = $border;
    // Unset unused attributes.
    unset($data['border_width'], $data['border_color']);
  }
  // Meger effect with effects param.
  if (isset($data['effect']) && isset($data['effects_param'])) {
    $data['effect'] .= ':' . $data['effects_param'];
  }

  return $data;
}

/**
 * Prepare cloudinary image effect before save.
 */
function cloudinary_from_image_effect_prepare($data) {
  // Unset border_color if border_width is empty.
  if (empty($data['border_width'])) {
    $data['border_color'] = '';
  }
  // Unset angles if automatic_rotation unchecked.
  if (empty($data['automatic_rotation'])) {
    $data['angles'] = [];
  }
  // Unset effects_param if effect doesn't have param.
  $effect_param_effects = explode(',', CLOUDINARY_VISIBLE_STATES_EFFECT);

  if (!in_array($data['effect'], $effect_param_effects)) {
    $data['effects_param'] = '';
  }
  // Unset gravity if mode doesn't have.
  $gravity_corp_modes = explode(',', CLOUDINARY_VISIBLE_STATES_CROP);

  if (!in_array($data['crop'], $gravity_corp_modes)) {
    $data['gravity'] = '';
  }
  // Unset x,y if crop mode isn't crop.
  if ($data['crop'] != 'crop') {
    $data['x'] = $data['y'] = '';
  }

  return $data;
}

/**
 * Build options for corp modes.
 */
function _cloudinary_options_crop($key = NULL) {
  $data = [
    '' => t('None'),
    'scale' => t('Scale'),
    'limit' => t('Limit'),
    'fill' => t('Fill'),
    'fit' => t('Fit'),
    'crop' => t('Crop'),
    'thumb' => t('Thumb'),
    'pad' => t('Pad'),
    'lfill' => t('Limited Fill'),
    'lpad' => t('Limit & Pad'),
    'mfit' => t('Fit (Scale Up)'),
    'mpad' => t('Pad (No Scale)'),
    'imagga_crop' => t('Imagga crop'),
    'imagga_scale' => t('Imagga scale'),
  ];

  if (!is_null($key) && isset($data[$key])) {
    return $data[$key];
  }

  return $data;
}

/**
 * Build options for angles.
 */
function _cloudinary_options_angles($key = NULL) {
  $data = [
    'auto_left' => t('Auto left'),
    'auto_right' => t('Auto right'),
    'exif' => t('Use EXIF data'),
    'hflip' => t('Horizontal flip'),
    'ignore' => t('Ignore'),
    'vflip' => t('Vertical flip'),
  ];

  if (!is_null($key) && isset($data[$key])) {
    return $data[$key];
  }

  return $data;
}

/**
 * Build options for effect.
 */
function _cloudinary_options_effect($key = NULL) {
  $data = [
    '' => t('None'),
    'grayscale' => t('Grayscale'),
    'blackwhite' => t('Blackwhite'),
    'sepia' => t('Sepia'),
    'brightness' => t('Brightness'),
    'saturation' => t('Saturation'),
    'hue' => t('Hue'),
    'oil_paint' => t('Oil paint'),
    'vignette' => t('Vignette'),
    'pixelate' => t('Pixelate'),
    'pixelate_faces' => t('Pixelate faces'),
    'gradient_fade' => t('Gradient fade'),
    'blur' => t('Blur'),
    'improve' => t('Improve'),
    'tilt_shift' => t('Tilt shift'),
    'sharpen' => t('Sharpen'),
    'unsharp_mask' => t('Unsharp mask'),
    'pixelate_region' => t('Pixelate region'),
    'red' => t('Red'),
    'blue' => t('Blue'),
    'green' => t('Green'),
    'contrast' => t('Contrast'),
    'vibrance' => t('Vibrance'),
    'auto_color' => t('Auto color'),
    'auto_brightness' => t('Auto brightness'),
    'auto_contrast' => t('Auto contrast'),
    'fill_light' => t('Fill light'),
    'blur_region' => t('Blur region'),
    'blur_faces' => t('Blur faces'),
    'make_transparent' => t('Make transparent'),
    'trim' => t('Trim'),
    'mask' => t('Mask'),
    'shadow' => t('Shadow'),
    'negate' => t('Negate'),
    'screen' => t('Screen'),
    'multiply' => t('Multiply'),
    'colorize' => t('Colorize'),
    'redeye' => t('Remove red eyes'),
    'rek_redeye' => t('ReKognition: Remove red eyes'),
    'overlay' => t('Overlay'),
    'gamma' => t('Gamma'),
  ];

  if (!is_null($key) && isset($data[$key])) {
    return $data[$key];
  }

  return $data;
}