You are here

lazy.module in Lazy-load 7

Same filename and directory in other branches
  1. 8.3 lazy.module
  2. 8 lazy.module
  3. 8.2 lazy.module

Module file for Lazy-load.

File

lazy.module
View source
<?php

/**
 * @file
 * Module file for Lazy-load.
 */

/**
 * Implements hook_menu().
 */
function lazy_menu() {
  return array(
    'admin/config/content/lazy' => array(
      'title' => 'Lazy-load',
      'description' => 'Configure how images and iframes are lazy-loaded.',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'lazy_configuration_form',
      ),
      'file' => 'lazy.admin.inc',
      'access arguments' => array(
        'administer filters',
      ),
    ),
  );
}

/**
 * Implements hook_init().
 */
function lazy_init() {
  $library = lazy_is_library_installed();
}

/**
 * Implements hook_help().
 */
function lazy_help($path, $arg) {
  switch ($path) {
    case 'admin/help#lazy':
      $filepath = dirname(__FILE__) . '/README.md';
      if (file_exists($filepath)) {
        $readme = file_get_contents($filepath);
      }
      if (!isset($readme)) {
        return NULL;
      }
      if (module_exists('markdown')) {
        $filters = module_invoke('markdown', 'filter_info');
        $info = $filters['filter_markdown'];
        if (function_exists($info['process callback'])) {
          $output = $info['process callback']($readme, NULL);
        }
        else {
          $output = '<pre>' . $readme . '</pre>';
        }
      }
      else {
        $output = '<pre>' . $readme . '</pre>';
      }
      return $output;
  }
}

/**
 * Implements hook_filter_info().
 */
function lazy_filter_info() {
  $filters['lazy_filter'] = array(
    'title' => t('Lazy-load images and IFRAMEs via bLazy'),
    'default settings' => _filter_lazy_defaults(),
    'process callback' => '_filter_lazy_process',
    'cache' => TRUE,
    'tips callback' => '_filter_lazy_tips',
    'weight' => 20,
  );
  return $filters;
}

/**
 * Callback to return configuration defaults.
 */
function _filter_lazy_defaults() {
  return array(
    'lazy_filter_errorClass' => variable_get('lazy_filter_errorClass'),
    'lazy_filter_loadInvisible' => variable_get('lazy_filter_loadInvisible'),
    'lazy_filter_offset' => variable_get('lazy_filter_offset'),
    'lazy_filter_saveViewportOffsetDelay' => variable_get('lazy_filter_saveViewportOffsetDelay'),
    'lazy_filter_selector' => variable_get('lazy_filter_selector'),
    'lazy_filter_alter_tag' => variable_get('lazy_filter_alter_tag'),
    'lazy_filter_skipClass' => variable_get('lazy_filter_skipClass'),
    'lazy_filter_src' => variable_get('lazy_filter_src'),
    'lazy_filter_successClass' => variable_get('lazy_filter_successClass'),
    'lazy_filter_validateDelay' => variable_get('lazy_filter_validateDelay'),
    'lazy_filter_placeholderSrc' => variable_get('lazy_filter_placeholderSrc'),
  );
}

/**
 * Implements callback_filter_process().
 */
function _filter_lazy_process($text, $filter) {
  $opt_skipClass = variable_get('lazy_filter_skipClass');
  $opt_selector = ltrim(variable_get('lazy_filter_selector'), '.');
  $opt_tags = variable_get('lazy_filter_alter_tag');
  $opt_src = variable_get('lazy_filter_src') !== 'src' ? variable_get('lazy_filter_src') : 'data-filterlazy-src';
  $opt_placeholderSrc = variable_get('lazy_filter_placeholderSrc');
  $library_installed = variable_get('lazy_library_installed');
  if ($library_installed && lazy_is_path_allowed()) {
    $html_dom = filter_dom_load($text);
    foreach ($opt_tags as $tag) {
      $matches = $html_dom
        ->getElementsByTagName($tag);
      foreach ($matches as $element) {
        $classes = $element
          ->getAttribute('class');
        $classes = $classes != '' ? explode(' ', $classes) : array();
        if (!in_array($opt_skipClass, $classes, TRUE)) {
          if (variable_get('lazy_prefer_native') && !$element
            ->hasAttribute('loading')) {

            // Required attribute for enabling native lazy-loading.
            $element
              ->setAttribute('loading', 'lazy');
          }
          elseif (!$element
            ->hasAttribute($opt_src)) {
            $classes[] = $opt_selector;
            $element
              ->setAttribute('class', implode(' ', $classes));
            $src = $element
              ->getAttribute('src');
            $element
              ->removeAttribute('src');
            $element
              ->setAttribute($opt_src, $src);
            $element
              ->setAttribute('src', $opt_placeholderSrc);
          }
        }
      }
    }
    $text = filter_dom_serialize($html_dom);
  }
  return trim($text);
}

/**
 * Implements callback_filter_tips().
 */
function _filter_lazy_tips($filter, $format, $long = FALSE) {
  $tags = variable_get('lazy_filter_alter_tag');
  $tags = implode(' ', $tags);
  $tags = trim(str_replace(array(
    '0',
  ), '', $tags));
  switch ($tags) {
    case 'img iframe':
      return t('Both %img and %iframe elements are lazy-loaded.', array(
        '%img' => '<img>',
        '%iframe' => '<iframe>',
      ));
    case 'img':
      return t('%img elements are lazy-loaded.', array(
        '%img' => '<img>',
        '%iframe' => '<iframe>',
      ));
    case 'iframe':
      return t('%iframe elements are lazy-loaded.', array(
        '%img' => '<img>',
        '%iframe' => '<iframe>',
      ));
  }
}

/**
 * Is filter enabled for any of text-formats.
 */
function lazy_is_filter_enabled() {
  $options = array(
    ':module' => 'lazy',
    ':name' => 'lazy_filter',
    ':status' => 1,
  );
  $enabled_formats = db_query('SELECT f.format, f.settings FROM {filter} f WHERE f.module = :module AND f.name = :name AND f.status = :status', $options)
    ->fetchAllKeyed();
  return (bool) count($enabled_formats);
}

/**
 * Is any of the image fields using the `Lazy-load image` formatter?
 */
function lazy_is_field_enabled() {
  $lazy_enabled_fields = array();
  $field_map = field_info_field_map();
  foreach ($field_map as $field_name => $field) {
    if ($field['type'] === 'image') {
      foreach ($field['bundles'] as $entity_type => $bundle) {
        $field_instance = field_info_instance($entity_type, $field_name, $bundle[0]);
        foreach ($field_instance['display'] as $display_name => $display) {
          if ($field_instance['display'][$display_name]['type'] === 'lazy') {
            $lazy_enabled_fields[] = implode('-', array(
              $field_instance['entity_type'],
              $field_instance['field_name'],
              $display_name,
            ));
          }
        }
      }
    }
  }
  return (bool) count($lazy_enabled_fields);
}

/**
 * Is path allowed?
 *
 * @return bool
 *   Returns true if current path is not listed in disabled pages.
 */
function lazy_is_path_allowed() {
  $pages = drupal_strtolower(variable_get('lazy_disabled_paths'));

  // Convert the Drupal path to lowercase.
  $path = drupal_strtolower(drupal_get_path_alias($_GET['q']));

  // Compare the lowercase internal and lowercase path alias (if any).
  $page_match = drupal_match_path($path, $pages);
  if ($path != $_GET['q']) {
    $page_match = $page_match || drupal_match_path($_GET['q'], $pages);
  }
  return !$page_match;
}

/**
 * Is Blazy library installed.
 */
function lazy_is_library_installed() {
  $blazy = libraries_detect('blazy');
  $blazy_installed = variable_get('lazy_library_installed');
  if ($blazy['installed'] !== $blazy_installed) {
    variable_set('lazy_library_installed', $blazy['installed']);
  }
  return $blazy['installed'];
}

/**
 * Implements hook_page_build().
 */
function lazy_page_build(&$page) {
  if (!variable_get('lazy_prefer_native')) {
    $path = drupal_get_path('module', 'lazy');
    $page['page_bottom']['lazy'] = array(
      '#attached' => array(),
    );
    $attached =& $page['page_bottom']['lazy']['#attached'];
    $attached['js'][] = array(
      'data' => array(
        'lazy' => array(
          'errorClass' => variable_get('lazy_filter_errorClass'),
          'loadInvisible' => variable_get('lazy_filter_loadInvisible'),
          'offset' => (int) variable_get('lazy_filter_offset'),
          'saveViewportOffsetDelay' => (int) variable_get('lazy_filter_saveViewportOffsetDelay'),
          'selector' => '.' . variable_get('lazy_filter_selector'),
          'src' => variable_get('lazy_filter_src'),
          'successClass' => variable_get('lazy_filter_successClass'),
          'validateDelay' => (int) variable_get('lazy_filter_validateDelay'),
        ),
      ),
      'type' => 'setting',
    );
    $attached['libraries_load'][] = array(
      'blazy',
    );
    $attached['js'][$path . '/lazy.js'] = array(
      'every_page' => TRUE,
    );
  }
}

/**
 * Implements hook_libraries_info().
 */
function lazy_libraries_info() {
  $libraries['blazy'] = array(
    'name' => 'bLazy',
    'vendor url' => 'https://github.com/dinbror/blazy/',
    'download url' => 'https://github.com/dinbror/blazy/archive/master.zip',
    'version callback' => 'lazy_library_get_version',
    'version arguments' => array(
      'file' => 'package.json',
    ),
    'files' => array(
      'js' => array(
        'blazy.min.js' => array(
          'group' => JS_LIBRARY,
          'every_page' => TRUE,
        ),
      ),
    ),
    'variants' => array(
      'source' => array(
        'files' => array(
          'js' => array(
            'blazy.js' => array(
              'group' => JS_LIBRARY,
              'every_page' => TRUE,
            ),
          ),
        ),
      ),
    ),
  );
  return $libraries;
}

/**
 * Callback function to return version from provided JSON file.
 */
function lazy_library_get_version($library, $options) {
  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
  if (empty($options['file']) || !file_exists($file)) {
    return 'n/a';
  }
  $file_content = file_get_contents($file);
  $json = json_decode($file_content, TRUE);
  return $json['version'];
}

/**
 * Implements hook_field_formatter_info().
 */
function lazy_field_formatter_info() {
  return array(
    'lazy' => array(
      'label' => t('Lazy-load image'),
      'field types' => array(
        'image',
      ),
      'settings' => array(
        'image_style' => '',
        'image_link' => '',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function lazy_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $image_styles = image_style_options(FALSE, PASS_THROUGH);
  $element['image_style'] = array(
    '#title' => t('Image style'),
    '#type' => 'select',
    '#default_value' => $settings['image_style'],
    '#empty_option' => t('None (original image)'),
    '#options' => $image_styles,
  );
  $link_types = array(
    'content' => t('Content'),
    'file' => t('File'),
  );
  $element['image_link'] = array(
    '#title' => t('Link image to'),
    '#type' => 'select',
    '#default_value' => $settings['image_link'],
    '#empty_option' => t('Nothing'),
    '#options' => $link_types,
  );
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function lazy_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $summary = array();
  $image_styles = image_style_options(FALSE, PASS_THROUGH);

  // Unset possible 'No defined styles' option.
  unset($image_styles['']);

  // Styles could be lost because of enabled/disabled modules that defines
  // their styles in code.
  if (isset($image_styles[$settings['image_style']])) {
    $summary[] = t('Image style: @style', array(
      '@style' => $image_styles[$settings['image_style']],
    ));
  }
  else {
    $summary[] = t('Original image');
  }
  $link_types = array(
    'content' => t('Linked to content'),
    'file' => t('Linked to file'),
  );

  // Display this setting only if image is linked.
  if (isset($link_types[$settings['image_link']])) {
    $summary[] = $link_types[$settings['image_link']];
  }
  return implode('<br />', $summary);
}

/**
 * Implements hook_field_formatter_view().
 */
function lazy_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();

  // Check if the formatter involves a link.
  if ($display['settings']['image_link'] == 'content') {
    $uri = entity_uri($entity_type, $entity);
  }
  elseif ($display['settings']['image_link'] == 'file') {
    $link_file = TRUE;
  }

  // Check if the formatter is set to lazy-load.
  // if ($display['type'] === 'lazy') {}
  foreach ($items as $delta => $item) {
    if (isset($link_file)) {
      $uri = array(
        'path' => file_create_url($item['uri']),
        'options' => array(),
      );
    }
    $element[$delta] = array(
      '#theme' => 'lazy_image_formatter',
      '#item' => $item,
      '#image_style' => $display['settings']['image_style'],
      '#path' => isset($uri) ? $uri : '',
    );
  }
  return $element;
}

/**
 * Implements hook_theme().
 */
function lazy_theme() {
  return array(
    'lazy_image' => array(
      'variables' => array(
        'style_name' => NULL,
        'path' => NULL,
        'width' => NULL,
        'height' => NULL,
        'alt' => '',
        'title' => NULL,
        'attributes' => array(),
      ),
    ),
    'lazy_image_formatter' => array(
      'variables' => array(
        'item' => NULL,
        'path' => NULL,
        'image_style' => NULL,
      ),
    ),
  );
}

/**
 * Returns HTML for an image.
 *
 * @param $variables
 *   An associative array containing:
 *   - path: Either the path of the image file (relative to base_path()) or a
 *     full URL.
 *   - width: The width of the image (if known).
 *   - height: The height of the image (if known).
 *   - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0
 *     always require an alt attribute. The HTML 5 draft allows the alt
 *     attribute to be omitted in some cases. Therefore, this variable defaults
 *     to an empty string, but can be set to NULL for the attribute to be
 *     omitted. Usually, neither omission nor an empty string satisfies
 *     accessibility requirements, so it is strongly encouraged for code
 *     calling theme('image') to pass a meaningful value for this variable.
 *     - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
 *     - http://www.w3.org/TR/xhtml1/dtds.html
 *     - http://dev.w3.org/html5/spec/Overview.html#alt
 *   - title: The title text is displayed when the image is hovered in some
 *     popular browsers.
 *   - attributes: Associative array of attributes to be placed in the img tag.
 */
function theme_lazy_image($variables) {
  $opt_skipClass = variable_get('lazy_filter_skipClass');
  $opt_selector = ltrim(variable_get('lazy_filter_selector'), '.');
  $opt_src = variable_get('lazy_filter_src') !== 'src' ? variable_get('lazy_filter_src') : 'data-filterlazy-src';
  $opt_placeholderSrc = variable_get('lazy_filter_placeholderSrc');
  if ($variables['style_name']) {

    // Determine the dimensions of the styled image.
    $dimensions = array(
      'width' => $variables['width'],
      'height' => $variables['height'],
    );
    image_style_transform_dimensions($variables['style_name'], $dimensions);
    $variables['width'] = $dimensions['width'];
    $variables['height'] = $dimensions['height'];

    // Determine the URL for the styled image.
    $variables['path'] = image_style_url($variables['style_name'], $variables['path']);
  }

  // Skip Blazy rendering if...
  //
  // library is not available OR
  // viewing a page listed in disabled pages
  $class = isset($variables['attributes']['class']) ? $variables['attributes']['class'] : array();
  if (!variable_get('lazy_library_installed') || !lazy_is_path_allowed() || in_array($opt_skipClass, $class, TRUE)) {
    return theme('image', $variables);
  }
  $attributes = $variables['attributes'];
  if (variable_get('lazy_prefer_native')) {

    // Required attribute for enabling native lazy-loading.
    $attributes['loading'] = 'lazy';
    $attributes['src'] = file_create_url($variables['path']);
  }
  else {
    $attributes['src'] = $opt_placeholderSrc;
    $attributes[$opt_src] = file_create_url($variables['path']);
    $attributes['class'][] = $opt_selector;
  }
  foreach (array(
    'width',
    'height',
    'alt',
    'title',
  ) as $key) {
    if (isset($variables[$key])) {
      $attributes[$key] = $variables[$key];
    }
  }
  return '<img' . drupal_attributes($attributes) . ' />';
}

/**
 * Returns HTML for an image field formatter.
 *
 * @param $variables
 *   An associative array containing:
 *   - item: Associative array of image data, which may include "uri", "alt",
 *     "width", "height", "title" and "attributes".
 *   - image_style: An optional image style.
 *   - path: An array containing the link 'path' and link 'options'.
 *
 * @ingroup themeable
 */
function theme_lazy_image_formatter($variables) {
  $item = $variables['item'];
  $image = array(
    'path' => $item['uri'],
  );
  if (array_key_exists('alt', $item)) {
    $image['alt'] = $item['alt'];
  }
  if (isset($item['attributes'])) {
    $image['attributes'] = $item['attributes'];
  }
  if (isset($item['width'], $item['height'])) {
    $image['width'] = $item['width'];
    $image['height'] = $item['height'];
  }

  // Do not output an empty 'title' attribute.
  if (isset($item['title']) && drupal_strlen($item['title']) > 0) {
    $image['title'] = $item['title'];
  }
  if ($variables['image_style']) {
    $image['style_name'] = $variables['image_style'];
  }
  $output = theme('lazy_image', $image);

  // The link path and link options are both optional, but for the options to be
  // processed, the link path must at least be an empty string.
  if (isset($variables['path']['path'])) {
    $path = $variables['path']['path'];
    $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();

    // When displaying an image inside a link, the html option must be TRUE.
    $options['html'] = TRUE;
    $output = l($output, $path, $options);
  }
  return $output;
}

Functions

Namesort descending Description
lazy_field_formatter_info Implements hook_field_formatter_info().
lazy_field_formatter_settings_form Implements hook_field_formatter_settings_form().
lazy_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
lazy_field_formatter_view Implements hook_field_formatter_view().
lazy_filter_info Implements hook_filter_info().
lazy_help Implements hook_help().
lazy_init Implements hook_init().
lazy_is_field_enabled Is any of the image fields using the `Lazy-load image` formatter?
lazy_is_filter_enabled Is filter enabled for any of text-formats.
lazy_is_library_installed Is Blazy library installed.
lazy_is_path_allowed Is path allowed?
lazy_libraries_info Implements hook_libraries_info().
lazy_library_get_version Callback function to return version from provided JSON file.
lazy_menu Implements hook_menu().
lazy_page_build Implements hook_page_build().
lazy_theme Implements hook_theme().
theme_lazy_image Returns HTML for an image.
theme_lazy_image_formatter Returns HTML for an image field formatter.
_filter_lazy_defaults Callback to return configuration defaults.
_filter_lazy_process Implements callback_filter_process().
_filter_lazy_tips Implements callback_filter_tips().