From 5653e37453d943d94c54d7ad1b833954c9bec783 Mon Sep 17 00:00:00 2001
From: Thomas Gielfeldt <gielfeldt@reload.dk>
Date: Wed, 15 Apr 2015 22:50:26 +0200
Subject: [PATCH] Issue #2467435 by gielfeldt: Add better ctools support.

---
 includes/nodequeue.admin.inc        |  87 +++++++++++++
 nodequeue.module                    |  31 +++++
 plugins/content_types/nodequeue.inc | 202 ++++++++++++++++++++++++++++++
 plugins/contexts/nodequeue.inc      | 239 ++++++++++++++++++++++++++++++++++++
 plugins/relationships/nodequeue.inc |  28 +++++
 5 files changed, 587 insertions(+)
 create mode 100644 plugins/content_types/nodequeue.inc
 create mode 100644 plugins/contexts/nodequeue.inc
 create mode 100644 plugins/relationships/nodequeue.inc

diff --git a/includes/nodequeue.admin.inc b/includes/nodequeue.admin.inc
index 65e8d67..1d121f6 100644
--- a/includes/nodequeue.admin.inc
+++ b/includes/nodequeue.admin.inc
@@ -1144,3 +1144,90 @@ function _nodequeue_autocomplete($sqid, $string) {
   $nodes = nodequeue_api_autocomplete($queue, $subqueue, $string);
   return $nodes;
 }
+
+/**
+ * Page callback for autocomplete on nodequeues.
+ */
+function nodequeue_autocomplete_nodequeue() {
+  $args = func_get_args();
+  $string = implode('/', $args);
+
+  $matches = array();
+  if (empty($string)) {
+    return $matches;
+  }
+
+  $query = db_select('nodequeue_queue', 'n')
+    ->fields('n', array('qid', 'title'))
+    ->range(0, variable_get('nodequeue_autocomplete_nodequeue_limit', 10));
+
+  // Run a match to see if they're specifying by nid.
+  $preg_matches = array();
+  $match = preg_match('/\[qid: (\d+)\]/', $string, $preg_matches);
+  if (!$match) {
+    $match = preg_match('/^qid: (\d+)/', $string, $preg_matches);
+  }
+
+  if ($match) {
+    // If it found a nid via specification, reduce our resultset to just that nid.
+    $query->condition('n.qid', $preg_matches[1]);
+  }
+  else {
+    // Build the constant parts of the query.
+    $query->where('LOWER(n.title) LIKE LOWER(:string)', array(':string' => '%' . db_like($string) . '%'));
+  }
+
+  $result = $query->execute();
+
+  foreach ($result as $queue) {
+    $matches[$queue->qid] = check_plain($queue->title) . " [qid: $queue->qid]";
+  }
+
+  drupal_json_output(drupal_map_assoc($matches));
+}
+
+/**
+ * Page callback for autocomplete on subqueues.
+ */
+function nodequeue_autocomplete_subqueue() {
+  $args = func_get_args();
+  $name = array_shift($args);
+  $string = implode('/', $args);
+
+  $matches = array();
+  if (empty($string)) {
+    return $matches;
+  }
+
+  $query = db_select('nodequeue_subqueue', 'n')
+    ->fields('n', array('sqid', 'title'))
+    ->range(0, variable_get('nodequeue_autocomplete_subqueue_limit', 10));
+
+  if ($name) {
+    $query->condition('n.name', $name);
+  }
+
+  // Run a match to see if they're specifying by nid.
+  $preg_matches = array();
+  $match = preg_match('/\[sqid: (\d+)\]/', $string, $preg_matches);
+  if (!$match) {
+    $match = preg_match('/^sqid: (\d+)/', $string, $preg_matches);
+  }
+
+  if ($match) {
+    // If it found a nid via specification, reduce our resultset to just that nid.
+    $query->condition('n.sqid', $preg_matches[1]);
+  }
+  else {
+    // Build the constant parts of the query.
+    $query->where('LOWER(n.title) LIKE LOWER(:string)', array(':string' => '%' . db_like($string) . '%'));
+  }
+
+  $result = $query->execute();
+
+  foreach ($result as $subqueue) {
+    $matches[$subqueue->sqid] = check_plain($subqueue->title) . " [sqid: $subqueue->sqid]";
+  }
+
+  drupal_json_output(drupal_map_assoc($matches));
+}
diff --git a/nodequeue.module b/nodequeue.module
index fc3dbbb..6e01c50 100644
--- a/nodequeue.module
+++ b/nodequeue.module
@@ -91,6 +91,20 @@ function nodequeue_menu() {
     'file' => 'includes/nodequeue.admin.inc',
     'type' => MENU_CALLBACK
   );
+  $items['nodequeue/nodequeue/autocomplete'] = array(
+    'title' => 'Autocomplete',
+    'page callback' => 'nodequeue_autocomplete_nodequeue',
+    'access callback' => '_nodequeue_access_admin_or_manipulate',
+    'file' => 'includes/nodequeue.admin.inc',
+    'type' => MENU_CALLBACK
+  );
+  $items['nodequeue/subqueue/autocomplete'] = array(
+    'title' => 'Autocomplete',
+    'page callback' => 'nodequeue_autocomplete_subqueue',
+    '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(
@@ -193,6 +207,23 @@ function nodequeue_menu() {
 }
 
 /**
+ * Implements hook_ctools_plugin_directory().
+ */
+function nodequeue_ctools_plugin_directory($owner, $plugin_type) {
+  $supported = array(
+    'ctools' => array(
+      'contexts' => 'contexts',
+      'relationships' => 'relationships',
+      'content_types' => 'content_types',
+    ),
+  );
+
+  if (isset($supported[$owner][$plugin_type])) {
+    return "plugins/" . $supported[$owner][$plugin_type];
+  }
+}
+
+/**
  * Helper function for a _menu_translate() bug.
  */
 function subqueue_to_arg() {
diff --git a/plugins/content_types/nodequeue.inc b/plugins/content_types/nodequeue.inc
new file mode 100644
index 0000000..33a3343
--- /dev/null
+++ b/plugins/content_types/nodequeue.inc
@@ -0,0 +1,202 @@
+<?php
+
+/**
+ * @file
+ * Plugin to add subqueues to panels.
+ */
+
+/**
+ * Plugin description.
+ */
+$plugin = array(
+  'icon' => 'icon_panelizer_form.png',
+  'title' => t('Nodequeue'),
+  'description' => t('Nodequeue.'),
+  'category' => t('Nodequeues'),
+  'all contexts' => TRUE,
+);
+
+/**
+ * Returns a list of content types we provide.
+ */
+function nodequeue_nodequeue_content_type_content_types() {
+  $subtypes = array();
+
+  $queues = nodequeue_load_queues(array_keys(nodequeue_get_all_qids()));
+
+  foreach ($queues as $queue) {
+    if (!empty($queue->name)) {
+      $subtypes['nodequeue:' . $queue->name] = array(
+        'title' => t('Nodequeue') . ': ' . $queue->title,
+        'category' => t('Nodequeues'),
+      );
+    }
+  }
+
+  $subtypes['subqueue:context'] = array(
+    'title' => t('Subqueue'),
+    'category' => t('Nodequeues'),
+    'required context' => new ctools_context_required(t('Subqueue'), 'nodequeue:subqueue'),
+  );
+
+  return $subtypes;
+}
+
+/**
+ * Render pane.
+ */
+function nodequeue_nodequeue_content_type_render($subtype, $conf, $panel_args = array(), $context = NULL) {
+  $block = new stdClass();
+  $block->content = array();
+
+  if (!empty($conf['context']) && $conf['context'] !== 'empty' && !empty($context[$conf['context']])) {
+    $sqid = $context[$conf['context']]->data->sqid;
+  }
+  else {
+    $sqid = $conf['subqueue'];
+  }
+
+  // If no subqueue is available, bail out.
+  if (!$sqid) {
+    return $block;
+  }
+
+  $backward = empty($conf['backward']) ? 0 : $conf['backward'];
+  $view_mode = empty($conf['view_mode']) ? 'teaser' : $conf['view_mode'];
+  $offset = empty($conf['offset']) ? 0 : $conf['offset'];
+  $num_items = empty($conf['num_items']) ? 0 : $conf['num_items'];
+
+  $nodes = nodequeue_load_nodes($sqid, $backward, $offset, $num_items);
+  foreach ($nodes as $node) {
+    $block->content[] = entity_view('node', array($node->nid => $node), $view_mode);
+  }
+
+  return $block;
+}
+
+/**
+ * Returns an edit form for the content_type.
+ */
+function nodequeue_nodequeue_content_type_edit_form($form, &$form_state) {
+
+  $conf = $form_state['conf'];
+
+  $weight = 1;
+
+  $subqueue_options = array();
+  list(,$name) = explode(':', $form_state['subtype_name'], 2);
+  $nodequeue = nodequeue_load_queue_by_name($name);
+  foreach (nodequeue_load_subqueues_by_queue(array($nodequeue->qid)) as $subqueue) {
+    $subqueue_options[$subqueue->sqid] = $subqueue->title . ' (' . $subqueue->count . ')';
+  }
+
+  $form['subqueue'] = array(
+    '#type' => 'select',
+    '#title' => t('Select queue to output'),
+    '#description' => t('Select the subqueue you want to display.'),
+    '#options' => $subqueue_options,
+    '#default_value' => isset($conf['subqueue']) ? $conf['subqueue'] : NULL,
+    '#weight' => $weight++,
+    '#states' => array(
+      'visible' => array(
+        ':input[name="context"]' => array('value' => 'empty'),
+      ),
+    ),
+  );
+
+  $form['num_items'] = array(
+    '#type' => 'select',
+    '#title' => t('Select max number of elements to output'),
+    '#options' => array(t('All')) + drupal_map_assoc(range(1, 30, 1)),
+    '#default_value' => isset($conf['num_items']) ? $conf['num_items'] : 0,
+    '#weight' => $weight++,
+  );
+
+  $form['offset'] = array(
+    '#type' => 'select',
+    '#title' => t('Select number of elements to skip'),
+    '#description' => t('
+        When fetching lists, it can skip a given number of items down the list.
+        This is useful when placing multiple panes with the same queues on a page.'),
+    '#options' => array(t('Display from start')) + drupal_map_assoc(range(1, 30, 1)),
+    '#default_value' => isset($conf['offset']) ? $conf['offset'] : 0,
+    '#weight' => $weight++,
+  );
+
+  $entity_info = entity_get_info('node');
+  $view_mode_options = array();
+  foreach ($entity_info['view modes'] as $view_mode_name => $view_mode_info) {
+    $view_mode_options[$view_mode_name] = $view_mode_info['label'];
+  }
+  $form['view_mode'] = array(
+    '#type' => 'select',
+    '#title' => t('Build mode'),
+    '#description' => t('Select a build mode for this node.'),
+    '#options' => $view_mode_options,
+    '#default_value' => isset($conf['view_mode']) ? $conf['view_mode'] : 'teaser',
+    '#weight' => $weight++,
+  );
+
+  $form['backward'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Backward'),
+    '#default_value' => isset($conf['backward']) ? $conf['backward'] : FALSE,
+    '#weight' => $weight++,
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler for the edit form.
+ */
+function nodequeue_nodequeue_content_type_edit_form_submit(&$form, &$form_state) {
+  // Clean up unwanted form api stuff.
+  form_state_values_clean($form_state);
+
+  $form_state['conf'] = $form_state['values'];
+}
+
+/**
+ * Returns the administrative title.
+ */
+function nodequeue_nodequeue_content_type_admin_title($subtype, $conf) {
+  list($type, $name) = explode(':', $subtype, 2);
+  $queue = $type == 'subqueue' ? (object) array('title' => t('Subqueue')) : nodequeue_load_queue_by_name($name);
+  $queue_title = is_object($queue) ? $queue->title : t('None');
+
+  return t('Nodequeue: @queue_title', array('@queue_title' => $queue_title));
+}
+
+/**
+ * Provide some basic info to the backend.
+ */
+function nodequeue_nodequeue_content_type_admin_info($subtype, $conf) {
+  $block = new stdClass();
+
+  $subqueues = array();
+  if (!empty($conf['context']) && $conf['context'] !== 'empty') {
+    $block->title = t('Derive subqueue from context');
+    return $block;
+  }
+
+  if (!empty($conf['subqueue'])) {
+    $subqueues = nodequeue_load_subqueues(array($conf['subqueue']));
+  }
+
+  if (empty($subqueues)) {
+    $block->title = t('Configured subqueue not found');
+    return $block;
+  }
+
+  $subqueue = current($subqueues);
+
+  $block->title = $subqueue->title;
+  if ($conf['override_title'] && !empty($conf['override_title_text'])) {
+    $block->title .= ' (' . $conf['override_title_text'] . ')';
+  }
+
+  $block->content = 'content';
+
+  return $block;
+}
diff --git a/plugins/contexts/nodequeue.inc b/plugins/contexts/nodequeue.inc
new file mode 100644
index 0000000..ee59c92
--- /dev/null
+++ b/plugins/contexts/nodequeue.inc
@@ -0,0 +1,239 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide a node context. A node context is a node wrapped in a
+ * context object that can be utilized by anything that accepts contexts.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+  'title' => t("Nodequeue"),
+  'description' => t('Nodequeue or subqueue object.'),
+  'convert list' => 'ctools_context_nodequeue_convert_list',
+  'convert' => 'ctools_context_nodequeue_convert',
+  'get child' => 'ctools_context_nodequeue_get_child',
+  'get children' => 'ctools_context_nodequeue_get_children',
+);
+
+function ctools_context_nodequeue_get_child($plugin, $parent, $child) {
+  $plugins = ctools_context_nodequeue_get_children($plugin, $parent);
+  return $plugins[$parent . ':' . $child];
+}
+
+function ctools_context_nodequeue_get_children($plugin, $parent) {
+  $plugins = array(
+    "$parent:nodequeue" => array(
+      'title' => t('Nodequeue'),
+      'keyword' => 'nodequeue',
+      'context name' => t('Nodequeue'),
+      'name' => "$parent:nodequeue",
+      'description' => t('Creates a queue context from a nodequeue ID.'),
+      'edit form' => 'ctools_context_nodequeue_settings_form',
+      'context' => 'ctools_context_create_nodequeue',
+      'defaults' => array('name' => ''),
+    ) + $plugin,
+    "$parent:subqueue" => array(
+      'title' => t('Subqueue'),
+      'keyword' => 'subqueue',
+      'context name' => t('Subqueue'),
+      'name' => "$parent:subqueue",
+      'description' => t('Creates a subqueue context from a subqueue ID.'),
+      'edit form' => 'ctools_context_subqueue_settings_form',
+      'context' => 'ctools_context_create_subqueue',
+      'defaults' => array('sqid' => ''),
+    ) + $plugin,
+  );
+  drupal_alter('ctools_nodequeue_contexts', $plugins);
+  return $plugins;
+}
+
+/**
+ * Provide a list of ways that this context can be converted to a string.
+ */
+function ctools_context_nodequeue_convert_list($plugin) {
+  $plugin_type = $plugin['name'] == 'nodequeue:nodequeue' ? 'nodequeue' : 'subqueue';
+  $list = array();
+  $tokens = token_info();
+  foreach ($tokens['tokens'][$plugin_type] as $id => $info) {
+    if (!isset($list[$id])) {
+      $list[$id] = $info['name'];
+    }
+  }
+  return $list;
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_nodequeue_convert($context, $type, $options = array()) {
+  $plugin_type = $context->plugin == 'nodequeue:nodequeue' ? 'nodequeue' : 'subqueue';
+  $tokens = token_info();
+  if (isset($tokens['tokens'][$plugin_type][$type])) {
+    $values = token_generate($plugin_type, array($type => $type), array($plugin_type => $context->data));
+    if (isset($values[$type])) {
+      return $values[$type];
+    }
+  }
+}
+
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_nodequeue($empty, $data = NULL, $conf = FALSE, $plugin) {
+  $plugin_type = $plugin['keyword'];
+  $context = new ctools_context(array('nodequeue:' . $plugin_type));
+  $context->plugin = $plugin['name'];
+  $context->keyword = $plugin_type;
+
+  if ($empty) {
+    return $context;
+  }
+
+  $data = (array) $data;
+  $context->data = nodequeue_load_queue_by_name($data['name']);
+
+  return $context;
+}
+
+function ctools_context_nodequeue_settings_form($form, &$form_state) {
+  $conf = &$form_state['conf'];
+  $plugin = &$form_state['plugin'];
+
+  $default = $conf['name'] && ($nodequeue = nodequeue_load_queue_by_name($conf['name'])) ? $nodequeue->title . ' [name: ' . $conf['name'] . ']' : '';
+  $form['nodequeue'] = array(
+    '#title' => t('Enter the title or ID of a queue'),
+    '#type' => 'textfield',
+    '#maxlength' => 512,
+    '#autocomplete_path' => 'nodequeue/nodequeue/autocomplete',
+    '#weight' => -10,
+    '#default_value' => $default,
+  );
+  $form['name'] = array(
+    '#type' => 'value',
+    '#value' => $conf['name'],
+  );
+
+  return $form;
+}
+
+/**
+ * Validate a node.
+ */
+function ctools_context_nodequeue_settings_form_validate($form, &$form_state) {
+  // Validate the autocomplete.
+  if (empty($form_state['values']['nodequeue'])) {
+    form_error($form['nodequeue'], t('You must select a nodequeue.'));
+    return;
+  }
+
+  $name = $form_state['values']['nodequeue'];
+  $preg_matches = array();
+  $match = preg_match('/\[name: (\d+)\]/', $name, $preg_matches);
+  if (!$match) {
+    $match = preg_match('/^name: (\d+)/', $name, $preg_matches);
+  }
+
+  if ($match) {
+    $name = $preg_matches[1];
+  }
+  $subqueue = NULL;
+  if ($name) {
+    $subqueue = nodequeue_load_queue_by_name($name);
+  }
+  if (!$subqueue) {
+    form_error($form['nodequeue'], t('Invalid nodequeue selected.'));
+    return;
+  }
+  form_set_value($form['name'], $name, $form_state);
+}
+
+function ctools_context_nodequeue_settings_form_submit($form, &$form_state) {
+  $form_state['conf']['name'] = $form_state['values']['name'];
+}
+
+
+
+
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_subqueue($empty, $data = NULL, $conf = FALSE, $plugin) {
+  $plugin_type = $plugin['keyword'];
+  $context = new ctools_context(array('nodequeue:' . $plugin_type));
+  $context->plugin = $plugin['name'];
+  $context->keyword = $plugin_type;
+
+  if ($empty) {
+    return $context;
+  }
+
+  $data = (array) $data;
+  $context->data = subqueue_load($data['sqid']);
+
+  return $context;
+}
+
+function ctools_context_subqueue_settings_form($form, &$form_state) {
+  $conf = &$form_state['conf'];
+  $plugin = &$form_state['plugin'];
+
+  $default = $conf['sqid'] && ($subqueue = subqueue_load($conf['sqid'])) ? $subqueue->title . ' [sqid: ' . $conf['sqid'] . ']' : '';
+
+  $form['subqueue'] = array(
+    '#title' => t('Enter the title or ID of a subqueue'),
+    '#type' => 'textfield',
+    '#maxlength' => 512,
+    '#autocomplete_path' => 'nodequeue/subqueue/autocomplete/',
+    '#weight' => -10,
+    '#default_value' => $default,
+  );
+  $form['sqid'] = array(
+    '#type' => 'value',
+    '#value' => $conf['sqid'],
+  );
+
+  return $form;
+}
+
+/**
+ * Validate a subqueue.
+ */
+function ctools_context_subqueue_settings_form_validate($form, &$form_state) {
+  // Validate the autocomplete.
+  if (empty($form_state['values']['subqueue'])) {
+    form_error($form['subqueue'], t('You must select a subqueue.'));
+    return;
+  }
+
+  $id = $form_state['values']['subqueue'];
+  $preg_matches = array();
+  $match = preg_match('/\[sqid: (\d+)\]/', $id, $preg_matches);
+  if (!$match) {
+    $match = preg_match('/^sqid: (\d+)/', $id, $preg_matches);
+  }
+
+  if ($match) {
+    $id = $preg_matches[1];
+  }
+  $subqueue = NULL;
+  if (is_numeric($id)) {
+    $subqueue = subqueue_load($id);
+  }
+  if (!$subqueue) {
+    form_error($form['subqueue'], t('Invalid subqueue selected.'));
+    return;
+  }
+  form_set_value($form['sqid'], $id, $form_state);
+}
+
+function ctools_context_subqueue_settings_form_submit($form, &$form_state) {
+  $form_state['conf']['sqid'] = $form_state['values']['sqid'];
+}
diff --git a/plugins/relationships/nodequeue.inc b/plugins/relationships/nodequeue.inc
new file mode 100644
index 0000000..ce2ca2c
--- /dev/null
+++ b/plugins/relationships/nodequeue.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file relationships/nodequeue.inc
+ * Plugin to provide an relationship handler for sub queues.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+  'title' => t('Nodequeue from subqueue'),
+  'keyword' => 'nodequeue',
+  'description' => t('Nodequeue from subqueue.'),
+  'required context' => new ctools_context_required(t('Subqueue'), 'nodequeue:subqueue'),
+  'context' => 'ctools_nodequeue_context',
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_nodequeue_context($context, $conf) {
+  if (!empty($context->data) && ($nodequeue = nodequeue_load($context->data->qid))) {
+    return ctools_context_create('nodequeue:nodequeue', $nodequeue);
+  }
+  return ctools_context_create_empty('nodequeue:nodequeue');
+}
-- 
2.1.2

