Eileen has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/265143

Change subject: CRM-17837 Improve Lybunt report.
......................................................................

CRM-17837 Improve Lybunt report.

Add tests for lynbunt results, get custom data working for contacts in it, 
remove non-working address group bys,
fix query to be more efficient on a large dataset

Change-Id: If33c0a42c699b07d21f48d7bec8c35a261c54d13
---
M CRM/Report/Form.php
M CRM/Report/Form/Contribute/Lybunt.php
M api/v3/ReportTemplate.php
M api/v3/examples/ReportTemplate/Getrows.php
M api/v3/examples/ReportTemplate/Getstatistics.php
5 files changed, 436 insertions(+), 265 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/wikimedia/fundraising/crm/civicrm 
refs/changes/43/265143/1

diff --git a/CRM/Report/Form.php b/CRM/Report/Form.php
index 4ec83c2..936db07 100644
--- a/CRM/Report/Form.php
+++ b/CRM/Report/Form.php
@@ -297,11 +297,20 @@
   protected $_aclWhere = NULL;
 
   /**
-   * Array of DAO tables having columns included in SELECT or ORDER BY clause
+   * Array of DAO tables having columns included in SELECT or ORDER BY clause.
+   *
+   * Where has also been added to this although perhaps the 'includes both' 
array should have a different name.
    *
    * @var array
    */
   protected $_selectedTables;
+
+  /**
+   * Array of DAO tables having columns included in WHERE or HAVING clause
+   *
+   * @var array
+   */
+  protected $filteredTables;
 
   /**
    * Output mode e.g 'print', 'csv', 'pdf'.
@@ -400,6 +409,13 @@
   protected $_createNew;
 
   /**
+   *  When a grand total row has calculated the status we pop it off to here.
+   *
+   * This allows us to access it from the stats function and avoid 
recalculating.
+   */
+  protected $rollupRow = array();
+
+  /**
    * SQL being run in this report.
    *
    * The sql in the report is stored in this variable in order to be displayed 
on the developer tab.
@@ -408,6 +424,16 @@
    */
 
   protected $sql;
+
+  /**
+   * SQL being run in this report as an array.
+   *
+   * The sql in the report is stored in this variable in order to be returned 
to api & test calls.
+   *
+   * @var string
+   */
+
+  protected $sqlArray;
   /**
    * Class constructor.
    */
@@ -1255,6 +1281,7 @@
     );
 
     $this->assignTabs();
+    $this->sqlArray[] = $sql;
     foreach (array('LEFT JOIN') as $term) {
       $sql = str_replace($term, '<br>&nbsp&nbsp' . $term, $sql);
     }
@@ -2307,26 +2334,27 @@
    * @return bool
    */
   public function grandTotal(&$rows) {
-    if (!$this->_rollup || ($this->_rollup == '') ||
+    if (!$this->_rollup || ($this->_rollup == '' || count($rows) == 1) ||
       ($this->_limit && count($rows) >= self::ROW_COUNT_LIMIT)
     ) {
       return FALSE;
     }
-    $lastRow = array_pop($rows);
+
+    $this->rollupRow = array_pop($rows);
 
     foreach ($this->_columnHeaders as $fld => $val) {
       if (!in_array($fld, $this->_statFields)) {
         if (!$this->_grandFlag) {
-          $lastRow[$fld] = "Grand Total";
+          $this->rollupRow[$fld] = "Grand Total";
           $this->_grandFlag = TRUE;
         }
         else {
-          $lastRow[$fld] = "";
+          $this->rollupRow[$fld] = "";
         }
       }
     }
 
-    $this->assign('grandStat', $lastRow);
+    $this->assign('grandStat', $this->rollupRow);
     return TRUE;
   }
 
@@ -2874,7 +2902,7 @@
         }
 
         if (!empty($orderByField)) {
-          $this->_orderByFields[] = $orderByField;
+          $this->_orderByFields[$orderByField['tplField']] = $orderByField;
           $orderBys[] = "{$orderByField['dbAlias']} {$orderBy['order']}";
 
           // Record any section headers for assignment to the template
@@ -2897,22 +2925,8 @@
    * @return array
    */
   public function unselectedSectionColumns() {
-    $selectColumns = array();
-    foreach ($this->_columns as $tableName => $table) {
-      if (array_key_exists('fields', $table)) {
-        foreach ($table['fields'] as $fieldName => $field) {
-          if (!empty($field['required']) ||
-            !empty($this->_params['fields'][$fieldName])
-          ) {
-
-            $selectColumns["{$tableName}_{$fieldName}"] = 1;
-          }
-        }
-      }
-    }
-
     if (is_array($this->_sections)) {
-      return array_diff_key($this->_sections, $selectColumns);
+      return array_diff_key($this->_sections, $this->getSelectColumns());
     }
     else {
       return array();
@@ -3052,7 +3066,7 @@
     $statistics = array();
 
     $count = count($rows);
-
+    // Why do we increment the count for rollup seems to artificially inflate 
the count.
     if ($this->_rollup && ($this->_rollup != '') && $this->_grandFlag) {
       $count++;
     }
@@ -3351,6 +3365,15 @@
   }
 
   /**
+   * Get the sql used to generate the report.
+   *
+   * @return string
+   */
+  public function getReportSql() {
+    return $this->sqlArray;
+  }
+
+  /**
    * Use the form name to create the tpl file name.
    *
    * @return string
@@ -3463,7 +3486,6 @@
    * @param int $rowCount
    */
   public function setPager($rowCount = self::ROW_COUNT_LIMIT) {
-
     // CRM-14115, over-ride row count if rowCount is specified in URL
     if ($this->_dashBoardRowCount) {
       $rowCount = $this->_dashBoardRowCount;
@@ -3478,8 +3500,12 @@
         'status' => ts('Records') . ' %%StatusMessage%%',
         'buttonBottom' => 'PagerBottomButton',
         'buttonTop' => 'PagerTopButton',
-        'pageID' => $this->get(CRM_Utils_Pager::PAGE_ID),
       );
+      if (!empty($this->controller)) {
+        // This happens when being called from the api Really we want the api 
to be able to
+        // pass paging parameters, but at this stage just preventing test 
crashes.
+        $params['pageID'] = $this->get(CRM_Utils_Pager::PAGE_ID);
+      }
 
       $pager = new CRM_Utils_Pager($params);
       $this->assign_by_ref('pager', $pager);
@@ -3728,6 +3754,7 @@
         case 'Money':
           $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_FLOAT;
           $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_MONEY;
+          $curFields[$fieldName]['type'] = CRM_Utils_Type::T_MONEY;
           break;
 
         case 'Float':
@@ -3998,6 +4025,22 @@
   }
 
   /**
+   * Check if table name has columns in WHERE or HAVING clause.
+   *
+   * @param string $tableName
+   *   Name of table (index of $this->_columns array).
+   *
+   * @return bool
+   */
+  public function isTableFiltered($tableName) {
+    // Cause the array to be generated if not previously done.
+    if (!$this->_selectedTables && ! $this->filteredTables) {
+      $this->selectedTables();
+    }
+    return in_array($tableName, $this->filteredTables);
+  }
+
+  /**
    * Fetch array of DAO tables having columns included in SELECT or ORDER BY 
clause.
    *
    * If the array is unset it will be built.
@@ -4044,6 +4087,7 @@
               'nnll'
             ) {
               $this->_selectedTables[] = $tableName;
+              $this->filteredTables[] = $tableName;
               break;
             }
           }
@@ -4629,4 +4673,26 @@
     }
   }
 
+
+  /**
+   * @return array
+   */
+  public function getSelectColumns()
+  {
+    $selectColumns = array();
+    foreach ($this->_columns as $tableName => $table) {
+      if (array_key_exists('fields', $table)) {
+        foreach ($table['fields'] as $fieldName => $field) {
+          if (!empty($field['required']) ||
+            !empty($this->_params['fields'][$fieldName])
+          ) {
+
+            $selectColumns["{$tableName}_{$fieldName}"] = 1;
+          }
+        }
+      }
+    }
+    return $selectColumns;
+  }
+
 }
diff --git a/CRM/Report/Form/Contribute/Lybunt.php 
b/CRM/Report/Form/Contribute/Lybunt.php
index cfa16c2..f795127 100644
--- a/CRM/Report/Form/Contribute/Lybunt.php
+++ b/CRM/Report/Form/Contribute/Lybunt.php
@@ -40,16 +40,42 @@
     'pieChart' => 'Pie Chart',
   );
 
-  protected $_customGroupExtends = array(
-    'Contact',
-    'Individual',
-    'Contribution',
-  );
-
   public $_drilldownReport = array('contribute/detail' => 'Link to Detail 
Report');
 
   protected $lifeTime_from = NULL;
   protected $lifeTime_where = NULL;
+  protected $_customGroupExtends = array(
+    'Contact',
+    'Individual',
+    'Household',
+    'Organization',
+  );
+
+  /**
+   * Report has results.
+   *
+   * Allows us to bypass the later query if no results are available.
+   *
+   * @var bool
+   */
+  protected $hasResults = FALSE;
+  /**
+   * Table containing list of contact IDs.
+   *
+   * @var string
+   */
+  protected $contactTempTable = '';
+
+  /**
+   * Status clause to be added in to both contact based & contribution based 
queries.
+   *
+   * The rationale seems to be that we construct a list of contacts and then 
show the relevant contributions for them.
+   *
+   * Presumably the clause originally was only status but now type is included 
too.
+   *
+   * @var string
+   */
+  protected $_statusClause = '';
 
   /**
    */
@@ -188,7 +214,7 @@
         ),
       ),
     );
-    $this->_columns += $this->addAddressFields();
+    $this->_columns += $this->addAddressFields(FALSE);
     $this->_columns += array(
       'civicrm_contribution' => array(
         'dao' => 'CRM_Contribute_DAO_Contribution',
@@ -199,17 +225,22 @@
             'required' => TRUE,
             'no_repeat' => TRUE,
           ),
-          'total_amount' => array(
-            'title' => ts('Total Amount'),
-            'no_display' => TRUE,
-            'required' => TRUE,
-            'no_repeat' => TRUE,
-          ),
           'receive_date' => array(
             'title' => ts('Year'),
             'no_display' => TRUE,
             'required' => TRUE,
             'no_repeat' => TRUE,
+          ),
+          'last_year_total_amount' => array(
+            'title' => ts('Last Year Total'),
+            'default' => TRUE,
+            'type' => CRM_Utils_Type::T_MONEY,
+          ),
+          'civicrm_life_time_total' => array(
+            'title' => ts('Lifetime Total'),
+            'default' => TRUE,
+            'type' => CRM_Utils_Type::T_MONEY,
+            'statistics' => array('sum' => ts('Lifetime total')),
           ),
         ),
         'filters' => array(
@@ -234,7 +265,7 @@
           ),
         ),
         'order_bys' => array(
-          'total_amount' => array(
+          'last_year_total_amount' => array(
             'title' => ts('Total amount last year (affects available 
columns)'),
             'description' => ts('When ordering by this life time amount cannot 
be calculated'),
             'default' => '0',
@@ -263,134 +294,146 @@
     parent::__construct();
   }
 
-  public function preProcess() {
-    parent::preProcess();
-  }
-
-  public function select() {
-
-    $this->_columnHeaders = $select = array();
-    $current_year = $this->_params['yid_value'];
-    $previous_year = $current_year - 1;
-
-    foreach ($this->_columns as $tableName => $table) {
-
-      if (array_key_exists('fields', $table)) {
-        foreach ($table['fields'] as $fieldName => $field) {
-
-          if (!empty($field['required']) ||
-            !empty($this->_params['fields'][$fieldName])
-          ) {
-            if ($fieldName == 'total_amount') {
-              $select[] = "SUM({$field['dbAlias']}) as 
{$tableName}_{$fieldName}";
-
-              $this->_columnHeaders["{$previous_year}"]['type'] = 
$field['type'];
-              $this->_columnHeaders["{$previous_year}"]['title'] = 
$previous_year;
-
-              $this->_columnHeaders["civicrm_life_time_total"]['type'] = 
$field['type'];
-              $this->_columnHeaders["civicrm_life_time_total"]['title'] = 
'LifeTime';;
-            }
-            elseif ($fieldName == 'receive_date') {
-              $select[] = self::fiscalYearOffset($field['dbAlias']) .
-                " as {$tableName}_{$fieldName} ";
-            }
-            else {
-              $select[] = "{$field['dbAlias']} as {$tableName}_{$fieldName} ";
-              $this->_columnHeaders["{$tableName}_{$fieldName}"]['type'] = 
CRM_Utils_Array::value('type', $field);
-              $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = 
CRM_Utils_Array::value('title', $field);
-            }
-
-            if (!empty($field['no_display'])) {
-              $this->_columnHeaders["{$tableName}_{$fieldName}"]['no_display'] 
= TRUE;
-            }
-          }
-        }
-      }
+  /**
+   * Build select clause for a single field.
+   *
+   * @param string $tableName
+   * @param string $tableKey
+   * @param string $fieldName
+   * @param string $field
+   *
+   * @return bool
+   */
+  public function selectClause(&$tableName, $tableKey, &$fieldName, &$field) {
+    if ($fieldName == 'last_year_total_amount') {
+      $this->_columnHeaders["{$tableName}_{$fieldName}"] = $field;
+      $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = 
$this->getLastYearColumnTitle();
+      $this->_statFields[$this->getLastYearColumnTitle()] = 
"{$tableName}_{$fieldName}";
+      return "SUM(IF(" . 
$this->whereClauseLastYear('contribution_civireport.receive_date') .  ", 
contribution_civireport.total_amount, 0)) as {$tableName}_{$fieldName}";
     }
-
-    $this->_select = "SELECT  " . implode(', ', $select) . " ";
+    if ($fieldName == 'civicrm_life_time_total') {
+      if (!$this->isOrderByLastYearTotal()) {
+        $this->_columnHeaders["{$tableName}_{$fieldName}"] = $field;
+        $this->_statFields[$field['title']] = "{$tableName}_{$fieldName}";
+        return "SUM({$this->_aliases[$tableName]}.total_amount) as 
{$tableName}_{$fieldName}";
+      }
+      return "'' as 'hack'";
+    }
+    if ($fieldName == 'receive_date') {
+      return self::fiscalYearOffset($field['dbAlias']) .
+        " as {$tableName}_{$fieldName} ";
+    }
+    return FALSE;
   }
 
+  /**
+   * Get the title for the last year column.
+   */
+  public function getLastYearColumnTitle() {
+    if ($this->getYearFilterType() == 'calendar') {
+      return $this->getCurrentYear() - 1;
+    }
+    return ($this->getCurrentYear() -1) . '/' . ($this->getCurrentYear());
+  }
+
+  /**
+   * Construct from clause.
+   *
+   * On the first run we are creating a table of contacts to include in the 
report.
+   *
+   * Once contactTempTable is populated we should avoid using any further 
filters that affect
+   * the contacts that should be visible.
+   */
   public function from() {
-
-    $this->_from = "
-        FROM  civicrm_contribution  {$this->_aliases['civicrm_contribution']}
-              INNER JOIN civicrm_contact {$this->_aliases['civicrm_contact']}
-                      ON {$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_contribution']}.contact_id
-              {$this->_aclFrom}";
-
-    if ($this->isTableSelected('civicrm_email')) {
-      $this->_from .= "
-              LEFT  JOIN civicrm_email  {$this->_aliases['civicrm_email']}
-                      ON {$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_email']}.contact_id
-                     AND {$this->_aliases['civicrm_email']}.is_primary = 1";
+    if (!empty($this->contactTempTable)) {
+      $this->_from = "
+        FROM  civicrm_contribution {$this->_aliases['civicrm_contribution']}
+        INNER JOIN $this->contactTempTable restricted_contacts
+          ON restricted_contacts.cid = 
{$this->_aliases['civicrm_contribution']}.contact_id
+          AND {$this->_aliases['civicrm_contribution']}.is_test = 0
+        INNER JOIN civicrm_contact {$this->_aliases['civicrm_contact']}
+          ON restricted_contacts.cid = 
{$this->_aliases['civicrm_contact']}.id";
+      if ($this->isTableSelected('civicrm_email')) {
+        $this->_from .= "
+          LEFT  JOIN civicrm_email  {$this->_aliases['civicrm_email']}
+            ON {$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_email']}.contact_id
+            AND {$this->_aliases['civicrm_email']}.is_primary = 1";
+      }
+      if ($this->isTableSelected('civicrm_phone')) {
+        $this->_from .= "
+          LEFT  JOIN civicrm_phone  {$this->_aliases['civicrm_phone']}
+            ON {$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_phone']}.contact_id
+            AND {$this->_aliases['civicrm_phone']}.is_primary = 1";
+      }
+      $this->addAddressFromClause();
     }
-    if ($this->isTableSelected('civicrm_phone')) {
-      $this->_from .= "
-              LEFT  JOIN civicrm_phone  {$this->_aliases['civicrm_phone']}
-                      ON {$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_phone']}.contact_id AND
-                         {$this->_aliases['civicrm_phone']}.is_primary = 1";
-    }
-    $this->addAddressFromClause();
-  }
-
-  public function where() {
-    $this->_statusClause = "";
-    $clauses = array($this->_aliases['civicrm_contribution'] . '.is_test = 0');
-    $current_year = $this->_params['yid_value'];
-    $previous_year = $current_year - 1;
-
-    foreach ($this->_columns as $tableName => $table) {
-      if (array_key_exists('filters', $table)) {
-        foreach ($table['filters'] as $fieldName => $field) {
-          $clause = NULL;
-          if ($fieldName == 'yid') {
-            $clause = "contribution_civireport.contact_id NOT IN
-(SELECT distinct contri.contact_id FROM civicrm_contribution contri
- WHERE contri.is_test = 0 AND " .
-              self::fiscalYearOffset('contri.receive_date') . " = 
$current_year) AND contribution_civireport.contact_id IN (SELECT distinct 
contri.contact_id FROM civicrm_contribution contri
- WHERE " . self::fiscalYearOffset('contri.receive_date') .
-              " = $previous_year AND contri.is_test = 0) AND " . 
self::fiscalYearOffset('contribution_civireport.receive_date') . " = 
$previous_year";
-          }
-          elseif (CRM_Utils_Array::value('type', $field) & 
CRM_Utils_Type::T_DATE
-          ) {
-            $relative = CRM_Utils_Array::value("{$fieldName}_relative", 
$this->_params);
-            $from = CRM_Utils_Array::value("{$fieldName}_from", 
$this->_params);
-            $to = CRM_Utils_Array::value("{$fieldName}_to", $this->_params);
-
-            if ($relative || $from || $to) {
-              $clause = $this->dateClause($field['name'], $relative, $from, 
$to, $field['type']);
-            }
-          }
-          else {
-            $op = CRM_Utils_Array::value("{$fieldName}_op", $this->_params);
-            if ($op) {
-              $clause = $this->whereClause($field,
-                $op,
-                CRM_Utils_Array::value("{$fieldName}_value", $this->_params),
-                CRM_Utils_Array::value("{$fieldName}_min", $this->_params),
-                CRM_Utils_Array::value("{$fieldName}_max", $this->_params)
-              );
-              if (($fieldName == 'contribution_status_id' ||
-                  $fieldName == 'financial_type_id') && !empty($clause)
-              ) {
-                $this->_statusClause .= " AND " . $clause;
-              }
-            }
-          }
-
-          if (!empty($clause)) {
-            $clauses[] = $clause;
-          }
-        }
+    else {
+      $this->_from .= "FROM civicrm_contact 
{$this->_aliases['civicrm_contact']}
+       INNER JOIN civicrm_contribution 
{$this->_aliases['civicrm_contribution']} USE index (received_date)
+         ON {$this->_aliases['civicrm_contribution']}.contact_id = 
{$this->_aliases['civicrm_contact']}.id
+         AND {$this->_aliases['civicrm_contribution']}.is_test = 0
+         AND " . 
$this->whereClauseLastYear("{$this->_aliases['civicrm_contribution']}.receive_date")
 . "
+       {$this->_aclFrom}
+       LEFT JOIN civicrm_contribution cont_exclude ON cont_exclude.contact_id 
= {$this->_aliases['civicrm_contact']}.id
+         AND " . $this->whereClauseThisYear('cont_exclude.receive_date')
+       ;
+      if ($this->isTableFiltered('civicrm_email')) {
+        $this->_from .= "
+          LEFT  JOIN civicrm_email  {$this->_aliases['civicrm_email']}
+            ON {$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_email']}.contact_id
+            AND {$this->_aliases['civicrm_email']}.is_primary = 1";
+      }
+      if ($this->isTableFiltered('civicrm_phone')) {
+        $this->_from .= "
+          LEFT  JOIN civicrm_phone  {$this->_aliases['civicrm_phone']}
+            ON {$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_phone']}.contact_id
+            AND {$this->_aliases['civicrm_phone']}.is_primary = 1";
+      }
+      if ($this->isTableFiltered('civicrm_address')) {
+        $this->_from .= "
+          LEFT JOIN civicrm_address {$this->_aliases['civicrm_address']}
+          ON ({$this->_aliases['civicrm_contact']}.id = 
{$this->_aliases['civicrm_address']}.contact_id)
+          AND {$this->_aliases['civicrm_address']}.is_primary = 1\n";
       }
     }
 
-    $this->_where = 'WHERE ' . implode(' AND ', $clauses);
+  }
 
-    if ($this->_aclWhere) {
-      $this->_where .= " AND {$this->_aclWhere} ";
+  /**
+   * Generate where clause.
+   *
+   * We are overriding this primarily for 'before-after' handling of the 
receive_date placeholder field.
+   *
+   * We call this twice. The first time we are generating a temp table and we 
want to do an IS NULL on the
+   * join that draws in contributions from this year. The second time we are 
filtering elsewhere (contacts via
+   * the temp table & contributions via selective addition of contributions in 
the select function).
+   *
+   * If lifetime total is NOT selected we can add a further filter here to 
possibly improve performance
+   * but the benefit if unproven as yet.
+   * $clause = 
$this->whereClauseLastYear("{$this->_aliases['civicrm_contribution']}.receive_date");
+   *
+   * @param array $field Field specifications
+   * @param string $op Query operator (not an exact match to sql)
+   * @param mixed $value
+   * @param float $min
+   * @param float $max
+   *
+   * @return null|string
+   */
+  public function whereClause(&$field, $op, $value, $min, $max) {
+    if ($field['name'] == 'receive_date') {
+      $clause = 1;
+      if (empty($this->contactTempTable)) {
+        $this->_whereClauses[] = "cont_exclude.id IS NULL";
+      }
     }
+    else {
+      $clause = parent::whereClause($field, $op, $value, $min, $max);
+    }
+    if ($field['name'] == 'contribution_status_id' || $field['name'] == 
'financial_type_id') {
+      $this->_statusClause .= " AND " . $clause;
+    }
+    return $clause;
   }
 
   /**
@@ -403,34 +446,89 @@
    * @param int $current_year
    * @return null|string
    */
-  public function whereClauseLastYear($fieldName, $current_year = NULL) {
-    if (empty($current_year)) {
-      $current_year = $this->_params['yid_value'];
-    }
-    $previous_year = $current_year - 1;
-    if (CRM_Utils_Array::value('yid_op', $this->_params) == 'calendar') {
-      $firstDateOfYear = "{$previous_year}-01-01";
-      $lastDateOfYear =  "{$previous_year}-12-31 23:11:59";
+  public function whereClauseLastYear($fieldName) {
+    return "$fieldName BETWEEN '" . $this->getFirstDateOfPriorRange() . "' AND 
'" . $this->getLastDateOfPriorRange() . "'";
+  }
+
+  /**
+   * Generate where clause for last calendar year or fiscal year.
+   *
+   * @todo must be possible to re-use relative dates stuff.
+   *
+   * @param string $fieldName
+   *
+   * @param int $current_year
+   * @return null|string
+   */
+  public function whereClauseThisYear($fieldName, $current_year = NULL) {
+    return "$fieldName BETWEEN '" . $this->getFirstDateOfCurrentRange() . "' 
AND '" . $this->getLastDateOfCurrentRange() . "'";
+  }
+
+
+  /**
+   * Get the year value for the current year.
+   *
+   * @return string
+   */
+  public function getCurrentYear() {
+    return $this->_params['yid_value'];
+  }
+
+  /**
+   * Get the date time of the first date in the 'this year' range.
+   *
+   * @return string
+   */
+  public function getFirstDateOfCurrentRange() {
+    $current_year = $this->getCurrentYear();
+    if ($this->getYearFilterType() == 'calendar') {
+      return "{$current_year }-01-01";
     }
     else {
       $fiscalYear = CRM_Core_Config::singleton()->fiscalYearStart;
-      $firstDateOfYear = 
"{$previous_year}-{$fiscalYear['M']}-{$fiscalYear['d']}";
-      $lastDateOfYear =  date('Ymdhis', strtotime(date($current_year . 
'-m-d'), '- 1 second'));
+      return "{$current_year}-{$fiscalYear['M']}-{$fiscalYear['d']}";
     }
-    return "$fieldName BETWEEN '{$firstDateOfYear}' AND '{$lastDateOfYear}'";
   }
+
+  /**
+   * Get the year value for the current year.
+   *
+   * @return string
+   */
+  public function getYearFilterType() {
+    return CRM_Utils_Array::value('yid_op', $this->_params, 'calendar');
+  }
+
+  /**
+   * Get the date time of the last date in the 'this year' range.
+   *
+   * @return string
+   */
+  public function getLastDateOfCurrentRange() {
+    return date('YmdHis', strtotime('+ 1 year - 1 second', 
strtotime($this->getFirstDateOfCurrentRange())));
+  }
+
+  /**
+   * Get the date time of the first date in the 'last year' range.
+   *
+   * @return string
+   */
+  public function getFirstDateOfPriorRange() {
+    return date('YmdHis', strtotime('- 1 year', 
strtotime($this->getFirstDateOfCurrentRange())));
+  }
+
+  /**
+   * Get the date time of the last date in the 'last year' range.
+   *
+   * @return string
+   */
+  public function getLastDateOfPriorRange() {
+    return date('YmdHis', strtotime('+ 1 year - 1 second', 
strtotime($this->getFirstDateOfPriorRange())));
+  }
+
 
   public function groupBy() {
     $this->_groupBy = "GROUP BY  
{$this->_aliases['civicrm_contribution']}.contact_id ";
-
-    if (!$this->isOrderByLastYearTotal()) {
-      // If we are ordering by last year total we can't also get the lifetime 
total
-      // in the same query without significant re-work so we may as well drop 
the
-      // expensive clause that supports it.
-      $this->_groupBy .= ', ' . 
self::fiscalYearOffset($this->_aliases['civicrm_contribution'] .
-        '.receive_date');
-    }
-
     $this->assign('chartSupported', TRUE);
   }
 
@@ -440,112 +538,111 @@
    * @return array
    */
   public function statistics(&$rows) {
+    // Set rollup to Null to foll the dubious parent:statistics calculation.
+    if (!empty($this->_rollup)) {
+      $this->_rowsFound = $this->_rowsFound -1;
+      $this->_rollup = '';
+    }
+
     $statistics = parent::statistics($rows);
     if (!empty($rows)) {
-      $select = "
-        SELECT SUM({$this->_aliases['civicrm_contribution']}.total_amount ) as 
amount,
-        SUM(IF( " . $this->whereClauseLastYear('receive_date') . ", 
total_amount, 0)) as last_year";
-
-      $where = "WHERE {$this->_aliases['civicrm_contact']}.id IN (" . 
implode(',', $this->_contactIds) . ")
-        AND {$this->_aliases['civicrm_contribution']}.is_test = 0 
{$this->_statusClause}";
-
-      $sql = "{$select} {$this->_from} {$where}";
-
-      $dao = CRM_Core_DAO::executeQuery($sql);
-      if ($dao->fetch()) {
-        $statistics['counts']['last_year_amount'] = array(
-          'value' => $dao->last_year,
-          'title' => 'Total ' . ($this->_params['yid_value'] - 1),
-          'type' => CRM_Utils_Type::T_MONEY,
-        );
-        $statistics['counts']['amount'] = array(
-          'value' => $dao->amount,
+      if (!empty($this->rollupRow) && 
!empty($this->rollupRow['civicrm_contribution_civicrm_life_time_total'])) {
+        $statistics['counts']['civicrm_contribution_civicrm_life_time_total'] 
= array(
+          'value' => 
$this->rollupRow['civicrm_contribution_civicrm_life_time_total'],
           'title' => 'Total LifeTime',
           'type' => CRM_Utils_Type::T_MONEY,
         );
+      }
+      else {
+        $select = "SELECT 
SUM({$this->_aliases['civicrm_contribution']}.total_amount) as amount ";
+        $sql = "{$select} {$this->_from} {$this->_where}";
+        $dao = CRM_Core_DAO::executeQuery($sql);
+        if ($dao->fetch()) {
+          $statistics['counts']['amount'] = array(
+            'value' => $dao->amount,
+            'title' => 'Total LifeTime',
+            'type' => CRM_Utils_Type::T_MONEY,
+          );
+        }
       }
     }
 
     return $statistics;
   }
 
-  public function postProcess() {
-
-    // get ready with post process params
-    $this->beginPostProcess();
-
+  /**
+   * This function is called by both the api (tests) and the UI.
+   */
+  public function beginPostProcessCommon() {
     $this->buildQuery();
     $this->resetFormSql();
 
-    $rows = $this->_contactIds = array();
-    $this->limit();
-    $getContacts = "SELECT SQL_CALC_FOUND_ROWS 
{$this->_aliases['civicrm_contact']}.id as cid {$this->_from} {$this->_where}  
GROUP BY {$this->_aliases['civicrm_contact']}.id {$this->_limit}";
-    $this->addToDeveloperTab($getContacts);
-    $dao = CRM_Core_DAO::executeQuery($getContacts);
-
-    while ($dao->fetch()) {
-      $this->_contactIds[] = $dao->cid;
-    }
-    $dao->free();
+    $this->contactTempTable = 'civicrm_report_temp_lybunt_c_' . date('Ymd_') . 
uniqid();
     if (empty($this->_params['charts'])) {
       $this->setPager();
     }
+    $this->limit();
+    $getContacts = "
+      CREATE TEMPORARY TABLE $this->contactTempTable
+      SELECT SQL_CALC_FOUND_ROWS {$this->_aliases['civicrm_contact']}.id as 
cid {$this->_from} {$this->_where}
+      GROUP BY {$this->_aliases['civicrm_contact']}.id {$this->_limit}";
+    $this->addToDeveloperTab($getContacts);
+    CRM_Core_DAO::executeQuery($getContacts);
 
-    if (!empty($this->_contactIds) || !empty($this->_params['charts'])) {
-      $this->_where = "WHERE {$this->_aliases['civicrm_contact']}.id IN (" . 
implode(',', $this->_contactIds) . ")
-       AND {$this->_aliases['civicrm_contribution']}.is_test = 0 
{$this->_statusClause}";
+    if ($this->isOrderByLastYearTotal()) {
+      $this->_rollup = '';
+      unset($this->_columnHeaders['civicrm_life_time_total']);
+    }
+    // Reset where clauses to be regenerated in postProcess.
+    $this->_whereClauses = array();
+  }
 
-      if ($this->isOrderByLastYearTotal()) {
-        $this->_rollup = '';
-        $this->_where .= " AND " . $this->whereClauseLastYear('receive_date');
-        unset($this->_columnHeaders['civicrm_life_time_total']);
-      }
+  /**
+   * Build the report query.
+   *
+   * The issue we are hitting is that if we want to do group by & then ORDER 
BY we have to
+   * wrap the query in an outer query with the order by - otherwise the group 
by takes precedent.
+   * This is an issue when we want to group by contact but order by the 
maximum aggregate donation.
+   *
+   * @param bool $applyLimit
+   *
+   * @return string
+   */
+  public function buildQuery($applyLimit = TRUE) {
+    $this->select();
+    $this->from();
+    $this->customDataFrom();
+    $this->where();
+    $this->groupBy();
+    $this->orderBy();
 
-      $sql = "{$this->_select} {$this->_from} {$this->_where} 
{$this->_groupBy} {$this->_rollup}";
-
-      if (!empty($this->_orderByArray)) {
-        $this->_orderBy = str_replace('contact_civireport.', 
'civicrm_contact_', "ORDER BY " . implode(', ', $this->_orderByArray));
-        $this->_orderBy = str_replace('contribution_civireport.', 
'civicrm_contribution_', $this->_orderBy);
-        $sql = "SELECT * FROM ( $sql ) as inner_query {$this->_orderBy}";
-      }
-
-      $this->addToDeveloperTab($sql);
-      $dao = CRM_Core_DAO::executeQuery($sql);
-      $current_year = $this->_params['yid_value'];
-      $previous_year = $current_year - 1;
-
-      while ($dao->fetch()) {
-
-        if (!$dao->civicrm_contribution_contact_id) {
-          continue;
-        }
-
-        $row = array();
-        foreach ($this->_columnHeaders as $key => $value) {
-          if (property_exists($dao, $key)) {
-            $rows[$dao->civicrm_contribution_contact_id][$key] = $dao->$key;
-          }
-        }
-
-        if ($dao->civicrm_contribution_receive_date) {
-          if ($dao->civicrm_contribution_receive_date == $previous_year) {
-            
$rows[$dao->civicrm_contribution_contact_id][$dao->civicrm_contribution_receive_date]
 = $dao->civicrm_contribution_total_amount;
-          }
-        }
-        else {
-          
$rows[$dao->civicrm_contribution_contact_id]['civicrm_life_time_total'] = 
$dao->civicrm_contribution_total_amount;
-        }
-      }
-      $dao->free();
+    // order_by columns not selected for display need to be included in SELECT
+    // This differs from parent in that we are getting those not in order by 
rather than not in
+    // sections, as we need to adapt to our contact group by.
+    $unselectedSectionColumns = array_diff_key($this->_orderByFields, 
$this->getSelectColumns());
+    foreach ($unselectedSectionColumns as $alias => $section) {
+      $this->_select .= ", {$section['dbAlias']} as {$alias}";
     }
 
-    $this->formatDisplay($rows, FALSE);
+    if ($applyLimit && empty($this->_params['charts'])) {
+      $this->limit();
+    }
 
-    // assign variables to templates
-    $this->doTemplateAssignment($rows);
+    $sql = "{$this->_select} {$this->_from} {$this->_where} {$this->_groupBy} 
{$this->_having} {$this->_rollup} {$this->_limit}";
 
-    // do print / pdf / instance stuff if needed
-    $this->endPostProcess($rows);
+    if (!empty($this->_orderByArray)) {
+      $this->_orderBy = str_replace('contact_civireport.', 'civicrm_contact_', 
"ORDER BY ISNULL(civicrm_contribution_contact_id), " . implode(', ', 
$this->_orderByArray));
+      $this->_orderBy = str_replace('contribution_civireport.', 
'civicrm_contribution_', $this->_orderBy);
+      foreach ($this->_orderByFields as $field) {
+        $this->_orderBy = str_replace($field['dbAlias'], $field['tplField'], 
$this->_orderBy);
+      }
+      $sql = str_replace('SQL_CALC_FOUND_ROWS', '', $sql);
+      $sql = "SELECT SQL_CALC_FOUND_ROWS  * FROM ( $sql ) as inner_query 
{$this->_orderBy}";
+    }
+
+    CRM_Utils_Hook::alterReportVar('sql', $this, $this);
+    $this->addToDeveloperTab($sql);
+    return $sql;
   }
 
   /**
@@ -553,6 +650,7 @@
    */
   function resetFormSql() {
     $this->sql = '';
+    $this->sqlArray = array();
   }
 
   /**
diff --git a/api/v3/ReportTemplate.php b/api/v3/ReportTemplate.php
index bf0cea9..4eaf71e 100644
--- a/api/v3/ReportTemplate.php
+++ b/api/v3/ReportTemplate.php
@@ -154,7 +154,8 @@
   $sql = $reportInstance->buildQuery();
   $rows = $metadata = $requiredMetadata  = array();
   $reportInstance->buildRows($sql, $rows);
-  $requiredMetadata = array();
+  $reportInstance->formatDisplay($rows);
+
   if (isset($params['options']) && !empty($params['options']['metadata'])) {
     $requiredMetadata = $params['options']['metadata'];
     if (in_array('title', $requiredMetadata)) {
@@ -162,11 +163,14 @@
     }
     if (in_array('labels', $requiredMetadata)) {
       foreach ($reportInstance->_columnHeaders as $key => $header) {
-        //would be better just to expect reports to provide titles but reports 
are not consistent so we anticipate empty
+        // Would be better just to expect reports to provide titles but 
reports are not consistent so we anticipate empty
         //NB I think these are already translated
         $metadata['metadata']['labels'][$key] = !empty($header['title']) ? 
$header['title'] : '';
       }
     }
+    if (in_array('sql', $requiredMetadata)) {
+      $metadata['metadata']['sql'] = $reportInstance->getReportSql();
+    }
   }
   return array($rows, $reportInstance, $metadata);
 }
diff --git a/api/v3/examples/ReportTemplate/Getrows.php 
b/api/v3/examples/ReportTemplate/Getrows.php
index 50d6461..8c79043 100644
--- a/api/v3/examples/ReportTemplate/Getrows.php
+++ b/api/v3/examples/ReportTemplate/Getrows.php
@@ -52,17 +52,20 @@
       '0' => array(
         'civicrm_contact_sort_name' => 'Default Organization',
         'civicrm_contact_id' => '1',
+        'civicrm_contact_sort_name_link' => 
'/index.php?q=civicrm/report/contact/detail&amp;reset=1&amp;force=1&amp;id_op=eq&amp;id_value=1',
+        'civicrm_contact_sort_name_hover' => 'View Constituent Detail Report 
for this contact.',
       ),
       '1' => array(
         'civicrm_contact_sort_name' => 'Second Domain',
         'civicrm_contact_id' => '2',
+        'civicrm_contact_sort_name_link' => 
'/index.php?q=civicrm/report/contact/detail&amp;reset=1&amp;force=1&amp;id_op=eq&amp;id_value=2',
+        'civicrm_contact_sort_name_hover' => 'View Constituent Detail Report 
for this contact.',
       ),
     ),
     'metadata' => array(
       'title' => 'ERROR: Title is not Set',
       'labels' => array(
         'civicrm_contact_sort_name' => 'Contact Name',
-        'civicrm_contact_id' => 'Internal Contact ID',
       ),
     ),
   );
diff --git a/api/v3/examples/ReportTemplate/Getstatistics.php 
b/api/v3/examples/ReportTemplate/Getstatistics.php
index 93e6fc6..bc98a48 100644
--- a/api/v3/examples/ReportTemplate/Getstatistics.php
+++ b/api/v3/examples/ReportTemplate/Getstatistics.php
@@ -9,7 +9,7 @@
  */
 function report_template_getstatistics_example() {
   $params = array(
-    'report_id' => 'contribute/recur',
+    'report_id' => 'contribute/recursummary',
   );
 
   try{
@@ -41,7 +41,8 @@
   $expectedResult = array(
     'is_error' => 0,
     'version' => 3,
-    'count' => 2,
+    'count' => 1,
+    'id' => 'counts',
     'values' => array(
       'counts' => array(
         'rowCount' => array(
@@ -49,7 +50,6 @@
           'value' => 0,
         ),
       ),
-      'filters' => array(),
     ),
   );
 

-- 
To view, visit https://gerrit.wikimedia.org/r/265143
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: If33c0a42c699b07d21f48d7bec8c35a261c54d13
Gerrit-PatchSet: 1
Gerrit-Project: wikimedia/fundraising/crm/civicrm
Gerrit-Branch: master
Gerrit-Owner: Eileen <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to