3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 class CRM_Report_Form_Mailing_Opened
extends CRM_Report_Form
{
19 protected $_summary = NULL;
21 protected $_emailField = FALSE;
23 protected $_phoneField = FALSE;
25 protected $_customGroupExtends = [
32 protected $_charts = [
34 'barChart' => 'Bar Chart',
35 'pieChart' => 'Pie Chart',
39 * This report has not been optimised for group filtering.
41 * The functionality for group filtering has been improved but not
42 * all reports have been adjusted to take care of it. This report has not
43 * and will run an inefficient query until fixed.
49 protected $groupFilterNotOptimised = TRUE;
54 public function __construct() {
55 $this->optimisedForOnlyFullGroupBy
= FALSE;
58 $this->_columns
['civicrm_contact'] = [
59 'dao' => 'CRM_Contact_DAO_Contact',
62 'title' => ts('Contact ID'),
66 'title' => ts('Contact Name'),
72 'title' => ts('Contact Name'),
75 'title' => ts('Contact Source'),
76 'type' => CRM_Utils_Type
::T_STRING
,
79 'title' => ts('Contact ID'),
85 'title' => ts('Contact Name'),
87 'default_order' => 'ASC',
90 'grouping' => 'contact-fields',
93 $this->_columns
['civicrm_mailing'] = [
94 'dao' => 'CRM_Mailing_DAO_Mailing',
98 'title' => ts('Mailing Name'),
101 'mailing_name_alias' => [
104 'no_display' => TRUE,
106 'mailing_subject' => [
108 'title' => ts('Mailing Subject'),
115 'title' => ts('Mailing Name'),
116 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
117 'type' => CRM_Utils_Type
::T_INT
,
118 'options' => CRM_Mailing_BAO_Mailing
::getMailingsList(),
119 'operator' => 'like',
121 'mailing_subject' => [
123 'title' => ts('Mailing Subject'),
124 'type' => CRM_Utils_Type
::T_STRING
,
125 'operator' => 'like',
131 'title' => ts('Mailing Name'),
133 'mailing_subject' => [
135 'title' => ts('Mailing Subject'),
138 'grouping' => 'mailing-fields',
141 $this->_columns
['civicrm_email'] = [
142 'dao' => 'CRM_Core_DAO_Email',
145 'title' => ts('Email'),
150 'email' => ['title' => ts('Email'), 'default_order' => 'ASC'],
152 'grouping' => 'contact-fields',
155 $this->_columns
['civicrm_phone'] = [
156 'dao' => 'CRM_Core_DAO_Phone',
157 'fields' => ['phone' => NULL],
158 'grouping' => 'contact-fields',
161 $this->_columns
['civicrm_mailing_event_opened'] = [
162 'dao' => 'CRM_Mailing_Event_DAO_Opened',
166 'no_display' => TRUE,
167 'dbAlias' => CRM_Utils_SQL
::supportsFullGroupBy() ?
'ANY_VALUE(mailing_event_opened_civireport.id)' : NULL,
170 'title' => ts('Open Date'),
176 'title' => ts('Open Date'),
177 'operatorType' => CRM_Report_Form
::OP_DATE
,
178 'type' => CRM_Utils_Type
::T_DATE
,
181 'title' => ts('Unique Opens'),
182 'type' => CRM_Utils_Type
::T_BOOLEAN
,
183 'pseudofield' => TRUE,
188 'title' => ts('Open Date'),
191 'grouping' => 'mailing-fields',
194 $this->_groupFilter
= TRUE;
195 $this->_tagFilter
= TRUE;
196 parent
::__construct();
199 public function preProcess() {
200 $this->assign('chartSupported', TRUE);
201 parent
::preProcess();
204 public function select() {
206 $this->_columnHeaders
= [];
207 foreach ($this->_columns
as $tableName => $table) {
208 if (array_key_exists('fields', $table)) {
209 foreach ($table['fields'] as $fieldName => $field) {
210 if (!empty($field['required']) ||
211 !empty($this->_params
['fields'][$fieldName])
213 if ($tableName == 'civicrm_email') {
214 $this->_emailField
= TRUE;
216 elseif ($tableName == 'civicrm_phone') {
217 $this->_phoneField
= TRUE;
220 $select[] = "{$field['dbAlias']} as {$tableName}_{$fieldName}";
221 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['type'] = $field['type'] ??
NULL;
222 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['no_display'] = $field['no_display'] ??
NULL;
223 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['title'] = $field['title'] ??
NULL;
229 if (!empty($this->_params
['charts'])) {
230 $select[] = "COUNT({$this->_aliases['civicrm_mailing_event_opened']}.id) as civicrm_mailing_opened_count";
231 $this->_columnHeaders
["civicrm_mailing_opened_count"]['title'] = ts('Opened Count');
234 $this->_selectClauses
= $select;
235 $this->_select
= "SELECT " . implode(', ', $select) . " ";
245 public static function formRule($fields, $files, $self) {
246 $errors = $grouping = [];
250 public function from() {
252 FROM civicrm_contact {$this->_aliases['civicrm_contact']} {$this->_aclFrom}";
255 INNER JOIN civicrm_mailing_event_queue
256 ON civicrm_mailing_event_queue.contact_id = {$this->_aliases['civicrm_contact']}.id
257 LEFT JOIN civicrm_email {$this->_aliases['civicrm_email']}
258 ON civicrm_mailing_event_queue.email_id = {$this->_aliases['civicrm_email']}.id
259 INNER JOIN civicrm_mailing_event_opened {$this->_aliases['civicrm_mailing_event_opened']}
260 ON {$this->_aliases['civicrm_mailing_event_opened']}.event_queue_id = civicrm_mailing_event_queue.id
261 INNER JOIN civicrm_mailing_job
262 ON civicrm_mailing_event_queue.job_id = civicrm_mailing_job.id
263 INNER JOIN civicrm_mailing {$this->_aliases['civicrm_mailing']}
264 ON civicrm_mailing_job.mailing_id = {$this->_aliases['civicrm_mailing']}.id
265 AND civicrm_mailing_job.is_test = 0
267 $this->joinPhoneFromContact();
270 public function where() {
272 $this->_where
.= " AND {$this->_aliases['civicrm_mailing']}.sms_provider_id IS NULL";
275 public function groupBy() {
277 // Do not use group by clause if distinct = 0 mentioned in url params. flag is used in mailing report screen, default value is TRUE
278 // this report is used to show total opened and unique opened
279 if (CRM_Utils_Request
::retrieve('distinct', 'Boolean', CRM_Core_DAO
::$_nullObject, FALSE, TRUE)) {
280 $groupBys = empty($this->_params
['charts']) ?
["civicrm_mailing_event_queue.email_id"] : ["{$this->_aliases['civicrm_mailing']}.id"];
281 if (!empty($this->_params
['unique_opens_value'])) {
282 $groupBys[] = "civicrm_mailing_event_queue.id";
285 if (!empty($groupBys)) {
286 $this->_groupBy
= "GROUP BY " . implode(', ', $groupBys);
290 public function postProcess() {
292 $this->beginPostProcess();
294 // get the acl clauses built before we assemble the query
295 $this->buildACLClause($this->_aliases
['civicrm_contact']);
297 $sql = $this->buildQuery(TRUE);
299 $rows = $graphRows = [];
300 $this->buildRows($sql, $rows);
302 $this->formatDisplay($rows);
303 $this->doTemplateAssignment($rows);
304 $this->endPostProcess($rows);
310 public function buildChart(&$rows) {
316 'legend' => ts('Mail Opened Report'),
317 'xname' => ts('Mailing'),
318 'yname' => ts('Opened'),
320 'tip' => ts('Mail Opened: %1', [1 => '#val#']),
322 foreach ($rows as $row) {
323 $chartInfo['values'][$row['civicrm_mailing_mailing_name_alias']] = $row['civicrm_mailing_opened_count'];
327 CRM_Utils_Chart
::buildChart($chartInfo, $this->_params
['charts']);
328 $this->assign('chartType', $this->_params
['charts']);
332 * Alter display of rows.
334 * Iterate through the rows retrieved via SQL and make changes for display purposes,
335 * such as rendering contacts as links.
338 * Rows generated by SQL, with an array for each row.
340 public function alterDisplay(&$rows) {
342 foreach ($rows as $rowNum => $row) {
344 // If the email address has been deleted
345 if (array_key_exists('civicrm_email_email', $row)) {
346 if (empty($rows[$rowNum]['civicrm_email_email'])) {
347 $rows[$rowNum]['civicrm_email_email'] = '<del>Email address deleted</del>';
352 // make count columns point to detail report
353 // convert display name to links
354 if (array_key_exists('civicrm_contact_sort_name', $row) &&
355 array_key_exists('civicrm_contact_id', $row)
357 $url = CRM_Utils_System
::url('civicrm/contact/view',
358 'reset=1&cid=' . $row['civicrm_contact_id'],
361 $rows[$rowNum]['civicrm_contact_sort_name_link'] = $url;
362 $rows[$rowNum]['civicrm_contact_sort_name_hover'] = ts("View Contact details for this contact.");
366 // skip looking further in rows, if first row itself doesn't
367 // have the column we need