Commit 06f6fd84 authored by merlinofchaos's avatar merlinofchaos

Most of user.views.inc plus some bug fixes, improvements to join handling. For...

Most of user.views.inc plus some bug fixes, improvements to join handling. For those interested in the real complexities, check out views_handler_filter_many_to_one to see cool stuff you can do with joins in views 2 that you could not in views 1
parent 0766852e
......@@ -1578,7 +1578,12 @@ function views_ui_add_item_form_submit($form, &$form_state) {
// Store in cache
views_ui_cache_set($form_state['view']);
if (!empty($form_state['id'])) {
$form_state['redirect'] = 'admin/build/views/nojs/config-item/' . $form_state['view']->name . "/$form_state[display_id]/$type/$form_state[id]";
}
else {
$form_state['redirect'] = 'admin/build/views/edit/' . $form_state['view']->name;
}
}
/**
......@@ -1776,7 +1781,7 @@ function views_ui_config_item_form_remove($form, &$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]/$type/$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;
......@@ -2031,3 +2036,36 @@ function views_ui_disable_page($view) {
function views_ui_admin_tools() {
return array('markup' => array('#value' => t('This page is not yet implemented.')));
}
/**
* Page callback for views user autocomplete
*/
function views_ui_autocomplete_user($string = '') {
error_log($string);
// The user enters a comma-separated list of tags. We only autocomplete the last tag.
$array = drupal_explode_tags($string);
// Fetch last tag
$last_string = trim(array_pop($array));
$matches = array();
if ($last_string != '') {
$prefix = count($array) ? implode(', ', $array) . ', ' : '';
if (strpos('anonymous', strtolower($last_string)) !== FALSE) {
$matches[$prefix . 'Anonymous'] = 'Anonymous';
}
$result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $last_string, 0, 10);
while ($account = db_fetch_object($result)) {
$n = $account->name;
// Commas and quotes in terms are special cases, so encode 'em.
if (strpos($account->name, ',') !== FALSE || strpos($account->name, '"') !== FALSE) {
$n = '"'. str_replace('"', '""', $account->name) .'"';
}
$matches[$prefix . $n] = check_plain($account->name);
}
}
drupal_json($matches);
}
......@@ -110,12 +110,29 @@ function views_get_table_join($table, $primary_table) {
* declare this an interface.
*
* Extensions of this class can be used to create more interesting joins.
*
* join definition
* - table: table to join (right table)
* - field: field to join on (right field)
* - left_table: The table we join to
* - left_field: The field we join to
* - type: either LEFT (default) or INNER
* - extra: Either a string that's directly added, or an array of items:
* - - table: if not set, current table; if NULL, no table. This field can't
* be set in the cached definition because it can't know aliases; this field
* can only be used by realtime joins.
* - - field: Field or formula
* - - operator: defaults to =
* - - value: Must be set. If an array, operator will be defaulted to IN.
* - - numeric: If true, the value will not be surrounded in quotes.
* - extra type: How all the extras will be combined. Either AND or OR. Defaults to AND.
*/
class views_join {
/**
* Construct the views_join object.
*/
function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
$this->extra_type = 'AND';
if (!empty($table)) {
$this->table = $table;
$this->left_table = $left_table;
......@@ -134,6 +151,9 @@ class views_join {
if (!empty($this->definition['extra'])) {
$this->extra = $this->definition['extra'];
}
if (!empty($this->definition['extra type'])) {
$this->extra = strtoupper($this->definition['extra_type']);
}
$this->type = !empty($this->definition['type']) ? strtoupper($this->definition['type']) : 'LEFT';
}
......@@ -148,14 +168,44 @@ class views_join {
// Tack on the extra.
if (isset($this->extra)) {
foreach ($this->extra as $field => $value) {
$output .= " AND $table[alias].$field";
if (is_array($this->extra)) {
$extras = array();
foreach ($this->extra as $info) {
$extra = '';
// Figure out the table name. Remember, only use aliases provided
// if at all possible.
$join_table = '';
if (!array_key_exists('table', $info)) {
$join_table = $table['alias'] . '.';
}
elseif (isset($info['table'])) {
$join_table = $info['table'] . '.';
}
// And now deal with the value and the operator
$value = $info['value'];
if (is_array($value) && !empty($value)) {
$output .= " IN ('". implode("','", $value) ."')";
$extra = "$join_table$info[field] IN ('". implode("','", $value) ."')";
}
else {
$operator = "=";
if (!empty($info['operator'])) {
$operator = $info['operator'];
}
if (empty($info['numeric'])) {
$extra .= "$join_table$info[field] $operator '$value'";
}
else {
$extra .= "$join_table$info[field] $operator $value";
}
}
else if ($value !== NULL) {
$output .= " = '$value'";
$extras[] = $extra;
}
$output .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
}
else if (is_string($this->extra)) {
$output .= " AND ($this->extra)";
}
}
return $output;
......@@ -334,8 +384,7 @@ class views_handler extends views_object {
*/
function ensure_my_table() {
if (!isset($this->table_alias)) {
$value = $this->query->ensure_table($this->table, $this->relationship);
$this->table_alias = $value;
$this->table_alias = $this->query->ensure_table($this->table, $this->relationship);
}
return $this->table_alias;
}
......@@ -1746,7 +1795,7 @@ class views_handler_filter_in_operator extends views_handler_filter {
function options(&$options) {
parent::options($options);
$options['operator'] = 'in';
$options['value'] = 0;
$options['value'] = array();
}
/**
......@@ -1769,7 +1818,6 @@ class views_handler_filter_in_operator extends views_handler_filter {
}
function value_submit($form, &$form_state) {
// This is so deeply deeply deeply nested due to the way the form is layered.
$form_state['values']['options']['value'] = array_filter($form_state['values']['options']['value']);
}
......@@ -2115,6 +2163,162 @@ class views_handler_filter_date extends views_handler_filter_numeric {
}
}
/**
* Complex filter to handle filtering for many to one relationships,
* such as terms (many terms per node) or roles (many roles per user).
*
* 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.
*/
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();
}
function options(&$options) {
parent::options($options);
$options['operator'] = 'or';
$options['value'] = array();
$options['depth'] = 0;
}
/**
* Provide inclusive/exclusive matching
*/
function operator_options() {
return array(
'or' => t('Is one of'),
'and' => t('Is all of'),
'not' => t('Is none of'),
);
}
function value_form(&$form, &$form_state) {
$form['value'] = array(
'#type' => 'select',
'#title' => $this->value_title,
'#options' => $this->value_options,
'#multiple' => TRUE,
'#size' => count($this->value_options) > 8 ? 8 : $this->value_options,
'#default_value' => (array) $this->value,
);
}
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.
*/
function ensure_my_table() {
if (!isset($this->table_alias)) {
$base_join = views_get_table_join($this->table, $this->query->primary_table);
if ($this->operator != 'not') {
// If it's an and or an or, we do one join per selected value.
// Clone the join for each table:
$this->table_aliases = array();
foreach ($this->value as $value) {
// Clone to make sure we aren't overwriting previous joins due
// to overzealous references.
$join = drupal_clone($base_join);
$join->extra = array(
array(
'field' => $this->real_field,
'value' => $value,
'numeric' => !empty($this->definition['numeric']),
),
);
$alias = $this->table_aliases[$value] = $this->query->add_table($this->table, $this->relationship, $join, $this->table . '_' . $value);
// and set table_alias to the first of these.
if (empty($this->table_alias)) {
$this->table_alias = $alias;
}
}
}
else {
// For not, we just do one join. We'll add a where clause during
// the query phase to ensure that $table.$field IS NULL.
$join = drupal_clone($base_join); // be safe!
$join->type = 'LEFT';
$join->extra = array();
$join->extra_type = 'OR';
foreach ($this->value as $value) {
$join->extra[] = array(
'field' => $this->field,
'value' => $value,
'numeric' => !empty($this->definition['numeric']),
);
}
$this->table_alias = $this->query->add_table($this->table, $this->relationship, $join);
}
}
return $this->table_alias;
}
function query() {
if (empty($this->value)) {
return;
}
$this->ensure_my_table();
if ($this->operator == 'not') {
$this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field IS NULL");
}
else {
$clauses = array();
foreach ($this->table_aliases as $value => $alias) {
if (!empty($this->definition['numeric'])) {
$clauses[] = "$alias.$this->real_field = %d";
}
else {
$clauses[] = "$alias.$this->real_field = '%s'";
}
}
// implode on either AND or OR.
$this->query->add_where($this->options['group'], implode(' ' . strtoupper($this->operator) . ' ', $clauses), $this->value);
}
}
}
/**
* @}
*/
......
......@@ -20,7 +20,7 @@ function views_views_plugins() {
'handler' => 'views_plugin_display_default',
'no ui' => TRUE,
'no remove' => TRUE,
'js' => array('misc/collapse.js', 'misc/textarea.js', 'misc/tabledrag.js', "$path/dependent.js"),
'js' => array('misc/collapse.js', 'misc/textarea.js', 'misc/tabledrag.js', 'misc/autocomplete.js', "$path/dependent.js"),
'use pager' => TRUE,
),
'page' => array(
......
......@@ -182,6 +182,38 @@ Drupal.Views.Ajax.previewResponse = function(data) {
}
}
Drupal.Views.updatePreviewForm = function() {
var url = $(this).attr('action');
url = url.replace('nojs', 'ajax');
$(this).ajaxSubmit({
url: url,
data: '',
type: 'POST',
success: Drupal.Views.Ajax.previewResponse,
error: function() { alert("An error occurred."); },
dataType: 'json'
});
return false;
}
Drupal.Views.updatePreviewLink = function() {
var url = $(this).attr('href');
url = url.replace('nojs', 'ajax');
$(this).ajaxSubmit({
url: url,
data: '',
type: 'GET',
success: Drupal.Views.Ajax.previewResponse,
error: function() { alert("An error occurred."); },
dataType: 'json'
});
return false;
}
Drupal.behaviors.ViewsAjaxLinks = function() {
// Make specified links ajaxy.
$('a.views-ajax-link:not(.views-processed)').addClass('views-processed').click(function() {
......@@ -230,19 +262,14 @@ Drupal.behaviors.ViewsAjaxLinks = function() {
$('form#views-ui-preview-form:not(.views-processed)')
.addClass('views-processed')
.submit(function() {
var url = $(this).attr('action');
url = url.replace('nojs', 'ajax');
.submit(Drupal.Views.updatePreviewForm);
$(this).ajaxSubmit({
url: url,
data: '',
type: 'POST',
success: Drupal.Views.Ajax.previewResponse,
error: function() { alert("An error occurred."); },
dataType: 'json'
});
$('div#views-live-preview form:not(.views-processed)')
.addClass('views-processed')
.submit(Drupal.Views.updatePreviewForm);
$('div#views-live-preview a:not(.views-processed)')
.addClass('views-processed')
.click(Drupal.Views.updatePreviewLink);
return false;
});
}
......@@ -41,7 +41,9 @@ function node_views_data() {
'field' => 'uid',
// also supported:
// 'type' => 'INNER',
// 'extra' => array('fieldname' => 'value')
// 'extra' => array(array('field' => 'fieldname', 'value' => 'value', 'operator' => '='))
// Unfortunately, you can't specify other tables here, but you can construct
// alternative joins in the handlers that can do that.
// 'table' => 'the actual name of this table in the database',
),
'node_revisions' => array(
......@@ -55,13 +57,14 @@ function node_views_data() {
// Fields
// title
// This definition has more items in it than it needs to as an example.
$data['node']['title'] = array(
'title' => t('Title'), // The item it appears as on the UI,
'help' => t('The title of the node.'), // The help that appears on the UI,
// Information for displaying a title as a field
'field' => array(
'field' => 'title', // the real field
'group' => t('Node'), // The group it appears in on the UI,
'field' => 'title', // the real field. This could be left out since it is the same.
'group' => t('Node'), // The group it appears in on the UI. Could be left out.
'handler' => 'views_handler_field_node',
'click sortable' => TRUE,
),
......@@ -357,6 +360,30 @@ function node_views_data() {
),
);
// nid
$data['node_revisions']['vid'] = array(
'title' => t('Vid'),
'help' => t('The revision ID of the node revision.'), // The help that appears on the UI,
// Information for displaying the nid
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
// Information for accepting a nid as an argument
'argument' => array(
'handler' => 'views_handler_argument_node_vid',
'click sortable' => TRUE,
),
// Information for accepting a nid as a filter
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
// Information for sorting on a nid.
'sort' => array(
'handler' => 'views_handler_sort',
),
);
// title
$data['node_revisions']['title'] = array(
'title' => t('Title'), // The item it appears as on the UI,
......@@ -393,7 +420,7 @@ function node_views_data() {
// revision timestamp
// changed field
$data['node']['changed'] = array(
$data['node_revisions']['timestamp'] = array(
'title' => t('Created date'), // The item it appears as on the UI,
'help' => t('The date the node revision was created.'), // The help that appears on the UI,
'field' => array(
......@@ -440,7 +467,7 @@ function node_views_data() {
'left_field' => 'nid',
'field' => 'nid',
'extra' => array(
'uid' => '***CURRENT_USER***',
array('field' => 'uid', 'value' => '***CURRENT_USER***', 'numeric' => TRUE),
),
),
);
......@@ -462,6 +489,8 @@ function node_views_data() {
/**
* Filter by language
*
* @ingroup views_filter_handlers
*/
class views_handler_filter_node_language extends views_handler_filter_in_operator {
function construct() {
......@@ -681,6 +710,7 @@ class views_handler_field_node_revision_link_revert extends views_handler_field_
function construct() {
parent::construct();
$this->additional_fields['uid'] = array('table' => 'node', 'field' => 'uid');
$this->additional_fields['node_vid'] = array('table' => 'node', 'field' => 'vid');
$this->additional_fields['vid'] = 'vid';
$this->additional_fields['format'] = 'format';
}
......@@ -701,6 +731,11 @@ class views_handler_field_node_revision_link_revert extends views_handler_field_
return;
}
// Current revision cannot be reverted.
if ($node->vid == $values->{$this->aliases['node_vid']}) {
return;
}
$text = !empty($this->options['text']) ? $this->options['text'] : t('revert');
return l($text, "node/$node->nid/revisions/$node->vid/revert", array('query' => drupal_get_destination()));
}
......@@ -715,6 +750,7 @@ class views_handler_field_node_revision_link_delete extends views_handler_field_
function construct() {
parent::construct();
$this->additional_fields['uid'] = array('table' => 'node', 'field' => 'uid');
$this->additional_fields['node_vid'] = array('table' => 'node', 'field' => 'vid');
$this->additional_fields['vid'] = 'vid';
$this->additional_fields['format'] = 'format';
}
......@@ -735,11 +771,20 @@ class views_handler_field_node_revision_link_delete extends views_handler_field_
return;
}
// Current revision cannot be deleted.
if ($node->vid == $values->{$this->aliases['node_vid']}) {
return;
}
$text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
return l($text, "node/$node->nid/revisions/$node->vid/delete", array('query' => drupal_get_destination()));
}
}
/**
* Field handler to display the marker for new content.
*
* @ingroup views_argument_handlers
*/
class views_handler_field_history_user_timestamp extends views_handler_field_node {
function construct() {
parent::construct();
......@@ -862,8 +907,31 @@ class views_handler_argument_node_nid extends views_handler_argument {
}
}
/**
* Argument handler to accept a node revision id.
*
* @ingroup views_argument_handlers
*/
class views_handler_argument_node_vid extends views_handler_argument {
// No constructor is necessary.
/**
* Override the behavior of title(). Get the title of the revision.
*/
function title() {
$title = db_result(db_query(db_rewrite_sql("SELECT n.title FROM {node_revisions} n WHERE n.vid = %d", $this->argument)));
if (empty($title)) {
return t('No title');
}
return check_plain($title);
}
}
/**
* Argument handler for a full date (CCYYMMDD)
*
* @ingroup views_argument_handlers
*/
class views_handler_argument_node_created_fulldate extends views_handler_argument_formula {
/**
......@@ -893,6 +961,8 @@ class views_handler_argument_node_created_fulldate extends views_handler_argumen
/**
* Argument handler for a year (CCYY)
*
* @ingroup views_argument_handlers
*/
class views_handler_argument_node_created_year extends views_handler_argument_formula {
/**
......@@ -906,6 +976,8 @@ class views_handler_argument_node_created_year extends views_handler_argument_fo
/**
* Argument handler for a year plus month (CCYYMM)
*
* @ingroup views_argument_handlers
*/
class views_handler_argument_node_created_year_month extends views_handler_argument_formula {
/**
......@@ -935,6 +1007,8 @@ class views_handler_argument_node_created_year_month extends views_handler_argum
/**
* Argument handler for a month (MM)
*
* @ingroup views_argument_handlers
*/
class views_handler_argument_node_created_month extends views_handler_argument_formula {
/**
......@@ -964,6 +1038,8 @@ class views_handler_argument_node_created_month extends views_handler_argument_f
/**
* Argument handler for a full date (CCYYMMDD)
*
* @ingroup views_argument_handlers
*/
class views_handler_argument_node_created_week extends views_handler_argument_formula {
/**
......@@ -985,6 +1061,8 @@ class views_handler_argument_node_created_week extends views_handler_argument_fo
/**
* Filter by node type
*
* @ingroup views_filter_handlers
*/
class views_handler_filter_node_type extends views_handler_filter_in_operator {
function construct() {
......@@ -1008,6 +1086,8 @@ class views_handler_filter_node_type extends views_handler_filter_in_operator {
/**
* Filter for new content
*
* @ingroup views_filter_handlers
*/
class views_handler_filter_history_user_timestamp extends views_handler_filter {