Merge pull request #21526 from mattwire/viewrecur_phpnotice
[civicrm-core.git] / CRM / Activity / Tokens.php
1 <?php
2
3 /*
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
6 | |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
11 */
12
13 /**
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 use Civi\Token\Event\TokenValueEvent;
19 use Civi\Token\TokenRow;
20
21 /**
22 * Class CRM_Member_Tokens
23 *
24 * Generate "activity.*" tokens.
25 *
26 * This TokenSubscriber was originally produced by refactoring the code from the
27 * scheduled-reminder system with the goal of making that system
28 * more flexible. The current implementation is still coupled to
29 * scheduled-reminders. It would be good to figure out a more generic
30 * implementation which is not tied to scheduled reminders, although
31 * that is outside the current scope.
32 *
33 * This has been enhanced to work with PDF/letter merge
34 */
35 class CRM_Activity_Tokens extends CRM_Core_EntityTokens {
36
37 use CRM_Core_TokenTrait;
38
39 /**
40 * Get the entity name for api v4 calls.
41 *
42 * @return string
43 */
44 protected function getApiEntityName(): string {
45 return 'Activity';
46 }
47
48 /**
49 * @return string
50 */
51 private function getEntityTableName(): string {
52 return 'civicrm_activity';
53 }
54
55 /**
56 * @return string
57 */
58 private function getEntityContextSchema(): string {
59 return 'activityId';
60 }
61
62 /**
63 * Mapping from tokenName to api return field
64 * Using arrays allows more complex tokens to be handled that require more than one API field.
65 * For example, an address token might want ['street_address', 'city', 'postal_code']
66 *
67 * @var array
68 */
69 private static $fieldMapping = [
70 'activity_id' => ['id'],
71 'activity_type' => ['activity_type_id'],
72 'status' => ['status_id'],
73 'campaign' => ['campaign_id'],
74 ];
75
76 /**
77 * @inheritDoc
78 */
79 public function alterActionScheduleQuery(\Civi\ActionSchedule\Event\MailingQueryEvent $e): void {
80 if ($e->mapping->getEntity() !== $this->getEntityTableName()) {
81 return;
82 }
83
84 // The joint expression for activities needs some extra nuance to handle.
85 // Multiple revisions of the activity.
86 // Q: Could we simplify & move the extra AND clauses into `where(...)`?
87 $e->query->param('casEntityJoinExpr', 'e.id = reminder.entity_id AND e.is_current_revision = 1 AND e.is_deleted = 0');
88 $e->query->select('e.id AS tokenContext_' . $this->getEntityContextSchema());
89 }
90
91 /**
92 * Evaluate the content of a single token.
93 *
94 * @param \Civi\Token\TokenRow $row
95 * The record for which we want token values.
96 * @param string $entity
97 * The name of the token entity.
98 * @param string $field
99 * The name of the token field.
100 * @param mixed $prefetch
101 * Any data that was returned by the prefetch().
102 *
103 * @throws \CRM_Core_Exception
104 */
105 public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL) {
106 $activityId = $row->context[$this->getEntityContextSchema()];
107
108 if (!empty($this->getDeprecatedTokens()[$field])) {
109 $realField = $this->getDeprecatedTokens()[$field];
110 parent::evaluateToken($row, $entity, $realField, $prefetch);
111 $row->format('text/plain')->tokens($entity, $field, $row->tokens['activity'][$realField]);
112 }
113 elseif (in_array($field, ['case_id'])) {
114 // An activity can be linked to multiple cases so case_id is always an array.
115 // We just return the first case ID for the token.
116 // this weird hack might exist because apiv3 is weird &
117 $caseID = CRM_Core_DAO::singleValueQuery('SELECT case_id FROM civicrm_case_activity WHERE activity_id = %1 LIMIT 1', [1 => [$activityId, 'Integer']]);
118 $row->tokens($entity, $field, $caseID ?? '');
119 }
120 else {
121 parent::evaluateToken($row, $entity, $field, $prefetch);
122 }
123 }
124
125 /**
126 * Get all the tokens supported by this processor.
127 *
128 * @return array|string[]
129 * @throws \API_Exception
130 */
131 protected function getAllTokens(): array {
132 $tokens = parent::getAllTokens();
133 if (array_key_exists('CiviCase', CRM_Core_Component::getEnabledComponents())) {
134 $tokens['case_id'] = ts('Activity Case ID');
135 }
136 return $tokens;
137 }
138
139 /**
140 * Get the basic tokens provided.
141 *
142 * @return array token name => token label
143 */
144 public function getBasicTokens(): array {
145 if (!isset($this->basicTokens)) {
146 $this->basicTokens = [
147 'id' => ts('Activity ID'),
148 'subject' => ts('Activity Subject'),
149 'details' => ts('Activity Details'),
150 'activity_date_time' => ts('Activity Date-Time'),
151 'created_date' => ts('Activity Created Date'),
152 'modified_date' => ts('Activity Modified Date'),
153 'activity_type_id' => ts('Activity Type ID'),
154 'status_id' => ts('Activity Status ID'),
155 'location' => ts('Activity Location'),
156 'duration' => ts('Activity Duration'),
157 ];
158 if (CRM_Campaign_BAO_Campaign::isCampaignEnable()) {
159 $this->basicTokens['campaign_id'] = ts('Campaign ID');
160 }
161 }
162 return $this->basicTokens;
163 }
164
165 /**
166 * @inheritDoc
167 */
168 public function getActiveTokens(TokenValueEvent $e) {
169 $messageTokens = $e->getTokenProcessor()->getMessageTokens();
170 if (!isset($messageTokens[$this->entity])) {
171 return NULL;
172 }
173
174 $activeTokens = [];
175 // if message token contains '_\d+_', then treat as '_N_'
176 foreach ($messageTokens[$this->entity] as $msgToken) {
177 if (array_key_exists($msgToken, $this->tokenNames)) {
178 $activeTokens[] = $msgToken;
179 }
180 elseif (in_array($msgToken, ['campaign', 'activity_id', 'status', 'activity_type', 'case_id'])) {
181 $activeTokens[] = $msgToken;
182 }
183 else {
184 $altToken = preg_replace('/_\d+_/', '_N_', $msgToken);
185 if (array_key_exists($altToken, $this->tokenNames)) {
186 $activeTokens[] = $msgToken;
187 }
188 }
189 }
190 return array_unique($activeTokens);
191 }
192
193 public function getPrefetchFields(TokenValueEvent $e): array {
194 $tokens = parent::getPrefetchFields($e);
195 $active = $this->getActiveTokens($e);
196 foreach ($this->getDeprecatedTokens() as $old => $new) {
197 if (in_array($old, $active, TRUE) && !in_array($new, $active, TRUE)) {
198 $tokens[] = $new;
199 }
200 }
201 return $tokens;
202 }
203
204 /**
205 * These tokens still work but we don't advertise them.
206 *
207 * We will actively remove from the following places
208 * - scheduled reminders
209 * - add to 'blocked' on pdf letter & email
210 *
211 * & then at some point start issuing warnings for them.
212 *
213 * @return string[]
214 */
215 protected function getDeprecatedTokens(): array {
216 return [
217 'activity_id' => 'id',
218 'activity_type' => 'activity_type_id:label',
219 'status' => 'status_id:label',
220 'campaign' => 'campaign_id:label',
221 ];
222 }
223
224 }