cloudinary-8.x-1.x-dev/modules/cloudinary_stream_wrapper/cloudinary_stream_wrapper.module

modules/cloudinary_stream_wrapper/cloudinary_stream_wrapper.module
<?php

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

use Cloudinary\Api\Admin\AdminApi;
use Cloudinary\Api\Upload\UploadApi;
use Cloudinary\Asset\AssetType;
use Drupal\image\Entity\ImageStyle;

/**
 * Flag for dealing with Cloudinary file.
 */
define('CLOUDINARY_STREAM_WRAPPER_FILE', 0100000 | 0777);

/**
 * Flag for dealing with Cloudinary directory.
 */
define('CLOUDINARY_STREAM_WRAPPER_FOLDER', 0040000 | 0777);

/**
 * The sample file name in root folder.
 */
define('CLOUDINARY_STREAM_WRAPPER_SAMPLE', 'sample.jpg');

/**
 * The tag prefix of Cloudinary used for list folder items by tag.
 */
define('CLOUDINARY_STREAM_WRAPPER_FOLDER_TAG_PREFIX', 'root/');

/**
 * Append type of image transformation.
 */
define('CLOUDINARY_STREAM_WRAPPER_TRANSFORMATION_APPEND', 0);

/**
 * New type of image transformation.
 */
define('CLOUDINARY_STREAM_WRAPPER_TRANSFORMATION_NEW', 1);

/**
 * Multiple type of image transformation.
 */
define('CLOUDINARY_STREAM_WRAPPER_TRANSFORMATION_MULTIPLE', 2);

/**
 * Implements hook_help().
 */
function cloudinary_stream_wrapper_help($path, $arg) {
  $output = '';

  switch ($path) {
    case 'admin/help#cloudinary_stream_wrapper':
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t("The Cloudinary module allows the local file system to be replaced with Cloudinary. Uploads are saved into the Drupal file table using Drupal 7's file stream wrapper system.") . '</p>';
      $output .= '<h3>' . t('How to use') . '</h3>';
      $output .= '<ul>';
      $output .= '<li>' . t("Image upload - Securely upload images or any other file, at any scale from any source. API for fast upload directly from your user's browsers or mobile apps.") . '</li>';
      $output .= '<li>' . t('Cloud storage - Store as many images as needed. Our image hosting service stores images privately and safely with automatic backup and historical revisions.') . '</li>';
      $output .= '<li>' . t('Powerful administration - Manage your media library interactively with our Digital Asset Management solution or via APIs. Gain insights using advanced analytics.') . '</li>';
      $output .= '<li>' . t('Image manipulation - Manipulate your images dynamically to fit any graphics design. Apply effects, resizing, cropping, face detection, watermarks and tons of image processing capabilities.') . '</li>';
      $output .= '<li>' . t("Fast delivery - Get your images delivered lightning-fast, responsive and highly optimized for any device in any location. Images are served via Akamai's worldwide CDN.") . '</li>';
      $output .= '</ul>';
      break;
  }

  return $output;
}

/**
 * Implements hook_form_FORM_ID_alter() for cloudinary_sdk_settings().
 *
 * Alters the setting form for Cloudinary settings.
 *
 * @see cloudinary_sdk_settings()
 */
function cloudinary_stream_wrapper_form_cloudinary_sdk_settings_alter(&$form, $form_state) {
  $config = cloudinary_sdk_config_load();

  if (!$config) {
    return;
  }

  // Get Cloudinary SDK configs.
  $configs = \Drupal::config('cloudinary_stream_wrapper.settings');

  $form['settings']['cloudinary_stream_wrapper_logging'] = [
    '#type' => 'checkbox',
    '#title' => t('Enable error logging.'),
    '#default_value' => (bool) $configs->get('cloudinary_stream_wrapper_logging'),
    '#description' => t('Enable if you want to logging any errors messages during file processing by cloudinary.'),
  ];

  $folders = [];

  try {
    $api = new AdminApi();
    $root_folders = $api->rootFolders();
    foreach ($root_folders['folders'] as $folder) {
      $folders[str_replace('.', '_', $folder['path'])] = t('Folder: /@folder', [
        '@folder' => "{$folder['path']} [cloudinary.{$folder['path']}]",
      ]);
    }
  } catch (\Exception $e) {
  }

  $form['folders'] = [
    '#type' => 'details',
    '#title' => t('Stream Wrapper Settings'),
    '#open' => FALSE,
    '#weight' => 10,
    '#access' => !empty($folders),
    '#description' => t('You can enable more stream wrappers for Cloudinary with root folders.'),
  ];

  $form['folders']['cloudinary_stream_wrapper_folders'] = [
    '#type' => 'checkboxes',
    '#title' => t('Cloudinary scheme with root folder'),
    '#options' => $folders,
    '#default_value' => $configs->get('cloudinary_stream_wrapper_folders'),
  ];
}

/**
 * Load files in folder on Cloudinary.
 */
function cloudinary_stream_wrapper_load_folder_files($tag, $options = []) {
  $resources = [];

  try {
    $api = new AdminApi();
    $data = (array) $api->assetsByTag($tag, $options);
    $resources = $data['resources'] ?? [];
  }
  catch (Exception $e) {
    $options = [
      '%folder' => $tag,
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Failed to load files of folder [%folder], [%message]', $options);
  }

  foreach ($resources as $key => $resource) {
    $path = $resource['public_id'];
    if (isset($resource['format'])) {
      $path .= '.' . $resource['format'];
    }

    $basename = pathinfo($path, PATHINFO_BASENAME);

    if ($basename) {
      $resources[$key] = $basename;
    }
    else {
      unset($resources[$key]);
    }
  }

  return $resources;
}

/**
 * Load file resource from storage.
 *
 * Default storage: Database, MongoDB, Redis;
 * It provides some hooks to intergate with other storage.
 */
function cloudinary_stream_wrapper_resource_prepare($public_id) {
  $data = ['public_id' => $public_id];
  $resource = \Drupal::moduleHandler()
    ->invokeAll('cloudinary_stream_wrapper_resource_prepare', [$data]);

  if ($data != $resource) {
    return $resource;
  }

  return FALSE;
}

/**
 * Delete files or files in directory on Cloudinary.
 */
function cloudinary_stream_wrapper_delete_resource($resource) {
  if (!$resource || empty($resource['public_id'])) {
    return FALSE;
  }

  if ($resource['mode'] == CLOUDINARY_STREAM_WRAPPER_FOLDER) {
    return cloudinary_stream_wrapper_delete_folder($resource);
  }
  elseif ($resource['mode'] == CLOUDINARY_STREAM_WRAPPER_FILE) {
    return cloudinary_stream_wrapper_delete_file($resource);
  }

  return FALSE;
}

/**
 * Build file resource structure for Cloudinary.
 */
function cloudinary_stream_wrapper_resource_file_structure($resource) {
  if (!is_array($resource)) {
    $resource = [];
  }

  $keys = [
    'public_id' => '',
    'format' => '',
    'version' => '',
    'resource_type' => '',
    'type' => '',
    'created_at' => '',
    'bytes' => '',
    'width' => '',
    'height' => '',
    'url' => '',
    'secure_url' => '',
    'tags' => '',
    'context' => '',
  ];

  $resource = array_intersect_key($resource, $keys);
  $resource['mode'] = CLOUDINARY_STREAM_WRAPPER_FILE;
  $resource['timestamp'] = strtotime($resource['created_at']);

  return $resource;
}

/**
 * Build folder resource structure for Cloudinary.
 */
function cloudinary_stream_wrapper_resource_folder_structure($path, $folders = [], $files = []) {
  return [
    'public_id' => $path,
    'mode' => CLOUDINARY_STREAM_WRAPPER_FOLDER,
    'bytes' => 0,
    'timestamp' => 0,
    'folders' => $folders,
    'files' => $files,
  ];
}

/**
 * Create new file on Cloudinary.
 */
function cloudinary_stream_wrapper_create_file($base64_data, $options) {
  try {
    $options['api_key'] = \Drupal::config('cloudinary_sdk.settings')
      ->get('cloudinary_sdk_api_key');
    $options['api_secret'] = \Drupal::config('cloudinary_sdk.settings')
      ->get('cloudinary_sdk_api_secret');
    $options['cloud_name'] = \Drupal::config('cloudinary_sdk.settings')
      ->get('cloudinary_sdk_cloud_name');
    // Allow change of the options in the very last moment before upload.
    \Drupal::moduleHandler()
      ->alter('cloudinary_stream_wrapper_options', $options);
    $data = (array) (new UploadApi())->upload($base64_data, $options);
    $resource = cloudinary_stream_wrapper_resource_file_structure($data);
    \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_resource_create', [$resource]);

    return TRUE;
  }
  catch (\Exception $e) {
    $options = [
      '%file' => $options['public_id'],
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Upload file [%file] into Cloudinary failed, [%message].', $options);
  }

  return FALSE;
}

/**
 * Delete file on Cloudinary.
 */
function cloudinary_stream_wrapper_delete_file($resource) {
  try {
    (new UploadApi())->destroy($resource['public_id']);
    \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_resource_delete', [$resource]);

    return TRUE;
  }
  catch (\Exception $e) {
    $options = [
      '%file' => $resource['public_id'],
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Failed to delete file [%file], [%message].', $options);
  }

  return FALSE;
}

/**
 * Load file on Cloudinary.
 */
function cloudinary_stream_wrapper_load_file($public_id, $options = []) {
  $resource = [];

  // Try to load file resource on Cloudinary.
  try {
    $api = new AdminApi();
    $data = (array) $api->asset($public_id, $options);
    $resource = cloudinary_stream_wrapper_resource_file_structure($data);
    \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_resource_loaded', [$resource]);
  }
  catch (\Exception $e) {
    $options = [
      '%file' => $public_id,
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Failed to load file [%file], [%message].', $options);
  }

  return $resource;
}

/**
 * Rename file on Cloudinary.
 */
function cloudinary_stream_wrapper_rename_file($src_resource, $dst_public_id) {
  $src_folder = dirname($src_resource['public_id']);
  $dst_folder = dirname($dst_public_id);

  try {
    $upload_api = new UploadApi();
    $data = $upload_api->rename($src_resource['public_id'], $dst_public_id);
    // Replace new tag if folder name different.
    if ($src_folder != $dst_folder) {
      $tag = CLOUDINARY_STREAM_WRAPPER_FOLDER_TAG_PREFIX . $dst_folder;
      $upload_api->replaceTag($tag, [$data['public_id']]);
    }
    // Load new file resource.
    $dst_resource = cloudinary_stream_wrapper_load_file($data['public_id'], [AssetType::KEY => $data['resource_type']]);
    \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_resource_rename', [
        $src_resource,
        $dst_resource,
      ]);

    return TRUE;
  }
  catch (\Exception $e) {
    $options = [
      '%file' => $src_resource['public_id'],
      '%newfile' => $dst_public_id,
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Rename file [%file] to new file [%newfile] failed, [%message].', $options);
  }

  return FALSE;
}

/**
 * Create new folder on Cloudinary.
 */
function cloudinary_stream_wrapper_create_folder($path) {
  try {
    $resource = cloudinary_stream_wrapper_resource_folder_structure($path);
    \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_resource_create', [$resource]);

    return TRUE;
  }
  catch (\Exception $e) {
    $options = [
      '%folder' => $path,
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Failed to create folder [%folder], [%message].', $options);
  }

  return FALSE;
}

/**
 * Delete files in folder on Cloudinary.
 */
function cloudinary_stream_wrapper_delete_folder($resource) {
  if ($resource['mode'] != CLOUDINARY_STREAM_WRAPPER_FOLDER) {
    return FALSE;
  }

  try {
    $api = new AdminApi();
    $api->deleteAssetsByPrefix($resource['public_id'], [AssetType::KEY => AssetType::IMAGE]);
    $api->deleteAssetsByPrefix($resource['public_id'], [AssetType::KEY => AssetType::RAW]);
    \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_resource_delete', [$resource]);

    return TRUE;
  }
  catch (\Exception $e) {
    $options = [
      '%folder' => $resource['public_id'],
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Delete folder [%folder] failed, [%message].', $options);
  }

  return FALSE;
}

/**
 * Load sub-folders on Cloudinary to check parent folder exist.
 */
function cloudinary_stream_wrapper_load_folder($path) {
  $data = cloudinary_stream_wrapper_get_folders($path);

  if ($data !== FALSE && is_array($data)) {
    $folders = [];

    foreach ($data as $folder) {
      $folders[] = $folder['name'];
    }
    // Load files in current path on Cloudinary.
    $tag = CLOUDINARY_STREAM_WRAPPER_FOLDER_TAG_PREFIX . $path;
    $image_files = cloudinary_stream_wrapper_load_folder_files($tag, [AssetType::KEY => AssetType::IMAGE]);
    $raw_files = cloudinary_stream_wrapper_load_folder_files($tag, [AssetType::KEY => AssetType::RAW]);
    $files = array_merge($image_files, $raw_files);

    $resource = cloudinary_stream_wrapper_resource_folder_structure($path, $folders, $files);
    \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_resource_loaded', [$resource]);

    return $resource;
  }

  return FALSE;
}

/**
 * Check the file is an image or not.
 */
function cloudinary_stream_wrapper_is_image($uri) {
  return (bool) preg_match('/\.(jpe?g|png|gif|bmp|webp)$/i', $uri);
}

/**
 * Get all root folders on Cloudinary.
 *
 * False if path not exist.
 */
function cloudinary_stream_wrapper_get_folders($path = '', $options = []) {
  $folders = FALSE;

  try {
    $api = new AdminApi();
    $data = (array) $api->subfolders($path, $options);

    if (isset($data['folders'])) {
      $folders = $data['folders'];
    }
  }
  catch (\Exception $e) {
    $options = [
      '%folder' => $path,
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Failed to get sub-folders under folder [%folder], [%message].', $options);
  }

  return $folders;
}

/**
 * Convert drupal image style to Cloudinary transformation.
 */
function cloudinary_stream_wrapper_transformation($style_name, $resource) {
  $transformations = &drupal_static(__FUNCTION__, []);

  if (!isset($transformations[$style_name])) {
    $effects = cloudinary_stream_wrapper_transformation_info();

    if (empty($effects)) {
      return FALSE;
    }

    $style_definition = ImageStyle::load($style_name);

    $style_effects = $style_definition->getEffects();
    $style_effects_config = $style_effects->getConfiguration();

    if (empty($style_effects_config)) {
      return FALSE;
    }

    $transformation = [];
    $tmp_effect = [];

    foreach ($style_effects_config as $effect) {
      if (!isset($effects[$effect['id']])) {
        continue;
      }

      $function = $effects[$effect['id']]['callback'];
      $return = $function($effect, $tmp_effect, $resource);
      // Ignore if return data is not an array or null data.
      if (empty($return) || !is_array($return) || !isset($return['type'])) {
        continue;
      }

      $datas = ($return['type'] == CLOUDINARY_STREAM_WRAPPER_TRANSFORMATION_MULTIPLE) ? $return['data'] : [$return];
      foreach ($datas as $data) {
        if ($data['type'] == CLOUDINARY_STREAM_WRAPPER_TRANSFORMATION_APPEND) {
          $tmp_effect = array_merge($tmp_effect, $data['data']);
        }
        elseif ($data['type'] == CLOUDINARY_STREAM_WRAPPER_TRANSFORMATION_NEW) {
          $transformation[] = $tmp_effect;
          $tmp_effect = $data['data'];
        }
      }
    }
    // Merge into transformation if not null.
    if ($tmp_effect) {
      $transformation = array_merge(array_filter($transformation), $tmp_effect);
      unset($tmp_effect);
    }

    $transformations[$style_name] = $transformation;
  }

  return $transformations[$style_name] ?? FALSE;
}

/**
 * Get all valid effect process.
 *
 * @see hook_cloudinary_stream_wrapper_transformation()
 */
function cloudinary_stream_wrapper_transformation_info() {
  $effects = &drupal_static(__FUNCTION__);

  if (!isset($effects)) {
    $effects = \Drupal::moduleHandler()
      ->invokeAll('cloudinary_stream_wrapper_transformation');
    // Support alter, so that other modules can modify exist settings.
    \Drupal::moduleHandler()
      ->alter('cloudinary_stream_wrapper_transformation', $effects);

    foreach ($effects as $key => $process) {
      // Load extra process file.
      if (isset($process['file']) && file_exists($process['file'])) {
        include_once $process['file'];
      }

      if (!isset($process['callback']) || !function_exists($process['callback'])) {
        unset($effects[$key]);
      }
    }
  }

  return $effects;
}

/**
 * Get filesize, width, height of remote image.
 */
function cloudinary_stream_wrapper_remote_image_info($url, $filesize = FALSE) {
  if (empty($url)) {
    return FALSE;
  }

  $result = [];
  $data_block = '';
  $headers = [];

  try {
    $handle = fopen($url, 'rb');
    if (!$handle) {
      return FALSE;
    }
    // Get filesize form headers.
    if ($filesize) {
      $meta = stream_get_meta_data($handle);
      if (is_array($meta['wrapper_data'])) {
        $headers = isset($meta['wrapper_data']['headers']) ? $meta['wrapper_data']['headers'] : $meta['wrapper_data'];
      }
      elseif (is_object($meta['wrapper_data']) && isset($meta['wrapper_data']->stream_headers)) {
        $headers = $meta['wrapper_data']->stream_headers;
      }

      foreach ($headers as $h) {
        if (preg_match('/length/iU', $h)) {
          $size = explode(':', $h);
          $result['bytes'] = trim(array_pop($size));
          break;
        }
      }
    }
    // Get image width and height.
    while (!feof($handle)) {
      $data_block .= fread($handle, 168);
      $size = getimagesize('data://image/jpeg;base64,' . base64_encode($data_block));
      if (!empty($size) && !empty($size[0])) {
        $result['width'] = $size[0];
        $result['height'] = $size[1];
        break;
      }
    }

    fclose($handle);
  }
  catch (\Exception $e) {
    $options = [
      '%url' => $url,
      '%message' => $e->getMessage(),
    ];
    cloudinary_stream_wrapper_logger('Failed to get remote image info [%url], [%message].', $options);
  }

  return $result;
}

/**
 * Logging error messages.
 *
 * @param $message string
 *   Error message.
 * @param array $options
 *   Message options.
 */
function cloudinary_stream_wrapper_logger($message, $options = []) {
  $enable = \Drupal::config('cloudinary_stream_wrapper.settings')
    ->get('cloudinary_stream_wrapper_logging');
  if (!empty($enable)) {
    \Drupal::logger('cloudinary_stream_wrapper')->error($message, $options);
  }
}

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

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