You are here

image_replace.module in Image Replace 7

Same filename and directory in other branches
  1. 8 image_replace.module

Provides an image style effect replacing the whole image with another one.

File

image_replace.module
View source
<?php

/**
 * @file
 * Provides an image style effect replacing the whole image with another one.
 */

/**
 * Determine replacement image uri for the given original filename.
 *
 * @param string $target_style
 *   The target image style name.
 * @param string $target_uri
 *   The uri of the image for which to find a replacement.
 *
 * @return string|null
 *   The replacement uri when a mapping for the given uri/style combination
 *   exists.
 */
function image_replace_get($target_style, $target_uri) {
  return db_select('image_replace')
    ->fields('image_replace', array(
    'replacement_uri',
  ))
    ->condition('target_style', $target_style)
    ->condition('target_uri', $target_uri)
    ->execute()
    ->fetchField();
}

/**
 * Add an image replacement mapping.
 *
 * @param string $target_style
 *   The target image style name.
 * @param string $target_uri
 *   The uri of the image for which to set a replacement.
 * @param string $replacement_uri
 *   The replacement uri to set for the given uri/style combination.
 */
function image_replace_add($target_style, $target_uri, $replacement_uri) {
  return db_insert('image_replace')
    ->fields(array(
    'target_style' => $target_style,
    'target_uri' => $target_uri,
    'replacement_uri' => $replacement_uri,
  ))
    ->execute();
}

/**
 * Remove the given image replacement mapping if it exists.
 *
 * @param string $target_style
 *   The target image style name.
 * @param string $target_uri
 *   The uri of the image for which to remove the replacement.
 */
function image_replace_remove($target_style, $target_uri) {
  return db_delete('image_replace')
    ->condition('target_style', $target_style)
    ->condition('target_uri', $target_uri)
    ->execute();
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add per image-field instance settings for image style replacement.
 */
function image_replace_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  if ($form['#field']['type'] == 'image') {
    $instance = $form['#instance'];
    $form['instance']['settings']['image_replace'] = array(
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#title' => t('Image replace'),
      '#description' => t('Use another image when rendered with certain image styles.'),
      '#weight' => 99,
      '#tree' => TRUE,
      '#element_validate' => array(
        'image_replace_form_field_ui_field_edit_form_element_validate',
      ),
    );
    $image_field_options = array();
    foreach (_image_replace_image_instances($instance['entity_type'], $instance['bundle']) as $field_name => $info) {
      $image_field_options[$field_name] = check_plain($info['label']);
    }
    unset($image_field_options[$form['#field']['field_name']]);
    foreach (_image_replace_style_options() as $image_style => $label) {
      $default_value = _image_replace_instance_setting($instance, 'source_field', $image_style);
      $form['instance']['settings']['image_replace'][$image_style]['source_field'] = array(
        '#type' => 'select',
        '#title' => $label,
        '#description' => t('The image field to use as a source when rendered with the %style image style.', array(
          '%style' => $label,
        )),
        '#options' => $image_field_options,
        '#default_value' => $default_value,
        '#empty_value' => FALSE,
      );
      if ($default_value) {
        $form['instance']['settings']['image_replace']['#collapsed'] = FALSE;
      }
    }
  }
}

/**
 * Form element validation callback.
 *
 * Displays a warning when replacement mapping is changed for fields with
 * existing content.
 */
function image_replace_form_field_ui_field_edit_form_element_validate($element, &$form_state, $form) {
  if (field_has_data($form['#field'])) {
    $changed = FALSE;
    foreach (_image_replace_style_options() as $image_style => $label) {
      $current_value = $element[$image_style]['source_field']['#default_value'];
      $new_value = $element[$image_style]['source_field']['#value'];
      if (!(empty($current_value) && empty($new_value)) && $current_value != $new_value) {
        $changed = TRUE;
        break;
      }
    }
    if ($changed) {
      $replacements = array(
        '@vbo_project_url' => 'https://drupal.org/project/views_bulk_operations',
        '@rules_project_url' => 'https://drupal.org/project/rules',
        '@admin_url' => url('admin/config/media/image-replace-rebuild-node', array(
          'query' => array(
            'type_op' => 'in',
            'type[]' => $form['#instance']['bundle'],
          ),
        )),
        '@media_url' => url('admin/config/media'),
      );
      $non_node_hint = t('If it is too cumbersome to track down all existing entities and resave them, it is suggested to build an administrative View using <a href="@vbo_project_url">Views Bulk Operations</a> and <a href="@rules_project_url">Rules</a>. Image Replace comes with such a View for content entities, which can serve as an example for other entity types.', $replacements);
      $hint_matrix[0][0] = $non_node_hint;
      $hint_matrix[0][1] = $non_node_hint;
      $hint_matrix[1][0] = t('If it is too cumbersome to track down all existing entities and resave them, it is suggested to install <a href="@vbo_project_url">Views Bulk Operations</a> and <a href="@rules_project_url">Rules</a>. Image Replace comes with an administrative View, which can be used to rebuild the image replacement mapping for existing content.', $replacements);
      $hint_matrix[1][1] = t('An <a href="@admin_url">administrative interface</a> capable of rebuilding the replacement mapping is part of the <a href="@media_url">Media configuration</a>', $replacements);
      $is_node = $form['#instance']['entity_type'] === 'node';
      $has_vbo = module_exists('views_bulk_operations') && module_exists('rules');
      $message = t('The image replacement settings have been modified. As a result, it is necessary to rebuild the image replacement mapping for existing content. Note: The replacement mapping is updated automatically when saving an entity.');
      $message .= '<br>';
      $message .= $hint_matrix[$is_node][$has_vbo];
      $message .= '<br>';
      $message .= t('Also note that images already might be cached in the browser or by any intermediate HTTP cache. On live sites the only way to force browsers to redownload a cached image is to reupload the image with a different name.');
      drupal_set_message($message, 'warning');
    }
  }
}

/**
 * Implements hook_field_attach_presave().
 *
 * Insert image mappings into the image replace table when entities are saved.
 */
function image_replace_field_attach_presave($entity_type, $entity) {
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);

  // Collect involved image fields and target mapping from image instance
  // settings.
  $involved_fields = array();
  $target_map = array();
  foreach (_image_replace_image_instances($entity_type, $bundle) as $target_field => $instance) {
    $source_map = _image_replace_instance_setting($instance, 'source_field');
    if (!empty($source_map)) {
      $involved_fields[$target_field] = $target_field;
      foreach ($source_map as $target_style => $source_field) {
        $target_map[$target_field][$target_style] = $source_field;
        $involved_fields[$source_field] = $source_field;
      }
    }
  }

  // Extract all uris from all involved image fields.
  $uri_map = array();
  foreach ($involved_fields as $field_name) {
    $uri_map[$field_name] = array();
    $items = field_get_items($entity_type, $entity, $field_name);
    if ($items) {
      foreach ($items as $item) {
        $file = file_load($item['fid']);
        $uri_map[$field_name][] = $file->uri;
      }
    }
  }

  // Synchronize image replacement entries.
  foreach ($target_map as $target_field => $source_map) {
    foreach ($source_map as $target_style => $source_field) {
      foreach ($uri_map[$target_field] as $delta => $target_uri) {
        image_replace_remove($target_style, $target_uri);
        if (isset($uri_map[$source_field][$delta])) {
          image_replace_add($target_style, $target_uri, $uri_map[$source_field][$delta]);
        }
      }
    }
  }

  // Flush derived images.
  foreach ($uri_map as $uris) {
    foreach ($uris as $uri) {
      image_path_flush($uri);
    }
  }
}

/**
 * Implements hook_image_effect_info().
 */
function image_replace_image_effect_info() {
  $effects = array();

  // The array is keyed on the machine-readable effect name.
  $effects['image_replace'] = array(
    'label' => t('Replace image'),
    'help' => t('Swap the original image if a replacement image was configured.'),
    'effect callback' => 'image_replace_effect',
    'dimensions passthrough' => TRUE,
  );
  return $effects;
}

/**
 * Image effect callback: Change the image if a replacement is available.
 */
function image_replace_effect(&$image, $data) {
  $replacement_file = image_replace_get($data['image_style'], $image->source);
  if ($replacement_file) {
    $replacement_image = image_load($replacement_file);
    if ($replacement_image) {
      foreach ($image as $key => $value) {
        unset($image->{$key});
      }
      foreach ($replacement_image as $key => $value) {
        $image->{$key} = $value;
      }
    }
  }
}

/**
 * Implements hook_image_styles_alter().
 *
 * Save image style name into replace effect settings in order to make the style
 * name available from within image_replace_effect callback.
 */
function image_replace_image_styles_alter(&$styles) {
  foreach ($styles as $image_style => $style) {
    foreach ($style['effects'] as $ieid => $effect) {
      if ($effect['name'] == 'image_replace') {
        $styles[$image_style]['effects'][$ieid]['data']['image_style'] = $image_style;
      }
    }
  }
}

/**
 * Implements hook_views_api().
 */
function image_replace_views_api() {
  return array(
    'api' => 3,
  );
}

/**
 * Collect info for all image field instances on a given entity_type/bundle.
 *
 * @param string $entity_type
 *   The entity type, e.g. node, for which the info shall be returned.
 * @param string $bundle
 *   The bundle name for which to return instances.
 *
 * @return array
 *   An associative array of instance arrays keyed by the field name.
 */
function _image_replace_image_instances($entity_type, $bundle) {
  $image_instances = array();
  foreach (field_info_field_map() as $field_name => $info) {
    if ($info['type'] == 'image' && !empty($info['bundles'][$entity_type]) && in_array($bundle, $info['bundles'][$entity_type])) {
      $instance_info = field_info_instance($entity_type, $field_name, $bundle);
      $image_instances[$field_name] = $instance_info;
    }
  }
  return $image_instances;
}

/**
 * Return image replace settings for the given field instance.
 *
 * @param array $instance
 *   An array defining the field instance.
 * @param string $key
 *   (Optional) The key of the setting to return. If $image_style is omitted,
 *   an associative array will be returned indexed with the image style name.
 *   If $image_style is given, the value of the setting for the given image
 *   style is returned.
 * @param string $image_style
 *   (Optional) The name of the image style for which to retrieve the settings.
 *   If $key is omitted, all settings for the given image style will be
 *   returned. If $key is given, only the value for the given setting key is
 *   returned.
 *
 * @return any
 *   When a setting is defined matching the parameters, the setting value,
 *   otherwise NULL.
 */
function _image_replace_instance_setting(array $instance, $key = NULL, $image_style = NULL) {
  $settings = isset($instance['settings']['image_replace']) ? $instance['settings']['image_replace'] : array();
  $result = $settings;
  if (isset($key) && isset($image_style)) {
    $result = isset($settings[$image_style][$key]) ? $settings[$image_style][$key] : NULL;
  }
  elseif (isset($image_style)) {
    $result = isset($settings[$image_style]) ? $settings[$image_style] : array();
  }
  elseif (isset($key)) {
    $result = array();
    foreach ($settings as $image_style => $values) {
      if (!empty($values[$key])) {
        $result[$image_style] = $values[$key];
      }
    }
  }
  return $result;
}

/**
 * Return a list of image replace enabled style => label map.
 *
 * @return array
 *   A key-value list where keys are style names and values are style labels of
 *   image styles having a replace effect configured.
 */
function _image_replace_style_options() {
  $styles = image_styles();
  $result = array();
  foreach ($styles as $style_name => $style) {
    foreach ($style['effects'] as $effect) {
      if ($effect['name'] == 'image_replace') {
        $result[$style_name] = check_plain($style['label']);
      }
    }
  }
  return $result;
}

Functions

Namesort descending Description
image_replace_add Add an image replacement mapping.
image_replace_effect Image effect callback: Change the image if a replacement is available.
image_replace_field_attach_presave Implements hook_field_attach_presave().
image_replace_form_field_ui_field_edit_form_alter Implements hook_form_FORM_ID_alter().
image_replace_form_field_ui_field_edit_form_element_validate Form element validation callback.
image_replace_get Determine replacement image uri for the given original filename.
image_replace_image_effect_info Implements hook_image_effect_info().
image_replace_image_styles_alter Implements hook_image_styles_alter().
image_replace_remove Remove the given image replacement mapping if it exists.
image_replace_views_api Implements hook_views_api().
_image_replace_image_instances Collect info for all image field instances on a given entity_type/bundle.
_image_replace_instance_setting Return image replace settings for the given field instance.
_image_replace_style_options Return a list of image replace enabled style => label map.