Merge pull request #14326 from civicrm/5.14
[civicrm-core.git] / CRM / Campaign / BAO / Campaign.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2019
32 */
33 class CRM_Campaign_BAO_Campaign extends CRM_Campaign_DAO_Campaign {
34
35 /**
36 * Takes an associative array and creates a campaign object.
37 *
38 * the function extract all the params it needs to initialize the create a
39 * contact object. the params array could contain additional unused name/value
40 * pairs
41 *
42 * @param array $params
43 * (reference ) an assoc array of name/value pairs.
44 *
45 * @return CRM_Campaign_DAO_Campaign
46 */
47 public static function create(&$params) {
48 if (empty($params)) {
49 return NULL;
50 }
51
52 if (empty($params['id'])) {
53
54 if (empty($params['created_id'])) {
55 $session = CRM_Core_Session::singleton();
56 $params['created_id'] = $session->get('userID');
57 }
58
59 if (empty($params['created_date'])) {
60 $params['created_date'] = date('YmdHis');
61 }
62
63 if (empty($params['name'])) {
64 $params['name'] = CRM_Utils_String::titleToVar($params['title'], 64);
65 }
66
67 CRM_Utils_Hook::pre('create', 'Campaign', NULL, $params);
68 }
69 else {
70 CRM_Utils_Hook::pre('edit', 'Campaign', $params['id'], $params);
71 }
72
73 $campaign = new CRM_Campaign_DAO_Campaign();
74 $campaign->copyValues($params);
75 $campaign->save();
76
77 if (!empty($params['id'])) {
78 CRM_Utils_Hook::post('edit', 'Campaign', $campaign->id, $campaign);
79 }
80 else {
81 CRM_Utils_Hook::post('create', 'Campaign', $campaign->id, $campaign);
82 }
83
84 /* Create the campaign group record */
85
86 $groupTableName = CRM_Contact_BAO_Group::getTableName();
87
88 if (isset($params['groups']) && !empty($params['groups']['include']) && is_array($params['groups']['include'])) {
89 foreach ($params['groups']['include'] as $entityId) {
90 $dao = new CRM_Campaign_DAO_CampaignGroup();
91 $dao->campaign_id = $campaign->id;
92 $dao->entity_table = $groupTableName;
93 $dao->entity_id = $entityId;
94 $dao->group_type = 'Include';
95 $dao->save();
96 }
97 }
98
99 //store custom data
100 if (!empty($params['custom']) &&
101 is_array($params['custom'])
102 ) {
103 CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_campaign', $campaign->id);
104 }
105
106 return $campaign;
107 }
108
109 /**
110 * Delete the campaign.
111 *
112 * @param int $id
113 * Id of the campaign.
114 *
115 * @return bool|mixed
116 */
117 public static function del($id) {
118 if (!$id) {
119 return FALSE;
120 }
121
122 CRM_Utils_Hook::pre('delete', 'Campaign', $id, CRM_Core_DAO::$_nullArray);
123
124 $dao = new CRM_Campaign_DAO_Campaign();
125 $dao->id = $id;
126 $result = $dao->delete();
127
128 CRM_Utils_Hook::post('delete', 'Campaign', $id, $dao);
129
130 return $result;
131 }
132
133 /**
134 * Retrieve DB object based on input parameters.
135 *
136 * It also stores all the retrieved values in the default array.
137 *
138 * @param array $params
139 * (reference ) an assoc array of name/value pairs.
140 * @param array $defaults
141 * (reference ) an assoc array to hold the flattened values.
142 *
143 * @return \CRM_Campaign_DAO_Campaign|null
144 */
145 public static function retrieve(&$params, &$defaults) {
146 $campaign = new CRM_Campaign_DAO_Campaign();
147
148 $campaign->copyValues($params);
149
150 if ($campaign->find(TRUE)) {
151 CRM_Core_DAO::storeValues($campaign, $defaults);
152 return $campaign;
153 }
154 return NULL;
155 }
156
157 /**
158 * Return the all eligible campaigns w/ cache.
159 *
160 * @param int $includeId
161 * Lets inlcude this campaign by force.
162 * @param int $excludeId
163 * Do not include this campaign.
164 * @param bool $onlyActive
165 * Consider only active campaigns.
166 *
167 * @param bool $onlyCurrent
168 * @param bool $appendDatesToTitle
169 * @param bool $forceAll
170 *
171 * @return mixed
172 * $campaigns a set of campaigns.
173 */
174 public static function getCampaigns(
175 $includeId = NULL,
176 $excludeId = NULL,
177 $onlyActive = TRUE,
178 $onlyCurrent = TRUE,
179 $appendDatesToTitle = FALSE,
180 $forceAll = FALSE
181 ) {
182 static $campaigns;
183 $cacheKey = 0;
184 $cacheKeyParams = [
185 'includeId',
186 'excludeId',
187 'onlyActive',
188 'onlyCurrent',
189 'appendDatesToTitle',
190 'forceAll',
191 ];
192 foreach ($cacheKeyParams as $param) {
193 $cacheParam = $$param;
194 if (!$cacheParam) {
195 $cacheParam = 0;
196 }
197 $cacheKey .= '_' . $cacheParam;
198 }
199
200 if (!isset($campaigns[$cacheKey])) {
201 $where = ['( camp.title IS NOT NULL )'];
202 if ($excludeId) {
203 $where[] = "( camp.id != $excludeId )";
204 }
205 if ($onlyActive) {
206 $where[] = '( camp.is_active = 1 )';
207 }
208 if ($onlyCurrent) {
209 $where[] = '( camp.end_date IS NULL OR camp.end_date >= NOW() )';
210 }
211 $whereClause = implode(' AND ', $where);
212 if ($includeId) {
213 $whereClause .= " OR ( camp.id = $includeId )";
214 }
215
216 //lets force all.
217 if ($forceAll) {
218 $whereClause = '( 1 )';
219 }
220
221 $query = "
222 SELECT camp.id,
223 camp.title,
224 camp.start_date,
225 camp.end_date
226 FROM civicrm_campaign camp
227 WHERE {$whereClause}
228 Order By camp.title";
229
230 $campaign = CRM_Core_DAO::executeQuery($query);
231 $campaigns[$cacheKey] = [];
232 $config = CRM_Core_Config::singleton();
233
234 while ($campaign->fetch()) {
235 $title = $campaign->title;
236 if ($appendDatesToTitle) {
237 $dates = [];
238 foreach (['start_date', 'end_date'] as $date) {
239 if ($campaign->$date) {
240 $dates[] = CRM_Utils_Date::customFormat($campaign->$date, $config->dateformatFull);
241 }
242 }
243 if (!empty($dates)) {
244 $title .= ' (' . implode('-', $dates) . ')';
245 }
246 }
247 $campaigns[$cacheKey][$campaign->id] = $title;
248 }
249 }
250
251 return $campaigns[$cacheKey];
252 }
253
254 /**
255 * Wrapper to self::getCampaigns( )
256 * w/ permissions and component check.
257 *
258 * @param int $includeId
259 * @param int $excludeId
260 * @param bool $onlyActive
261 * @param bool $onlyCurrent
262 * @param bool $appendDatesToTitle
263 * @param bool $forceAll
264 * @param bool $doCheckForComponent
265 * @param bool $doCheckForPermissions
266 *
267 * @return mixed
268 */
269 public static function getPermissionedCampaigns(
270 $includeId = NULL,
271 $excludeId = NULL,
272 $onlyActive = TRUE,
273 $onlyCurrent = TRUE,
274 $appendDatesToTitle = FALSE,
275 $forceAll = FALSE,
276 $doCheckForComponent = TRUE,
277 $doCheckForPermissions = TRUE
278 ) {
279 $cacheKey = 0;
280 $cachekeyParams = [
281 'includeId',
282 'excludeId',
283 'onlyActive',
284 'onlyCurrent',
285 'appendDatesToTitle',
286 'doCheckForComponent',
287 'doCheckForPermissions',
288 'forceAll',
289 ];
290 foreach ($cachekeyParams as $param) {
291 $cacheKeyParam = $$param;
292 if (!$cacheKeyParam) {
293 $cacheKeyParam = 0;
294 }
295 $cacheKey .= '_' . $cacheKeyParam;
296 }
297
298 static $validCampaigns;
299 if (!isset($validCampaigns[$cacheKey])) {
300 $isValid = TRUE;
301 $campaigns = [
302 'campaigns' => [],
303 'hasAccessCampaign' => FALSE,
304 'isCampaignEnabled' => FALSE,
305 ];
306
307 //do check for component.
308 if ($doCheckForComponent) {
309 $campaigns['isCampaignEnabled'] = $isValid = self::isCampaignEnable();
310 }
311
312 //do check for permissions.
313 if ($doCheckForPermissions) {
314 $campaigns['hasAccessCampaign'] = $isValid = self::accessCampaign();
315 }
316
317 //finally retrieve campaigns from db.
318 if ($isValid) {
319 $campaigns['campaigns'] = self::getCampaigns($includeId,
320 $excludeId,
321 $onlyActive,
322 $onlyCurrent,
323 $appendDatesToTitle,
324 $forceAll
325 );
326 }
327
328 //store in cache.
329 $validCampaigns[$cacheKey] = $campaigns;
330 }
331
332 return $validCampaigns[$cacheKey];
333 }
334
335 /**
336 * Is CiviCampaign enabled.
337 * @return bool
338 */
339 public static function isCampaignEnable() {
340 static $isEnable = NULL;
341
342 if (!isset($isEnable)) {
343 $isEnable = FALSE;
344 $config = CRM_Core_Config::singleton();
345 if (in_array('CiviCampaign', $config->enableComponents)) {
346 $isEnable = TRUE;
347 }
348 }
349
350 return $isEnable;
351 }
352
353 /**
354 * Retrieve campaigns for dashboard.
355 *
356 * @param array $params
357 * @param bool $onlyCount
358 *
359 * @return array|int
360 */
361 public static function getCampaignSummary($params = [], $onlyCount = FALSE) {
362 $campaigns = [];
363
364 //build the limit and order clause.
365 $limitClause = $orderByClause = $lookupTableJoins = NULL;
366 if (!$onlyCount) {
367 $sortParams = [
368 'sort' => 'start_date',
369 'offset' => 0,
370 'rowCount' => 10,
371 'sortOrder' => 'desc',
372 ];
373 foreach ($sortParams as $name => $default) {
374 if (!empty($params[$name])) {
375 $sortParams[$name] = $params[$name];
376 }
377 }
378
379 //need to lookup tables.
380 $orderOnCampaignTable = TRUE;
381 if ($sortParams['sort'] == 'status') {
382 $orderOnCampaignTable = FALSE;
383 $lookupTableJoins = "
384 LEFT JOIN civicrm_option_value status ON ( status.value = campaign.status_id OR campaign.status_id IS NULL )
385 INNER JOIN civicrm_option_group grp ON ( status.option_group_id = grp.id AND grp.name = 'campaign_status' )";
386 $orderByClause = "ORDER BY status.label {$sortParams['sortOrder']}";
387 }
388 elseif ($sortParams['sort'] == 'campaign_type') {
389 $orderOnCampaignTable = FALSE;
390 $lookupTableJoins = "
391 LEFT JOIN civicrm_option_value campaign_type ON ( campaign_type.value = campaign.campaign_type_id
392 OR campaign.campaign_type_id IS NULL )
393 INNER JOIN civicrm_option_group grp ON ( campaign_type.option_group_id = grp.id AND grp.name = 'campaign_type' )";
394 $orderByClause = "ORDER BY campaign_type.label {$sortParams['sortOrder']}";
395 }
396 elseif ($sortParams['sort'] == 'isActive') {
397 $sortParams['sort'] = 'is_active';
398 }
399 if ($orderOnCampaignTable) {
400 $orderByClause = "ORDER BY campaign.{$sortParams['sort']} {$sortParams['sortOrder']}";
401 }
402 $limitClause = "LIMIT {$sortParams['offset']}, {$sortParams['rowCount']}";
403 }
404
405 //build the where clause.
406 $queryParams = $where = [];
407 if (!empty($params['id'])) {
408 $where[] = "( campaign.id = %1 )";
409 $queryParams[1] = [$params['id'], 'Positive'];
410 }
411 if (!empty($params['name'])) {
412 $where[] = "( campaign.name LIKE %2 )";
413 $queryParams[2] = ['%' . trim($params['name']) . '%', 'String'];
414 }
415 if (!empty($params['title'])) {
416 $where[] = "( campaign.title LIKE %3 )";
417 $queryParams[3] = ['%' . trim($params['title']) . '%', 'String'];
418 }
419 if (!empty($params['start_date'])) {
420 $startDate = CRM_Utils_Date::processDate($params['start_date']);
421 $where[] = "( campaign.start_date >= %4 OR campaign.start_date IS NULL )";
422 $queryParams[4] = [$startDate, 'String'];
423 }
424 if (!empty($params['end_date'])) {
425 $endDate = CRM_Utils_Date::processDate($params['end_date'], '235959');
426 $where[] = "( campaign.end_date <= %5 OR campaign.end_date IS NULL )";
427 $queryParams[5] = [$endDate, 'String'];
428 }
429 if (!empty($params['description'])) {
430 $where[] = "( campaign.description LIKE %6 )";
431 $queryParams[6] = ['%' . trim($params['description']) . '%', 'String'];
432 }
433 if (!empty($params['campaign_type_id'])) {
434 $typeId = $params['campaign_type_id'];
435 if (is_array($params['campaign_type_id'])) {
436 $typeId = implode(' , ', $params['campaign_type_id']);
437 }
438 $where[] = "( campaign.campaign_type_id IN ( {$typeId} ) )";
439 }
440 if (!empty($params['status_id'])) {
441 $statusId = $params['status_id'];
442 if (is_array($params['status_id'])) {
443 $statusId = implode(' , ', $params['status_id']);
444 }
445 $where[] = "( campaign.status_id IN ( {$statusId} ) )";
446 }
447 if (array_key_exists('is_active', $params)) {
448 $active = "( campaign.is_active = 1 )";
449 if (!empty($params['is_active'])) {
450 $active = "( campaign.is_active = 0 OR campaign.is_active IS NULL )";
451 }
452 $where[] = $active;
453 }
454 $whereClause = NULL;
455 if (!empty($where)) {
456 $whereClause = ' WHERE ' . implode(" \nAND ", $where);
457 }
458
459 $properties = [
460 'id',
461 'name',
462 'title',
463 'start_date',
464 'end_date',
465 'status_id',
466 'is_active',
467 'description',
468 'campaign_type_id',
469 ];
470
471 $selectClause = '
472 SELECT campaign.id as id,
473 campaign.name as name,
474 campaign.title as title,
475 campaign.is_active as is_active,
476 campaign.status_id as status_id,
477 campaign.end_date as end_date,
478 campaign.start_date as start_date,
479 campaign.description as description,
480 campaign.campaign_type_id as campaign_type_id';
481 if ($onlyCount) {
482 $selectClause = 'SELECT COUNT(*)';
483 }
484 $fromClause = 'FROM civicrm_campaign campaign';
485
486 $query = "{$selectClause} {$fromClause} {$lookupTableJoins} {$whereClause} {$orderByClause} {$limitClause}";
487
488 //in case of only count.
489 if ($onlyCount) {
490 return (int) CRM_Core_DAO::singleValueQuery($query, $queryParams);
491 }
492
493 $campaign = CRM_Core_DAO::executeQuery($query, $queryParams);
494 while ($campaign->fetch()) {
495 foreach ($properties as $property) {
496 $campaigns[$campaign->id][$property] = $campaign->$property;
497 }
498 }
499
500 return $campaigns;
501 }
502
503 /**
504 * Get the campaign count.
505 *
506 */
507 public static function getCampaignCount() {
508 return (int) CRM_Core_DAO::singleValueQuery('SELECT COUNT(*) FROM civicrm_campaign');
509 }
510
511 /**
512 * Get Campaigns groups.
513 *
514 * @param int $campaignId
515 * Campaign id.
516 *
517 * @return array
518 */
519 public static function getCampaignGroups($campaignId) {
520 static $campaignGroups;
521 if (!$campaignId) {
522 return [];
523 }
524
525 if (!isset($campaignGroups[$campaignId])) {
526 $campaignGroups[$campaignId] = [];
527
528 $query = "
529 SELECT grp.title, grp.id
530 FROM civicrm_campaign_group campgrp
531 INNER JOIN civicrm_group grp ON ( grp.id = campgrp.entity_id )
532 WHERE campgrp.group_type = 'Include'
533 AND campgrp.entity_table = 'civicrm_group'
534 AND campgrp.campaign_id = %1";
535
536 $groups = CRM_Core_DAO::executeQuery($query, [1 => [$campaignId, 'Positive']]);
537 while ($groups->fetch()) {
538 $campaignGroups[$campaignId][$groups->id] = $groups->title;
539 }
540 }
541
542 return $campaignGroups[$campaignId];
543 }
544
545 /**
546 * Update the is_active flag in the db.
547 *
548 * @param int $id
549 * Id of the database record.
550 * @param bool $is_active
551 * Value we want to set the is_active field.
552 *
553 * @return bool
554 * true if we found and updated the object, else false
555 */
556 public static function setIsActive($id, $is_active) {
557 return CRM_Core_DAO::setFieldValue('CRM_Campaign_DAO_Campaign', $id, 'is_active', $is_active);
558 }
559
560 /**
561 * @return bool
562 */
563 public static function accessCampaign() {
564 static $allow = NULL;
565
566 if (!isset($allow)) {
567 $allow = FALSE;
568 if (CRM_Core_Permission::check('manage campaign') ||
569 CRM_Core_Permission::check('administer CiviCampaign')
570 ) {
571 $allow = TRUE;
572 }
573 }
574
575 return $allow;
576 }
577
578 /**
579 * Add select element for campaign
580 * and assign needful info to templates.
581 *
582 * @param CRM_Core_Form $form
583 * @param int $connectedCampaignId
584 */
585 public static function addCampaign(&$form, $connectedCampaignId = NULL) {
586 //some forms do set default and freeze.
587 $appendDates = TRUE;
588 if ($form->get('action') & CRM_Core_Action::VIEW) {
589 $appendDates = FALSE;
590 }
591
592 $campaignDetails = self::getPermissionedCampaigns($connectedCampaignId, NULL, TRUE, TRUE, $appendDates);
593 $fields = ['campaigns', 'hasAccessCampaign', 'isCampaignEnabled'];
594 foreach ($fields as $fld) {
595 $$fld = CRM_Utils_Array::value($fld, $campaignDetails);
596 }
597
598 $showAddCampaign = FALSE;
599 if ($connectedCampaignId || ($isCampaignEnabled && $hasAccessCampaign)) {
600 $showAddCampaign = TRUE;
601 $campaign = $form->addEntityRef('campaign_id', ts('Campaign'), [
602 'entity' => 'Campaign',
603 'create' => TRUE,
604 'select' => ['minimumInputLength' => 0],
605 ]);
606 //lets freeze when user does not has access or campaign is disabled.
607 if (!$isCampaignEnabled || !$hasAccessCampaign) {
608 $campaign->freeze();
609 }
610 }
611
612 //carry this info to templates.
613 $infoFields = [
614 'showAddCampaign',
615 'hasAccessCampaign',
616 'isCampaignEnabled',
617 ];
618 foreach ($infoFields as $fld) {
619 $campaignInfo[$fld] = $$fld;
620 }
621 $form->assign('campaignInfo', $campaignInfo);
622 }
623
624 /**
625 * Add campaign in component search.
626 * and assign needful info to templates.
627 *
628 * @param CRM_Core_Form $form
629 * @param string $elementName
630 */
631 public static function addCampaignInComponentSearch(&$form, $elementName = 'campaign_id') {
632 $campaignInfo = [];
633 $campaignDetails = self::getPermissionedCampaigns(NULL, NULL, FALSE, FALSE, FALSE, TRUE);
634 $fields = ['campaigns', 'hasAccessCampaign', 'isCampaignEnabled'];
635 foreach ($fields as $fld) {
636 $$fld = CRM_Utils_Array::value($fld, $campaignDetails);
637 }
638 $showCampaignInSearch = FALSE;
639 if ($isCampaignEnabled && $hasAccessCampaign && !empty($campaigns)) {
640 //get the current campaign only.
641 $currentCampaigns = self::getCampaigns(NULL, NULL, FALSE);
642 $pastCampaigns = array_diff($campaigns, $currentCampaigns);
643 $allCampaigns = [];
644 if (!empty($currentCampaigns)) {
645 $allCampaigns = ['crm_optgroup_current_campaign' => ts('Current Campaigns')] + $currentCampaigns;
646 }
647 if (!empty($pastCampaigns)) {
648 $allCampaigns += ['crm_optgroup_past_campaign' => ts('Past Campaigns')] + $pastCampaigns;
649 }
650
651 $showCampaignInSearch = TRUE;
652 $form->add('select', $elementName, ts('Campaigns'), $allCampaigns, FALSE,
653 ['id' => 'campaigns', 'multiple' => 'multiple', 'class' => 'crm-select2']
654 );
655 }
656 $infoFields = [
657 'elementName',
658 'hasAccessCampaign',
659 'isCampaignEnabled',
660 'showCampaignInSearch',
661 ];
662 foreach ($infoFields as $fld) {
663 $campaignInfo[$fld] = $$fld;
664 }
665 $form->assign('campaignInfo', $campaignInfo);
666 }
667
668 /**
669 * @return array
670 */
671 public static function getEntityRefFilters() {
672 return [
673 ['key' => 'campaign_type_id', 'value' => ts('Campaign Type')],
674 ['key' => 'status_id', 'value' => ts('Status')],
675 [
676 'key' => 'start_date',
677 'value' => ts('Start Date'),
678 'options' => [
679 ['key' => '{">":"now"}', 'value' => ts('Upcoming')],
680 [
681 'key' => '{"BETWEEN":["now - 3 month","now"]}',
682 'value' => ts('Past 3 Months'),
683 ],
684 [
685 'key' => '{"BETWEEN":["now - 6 month","now"]}',
686 'value' => ts('Past 6 Months'),
687 ],
688 [
689 'key' => '{"BETWEEN":["now - 1 year","now"]}',
690 'value' => ts('Past Year'),
691 ],
692 ],
693 ],
694 [
695 'key' => 'end_date',
696 'value' => ts('End Date'),
697 'options' => [
698 ['key' => '{">":"now"}', 'value' => ts('In the future')],
699 ['key' => '{"<":"now"}', 'value' => ts('In the past')],
700 ['key' => '{"IS NULL":"1"}', 'value' => ts('Not set')],
701 ],
702 ],
703 ];
704 }
705
706 /**
707 * Links to create new campaigns from entityRef widget
708 *
709 * @return array|bool
710 */
711 public static function getEntityRefCreateLinks() {
712 if (CRM_Core_Permission::check([['administer CiviCampaign', 'manage campaign']])) {
713 return [
714 [
715 'label' => ts('New Campaign'),
716 'url' => CRM_Utils_System::url('civicrm/campaign/add', "reset=1",
717 NULL, NULL, FALSE, FALSE, TRUE),
718 'type' => 'Campaign',
719 ],
720 ];
721 }
722 return FALSE;
723 }
724
725 }