You are here

views_calc_table.inc in Views Calc 7

Same filename and directory in other branches
  1. 6.3 views_calc_table.inc
  2. 6 views_calc_table.inc

Copied from the table style plugin.

File

views_calc_table.inc
View source
<?php

/**
 * @file
 * Copied from the table style plugin.
 */

/**
 * Style plugin to render each item as a row in a table.
 *
 * @ingroup views_style_plugins
 */
class views_calc_table extends views_plugin_style_table {

  /**
   * Option definition.
   */
  function option_definition() {
    $options = parent::option_definition();
    $options['detailed_values'] = array(
      'default' => 0,
    );
    $options['precision'] = array(
      'default' => 0,
    );
    $options['decimal'] = array(
      'default' => '.',
      'translatable' => TRUE,
    );
    $options['separator'] = array(
      'default' => ',',
      'translatable' => TRUE,
    );
    $options['info'] = array(
      'default' => array(),
    );
    return $options;
  }

  /**
   * Render the given style.
   */
  function options_form(&$form, &$form_state) {
    parent::options_form($form, $form_state);
    $form['#theme'] = 'views_calc_ui_table';
    $form['detailed_values'] = array(
      '#title' => t('Show details'),
      '#type' => 'select',
      '#options' => array(
        0 => t('Yes'),
        1 => t('No'),
      ),
      '#default_value' => $this->options['detailed_values'],
      '#description' => t("Select 'Yes' to show detailed values followed by column calculations, 'No' to surpress details and show only calculated column totals."),
    );
    $handlers = $this->display->handler
      ->get_handlers('field');
    $columns = $this
      ->sanitize_columns($this->options['columns']);
    foreach ($columns as $field => $column) {
      $safe = str_replace(array(
        '][',
        '_',
        ' ',
      ), '-', $field);
      $id = 'edit-style-options-columns-' . $safe;
      $form['info'][$field]['has_calc'] = array(
        '#type' => 'checkbox',
        '#title' => t('Display calculation'),
        '#default_value' => isset($this->options['info'][$field]['has_calc']) ? $this->options['info'][$field]['has_calc'] : 0,
      );
      $options = _views_calc_calc_options();
      $form['info'][$field]['calc'] = array(
        '#type' => 'select',
        '#options' => $options,
        '#multiple' => TRUE,
        '#default_value' => isset($this->options['info'][$field]['calc']) ? $this->options['info'][$field]['calc'] : array(),
        '#states' => array(
          'visible' => array(
            ':checkbox[name="style_options[info][' . $field . '][has_calc]"]' => array(
              'checked' => TRUE,
            ),
          ),
        ),
      );
    }
    $form['precision'] = array(
      '#type' => 'textfield',
      '#title' => t('Default precision'),
      '#default_value' => $this->options['precision'],
      '#description' => t('Specify how many digits to print after the decimal point in calculated values, if not specified in the field settings.'),
      '#dependency' => array(
        'edit-options-set-precision' => array(
          TRUE,
        ),
      ),
      '#size' => 2,
    );
    $form['decimal'] = array(
      '#type' => 'textfield',
      '#title' => t('Default decimal point'),
      '#default_value' => $this->options['decimal'],
      '#description' => t('What single character to use as a decimal point in calculated values, if not specified in the field settings.'),
      '#size' => 2,
    );
    $form['separator'] = array(
      '#type' => 'select',
      '#title' => t('Default thousands marker'),
      '#options' => array(
        '' => t('- None -'),
        ',' => t('Comma'),
        ' ' => t('Space'),
        '.' => t('Decimal'),
        '\'' => t('Apostrophe'),
      ),
      '#default_value' => $this->options['separator'],
      '#description' => t('What single character to use as the thousands separator in calculated values, if not specified in the field settings.'),
      '#size' => 2,
    );
  }

  /**
   * Views Method pre_render().
   *
   * Build grand total and page sub total.
   * Query calc fields using sub-view and add data.
   *
   * TODO
   * figure out what changes are needed so Views field groups will work.
   */
  function pre_render($results) {
    parent::pre_render($results);

    // If there are no calc fields, do nothing.
    if (!($calc_fields = $this
      ->get_calc_fields())) {
      return;
    }

    // If we're not getting a summary row, do nothing.
    if (!empty($this->view->views_calc_calculation)) {
      return;
    }
    $this->view->totals = array();
    $this->view->sub_totals = array();
    $this->view->views_calc_fields = $calc_fields;
    $this->view->views_calc_calculation = FALSE;
    $maxitems = $this->view
      ->get_items_per_page();

    // check if Subtotals are displayed
    if (!empty($maxitems) && $this->view->query->pager
      ->get_total_items() > $maxitems) {
      $ids = array();
      foreach ($this->view->result as $delta => $value) {
        $ids[] = $value->{$this->view->base_field};
      }

      // Add sub_total rows to the results.
      // We need one query per aggregation because theming needs unrenamed views field alias.
      // TODO Looks like we have problems unless we
      // force a non-page display, need to keep an eye on this.
      $this
        ->execute_summary_view($ids);
    }

    // Add grand totals to the results.
    $this
      ->execute_summary_view();
  }
  function execute_summary_view($ids = array()) {

    // Clone view for local subquery.
    $summary_view = $this->view
      ->clone_view();
    $summary_view
      ->set_display($this->view->current_display);

    // copy the query object by value not by reference!
    $summary_view->query = clone $this->view->query;
    $summary_view
      ->set_display($this->view->current_display);

    // Make sure the view is completely valid.
    $errors = $summary_view
      ->validate();
    if (is_array($errors)) {
      foreach ($errors as $error) {
        drupal_set_message($error, 'error');
      }
      return NULL;
    }

    // intialize summary view
    $is_subtotal = !empty($ids);
    $summary_view->preview = TRUE;
    $summary_view->is_cacheable = FALSE;
    $summary_view->views_calc_calculation = TRUE;
    $summary_view->views_calc_sub_total = $is_subtotal;

    // If it's a subtotal calc, we're filtering the page elements by $ids.
    $summary_view->views_calc_ids = $ids;
    $summary_view->views_calc_fields = $this->view->views_calc_fields;
    $summary_view->build_info = $this->view->build_info;
    $summary_view->field = $this->view->field;

    // Call the method which adds aggregation fields before rendering
    // In an earlier version this was done in the overridden method query() of this class
    // which is not called for some reason here.
    $this
      ->add_aggregation_fields($summary_view);

    // We don't need any offset in the calculation queries because
    // the statement does only return the records with the passed ids
    $summary_view->query->offset = 0;

    // Execute and render the view in preview mode. Note that we only store
    // the result if the executed view query returns any result.
    $summary_view
      ->pre_execute($this->view->args);

    // All results of the calculation queries are used, so we don't page them.
    // Else we will loose the global pager from the page view.
    $summary_view
      ->set_items_per_page(0);
    $summary_view
      ->execute();
    $summary_view
      ->post_execute();
    if (!empty($summary_view->result)) {
      if ($is_subtotal) {
        $this->view->sub_totals = array_shift($summary_view->result);
      }
      else {
        $this->view->totals = array_shift($summary_view->result);
      }
    }
  }

  /**
   * Query grand total.
   */
  function add_aggregation_fields(&$view) {

    // Create summary rows.
    // Empty out any fields that have been added to the query,
    // we don't need them for the summary totals.
    $view->query->fields = array();

    // Clear out any sorting and grouping, it can create unexpected results
    // when Views adds aggregation values for the sorts.
    $view->query->orderby = array();
    $view->query->groupby = array();

    // See if this view has any calculations.
    $has_calcs = TRUE;
    $calc_fields = $view->views_calc_fields;
    foreach ($calc_fields as $calc => $fields) {
      foreach ($view->field as $field) {

        // Is this a field or a property?
        if (!empty($field->field_info)) {
          $query_field = substr($field->field, 0, 3) == 'cid' ? $field->definition['calc'] : $field->table . '.' . $field->real_field;
        }
        else {
          $query_field = substr($field->field, 0, 3) == 'cid' ? $field->definition['calc'] : $field->table_alias . '.' . $field->real_field;
        }
        if (isset($field->aliases['entity_type'])) {
          $query_alias = $field->aliases['entity_type'];
        }
        else {
          $query_alias = $field->field_alias;
        }

        // Bail if we have a broken handler.
        if ($query_alias == 'unknown') {
          continue;
        }
        $view->query
          ->add_table($field->table, $field->relationship, NULL, $field->table);

        // aggregation functions
        $ext_alias = views_calc_shorten($calc . '__' . $query_alias);
        if (in_array($field->options['id'], $fields)) {

          // Calculated fields.
          $view->query
            ->add_field(NULL, $calc . '(' . $query_field . ')', $ext_alias);
          $has_calcs = TRUE;
        }
      }
    }

    // TODO This won't work right with relationships, need a fix here.
    // Limit to specific primary ids. This is for subtotals.
    if (!empty($view->views_calc_ids)) {

      //$view->query->add_where(NULL, $view->base_table . "." . $view->base_field . " IN (%s)", implode(',', $view->views_calc_ids));
      $view->query
        ->add_where(NULL, $view->base_table . "." . $view->base_field, $view->views_calc_ids);
    }

    // TODO We may need to ask which field to group by.
    // Some databases are going to complain if we don't have a groupby field with using aggregation.
    if ($has_calcs) {
      $this->view->query
        ->add_groupby($this->view->base_table . '.' . $this->view->base_field);
    }
  }

  /**
   * Get views_calc fields
   */
  function get_calc_fields() {

    // TODO on preview this returns the wrong list.
    $options = $this->view->style_plugin->options;
    $handler = $this->view->style_plugin;
    $fields = $this->view->field;
    $columns = $handler
      ->sanitize_columns($options['columns'], $fields);
    $calcs = array_keys(_views_calc_calc_options());
    $calc_fields = array();
    foreach ($columns as $field => $column) {
      if ($field == $column && empty($fields[$field]->options['exclude'])) {
        if ($options['info'][$field]['has_calc']) {
          foreach ($calcs as $calc) {
            if (isset($this->options['info'][$field]['calc'][$calc])) {
              $calc_fields[$calc][] = $field;
            }
          }
        }
      }
    }
    return $calc_fields;
  }

}

Classes

Namesort descending Description
views_calc_table Style plugin to render each item as a row in a table.