Skip to content
media_gallery.module 62.1 KiB
Newer Older
<?php

/**
 * @file
 * Lets users create galleries made up of media items.
 */

require_once(dirname(__FILE__) . '/media_gallery.fields.inc');
require_once(dirname(__FILE__) . '/fields_rsi_prevention.inc');

/**
 * The pager element to use for paging through the media items in a gallery.
 *
 * We avoid using the default pager for now because there is too much risk of
 * it colliding with other pagers initialized for the same page (for example,
 * by the comment module, if the gallery node manages to get comment loading or
 * display functions called on it).
 */
define('MEDIA_GALLERY_PAGER_ELEMENT', 1);

/**
 * Helper function to return the view modes used by this module for displaying files in gallery context.
 */
function media_gallery_file_view_modes() {
  return array(
    'media_gallery_thumbnail' => t('Gallery thumbnail'),
    'media_gallery_lightbox' => t('Gallery lightbox'),
    'media_gallery_detail' => t('Gallery detail'),
    'media_gallery_block_thumbnail' => t('Gallery block thumbnail'),
    'media_gallery_collection_thumbnail' => t('Gallery collection thumbnail'),
  );
}

/**
 * Implements hook_menu().
 */
function media_gallery_menu() {
  $items['admin/config/media/galleries'] = array(
    'title' => 'Gallery settings',
    'description' => 'Configure settings for the "All galleries" page.',
    'access arguments' => array('administer media galleries'),
    'page callback' => 'media_gallery_admin_settings',
    'file' => 'media_gallery.admin.inc',
  );
  $items['media-gallery/sort/collection/%taxonomy_term/%'] = array(
    'title' => 'Gallery sort callback',
    'access callback' => 'media_gallery_edit_access_ajax',
    'access arguments' => array('collection', 3, 4),
    'page callback' => 'media_gallery_ajax_sort',
    'page arguments' => array('collection', 3),
    'file' => 'media_gallery.pages.inc',
  );
  $items['media-gallery/sort/gallery/%node/%'] = array(
    'title' => 'Gallery sort callback',
    'access callback' => 'media_gallery_edit_access_ajax',
    'access arguments' => array('gallery', 3, 4),
    'page callback' => 'media_gallery_ajax_sort',
    'page arguments' => array('gallery', 3),
    'file' => 'media_gallery.pages.inc',
  );
  $items['media-gallery/detail/%node/%file'] = array(
    'page callback' => 'media_gallery_detail_page',
    'page arguments' => array(2, 3),
    'access callback' => 'node_access',
    'access arguments' => array('view', 2),
    'file' => 'media_gallery.pages.inc',
  );
  $items['media-gallery/detail/%node/%file/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  // An in-gallery-context version of media/%file/edit.
  $items['media-gallery/detail/%node/%file/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'media_gallery_media_page_edit',
    'page arguments'  => array(2, 3),
    'access callback' => 'media_access',
    'access arguments' => array('edit'),
    'weight' => 0,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'file' => 'media_gallery.pages.inc',
  );
  $items['media-gallery/detail/%node/%file/remove'] = array(
    'title' => 'Remove',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('media_gallery_remove_item_form', 2, 3),
    'access callback' => 'media_gallery_remove_item_access',
    'access arguments' => array(2, 3),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'file' => 'media_gallery.pages.inc',
  );
  $items['media-gallery/lightbox/%node/%file'] = array(
    'page callback' => 'media_gallery_lightbox_page',
    'page arguments' => array(2, 3),
    'access callback' => 'node_access',
    'access arguments' => array('view', 2),
    'file' => 'media_gallery.pages.inc',
    'delivery callback' => 'media_gallery_lightbox_page_deliver',
  );
  $items['media-gallery/add-images/%node/%'] = array(
    'access callback' => 'media_gallery_edit_access_ajax',
    'access arguments' => array('gallery', 2, 3),
    'page callback' => 'media_gallery_add_images',
    'page arguments' => array(2),
    'file' => 'media_gallery.pages.inc',
  );
  // An in-gallery-context version of media/%media_multi/edit.
  $items['node/%node/multiedit'] = array(
Jacob Singh's avatar
Jacob Singh committed
    'title' => 'Edit media',
    'page callback' => 'media_gallery_media_page_multiedit',
    'page arguments' => array(1),
    'access callback' => 'media_gallery_multiedit_access',
    'access arguments' => array(1),
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'type' => MENU_LOCAL_TASK,
    'file' => 'media_gallery.pages.inc',
  );

  // @todo Move to Media module once it is ready.
    'title' => 'Download',
    'page callback' => 'media_download',
    'page arguments'  => array(1),
    'access callback' => 'media_access',
    'access arguments' => array('view'),
    'type' => MENU_CALLBACK,
    'file' => 'media.pages.inc',
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function media_gallery_menu_alter(&$items) {
  // Take over taxonomy term list pages by substituting our own callback.
  // TODO: Use hook_entity_info_alter() to change the entity uri callback for
  // gallery collections only.
  $items['taxonomy/term/%taxonomy_term']['page callback'] = 'media_gallery_list_galleries';
  $items['taxonomy/term/%taxonomy_term']['file'] = 'media_gallery.pages.inc';
  $items['taxonomy/term/%taxonomy_term']['module'] = 'media_gallery';

  // If you're viewing a media item in context somewhere (which we do inside
  // media gallery nodes), that means it's being used on your site, which means
  // you won't be allowed to delete it anyway. Therefore, do not show
  // contextual links there.
  // @todo Perhaps this should be changed in the media module itself?
  $items['media/%file/delete']['context'] = MENU_CONTEXT_PAGE;
}

/**
 * Implements hook_admin_paths().
 */
function media_gallery_admin_paths() {
  $paths = array(
    'media-gallery/detail/*/*/edit' => TRUE,
    'media-gallery/detail/*/*/remove' => TRUE,
    'node/*/multiedit' => TRUE,
  );
  return $paths;
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function media_gallery_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  // Rename the "Edit" tab on gallery nodes to "Edit gallery".
  if (($node = menu_get_object()) && isset($node->type) && $node->type == 'media_gallery' && !empty($data['tabs'])) {
    $tabs = &$data['tabs'][0]['output'];
    foreach ($tabs as &$tab) {
      if (isset($tab['#link']['path']) && $tab['#link']['path'] == 'node/%/edit') {
        $tab['#link']['title'] = t('Edit gallery');
      }
    }
  }

  // Rename the "Edit" tab on the "All Galleries" taxonomy term to "Edit all
  // galleries" and point it to our configuration page.
  // @todo: Once we have additional gallery-related taxonomy terms and
  //   http://drupal.org/node/678592 is committed to core (so the term edit
  //   pages show the correct admin theme) we'll do something different here,
  //   perhaps not even alter anything at all.
  if (($term = menu_get_object('taxonomy_term', 2)) && isset($term->vid) && $term->vid == variable_get('media_gallery_collection_vid')) {
    $tabs = &$data['tabs'][0]['output'];
    foreach ($tabs as &$tab) {
      if (isset($tab['#link']['path']) && $tab['#link']['path'] == 'taxonomy/term/%/edit') {
        $tab['#link']['href'] = 'admin/config/media/galleries';
        $tab['#link']['title'] = t('Edit all galleries');
      }
    }
  }
}

/**
 * Implements hook_node_load().
 */
function media_gallery_node_load($nodes, $types) {
  // Store a copy of the media_gallery_media field before mucking with it in
  // media_gallery_view(). We use hook_node_load() instead of hook_load(),
  // because the latter runs before fields are loaded.
  if (in_array('media_gallery', $types)) {
    foreach ($nodes as $node) {
      if ($node->type == 'media_gallery') {
        $node->media_gallery_media_original = $node->media_gallery_media;
      }
    }
  }
}

/**
 * Implements hook_view().
 */
function media_gallery_view($node, $view_mode) {
  // Add display elements and resources for users who can edit the gallery.
  if (node_access('update', $node)) {
    // Add the "Add images" link, themed as a local action. Note that this
    // element is listed in hook_field_extra_fields(), so whether or not it
    // will *actually* be displayed in the current view mode depends on the
    // site's configuration for the corresponding pseudo-field.
Jacob Singh's avatar
Jacob Singh committed
    $node->content['add_media_link'] = array(
      '#theme' => 'menu_local_action',
      '#link' => array(
Jacob Singh's avatar
Jacob Singh committed
        'title' => t('Add media'),
        'href' => 'media/browser',
        'localized_options' => array(
          'query' => array('render' => 'media-popup'),
          'attributes' => array(
            'class' => 'media-gallery-add launcher',
          ),
        ),
      ),
      // @todo Drupal could really use a theme_menu_local_actions() function...
      '#prefix' => '<ul class="field action-links">',
      '#suffix' => '</ul>',
    );

    // Enable the "Add media" link to launch the media browser.
    module_load_include('inc', 'media', 'includes/media.browser');
    media_attach_browser_js($node->content['add_media_link']);
    $node->content['add_media_link']['#attached']['js'][] = drupal_get_path('module', 'media_gallery') . '/media_gallery.addimage.js';

    // These JS settings are used by the "add media" link but some are also
    // shared by the drag-and-drop code below.
    $instance = field_info_instance('node', 'media_gallery_media', $node->type);
    $token = drupal_get_token('media_gallery');
      'mediaGalleryAddImagesUrl' => url('media-gallery/add-images/' . $node->nid . '/' . $token),
      'mediaGallerySortGalleryUrl' => url('media-gallery/sort/gallery/' . $node->nid . '/' . $token),
      'mediaGalleryAllowedMediaTypes' => array_filter($instance['widget']['settings']['allowed_types']),
    );

    // When viewing the full node, add front-end resources for drag-and-drop
    // sorting.
    if ($view_mode == 'full') {
      drupal_add_css(drupal_get_path('module', 'media_gallery') . '/media_gallery.dragdrop.css');
      drupal_add_library('system', 'ui.sortable');
      drupal_add_library('system', 'jquery.bbq');
      drupal_add_js(drupal_get_path('module', 'media_gallery') . '/media_gallery.dragdrop.js');
      drupal_add_js($gallery_js_settings, array('type' => 'setting'));
    }
    else {
Jacob Singh's avatar
Jacob Singh committed
      // Otherwise, attach the setting to the "add media" link, as per above.
      $node->content['add_media_link']['#attached']['js'][] = array(
Jacob Singh's avatar
Jacob Singh committed
  // For the teaser, only the first thumbnail needs to be displayed, so remove the
  // rest from the node's field data, so that the field formatter doesn't waste
  // time building the render structure for items that won't be shown.
  if ($view_mode == 'teaser') {
    if (!empty($node->media_gallery_media[LANGUAGE_NONE])) {
      $first_item = array_shift($node->media_gallery_media[LANGUAGE_NONE]);
      $node->media_gallery_media[LANGUAGE_NONE] = array($first_item);
    }
  }
  // For the full display, implement pagination.
Jacob Singh's avatar
Jacob Singh committed
  elseif ($view_mode == 'full' || $view_mode == 'media_gallery_block') {
    $full = $view_mode == 'full' ? TRUE : FALSE;
    if (!empty($node->media_gallery_media)) {
      $media = $node->media_gallery_media[LANGUAGE_NONE];
    // Only display the requested number of media items per page.
Jacob Singh's avatar
Jacob Singh committed
    $columns = $full ? $node->media_gallery_columns[LANGUAGE_NONE][0]['value'] : $node->media_gallery_block_columns[LANGUAGE_NONE][0]['value'];
    $rows = $full ? $node->media_gallery_rows[LANGUAGE_NONE][0]['value'] : $node->media_gallery_block_rows[LANGUAGE_NONE][0]['value'];
Jacob Singh's avatar
Jacob Singh committed
    $deltas = array_keys($media);
    $total = count($deltas);

    // Initialize the pager and find out what page we are viewing.
Jacob Singh's avatar
Jacob Singh committed
    $page = $full ? pager_default_initialize($total, $number_per_page, MEDIA_GALLERY_PAGER_ELEMENT) : 0;

    // Deny access to all media items that aren't on this page.
    $min_on_page = $number_per_page * $page;
    $max_on_page = $number_per_page * ($page + 1) - 1;
Jacob Singh's avatar
Jacob Singh committed
    $pre_links = array();
    $post_links = array();
Jacob Singh's avatar
Jacob Singh committed
      $item = $media[$delta];
      $fid = _media_gallery_get_media_fid($item);
      if ($key < $min_on_page) {
        $pre_links[$key] = array(
Jacob Singh's avatar
Jacob Singh committed
          'title' => '',
          'href' => 'media-gallery/detail/' . $node->nid . '/' . $fid,
          'attributes' => array('class' => array('colorbox-supplemental-link pre')),
Jacob Singh's avatar
Jacob Singh committed
        unset($media[$delta]);
      }
      elseif ($key > $max_on_page) {
        $post_links[$key] = array(
Jacob Singh's avatar
Jacob Singh committed
          'title' => '',
          'href' => 'media-gallery/detail/' . $node->nid . '/' . $fid,
          'attributes' => array('class' => array('colorbox-supplemental-link post')),
Jacob Singh's avatar
Jacob Singh committed
        unset($media[$delta]);
      }
    }

    // Field rendering requires sequential deltas, so re-key.
    // @todo Open a Drupal core issue about this.
    
    if ($media) {
      $node->media_gallery_media[LANGUAGE_NONE] = array_values($media);
    }
    else {
      $node->media_gallery_media[LANGUAGE_NONE] = array();  
    }

    // Create a set of dummy links to media items that don't appear on this
    // page, so colorbox can link to them in the slideshow.
Jacob Singh's avatar
Jacob Singh committed
    // @todo If the gallery contains 1000 media, then rendering each link takes
    //   extra server-side time, extra network time to transfer the markup, and
    //   extra client-side time to initialize the DOM. Performance can likely be
    //   significantly improved if we only send the fids to Drupal.settings, and
    //   add JavaScript code to make that information usable by Colorbox.
    $node->content['colorbox_links_pre'] = array(
      '#theme' => 'links',
      '#links' => $pre_links,
      '#weight' => -20,
      '#attributes' => array('class' => array('colorbox-supplemental-links')),
    );
    $node->content['colorbox_links_post'] = array(
      '#theme' => 'links',
      '#links' => $post_links,
      '#weight' => 20,
      '#attributes' => array('class' => array('colorbox-supplemental-links')),
    );
    // Finally, display the pager, with a high weight so it appears at the
    // bottom.
Jacob Singh's avatar
Jacob Singh committed
    if ($full) {
      $node->content['media_gallery_pager'] = array(
        '#theme' => 'pager',
        '#element' => MEDIA_GALLERY_PAGER_ELEMENT,
        '#weight' => 2000,
      );
    }
  }
  return $node;
}

/**
 * Implements hook_field_extra_fields().
 */
function media_gallery_field_extra_fields() {
Jacob Singh's avatar
Jacob Singh committed
  // Allow the "Add media" link to be sorted with respect to the actual media
  // gallery fields.
  $extra['node']['media_gallery'] = array(
    'display' => array(
Jacob Singh's avatar
Jacob Singh committed
      'add_media_link' => array(
        'label' => t('"Add media" link'),
        'weight' => 1,
      ),
    ),
  );

  return $extra;
}

/**
 * Access callback for AJAX-based gallery editing operations.
Jacob Singh's avatar
Jacob Singh committed
 *
 * @param string $type
 *   The type of item being edited: 'gallery' for an individual gallery or
 *   'collection' for a gallery collection.
 * @param mixed $item
 *   For a media gallery, the $node object for that gallery; for gallery
 *   collections, the taxonomy term corresponding to the collection.
 * @param string $token
 *   A token from drupal_get_token.
 */
function media_gallery_edit_access_ajax($type, $item, $token) {
  if (!drupal_valid_token($token, 'media_gallery')) {
    return FALSE;
  }
  switch ($type) {
    case 'gallery':
      return node_access('update', $item);
      break;
    case 'collection':
      return user_access('administer media galleries');
      break;
    default:
      return FALSE;
  }
}

/**
 * Implements hook_theme().
 */
function media_gallery_theme() {
  return array(
    'media_gallery_collection' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_teaser' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_media_item_thumbnail' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
      'template' => 'media-gallery-media-item-thumbnail',
    ),
    'media_gallery_media_item_lightbox' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_media_item_detail' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_block_thumbnail' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_collection_thumbnail' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_download_link' => array(
      'variables' => array('file' => NULL, 'text' => NULL, 'options' => array()),
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_meta' => array(
Jacob Singh's avatar
Jacob Singh committed
      'variables' => array('display' => NULL, 'location' => NULL, 'title' => NULL, 'license' => NULL, 'description' => NULL),
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_item' => array(
      'variables' => array('image' => NULL, 'link_path' => NULL, 'classes' => NULL),
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_license' => array(
      'variables' => array('element' => NULL, 'color' => 'dark'),
      'file' => 'media_gallery.theme.inc',
    ),
    'media_gallery_file_field_inline' => array(
      'render element' => 'element',
      'file' => 'media_gallery.theme.inc',
    ),
  );
}

/**
 * Implements hook_permission().
 */
function media_gallery_permission() {
  return array(
    'administer media galleries' => array(
      'title' => t('Administer media galleries'),
    ),
  );
}

/**
 * Implements hook_node_info().
 */
function media_gallery_node_info() {
  return array(
    'media_gallery' => array(
      'name' => t('Gallery'),
      'base' => 'media_gallery',
Jacob Singh's avatar
Jacob Singh committed
      'description' => t('A flexible gallery of media.'),
      'help' => t('Create a gallery of thumbnails including custom display settings.  Once your gallery is saved, your media can be added.'),
    ),
  );
}

/**
 * Implements hook_update().
 */
function media_gallery_update($node) {
  // If the media gallery node is being saved and is configured to not provide
  // a block, remove all blocks associated with it from the database. The block
  // module might not be installed, so we need to check that the database table
  // exists before querying it.
  if (empty($node->media_gallery_expose_block[LANGUAGE_NONE][0]['value']) && db_table_exists('block')) {
    db_delete('block')
      ->condition('module', 'media_gallery')
      ->condition('delta', $node->nid)
      ->execute();
  }
}

/**
 * Implements hook_block_info().
 */
function media_gallery_block_info() {
  $blocks = array();

  // Define a block for each media gallery node that is configured to expose
  // one.
  $query = new EntityFieldQuery();
  $query->entityCondition('entity_type', 'node');
  $query->entityCondition('bundle', 'media_gallery');
  $query->fieldCondition('media_gallery_expose_block', 'value', 1);
  $result = $query->execute();
  if (!empty($result['node'])) {
    // There is no reason to waste going through a full node_load_multiple()
    // when we only need the titles.
    $nids = array_keys($result['node']);
    $node_titles = db_query("SELECT nid, title FROM {node} WHERE nid IN (:nids)", array(':nids' => $nids))->fetchAllKeyed();
    foreach ($node_titles as $nid => $title) {
      // The 'info' element is escaped on display, so we pass it through
      // unfiltered here.
      $blocks[$nid]['info'] = t('Recent gallery items: !title', array('!title' => $title));
      $blocks[$nid]['visibility'] = 0;
      $blocks[$nid]['pages'] = 'node/' . $nid . "\ngalleries";
    }
  }
Jacob Singh's avatar
Jacob Singh committed

Jacob Singh's avatar
Jacob Singh committed

/**
 * Implements hook_block_view().
 */
function media_gallery_block_view($delta = '') {
  $node = node_load($delta);
  if (empty($node->media_gallery_expose_block[LANGUAGE_NONE][0]['value'])) {
    // Bail out now if the node doesn't exist or if it is configured not to
    // display a block.
    $block['subject'] = NULL;
    $block['content'] = '';
  }
  elseif (empty($node->media_gallery_media_original)) {
    // Bail out now if there won't be any media items to show.
    $block['subject'] = check_plain($node->title);
    $block['content'] = t('No content available.');
  }
  else {
    // Collect an array of file IDs associated with this gallery. For
    // simplicity we will assume (here and below) that this is not a
    // multilingual field. Also note that the node may have been loaded and
    // viewed elsewhere on the page, in which case the 'media_gallery_media'
    // field was modified and does not contain what we want, so we have to go
    // back to the original field value set in hook_node_load() instead, and
    // also clone the node before changing it so our modifications do not
    // affect other places where it might be being viewed.
    $node = clone $node;
    $node->media_gallery_media = $node->media_gallery_media_original;
    $files = &$node->media_gallery_media[LANGUAGE_NONE];
    $gallery_fids = array();
    foreach ($files as $file) {
      $gallery_fids[] = _media_gallery_get_media_fid($file);
    }
    // Construct a list of file IDs that is limited to the specified number of
    // items and ordered by most recent; these are the files that will be
    // displayed in the block.
    $columns = !empty($node->media_gallery_block_columns[LANGUAGE_NONE][0]['value']) ? $node->media_gallery_block_columns[LANGUAGE_NONE][0]['value'] : 1;
    $rows = !empty($node->media_gallery_block_rows[LANGUAGE_NONE][0]['value']) ? $node->media_gallery_block_rows[LANGUAGE_NONE][0]['value'] : 1;
    $number_to_show = $columns * $rows;
    $block_fids = db_select('file_managed', 'f')
      ->fields('f', array('fid'))
      ->condition('fid', $gallery_fids, 'IN')
      ->orderBy('timestamp', 'DESC')
      ->range(0, $number_to_show)
      ->execute()
      ->fetchCol();
    // Before sorting, remove any items that will not display in the block.
    $fid_order = array_flip($block_fids);
    if ($number_to_show < count($files)) {
      foreach ($files as $key => $file) {
        if (!isset($fid_order[_media_gallery_get_media_fid($file)])) {
          unset($files[$key]);
        }
      }
    }
    // Prepare the sorting function with the list of file ID orders.
    _media_gallery_sort_by_recent(NULL, NULL, $fid_order);
    // Perform the sort.
    usort($files, '_media_gallery_sort_by_recent');
    // Display the block.
    $block['subject'] = check_plain($node->title);
    $block['content']['gallery'] = node_view($node, 'media_gallery_block');
    // Move the node's contextual links so that they display on the block
    // rather than the node (i.e., in the same dropdown as the "Configure
    // block" link). This is also required in order to properly integrate with
    // the code in media_gallery_contextual_links_view_alter().
    if (isset($block['content']['gallery']['#contextual_links'])) {
      $block['content']['#contextual_links'] = $block['content']['gallery']['#contextual_links'];
      unset($block['content']['gallery']['#contextual_links']);
    }
    $block['content']['more_link'] = array(
      '#theme' => 'more_link',
      '#url' => 'node/' . $node->nid,
      '#title' => t('Show the complete gallery'),
      '#weight' => 1000,
    );
  }

  return $block;
}

/**
 * Implements hook_block_configure().
 */
function media_gallery_block_configure($delta = '') {
  // Duplicate the form for configuring media gallery node fields, including
  // our module's custom alterations. We can't use drupal_get_form() here
  // because that will mess up the form submission, so for now we have to live
  // without getting any other module's alterations to this part of the node
  // form.
  $node = node_load($delta);
  $form = array();
  $form_state = array();
  field_attach_form('node', $node, $form, $form_state, $node->language);
  media_gallery_form_media_gallery_node_form_alter($form, $form_state);

  // Pull out only the parts of the node form that allow configuration of the
  // block settings; these are the ones we want to display.
Jacob Singh's avatar
Jacob Singh committed
  $block_settings = array('block' => $form['block']);

  // Store a record of all node fields that we will need to save when
  // hook_block_save() is called.
Jacob Singh's avatar
Jacob Singh committed
  $block_settings['block']['media_gallery_block_fields'] = array(
Jacob Singh's avatar
Jacob Singh committed
    '#value' => element_children($block_settings['block']),
  );

  // Don't allow people to destroy the block itself from the block
  // configuration page.
Jacob Singh's avatar
Jacob Singh committed
  $block_settings['block']['media_gallery_expose_block']['#access'] = FALSE;
Jacob Singh's avatar
Jacob Singh committed
  // Customize the fieldset display.
  $block_settings['block']['#collapsible'] = FALSE;
  $block_settings['block']['#title'] = t('Block settings');
  unset($block_settings['block']['#weight']);
Jacob Singh's avatar
Jacob Singh committed
  // Add the necessary attached JS and CSS.
  _media_gallery_attach_form_resources($block_settings['block']);

  return $block_settings;
}

/**
 * Implements hook_block_save().
 */
function media_gallery_block_save($delta = '', $edit = array()) {
  // Save the block-related media gallery fields on the node.
  $node = node_load($delta);
  foreach ($edit['media_gallery_block_fields'] as $field) {
    $node->{$field} = $edit[$field];
  }
  node_save($node);
}

/**
 * Implements hook_library().
 */
function media_gallery_library() {
Moloc's avatar
Moloc committed
  $colorbox_path = variable_get('media_gallery_library_path', FALSE);
  if ($colorbox_path === FALSE ) {
    $colorbox_path = module_exists('libraries') ? libraries_get_path('colorbox') : 'sites/all/libraries/colorbox';
  }
  else {
    $colorbox_path .= '/colorbox';
  }
  $stylesheet = variable_get('media_gallery_colorbox_stylesheet', 'example1');
  $libraries['colorbox'] = array(
    'title' => 'Colorbox',
    'website' => 'http://colorpowered.com/colorbox/',
    'version' => '1.3.9',
    'js' => array(
Moloc's avatar
Moloc committed
      $colorbox_path . '/colorbox/jquery.colorbox-min.js' => array(),
Moloc's avatar
Moloc committed
      $colorbox_path . '/' . $stylesheet . '/colorbox.css' => array(
        'type' => 'file',
        'media' => 'screen',
      ),
    ),
  );
  return $libraries;
}

/**
 * Helper function to sort media gallery items by an ordered list of file IDs.
 *
 * Call once with $set_fid_order set to an array of file orders, keyed by the
 * file ID, before performing the actual sort.
 */
function _media_gallery_sort_by_recent($a, $b, $set_fid_order = NULL) {
  $fid_order = &drupal_static(__FUNCTION__, array());
  // Stored the ordered list if this is a preparatory call.
  if (isset($set_fid_order)) {
    $fid_order = $set_fid_order;
    return;
  }
  // Otherwise, perform the sort.
  $a_order = $fid_order[_media_gallery_get_media_fid($a)];
  $b_order = $fid_order[_media_gallery_get_media_fid($b)];
  if ($a_order < $b_order) {
    return -1;
  }
  elseif ($b_order < $a_order) {
    return 1;
  }
  else {
    return 0;
  }
}

Jacob Singh's avatar
Jacob Singh committed
/**
 * Returns the number of files of each type attached to a media gallery node.
 */
function media_gallery_get_media_type_count($node, $media_field = 'media_gallery_media') {
  $fids = media_gallery_get_file_ids($node, $media_field);
  if (empty($fids)) {
    return array();
  }
  $query = db_select('file_managed', 'f');
  $type = $query->addField('f', 'type');
  $query->addExpression('COUNT(*)');
  $type_count = $query->condition('f.fid', $fids, 'IN')
    ->groupBy($type)
    ->execute()
    ->fetchAllKeyed();
  return $type_count;
}

/**
 * Returns all file IDs attached to a media gallery node.
 */
Jacob Singh's avatar
Jacob Singh committed
function media_gallery_get_file_ids($node, $media_field = 'media_gallery_media') {
  $media_items = field_get_items('node', $node, $media_field);
  if( $media_items !== FALSE ) {
    foreach ($media_items as $item) {
      $fids[] = _media_gallery_get_media_fid($item);
    }
  }
  return drupal_map_assoc($fids);
}

/**
 * Determines the file ID from a media file array or object.
 *
 * This is ugly, but necessary since a media field attached to a node may
 * be represented either as an array or a full object, depending on whether the
 * node has been processed for viewing yet or not.
 *
 * @param $file
 *   Either a media file object or media file array.
 *
 * @return
 *   The value of the 'fid' object property or array key.
 */
function _media_gallery_get_media_fid($file) {
  return is_object($file) ? $file->fid : $file['fid'];
}

/**
 * Removes a media item from a gallery.
 *
 * @param $node
 *   The gallery node object.
 * @param $file
 *   The file to remove from the gallery.
function media_gallery_remove_item_from_gallery($node, $file) {
  $items = &$node->media_gallery_media[LANGUAGE_NONE];
  foreach ($items as $key => $item) {
    if ($file->fid == _media_gallery_get_media_fid($item)) {
      unset($items[$key]);
    }
  }
  node_save($node);
  return $node;
}

/**
 * Implements hook_entity_info_alter().
 */
function media_gallery_entity_info_alter(&$entity_info) {
  // Add view modes for displaying files in gallery contexts.
  foreach (media_gallery_file_view_modes() as $view_mode => $label) {
    $entity_info['file']['view modes'][$view_mode] = array('label' => $label, 'custom settings' => FALSE);

  // Add a view mode for media_gallery_block_view() to use for displaying a
  // media gallery node in a block. Drupal does not support restricting view
  // modes to specific bundles.
  $entity_info['node']['view modes']['media_gallery_block'] = array('label' => t('Media gallery block'), 'custom settings' => FALSE);
}

/**
 * Implements hook_form().
 */
function media_gallery_form($node, $form_state) {
  $form = node_content_form($node, $form_state);
  return $form;
}

/**
 * Implements hook_form_alter().
 */
function media_gallery_form_alter(&$form, &$form_state, $form_id) {
  if (strpos($form_id, 'media_edit') === 0) {
    // Act on both the regular and multiform versions of the edit form.
    if ($form_id === 'media_edit' || preg_match('/^media_edit_[0-9]+$/', $form_id)) {
Jacob Singh's avatar
Jacob Singh committed
      // Prepopulate the media_edit form with our best guess at the image title.
      if (!empty($form['media_title']) && empty($form['media_title'][LANGUAGE_NONE][0]['value']['#default_value'])) {
        $fid = $form['fid']['#value'];
        $file = file_load($fid);
        if ($file->type === 'image') {
          $form['media_title'][LANGUAGE_NONE][0]['value']['#default_value'] = _media_gallery_get_media_title($file);
Jacob Singh's avatar
Jacob Singh committed
      // Prepopulate the license field with the correct default.
      if ($form['field_license'][LANGUAGE_NONE]['#default_value'] == '_none') {
        $form['field_license'][LANGUAGE_NONE]['#default_value'] = 'none';
      }
      unset($form['field_license'][LANGUAGE_NONE]['#options']['_none']);
    // Attach JavaScript and CSS needed to alter elements in the form.
    _media_gallery_attach_edit_resources($form);
  }
}

/*
 * Implements hook_form_FORM_ID_alter().
 */
function media_gallery_form_media_gallery_node_form_alter(&$form, &$form_state) {
  _media_gallery_attach_form_resources($form);

  // The UI for the multi value media field and the node weight is elsewhere.
  $form['media_gallery_media']['#access'] = FALSE;
  $form['media_gallery_weight']['#access'] = FALSE;
Jacob Singh's avatar
Jacob Singh committed

  // Hiding this field because we only support a single collection at the moment.
  $form['media_gallery_collection']['#access'] = FALSE;

  // Wrap a fieldset around the gallery settings.
  $form['settings_wrapper'] = array(
    '#type' => 'fieldset',
    '#title' => t('Gallery settings'),
    '#weight' => 10,
  );

Jacob Singh's avatar
Jacob Singh committed
  unset($form['media_gallery_lightbox_extras'][LANGUAGE_NONE]['#description']);
  $form['media_gallery_lightbox_extras'][LANGUAGE_NONE]['#label'] = 'Show title and description';

  // These are the items that need to be added to the fieldset. The array
  // values represent a subgroup within the fieldset array that the items are
  // further grouped by.
  $fieldset = array(
    'media_gallery_columns' => 'gallery',
    'media_gallery_rows' => 'gallery',
    'media_gallery_image_info_where' => 'gallery',
    'media_gallery_allow_download' => 'presentation',
Jacob Singh's avatar
Jacob Singh committed
    'media_gallery_format' => 'presentation',
    'media_gallery_lightbox_extras' => 'presentation',
  );

  // Move the items to the fieldset.
  foreach($fieldset as $id => $subgroup) {
    $form['settings_wrapper'][$subgroup][$id] = $form[$id];
    // locale_field_node_form_submit() looks for field language information in
    // a hard-coded part of $form.
    // @todo Other modules may as well, so would be best to move form elements
    //   within #pre_render rather than in hook_form_alter().
    $form[$id] = array('#language' => $form[$id]['#language']);
Jacob Singh's avatar
Jacob Singh committed
  }

  // Add a vertical tab menu for blocks
  $form['block'] = array(
    '#type' => 'fieldset',
    '#title' => 'Blocks',
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#group' => 'additional_settings',
    '#attached' => array(),
    '#weight' => -100,
  );

  $fieldset = array(
    'media_gallery_expose_block' => 'block',
    'media_gallery_block_columns' => 'block',
    'media_gallery_block_rows' => 'block',
  );

  // Move the items to the fieldset.
  foreach($fieldset as $id => $subgroup) {
Jacob Singh's avatar
Jacob Singh committed
    $form[$subgroup][$id] = $form[$id];
    // locale_field_node_form_submit() looks for field language information in
    // a hard-coded part of $form.
    // @todo Other modules may as well, so would be best to move form elements
    //   within #pre_render rather than in hook_form_alter().
    $form[$id] = array('#language' => $form[$id]['#language']);
Jacob Singh's avatar
Jacob Singh committed
  // Add a class to the fieldset to target it in the js
  $form['block']['#attributes']['class'] = array('block-form');

  // Add classes where necessary for JS enhancement.
  $form['settings_wrapper']['gallery']['media_gallery_image_info_where']['#attributes']['class'][] = 'form-inline label';
  $form['settings_wrapper']['gallery']['media_gallery_image_info']['#attributes']['class'][] = 'form-inline';

  // Use #prefix and #suffix to group the fields.
Jacob Singh's avatar
Jacob Singh committed
  $form['settings_wrapper']['presentation']['media_gallery_format']['#attributes']['class'][] = 'media-gallery-show no-group-label';
  $form['settings_wrapper']['gallery']['#prefix'] = '<div class="gallery-settings settings-group hidden clearfix"><div class="setting-icon"></div><div class="no-overflow">';
  $form['settings_wrapper']['gallery']['#suffix'] = '</div></div>';

  $form['settings_wrapper']['presentation']['#prefix'] = '<div class="presentation-settings settings-group hidden clearfix"><div class="group-label">' . t('Presentation settings') . '</div><div class="setting-icon"></div><div class="no-overflow">';
  $form['settings_wrapper']['presentation']['#suffix'] = '</div></div>';

  // Enhance the "number of rows" textfields by adding a dropdown element.
  $form['settings_wrapper']['gallery']['media_gallery_rows']['#process'][] = 'media_gallery_process_dropdown';
  $form['settings_wrapper']['gallery']['media_gallery_rows']['#media_gallery_dropdown_options'] = array('1', '3', '5', '10', 'other');
Jacob Singh's avatar
Jacob Singh committed
  $form['block']['media_gallery_block_rows']['#process'][] = 'media_gallery_process_dropdown';
  $form['block']['media_gallery_block_rows']['#media_gallery_dropdown_options'] = array('1', '2', '3', '4', 'other');

  // Adjust the weight of the fields in the presentation wrapper
  $form['settings_wrapper']['presentation']['media_gallery_allow_download']['#weight'] = 0;

  // @todo At some point, it would be nice to have a functional preview display
  //   of gallery nodes, but until happens, remove the Preview button.
  $form['actions']['preview']['#access'] = FALSE;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function media_gallery_form_media_edit_alter(&$form, &$form_state) {
  // Adjust the media edit form when it is shown within a gallery context.
  if (isset($form_state['media_gallery']['gallery'])) {
    // Remove the Delete button, since media entities can't be deleted when they
    $form['actions']['delete']['#access'] = FALSE;

    // Instead, provide a "Remove" checkbox to let users remove the item from
    // the gallery.
    _media_gallery_add_remove_checkbox($form, $form_state, $form_state['media_gallery']['gallery']);

    // Add a submit handler to alter $form_state['redirect'] to the
    // in-gallery-context View page. It's annoying to have to add a submit
    // handler for this, but see http://drupal.org/node/579366#comment-2099836.
    // Make sure to add this for the form-level submit handlers and also for the
    // button-level submit handlers of the "Save" button, in case those are
    // being used.
    $form['#submit'][] = 'media_gallery_media_edit_submit';
    if (isset($form['actions']['submit']['#submit'])) {
      $form['actions']['submit']['#submit'][] = 'media_gallery_media_edit_submit';
    }
  }
  // On the media gallery multiedit page, add a "Remove" checkbox to each item.
  elseif (($node = menu_get_object()) && arg(2) === 'multiedit' && $node->type === 'media_gallery') {
    _media_gallery_add_remove_checkbox($form, $form_state, $node);
  }
}

/**
 * Add a "remove" checkbox to the media edit form.
 */
function _media_gallery_add_remove_checkbox(&$form, &$form_state, $node) {
  // Keep a reference to the gallery this media item belongs to, so we know
  // what to remove it from.
  $form['#gallery'] = $node;

  // Put the remove checkbox inside the "preview" section, so it shows up
  // underneath the thumbnail.
  // @todo: Move into $form['preview']['remove'] when issue
  // http://drupal.org/node/1055986 get committed.
  $form['remove'] = array(
    '#type' => 'checkbox',
    '#title' => t('Remove from gallery'),
    '#description' => t('The original file remains in your <a href="@library">media library</a>.', array('@library' => url('admin/content/media'))),