From 09811e62810646b3f90160a18b412163f3686d42 Mon Sep 17 00:00:00 2001
From: Earl Miles <merlin@logrus.com>
Date: Wed, 4 Feb 2009 22:32:34 +0000
Subject: [PATCH] Basic taxonomy_term override plus a bunch of plugins needed
 to allow that to happen.

---
 ctools.module                         |  39 ++++++++
 delegator/plugins/tasks/node_edit.inc |   5 +-
 delegator/plugins/tasks/node_view.inc |   5 +-
 delegator/plugins/tasks/page.inc      |   2 +-
 delegator/plugins/tasks/term_view.inc | 130 ++++++++++++++++++++++++++
 plugins/access/term_vocabulary.inc    |  90 ++++++++++++++++++
 plugins/arguments/string.inc          |  41 ++++++++
 plugins/arguments/terms.inc           |  42 +++++++++
 plugins/contexts/string.inc           |  45 +++++++++
 plugins/contexts/term.inc             |  20 ----
 plugins/contexts/terms.inc            |  56 +++++++++++
 11 files changed, 452 insertions(+), 23 deletions(-)
 create mode 100644 delegator/plugins/tasks/term_view.inc
 create mode 100644 plugins/access/term_vocabulary.inc
 create mode 100644 plugins/arguments/string.inc
 create mode 100644 plugins/arguments/terms.inc
 create mode 100644 plugins/contexts/string.inc

diff --git a/ctools.module b/ctools.module
index f50e288e..fa41de94 100644
--- a/ctools.module
+++ b/ctools.module
@@ -158,3 +158,42 @@ function ctools_cron() {
     ctools_object_cache_clean();
   }
 }
+
+/*
+ * Break x,y,z and x+y+z into an array. Numeric only.
+ *
+ * @param $str
+ *   The string to parse.
+ *
+ * @return $object
+ *   An object containing
+ *   - operator: Either 'and' or 'or'
+ *   - value: An array of numeric values.
+ */
+function ctools_break_phrase($str) {
+  $object = new stdClass();
+
+  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
+    // The '+' character in a query string may be parsed as ' '.
+    $object->operator = 'or';
+    $object->value = preg_split('/[+ ]/', $str);
+  }
+  else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
+    $object->operator = 'and';
+    $object->value = explode(',', $str);
+  }
+
+  // Keep an 'error' value if invalid strings were given.
+  if (!empty($str) && (empty($object->value) || !is_array($object->value))) {
+    $object->value = array(-1);
+    $object->invalid_input = TRUE;
+    return $object;
+  }
+
+  // Doubly ensure that all values are numeric only.
+  foreach ($object->value as $id => $value) {
+    $object->value[$id] = intval($value);
+  }
+
+  return $object;
+}
diff --git a/delegator/plugins/tasks/node_edit.inc b/delegator/plugins/tasks/node_edit.inc
index c6fd11a7..fccda22d 100644
--- a/delegator/plugins/tasks/node_edit.inc
+++ b/delegator/plugins/tasks/node_edit.inc
@@ -10,17 +10,20 @@ function delegator_node_edit_delegator_tasks() {
     'node_edit' => array(
       // This is a 'page' task and will fall under the page admin UI
       'task type' => 'page',
+
       'title' => t('Node edit'),
       'description' => t('The node edit task allows you to control what handler will handle the job of editing a node at the path <em>node/%node/edit</em>. It also handles the node add page at node/add/%type. If no handler is set or matches the criteria, the default Drupal node renderer will be used. Please note that some modules sufficiently customize their node add and edit procedure that this may not successfully override adding or editing of all types.'),
       'admin title' => 'Node edit', // translated by menu system
       'admin description' => 'Overrides for the built in node edit handler at <em>node/%node/edit</em>.',
       'admin path' => 'node/%node/edit',
+
       // Menu hooks so that we can alter the node/%node menu entry to point to us.
       'hook menu' => 'delegator_node_edit_menu',
       'hook menu alter' => 'delegator_node_edit_menu_alter',
+
       // This is task uses 'context' handlers and must implement these to give the
       // handler data it needs.
-      'handler type' => 'context', // handler type -- misnamed
+      'handler type' => 'context',
       'get arguments' => 'delegator_node_edit_get_arguments',
       'get context placeholders' => 'delegator_node_edit_get_contexts',
     ),
diff --git a/delegator/plugins/tasks/node_view.inc b/delegator/plugins/tasks/node_view.inc
index febe5ce9..1081c31d 100644
--- a/delegator/plugins/tasks/node_view.inc
+++ b/delegator/plugins/tasks/node_view.inc
@@ -19,17 +19,20 @@ function delegator_node_view_delegator_tasks() {
     'node_view' => array(
       // This is a 'page' task and will fall under the page admin UI
       'task type' => 'page',
+
       'title' => t('Node view'),
       'description' => t('The node view task allows you to control what handler will handle the job of rendering a node view at the path <em>node/%node</em>. If no handler is set or matches the criteria, the default Drupal node renderer will be used.'),
       'admin title' => 'Node view', // translated by menu system
       'admin description' => 'Overrides for the built in node view handler at <em>node/%node</em>.',
       'admin path' => 'node/%node',
+
       // Menu hooks so that we can alter the node/%node menu entry to point to us.
       'hook menu' => 'delegator_node_view_menu',
       'hook menu alter' => 'delegator_node_view_menu_alter',
+
       // This is task uses 'context' handlers and must implement these to give the
       // handler data it needs.
-      'handler type' => 'context', // handler type -- misnamed
+      'handler type' => 'context',
       'get arguments' => 'delegator_node_view_get_arguments',
       'get context placeholders' => 'delegator_node_view_get_contexts',
     ),
diff --git a/delegator/plugins/tasks/page.inc b/delegator/plugins/tasks/page.inc
index 0bf9dbe9..bc237cfe 100644
--- a/delegator/plugins/tasks/page.inc
+++ b/delegator/plugins/tasks/page.inc
@@ -20,7 +20,7 @@
 function delegator_page_delegator_tasks() {
   return array(
     'page' => array(
-      'title' => t('User pages'),
+      'title' => t('Custom pages'),
       'description' => t('Administrator created pages that have a URL path, access control and entries in the Drupal menu system.'),
       'subtasks' => TRUE,
       'subtask callback' => 'delegator_page_subtask',
diff --git a/delegator/plugins/tasks/term_view.inc b/delegator/plugins/tasks/term_view.inc
new file mode 100644
index 00000000..19e30b60
--- /dev/null
+++ b/delegator/plugins/tasks/term_view.inc
@@ -0,0 +1,130 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Handle the 'term view' override task.
+ *
+ * This plugin overrides term/%term and reroutes it to the delegator, where
+ * a list of tasks can be used to service this request based upon criteria
+ * supplied by access plugins.
+ */
+
+/**
+ * Specialized implementation of hook_delegator_tasks(). See api-task.html for
+ * more information.
+ */
+function delegator_term_view_delegator_tasks() {
+  return array(
+    'term_view' => array(
+      // This is a 'page' task and will fall under the page admin UI
+      'task type' => 'page',
+
+      'title' => t('Taxonomy term view'),
+      'description' => t('Control what handles the job of displaying a term at taxonomy/term/%term.'),
+      'admin title' => 'Taxonomy term view', // translated by menu system
+      'admin description' => 'Overrides for the built in taxonomy term handler at <em>taxonomy/term/%term</em>.',
+      'admin path' => 'taxonomy/term/%term',
+
+      // Menu hooks so that we can alter the term/%term menu entry to point to us.
+      'hook menu' => 'delegator_term_view_menu',
+      'hook menu alter' => 'delegator_term_view_menu_alter',
+
+      // This is task uses 'context' handlers and must implement these to give the
+      // handler data it needs.
+      'handler type' => 'context',
+      'get arguments' => 'delegator_term_view_get_arguments',
+      'get context placeholders' => 'delegator_term_view_get_contexts',
+
+      // Allow additional operations
+      'operations' => array(
+        array(
+          'title' => t('Task handlers'),
+          'href' => "admin/build/delegator/term_view",
+        ),
+/*
+        array(
+          'title' => t('Settings'),
+          'href' => "admin/build/delegator/term_view/settings",
+        ),
+*/
+      ),
+    ),
+  );
+}
+
+/**
+ * Callback defined by delegator_term_view_delegator_tasks().
+ *
+ * Alter the term view input so that term view comes to us rather than the
+ * normal term view process.
+ */
+function delegator_term_view_menu_alter(&$items, $task) {
+  // Override the term view handler for our purpose.
+  $items['taxonomy/term/%']['page callback'] = 'delegator_term_view';
+  $items['taxonomy/term/%']['file path'] = $task['path'];
+  $items['taxonomy/term/%']['file'] = $task['file'];
+}
+
+/**
+ * Entry point for our overridden term view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * term view, which is term_page_view().
+ */
+function delegator_term_view($terms, $depth = 0, $op = 'page') {
+  // While we ordinarily should never actually get feeds through here,
+  // just in case
+  if ($op != 'feed') {
+    // Load my task plugin
+    $task = delegator_get_task('term_view');
+
+    // Load the term into a context.
+    ctools_include('context');
+    ctools_include('context-task-handler');
+    $contexts = ctools_context_handler_get_task_contexts($task, '', array($terms, $depth));
+
+    $output = ctools_context_handler_render($task, '', $contexts);
+    if ($output !== FALSE) {
+      return $output;
+    }
+  }
+
+  // Otherwise, fall back.
+  module_load_include('inc', 'taxonomy', 'taxonomy.pages');
+  return taxonomy_term_page($terms, $depth, $op);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the term view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function delegator_term_view_get_arguments($task, $subtask_id) {
+  return array(
+    array(
+      'keyword' => 'term',
+      'identifier' => t('Term(s) being viewed'),
+      'id' => 0,
+      'name' => 'terms',
+      'settings' => array(),
+    ),
+    array(
+      'keyword' => 'depth',
+      'identifier' => t('Depth'),
+      'id' => 0,
+      'name' => 'string',
+      'settings' => array(),
+    ),
+  );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function delegator_term_view_get_contexts($task, $subtask_id) {
+  return ctools_context_get_placeholders_from_argument(delegator_term_view_get_arguments($task, $subtask_id));
+}
+
diff --git a/plugins/access/term_vocabulary.inc b/plugins/access/term_vocabulary.inc
new file mode 100644
index 00000000..949598fc
--- /dev/null
+++ b/plugins/access/term_vocabulary.inc
@@ -0,0 +1,90 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Plugin to provide access control based upon term vocabulary
+ */
+
+/**
+ * Implementation of specially named hook_ctools_arguments().
+ */
+function ctools_term_vocabulary_ctools_access() {
+  $args['term_vocabulary'] = array(
+    'title' => t("Term access by vocabulary"),
+    'description' => t('Control access by term vocabulary.'),
+    'callback' => 'ctools_term_vocabulary_ctools_access_check',
+    'default' => array('vids' => array()),
+    'settings form' => 'ctools_term_vocabulary_ctools_access_settings',
+    'settings form submit' => 'ctools_term_vocabulary_ctools_access_settings_submit',
+    'summary' => 'ctools_term_vocabulary_ctools_acesss_summary',
+    'required context' => new ctools_context_required(t('Term'), array('term', 'terms')),
+  );
+
+  return $args;
+}
+
+/**
+ * Settings form for the 'by term_vocabulary' access plugin
+ */
+function ctools_term_vocabulary_ctools_access_settings(&$form, &$form_state, $conf) {
+  $options = array();
+  $vocabularies = taxonomy_get_vocabularies();
+  foreach ($vocabularies as $voc) {
+    $options[$voc->vid] = check_plain($voc->name);
+  }
+
+  $form['settings']['vids'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Vocabularies'),
+    '#options' => $options,
+    '#description' => t('Only terms in the checked vocabularies will be valid.'),
+    '#default_value' => $conf['vids'],
+  );
+}
+
+/**
+ * Compress the term_vocabularys allowed to the minimum.
+ */
+function ctools_term_vocabulary_ctools_access_settings_submit(&$form, &$form_state) {
+  $form_state['values']['settings']['vids'] = array_filter($form_state['values']['settings']['vids']);
+}
+
+/**
+ * Check for access.
+ */
+function ctools_term_vocabulary_ctools_access_check($conf, $context) {
+  // As far as I know there should always be a context at this point, but this
+  // is safe.
+  if (empty($context) || empty($context->data) || empty($context->data->vid)) {
+    return FALSE;
+  }
+
+  if (array_filter($conf['vids']) && empty($conf['vids'][$context->data->vid])) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+ * Provide a summary description based upon the checked term_vocabularys.
+ */
+function ctools_term_vocabulary_ctools_acesss_summary($conf, $context) {
+  if (!isset($conf['type'])) {
+    $conf['type'] = array();
+  }
+  $vocabularies = taxonomy_get_vocabularies();
+
+  $names = array();
+  foreach (array_filter($conf['vids']) as $vid) {
+    $names[] = check_plain($vocabularies[$vid]->name);
+  }
+
+  if (empty($names)) {
+    return t('@identifier can be any vocabulary', array('@identifier' => $context->identifier));
+  }
+
+  return format_plural(count($names), '@identifier can be vocabulary "@vids"', '@identifier can be vocabulary "@vids"', array('@vids' => implode(', ', $names), '@identifier' => $context->identifier));
+}
+
diff --git a/plugins/arguments/string.inc b/plugins/arguments/string.inc
new file mode 100644
index 00000000..0bb64653
--- /dev/null
+++ b/plugins/arguments/string.inc
@@ -0,0 +1,41 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a raw string
+ */
+
+/**
+ * Implementation of specially named hook_ctools_arguments().
+ */
+function ctools_string_ctools_arguments() {
+  $args['string'] = array(
+    'title' => t("String"),
+    // keyword to use for %substitution
+    'keyword' => 'string',
+    'description' => t('A string is a minimal context that simply holds a string that can be used for some other purpose.'),
+    'context' => 'ctools_string_context',
+  );
+  return $args;
+}
+
+/**
+ * Discover if this argument gives us the term we crave.
+ */
+function ctools_string_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+  // If unset it wants a generic, unfilled context.
+  if ($empty) {
+    return ctools_context_create_empty('string');
+  }
+
+  $string = ctools_break_phrase($arg);
+  if (empty($string->value) || !empty($string->invalid_input)) {
+    return FALSE;
+  }
+
+  $context = ctools_context_create('string', $string);
+  $context->original_argument = $arg;
+  return $context;
+}
diff --git a/plugins/arguments/terms.inc b/plugins/arguments/terms.inc
new file mode 100644
index 00000000..1977ad30
--- /dev/null
+++ b/plugins/arguments/terms.inc
@@ -0,0 +1,42 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a Taxonomy term
+ */
+
+/**
+ * Implementation of specially named hook_ctools_arguments().
+ */
+function ctools_terms_ctools_arguments() {
+  $args['terms'] = array(
+    'title' => t("Taxonomy term (multiple)"),
+    // keyword to use for %substitution
+    'keyword' => 'term',
+    'description' => t('Creates a group of taxonomy terms from a list of tids separated by a comma or a plus sign. In general the first term of the list will be used for panes.'),
+    'context' => 'ctools_terms_context',
+    'settings form' => 'ctools_terms_settings_form',
+  );
+  return $args;
+}
+
+/**
+ * Discover if this argument gives us the term we crave.
+ */
+function ctools_terms_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+  // If unset it wants a generic, unfilled context.
+  if ($empty) {
+    return ctools_context_create_empty('terms');
+  }
+
+  $terms = ctools_break_phrase($arg);
+  if (empty($terms->value) || !empty($terms->invalid_input)) {
+    return FALSE;
+  }
+
+  $context = ctools_context_create('terms', $terms);
+  $context->original_argument = $arg;
+  return $context;
+}
diff --git a/plugins/contexts/string.inc b/plugins/contexts/string.inc
new file mode 100644
index 00000000..d07abfcb
--- /dev/null
+++ b/plugins/contexts/string.inc
@@ -0,0 +1,45 @@
+<?php
+// $Id$
+
+
+/**
+ * @file
+ *
+ * Plugin to provide a string context
+ */
+
+/**
+ * Implementation of specially named hook_ctools_contexts().
+ */
+function ctools_string_ctools_contexts() {
+  $args['string'] = array(
+    'title' => t('String'),
+    'description' => t('A context that is just a string.'),
+    'context' => 'ctools_context_create_string',
+    'keyword' => 'string',
+    'no ui' => TRUE,
+    'context name' => 'string',
+  );
+  return $args;
+}
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_string($empty, $data = NULL, $conf = FALSE) {
+  // The input is expected to be an object as created by ctools_break_phrase
+  // which contains a group of string.
+
+  $context = new ctools_context('string');
+  $context->plugin = 'string';
+
+  if ($empty) {
+    return $context;
+  }
+
+  if (!empty($data) && is_object($data)) {
+    $context->data = $data;
+    return $context;
+  }
+}
diff --git a/plugins/contexts/term.inc b/plugins/contexts/term.inc
index d8c60158..8b174e74 100644
--- a/plugins/contexts/term.inc
+++ b/plugins/contexts/term.inc
@@ -1,7 +1,6 @@
 <?php
 // $Id$
 
-
 /**
  * @file
  *
@@ -48,22 +47,3 @@ function ctools_context_create_term($empty, $data = NULL, $conf = FALSE) {
     return $context;
   }
 }
-
-function ctools_context_term_settings_form($conf, $external = FALSE) {
-  $form = array();
-  if ($external) {
-    $form['external'] = array(
-      '#type' => 'checkbox',
-      '#default_value' => $conf['external'],
-      '#title' => t('Require this context from an external source (such as a containing panel page).'),
-      '#description' => t('If selected, term selection (below) will be ignored'),
-    );
-  }
-  return $form;
-}
-
-/**
- * Validate a term.
- */
-function ctools_context_term_settings_form_validate($form, &$form_values, &$form_state) {
-}
diff --git a/plugins/contexts/terms.inc b/plugins/contexts/terms.inc
index e69de29b..71168318 100644
--- a/plugins/contexts/terms.inc
+++ b/plugins/contexts/terms.inc
@@ -0,0 +1,56 @@
+<?php
+// $Id$
+
+
+/**
+ * @file
+ *
+ * Plugin to provide a terms context
+ */
+
+/**
+ * Implementation of specially named hook_ctools_contexts().
+ */
+function ctools_terms_ctools_contexts() {
+  $args['terms'] = array(
+    'title' => t("Taxonomy terms"),
+    'description' => t('Multiple taxonomy terms, as a group.'),
+    'context' => 'ctools_context_create_terms',
+    'settings form' => 'ctools_context_terms_settings_form',
+    'settings form validate' => 'ctools_context_terms_settings_form_validate',
+    'keyword' => 'terms',
+    'no ui' => TRUE,
+    'context name' => 'terms',
+  );
+  return $args;
+}
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_terms($empty, $data = NULL, $conf = FALSE) {
+  // The input is expected to be an object as created by ctools_break_phrase
+  // which contains a group of terms.
+
+  $context = new ctools_context(array('terms', 'term'));
+  $context->plugin = 'terms';
+
+  if ($empty) {
+    return $context;
+  }
+
+  if (!empty($data) && is_object($data)) {
+    $context->operator = $data->operator;
+    $context->tids     = $data->value;
+    if (!isset($data->term)) {
+      // load the first term:
+      reset($context->tids);
+      $data->term = taxonomy_get_term(current($context->tids));
+    }
+    $context->data     = $data->term;
+    $context->title    = $data->term->name;
+    $context->argument = implode($context->operator == 'or' ? '+' : ',', $context->tids);
+    return $context;
+  }
+}
-- 
GitLab