You are here

class JuiceboxGallery in Juicebox HTML5 Responsive Image Galleries 8.3

Same name and namespace in other branches
  1. 8.2 src/JuiceboxGallery.php \Drupal\juicebox\JuiceboxGallery

Class to generate the script and markup for a Juicebox gallery.

This PHP only class does not use any Drupal statics and can be used independently.

Hierarchy

Expanded class hierarchy of JuiceboxGallery

File

src/JuiceboxGallery.php, line 11

Namespace

Drupal\juicebox
View source
class JuiceboxGallery implements JuiceboxGalleryInterface {

  /**
   * Base properties to contain structured gallery data.
   *
   * @var string
   */
  protected $id = '';

  /**
   * The settings property is specific to the logic used in this object.
   *
   * @var array
   */
  protected $settings = [];

  // The options and images properties are used to store configuration and
  // image data that will be processed into XML for use by the Juicebox.

  /**
   * Javascript library.
   *
   * @var array
   */
  protected $options = [];

  /**
   * Store images data.
   *
   * @var array
   */
  protected $images = [];

  /**
   * Constructor.
   *
   * @param string $id
   *   A unique string id that can represent this gallery.
   * @param array $settings
   *   Configuration data to be used by this gallery object. These options
   *   relate to any "output" processing applied to the raw gallery variables
   *   upon rendering.
   *   - filter_markup: Optional boolean indicating whether-or-not to filter all
   *     image title and caption values for syntactic compatibility with
   *     Juicebox. This designates a gallery-wide default and can still be
   *     overriden on an image-by-image basis. Defaults to TRUE.
   *   - process_attributes: Optional boolean to activate legacy attribute name
   *     support for underscore-separated and dash-separated attributes
   *     (e.g., convert image_url to imageURL). Defaults to FALSE.
   */
  public function __construct($id = '', array $settings = []) {

    // Set the ID.
    $this->id = $id;

    // Ensure some default settings are specified.
    $settings += [
      'filter_markup' => TRUE,
      'process_attributes' => FALSE,
    ];
    $this->settings = $settings;
  }

  /**
   * {@inheritdoc}
   */
  public function setId($value, $reset = TRUE) {
    if ($reset || empty($this->id)) {
      $this->id = $value;
      return TRUE;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getId() {
    return $this->id;
  }

  /**
   * {@inheritdoc}
   */
  public function addImage(array $src_data = [], $title = '', $caption = '', $filter_markup = NULL, $override_id = NULL, $offset = NULL) {

    // If we are anticipating an override, but there is nothing to override,
    // don't do anything. Also, override_id and offset are mutually exclusive.
    if (isset($override_id) && (empty($this->images[$override_id]) || isset($offset))) {
      return FALSE;
    }

    // Make sure we have sufficient image data to work with. If legacy attribute
    // names are supported in the configuration then some names may be coming in
    // different formats (image_url vs. imageURL). In this case we need a
    // canonical form for validation.
    $src_data_canonical = !empty($this->settings['process_attributes']) ? $this
      ->processAttributes($src_data) : $src_data;
    if (!isset($src_data_canonical['imageURL']) || !isset($src_data_canonical['thumbURL'])) {
      return FALSE;
    }

    // Add image to gallery, overriding if necessary.
    $addition = [
      'src_data' => $src_data,
      'title' => $title,
      'caption' => $caption,
      'filter_markup' => $filter_markup,
    ];
    if (isset($override_id)) {
      $this->images[$override_id] = $addition;
    }
    elseif (isset($offset)) {
      array_splice($this->images, $offset, 0, [
        $addition,
      ]);
    }
    else {
      $this->images[] = $addition;
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function updateImage($image_id, array $src_data = [], $title = '', $caption = '', $filter = TRUE) {

    // Updating can be accomplished with addImage(), so just pass-through the
    // needed params.
    return $this
      ->addImage($src_data, $title, $caption, $filter, $image_id);
  }

  /**
   * {@inheritdoc}
   */
  public function getImages($filtered = FALSE) {
    $images = $this->images;

    // If we are not returning the raw input data we need to apply any output
    // processing that may be specified in the object configuration.
    if ($filtered) {
      foreach ($images as &$image) {
        $image['title'] = !empty($this->settings['filter_markup']) ? $this
          ->filterMarkup($image['title']) : $image['title'];
        $image['caption'] = !empty($this->settings['filter_markup']) ? $this
          ->filterMarkup($image['caption']) : $image['caption'];
        $image['src_data'] = !empty($this->settings['process_attributes']) ? $this
          ->processAttributes($image['src_data']) : $image['src_data'];

        // Also derive linkURL and linkTarget values if they are not explicitly
        // set.
        if (!isset($image['src_data']['linkURL'])) {
          $image['src_data']['linkURL'] = $image['src_data']['imageURL'];
        }
        if (!isset($image['src_data']['linkTarget'])) {
          $image['src_data']['linkTarget'] = '_blank';
        }
      }
    }
    return $images;
  }

  /**
   * {@inheritdoc}
   */
  public function removeImage($id) {
    if (!empty($this->images[$id])) {
      unset($this->images[$id]);
      return TRUE;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function addOption($option_name, $option_value, $override = TRUE) {

    // Always use lowercase keys to allow for future lookups.
    $option_name = strtolower($option_name);
    if (!empty($this->options[$option_name]) && !$override) {
      return FALSE;
    }

    // Add option,.
    $this->options[$option_name] = $option_value;
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getOptions($filtered = FALSE) {
    $options = $this->options;

    // If we are not returning the raw input data we need to apply any output
    // processing that may be specified in the object configuration.
    if ($filtered) {
      $options = !empty($this->settings['process_attributes']) ? $this
        ->processAttributes($options) : $options;
    }
    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function removeOption($option_name) {
    $option_name = strtolower($option_name);
    if (!empty($this->options[$option_name])) {
      unset($this->options[$option_name]);
      return TRUE;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getChecksum() {
    return md5(json_encode($this->images) . json_encode($this->options));
  }

  /**
   * {@inheritdoc}
   */
  public function renderXml($embed_wrap_id = NULL) {

    // We use DOMDocument instead of a SimpleXMLElement to build the XML as it's
    // much more flexible (CDATA is supported, etc.).
    $dom = new \DOMDocument('1.0', 'UTF-8');
    $dom->formatOutput = TRUE;
    $juicebox = $dom
      ->appendChild($dom
      ->createElement('juicebox'));

    // Get filtered attributes.
    $gallery_attributes = $this
      ->getOptions(TRUE);
    foreach ($gallery_attributes as $attribute => $value) {
      $juicebox
        ->setAttribute($attribute, $value);
    }

    // Get filtered image data.
    $gallery_images = $this
      ->getImages(TRUE);
    foreach ($gallery_images as $image) {
      $juicebox_image = $juicebox
        ->appendChild($dom
        ->createElement('image'));
      foreach ($image['src_data'] as $attribute => $value) {
        $juicebox_image
          ->setAttribute($attribute, $value);
      }
      $juicebox_image_title = $juicebox_image
        ->appendChild($dom
        ->createElement('title'));
      $juicebox_image_title
        ->appendChild($dom
        ->createCDATASection($image['title']));
      $juicebox_image_caption = $juicebox_image
        ->appendChild($dom
        ->createElement('caption'));
      $juicebox_image_caption
        ->appendChild($dom
        ->createCDATASection($image['caption']));
    }
    $prefix = $suffix = '';
    if ($embed_wrap_id) {
      $prefix = '<script id="' . $embed_wrap_id . '" type="text/xml">';
      $suffix = '</script>';
    }
    return $prefix . $dom
      ->saveXML() . $suffix;
  }

  /**
   * {@inheritdoc}
   */
  public function renderEmbed() {
    $output = '';
    $output .= '<div class="juicebox-parent">';
    $output .= '<div id="' . $this->id . '" class="juicebox-container">';
    $output .= '</div></div>';
    return $output;
  }

  /**
   * {@inheritdoc}
   */
  public function renderJavascript($xml_url, $add_script_tags = FALSE, $jquery_defer = FALSE) {

    // Get variable inputs for the juicebox object and represent them as a
    // string.
    $vars_json = json_encode($this
      ->getJavascriptVars($xml_url));

    // Set the Juicebox-specific code.
    $output = "new juicebox(" . $vars_json . ");";

    // Set wrappers.
    $script_wrap_prefix = $script_wrap_suffix = $inner_wrap_prefix = $inner_wrap_suffix = '';
    if ($add_script_tags) {
      $script_wrap_prefix = '<script type="text/javascript">' . "\n<!--//--><![CDATA[//><!--\n";
      $script_wrap_suffix = "\n//--><!]]>\n" . '</script>';
    }
    if ($jquery_defer) {
      $inner_wrap_prefix = 'jQuery(document).ready(function () {';
      $inner_wrap_suffix = '});';
    }
    return "\n" . $script_wrap_prefix . $inner_wrap_prefix . $output . $inner_wrap_suffix . $script_wrap_suffix . "\n";
  }

  /**
   * {@inheritdoc}
   */
  public function getJavascriptVars($xml_url) {
    $vars = [
      'configUrl' => $xml_url,
      'containerId' => $this
        ->getId(),
    ];

    // Add options that need to be loaded immediately (before XML is available).
    $load_before_xml = [
      'gallerywidth',
      'galleryheight',
      'backgroundcolor',
      'themeurl',
      'baseurl',
      'showpreloader',
      'debugmode',
    ];
    $current_options = $this
      ->getOptions(TRUE);
    foreach ($load_before_xml as $option) {
      if (!empty($current_options[$option])) {
        $vars[$option] = $current_options[$option];
      }
    }
    return $vars;
  }

  /**
   * Process an attribute list for valid use in a Juicebox gallery.
   *
   * For legacy support we can allow a variety of attribute name inputs,
   * including underscore-separated and dash-separated values. Because the
   * Juicebox library expects attribute names in camelCase we use this method to
   * make a best-effort conversion (e.g., convert image_url to imageURL). Note
   * that this process does NOT do any sanitization and can only correctly
   * convert certain input names.
   *
   * @param array $attributes
   *   An associative array of name => value pairs for juicebox XML attributes
   *   to be converted.
   *
   * @return array
   *   The converted array of attributes.
   */
  protected function processAttributes(array $attributes) {
    $filtered = [];
    foreach ($attributes as $name => $value) {

      // First make some adjustments for legacy support. We used to use some
      // specialized keys that are no longer valid, but still want to support
      // them as input. These values are special and cannot be handled alone by
      // the word-separator processing logic below.
      $name_mappings = [
        'image_url_small' => 'smallImageURL',
        'image_url_large' => 'largeImageURL',
      ];
      if (array_key_exists($name, $name_mappings)) {
        $name = $name_mappings[$name];
      }

      // Check that the name is in a format expected by the XML such that any
      // word-separators (like "-" and "_") are dropped. Also, the Juicebox
      // library uses camelCase for attributes so we do our best to deliver
      // that. Note that for image attributes proper camelCase appears to be
      // REQUIRED, but for gallery attributes all-lowercase values will work in
      // addition to camelCase.
      $parts = preg_split('/(_|-)/', $name);
      $i = 0;
      foreach ($parts as &$word) {

        // Don't alter the first word.
        if ($i) {
          $word = ucfirst($word);

          // For some reason the library requires that some attributes
          // containing a "url" string capitalize the "url" part.
          $word = $word == 'Url' ? 'URL' : $word;
        }
        $i++;
      }
      $name = implode('', $parts);
      $filtered[$name] = $value;
    }
    return $filtered;
  }

  /**
   * Filter markup for valid display in a Juicebox gallery.
   *
   * Some markup that validates fine via external filters will not be
   * syntactically valid once rendered within Juicebox. This is because Juicebox
   * will wrap titles and captions in block-level tags, like <p>, making any
   * block-level elements they contain invalid. This filter accommodates for
   * this and is meant to be applied AFTER any external filters. Note that this
   * process does NOT do any sanitization.
   *
   * @param string $markup
   *   The markup to be filtered after it has been processed externally.
   *
   * @return string
   *   Valid filtered markup ready for display in a Juicebox gallery.
   */
  protected function filterMarkup($markup) {

    // Set inline html5 elements that are safe in a Juicebox gallery. Ref:
    // http://www.w3.org/html/wg/drafts/html/master/single-page.html#phrasing-content
    $valid_elements = "<a><abbr><area><audio><b><bdi><bdo><br><button><canvas><cite><code><data><del><dfn><em><embed><i><iframe><img><input><ins><kbd><keygen><label><link><map><mark><math><meta><meter><noscript><object><output><progress><q><ruby><s><samp><script><select><small><span><strong><sub><sup><svg><template><textarea><time><u><var><video><wbr>";

    // Add some html4 additions for legacy support.
    // Ref: http://www.htmlhelp.com/reference/html40/inline.html
    $valid_elements .= "<acronym><basefont><big><font><rp><rt><strike><tt>";
    $markup = strip_tags($markup, $valid_elements);

    // Also remove newlines to keep the output concise.
    $markup = str_replace([
      "\r",
      "\n",
    ], '', $markup);
    return $markup;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
JuiceboxGallery::$id protected property Base properties to contain structured gallery data.
JuiceboxGallery::$images protected property Store images data.
JuiceboxGallery::$options protected property Javascript library.
JuiceboxGallery::$settings protected property The settings property is specific to the logic used in this object.
JuiceboxGallery::addImage public function Add a new image to the gallery. Overrides JuiceboxGalleryInterface::addImage
JuiceboxGallery::addOption public function Add a new Juicebox configuration option to the gallery. Overrides JuiceboxGalleryInterface::addOption
JuiceboxGallery::filterMarkup protected function Filter markup for valid display in a Juicebox gallery.
JuiceboxGallery::getChecksum public function Get a gallery checksum. Overrides JuiceboxGalleryInterface::getChecksum
JuiceboxGallery::getId public function Getter method for the gallery ID. Overrides JuiceboxGalleryInterface::getId
JuiceboxGallery::getImages public function Getter method for the gallery images. Overrides JuiceboxGalleryInterface::getImages
JuiceboxGallery::getJavascriptVars public function Get the variables needed to instantiate a new JS Juicebox. Overrides JuiceboxGalleryInterface::getJavascriptVars
JuiceboxGallery::getOptions public function Getter method for the gallery options. Overrides JuiceboxGalleryInterface::getOptions
JuiceboxGallery::processAttributes protected function Process an attribute list for valid use in a Juicebox gallery.
JuiceboxGallery::removeImage public function Remove an image from the gallery. Overrides JuiceboxGalleryInterface::removeImage
JuiceboxGallery::removeOption public function Remove an option from the gallery. Overrides JuiceboxGalleryInterface::removeOption
JuiceboxGallery::renderEmbed public function Get the embed code for Juicebox gallery once images and options are added. Overrides JuiceboxGalleryInterface::renderEmbed
JuiceboxGallery::renderJavascript public function Get javascript code for Juicebox gallery once images and options are added. Overrides JuiceboxGalleryInterface::renderJavascript
JuiceboxGallery::renderXml public function Render the XML for Juicebox gallery once images and options are added. Overrides JuiceboxGalleryInterface::renderXml
JuiceboxGallery::setId public function Setter method for the gallery ID (if was not passed in constructor). Overrides JuiceboxGalleryInterface::setId
JuiceboxGallery::updateImage public function Update an existing image in the gallery. Overrides JuiceboxGalleryInterface::updateImage
JuiceboxGallery::__construct public function Constructor.