summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEarl Miles2009-11-10 23:20:06 (GMT)
committer Earl Miles2009-11-10 23:20:06 (GMT)
commitd89ada2bade8c35cca20a3e403e85858686ba24d (patch)
treee0ff3de332405401e180cb5db192f372b1ff2c0a
parent48c38e98710d75799afce8b54db73b3b5490c77a (diff)
#396380 by merlinofchaos, dereine and dagmar: Initial support for GROUP BY queries!!!!!!!!!!!!
-rw-r--r--CHANGELOG.txt2
-rw-r--r--handlers/views_handler_argument.inc4
-rw-r--r--handlers/views_handler_field.inc15
-rw-r--r--handlers/views_handler_filter.inc2
-rw-r--r--handlers/views_handler_filter_string.inc2
-rw-r--r--includes/admin.inc123
-rw-r--r--includes/handlers.inc86
-rw-r--r--includes/view.inc23
-rw-r--r--modules/comment.views.inc1
-rw-r--r--modules/node/views_handler_field_node.inc16
-rw-r--r--modules/profile.views.inc1
-rw-r--r--modules/taxonomy.views.inc3
-rw-r--r--modules/upload.views.inc1
-rw-r--r--modules/user.views.inc1
-rw-r--r--plugins/views_plugin_display.inc49
-rw-r--r--plugins/views_plugin_query.inc13
-rw-r--r--plugins/views_plugin_query_default.inc155
-rw-r--r--views.module34
-rw-r--r--views_ui.module1
19 files changed, 427 insertions, 105 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index b81a4ca..9f3ea1e 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,4 +1,6 @@
CHANGELOG for Views 3 for Drupal 7
+Views 6.x-3.x-dev
+ o #396380 by merlinofchaos, dereine and dagmar: Initial support for GROUP BY queries!!!!!!!!!!!!
Views 6.x-3.0-alpha1
Bugs fixed
diff --git a/handlers/views_handler_argument.inc b/handlers/views_handler_argument.inc
index 14774ee..11ac931 100644
--- a/handlers/views_handler_argument.inc
+++ b/handlers/views_handler_argument.inc
@@ -121,7 +121,7 @@ class views_handler_argument extends views_handler {
'#default_value' => $this->options['title'],
'#description' => t('The title to use when this argument is present. It will override the title of the view and titles from previous arguments. You can use percent substitution here to replace with argument titles. Use "%1" for the first argument, "%2" for the second, etc.'),
);
-
+
$form['breadcrumb'] = array(
'#prefix' => '<div class="clear-block">',
'#suffix' => '</div>',
@@ -714,7 +714,7 @@ class views_handler_argument extends views_handler {
if (isset($this->argument)) {
return $this->argument;
}
-
+
// Otherwise, we have to pretend to process ourself to find the value.
$value = NULL;
// Find the position of this argument within the view.
diff --git a/handlers/views_handler_field.inc b/handlers/views_handler_field.inc
index 62b3079..e53df5a 100644
--- a/handlers/views_handler_field.inc
+++ b/handlers/views_handler_field.inc
@@ -94,7 +94,12 @@ class views_handler_field extends views_handler {
else {
$table_alias = $this->table_alias;
}
- $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field']);
+ $params = array();
+ if (!empty($info['params'])) {
+ $params = $info['params'];
+ }
+
+ $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field'], NULL, $params);
}
else {
$this->aliases[$info] = $this->query->add_field($this->table_alias, $info);
@@ -173,6 +178,8 @@ class views_handler_field extends views_handler {
* should have.
*/
function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Label'),
@@ -272,7 +279,7 @@ class views_handler_field extends views_handler {
'edit-options-alter-make-link' => array(1)
),
);
-
+
// Get a list of the available fields and arguments for token replacement.
$options = array();
@@ -458,7 +465,7 @@ class views_handler_field extends views_handler {
$this->original_value = $this->last_render;
$alter = $item + $this->options['alter'];
- $items[] = $this->render_text($alter);
+ $items[] = $this->render_text($alter);
}
$value = $this->render_items($items);
@@ -511,7 +518,7 @@ class views_handler_field extends views_handler {
return $value;
}
-
+
/**
* Render this field as altered text, from a fieldset set by the user.
*/
diff --git a/handlers/views_handler_filter.inc b/handlers/views_handler_filter.inc
index afb70fc..50056f2 100644
--- a/handlers/views_handler_filter.inc
+++ b/handlers/views_handler_filter.inc
@@ -271,7 +271,7 @@ class views_handler_filter extends views_handler {
if (!empty($form_state['values']['options']['expose']['identifier']) && $form_state['values']['options']['expose']['identifier'] == 'value') {
form_error($form['expose']['identifier'], t('This identifier is not allowed.'));
}
-
+
if (!$this->view->display_handler->is_identifier_unique($form_state['id'], $form_state['values']['options']['expose']['identifier'])) {
form_error($form['expose']['identifier'], t('This identifier is used by another handler.'));
}
diff --git a/handlers/views_handler_filter_string.inc b/handlers/views_handler_filter_string.inc
index e518c9f..994d164 100644
--- a/handlers/views_handler_filter_string.inc
+++ b/handlers/views_handler_filter_string.inc
@@ -300,7 +300,7 @@ class views_handler_filter_string extends views_handler_filter {
function op_not($field, $upper) {
$this->query->add_where($this->options['group'], "$upper(%s) NOT LIKE $upper('%%%s%%')", $field, $this->value);
}
-
+
function op_shorter($field, $upper) {
$this->query->add_where($this->options['group'], "LENGTH($upper(%s)) < %d", $field, $this->value);
}
diff --git a/includes/admin.inc b/includes/admin.inc
index 9c95b4a..6885c2f 100644
--- a/includes/admin.inc
+++ b/includes/admin.inc
@@ -1226,28 +1226,21 @@ function template_preprocess_views_ui_edit_item(&$vars) {
// Get relationship labels
$relationships = array();
// @todo: get_handlers()
- foreach ($display->handler->get_option('relationships') as $id => $relationship) {
- $handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
- if (empty($handler)) {
- continue;
- }
- $handler->init($view, $relationship);
+ foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
$relationships[$id] = $handler->label();
}
}
- // @todo: get_handlers()
foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
$fields[$id] = array();
- $handler = views_get_handler($field['table'], $field['field'], $type);
+ $handler = $display->handler->get_handler($type, $id);
if (empty($handler)) {
$fields[$id]['class'] = 'broken';
$fields[$id]['title'] = t("Error: handler for @table > @field doesn't exist!", array('@table' => $field['table'], '@field' => $field['field']));
$fields[$id]['info'] = '';
continue;
}
- $handler->init($view, $field);
$field_name = $handler->ui_name(TRUE);
if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
@@ -1261,6 +1254,9 @@ function template_preprocess_views_ui_edit_item(&$vars) {
}
$fields[$id]['info'] = $handler->admin_summary();
+ if ($display->handler->use_group_by()) {
+ $fields[$id]['links'] = l('<span>' . t('Group settings') . '</span>', "admin/build/views/nojs/config-item-group/$view->name/$display->id/$type/$id", array('attributes' => array('class' => 'views-button-configure views-ajax-link', 'title' => t('Group settings')), 'html' => true));
+ }
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));
}
@@ -1455,6 +1451,10 @@ function views_ui_ajax_forms($key = NULL) {
'form_id' => 'views_ui_config_item_form',
'args' => array('type', 'id'),
),
+ 'config-item-group' => array(
+ 'form_id' => 'views_ui_config_item_group_form',
+ 'args' => array('type', 'id'),
+ ),
'config-item-extra' => array(
'form_id' => 'views_ui_config_item_extra_form',
'args' => array('type', 'id'),
@@ -2014,17 +2014,10 @@ function views_ui_rearrange_form(&$form_state) {
// Get relationship labels
$relationships = array();
- // @todo: get_handlers()
- foreach ($display->handler->get_option('relationships') as $id => $relationship) {
- $handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
- if (empty($handler)) {
- continue;
- }
- $handler->init($view, $relationship);
+ foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
$relationships[$id] = $handler->label();
}
- // @todo: get_handlers()
foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
$form[$id] = array('#tree' => TRUE);
$form[$id]['weight'] = array(
@@ -2032,9 +2025,8 @@ function views_ui_rearrange_form(&$form_state) {
'#delta' => 25,
'#default_value' => ++$count,
);
- $handler = views_get_handler($field['table'], $field['field'], $type);
+ $handler = $display->handler->get_handler($type, $id);
if ($handler) {
- $handler->init($view, $field);
$name = $handler->ui_name() . ' ' . $handler->admin_summary();
if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
$name = '(' . $relationships[$field['relationship']] . ') ' . $name;
@@ -2155,7 +2147,7 @@ function views_ui_add_item_form(&$form_state) {
// Figure out all the base tables allowed based upon what the relationships provide.
$base_tables = $view->get_base_tables();
- $options = views_fetch_fields(array_keys($base_tables), $type);
+ $options = views_fetch_fields(array_keys($base_tables), $type, $display->handler->use_group_by());
if (!empty($options)) {
$groups = array('all' => t('<All>'));
@@ -2220,6 +2212,11 @@ function views_ui_add_item_form_submit($form, &$form_state) {
list($table, $field) = explode('.', $field, 2);
$id = $form_state['view']->add_item($form_state['display_id'], $type, $table, $field);
+ // check to see if we have group by settings
+ if ($form_state['view']->display_handler->use_group_by()) {
+ views_ui_add_form_to_stack('config-item-group', $form_state['view'], $form_state['display_id'], array($type, $id));
+ }
+
// check to see if this type has settings, if so add the settings form first
$handler = views_get_handler($table, $field, $type);
if ($handler && $handler->has_extra_options()) {
@@ -2251,12 +2248,11 @@ function views_ui_config_item_form(&$form_state) {
$item = $view->get_item($display_id, $type, $id);
if ($item) {
- $handler = views_get_handler($item['table'], $item['field'], $type);
+ $handler = $view->display_handler->get_handler($type, $id);
if (empty($handler)) {
$form['markup'] = array('#value' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
}
else {
- $handler->init($view, $item);
$types = views_object_types();
if ($view->display_handler->defaultable_sections($types[$type]['plural'])) {
@@ -2284,7 +2280,7 @@ function views_ui_config_item_form(&$form_state) {
// If this relationship is valid for this type, add it to the list.
$data = views_fetch_data($relationship['table']);
$base = $data[$relationship['field']]['relationship']['base'];
- $base_fields = views_fetch_fields($base, $form_state['type']);
+ $base_fields = views_fetch_fields($base, $form_state['type'], $view->display_handler->use_group_by());
if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
$relationship_handler->init($view, $relationship);
$relationship_options[$relationship['id']] = $relationship_handler->label();
@@ -2294,7 +2290,7 @@ function views_ui_config_item_form(&$form_state) {
if (!empty($relationship_options)) {
// Make sure the existing relationship is even valid. If not, force
// it to none.
- $base_fields = views_fetch_fields($view->base_table, $form_state['type']);
+ $base_fields = views_fetch_fields($view->base_table, $form_state['type'], $view->display_handler->use_group_by());
if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
$relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options);
}
@@ -2369,9 +2365,7 @@ function views_ui_config_item_form_submit($form, &$form_state) {
// Store the item back on the view
$form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
- $handler = views_get_handler($item['table'], $item['field'], $form_state['type']);
- $handler->init($form_state['view'], $item);
- if ($handler && $handler->needs_style_plugin()) {
+ if ($form_state['handler'] && $form_state['handler']->needs_style_plugin()) {
views_ui_add_form_to_stack('change-style', $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE);
}
@@ -2380,6 +2374,73 @@ function views_ui_config_item_form_submit($form, &$form_state) {
}
/**
+ * Form to config_item items in the views UI.
+ */
+function views_ui_config_item_group_form(&$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+
+ $view->init_query();
+
+ $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 = $view->display_handler->get_handler($type, $id);
+ if (empty($handler)) {
+ $form['markup'] = array('#value' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
+ }
+ else {
+ $handler->init($view, $item);
+ $types = views_object_types();
+
+ $form['#title'] = check_plain($view->display[$display_id]->display_title) . ': ';
+ $form['#title'] .= t('Configure group settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
+
+ $form['#section'] = $display_id . '-' . $type . '-' . $id;
+
+ $info = $view->query->get_aggregation_info();
+ foreach ($info as $id => $aggregate) {
+ $group_types[$id] = $aggregate['title'];
+ }
+
+ $form['group_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Group type'),
+ '#default_value' => $handler->options['group_type'],
+ '#description' => t('Grouping is enabled for this display. You must select what function to use on this field.'),
+ '#options' => $group_types,
+ );
+ $form_state['handler'] = &$handler;
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_group_form');
+ }
+ return $form;
+}
+
+/**
+ * Submit handler for configing group settings on a view.
+ */
+function views_ui_config_item_group_form_submit($form, &$form_state) {
+ $item = $form_state['handler']->options;
+
+ $item['group_type'] = $form_state['values']['group_type'];
+
+ // Store the item back on the view
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+ // Write to cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
* Submit handler for removing an item from a view
*/
function views_ui_config_item_form_remove($form, &$form_state) {
@@ -2443,8 +2504,7 @@ function views_ui_config_item_extra_form(&$form_state) {
// Get form from the handler.
$handler->extra_options_form($form['options'], $form_state);
- $form_state['handler'] = &$handler;
-
+ $form_state['handler'] = &$handler;
}
views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_extra_form');
@@ -2885,7 +2945,7 @@ function _views_sort_types($a, $b) {
* @return
* A keyed array of in the form of 'base_table' => 'Description'.
*/
-function views_fetch_fields($base, $type) {
+function views_fetch_fields($base, $type, $grouping = FALSE) {
static $fields = array();
if (empty($fields)) {
$data = views_fetch_data();
@@ -2917,6 +2977,9 @@ function views_fetch_fields($base, $type) {
}
foreach (array('field', 'sort', 'filter', 'argument', 'relationship') as $key) {
if (!empty($info[$key])) {
+ if ($grouping && !empty($info[$key]['no group by'])) {
+ continue;
+ }
if (!empty($info[$key]['skip base'])) {
foreach ((array) $info[$key]['skip base'] as $base_name) {
$skip_bases[$field][$key][$base_name] = TRUE;
diff --git a/includes/handlers.inc b/includes/handlers.inc
index 95f9dc5..065cb98 100644
--- a/includes/handlers.inc
+++ b/includes/handlers.inc
@@ -14,11 +14,24 @@ function _views_create_handler($definition, $type = 'handler') {
return;
}
- if (!class_exists($definition['handler']) && !views_include_handler($definition, $type)) {
+ if (!empty($definition['override handler']) &&
+ !class_exists($definition['override handler']) &&
+ !views_include_handler($definition['override handler'], $definition, $type)) {
return;
}
- $handler = new $definition['handler'];
+ if (!class_exists($definition['handler']) &&
+ !views_include_handler($definition['handler'], $definition, $type)) {
+ return;
+ }
+
+ if (!empty($definition['override handler'])) {
+ $handler = new $definition['override handler'];
+ }
+ else {
+ $handler = new $definition['handler'];
+ }
+
$handler->set_definition($definition);
// let the handler have something like a constructor.
$handler->construct();
@@ -32,24 +45,24 @@ function _views_create_handler($definition, $type = 'handler') {
* This will also attempt to include all parents, though we're maxing the
* parent chain to 10 to prevent infinite loops.
*/
-function views_include_handler($definition, $type, $count = 0) {
+function views_include_handler($handler, $definition, $type, $count = 0) {
// Do not proceed if the class already exists.
- if (isset($definition['handler']) && class_exists($definition['handler'])) {
+ if (isset($handler) && class_exists($handler)) {
return TRUE;
}
// simple infinite loop prevention.
if ($count > 10) {
- vpr(t('Handler @handler include tried to loop infinitely!', array('@handler' => $definition['handler'])));
+ vpr(t('Handler @handler include tried to loop infinitely!', array('@handler' => $handler)));
return FALSE;
}
if (!isset($definition['path'])) {
if ($type == 'handler') {
- $definition += views_fetch_handler_data($definition['handler']);
+ $definition += views_fetch_handler_data($handler);
}
else {
- $definition += views_fetch_plugin_data($type, $definition['handler']);
+ $definition += views_fetch_plugin_data($type, $handler);
}
}
@@ -62,7 +75,7 @@ function views_include_handler($definition, $type, $count = 0) {
}
if ($parent) {
- $rc = views_include_handler($parent, $type, $count + 1);
+ $rc = views_include_handler($parent['handler'], $parent, $type, $count + 1);
// If the parent chain cannot be included, don't try; this will
// help alleviate problems with modules with cross dependencies.
if (!$rc) {
@@ -78,7 +91,7 @@ function views_include_handler($definition, $type, $count = 0) {
}
}
- return class_exists($definition['handler']);
+ return class_exists($handler);
}
/**
@@ -262,6 +275,14 @@ class views_handler extends views_object {
$this->query = &$view->query;
}
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['group_type'] = array('default' => 'group');
+
+ return $options;
+ }
+
/**
* Return a string representing this handler's name in the UI.
*/
@@ -271,15 +292,39 @@ class views_handler extends views_object {
}
/**
- * Provide a form for setting options.
+ * Shortcut to get a handler's raw field value.
+ *
+ * This should be overridden for handlers with formulae or other
+ * non-standard fields. Because this takes an argument, fields
+ * overriding this can just call return parent::get_field($formula)
*/
- function options_form(&$form, &$form_state) { }
+ function get_field($field = NULL) {
+ if (!isset($field)) {
+ if (!empty($this->formula)) {
+ $field = $this->get_formula();
+ }
+ else {
+ $field = $this->table_alias . '.' . $this->real_field;
+ }
+ }
+
+ // If grouping, check to see if the aggregation method needs to modify the field.
+ if ($this->view->display_handler->use_group_by()) {
+ $info = $this->query->get_aggregation_info();
+ if (!empty($info[$this->options['group_type']]['method']) && function_exists($info[$this->options['group_type']]['method'])) {
+ return $info[$this->options['group_type']]['method']($this->options['group_type'], $field);
+ }
+ }
+
+ return $field;
+ }
/**
* Validate the options form.
*/
function options_validate($form, &$form_state) { }
+ function options_form(&$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.
@@ -510,7 +555,7 @@ class views_handler extends views_object {
*
* If we were using PHP5, this would be abstract.
*/
- function query() { }
+ function query($group_by = FALSE) { }
/**
* Ensure the main table for this handler is in the query. This is used
@@ -1248,6 +1293,20 @@ function views_views_handlers() {
'views_handler_sort_random' => array(
'parent' => 'views_handler_sort',
),
+
+ // group by handlers
+ 'views_handler_argument_group_by_numeric' => array(
+ 'parent' => 'views_handler_argument',
+ ),
+ 'views_handler_field_group_by_numeric' => array(
+ 'parent' => 'views_handler_field',
+ ),
+ 'views_handler_filter_group_by_numeric' => array(
+ 'parent' => 'views_handler_filter_numeric',
+ ),
+ 'views_handler_sort_group_by_numeric' => array(
+ 'parent' => 'views_handler_sort',
+ ),
),
);
}
@@ -1457,7 +1516,8 @@ class views_join {
*/
function views_views_api() {
return array(
- 'api' => 2,
+ // in your modules do *not* use views_api_version()!!!
+ 'api' => views_api_version(),
'path' => drupal_get_path('module', 'views') . '/modules',
);
}
diff --git a/includes/view.inc b/includes/view.inc
index 715db70..e24393b 100644
--- a/includes/view.inc
+++ b/includes/view.inc
@@ -51,17 +51,18 @@ class view extends views_db_object {
// Used to store views that were previously running if we recurse.
var $old_view = array();
+
+ // Where the $query object will reside:
+ var $query = NULL;
/**
* Constructor
*/
- function view() {
+ function __construct() {
parent::init();
// Make sure all of our sub objects are arrays.
foreach ($this->db_objects() as $object) {
$this->$object = array();
}
-
- $this->query = new stdClass();
}
/**
@@ -474,7 +475,7 @@ class view extends views_db_object {
}
else {
$arg_title = $argument->get_title();
- $argument->query();
+ $argument->query($this->display_handler->use_group_by());
}
// Add this argument's substitution
@@ -525,6 +526,14 @@ class view extends views_db_object {
* Do some common building initialization.
*/
function init_query() {
+ if (!empty($this->query)) {
+ $class = get_class($this->query);
+ if ($class && $class != 'stdClass') {
+ // return if query is already initialized.
+ return;
+ }
+ }
+
// Create and initialize the query object.
$views_data = views_fetch_data($this->base_table);
$this->base_field = $views_data['table']['base']['field'];
@@ -629,10 +638,10 @@ class view extends views_db_object {
}
// Allow display handler to affect the query:
- $this->display_handler->query();
+ $this->display_handler->query($this->display_handler->use_group_by());
// Allow style handler to affect the query:
- $this->style_plugin->query();
+ $this->style_plugin->query($this->display_handler->use_group_by());
if (variable_get('views_sql_signature', FALSE)) {
$this->query->add_signature($this);
@@ -668,7 +677,7 @@ class view extends views_db_object {
}
}
$handlers[$id]->set_relationship();
- $handlers[$id]->query();
+ $handlers[$id]->query($this->display_handler->use_group_by());
}
}
}
diff --git a/modules/comment.views.inc b/modules/comment.views.inc
index 3bc8521..99cf961 100644
--- a/modules/comment.views.inc
+++ b/modules/comment.views.inc
@@ -389,6 +389,7 @@ function comment_views_data_alter(&$data) {
'help' => t('The number of new comments on the node.'),
'field' => array(
'handler' => 'views_handler_field_node_new_comments',
+ 'no group by' => TRUE,
),
);
diff --git a/modules/node/views_handler_field_node.inc b/modules/node/views_handler_field_node.inc
index 8cffbb5..04953c8 100644
--- a/modules/node/views_handler_field_node.inc
+++ b/modules/node/views_handler_field_node.inc
@@ -9,14 +9,14 @@
* Field handler to provide simple renderer that allows linking to a node.
*/
class views_handler_field_node extends views_handler_field {
- /**
- * Constructor to provide additional field to add.
- */
- function construct() {
- parent::construct();
- $this->additional_fields['nid'] = 'nid';
- if (module_exists('translation')) {
- $this->additional_fields['language'] = array('table' => 'node', 'field' => 'language');
+
+ function init(&$view, $options) {
+ parent::init($view, $options);
+ if (!empty($this->options['link_to_node'])) {
+ $this->additional_fields['nid'] = 'nid';
+ if (module_exists('translation')) {
+ $this->additional_fields['language'] = array('table' => 'node', 'field' => 'language');
+ }
}
}
diff --git a/modules/profile.views.inc b/modules/profile.views.inc
index e1255b0..edc2677 100644
--- a/modules/profile.views.inc
+++ b/modules/profile.views.inc
@@ -195,6 +195,7 @@ function profile_views_fetch_field($field) {
'help' => t('Profile freeform list %field-name.', array('%field-name' => $field->title)),
'field' => array(
'handler' => 'views_handler_field_profile_list',
+ 'no group by' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_string',
diff --git a/modules/taxonomy.views.inc b/modules/taxonomy.views.inc
index 5877d15..4ec4ecc 100644
--- a/modules/taxonomy.views.inc
+++ b/modules/taxonomy.views.inc
@@ -203,6 +203,7 @@ function taxonomy_views_data() {
'help' => t('Display all taxonomy terms associated with a node from specified vocabularies.'),
'handler' => 'views_handler_field_term_node_tid',
'skip base' => 'term_data',
+ 'no group by' => TRUE,
),
'argument' => array(
'handler' => 'views_handler_argument_term_node_tid',
@@ -265,7 +266,7 @@ function taxonomy_views_data() {
'argument' => array(
'help' => t('The parent term of the term.'),
'handler' => 'views_handler_argument_numeric',
- ),
+ ),
);
// ----------------------------------------------------------------------
diff --git a/modules/upload.views.inc b/modules/upload.views.inc
index 06bbaed..bc4d450 100644
--- a/modules/upload.views.inc
+++ b/modules/upload.views.inc
@@ -116,6 +116,7 @@ function upload_views_data_alter(&$data) {
'real field' => 'vid',
'field' => array(
'handler' => 'views_handler_field_upload_fid',
+ 'no group by' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_upload_fid',
diff --git a/modules/user.views.inc b/modules/user.views.inc
index a14a5f4..fa53e12 100644
--- a/modules/user.views.inc
+++ b/modules/user.views.inc
@@ -275,6 +275,7 @@ function user_views_data() {
'help' => t('Roles that a user belongs to.'),
'field' => array(
'handler' => 'views_handler_field_user_roles',
+ 'no group by' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_user_roles',
diff --git a/plugins/views_plugin_display.inc b/plugins/views_plugin_display.inc
index 53aab1a..86613a5 100644
--- a/plugins/views_plugin_display.inc
+++ b/plugins/views_plugin_display.inc
@@ -130,6 +130,13 @@ class views_plugin_display extends views_plugin {
}
/**
+ * Does the display have a more link enabled?
+ */
+ function use_group_by() {
+ return $this->get_option('group_by');
+ }
+
+ /**
* Should the enabled display more link be shown when no more items?
*/
function use_more_always() {
@@ -256,6 +263,7 @@ class views_plugin_display extends views_plugin {
'exposed_block' => TRUE,
'link_display' => TRUE,
+ 'group_by' => TRUE,
'style_plugin' => TRUE,
'style_options' => TRUE,
@@ -361,6 +369,9 @@ class views_plugin_display extends views_plugin {
'distinct' => array(
'default' => FALSE,
),
+ 'group_by' => array(
+ 'default' => FALSE,
+ ),
'style_plugin' => array(
'default' => 'default',
@@ -559,7 +570,21 @@ class views_plugin_display extends views_plugin {
$types = views_object_types();
$plural = $types[$type]['plural'];
foreach ($this->get_option($plural) as $id => $info) {
- $handler = views_get_handler($info['table'], $info['field'], $type);
+ // If aggregation is on, the group type might override the actual
+ // handler that is in use. This piece of code checks that and,
+ // if necessary, sets the override handler.
+ $override = NULL;
+ if ($this->use_group_by() && !empty($info['group_type'])) {
+ if (empty($this->view->query)) {
+ $this->view->init_query();
+ }
+ $aggregate = $this->view->query->get_aggregation_info();
+ if (!empty($aggregate[$info['group_type']]['handler'][$type])) {
+ $override = $aggregate[$info['group_type']]['handler'][$type];
+ }
+ }
+
+ $handler = views_get_handler($info['table'], $info['field'], $type, $override);
if ($handler) {
$handler->init($this->view, $info);
$this->handlers[$type][$id] = &$handler;
@@ -731,6 +756,16 @@ class views_plugin_display extends views_plugin {
'desc' => t('Display only distinct items, without duplicates.'),
);
+ $this->view->init_query();
+ if ($this->view->query->get_aggregation_info()) {
+ $options['group_by'] = array(
+ 'category' => 'advanced',
+ 'title' => t('Use grouping'),
+ 'value' => $this->get_option('group_by') ? t('Yes') : t('No'),
+ 'desc' => t('Allow grouping and aggregation (calculation) of fields.'),
+ );
+ }
+
$access_plugin = $this->get_access_plugin();
if (!$access_plugin) {
// default to the no access control plugin.
@@ -946,6 +981,15 @@ class views_plugin_display extends views_plugin {
'#default_value' => $this->get_option('distinct'),
);
break;
+ case 'group_by':
+ $form['#title'] .= t('Allow grouping and aggregation (calculation) of fields.');
+ $form['group_by'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Group by'),
+ '#description' => t('If enabled, some fields may become unavailable. All fields that are selected for grouping will be collapsed to one record per distinct value. Other fields which are selected for aggregation will have the function run on them. For example, you can group nodes on title and count the number of nids in order to get a list of duplicate titles.'),
+ '#default_value' => $this->get_option('distinct'),
+ );
+ break;
case 'access':
$form['#title'] .= t('Access restrictions');
$form['access'] = array(
@@ -1526,6 +1570,9 @@ class views_plugin_display extends views_plugin {
case 'distinct':
$this->set_option($section, $form_state['values'][$section]);
break;
+ case 'group_by':
+ $this->set_option($section, $form_state['values'][$section]);
+ break;
case 'row_plugin':
// This if prevents resetting options to default if they don't change
// the plugin.
diff --git a/plugins/views_plugin_query.inc b/plugins/views_plugin_query.inc
index 3748318..183c002 100644
--- a/plugins/views_plugin_query.inc
+++ b/plugins/views_plugin_query.inc
@@ -23,12 +23,12 @@ class views_plugin_query extends views_plugin {
* Provide a countquery if this is true, otherwise provide a normal query.
*/
function query($get_count = FALSE) { }
-
+
/**
* Let modules modify the query just prior to finalizing it.
*/
function alter(&$view) { }
-
+
/**
* Builds the necessary info to execute the query.
*/
@@ -37,7 +37,7 @@ class views_plugin_query extends views_plugin {
/**
* Executes the query and fills the associated view object with according
* values.
- *
+ *
* Values to set: $view->result, $view->total_rows, $view->execute_time,
* $view->pager['current_page'].
*/
@@ -50,4 +50,11 @@ class views_plugin_query extends views_plugin {
* discern where particular queries might be coming from.
*/
function add_signature(&$view) { }
+
+ /**
+ * Get aggregation info for group by queries.
+ *
+ * If NULL, aggregation is not allowed.
+ */
+ function get_aggregation_info() { }
}
diff --git a/plugins/views_plugin_query_default.inc b/plugins/views_plugin_query_default.inc
index 9fcb6e5..5d15087 100644
--- a/plugins/views_plugin_query_default.inc
+++ b/plugins/views_plugin_query_default.inc
@@ -93,6 +93,8 @@ class views_plugin_query_default extends views_plugin_query{
'alias' => $base_table,
);
+/**
+ * -- we no longer want the base field to appear automatigically.
if ($base_field) {
$this->fields[$base_field] = array(
'table' => $base_table,
@@ -100,6 +102,7 @@ class views_plugin_query_default extends views_plugin_query{
'alias' => $base_field,
);
}
+ */
$this->count_field = array(
'table' => $base_table,
@@ -612,11 +615,14 @@ class views_plugin_query_default extends views_plugin_query{
* The alias to create. If not specified, the alias will be $table_$field
* unless $table is NULL. When adding formulae, it is recommended that an
* alias be used.
+ * @param $params
+ * An array of parameters additional to the field that will control items
+ * such as aggregation functions and DISTINCT.
*
* @return $name
* The name that this field can be referred to as. Usually this is the alias.
*/
- function add_field($table, $field, $alias = '', $params = NULL) {
+ function add_field($table, $field, $alias = '', $params = array()) {
// We check for this specifically because it gets a special alias.
if ($table == $this->base_table && $field == $this->base_field && empty($alias)) {
$alias = $this->base_field;
@@ -630,22 +636,30 @@ class views_plugin_query_default extends views_plugin_query{
$alias = $table . '_' . $field;
}
- $name = $alias ? $alias : $field;
+ // Make sure an alias is assigned
+ $alias = $alias ? $alias : $field;
- // @todo FIXME -- $alias, then $name is inconsistent
- if (empty($this->fields[$alias])) {
- $this->fields[$name] = array(
- 'field' => $field,
- 'table' => $table,
- 'alias' => $alias,
- );
+ // Create a field info array.
+ $field_info = array(
+ 'field' => $field,
+ 'table' => $table,
+ 'alias' => $alias,
+ ) + $params;
+
+ // Test to see if the field is actually the same or not. Due to
+ // differing parameters changing the aggregation function, we need
+ // to do some automatic alias collision detection:
+ $base = $alias;
+ $counter = 0;
+ while (!empty($this->fields[$alias]) && $this->fields[$alias] != $field_info) {
+ $field_info['alias'] = $alias = $base . '_' . ++$counter;
}
- foreach ((array)$params as $key => $value) {
- $this->fields[$name][$key] = $value;
+ if (empty($this->fields[$alias])) {
+ $this->fields[$alias] = $field_info;
}
- return $name;
+ return $alias;
}
/**
@@ -806,8 +820,10 @@ class views_plugin_query_default extends views_plugin_query{
* must also be in the SELECT portion. If an $alias isn't specified
* one will be generated for from the $field; however, if the
* $field is a formula, this alias will likely fail.
+ * @param $params
+ * Any params that should be passed through to the add_field.
*/
- function add_orderby($table, $field, $order, $alias = '') {
+ function add_orderby($table, $field, $order, $alias = '', $params = array()) {
if ($table) {
$this->ensure_table($table);
}
@@ -822,17 +838,21 @@ class views_plugin_query_default extends views_plugin_query{
}
if ($field) {
- $this->add_field($table, $field, $as);
+ $this->add_field($table, $field, $as, $params);
}
$this->orderby[] = "$as " . strtoupper($order);
+/**
+ * -- removing, this should be taken care of by field adding now.
+ * -- leaving commented because I am unsure.
// If grouping, all items in the order by must also be in the
// group by clause. Check $table to ensure that this is not a
// formula.
if ($this->groupby && $table) {
$this->add_groupby($as);
}
+ */
}
/**
@@ -885,10 +905,8 @@ class views_plugin_query_default extends views_plugin_query{
function query($get_count = FALSE) {
// Check query distinct value.
if (empty($this->no_distinct) && $this->distinct && !empty($this->fields)) {
- if (!empty($this->fields[$this->base_field])) {
- $this->fields[$this->base_field]['distinct'] = TRUE;
- $this->add_groupby($this->base_field);
- }
+ $base_field_alias = $this->add_field($this->base_table, $this->base_field, NULL, array('distinct' => TRUE));
+ $this->add_groupby($base_field_alias);
}
/**
@@ -898,7 +916,7 @@ class views_plugin_query_default extends views_plugin_query{
$fields_array = $this->fields;
if ($get_count && !$this->groupby) {
foreach ($fields_array as $field) {
- if (!empty($field['distinct'])) {
+ if (!empty($field['distinct']) || !empty($field['function'])) {
$get_count_optimized = FALSE;
break;
}
@@ -911,7 +929,8 @@ class views_plugin_query_default extends views_plugin_query{
$get_count_optimized = TRUE;
}
- $joins = $fields = $where = $having = $orderby = $groupby = '';
+ $joins = $where = $having = $orderby = $groupby = '';
+ $fields = $distinct = array();
// Add all the tables to the query via joins. We assume all LEFT joins.
foreach ($this->table_queue as $table) {
if (is_object($table['join'])) {
@@ -923,9 +942,6 @@ class views_plugin_query_default extends views_plugin_query{
$non_aggregates = array();
foreach ($fields_array as $field) {
- if ($fields) {
- $fields .= ",\n ";
- }
$string = '';
if (!empty($field['table'])) {
$string .= $field['table'] . '.';
@@ -937,20 +953,37 @@ class views_plugin_query_default extends views_plugin_query{
if (!empty($field['distinct'])) {
$string = "DISTINCT($string)";
}
+
if (!empty($field['count'])) {
- $string = "COUNT($string)";
+ // Retained for compatibility
+ $field['function'] = 'COUNT';
+ }
+
+ if (!empty($field['function'])) {
+ $info = $this->get_aggregation_info();
+ if (!empty($info[$field['function']]['method']) && function_exists($info[$field['function']]['method'])) {
+ $string = $info[$field['function']]['method']($field['function'], $string);
+ }
+
$has_aggregate = TRUE;
}
elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
$string = $GLOBALS['db_type'] == 'pgsql' ? "FIRST($string)" : $string;
}
- else {
+ elseif (empty($field['aggregate'])) {
$non_aggregates[] = $fieldname;
}
+
if ($field['alias']) {
$string .= " AS $field[alias]";
}
- $fields .= $string;
+
+ if (!empty($field['distinct']) && empty($field['function'])) {
+ $distinct[] = $string;
+ }
+ else {
+ $fields[] = $string;
+ }
if ($get_count_optimized) {
// We only want the first field in this case.
@@ -958,8 +991,8 @@ class views_plugin_query_default extends views_plugin_query{
}
}
- if ($has_aggregate || $this->groupby) {
- $groupby = "GROUP BY " . implode(', ', array_unique(array_merge($this->groupby, $non_aggregates))) . "\n";
+ if ($has_aggregate || $this->groupby) {
+ $groupby = "GROUP BY " . implode(', ', array_unique(array_merge($this->groupby, $non_aggregates))) . "\n";
if ($this->having) {
$having = $this->condition_sql('having');
}
@@ -974,7 +1007,7 @@ class views_plugin_query_default extends views_plugin_query{
$where = $this->condition_sql();
- $query = "SELECT $fields\n FROM {" . $this->base_table . "} $this->base_table \n$joins $where $groupby $having $orderby";
+ $query = "SELECT " . implode(",\n", array_merge($distinct, $fields)) . "\n FROM {" . $this->base_table . "} $this->base_table \n$joins $where $groupby $having $orderby";
$replace = array('&gt;' => '>', '&lt;' => '<');
$query = strtr($query, $replace);
@@ -1109,4 +1142,68 @@ class views_plugin_query_default extends views_plugin_query{
$view->query->add_field(NULL, "'" . $view->name . ':' . $view->current_display . "'", 'view_name');
}
+ function get_aggregation_info() {
+ // @todo -- need a way to get database specific and customized aggregation
+ // functions into here.
+ return array(
+ 'group' => array(
+ 'title' => t('Group results together'),
+ 'is aggregate' => FALSE,
+ ),
+ 'count' => array(
+ 'title' => t('Count'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'field' => 'views_handler_field_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'sum' => array(
+ 'title' => t('Sum'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'field' => 'views_handler_field_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'avg' => array(
+ 'title' => t('Average'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'field' => 'views_handler_field_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'min' => array(
+ 'title' => t('Minimum'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'field' => 'views_handler_field_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'max' => array(
+ 'title' => t('Maximum'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'field' => 'views_handler_field_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ );
+ }
+}
+
+function views_query_default_aggregation_method_simple($group_type, $field) {
+ return strtoupper($group_type) . '(' . $field . ')';
}
diff --git a/views.module b/views.module
index 238d834..1361a83 100644
--- a/views.module
+++ b/views.module
@@ -13,7 +13,14 @@
* Advertise the current views api version
*/
function views_api_version() {
- return 2.0;
+ return '3.0-alpha1';
+}
+
+/**
+ * Views will not load plugins advertising a version older than this.
+ */
+function views_api_minimum_version() {
+ return '2';
}
/**
@@ -535,7 +542,8 @@ function views_get_module_apis() {
foreach (module_implements('views_api') as $module) {
$function = $module . '_views_api';
$info = $function();
- if (isset($info['api']) && $info['api'] == 2.000) {
+ if (version_compare($info['api'], views_api_minimum_version(), '>=') &&
+ version_compare($info['api'], views_api_version(), '<=')) {
if (!isset($info['path'])) {
$info['path'] = drupal_get_path('module', $module);
}
@@ -624,19 +632,35 @@ function views_include_default_views() {
* The name of the field this handler is from.
* @param $key
* The type of handler. i.e, sort, field, argument, filter, relationship
+ * @param $override
+ * Override the actual handler object with this class. Used for
+ * aggregation when the handler is redirected to the aggregation
+ * handler.
*
* @return
* An instance of a handler object. May be views_handler_broken.
*/
-function views_get_handler($table, $field, $key) {
+function views_get_handler($table, $field, $key, $override = NULL) {
$data = views_fetch_data($table);
+ $handler = NULL;
+
if (isset($data[$field][$key])) {
// Set up a default handler:
if (empty($data[$field][$key]['handler'])) {
$data[$field][$key]['handler'] = 'views_handler_' . $key;
}
- return _views_prepare_handler($data[$field][$key], $data, $field);
+
+ if ($override) {
+ $data[$field][$key]['override handler'] = $override;
+ }
+
+ $handler = _views_prepare_handler($data[$field][$key], $data, $field);
+ }
+
+ if ($handler) {
+ return $handler;
}
+
// DEBUG -- identify missing handlers
vpr("Missing handler: $table $field $key");
$broken = array(
@@ -967,7 +991,7 @@ function views_exposed_form(&$form_state) {
}
}
}
-
+
$form['submit'] = array(
'#name' => '', // prevent from showing up in $_GET.
'#type' => 'submit',
diff --git a/views_ui.module b/views_ui.module
index 49fe613..ca3745b 100644
--- a/views_ui.module
+++ b/views_ui.module
@@ -265,6 +265,7 @@ function views_ui_cache_set(&$view) {
unset($view->display_handler);
unset($view->current_display);
unset($view->default_display);
+ $view->query = NULL;
foreach (array_keys($view->display) as $id) {
unset($view->display[$id]->handler);
unset($view->display[$id]->default_display);