<?php
/**
 * @file
 * A light-weight, customizable image gallery plugin for Drupal based on jQuery
 */

/**
 * Implements hook_menu().
 */
function galleria_menu() {
  $items = array();

  $items['admin/config/media/galleria'] = array(
    'title' => 'Galleria',
    'description' => 'Configure Galleria image galleries.',
    'page callback' => 'galleria_page_optionset_list',
    'access arguments' => array('administer site configuration'),
    'file' => 'includes/galleria.admin.inc',
  );
  $items['admin/config/media/galleria/list'] = array(
    'title' => 'Option sets',
    'description' => 'List the current Galleria option sets.',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 1,
  );
  $items['admin/config/media/galleria/add'] = array(
    'title' => 'Add option set',
    'description' => 'Add a new Galleria option set.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('galleria_form_optionset_add'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_LOCAL_ACTION,
    'weight' => 1,
    'file' => 'includes/galleria.admin.inc',
  );
  $items['admin/config/media/galleria/edit/%galleria_optionset'] = array(
    'title' => 'Edit option set',
    'description' => 'Configure an option set.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('galleria_form_optionset_edit', 5),
    'access arguments' => array('administer site configuration'),
    'file' => 'includes/galleria.admin.inc',
  );
  $items['admin/config/media/galleria/delete/%galleria_optionset'] = array(
    'title' => 'Delete option set',
    'description' => 'Delete an option set.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('galleria_optionset_form_delete', 5),
    'access arguments' => array('administer site configuration'),
    'file' => 'includes/galleria.admin.inc',
  );
  $items['admin/config/media/galleria/advanced'] = array(
    'title' => 'Advanced settings',
    'description' => 'Configure the advanced Galleria module settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('galleria_form_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
    'file' => 'includes/galleria.admin.inc',
  );

  return $items;
}

/**
 * Implements hook_help().
 */
function galleria_help($path, $arg) {
  switch ($path) {
    case 'admin/config/media/galleria/edit/%':
      return
          '<p>'
        . t('An <em>option set</em> defines exactly how a Galleria image gallery looks like on your site. '
          . 'It is s a combination of <a href="@styles">image styles</a> for the various image sizes, Galleria theme and options.', array('@styles' => url('admin/config/media/image-styles'))). '<br />'
        . t('For a full documentation of all options, refer to the official <a href="@docs">Galleria documentation</a>.', array('@docs' => url('http://galleria.aino.se/docs/1.2/options/')))
        . '</p>';

    case 'admin/config/media/galleria/advanced':
      return '<p>' . t('This page lists some automatically detected files and folders. To improve performance, they are cached until the files get deleted.<br />'
                   . 'If the module behaves strangely, try to clear the cached values below.') . '</p>';
  }
}

/**
 * Implements hook_theme().
 */
function galleria_theme() {
  return array(
   'galleria_container' => array(
      'variables' => array('items' => array(), 'settings' => array()),
      'template' => 'theme/galleria-container',
      'file' => 'theme/theme.inc',
    ),
    'galleria_form_table' => array(
      'render element' => 'form',
      'file' => 'includes/galleria.admin.inc',
    ),
  );
}

/**
 * Implements hook_image_default_styles().
 *
 * Provides default image style presets that can be overridden by site administrators.
 */
function galleria_image_default_styles() {
  $styles = array();

  // image preset for the big image
  $styles['galleria_zoom'] = array(
    'effects' => array(
      array(
        'name' => 'image_scale',
        'data' => array('width' => 450, 'height' => 300, 'upscale' => 1),
        'weight' => 0,
      ),
    )
  );

  // image preset for thumbnails
  $styles['galleria_thumb'] = array(
    'effects' => array(
      array(
        'name' => 'image_scale',
        'data' => array('width' => 50, 'height' => 40, 'upscale' => 1),
        'weight' => 0,
      ),
    )
  );

  return $styles;
}

/**
 * Implements hook_views_api().
 *
 * This tells drupal that there is Views integration file named
 * galleria.views.inc
 */
function galleria_views_api() {
  return array(
    'api' => 3,
  );
}


/**
 * Fetches all option sets from the database and returns them as an associative array.
 */
function galleria_optionsets() {
  $optionsets = db_query("SELECT * FROM {galleria_optionset}")->fetchAllAssoc('name', PDO::FETCH_ASSOC);
  foreach ($optionsets as &$optionset) {
    $optionset['options'] = empty($optionset['options']) ? array() : unserialize($optionset['options']);
    $optionset['plugins'] = empty($optionset['plugins']) ? array() : unserialize($optionset['plugins']);
  }
  return $optionsets;
}

/**
 * Fetches the given option set and returns it as an associative array or FALSE, if no set could be found.
 */
function galleria_optionset_load($optionset_name) {
  $optionset = db_query("SELECT * FROM {galleria_optionset} WHERE name = :name", array(':name' => $optionset_name))->fetchAssoc();
  if ($optionset !== FALSE) {
    $optionset['options'] = empty($optionset['options']) ? array() : unserialize($optionset['options']);
    $optionset['plugins'] = empty($optionset['plugins']) ? array() : unserialize($optionset['plugins']);
  }
  return $optionset;
}

/**
 * Checks whether an option set with the given name already exists.
 */
function galleria_optionset_exists($optionset_name) {
  return (galleria_optionset_load($optionset_name) !== FALSE);
}

/**
 * Saves the given option set to the database.
 * Set the $new flag if this set has not been written before.
 */
function galleria_optionset_save($optionset, $new = FALSE) {
  $optionset += array(
    'title' => $optionset['name'],
    'options' => array(),
  );

  if ($new) {
    drupal_write_record('galleria_optionset', $optionset);
  }
  else {
    drupal_write_record('galleria_optionset', $optionset, 'name');
  }
  return $optionset;
}

/**
 * Deletes the given option set from the database.
 */
function galleria_optionset_delete($optionset) {
  db_delete('galleria_optionset')->condition('name', $optionset['name'])->execute();
  // TODO: update formatters?
  return TRUE;
}


/*
 * Returns the JavaScript file of the Galleria core.
 * Uses a cached filename until this file gets deleted or the cache gets cleared.
 */
function galleria_get_library_file() {
  $cache = cache_get('galleria_lib_file');
  if (($cache !== FALSE) && file_exists($cache->data))
    return $cache->data;

  // Search for library file
  $libpath = libraries_get_path('galleria');

  // Seach for minimized files first.
  // Sort the found files to use the newest version if there's more than one.
  $js = glob($libpath . '/galleria-*.min.js');
  if (count($js) == 0)
    $js = glob($libpath . '/galleria-*.js');

  if (count($js) > 0) {
    rsort($js);
    cache_set('galleria_lib_file', $js[0]);
    return $js[0];
  }
  else {
    // Could not find JavaScript library
    return FALSE;
  }
}

/**
 * Searches for available themes inside the Galleria folder.
 * The list of themes is cached for performance reasons.
 */
function galleria_get_themes($nocache = FALSE) {
  if (!$nocache && (($themes = cache_get('galleria_themes')) !== FALSE))
    return $themes->data;

  $themes = array();

  // Search for theme folders
  $path = libraries_get_path('galleria') . '/themes/';

  if (is_dir($path) && (($path_handle = opendir($path)) !== FALSE)) {
    while (($theme = readdir($path_handle)) !== FALSE) {

      if (!is_dir($path . $theme) || $theme[0] == '.')
        continue;

      // Search for the theme JavaScript file, minified version first
      $js = glob($path . $theme . '/*.min.js');
      if (count($js) == 0)
        $js = glob($path . $theme . '/*.js');

      // Sort by filename to (hopefully) get the newest version.
      if (count($js) > 0) {
        rsort($js);
        $themes[$theme] = $js[0];
      }
    }
    closedir($path_handle);

    cache_set('galleria_themes', $themes);
  }

  return $themes;
}

/**
 * Returns the JavaScript file of the given theme, or FALSE if it could not be found.
 */
function galleria_get_theme_file($theme) {
  $themes = galleria_get_themes();
  return isset($themes[$theme]) ? $themes[$theme] : FALSE;
}


/**
 * Searches for available plugins inside the Galleria folder.
 * The list of plugins is cached for performance reasons.
 */
function galleria_get_plugins($nocache = FALSE) {
  if (!$nocache && (($plugins = cache_get('galleria_plugins')) !== FALSE))
    return $plugins->data;

  $plugins = array();

  // Search for theme folders
  $path = libraries_get_path('galleria') . '/plugins/';

  if (is_dir($path) && (($path_handle = opendir($path)) !== FALSE)) {
    while (($plugin = readdir($path_handle)) !== FALSE) {

      if (!is_dir($path . $plugin) || $plugin[0] == '.')
        continue;

      // Search for the theme JavaScript file, minified version first
      $js = glob($path . $plugin . '/*.min.js');
      if (count($js) == 0)
        $js = glob($path . $plugin . '/*.js');

      // Sort by filename to (hopefully) get the newest version.
      if (count($js) > 0) {
        rsort($js);
        $plugins[$plugin] = $js[0];
      }
    }
    closedir($path_handle);

    cache_set('galleria_plugins', $plugins);
  }

  return $plugins;
}

/**
 * Returns the JavaScript file of the given plugin, or FALSE if it could not be found.
 */
function galleria_get_plugin_file($plugin) {
  $plugins = galleria_get_plugins();
  return isset($plugins[$plugin]) ? $plugins[$plugin] : FALSE;
}


/**
 * Implements hook_field_formatter_info().
 *
 * Adds the Galleria format option within the manage display form of
 * of an image field.
 */
function galleria_field_formatter_info() {
  return array(
    'galleria' => array(
      'label' => t('Galleria'),
      'field types' => array('image', 'media', 'node_reference', 'file'),
      'settings' => array(
        'optionset' => 'default',
        'title_field' => '',
        'alt_field' => '',
        'referenced_fields' => array(),
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 *
 * Provides display settings form within the manage display page of
 * an image field with formatter galleria.
 */
function galleria_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  $form = array(
    '#tree' => TRUE,
  );

  // Show select box for the option set
  $optionsets = array();
  foreach (galleria_optionsets() as $name => $optionset) {
    $optionsets[$name] = check_plain($optionset['title']);
  }
  $form['optionset'] = array(
    '#title' => t('Option set'),
    '#type' => 'select',
    '#options' => $optionsets,
    '#default_value' => $settings['optionset'],
  );

  if ($field['type'] == 'file') {
    // Provide alternate settings to allow selection of caption and alt tags.
    $bundles = field_info_instances('file');
    // Determine if there are additional fields on the image instance.
    if (isset($bundles['image']) && !empty($bundles['image'])) {
      $options = array('' => t('None'));
      foreach ($bundles['image'] as $field_name => $field_details) {
        $options[$field_name] = $field_details['label'];
      }
      $form['alt_field'] = array(
        '#title' => t('Alt field'),
        '#description' => t('Select an optional field to draw alt tags from.'),
        '#type' => 'select',
        '#options' => $options,
        '#default_value' => isset($settings['alt_field']) ? $settings['alt_field'] : ''
      );
      $form['title_field'] = array(
        '#description' => t('Select an optional field to draw images titles from.'),
        '#title' => t('Title field'),
        '#type' => 'select',
        '#options' => $options,
        '#default_value' => isset($settings['title_field']) ? $settings['title_field'] : ''
      );
    }
    
  }

  // Show select box for image fields if we're formatting a node_reference field
  if ($field['type'] == 'node_reference') {
    // Find all image fields
    $referenceable_types = array_filter($field['settings']['referenceable_types']);
    foreach ($referenceable_types as $referenceable_type) {
      $image_fields = array();
      foreach (field_info_instances('node', $referenceable_type) as $field_name => $field_instance) {
        $field = field_info_field($field_name);
        if (($field['type'] == 'image') || ($field['type'] == 'media')) {
          $image_fields[$field_name] = $field_instance['label'];
        }
      }

      if (!empty($image_fields)) {
        $form['referenced_fields'][$referenceable_type] = array(
          '#type' => 'checkboxes',
          '#title' => node_type_get_name($referenceable_type),
          '#options' => $image_fields,
          '#default_value' => isset($settings['referenced_fields'][$referenceable_type]) ? $settings['referenced_fields'][$referenceable_type] : array(),
        );
      }
    }

    if (count($form['referenced_fields']) == 0) {
      drupal_set_message(t('The referenced node type(s) does not contain any valid image fields.'), 'error');
    }
    else {
      $form['referenced_fields'] += array(
        '#type' => 'fieldset',
        '#title' => t('Image source fields'),
        '#description' => t('Select the image fields of the referenced content types to use as the source for Galleria.'),
      );
    }
  }

  return $form;
}

/**
 * Implements hook_field_formatter_settings_summary().
 *
 * Displays the summary of the set options of a Galleria formatted image field
 */
function galleria_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  $summary = array();

  // Optionset
  $optionset = t('Default settings');
  if (!empty($settings['optionset'])) {
    $o = galleria_optionset_load($settings['optionset']);
    if ($o !== FALSE) {
      $optionset = $o['title'];
    }
  }
  $summary[] = t('Option set: %optionset', array('%optionset' => $optionset));

  // Summary for file fields.
  if ($field['type'] == 'file') {
    // Prepare options list.
    if (!empty($settings['alt_field']) ||
        !empty($settings['title_field'])) {
      $bundles = field_info_instances('file');
      // Determine if there are additional fields on the image instance.
      if (isset($bundles['image']) && !empty($bundles['image'])) {
        $options = array('' => t('None'));
        foreach ($bundles['image'] as $field_name => $field_details) {
          $options[$field_name] = $field_details['label'];
        }
      }
    }
    if (!empty($settings['alt_field'])) {
      $summary[] = t('Alt field: @title', array('@title' => $options[$settings['alt_field']]));
    }
    if (!empty($settings['title_field'])) {
      $summary[] = t('Title field: @title', array('@title' => $options[$settings['title_field']]));
    }
  }

  // For node_reference fields: Referenced image fields
  if ($field['type'] == 'node_reference') {
    $referenced_fields = array();
    foreach ($settings['referenced_fields'] as $node_type => $fields) {
      $fields = array_keys(array_filter($fields));
      if (!empty($fields)) {
        foreach ($fields as &$field) {
          $field_info = field_info_instance('node', $field, $node_type);
          $field = $field_info['label'];
        }
        $referenced_fields[] = node_type_get_name($node_type) . ': ' . implode(', ', $fields);
      }
    }
    $referenced_fields = empty($referenced_fields) ? t('All') : implode('; ', $referenced_fields);
    $summary[] = t('Referenced image fields: %fields', array('%fields' => $referenced_fields));
  }

  return '<p>' . implode("</p>\n<p>", $summary) . '</p>';
}


/**
 * Implements hook_field_formatter_prepare_view().
 * 
 * Loads the attached nodes for node_reference fields so we can later access the image fields.
 */
function galleria_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
  if ($field['type'] == 'node_reference') {
    // Collect ids to load. 
    $ids = array();
    foreach ($displays as $id => $display) {
      foreach ($items[$id] as $item) {
        if ($item['access']) {
          $ids[$item['nid']] = $item['nid'];
        }
      }
    }

    // Load the nodes.
    $nodes = node_load_multiple($ids);
    field_attach_prepare_view('node', $nodes, 'default', $langcode);
    entity_prepare_view('node', $nodes, $langcode);

    // Add the loaded nodes to the items.
    foreach ($displays as $id => $display) {
      foreach ($items[$id] as &$item) {
        if ($item['access']) {
          $item['node'] = $nodes[$item['nid']];
        }
      }
    }

  }
}

/**
 * Implements hook_field_formatter_view().
 *
 * Prepares a renderable array of images and adds the neccessary JS and CSS
 */
function galleria_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  if ($field['type'] == 'media') {
    $items = galleria_prepare_media_images($items);
  }
  elseif ($field['type'] == 'file') {
    $items = galleria_prepare_file_images($items, $display['settings'], $langcode);
  }
  elseif ($field['type'] == 'node_reference') {
    // Cleanup referenced_fields array
    $referenced_fields = &$display['settings']['referenced_fields'];
    foreach ($referenced_fields as &$field) {
      $field = array_filter($field);
    }
    $referenced_fields = array_filter($referenced_fields);

    // Collect items
    $images = array();
    foreach ($items as $item) {
      $node = $item['node'];
      foreach (field_info_instances('node', $node->type) as $field_name => $field_instance) {
        $field_info = field_info_field($field_name);
        if ($field_info['type'] == 'image' || $field_info['type'] == 'media') {
          if (empty($referenced_fields) || !empty($referenced_fields[$node->type][$field_name])) {
            $lang = field_language('node', $node, $field_name, $langcode);
            $node_items = $node->{$field_name}[$lang];

            if ($field_info['type'] == 'media') {
              $node_items = galleria_prepare_media_images($node_items);
            }

            $images = array_merge($images, $node_items);
          }
        }
      }
    }
    $items = $images;
  }

  $element = array();
  if (count($items) > 0) {
    $element[] = array(
      '#theme' => 'galleria_container',
      '#items' => $items,
      '#settings' => $display['settings'],
    );
  }
  return $element;
}

/**
 * Util function to parse media $items and return images in format expected by
 * galleria formatter.
 *
 * @param array $items
 *   Array of field items.
 */
function galleria_prepare_media_images($items) {
  $image_items = array();
  foreach ($items as $item) {
    if ($item['file']->type == 'image') {
      $file = (array) $item['file'];
      $file += array('alt' => '', 'title' => '');
      $image_items[] = $file;
    }
  }
  return $image_items;
}

/**
 * Util function to parse file $items and return images in format expected by
 * galleria formatter.
 *
 * @param array $items
 *   Array of field items.
 * @param array $settings
 *   Array of settings
 * @param string $langcode
 *   Language code of the field
 */
function galleria_prepare_file_images($items, $settings, $langcode) {
  $image_fids = array();
  $alt_field = empty($settings['alt_field']) ? FALSE : $settings['alt_field'];
  $title_field = empty($settings['title_field']) ? FALSE : $settings['title_field'];
  // First pass, fetch the fids. We pass twice so we can minimize calls to
  // entity_load which is expensive to call multiple times.
  foreach ($items as $delta => $item) {
    if ($item['type'] == 'image') {
      $image_fids[] = $item['fid'];
      // Set a sensible default.
      $items[$delta]['alt'] = $items[$delta]['title'] = '';
    }
    else {
      // Galleria only handles images.
      unset($items['key']);
    }
  }
  $file_entities = entity_load('file', $image_fids);
  // Second pass, set the alt/title tag.
  foreach ($items as $delta => $item) {
    if ($item['type'] == 'image') {
      $file_entity = $file_entities[$item['fid']];
      if ($alt_field) {
        $alt_field_items = field_get_items('file', $file_entity, $alt_field, $langcode);
        if (!empty($alt_field_items[0]['safe_value'])) {
          $items[$delta]['alt'] = $alt_field_items[0]['safe_value'];
        }
      }
      if ($title_field) {
        $title_field_items = field_get_items('file', $file_entity, $title_field, $langcode);
        if (!empty($title_field_items[0]['safe_value'])) {
          $items[$delta]['title'] = $title_field_items[0]['safe_value'];
        }
      }
    }
  }
  return $items;
}

/**
 * This function loads the required JavaScripts and settings for a Galleria instance.
 */
function galleria_add_js($id, $optionset) {
  // Static array to remember which scripts are already attached.
  $cache = &drupal_static(__FUNCTION__, array());

  // Library JavaScript
  //   Cache this filename to prevent multiple file_exists() calls.
  if (!isset($cache['lib'])) {
    $lib = galleria_get_library_file();
    if ($lib === FALSE) {
      drupal_set_message(t('The Galleria JavaScript file was not found in its path. Please refer to README.txt for installation instructions.'), 'error');
      return;
    }
    $cache['lib'] = $lib;
  }
  drupal_add_js($cache['lib'], array('group' => JS_LIBRARY));

  // Theme
  //   As it's only possible to use one theme per page, use the first one we get for everything.
  if (empty($cache['theme'])) {
    $theme = (!empty($optionset['theme'])) ? $optionset['theme'] : 'classic';
    $theme_file = galleria_get_theme_file($theme);
    if (($theme_file === FALSE) && ($theme !== 'classic')) {
      // Try to fall back to classic theme, the other one might have been deleted.
      $theme_file = galleria_get_theme_file('classic');
    }
    $cache['theme'] = $theme_file;
  }

  // Plugins
  $plugins = empty($optionset['plugins']) ? array() : $optionset['plugins'];
  foreach ($plugins as $plugin) {
    if (empty($cache['plugins'][$plugin])) {
      $plugin_file = galleria_get_plugin_file($plugin);
      if ($plugin_file !== FALSE) {
        $cache['plugins'][$plugin] = $plugin_file;
        drupal_add_js($plugin_file);
      }
    }
  }

  // JavaScript settings
  $js_settings = array(
    'themepath' => file_create_url($cache['theme']),
    'optionsets' => array(
      $optionset['name'] => $optionset['options'],
    ),
    'instances' => array(
      'galleria-' . $id => $optionset['name'],
    ),
  );
  drupal_add_js(array('galleria' => $js_settings), 'setting');

  // Loader JavaScript
  drupal_add_js(drupal_get_path('module', 'galleria') . '/js/galleria.load.js');
}