Initial taxonomy implementation. There's still a ways to go -- I totally punted on...
authorEarl Miles
Mon, 31 Mar 2008 00:18:45 +0000 (00:18 +0000)
committerEarl Miles
Mon, 31 Mar 2008 00:18:45 +0000 (00:18 +0000)
includes/admin.inc
includes/handlers.inc
includes/query.inc
modules/node.views.inc
modules/profile.views.inc
modules/taxonomy.views.inc [new file with mode: 0644]
modules/user.views.inc
views_ui.module

index d09af08..61da34e 100644 (file)
@@ -857,6 +857,10 @@ function template_preprocess_views_ui_edit_item(&$vars) {
     }
     $fields[$id]['info'] = $handler->admin_summary();
 
+    if ($handler->has_extra_options()) {
+      $fields[$id]['links'] = l('<span>' . t('Settings') . '</span>', "admin/build/views/nojs/config-item-extra/$view->name/$display->id/$type/$id", array('attributes' => array('class' => 'views-button-configure views-ajax-link', 'title' => t('Settings')), 'html' => true));
+    }
+
     if ($handler->needs_style_plugin()) {
       $style_plugin = views_fetch_plugin_data('style', $handler->options['style_plugin']);
       $style_title = empty($style_plugin['title']) ? t('Missing style plugin') : $style_plugin['title'];
@@ -1837,6 +1841,109 @@ function views_ui_config_item_form_expose($form, &$form_state) {
 }
 
 /**
+ * Page callback to configure an item
+ */
+function views_ui_config_item_extra($js, $view, $display_id, $type, $id) {
+  views_include('ajax');
+  $form_state = array(
+    'view' => &$view,
+    'ajax' => $js,
+    'display_id' => $display_id,
+    'id' => $id,
+    'type' => $type,
+  );
+
+  $output = views_ajax_form_wrapper('views_ui_config_item_extra_form', $form_state);
+
+  if ($js) {
+    // If we don't have an output object, it was submitted. Set up the submission.
+    if (empty($output)) {
+      // Sometimes we need to re-generate the form for multi-step type operations.
+      return views_ui_regenerate_tabs($view, $display_id);
+    }
+    return views_ajax_render($output);
+  }
+
+  return $output;
+}
+
+/**
+ * Form to config_item items in the views UI.
+ */
+function views_ui_config_item_extra_form(&$form_state) {
+  $view = &$form_state['view'];
+  $display_id = $form_state['display_id'];
+  $type = $form_state['type'];
+  $id = $form_state['id'];
+
+  $form = array('options' => array('#tree' => TRUE));
+  if (!$view->set_display($display_id)) {
+    views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
+  }
+  $item = $view->get_item($display_id, $type, $id);
+
+  if ($item) {
+    $handler = views_get_handler($item['table'], $item['field'], $type);
+    if (empty($handler)) {
+      $form['markup'] = array('#value' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
+      break;
+    }
+    else {
+      $handler->init($view, $item);
+      $types = views_object_types();
+
+      $form['#title'] = check_plain($view->display[$display_id]->display_title) . ': ';
+      $form['#title'] .= t('Configure extra settings for @type "@item"', array('@type' => strtolower($types[$type]['stitle']), '@item' => $handler->ui_name()));
+
+      $form['#section'] = $display_id . '-' . $type . '-' . $id;
+
+      // Get form from the handler.
+      $handler->extra_options_form($form['options'], $form_state);
+        $form_state['handler'] = &$handler;
+
+    }
+
+    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_form', NULL, t('Remove'), 'remove');
+  }
+  return $form;
+}
+
+/**
+ * Submit handler for configing new item(s) to a view.
+ */
+function views_ui_config_item_extra_form_validate($form, &$form_state) {
+  $form_state['handler']->extra_options_validate($form['options'], $form_state);
+}
+
+/**
+ * Submit handler for configing new item(s) to a view.
+ */
+function views_ui_config_item_extra_form_submit($form, &$form_state) {
+  // Run it through the handler's submit function.
+  $form_state['handler']->extra_options_submit($form['options'], $form_state);
+  $item = $form_state['handler']->options;
+
+  // Store the data we're given.
+  foreach ($form_state['values']['options'] as $key => $value) {
+    $item[$key] = $value;
+  }
+
+  // Store the item back on the view
+  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+  views_ui_check_stack($form_state);
+
+  // Write to cache
+  views_ui_cache_set($form_state['view']);
+  if (isset($form_state['next'])) {
+    $form_state['redirect'] = 'admin/build/views/nojs/config-item/' . $form_state['view']->name . "/$form_state[display_id]/$form_state[type]/$form_state[next]";
+  }
+  else {
+    $form_state['redirect'] = 'admin/build/views/edit/' . $form_state['view']->name;
+  }
+}
+
+/**
  * Page callback to change the summary style of an argument
  */
 function views_ui_change_style($js, $view, $display_id, $type, $id) {
index 7fdb4fa..b0b72e6 100644 (file)
@@ -296,6 +296,33 @@ class views_handler extends views_object {
   function options_submit($form, &$form_state) { }
 
   /**
+   * If a handler has 'extra options' it will get a little settings widget and
+   * another form called extra_options.
+   */
+  function has_extra_options() { return FALSE; }
+
+  /**
+   * Provide defaults for the handler.
+   */
+  function extra_options(&$option) { }
+
+  /**
+   * Provide a form for setting options.
+   */
+  function extra_options_form(&$form, &$form_state) { }
+
+  /**
+   * Validate the options form.
+   */
+  function extra_options_validate($form, &$form_state) { }
+
+  /**
+   * Perform any necessary changes to the form values prior to storage.
+   * There is no need for this function to actually store the data.
+   */
+  function extra_options_submit($form, &$form_state) { }
+
+  /**
    * Set new exposed option defaults when exposed setting is flipped
    * on.
    */
@@ -1858,6 +1885,15 @@ class views_handler_filter_in_operator extends views_handler_filter {
   function construct() {
     parent::construct();
     $this->value_title = t('Options');
+    $this->value_options = NULL;
+  }
+
+  /**
+   * Child classes should override this function to set the 'value options'.
+   * This can use a guard to be used to reduce database hits as much as
+   * possible.
+   */
+  function get_value_options() {
     $this->value_options = array(t('Yes'), t('No'));
   }
 
@@ -1878,6 +1914,7 @@ class views_handler_filter_in_operator extends views_handler_filter {
   }
 
   function value_form(&$form, &$form_state) {
+    $this->get_value_options();
     $form['value'] = array(
       '#type' => 'checkboxes',
       '#title' => $this->value_title,
@@ -1895,6 +1932,7 @@ class views_handler_filter_in_operator extends views_handler_filter {
       return t('exposed');
     }
 
+    $this->get_value_options();
     if (count($this->value) == 1) {
       // If there is only one, show it as an =.
       $keys = array_keys($this->value);
@@ -2239,21 +2277,16 @@ class views_handler_filter_date extends views_handler_filter_numeric {
  * The construct method needs to be overridden to provide a list of options;
  * alternately, the value_form and admin_summary methods need to be overriden
  * to provide something that isn't just a select list.
+ *
+ * @todo Should this handler allow an option as to whether or not to avoid
+ * duplicates? That would have an effect on the join, but if you're not
+ * concerned about duplicates you'll get more efficient queries.
  */
-class views_handler_filter_many_to_one extends views_handler_filter {
-  function construct() {
-    parent::construct();
-    // @todo Pull depth information from definition.
-    $this->allow_depth = FALSE;
-    $this->value_title = t('Options');
-    $this->value_options = array();
-  }
-
+class views_handler_filter_many_to_one extends views_handler_filter_in_operator {
   function options(&$options) {
     parent::options($options);
     $options['operator'] = 'or';
     $options['value'] = array();
-    $options['depth'] = 0;
   }
 
   /**
@@ -2268,6 +2301,7 @@ class views_handler_filter_many_to_one extends views_handler_filter {
   }
 
   function value_form(&$form, &$form_state) {
+    $this->get_value_options();
     $form['value'] = array(
       '#type' => 'select',
       '#title' => $this->value_title,
@@ -2278,39 +2312,6 @@ class views_handler_filter_many_to_one extends views_handler_filter {
     );
   }
 
-  function admin_summary() {
-    if (!empty($this->options['exposed'])) {
-      return t('exposed');
-    }
-
-    if (count($this->value) == 1) {
-      // If there is only one, show it as an =.
-      $keys = array_keys($this->value);
-      $key = array_shift($keys);
-      if (!empty($this->value_options[$key])) {
-        $value = check_plain($this->value_options[$key]);
-      }
-      else {
-        $value = t('Unknown');
-      }
-
-      return ($this->operator == 'in' ? '=' : '<>') . ' ' . $value;
-    }
-    $output = '';
-    foreach ($this->value as $value) {
-      if ($output) {
-        $output .= ', ';
-      }
-      if (strlen($output) > 8) {
-        $output .= '...';
-        break;
-      }
-      $output .= check_plain($this->value_options[$value]);
-    }
-
-    return check_plain($this->operator) . ' ' . $output;
-  }
-
   /**
    * Override ensure_my_table so we can control how this joins in.
    * The operator actually has influence over joining.
index 98a446a..ff0ba6c 100644 (file)
@@ -481,6 +481,14 @@ class views_query {
     if (!empty($this->table_queue[$table])) {
       return $this->table_queue[$table];
     }
+
+    // In rare cases we might *only* have aliased versions of the table.
+    if (!empty($this->tables[$this->primary_table][$table])) {
+      $alias = $this->tables[$this->primary_table][$table]['alias'];
+      if (!empty($this->table_queue[$alias])) {
+        return $this->table_queue[$alias];
+      }
+    }
   }
 
   /**
index 1eafa3a..27dfc4f 100644 (file)
@@ -26,7 +26,7 @@ function node_views_data() {
   $data['node']['table']['base'] = array(
     'field' => 'nid',
     'title' => t('Node'),
-    'help' => t('The node type allows views of your site\'s primary content.'),
+    'help' => t('Nodes are a Drupal site\'s primary content.'),
     'weight' => 10,
   );
 
@@ -92,7 +92,7 @@ function node_views_data() {
     // Information for accepting a nid as an argument
     'argument' => array(
       'handler' => 'views_handler_argument_node_nid',
-      'click sortable' => TRUE,
+      'name field' => 'title', // the field to display in the summary.
     ),
     // Information for accepting a nid as a filter
     'filter' => array(
@@ -311,7 +311,7 @@ function node_views_data() {
   $data['node_revisions']['table']['base'] = array(
     'field' => 'vid',
     'title' => t('Node revisions'),
-    'help' => t('Node revisions allow you to list revisions of nodes.'),
+    'help' => t('Node revisions are a history of changes to nodes.'),
   );
 
   // For other base tables, explain how we join
@@ -840,8 +840,6 @@ class views_handler_argument_node_type extends views_handler_argument {
  * @ingroup views_argument_handlers
  */
 class views_handler_argument_node_nid extends views_handler_argument {
-  // No constructor is necessary.
-
   /**
    * Override the behavior of title(). Get the title of the node.
    */
@@ -1008,22 +1006,15 @@ class views_handler_argument_node_created_week extends views_handler_argument_fo
  * @ingroup views_filter_handlers
  */
 class views_handler_filter_node_type extends views_handler_filter_in_operator {
-  function construct() {
-    parent::construct();
-    $this->value_title = t('Node type');
-    $types = node_get_types();
-    foreach ($types as $type => $info) {
-      $options[$type] = $info->name;
+  function get_value_options() {
+    if (!isset($this->value_options)) {
+      $this->value_title = t('Node type');
+      $types = node_get_types();
+      foreach ($types as $type => $info) {
+        $options[$type] = $info->name;
+      }
+      $this->value_options = $options;
     }
-    $this->value_options = $options;
-  }
-
-  function options(&$options) {
-    parent::options($options);
-    // Set the default value to the first node type, so that there is something
-    // reasonable there.
-    $types = array_keys(node_get_types());
-    $options['value'] = array(array_shift($types));
   }
 }
 
@@ -1080,10 +1071,11 @@ class views_handler_filter_history_user_timestamp extends views_handler_filter {
  * @ingroup views_filter_handlers
  */
 class views_handler_filter_node_language extends views_handler_filter_in_operator {
-  function construct() {
-    parent::construct();
-    $this->value_title = t('Node language');
-    $this->value_options = locale_language_list();
+  function get_value_options() {
+    if (empty($this->value_options)) {
+      $this->value_title = t('Node language');
+      $this->value_options = locale_language_list();
+    }
   }
 
   function options(&$options) {
index a61e1db..73c0392 100644 (file)
@@ -330,11 +330,6 @@ class views_handler_field_profile_list extends views_handler_field_prerender_lis
  * @ingroup views_filter_handlers
  */
 class views_handler_filter_profile_selection extends views_handler_filter_in_operator {
-  function construct() {
-    parent::construct();
-    $this->value_options = NULL;
-  }
-
   function get_value_options() {
     if (isset($this->value_options)) {
       return;
@@ -351,23 +346,6 @@ class views_handler_filter_profile_selection extends views_handler_filter_in_ope
       }
     }
   }
-
-  function options(&$options) {
-    parent::options($options);
-    $options['value'] = array();
-  }
-
-  function options_form(&$form, &$form_state) {
-    // Do this only when really needed in order to reduce database hits.
-    $this->get_value_options();
-    parent::options_form($form, $form_state);
-  }
-
-  function admin_summary() {
-    // Do this only when really needed in order to reduce database hits.
-    $this->get_value_options();
-    return parent::admin_summary();
-  }
 }
 
 /**
diff --git a/modules/taxonomy.views.inc b/modules/taxonomy.views.inc
new file mode 100644 (file)
index 0000000..4f8265d
--- /dev/null
@@ -0,0 +1,689 @@
+<?php
+/**
+ * @file
+ *
+ * Provide views data and handlers for taxonomy.module
+ */
+
+/**
+ * @defgroup views_taxonomy_module taxonomy.module handlers
+ *
+ * @{
+ */
+
+/**
+ * Implementation of hook_views_data()
+ */
+function taxonomy_views_data() {
+//  $vocabularies = taxonomy_get_vocabularies();
+
+  $data = array();
+
+  // ----------------------------------------------------------------------
+  // vocabulary table
+
+  $data['vocabulary']['table']['group']  = t('Taxonomy');
+
+  $data['vocabulary']['table']['join'] = array(
+    // vocabulary links to term_data directly via vid.
+    'term_data' => array(
+      'left_field' => 'vid',
+      'field' => 'vid',
+    ),
+    // vocabulary links to node through term_data via vid
+    'node' => array(
+      'left_table' => 'term_data',
+      'left_field' => 'vid',
+      'field' => 'vid',
+    ),
+    // vocabulary links to node_revision via term_data
+    'node_revision' => array(
+      'left_table' => 'term_data',
+      'left_field' => 'vid',
+      'field' => 'vid',
+    ),
+    // vocabulary links to users via term_data
+    'users' => array(
+      'left_table' => 'term_data',
+      'left_field' => 'vid',
+      'field' => 'vid',
+    ),
+  );
+
+  // vocabulary name
+  $data['vocabulary']['name'] = array(
+    'title' => t('Vocabulary name'), // The item it appears as on the UI,
+    'field' => array(
+      'help' => t('Name of the vocabulary a term is a member of. This will be the vocabulary that whichever term the "Taxonomy: Term" field is; and can similarly cause duplicates.'),
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+  );
+  $data['vocabulary']['vid'] = array(
+    'title' => t('Vocabulary ID'), // The item it appears as on the UI,
+    'help' => t('The taxonomy vocabulary ID'),
+    'argument' => array(
+      'handler' => 'views_handler_argument_vocabulary_vid',
+      'name field' => 'name',
+    ),
+  );
+
+  // ----------------------------------------------------------------------
+  // term_data table
+
+  $data['term_data']['table']['group']  = t('Taxonomy');
+  $data['term_data']['table']['base'] = array(
+    'field' => 'tid',
+    'title' => t('Terms'),
+    'help' => t('Taxonomy terms are attached to nodes.'),
+    'weight' => 10,
+  );
+
+
+  // The term data table
+  $data['term_data']['table']['join'] = array(
+    'node' => array(
+      'left_table' => 'term_node',
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+    'node_revision' => array(
+      'left_table' => 'term_node',
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+    'users' => array(
+      'left_table' => 'term_node',
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+  );
+
+  // Term name field
+  $data['term_data']['name'] = array(
+    'title' => t('Term'),
+    'help' => t('Taxonomy terms. Note that using this can cause duplicate nodes to appear in views; you must add filters to reduce the resultset.'),
+    'field' => array(
+      'handler' => 'views_handler_field_taxonomy',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+//    'argument' => array(
+//      'handler' => 'views_handler_argument_term_data_name', // @todo
+//      'help' => t('Taxonomy term name'),
+//    ),
+  );
+
+  // taxonomy weight
+  $data['term_data']['weight'] = array(
+    'title' => t('Weight'),
+    'help' => t('The term weight field'),
+    'field' => array(
+      'handler' => 'views_hander_field',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_hander_sort',
+    ),
+  );
+
+  // Term description
+  $data['term_data']['description'] = array(
+    'title' => t('Term description'), // The item it appears as on the UI,
+    'help' => t('The description associated with a taxonomy term.'),
+    'field' => array(
+      'field' => 'description', // the real field
+      'group' => t('Taxonomy'), // The group it appears in on the UI,
+      'handler' => 'views_handler_field_markup',
+      'format' => FILTER_FORMAT_DEFAULT,
+    ),
+  );
+
+  // Term vocabulary
+  $data['term_data']['vid'] = array(
+    'title' => t('Vocabulary'),
+    'help' => t('Filter the results of "Taxonomy: Term" to a particular vocabulary.'),
+    'filter' => array(
+      'handler' => 'views_handler_filter_vocabulary_vid', // @todo
+    ),
+  );
+
+  // ----------------------------------------------------------------------
+  // term_node table
+
+  $data['term_node']['table']['group']  = t('Taxonomy');
+
+  $data['term_node']['table']['join'] = array(
+    'term_data' => array(
+      // links directly to term_data via tid
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+    'node' => array(
+      // links directly to node via nid
+      'left_field' => 'nid',
+      'field' => 'nid',
+    ),
+    'node_revisions' => array(
+      // links directly to node_revisions via vid
+      'left_field' => 'vid',
+      'field' => 'vid',
+    ),
+    'users' => array(
+      // Links to users via node.nid
+      'left_table' => 'node',
+      'left_field' => 'nid',
+      'field' => 'nid',
+    ),
+  );
+
+  // tid field
+  $data['term_node']['tid'] = array(
+    'title' => t('Term ID'),
+    'help' => t('The taxonomy term ID'),
+    'field' => array(
+      'title' => t('All terms'),
+      'help' => t('Display all taxonomy terms associated with a node.'),
+      'handler' => 'views_handler_field_term_node_tid',
+    ),
+//    'argument' => array(
+//      'handler' => 'views_handler_argument_term_node_tid', // @todo
+//    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_term_node_tid',
+    ),
+  );
+
+  // @todo: term_relation
+  // @todo: term_synonym
+
+  // ----------------------------------------------------------------------
+  // term_hierarchy table
+
+  $data['term_hierarchy']['table']['group']  = t('Taxonomy');
+
+  $data['term_hierarchy']['table']['join'] = array(
+    'term_hierarchy' => array(
+      // links to self through left.tid = right.parent
+      'left_field' => 'tid',
+      'field' => 'right',
+    ),
+    'term_data' => array(
+      // links directly to term_data via tid
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+    'node' => array(
+      // links to node thorugh term_data
+      'left_table' => 'term_data',
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+    'node_revisions' => array(
+      // links to node_revisions thorugh term_data
+      'left_table' => 'term_data',
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+    'users' => array(
+      // links to users thorugh term_data
+      'left_table' => 'term_data',
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+  );
+
+  // ----------------------------------------------------------------------
+  // term_synonym table
+
+  $data['term_synonym']['table']['group']  = t('Taxonomy');
+
+  $data['term_synonym']['table']['join'] = array(
+    'term_data' => array(
+      // links directly to term_data via tid
+      'left_field' => 'tid',
+      'field' => 'tid',
+    ),
+    'node' => array(
+      // links directly to node via nid
+      'left_field' => 'nid',
+      'field' => 'nid',
+    ),
+    'node_revisions' => array(
+      // links directly to node_revisions via vid
+      'left_field' => 'vid',
+      'field' => 'vid',
+    ),
+    'users' => array(
+      // Links to users via node.nid
+      'left_table' => 'node',
+      'left_field' => 'nid',
+      'field' => 'nid',
+    ),
+  );
+
+  $data['term_synonym']['name'] = array(
+    'title' => t('Term synonym'),
+    'help' => t('Term synonyms may be used to find terms by alternate names'),
+//    'argument' => array(
+//      'handler' => 'views_handler_argument_string', // @todo this should be own argument probably
+//    ),
+  );
+  return $data;
+}
+
+class views_handler_field_term_node_tid extends views_handler_field_prerender_list {
+  function init(&$view, $options) {
+    parent::init($view, $options);
+    if ($view->base_table == 'node_revisions') {
+      $this->additional_fields['nid'] = array('table' => 'node_revisions', 'field' => 'vid');
+    }
+    else {
+      $this->additional_fields['nid'] = array('table' => 'node', 'field' => 'nid');
+    }
+  }
+
+  /**
+   * Provide meaningful defaults
+   */
+  function options(&$options) {
+    parent::options($options);
+    $options['link_to_taxonomy'] = TRUE;
+    $options['limit'] = FALSE;
+    $options['vids'] = array();
+  }
+
+  /**
+   * Provide "link to term" option.
+   */
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $form['link_to_taxonomy'] = array(
+      '#title' => t('Link this field to its term page'),
+      '#type' => 'checkbox',
+      '#default_value' => !empty($this->options['link_to_taxonomy']),
+    );
+
+    $form['limit'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Limit terms by vocabulary'),
+      '#default_value'=> $this->options['limit'],
+    );
+
+    $options = array();
+    $vocabularies = taxonomy_get_vocabularies();
+    foreach ($vocabularies as $voc) {
+      $options[$voc->vid] = $voc->name;
+    }
+
+    $form['vids'] = array(
+      '#prefix' => '<div><div id="edit-options-vids">',
+      '#suffix' => '</div></div>',
+      '#type' => 'checkboxes',
+      '#title' => t('Vocabularies'),
+      '#options' => $options,
+      '#default_value' => $this->options['vids'],
+      '#process' => array('expand_checkboxes', 'views_process_dependency'),
+      '#dependency' => array('edit-options-limit' => array(TRUE)),
+    );
+  }
+
+  /**
+   * Add this term to the query
+   */
+  function query() {
+    $this->add_additional_fields();
+  }
+
+  function pre_render($results) {
+    $this->field_alias = $this->aliases['nid'];
+    $nids = array();
+    foreach ($results as $result) {
+      $nids[] = $result->{$this->aliases['nid']};
+    }
+
+    if ($nids) {
+      $vids = '';
+      if (!empty($this->options['limit']) && !empty($this->options['vids'])) {
+        $vids = " AND td.vid IN (" . implode(', ', array_keys(array_filter($this->options['vids']))) . ")";
+      }
+
+      $result = db_query("SELECT tn.nid, td.* FROM {term_data} td INNER JOIN {term_node} tn ON td.tid = tn.tid WHERE tn.nid IN (" . implode(', ', $nids) . ")$vids ORDER BY td.weight, td.name");
+
+      while ($term = db_fetch_object($result)) {
+        if (empty($this->options['link_to_taxonomy'])) {
+          $this->items[$term->nid][$term->tid] = check_plain($term->name);
+        }
+        else {
+          $this->items[$term->nid][$term->tid] = l($term->name, taxonomy_term_path($term));
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Field handler to provide simple renderer that allows linking to a taxonomy
+ * term.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_taxonomy extends views_handler_field {
+  /**
+   * Constructor to provide additional field to add.
+   *
+   * This constructer assumes the term_data table. If using another
+   * table, we'll need to be more specific.
+   */
+  function construct() {
+    parent::construct();
+    $this->additional_fields['vid'] = 'vid';
+    $this->additional_fields['tid'] = 'tid';
+  }
+
+  /**
+   * Provide link to taxonomy option
+   */
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $form['link_to_taxonomy'] = array(
+      '#title' => t('Link this field to its taxonomy term page'),
+      '#type' => 'checkbox',
+      '#default_value' => !empty($this->options['link_to_taxonomy']),
+    );
+  }
+
+  /**
+   * Render whatever the data is as a link to the taxonomy.
+   *
+   * Data should be made XSS safe prior to calling this function.
+   */
+  function render_link($data, $values) {
+    if (!empty($this->options['link_to_taxonomy']) && !empty($values->{$this->aliases['tid']})) {
+      $term = new stdClass();
+      $term->tid = $values->{$this->aliases['tid']};
+      $term->vid = $values->{$this->aliases['vid']};
+
+      return l($data, taxonomy_term_path($term), array('html' => TRUE));
+    }
+    else {
+      return $data;
+    }
+  }
+
+  function render($values) {
+    return $this->render_link(check_plain($values->{$this->field_alias}), $values);
+  }
+}
+
+class views_handler_argument_taxonomy extends views_handler_argument {
+
+  /**
+   * Override the behavior of title(). Get the title of the node.
+   */
+  function title() {
+    $term = taxonomy_get_term($this->argument);
+    if (!empty($term)) {
+      return check_plain($term->name);
+    }
+    // TODO review text
+    return t('No name');
+  }
+}
+
+/**
+ * Overide equality so we can steal some comparison code and just overide the choice form.
+ */
+class views_handler_filter_taxonomy extends views_handler_filter_in_operator  {
+
+  function value_form(&$form, &$form_state) {
+    $options = array();
+    $where = '';
+
+    if (isset($this->definition['vid'])) {
+      $where = "WHERE td.vid = {$this->definition['vid']}";
+    }
+    $result = db_query("SELECT DISTINCT(td.tid), td.name, td.weight, v.name as vocabname, v.weight FROM {term_data} td LEFT JOIN {vocabulary} v ON v.vid = td.vid $where ORDER BY v.weight, v.name, td.weight, td.name");
+    while ($obj = db_fetch_object($result)) {
+      if ($this->definition['vid']) {
+        $options[$obj->tid] = "$obj->name";
+      }
+      else {
+        $options[$obj->tid] = "$obj->vocabname: $obj->name";
+      }
+    }
+
+    $form['value'] = array(
+      '#title' => t('Term'),
+      '#type' => 'select',
+      '#multiple' => TRUE,
+      '#options' => $options,
+      '#default_value' => $options,
+    );
+    return $form;
+  }
+
+  function admin_summary() {
+    $output = '';
+
+    foreach ($this->value as $tid) {
+      $term = taxonomy_get_term($tid);
+      $terms[] = $term->name;
+    }
+    $output .= implode(', ', $terms);
+    return check_plain($this->operator) .' '. check_plain($output);
+  }
+}
+
+/**
+ * Filter by vocabulary id
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_vocabulary_vid extends views_handler_filter_in_operator {
+  function get_value_options() {
+    if (isset($this->value_options)) {
+      return;
+    }
+
+    $this->value_options = array();
+    $vocabularies = taxonomy_get_vocabularies();
+    foreach ($vocabularies as $voc) {
+      $this->value_options[$voc->vid] = $voc->name;
+    }
+  }
+}
+
+/**
+ * Argument handler to accept a user id.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_vocabulary_vid extends views_handler_argument {
+  /**
+   * Override the behavior of title(). Get the name of the user.
+   */
+  function title() {
+    $title = db_result(db_query("SELECT v.name FROM {vocabulary} v WHERE v.vid = %d", $this->argument));
+
+    if (empty($title)) {
+      return t('No vocabulary');
+    }
+
+    return check_plain($title);
+  }
+}
+
+/**
+ * Filter by term id
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_term_node_tid extends views_handler_filter_many_to_one {
+  function has_extra_options() { return TRUE; }
+
+  function get_value_options() { /* don't overwrite the value options */ }
+
+  function options(&$options) {
+    parent::options($options);
+    $options['type'] = 'textfield';
+    $options['vid'] = db_result(db_query('SELECT MIN(vid) FROM {vocabulary} v'));
+  }
+
+  function extra_options_form(&$form, &$form_state) {
+    $vocabularies = taxonomy_get_vocabularies();
+    foreach ($vocabularies as $voc) {
+      $options[$voc->vid] = $voc->name;
+    }
+
+    $form['vid'] = array(
+      '#prefix' => '<div class="views-left-40">',
+      '#suffix' => '</div>',
+      '#type' => 'radios',
+      '#title' => t('Vocabulary'),
+      '#options' => $options,
+      '#description' => t('Select which vocabulary to show terms for in the regular options'),
+      '#default_value' => $this->options['vid'],
+    );
+
+    $form['type'] = array(
+      '#prefix' => '<div class="views-left-40">',
+      '#suffix' => '</div>',
+      '#type' => 'radios',
+      '#title' => t('Selection type'),
+      '#options' => array('select' => t('Dropdown'), 'textfield' => t('Autocomplete')),
+      '#default_value' => $this->options['type'],
+    );
+
+  }
+
+  function value_form(&$form, &$form_state) {
+    $vocabulary = taxonomy_vocabulary_load($this->options['vid']);
+    if (!$vocabulary) {
+      $form['markup'] = array(
+        '#prefix' => '<div class="form-item">',
+        '#suffix' => '</div>',
+        '#value' => t('An invalid vocabulary is selected. Please change it in the options.'),
+      );
+      return;
+    }
+
+    if ($this->options['type'] == 'textfield') {
+      $default = '';
+      if ($this->value) {
+        $result = db_query("SELECT * FROM {term_data} td WHERE td.tid IN (" . implode(', ', $this->value));
+        while ($term = db_fetch_object($result)) {
+          if ($value) {
+            $value .= ', ';
+          }
+          $value .= $term->name;
+        }
+      }
+
+      $form['value'] = array(
+        '#title' => t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)),
+        '#type' => 'textfield',
+        '#autocomplete_path' => 'taxonomy/autocomplete/' . $vocabulary->vid,
+        '#default_value' => $default,
+      );
+    }
+    else {
+      $form['value'] = _taxonomy_term_select(
+        t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)),
+        'value',
+        $this->value,
+        $vocabulary->vid,
+        '',
+        TRUE,
+        NULL
+      );
+      unset($form['value']['#weight']);
+    }
+  }
+
+  function value_validate($form, &$form_state) {
+    if ($this->options['type'] == 'textfield') {
+      $values = drupal_explode_tags($form_state['values']['options']['value']);
+      $tids = array();
+      $placeholders = array();
+      $args = array();
+      $results = array();
+      foreach ($values as $value) {
+        if (strtolower($value) == 'anonymous') {
+          $tids[] = 0;
+        }
+        else {
+          $missing[strtolower($value)] = TRUE;
+          $args[] = $value;
+          $placeholders[] = "'%s'";
+        }
+      }
+
+      $result = db_query("SELECT * FROM {term_data} WHERE name IN (" . implode(', ', $placeholders) . ")", $args);
+      while ($term = db_fetch_object($result)) {
+        unset($missing[strtolower($term->name)]);
+        $tids[] = $term->tid;
+      }
+
+      if ($missing) {
+        form_error($form['value'], t('Unable to find term(s): @terms', array('@terms', implode(', ', $missing))));
+      }
+
+      $form_state['values']['options']['value'] = $tids;
+    }
+  }
+
+  function value_submit($form, &$form_state) {
+    // prevent array_filter from messing up our arrays in parent submit.
+  }
+
+  function admin_summary() {
+    // set up $this->value_options for the parent summary
+    $this->value_options = array();
+
+    if ($this->value) {
+      $result = db_query("SELECT * FROM {term_data} td WHERE td.tid IN ("  . implode(', ', $this->value) . ")");
+
+      while ($term = db_fetch_object($result)) {
+        $this->value_options[$term->tid] = $term->name;
+      }
+    }
+    return parent::admin_summary();
+  }
+
+  /**
+   * Override ensure_my_table so we can control how this joins in.
+   * The operator actually has influence over joining.
+   */
+  function ensure_my_table() {
+    if (!isset($this->table_alias)) {
+      $base_join = views_get_table_join($this->table, $this->query->primary_table);
+      if ($this->operator == 'or') {
+        // For this stuff, we just do a single join, simple join:
+        return views_handler_field::ensure_my_table();
+      }
+      else {
+        // base handling for everything else is ok:
+        return parent::ensure_my_table();
+      }
+    }
+    return $this->table_alias;
+  }
+
+  function query() {
+    if ($this->value && $this->operator == 'or') {
+      $this->ensure_my_table();
+      $this->operator = 'in';
+      views_handler_filter_in_operator::query();
+    }
+    else {
+      return parent::query();
+    }
+  }
+}
+
+/**
+ * @}
+ */
index 024a7a3..f6fd78c 100644 (file)
@@ -22,7 +22,7 @@ function user_views_data() {
   $data['users']['table']['base'] = array(
     'field' => 'uid',
     'title' => t('User'),
-    'help' => t('Allows views of your site\'s users.'),
+    'help' => t('Users who have created accounts on your site.'),
   );
 
   // @todo: restructure this back to normal.
@@ -88,6 +88,7 @@ function user_views_data() {
     // Information for accepting a uid as an argument
     'argument' => array(
       'handler' => 'views_handler_argument_user_uid',
+      'name field' => 'name', // display this field in the summary
     ),
     // Information for accepting a uid as a filter
     'filter' => array(
@@ -471,8 +472,7 @@ class views_handler_field_user_roles extends views_handler_field_prerender_list
 
   function pre_render($results) {
     $uids = array();
-    $roles = array();
-    $this->roles = array();
+    $this->items = array();
 
     foreach ($results as $result) {
       $uids[] = $result->{$this->aliases['uid']};
@@ -603,8 +603,7 @@ class views_handler_filter_user_name extends views_handler_filter_in_operator {
  * @ingroup views_filter_handlers
  */
 class views_handler_filter_user_roles extends views_handler_filter_many_to_one {
-  function construct() {
-    parent::construct();
+  function get_value_options() {
     $this->value_options = user_roles(TRUE);
     unset($this->value_options[DRUPAL_AUTHENTICATED_RID]);
   }
index 1b1f7ac..9a05ff1 100644 (file)
@@ -137,6 +137,10 @@ function views_ui_menu() {
     'page callback' => 'views_ui_config_item',
     'page arguments' => array(3, 5),
   );
+  $items['admin/build/views/%views_ui_js/config-item-extra/%views_ui_cache'] = $callback + array(
+    'page callback' => 'views_ui_config_item_extra',
+    'page arguments' => array(3, 5),
+  );
   // display specific parameters
   $items['admin/build/views/%views_ui_js/display/%views_ui_cache'] = $callback + array(
     'page callback' => 'views_ui_edit_display',