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