You are here

nodequeue.module in Nodequeue 7.2

Maintains queues of nodes in arbitrary order.

File

nodequeue.module
View source
<?php

/**
 *  NOTES
 *  Here are various notes I've taken about notable changes and/or ommissions
 *
 *  - Everything has been moved to admin/structure/nodequeue which seemed the
 *    most appropriate destination now that admin/content is out of the picture.
 *
 *  - None of the PagerQueries are working, they're all just normal queries for
 *    the time being.
 */

/**
 * @file
 * Maintains queues of nodes in arbitrary order.
 */
define('NODEQUEUE_OK', 0);
define('NODEQUEUE_INVALID_POSITION', 1);
define('NODEQUEUE_INVALID_NID', 2);
define('NODEQUEUE_DUPLICATE_POSITION', 3);

/* --- HOOKS ---------------------------------------------------------------- */

/**
 * Implements hook_action_info().
 */
function nodequeue_action_info() {
  return array(
    'nodequeue_add_action' => array(
      'type' => 'node',
      'label' => t('Add to Nodequeues'),
      'configurable' => TRUE,
      'triggers' => array(
        'node_presave',
        'node_insert',
        'node_update',
      ),
    ),
    'nodequeue_remove_action' => array(
      'type' => 'node',
      'label' => t('Remove from Nodequeues'),
      'configurable' => TRUE,
      'triggers' => array(
        'node_delete',
      ),
    ),
  );
}

/**
 * Implements hook_permission().
 */
function nodequeue_permission() {
  return array(
    'administer nodequeue' => array(
      'title' => t('Administer nodequeue'),
      'description' => t('Administer the nodequeue module.'),
    ),
    'manipulate queues' => array(
      'title' => t('Manipulate queues'),
      'description' => t('Manipulate queues.'),
    ),
    'manipulate all queues' => array(
      'title' => t('Manipulate all queues'),
      'description' => t('Manipulate all queues.'),
    ),
  );
}

/**
 * Implements hook_init().
 *
 * Loads subsidiary includes for other modules.
 */
function nodequeue_init() {
  include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'nodequeue') . '/includes/nodequeue.actions.inc';
}

/**
 * Implements hook_form_FORM_ID_alter().
 * - Adds a vertical tab for nodequeue settings.
 */
function nodequeue_form_node_type_form_alter(&$form, &$form_state) {
  $form['nodequeue'] = array(
    '#type' => 'fieldset',
    '#title' => t('Queues'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#group' => 'additional_settings',
    '#access' => 'administer nodequeue',
  );
  $options = array();
  $defaults = array();
  $queues = nodequeue_load_queues(array_keys(nodequeue_get_all_qids()));
  foreach ($queues as $qid => $queue) {
    $options[$qid] = t($queue->title);
    if (in_array($form['#node_type']->type, $queue->types)) {
      $defaults[] = $qid;
    }
  }
  $form['nodequeue']['nodequeue_queues'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Queues where this content type can be added'),
    '#options' => $options,
    '#default_value' => $defaults,
  );

  // Add a submit handler.
  $form['#submit'][] = 'nodequeue_form_node_type_form_submit';
}

/**
 * Submit handler for the node type form.
 */
function nodequeue_form_node_type_form_submit(&$form, &$form_state) {
  $enabled_queues = $form_state['values']['nodequeue_queues'];
  foreach ($enabled_queues as $qid => $enabled) {
    $type = $form_state['values']['type'];
    $queue = nodequeue_load($qid);
    $types = $queue->types;
    if ($enabled) {
      if (!in_array($type, $types)) {
        $types[] = $type;
      }
    }
    else {
      $key = array_search($type, $types);
      if ($key !== FALSE) {
        unset($types[$key]);
      }
    }
    $queue->types = array_filter($types);
    $qid = nodequeue_save($queue);

    // sets $queue->qid if needed.
  }
}

/**
 * Implements hook_menu().
 */
function nodequeue_menu() {
  $items = array();
  $admin_access = array(
    'administer nodequeue',
  );

  // administrative items
  $items['admin/structure/nodequeue'] = array(
    'title' => 'Nodequeues',
    'page callback' => 'nodequeue_view_queues',
    'access callback' => '_nodequeue_access_admin_or_manipulate',
    'description' => 'Create and maintain simple nodequeues.',
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/structure/nodequeue/list'] = array(
    'title' => 'List nodequeues',
    'page callback' => 'nodequeue_view_queues',
    'access callback' => '_nodequeue_access_admin_or_manipulate',
    'file' => 'includes/nodequeue.admin.inc',
    'weight' => -1,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/structure/nodequeue/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'nodequeue_admin_settings',
    ),
    'access arguments' => $admin_access,
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['nodequeue/autocomplete'] = array(
    'title' => 'Autocomplete',
    'page callback' => 'nodequeue_autocomplete',
    'access callback' => '_nodequeue_access_admin_or_manipulate',
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $info = nodequeue_api_info();
  foreach ($info as $key => $data) {
    $items['admin/structure/nodequeue/add/' . $key] = array(
      'title' => 'Add @type',
      'title arguments' => array(
        '@type' => strtolower($data['title']),
      ),
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'nodequeue_edit_queue_form',
        $key,
      ),
      'access arguments' => $admin_access,
      'file' => 'includes/nodequeue.admin.inc',
      'type' => MENU_LOCAL_ACTION,
    );
  }
  $items['node/%node/nodequeue'] = array(
    'title' => '@tab',
    'title arguments' => array(
      '@tab' => variable_get('nodequeue_tab_name', 'Nodequeue'),
    ),
    'page callback' => 'nodequeue_node_tab',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'nodequeue_node_tab_access',
    'access arguments' => array(
      1,
    ),
    'file' => 'includes/nodequeue.admin.inc',
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
  );

  // Administrative items for an individual queue.
  $items['admin/structure/nodequeue/%nodequeue'] = array(
    'title callback' => 'nodequeue_admin_view_title',
    'title arguments' => array(
      3,
    ),
    'page callback' => 'nodequeue_admin_view',
    'page arguments' => array(
      3,
    ),
    'access callback' => 'nodequeue_queue_access',
    'access arguments' => array(
      3,
    ),
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/structure/nodequeue/%nodequeue/view'] = array(
    'title callback' => 'nodequeue_admin_view_title',
    'title arguments' => array(
      3,
    ),
    'page callback' => 'nodequeue_admin_view',
    'page arguments' => array(
      3,
    ),
    'access callback' => 'nodequeue_queue_access',
    'access arguments' => array(
      3,
    ),
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );

  // Administrative items for a subqueue.
  $items['admin/structure/nodequeue/%nodequeue/view/%subqueue'] = array(
    'title callback' => 'nodequeue_admin_view_title',
    'title arguments' => array(
      3,
    ),
    'page callback' => 'nodequeue_admin_view',
    'page arguments' => array(
      3,
      5,
    ),
    'access callback' => 'nodequeue_queue_access',
    'access arguments' => array(
      3,
      5,
    ),
    'file' => 'includes/nodequeue.admin.inc',
    'tab parent' => 'admin/structure/nodequeue/%',
    'type' => MENU_CALLBACK,
  );
  $items['admin/structure/nodequeue/%nodequeue/edit'] = array(
    'title' => 'Edit queue settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'nodequeue_edit_queue_form',
      3,
    ),
    'access arguments' => $admin_access,
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/structure/nodequeue/%nodequeue/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'nodequeue_admin_delete',
      3,
    ),
    'access arguments' => $admin_access,
    'file' => 'includes/nodequeue.admin.inc',
    'weight' => 5,
    'type' => MENU_CALLBACK,
  );
  $items['nodequeue/%nodequeue/add-node/%subqueue/%node'] = array(
    'page callback' => 'nodequeue_admin_add_node',
    'page arguments' => array(
      1,
      3,
      4,
    ),
    'access callback' => 'nodequeue_node_and_queue_access',
    'access arguments' => array(
      4,
      1,
      3,
    ),
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['nodequeue/%nodequeue/remove-node/%subqueue/%node'] = array(
    'page callback' => 'nodequeue_admin_remove_node',
    'page arguments' => array(
      1,
      3,
      4,
    ),
    'access callback' => 'nodequeue_node_and_queue_access',
    'access arguments' => array(
      4,
      1,
      3,
    ),
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items["admin/structure/nodequeue/%nodequeue/clear/%subqueue"] = array(
    'title' => 'Clear',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'nodequeue_clear_confirm',
      3,
      5,
    ),
    'access callback' => 'nodequeue_queue_access',
    'access arguments' => array(
      3,
      5,
    ),
    'file' => 'includes/nodequeue.admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Helper function for a _menu_translate() bug.
 */
function subqueue_to_arg() {
  return '';
}

/**
 * Implements hook_admin_paths().
 */
function nodequeue_admin_paths() {
  if (variable_get('node_admin_theme')) {
    $paths = array(
      'node/*/nodequeue' => TRUE,
    );
    return $paths;
  }
}

/**
 * Implements hook_node_delete.
 */
function nodequeue_node_delete($node) {

  // If a node is being deleted, ensure it's also removed from any queues.
  $result = db_query("SELECT qid, sqid FROM {nodequeue_nodes} WHERE nid =:nid", array(
    ':nid' => $node->nid,
  ));
  foreach ($result as $obj) {

    // If the queue is being tracked by translation set and the node is part
    // of a translation set, don't delete the queue record.
    // Instead, data will be updated in the 'translation_change' op, below.
    $queues = nodequeue_load_queues(array(
      $obj->qid,
    ));
    $queue = array_shift($queues);
    if (!$queue->i18n || isset($node->tnid) && empty($node->tnid)) {

      // This removes by nid, not position, because if we happen to have a
      // node in a queue twice, the 2nd position would be wrong.
      nodequeue_subqueue_remove_node($obj->sqid, $node->nid);
    }
    if (module_exists('rules')) {
      rules_invoke_event('nodequeue_node_changed', nodequeue_load_subqueue($obj->sqid), $node);
    }
  }
}

/**
 * Implements hook_node_view().
 */
function nodequeue_node_view($node, $view_mode) {
  $links = nodequeue_node_links($node);
  if (!empty($links)) {
    $node->content['links']['nodequeue'] = array(
      '#links' => $links,
      '#theme' => 'links__node__nodequeue',
    );
  }
}

/**
 * Implements hook_node_update()
 */
function nodequeue_node_update($node) {
  if (module_exists('rules')) {
    $result = db_query("SELECT qid, sqid FROM {nodequeue_nodes} WHERE nid =:nid", array(
      ':nid' => $node->nid,
    ));
    foreach ($result as $obj) {
      rules_invoke_event('nodequeue_node_changed', nodequeue_load_subqueue($obj->sqid), $node);
    }
  }
}

/**
 * Implementats hook_forms().
 */
function nodequeue_forms($form_id) {
  $forms = array();
  if (strpos($form_id, 'nodequeue_arrange_subqueue_form_') === 0) {
    $forms[$form_id] = array(
      'callback' => 'nodequeue_arrange_subqueue_form',
    );
  }
  return $forms;
}

/**
 * Implements hook_theme().
 */
function nodequeue_theme() {
  return array(
    'nodequeue_arrange_subqueue_form_table' => array(
      'render element' => 'form',
      'file' => 'nodequeue.theme.inc',
    ),
    'nodequeue_subqueue_empty_text' => array(
      'variables' => array(),
      'file' => 'nodequeue.theme.inc',
    ),
    'nodequeue_subqueue_full_text' => array(
      'variables' => array(),
      'file' => 'nodequeue.theme.inc',
    ),
    'nodequeue_subqueue_count_text' => array(
      'variables' => array(
        'count' => 0,
      ),
      'file' => 'nodequeue.theme.inc',
    ),
  );
}

/**
 * Implements hook_element_info().
 */
function nodequeue_element_info() {
  $type = array();
  $type['position'] = array(
    '#input' => TRUE,
    '#delta' => 10,
    '#default_value' => 0,
    '#process' => array(
      'process_position',
      'ajax_process_form',
    ),
  );
  return $type;
}

/**
 * Expand position elements into selects. Works like the weight element, except
 * only positive values are allowed.
 */
function process_position($element) {
  for ($n = 1; $n <= $element['#delta']; $n++) {
    $positions[$n] = $n;
  }
  $element['#options'] = $positions;
  $element['#options']['r'] = t('Remove');
  $element['#type'] = 'select';

  // add default properties for the select element
  $element += element_info('select');
  return $element;
}

/**
 * If no default value is set for position select boxes, use 1.
 */
function position_value(&$form) {
  if (isset($form['#default_value'])) {
    $form['#value'] = $form['#default_value'];
  }
  else {
    $form['#value'] = 1;
  }
}

/**
 * Implements hook_contextual_links_view_alter().
 */
function nodequeue_contextual_links_view_alter(&$element, $items) {

  // Bail if the user doesn't have access to edit nodequeues.
  if (!user_access('manipulate queues')) {
    return;
  }
  if (!variable_get('nodequeue_show_contextual_links', TRUE)) {
    return;
  }

  // Contextual links for Blocks.
  $has_context = isset($element['#element']['#contextual_links']);
  if ($has_context) {
    $empty_context = empty($element['#element']['#contextual_links']['views_ui']);
    if ($empty_context) {
      return;
    }
    $views_info = $element['#element']['#contextual_links'];
  }

  // Contextual links for Views.
  $has_views_context = isset($element['#element']['#views_contextual_links_info']);
  if ($has_views_context) {
    $empty_views_context = empty($element['#element']['#views_contextual_links_info']['views_ui']);
    if ($empty_views_context) {
      return;
    }
    $views_info = $element['#element']['#views_contextual_links_info'];
  }

  // Contextual links for Context blocks, or DS blocks.
  $has_views_content_context = isset($element['#element']['content']['#views_contextual_links_info']);
  if ($has_views_content_context) {
    $empty_views_content_context = empty($element['#element']['content']['#views_contextual_links_info']['views_ui']);
    if ($empty_views_context) {
      return;
    }
    $views_info = $element['#element']['content']['#views_contextual_links_info'];
  }

  // Return if there is no view info.
  if (!isset($views_info)) {
    return;
  }
  $display_id = $views_info['views_ui']['view_display_id'];
  $view_name = $views_info['views_ui']['view_name'];
  $view = views_get_view($view_name, TRUE);

  // Return if the view wasn't saved yet or has no display objects.
  if (empty($view)) {
    return;
  }
  $view
    ->build($display_id);
  $nodequeue_rels = array(
    'nodequeue_handler_relationship_nodequeue',
  );
  $subqueue_arg_titles = array(
    'Subqueue reference',
    'Subqueue reference (optional)',
  );
  $elview = $element['#element']['#views_contextual_links_info']['views_ui']['view'];
  $relationship = '';
  if (!empty($elview->display_handler->handlers['argument']['name']->relationship)) {
    $relationship = $elview->display_handler->handlers['argument']['name']->relationship;
  }
  if (!empty($relationship) && $relationship == 'nodequeue_nodes_node') {
    $argument = $elview->display_handler->handlers['argument']['name']->argument;
  }

  // Cycle through all the relationships to find ones provided by nodequeue.
  // We'll use this as a trigger to attach the links.
  foreach ($view->relationship as $rel) {
    if (!in_array(get_class($rel), $nodequeue_rels, TRUE)) {
      continue;
    }
    foreach ($rel->options['names'] as $queue_name) {
      if (gettype($queue_name) != 'string') {
        if (empty($argument)) {
          continue;
        }
        else {
          $queue_name = $argument;
        }
      }
      $qid_map = nodequeue_get_qid_map();
      $qid = $qid_map[$queue_name];
      $queue = nodequeue_load($qid);
      $element['#links'][$queue_name] = array(
        'title' => t('Edit queue'),
        'href' => 'admin/structure/nodequeue/' . $qid . '/view',
        'query' => array(
          'destination' => current_path(),
        ),
      );

      // Cycle through all arguments to find ones that limit us by subqueue.
      // If we find some, add further links to subqueues.
      foreach ($view->argument as $arg) {
        $is_subqueue_arg = $arg->options['relationship'] == $rel->options['id'] && in_array($arg->definition['title'], $subqueue_arg_titles, TRUE);
        if (!$is_subqueue_arg) {
          continue;
        }

        // Provide contextual links for each of the subqueues.
        $subqueues = nodequeue_load_subqueues_by_reference(array(
          $qid => $arg->value,
        ));
        foreach ($subqueues as $sqid => $subqueue) {
          $element['#links'][$queue_name . '_' . $sqid] = array(
            'title' => t('Edit Subqueue'),
            'href' => 'admin/structure/nodequeue/' . $qid . '/view/' . $sqid,
            'query' => array(
              'destination' => current_path(),
            ),
          );
        }
      }
    }
  }
}

/**
 * Implements hook_views_api().
 */
function nodequeue_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'nodequeue') . '/includes/views',
  );
}

// --------------------------------------------------------------------------
// Nodequeue Apache Solr Search Integration

/**
 * Implements hook_form_FORM_ID_alter().
 */
function nodequeue_form_apachesolr_search_bias_form_alter(&$form, &$form_state) {

  // Setup for the form building.
  $weights = drupal_map_assoc(array(
    '21.0',
    '13.0',
    '8.0',
    '5.0',
    '3.0',
    '2.0',
    '1.0',
    '0.8',
    '0.5',
    '0.3',
    '0.2',
    '0.1',
  ));
  $weights['0'] = t('Normal');
  $queues = nodequeue_load_subqueues_by_queue(array_keys(nodequeue_get_all_qids()));
  $env_id = $form['#env_id'];

  // Build the form.
  $form['biasing']['nodequeue_boost'] = array(
    '#type' => 'fieldset',
    '#title' => t('Nodequeue Biasing'),
    '#weight' => -5,
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['biasing']['nodequeue_boost']['nodequeue_apachesolr_boost'] = array(
    '#type' => 'item',
    '#description' => t("Specify to bias the search result when a node is in a queue. Any value except <em>Normal</em> will increase the score for the given queue in the search results"),
  );
  foreach ($queues as $sqid => $queue) {
    $boost = apachesolr_environment_variable_get($env_id, "nodequeue_apachesolr_boost_{$sqid}", 0);

    // Add in setting for each queue.
    $form['biasing']['nodequeue_boost']['nodequeue_apachesolr_boost']["nodequeue_apachesolr_boost_{$sqid}"] = array(
      '#type' => 'select',
      '#title' => t('Weight for %title nodequeue', array(
        '%title' => $queue->title,
      )),
      '#options' => $weights,
      '#default_value' => $boost,
    );
  }
  $form['actions']['submit']['#submit'][] = 'nodequeue_search_bias_form_submit';
}

/**
 * Implements hook_apachesolr_index_document_build_ENTITY_TYPE().
 */
function nodequeue_apachesolr_index_document_build_node(ApacheSolrDocument $document, $entity, $env_id) {
  if (empty($document)) {
    return;
  }
  $queues = nodequeue_load_queues(array_keys(nodequeue_get_all_qids()));
  $subqueues = nodequeue_get_subqueues_by_node($queues, $entity);
  nodequeue_set_subqueue_positions($subqueues, $entity->nid);
  if (is_array($subqueues)) {
    foreach ($subqueues as $sqid => $subqueue) {
      if (!empty($subqueue->position)) {
        $key = _nodequeue_solr_qid_key();
        $document
          ->setMultiValue($key, $sqid);
      }
    }
  }
}

/**
 * Returns the apachesolr index key for group id.
 */
function _nodequeue_solr_qid_key() {
  $qid_key = array(
    'index_type' => 'sint',
    'multiple' => TRUE,
    'name' => "nodequeue",
  );
  return apachesolr_index_key($qid_key);
}

/**
 * Implements hook_apachesolr_query_alter().
 */
function nodequeue_apachesolr_query_alter(DrupalSolrQueryInterface $query) {
  $queues = nodequeue_load_subqueues_by_queue(array_keys(nodequeue_get_all_qids()));
  $added = FALSE;
  $env_id = $query
    ->solr('getId');
  foreach ($queues as $sqid => $queue) {
    $boost = apachesolr_environment_variable_get($env_id, "nodequeue_apachesolr_boost_{$sqid}", 0);
    if (!empty($boost)) {
      $query
        ->addParam('bq', _nodequeue_solr_qid_key() . ":{$sqid}^{$boost}");
      if (!$added) {

        // Only want to add the facet.field once. no need to repeat it.
        $query
          ->addParam('facet.field', _nodequeue_solr_qid_key());
        $added = TRUE;
      }
    }
  }
}

/**
 * Additional submit handler for nodequeue values.
 */
function nodequeue_search_bias_form_submit(&$form, &$form_state) {

  // Exclude unnecessary elements.
  form_state_values_clean($form_state);
  foreach ($form_state['values'] as $key => $value) {
    if (is_array($value) && isset($form_state['values']['array_filter'])) {
      $value = array_keys(array_filter($value));
    }

    // There is no need to set default variable values.
    if (!isset($form[$key]['#default_value']) || $form[$key]['#default_value'] != $value) {
      if (preg_match('/nodequeue_apachesolr_boost_/', $key)) {
        apachesolr_environment_variable_set($form['#env_id'], $key, $value);
      }
    }
  }
}

// --------------------------------------------------------------------------
// Nodequeue manipulation API.

/**
 * @defgroup nodequeue_api
 * @{
 * Access to the internals of nodequeues are handled primarily through these
 * API functions. They allow easy loading of queues for manipulation.
 */

/**
 * The nodequeue queue class; the constructor makes it so we don't have to
 * always check to see if our variables are empty or not.
 */
class nodequeue_queue {
  public $name = '';
  public $title = '';
  public $size = 0;
  public $link = '';
  public $link_remove = '';
  public $roles = array();
  public $types = array();
  public $show_in_links = TRUE;
  public $show_in_tab = TRUE;
  public $show_in_ui = TRUE;
  public $reference = 0;
  public $i18n = 0;
  public $subqueue_title = '';
  public $reverse = 0;
  public $unique_entries = FALSE;
  public $insert_at_front = 0;

  // runtime
  public $subqueues = array();
  public $subqueue = NULL;
  public $current = NULL;
  public function __construct($type) {
    $this->owner = $type;
  }

}

/**
 * Fetch a list of available queues for a given location.
 *
 * @param $type
 *   Content type of the node.
 * @param  $location
 *   Location where the link can appear, for example: link or tab
 * @param $account
 *   The user account, or NULL to use logged-in user.
 * @param $bypass_cache
 *   Whether or not to bypass the cache and load all qids fresh.
 *
 * @return array
 *   Array of fully loaded queues keyed by qid.
 */
function nodequeue_load_queues_by_type($type, $location = NULL, $account = NULL, $bypass_cache = FALSE) {
  $qids = nodequeue_get_qids($type, $account, $bypass_cache);
  if ($location) {
    nodequeue_filter_qids($qids, $location);
  }
  return nodequeue_load_queues(array_keys($qids), $bypass_cache);
}

/**
 * Filter a list of qids returned by nodequeue_get_qids to a location.
 *
 * @param $qids
 *   An array of $qids from @see nodequeue_get_qids()
 * @param $location
 *   One of:
 *   - 'links': Only check for queues that have node links.
 *   - 'tab': Only check for queues that appear on the node tab.
 *   - 'ui': Only check for queues that appear in the UI.
 */
function nodequeue_filter_qids(&$qids, $location) {
  $var = "show_in_{$location}";
  foreach ($qids as $qid => $info) {
    if (empty($info->{$var})) {
      unset($qids[$qid]);
    }
  }
}

/**
 * Get an array of qids applicable to this node type.
 *
 * @param $type
 *   The node type.
 * @param $account
 *   The account to test against. Defaults to the currently logged in user.
 *
 * @return $qids
 *   An array in the format: @code { array($qid => array('qid' => $qid, 'show_in_tab' '
 *   => true/false, 'show_in_links' => true/false }
 *
 * @param $bypass_cache
 *  Boolean value indicating whether to bypass the cache or not.
 */
function nodequeue_get_qids($type, $account = NULL, $bypass_cache = FALSE) {
  if (!isset($account)) {
    global $user;
    $account = $user;
  }
  $cache =& drupal_static(__FUNCTION__, array());
  if ($bypass_cache || !isset($cache[$type])) {
    $roles_join = $roles_where = '';
    $roles = array();

    // superuser always has access.
    if (!user_access('manipulate all queues', $account)) {
      $roles = array_keys((array) $account->roles) + array(
        DRUPAL_AUTHENTICATED_RID,
      );
      $roles_join = "INNER JOIN {nodequeue_roles} nr ON nr.qid = nq.qid ";
      $roles_where .= "AND nr.rid IN (:roles)";
    }
    $sql = 'SELECT nq.qid, nq.show_in_tab, nq.show_in_links, nq.show_in_ui, nq.i18n ' . 'FROM {nodequeue_queue} nq ' . 'INNER JOIN {nodequeue_types} nt ON nt.qid = nq.qid ' . $roles_join . "WHERE nt.type = :type " . $roles_where;
    $result = db_query($sql, array(
      ':type' => $type,
      ':roles' => $roles,
    ));
    $qids = array();
    foreach ($result as $qid) {
      $qids[$qid->qid] = $qid;
    }
    $cache[$type] = $qids;
  }
  return $cache[$type];
}

/**
 * Get an array of qids using the pager query. This administrative list
 * does no permission checking, so should only be available to users who
 * have passed the 'administer queues' check.
 *
 * @param $page_size
 *   The page size to use. If this is 0 or NULL, all queues will be returned.
 *   Defaults to 0.
 * @param $pager_element
 *   In the rare event this should use another pager element, set this..
 * @param $bypass_cache
 *   Boolean value indicating whether to bypass the cache or not.
 *
 * @return $qids
 *   An array in the format: @code { array($qid => $qid) }
 */
function nodequeue_get_all_qids($page_size = 0, $pager_element = 0, $bypass_cache = FALSE) {
  $cache =& drupal_static(__FUNCTION__, array());
  if ($bypass_cache || empty($cache[$page_size])) {
    $query = db_select('nodequeue_queue', 'nq')
      ->fields('nq', array(
      'qid',
    ));
    if (!empty($page_size)) {
      $query
        ->extend('PagerDefault')
        ->extend('TableSort')
        ->limit($page_size)
        ->element($pager_element);
    }
    $qids = $query
      ->execute()
      ->fetchAllKeyed(0, 0);
    $cache[$page_size] = $qids;
  }
  return $cache[$page_size];
}

/**
 * Load an array of $qids.
 *
 * This exists to provide a way of loading a bunch of queues with
 * the fewest queries. Loading 5 queues results in only 4 queries,
 * not 20. This also caches queues so that they don't get loaded
 * repeatedly.
 *
 * @param $qids
 *   An array of queue IDs to load.
 *
 * @param $bypass_cache
 *   Boolean value indicating whether to bypass the cache or not.
 *
 * @return array
 *   if $bypass_cache is TRUE then it will return data from catch
 *   else from database.
 */
function nodequeue_load_queues($qids = array(), $bypass_cache = FALSE) {
  $static = drupal_static(__FUNCTION__, array(
    'queues' => array(),
    'qids' => array(),
  ));

  // Create a list of queues that need to be loaded.
  $to_load = array();
  foreach ($qids as $qid) {
    if ($bypass_cache || !isset($static['qids'][$qid])) {
      $to_load[] = $qid;
    }
  }

  // Load fresh queues from the database.
  $new_queues = array();
  if (!empty($to_load)) {
    $result = db_query("SELECT q.*, (SELECT count(*) FROM {nodequeue_subqueue} s WHERE q.qid = s.qid) AS subqueues FROM {nodequeue_queue} q WHERE q.qid IN (:to_load)", array(
      ':to_load' => $to_load,
    ));
    foreach ($result as $queue) {
      $qid = $queue->qid;
      if (!isset($static['queues'][$qid])) {
        $new_queues[$qid] = $queue;

        // Add valid defaults values.
        $new_queues[$qid]->types = array();
        $new_queues[$qid]->roles = array();
        $new_queues[$qid]->count = 0;
      }
    }

    // Add role data to all new queues.
    $result = db_query("SELECT qid, rid FROM {nodequeue_roles} WHERE qid IN (:to_load)", array(
      ':to_load' => $to_load,
    ));
    foreach ($result as $obj) {
      $qid = $obj->qid;
      $new_queues[$qid]->roles[] = $obj->rid;
    }

    // Add type data to all new queues.
    $result = db_query("SELECT qid, type FROM {nodequeue_types} WHERE qid IN (:to_load)", array(
      ':to_load' => $to_load,
    ));
    foreach ($result as $obj) {
      $qid = $obj->qid;
      $new_queues[$qid]->types[] = $obj->type;
    }

    // Allow other modules to load additional data to the new queues.
    $context = 'load_queues';
    drupal_alter('nodequeue', $new_queues, $context);
  }
  if ($bypass_cache) {
    return $new_queues;
  }
  if (!empty($new_queues)) {
    $static['queues'] += $new_queues;
    $static['qids'] += array_keys($new_queues);
  }

  // Return only the requested queues.
  $return = array();
  foreach ($qids as $qid) {
    if (isset($static['queues'][$qid])) {
      $return[$qid] = $static['queues'][$qid];
    }
  }
  return $return;
}

/**
 * Load a nodequeue.
 *
 * @param $qid
 *   The qid of the queue to load.
 *
 * @return mixed
 *   if $queues is empty the function will return empty array else $queues.
 */
function nodequeue_load($qid) {
  $queues = nodequeue_load_queues(array(
    $qid,
  ));
  return !empty($queues) ? array_shift($queues) : array();
}

/**
 * This function exists so that %subqueue will work in hook_menu.
 */
function subqueue_load($sqid) {
  if (!$sqid) {
    return NULL;
  }
  $queues = nodequeue_load_subqueues(array(
    $sqid,
  ));
  return !empty($queues) ? array_shift($queues) : array();
}

/**
 * Load a list of subqueues.
 *
 * This exists to provide a way of loading a bunch of queues with
 * the fewest queries. Loading 5 queues results in only 4 queries,
 * not 20. This also caches queues so that they don't get loaded
 * repeatedly.
 *
 * @param array $sqids
 *   An array of subqueue IDs to load.
 * @param bool $bypass_cache
 *   Boolean value indicating whether to bypass the cache or not.
 *
 * @return array
 *   An array of subqueues.
 */
function nodequeue_load_subqueues($sqids, $bypass_cache = FALSE) {
  $cache =& drupal_static(__FUNCTION__, array());
  $to_load = array();
  $subqueues = array();
  foreach ($sqids as $sqid) {
    if ($bypass_cache || !isset($cache[$sqid])) {
      $to_load[] = $sqid;
    }
  }
  if (!empty($to_load)) {
    $result = db_query("SELECT s.*, (SELECT count(*) FROM {nodequeue_nodes} n WHERE n.sqid = s.sqid) AS count FROM {nodequeue_subqueue} s WHERE s.sqid IN (:to_load)", array(
      ':to_load' => $to_load,
    ));
    foreach ($result as $obj) {

      // Sometimes we want to get to subqueues by reference, sometimes by sqid.
      // sqid is always unique, but reference is sometimes more readily available.
      $cache[$obj->sqid] = $obj;
    }
  }
  foreach ($sqids as $sqid) {
    if (isset($cache[$sqid])) {
      $subqueues[$sqid] = $cache[$sqid];
    }
  }
  return $subqueues;
}

/**
 * Load a single subqueue.
 *
 * @param $sqid
 *   The subqueue ID to load.
 * @param $bypass_cache
 *   Boolean value indicating whether to bypass the cache or not.
 *
 * @return mixed
 *
 */
function nodequeue_load_subqueue($sqid, $bypass_cache = FALSE) {
  $subqueues = nodequeue_load_subqueues(array(
    $sqid,
  ), $bypass_cache);
  if ($subqueues) {
    return array_shift($subqueues);
  }
}

/**
 * Load the entire set of subqueues for a queue.
 *
 * This will load the entire set of subqueues for a given queue (and can
 * respect the pager, if desired). It does NOT cache the subqueues like
 * nodequeue_load_subqueues does, so beware of this mixed caching.
 *
 * @param $qids
 *   A $qid or array of $qids
 * @param $page_size
 *   If non-zero, use the pager_query and limit the page-size to the parameter.
 *
 * @return array
 *   If $qids is empty it will return empty array else $subqueues.
 *
 */
function nodequeue_load_subqueues_by_queue($qids, $page_size = 0) {
  if (is_numeric($qids)) {
    $qids = array(
      $qids,
    );
  }
  if (empty($qids)) {
    return array();
  }
  $query = "SELECT s.*, (SELECT count(*) FROM {nodequeue_nodes} n WHERE n.sqid = s.sqid) AS count FROM {nodequeue_subqueue} s WHERE s.qid IN (:qids)";
  $result = db_query($query, array(
    ':qids' => $qids,
  ));
  $subqueues = array();
  foreach ($result as $subqueue) {
    $subqueues[$subqueue->sqid] = $subqueue;
  }
  return $subqueues;
}

/**
 * Load a set of subqueues by reference.
 *
 * This can be used to load a set of subqueues by reference; it will primarily
 * be used by plugins that are managing subqueues.
 *
 * @param array $references
 *   A keyed array of references to load. The key is the $qid and each value
 *   is another array of references.
 * @param bool $bypass_cache
 *
 * @return object
 *
 */
function nodequeue_load_subqueues_by_reference($references, $bypass_cache = FALSE) {
  $cache =& drupal_static(__FUNCTION__, array());
  $subqueues = array();
  if ($bypass_cache) {
    $cache = array();
  }
  if (!empty($references)) {
    $query = db_select('nodequeue_subqueue', 's')
      ->groupBy('s.sqid')
      ->groupBy('s.qid')
      ->groupBy('s.reference')
      ->fields('s', array(
      'title',
      'sqid',
      'qid',
      'reference',
    ));
    $query
      ->leftJoin('nodequeue_nodes', 'n', 'n.sqid = s.sqid');
    $query
      ->addExpression('COUNT(n.position)', 'count');
    $where = db_or();
    foreach ($references as $qid => $reference) {
      $where
        ->condition(db_and()
        ->condition('s.qid', $qid)
        ->condition('s.reference', $reference));
    }
    $query
      ->condition($where);
    $result = $query
      ->execute();
    foreach ($result as $subqueue) {
      $cache[$subqueue->qid][$subqueue->reference] = $subqueues[$subqueue->sqid] = $subqueue;
    }
  }
  return $subqueues;
}

/**
 * Return a queue by its machine name. This is obviously not ideal due to the
 * extra queries, but probably preferable to changing current API calls.
 *
 * @param $name
 *   The queue machine name
 * @return array|mixed
 *   The queue definition, or an empty array if no queue was found with the
 *   given machine name.
 */
function nodequeue_load_queue_by_name($name) {
  $map = nodequeue_get_qid_map();
  if (isset($map[$name])) {
    $queues = nodequeue_load_queues(array(
      $map[$name],
    ));
    if ($queues) {
      return current($queues);
    }
  }
  return array();
}

/**
 * Return a map of queue name to qid values to aid in various lookups.
 *
 * @return array
 *   A array of qids, keyed by machine name.
 */
function nodequeue_get_qid_map() {
  $map =& drupal_static(__FUNCTION__, array());
  if (!$map) {
    $result = db_query("SELECT qid, name FROM {nodequeue_queue}");
    while ($get = $result
      ->fetchObject()) {
      $map[$get->name] = $get->qid;
    }
  }
  return $map;
}

/**
 * Save a nodequeue. This does not save subqueues; those must be added separately.
 */
function nodequeue_save(&$queue) {
  $nodequeue_queue_fields = array(
    'name' => $queue->name,
    'title' => $queue->title,
    'subqueue_title' => $queue->subqueue_title,
    'size' => $queue->size,
    'link' => $queue->link,
    'link_remove' => $queue->link_remove,
    'owner' => $queue->owner,
    'show_in_links' => $queue->show_in_links ? 1 : 0,
    'show_in_tab' => $queue->show_in_tab,
    'show_in_ui' => $queue->show_in_ui,
    'i18n' => $queue->i18n,
    'reverse' => $queue->reverse,
    'unique_entries' => $queue->unique_entries ? 1 : 0,
    'insert_at_front' => $queue->insert_at_front,
    'reference' => $queue->reference,
  );
  if (!isset($queue->qid)) {
    $queue->qid = db_insert('nodequeue_queue')
      ->fields($nodequeue_queue_fields)
      ->execute();
    if (module_exists('views')) {
      views_invalidate_cache();
    }
  }
  else {
    db_update('nodequeue_queue')
      ->fields($nodequeue_queue_fields)
      ->condition('qid', $queue->qid)
      ->execute();
    db_delete('nodequeue_roles')
      ->condition('qid', $queue->qid)
      ->execute();
    db_delete('nodequeue_types')
      ->condition('qid', $queue->qid)
      ->execute();
  }
  if (is_array($queue->roles)) {
    foreach ($queue->roles as $rid) {
      db_insert('nodequeue_roles')
        ->fields(array(
        'qid' => $queue->qid,
        'rid' => $rid,
      ))
        ->execute();
    }
  }
  if (is_array($queue->types)) {
    foreach ($queue->types as $type) {
      db_insert('nodequeue_types')
        ->fields(array(
        'qid' => $queue->qid,
        'type' => $type,
      ))
        ->execute();
    }
  }

  // Set global that tells us if we need to activate nodequeue_node_links().
  if (db_query("SELECT COUNT(*) FROM {nodequeue_queue} WHERE link <> ''")
    ->fetchField()) {
    variable_set('nodequeue_links', TRUE);
  }
  else {
    variable_set('nodequeue_links', FALSE);
  }
  if (isset($queue->add_subqueue) && is_array($queue->add_subqueue)) {
    foreach ($queue->add_subqueue as $reference => $title) {

      // If reference is unset it should be set to the qid; this is generally
      // used for a single subqueue; setting the reference to the qid makes
      // it easy to find that one subqueue.
      if ($reference == 0) {
        $reference = $queue->qid;
      }
      nodequeue_add_subqueue($queue, $title, $reference);
    }
  }
  return $queue->qid;
}

/**
 * Delete a nodequeue.
 */
function nodequeue_delete($qid) {

  // Load the queue before it's deleted.
  $queue = nodequeue_load($qid);
  db_delete('nodequeue_roles')
    ->condition('qid', $qid)
    ->execute();
  db_delete('nodequeue_types')
    ->condition('qid', $qid)
    ->execute();
  db_delete('nodequeue_queue')
    ->condition('qid', $qid)
    ->execute();
  db_delete('nodequeue_nodes')
    ->condition('qid', $qid)
    ->execute();
  db_delete('nodequeue_subqueue')
    ->condition('qid', $qid)
    ->execute();

  // Invoke a hook to notify other modules that a nodequeue has been deleted.
  module_invoke_all('nodequeue_delete', $qid, $queue);
}

/**
 * Add a new subqueue to a queue.
 *
 * @param $queue
 *   The queue object that is the parent of this subqueue.
 * @param $title
 *   The title of the subqueue.
 * @param $reference
 *   A reference that uniquely identifies this subqueue. If NULL it will
 *   be assigned the sqid.
 *
 * @return object
 */
function nodequeue_add_subqueue(&$queue, $title, $reference = NULL) {
  if (empty($reference)) {
    $insert_reference = "";
  }
  else {
    $insert_reference = $reference;
  }
  $subqueue = new stdClass();
  $subqueue->reference = $reference;
  $subqueue->qid = $queue->qid;
  $subqueue->title = $title;
  $subqueue->sqid = db_insert('nodequeue_subqueue')
    ->fields(array(
    'qid' => $queue->qid,
    'reference' => $insert_reference,
    'title' => $title,
  ))
    ->execute();

  // If somehow the $reference is null, here we set it to the sqid.
  // We have to do it here, because before the insert we don't know what the sqid will be.
  if (empty($reference)) {
    db_update('nodequeue_subqueue')
      ->fields(array(
      'reference' => $subqueue->sqid,
    ))
      ->condition('sqid', $subqueue->sqid)
      ->execute();
  }
  return $subqueue;
}

/**
 * Change the title of a subqueue.
 *
 * Note that only the title of a subqueue is changeable; it can change to
 * reflect updates in taxonomy term names, for example.
 */
function nodequeue_subqueue_update_title($sqid, $title) {
  db_update('nodequeue_subqueue')
    ->fields(array(
    'title' => $title,
  ))
    ->condition('sqid', $sqid)
    ->execute();
}

/**
 * Remove a subqueue.
 */
function nodequeue_remove_subqueue($sqid) {
  nodequeue_queue_clear($sqid);
  db_delete('nodequeue_subqueue')
    ->condition('sqid', $sqid)
    ->execute();
}

// --------------------------------------------------------------------------
// Queue position control

/**
 * Add a node to a queue.
 *
 * @param $queue_name
 *   A machine-readable name of the queue.
 * @param $nid
 *   The node ID. Defaults to NULL.
 */
function nodequeue_queue_add($queue_name, $nid = NULL) {
  if (!empty($nid)) {
    $queue = nodequeue_load_queue_by_name($queue_name);
    $subqueues = nodequeue_load_subqueues_by_queue($queue->qid);
    foreach ($subqueues as $subqueue) {
      nodequeue_subqueue_add($queue, $subqueue, $nid);
    }
  }
}

/**
 * Add a node to a subqueue.
 *
 * @param $queue
 *   The parent queue of the subqueue. This is required so that we can
 *   pop nodes out if the queue breaks size limits.
 * @param $subqueue
 *   The subqueue to add the node to.
 * @param $nid
 *   The node ID. Defaults to NULL.
 *
 * @throws Exception
 */
function nodequeue_subqueue_add($queue, &$subqueue, $nid = NULL) {
  if (!empty($nid)) {

    // Don't add the node if the queue is configured to enforce uniqueness.
    if (!empty($queue->unique_entries)) {
      $result = db_query("SELECT nid FROM {nodequeue_nodes} WHERE sqid = :sqid AND qid = :qid AND nid = :nid", array(
        ':sqid' => $subqueue->sqid,
        ':qid' => $queue->qid,
        ':nid' => $nid,
      ));
      if (empty($result) || $result
        ->rowCount() > 0) {

        // We have no way of telling the caller that the node was not added, so
        // log the event and return.
        watchdog('nodequeue', 'The queue does not allow duplicated entries and already contained the node with nid !nid', array(
          '!nid' => $nid,
        ));
        return;
      }
    }
    $transaction = db_transaction();
    try {
      if ($queue->insert_at_front) {
        db_update('nodequeue_nodes')
          ->expression('position', 'position + 1')
          ->condition('sqid', $subqueue->sqid)
          ->execute();
        $position = 1;
      }
      else {
        $position = db_query("SELECT COALESCE((SELECT MAX(position) + 1 FROM {nodequeue_nodes} WHERE sqid = :sqid), 1)", array(
          ':sqid' => $subqueue->sqid,
        ))
          ->fetchField();
      }
      db_insert('nodequeue_nodes')
        ->fields(array(
        'sqid' => $subqueue->sqid,
        'qid' => $queue->qid,
        'nid' => $nid,
        'position' => $position,
        'timestamp' => REQUEST_TIME,
      ))
        ->execute();
    } catch (Exception $e) {
      $transaction
        ->rollback();
      watchdog_exception('nodequeue', $e);
      throw $e;
    }
    $subqueue->count = db_query("SELECT COUNT(nid) FROM {nodequeue_nodes} WHERE sqid = :sqid", array(
      ':sqid' => $subqueue->sqid,
    ))
      ->fetchField();

    // If adding this would make the queue too big, pop the front node
    // (or nodes) out.
    if (!empty($queue->size)) {

      // 0 means infinity so never do this if FALSE.
      nodequeue_check_subqueue_size($queue, $subqueue, $queue->size);
    }
    if (module_exists('apachesolr')) {
      apachesolr_mark_entity('node', $nid);
    }

    // Invoke the hook to notify other modules of the node addition.
    module_invoke_all('nodequeue_add', $subqueue->sqid, $nid);
  }
}

/**
 * Remove a node from the queue. If a node is in the queue more than once,
 * only the first (closest to 0 position, or the front of the queue) will
 * be removed.
 *
 * @param $sqid
 *   The subqueue to remove nodes from.
 * @param $nid
 *   The node to remove.
 */
function nodequeue_subqueue_remove_node($sqid, $nid) {
  if ($pos = nodequeue_get_subqueue_position($sqid, $nid)) {
    nodequeue_subqueue_remove($sqid, $pos);
    if (module_exists('apachesolr')) {
      apachesolr_mark_entity('node', $nid);
    }
  }
}

/**
 * Remove a node or node(s) from a nodequeue by position.
 *
 * If you know the nid but but not the position, use
 * @see nodequeue_subqueue_remove_node() instead.
 *
 * @param $sqid
 *   The subqueue to remove nodes from.
 * @param $start
 *   The first position (starting from 1) to remove.
 * @param $end
 *   The last position to remove. If NULL or equal to $start,
 *   only one node will be removed. Thus if $start is 1 and $end is 2,
 *   the first and second items will be removed from the queue.
 *
 */
function nodequeue_subqueue_remove($sqid, $start, $end = NULL) {
  if (!isset($end)) {
    $end = $start;
  }

  // Retrieve the nodes that are being removed.
  $result = db_query("SELECT nid FROM {nodequeue_nodes} WHERE sqid = :sqid AND position >= :start AND position <= :end", array(
    ':sqid' => $sqid,
    ':start' => $start,
    ':end' => $end,
  ));
  $diff = $end - $start + 1;
  db_delete('nodequeue_nodes')
    ->condition('sqid', $sqid)
    ->condition('position', $start, '>=')
    ->condition('position', $end, '<=')
    ->execute();
  db_update('nodequeue_nodes')
    ->expression('position', 'position - ' . $diff)
    ->condition('sqid', $sqid)
    ->condition('position', $end, '>')
    ->execute();

  // Invoke the hook to let other modules know that the nodes were removed.
  foreach ($result as $node) {
    module_invoke_all('nodequeue_remove', $sqid, $node->nid);
  }
}

/**
 * Empty a subqueue.
 *
 * @param $sqid
 *   The sqid to empty.
 */
function nodequeue_queue_clear($sqid) {
  db_delete('nodequeue_nodes')
    ->condition('sqid', $sqid)
    ->execute();
}

/**
 * Guarantee that a subqueue has not gotten too big. It's important to call
 * this after an operation that might have reduced a queue's maximum size.
 * It stores the count to save a query if this is to be followed by an add
 * operation.
 *
 * @param $queue
 *   The queue object.
 * @param $subqueue
 * @param null $size
 *
 * @internal param $reference
 *   The subqueue to check.
 */
function nodequeue_check_subqueue_size($queue, &$subqueue, $size = NULL) {
  if (!isset($size)) {
    $size = $queue->size;
  }
  if ($queue->size && $subqueue->count > $size) {
    if ($queue->insert_at_front) {
      nodequeue_subqueue_remove($subqueue->sqid, $size + 1, $subqueue->count);
    }
    else {
      nodequeue_subqueue_remove($subqueue->sqid, 1, $subqueue->count - $size);
    }
    $subqueue->count = $size;
  }
}

/**
 * Guarantee that all subqueues are within the size constraints set
 * by $queue->size.
 */
function nodequeue_check_subqueue_sizes($queue) {

  // Don't check if size is 0, as that means infinite size.
  if (!$queue->size) {
    return;
  }
  $subqueues = nodequeue_load_subqueues_by_queue($queue->qid);
  foreach ($subqueues as $subqueue) {
    nodequeue_check_subqueue_size($queue, $subqueue);
  }
}

/**
 * Swap two positions within a subqueue.
 */
function nodequeue_queue_swap($subqueue, $pos1, $pos2) {

  // Grab the nid off one of the positions so we can more easily swap.
  $nid = db_query("SELECT nid FROM {nodequeue_nodes} WHERE sqid = :sqid AND position = :position", array(
    ':sqid' => $subqueue->sqid,
    ':position' => $pos1,
  ))
    ->fetchField();
  if (!$nid) {
    return;
  }
  db_update('nodequeue_nodes')
    ->fields(array(
    'position' => $pos1,
  ))
    ->condition('position', $pos2)
    ->condition('sqid', $subqueue->sqid)
    ->execute();
  db_update('nodequeue_nodes')
    ->fields(array(
    'position' => $pos2,
  ))
    ->condition('nid', $nid)
    ->condition('sqid', $subqueue->sqid)
    ->execute();

  // notify other modules of the swap
  module_invoke_all('nodequeue_swap', $subqueue->sqid, $nid);
}

/**
 * Move a position within a subqueue up by one.
 */
function nodequeue_queue_up($subqueue, $position) {
  if ($position < 2 || $position > $subqueue->count) {
    return;
  }
  nodequeue_queue_swap($subqueue, $position - 1, $position);
}

/**
 * Move a position within a subqueue down by one.
 */
function nodequeue_queue_down($subqueue, $position) {
  if ($position < 1 || $position >= $subqueue->count) {
    return;
  }
  nodequeue_queue_swap($subqueue, $position + 1, $position);
}

/**
 * Move an item to the front of the queue.
 */
function nodequeue_queue_front($subqueue, $position) {
  if ($position < 2 || $position > $subqueue->count) {
    return;
  }
  $result = db_query("SELECT * FROM {nodequeue_nodes} WHERE sqid= :sqid AND position = :position", array(
    ':sqid' => $subqueue->sqid,
    ':position' => $position,
  ));
  $entry = $result
    ->fetchObject();
  db_delete('nodequeue_nodes')
    ->condition('sqid', $subqueue->sqid)
    ->condition('position', $position)
    ->execute();
  db_update('nodequeue_nodes')
    ->expression('position', 'position + 1')
    ->condition('sqid', $subqueue->sqid)
    ->condition('position', $position, '<')
    ->execute();
  db_insert('nodequeue_nodes')
    ->fields(array(
    'qid' => $entry->qid,
    'sqid' => $subqueue->sqid,
    'nid' => $entry->nid,
    'position' => 1,
    'timestamp' => $entry->timestamp,
  ))
    ->execute();
}

/**
 * Move an item to the back of the queue.
 */
function nodequeue_queue_back($subqueue, $position) {
  if ($position < 1 || $position >= $subqueue->count) {
    return;
  }
  $result = db_query("SELECT * FROM {nodequeue_nodes} WHERE sqid= :sqid AND position = :position", array(
    ':sqid' => $subqueue->sqid,
    ':position' => $position,
  ));
  $entry = $result
    ->fetchObject();
  db_delete('nodequeue_nodes')
    ->condition('sqid', $subqueue->sqid)
    ->condition('position', $position)
    ->execute();
  db_update('nodequeue_nodes')
    ->expression('position', 'position - 1')
    ->condition('sqid', $subqueue->sqid)
    ->condition('position', $position, '>')
    ->condition('position', $subqueue->count, '<=')
    ->execute();
  db_insert('nodequeue_nodes')
    ->fields(array(
    'qid' => $entry->qid,
    'sqid' => $subqueue->sqid,
    'nid' => $entry->nid,
    'position' => $subqueue->count,
    'timestamp' => $entry->timestamp,
  ))
    ->execute();
}

/**
 * Get the position of a node in a subqueue, or 0 if not found.
 */
function nodequeue_get_subqueue_position($sqid, $nid) {

  // We use MIN to make sure we always get the closest to the front of the
  // queue in case the queue has nodes in it multiple times.
  $pos = db_query("SELECT MIN(position) FROM {nodequeue_nodes} WHERE sqid = :sqid AND nid = :nid", array(
    ':sqid' => $sqid,
    ':nid' => $nid,
  ))
    ->fetchField();
  return $pos;
}

/**
 * Get the position of a node in a subqueue, or FALSE if not found.
 *
 * @deprecated Use nodequeue_get_subqueue_position() instead.
 */
function nodequeue_subqueue_position($sqid, $nid) {
  $pos = nodequeue_get_subqueue_position($sqid, $nid);
  return $pos != 0 ? $pos : FALSE;
}

/**
 * Get the position of a node in several subqueues.
 */
function nodequeue_set_subqueue_positions(&$subqueues, $nid) {
  if (empty($subqueues)) {
    return;
  }
  $query = db_select('nodequeue_nodes', 'n')
    ->fields('n', array(
    'sqid',
  ))
    ->condition('sqid', array_keys($subqueues), 'IN')
    ->condition('nid', $nid)
    ->groupBy('sqid');
  $query
    ->addExpression('MIN(position)', 'position');
  $result = $query
    ->execute();
  foreach ($result as $obj) {
    $subqueues[$obj->sqid]->position = $obj->position;
  }
}

/**
 * Get a list of valid subqueues for a node, along with the position of the node.
 *
 * @param $queues
 *   An array of fully loaded queue objects.
 * @param $node
 *   A fully loaded node object.
 *
 * @return array|object
 *   if references is missing between $queues and $node it will return empty array
 *   else $subqueues.
 */
function nodequeue_get_subqueues_by_node($queues, $node) {

  // Determine which subqueues are valid for each queue.
  $references = array();
  $last_nid =& drupal_static(__FUNCTION__, 0);
  foreach ($queues as $queue) {
    if ($result = nodequeue_api_subqueues($queue, $node)) {
      $references[$queue->qid] = is_array($result) ? $result : array(
        $result,
      );
    }
  }
  if (empty($references)) {
    return array();
  }

  // only allow the static cache to be used if the nid is the same as the last
  $subqueues = nodequeue_load_subqueues_by_reference($references, $last_nid != $node->nid);
  $last_nid = $node->nid;
  return $subqueues;
}

/**
 * Get a textual representation of a nodequeue's queue size.
 */
function nodequeue_subqueue_size_text($max, $count, $long = TRUE) {
  if (empty($count)) {
    $message = theme('nodequeue_subqueue_empty_text');
  }
  elseif ($count == $max) {
    $message = theme('nodequeue_subqueue_full_text');
  }
  else {
    if ($long) {
      $message = theme('nodequeue_subqueue_count_text', array(
        'count' => $count,
      ));
    }
    else {
      $message = $count;
    }
  }
  return $message;
}

/**
 * Substitute the subqueue title into some other string.
 *
 * This function does NOT check_plain the title! The output MUST be checked
 * after this is complete.
 */
function nodequeue_title_substitute($text, $queue, $subqueue) {
  if (empty($text)) {
    return $subqueue->title;
  }
  $text = str_replace('%subqueue', $subqueue->title, $text);
  return $text;
}

/**
 * Shuffle a queue.
 *
 * @param $subqueue
 *   The subqueue to shuffle. May be a sqid or the loaded object.
 */
function nodequeue_subqueue_shuffle($subqueue) {

  // Load the queue
  if (!is_object($subqueue)) {
    $subqueue = nodequeue_load_subqueue($subqueue);
  }
  if (empty($subqueue)) {
    return;
  }
  $count = $subqueue->count;

  // Swap each item with another randomly picked one.
  foreach (range(1, $count) as $i) {
    nodequeue_queue_swap($subqueue, $i, rand(1, $count));
  }
}

/**
 * @} End of defgroup "nodequeue_api"
 */

// --------------------------------------------------------------------------
// Hooks to implement the default nodequeue type.

/**
 * Implements hook_nodequeue_info().
 */
function nodequeue_nodequeue_info() {
  return array(
    'nodequeue' => array(
      'title' => t('Simple queue'),
      'description' => t('Simple queues have just one subqueue. Nodes put into a queue are added to the back of the queue; when a node is added to a full queue, the node in the front of the queue will be popped out to make room.'),
    ),
  );
}

/**
 * Implements hook_nodequeue_form_submit().
 */
function nodequeue_nodequeue_form_submit(&$queue, $form_state) {

  // This will add a single subqueue to our new queue.
  if (!isset($queue->qid) && !isset($queue->add_subqueue)) {

    // A 0 will set the reference to the sqid of the queue.
    $queue->add_subqueue = array(
      0 => $queue->title,
    );
  }

  //If the qid is set at this point, we're saving an existing queue.
  if (isset($queue->qid)) {

    //We don't check to see if the title has been updated since the $queue object already matches $form_state['values'].
    db_update('nodequeue_subqueue')
      ->fields(array(
      'title' => $form_state['values']['title'],
    ))
      ->condition('qid', $queue->qid)
      ->execute();
  }
}

// --------------------------------------------------------------------------
// External queue fetching

/**
 * In general it's preferable to use Views for this functionality.
 */
function nodequeue_node_titles($sqid, $title = '', $backward = TRUE, $from = 0, $count = 0, $published_only = TRUE) {
  $orderby = $backward ? "DESC" : "ASC";
  $query = db_select('node', 'n')
    ->fields('n', array(
    'nid',
    'title',
  ))
    ->condition('nn.sqid', $sqid)
    ->orderBy('nn.position', $orderby)
    ->addTag('node_access');
  $query
    ->leftJoin('nodequeue_nodes', 'nn', 'n.nid = nn.nid');
  if ($published_only) {
    $query
      ->condition('n.status', 1);
  }
  if ($count) {
    $result = $query
      ->range($from, $count)
      ->execute();
  }
  else {
    $result = $query
      ->execute();
  }
  return node_title_list($result, $title);
}

/**
 * Returns an array of nodequeue links for a node.
 */
function nodequeue_node_links($node) {
  $links = array();
  if (variable_get('nodequeue_links', FALSE) && user_access('manipulate queues')) {
    $queues = nodequeue_load_queues_by_type($node->type, 'links');
    $subqueues = nodequeue_get_subqueues_by_node($queues, $node);
    if (empty($subqueues)) {
      return;
    }

    // resort the subqueues to retain consistent ordering:
    ksort($subqueues);

    // Due to caching, we can accidentally get positions leftover
    // from previous iterations on teaser list pages, so we must
    // remove any existing positions here.
    foreach ($subqueues as $id => $subqueue) {
      unset($subqueues[$id]->position);
    }
    if (!module_exists('translation')) {
      nodequeue_set_subqueue_positions($subqueues, $node->nid);
    }
    foreach ($subqueues as $subqueue) {
      $queue = $queues[$subqueue->qid];
      $id = nodequeue_get_content_id($queue, $node);
      if (module_exists('translation')) {
        $subqueue = array(
          $subqueue->sqid => $subqueue,
        );
        nodequeue_set_subqueue_positions($subqueue, $id);
        $subqueue = array_shift($subqueue);
      }
      $query_string = nodequeue_get_query_string($id, TRUE);
      $class = 'nodequeue-ajax-toggle nodequeue-toggle-q-' . $queue->qid . ' nodequeue-toggle-sq-' . $subqueue->sqid . ' nodequeue-toggle-ref-' . $subqueue->reference;
      if (!isset($subqueue->position)) {
        $links[$class] = array(
          'title' => nodequeue_title_substitute($queue->link, $queue, $subqueue),
          'href' => "nodequeue/{$queue->qid}/add-node/{$subqueue->sqid}/{$id}",
          'attributes' => array(
            'class' => array(
              $class . ' toggle-add',
            ),
          ),
          'query' => $query_string,
          'purl' => array(
            'disabled' => TRUE,
          ),
        );
      }
      elseif ($queue->link_remove) {
        $links[$class] = array(
          'title' => nodequeue_title_substitute($queue->link_remove, $queue, $subqueue),
          'href' => "nodequeue/{$queue->qid}/remove-node/{$subqueue->sqid}/{$id}",
          'attributes' => array(
            'class' => array(
              $class . ' toggle-remove',
            ),
          ),
          'query' => $query_string,
          'purl' => array(
            'disabled' => TRUE,
          ),
        );
      }
    }
    drupal_add_js(drupal_get_path('module', 'nodequeue') . '/nodequeue.js');
    drupal_add_css(drupal_get_path('module', 'nodequeue') . '/nodequeue.css');
  }
  return $links;
}

/**
 * Get node_view output from a nodequeue.
 *
 * @param integer $sqid
 *   Subqueue ID
 * @param boolean $backward
 *   If TRUE (default), display the node with the highest queue position first.
 * @param $view_mode
 *   View mode, e.g. 'full', 'teaser'... For backward compatibility, non-string
 *   arguments will be converted to boolean, TRUE implies 'teaser' and FALSE
 *   implies 'full'.
 * @param boolean $links
 *   Deprecated.
 * @param integer $from
 *   Display offset from highest (if $backward is FALSE, lowest) queue position.
 * @param integer $count
 *   Maximum number of nodes to display.
 *
 * @return array
 *   An array as expected by drupal_render().
 */
function nodequeue_view_nodes($sqid, $backward = TRUE, $view_mode = 'teaser', $links = TRUE, $from = 0, $count = 0) {

  // This is for backward compatibility with 'D6 style' argument, which used
  // to be $teaser = TRUE (and previously wasn't converted as necessary in D7
  // version):
  if (!is_string($view_mode)) {
    $view_mode = $view_mode ? 'teaser' : 'full';
  }
  $nodes = nodequeue_load_nodes($sqid, $backward, $from, $count);
  $output = node_view_multiple($nodes, $view_mode);

  // Keep backward compatibility with the previous version of this function, which
  // returned an array of individually built nodes.
  // @todo reevaluate this.
  unset($output['nodes']['#sorted']);
  return array_values($output['nodes']);
}

/**
 * Load an array of node objects belonging to a particular nodequeue.
 *
 * @param integer $sqid
 *   Subqueue ID
 * @param boolean $backward
 *   If TRUE (default), display the node with the highest queue position first.
 * @param integer $from
 *   Display offset from highest (if $backward is FALSE, lowest) queue position.
 * @param integer $count
 *   Maximum number of nodes to display.
 * @param bool $published_only
 *   If TRUE (default), only load published nodes.
 *
 * @return array
 *   An array of node objects indexed by nid.
 */
function nodequeue_load_nodes($sqid, $backward = FALSE, $from = 0, $count = 5, $published_only = TRUE) {
  $orderby = $backward ? "DESC" : "ASC";
  $query = db_select('node', 'n')
    ->fields('n', array(
    'nid',
  ))
    ->condition('nn.sqid', $sqid)
    ->orderBy('nn.position', $orderby)
    ->addTag('node_access');
  $query
    ->join('nodequeue_nodes', 'nn', 'n.nid = nn.nid');
  if ($published_only) {
    $query
      ->condition('n.status', 1);
  }
  if ($count) {
    $result = $query
      ->range($from, $count)
      ->execute()
      ->fetchCol();
  }
  else {
    $result = $query
      ->execute()
      ->fetchCol();
  }
  return node_load_multiple($result);
}

/**
 * Load the first node of a queue.
 */
function nodequeue_load_front($sqid) {
  $nodequeue_nodes = nodequeue_load_nodes($sqid, FALSE, 0, 1);
  return array_shift($nodequeue_nodes);
}

/**
 * Load the last node of a queue.
 */
function nodequeue_load_back($sqid, $teaser = TRUE, $links = TRUE) {
  $nodequeue_nodes = nodequeue_load_nodes($sqid, TRUE, 0, 1);
  return array_shift($nodequeue_nodes);
}

/**
 * View a random node from a queue.
 */
function nodequeue_view_random_node($sqid, $teaser = TRUE, $links = TRUE) {
  $query = db_select('node', 'n')
    ->fields('n', array(
    'nid',
  ));
  $query
    ->join('nodequeue_nodes', 'nn', 'n.nid = nn.nid');
  $count = $query
    ->addTag('node_access')
    ->condition('nn.sqid', $sqid)
    ->condition('n.status', 1)
    ->countQuery()
    ->execute()
    ->fetchField();
  return nodequeue_view_nodes($sqid, FALSE, $teaser, $links, rand(0, $count - 1), 1);
}

/**
 * Load a random node object from a queue.
 */
function nodequeue_load_random_node($sqid) {
  $query = db_select('node', 'n')
    ->fields('n', array(
    'nid',
  ));
  $query
    ->join('nodequeue_nodes', 'nn', 'n.nid = nn.nid');
  $count = $query
    ->addTag('node_access')
    ->condition('nn.sqid', $sqid)
    ->condition('n.status', 1)
    ->countQuery()
    ->execute()
    ->fetchField();
  $nodequeue_nodes = nodequeue_load_nodes($sqid, TRUE, rand(0, $count - 1), 1);
  return array_shift($nodequeue_nodes);
}

/**
 * Get the position of a node in a queue; this queue MUST have only one
 * subqueue or the results of this function will be unpredictable.
 *
 * @param $qid
 *   Queue ID for the queue with the node in it.
 * @param $nid
 *   Node ID for the node you are seeking.
 *
 * return int
 *   Position of the node in the quue.
 */
function nodequeue_queue_position($qid, $nid) {
  $sqid = db_select('nodequeue_subqueue', 'ns')
    ->fields('ns', array(
    'sqid',
  ))
    ->condition('qid', $qid)
    ->range(0, 1)
    ->execute()
    ->fetchField();
  return nodequeue_get_subqueue_position($sqid, $nid);
}

// --------------------------------------------------------------------------
// API for modules implementing subqueues.

/**
 * Send the nodequeue edit form to the owning module for modification.
 *
 * @param $queue
 *   The queue being edited.
 * @param &$form
 *   The form. This may be modified.
 */
function nodequeue_api_queue_form($queue, &$form) {
  $function = $queue->owner . "_nodequeue_form";
  if (function_exists($function)) {
    $function($queue, $form);
  }
}

/**
 * Validate the nodequeue edit form.
 *
 * @param $queue
 *   The queue being edited.
 * @param $form_state
 *   The form values that were submitted.
 * @param &$form
 *   The actual form object. This may be modified.
 */
function nodequeue_api_queue_form_validate($queue, &$form_state, &$form) {
  $function = $queue->owner . "_nodequeue_form_validate";
  if (function_exists($function)) {
    $function($queue, $form_state, $form);
  }
}

/**
 * Send the nodequeue edit form to the owning module upon submit.
 *
 * @param &$queue
 *   The queue being edited. This may be modified prior to being
 *   saved.
 * @param $form_state
 *   The form values that were submitted.
 */
function nodequeue_api_queue_form_submit(&$queue, &$form_state) {
  $function = $queue->owner . "_nodequeue_form_submit";
  if (function_exists($function)) {
    $function($queue, $form_state);
  }
}

/**
 * Send the nodequeue edit form to the owning module after the queue
 * has been saved.
 *
 * @param &$queue
 *   The queue being edited. This may be modified prior to being
 *   saved.
 * @param $form_state
 *   The form values that were submitted.
 */
function nodequeue_api_queue_form_submit_finish($queue, &$form_state) {
  $function = $queue->owner . "_nodequeue_form_submit_finish";
  if (function_exists($function)) {
    $function($queue, $form_state);
  }
}

/**
 * Fetch a list of subqueues that are valid for this node from
 * the owning module.
 *
 * @param $queue
 *   The queue being edited.
 * @param $node
 *   The loaded node object being checked.
 *
 * @return
 *   An array of subqueues. This will be keyed by $sqid.
 */
function nodequeue_api_subqueues(&$queue, $node) {
  $function = $queue->owner . "_nodequeue_subqueues";

  // This will return an array of references.
  if (function_exists($function)) {
    return $function($queue, $node);
  }
  else {
    return $queue->qid;
  }
}

/**
 * Fetch a list of nodes available to a given subqueue
 * for autocomplete.
 *
 * @param $queue
 *   The queue that owns the subqueue
 * @param $subqueue
 *   The subqueue
 * @param $string
 *   The string being matched.
 *
 * @return array
 *   An keyed array $nid => $title
 */
function nodequeue_api_autocomplete($queue, $subqueue, $string) {
  $matches = array();
  if (empty($string)) {
    return $matches;
  }
  $query = db_select('node', 'n')
    ->addTag('node_access')
    ->fields('n', array(
    'nid',
    'tnid',
    'title',
  ))
    ->range(0, variable_get('nodequeue_autocomplete_limit', 10));
  if (!empty($queue->types)) {
    $query
      ->condition('n.type', $queue->types, 'IN');
  }
  global $user;
  if (!user_access('administer nodes', $user)) {
    $query
      ->condition(db_or()
      ->condition('n.status', 1)
      ->condition('n.uid', $user->uid));
  }

  // Run a match to see if they're specifying by nid.
  $preg_matches = array();
  $match = preg_match('/\\[nid: (\\d+)\\]/', $string, $preg_matches);
  if (!$match) {
    $match = preg_match('/^nid: (\\d+)/', $string, $preg_matches);
  }
  if ($match) {

    // If it found a nid via specification, reduce our resultset to just that nid.
    $query
      ->condition('n.nid', $preg_matches[1]);
  }
  else {

    // Build the constant parts of the query.
    $query
      ->where('LOWER(n.title) LIKE LOWER(:string)', array(
      ':string' => '%' . db_like($string) . '%',
    ));
  }
  $query
    ->addMetaData('queue', $queue);
  $query
    ->addMetaData('subqueue', $subqueue);
  $query
    ->addTag('nodequeue_api_autocomplete');
  $query
    ->addTag('i18n_select');
  $result = $query
    ->execute();

  // Call to hook_nodequeue_api_autocomplete.
  $matches = module_invoke_all('nodequeue_api_autocomplete', $queue, $subqueue, $string, $result);
  if (!empty($matches)) {
    return $matches;
  }
  foreach ($result as $node) {
    $id = nodequeue_get_content_id($queue, $node);
    $matches[$node->nid] = check_plain($node->title) . " [nid: {$id}]";
  }
  return $matches;
}

/**
 * Collect info about all of the possible nodequeue types from owning
 * modules.
 */
function nodequeue_api_info() {
  return module_invoke_all('nodequeue_info');
}
function nodequeue_api_queue_access($queue, $account = NULL) {
  if (!$account) {
    global $user;
    $account = $user;
  }
  if ($queue->owner != 'nodequeue') {

    // Avoids an infinite loop.
    $function = $queue->owner . '_queue_access';
    if (function_exists($function)) {
      $access = $function($queue, $account);
    }
  }
  if (!isset($access)) {
    $access = TRUE;
  }
  return $access;
}

/**
 * Allows the owning module of a subqueue to restrict access to viewing and
 * manipulating the queue.
 */
function nodequeue_api_subqueue_access($subqueue, $account = NULL, $queue = NULL) {
  if (!$account) {
    global $user;
    $account = $user;
  }
  if (!$queue) {
    $queue = nodequeue_load($subqueue->qid);
  }
  $function = $queue->owner . '_subqueue_access';
  if (function_exists($function)) {
    $access = $function($subqueue, $account, $queue);
  }
  if (!isset($access)) {
    $access = TRUE;
  }
  return $access;
}

/**
 * Generate a query string to use on nodequeue's private links.
 *
 * @param $seed
 *   The seed to use when generating a token. If NULL no token will
 *   be generated.
 * @param bool $destination
 *   The destination to use. If FALSE one won't be used; if TRUE
 *   one will be generated from drupal_get_destination().
 * @param array $query
 *   An array of additional items to add to the query.
 *
 * @return array|string
 *   The query string suitable for use in the l() function.
 */
function nodequeue_get_query_string($seed, $destination = FALSE, $query = array()) {
  $dest = drupal_get_destination();
  foreach ($dest as $key => $value) {
    $query[$key] = $value;
  }
  if (isset($seed)) {
    $token = explode('=', nodequeue_get_token($seed));
    $query[$token[0]] = $token[1];
  }
  return $query;
  return implode('&', $query);
}

/**
 * Get a private token used to protect nodequeue's links from spoofing.
 */
function nodequeue_get_token($nid) {
  return 'token=' . drupal_get_token($nid);
}

/**
 * Check to see if the token generated from seed matches.
 */
function nodequeue_check_token($seed) {
  return drupal_get_token($seed) == $_GET['token'];
}

/* --- UTILITY -------------------------------------------------------------- */

/**
 * Helper function - since hook_menu now takes a function instead of a boolean,
 * this function is used to compute the user's access.
 *
 * @return boolean
 */
function _nodequeue_access_admin_or_manipulate() {
  return user_access('administer nodequeue') || user_access('manipulate queues') || user_access('manipulate all queues');
}

/**
 * Used by menu system to determine access to the node and the queue in question.
 *
 * No, this isn't some odd hook_access implementation.
 *
 * @param object $node
 * @param object $queue
 * @param object $subqueue
 * @return bool
 */
function nodequeue_node_and_queue_access($node, $queue, $subqueue = NULL) {
  return nodequeue_nodequeue_access($node->type) && nodequeue_queue_access($queue, $subqueue);
}

/**
 * Return TRUE if $user can queue(s) for this node.
 *
 * @param $type
 *   The node type.
 * @param $location
 *   Optional argument. May be one of:
 *   - 'links': Only check for queues that have node links.
 *   - 'tab': Only check for queues that appear on the node tab.
 *   - 'ui': Only check for queues that appear in the UI.
 * @param object $account
 *
 * @return bool
 *   if $qids is empty it will return FALSE else TRUE.
 */
function nodequeue_nodequeue_access($type, $location = NULL, $account = NULL) {
  if (isset($type->type)) {
    $type = $type->type;
  }
  $qids = nodequeue_get_qids($type, $account);
  if ($location) {
    nodequeue_filter_qids($qids, $location);
  }
  return !empty($qids);
}

/**
 * Return TRUE If the specified account has access to manipulate this queue.
 */
function nodequeue_queue_access($queue, $subqueue = NULL, $account = NULL) {
  if (!$account) {
    global $user;
    $account = $user;
  }

  // Automatically true if all queues.
  if (user_access('manipulate all queues', $account)) {
    return TRUE;
  }

  // Automatically false if they can't manipulate queues at all.
  if (!user_access('manipulate queues', $account) || empty($queue->roles)) {
    return FALSE;
  }
  if ($subqueue) {
    return nodequeue_api_subqueue_access($subqueue, $account);
  }
  if (!nodequeue_api_queue_access($queue, $account)) {
    return FALSE;
  }
  $roles = array_keys((array) $account->roles) + array(
    DRUPAL_AUTHENTICATED_RID,
  );
  return (bool) array_intersect($roles, $queue->roles);
}
function nodequeue_node_tab_access($node) {
  if (!variable_get('nodequeue_use_tab', 1) || !user_access('manipulate queues')) {

    // For performance reasons: If the menu tab is disabled or the user can't
    // manipulate queues, there is no reason to run the rest of these queries.
    return FALSE;
  }
  $queues = nodequeue_load_queues_by_type($node->type, 'tab');
  $subqueues = nodequeue_get_subqueues_by_node($queues, $node);
  if (empty($subqueues)) {
    return FALSE;
  }
  foreach ($subqueues as $subqueue) {
    if (nodequeue_api_subqueue_access($subqueue)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Print the JSON output for our AJAX calls.
 */
function nodequeue_js_output($label, $href, $count = NULL, $sqid = NULL) {
  $return = new stdClass();
  $return->status = 1;
  $return->label = check_plain($label);
  $return->href = $href;
  if (isset($count)) {
    $return->count = $count;
  }
  if (isset($sqid)) {
    $return->sqid = $sqid;
  }
  drupal_json_output($return);
  drupal_exit();
}

/**
 * Return content id based on i18n settings
 */
function nodequeue_get_content_id($queue, $node) {
  return $queue->i18n && !empty($node->tnid) ? $node->tnid : $node->nid;
}

/**
 * Determine if the machine name is in use.
 */
function nodequeue_machine_name_exists($machine_name) {
  $queue_exists = db_query_range('SELECT 1 FROM {nodequeue_queue} WHERE name = :name', 0, 1, array(
    ':name' => $machine_name,
  ))
    ->fetchField();
  return $queue_exists;
}

/**
 * Implements hook_nodequeue_add()
 */
function nodequeue_nodequeue_add($sqid, $nid) {
  if (module_exists('rules')) {

    // When a node is added to a nodequeue invoke the corresponding rules event
    $subqueue = nodequeue_load_subqueue($sqid);
    $node = node_load($nid);
    rules_invoke_event('nodequeue_added', $subqueue, $node);
    rules_invoke_event('nodequeue_saved', $subqueue, $node);
  }
}

/**
 * Implements hook_nodequeue_remove().
 */
function nodequeue_nodequeue_remove($sqid, $nid) {
  if (module_exists('rules')) {

    // When a node is removed from a nodequeue invoke the corresponding rules event
    $subqueue = nodequeue_load_subqueue($sqid);
    $node = node_load($nid);
    rules_invoke_event('nodequeue_removed', $subqueue, $node);
    rules_invoke_event('nodequeue_saved', $subqueue, $node);
  }
}

Functions

Namesort descending Description
nodequeue_action_info Implements hook_action_info().
nodequeue_add_subqueue Add a new subqueue to a queue.
nodequeue_admin_paths Implements hook_admin_paths().
nodequeue_apachesolr_index_document_build_node Implements hook_apachesolr_index_document_build_ENTITY_TYPE().
nodequeue_apachesolr_query_alter Implements hook_apachesolr_query_alter().
nodequeue_api_autocomplete Fetch a list of nodes available to a given subqueue for autocomplete.
nodequeue_api_info Collect info about all of the possible nodequeue types from owning modules.
nodequeue_api_queue_access
nodequeue_api_queue_form Send the nodequeue edit form to the owning module for modification.
nodequeue_api_queue_form_submit Send the nodequeue edit form to the owning module upon submit.
nodequeue_api_queue_form_submit_finish Send the nodequeue edit form to the owning module after the queue has been saved.
nodequeue_api_queue_form_validate Validate the nodequeue edit form.
nodequeue_api_subqueues Fetch a list of subqueues that are valid for this node from the owning module.
nodequeue_api_subqueue_access Allows the owning module of a subqueue to restrict access to viewing and manipulating the queue.
nodequeue_check_subqueue_size Guarantee that a subqueue has not gotten too big. It's important to call this after an operation that might have reduced a queue's maximum size. It stores the count to save a query if this is to be followed by an add operation.
nodequeue_check_subqueue_sizes Guarantee that all subqueues are within the size constraints set by $queue->size.
nodequeue_check_token Check to see if the token generated from seed matches.
nodequeue_contextual_links_view_alter Implements hook_contextual_links_view_alter().
nodequeue_delete Delete a nodequeue.
nodequeue_element_info Implements hook_element_info().
nodequeue_filter_qids Filter a list of qids returned by nodequeue_get_qids to a location.
nodequeue_forms Implementats hook_forms().
nodequeue_form_apachesolr_search_bias_form_alter Implements hook_form_FORM_ID_alter().
nodequeue_form_node_type_form_alter Implements hook_form_FORM_ID_alter().
nodequeue_form_node_type_form_submit Submit handler for the node type form.
nodequeue_get_all_qids Get an array of qids using the pager query. This administrative list does no permission checking, so should only be available to users who have passed the 'administer queues' check.
nodequeue_get_content_id Return content id based on i18n settings
nodequeue_get_qids Get an array of qids applicable to this node type.
nodequeue_get_qid_map Return a map of queue name to qid values to aid in various lookups.
nodequeue_get_query_string Generate a query string to use on nodequeue's private links.
nodequeue_get_subqueues_by_node Get a list of valid subqueues for a node, along with the position of the node.
nodequeue_get_subqueue_position Get the position of a node in a subqueue, or 0 if not found.
nodequeue_get_token Get a private token used to protect nodequeue's links from spoofing.
nodequeue_init Implements hook_init().
nodequeue_js_output Print the JSON output for our AJAX calls.
nodequeue_load Load a nodequeue.
nodequeue_load_back Load the last node of a queue.
nodequeue_load_front Load the first node of a queue.
nodequeue_load_nodes Load an array of node objects belonging to a particular nodequeue.
nodequeue_load_queues Load an array of $qids.
nodequeue_load_queues_by_type Fetch a list of available queues for a given location.
nodequeue_load_queue_by_name Return a queue by its machine name. This is obviously not ideal due to the extra queries, but probably preferable to changing current API calls.
nodequeue_load_random_node Load a random node object from a queue.
nodequeue_load_subqueue Load a single subqueue.
nodequeue_load_subqueues Load a list of subqueues.
nodequeue_load_subqueues_by_queue Load the entire set of subqueues for a queue.
nodequeue_load_subqueues_by_reference Load a set of subqueues by reference.
nodequeue_machine_name_exists Determine if the machine name is in use.
nodequeue_menu Implements hook_menu().
nodequeue_nodequeue_access Return TRUE if $user can queue(s) for this node.
nodequeue_nodequeue_add Implements hook_nodequeue_add()
nodequeue_nodequeue_form_submit Implements hook_nodequeue_form_submit().
nodequeue_nodequeue_info Implements hook_nodequeue_info().
nodequeue_nodequeue_remove Implements hook_nodequeue_remove().
nodequeue_node_and_queue_access Used by menu system to determine access to the node and the queue in question.
nodequeue_node_delete Implements hook_node_delete.
nodequeue_node_links Returns an array of nodequeue links for a node.
nodequeue_node_tab_access
nodequeue_node_titles In general it's preferable to use Views for this functionality.
nodequeue_node_update Implements hook_node_update()
nodequeue_node_view Implements hook_node_view().
nodequeue_permission Implements hook_permission().
nodequeue_queue_access Return TRUE If the specified account has access to manipulate this queue.
nodequeue_queue_add Add a node to a queue.
nodequeue_queue_back Move an item to the back of the queue.
nodequeue_queue_clear Empty a subqueue.
nodequeue_queue_down Move a position within a subqueue down by one.
nodequeue_queue_front Move an item to the front of the queue.
nodequeue_queue_position Get the position of a node in a queue; this queue MUST have only one subqueue or the results of this function will be unpredictable.
nodequeue_queue_swap Swap two positions within a subqueue.
nodequeue_queue_up Move a position within a subqueue up by one.
nodequeue_remove_subqueue Remove a subqueue.
nodequeue_save Save a nodequeue. This does not save subqueues; those must be added separately.
nodequeue_search_bias_form_submit Additional submit handler for nodequeue values.
nodequeue_set_subqueue_positions Get the position of a node in several subqueues.
nodequeue_subqueue_add Add a node to a subqueue.
nodequeue_subqueue_position Deprecated Get the position of a node in a subqueue, or FALSE if not found.
nodequeue_subqueue_remove Remove a node or node(s) from a nodequeue by position.
nodequeue_subqueue_remove_node Remove a node from the queue. If a node is in the queue more than once, only the first (closest to 0 position, or the front of the queue) will be removed.
nodequeue_subqueue_shuffle Shuffle a queue.
nodequeue_subqueue_size_text Get a textual representation of a nodequeue's queue size.
nodequeue_subqueue_update_title Change the title of a subqueue.
nodequeue_theme Implements hook_theme().
nodequeue_title_substitute Substitute the subqueue title into some other string.
nodequeue_views_api Implements hook_views_api().
nodequeue_view_nodes Get node_view output from a nodequeue.
nodequeue_view_random_node View a random node from a queue.
position_value If no default value is set for position select boxes, use 1.
process_position Expand position elements into selects. Works like the weight element, except only positive values are allowed.
subqueue_load This function exists so that %subqueue will work in hook_menu.
subqueue_to_arg Helper function for a _menu_translate() bug.
_nodequeue_access_admin_or_manipulate Helper function - since hook_menu now takes a function instead of a boolean, this function is used to compute the user's access.
_nodequeue_solr_qid_key Returns the apachesolr index key for group id.

Constants

Namesort descending Description
NODEQUEUE_DUPLICATE_POSITION
NODEQUEUE_INVALID_NID
NODEQUEUE_INVALID_POSITION
NODEQUEUE_OK @file Maintains queues of nodes in arbitrary order.

Classes

Namesort descending Description
nodequeue_queue The nodequeue queue class; the constructor makes it so we don't have to always check to see if our variables are empty or not.