Commit | Line | Data |
---|---|---|
6050e9ad TO |
1 | <?php |
2 | ||
3 | /* | |
4 | +--------------------------------------------------------------------+ | |
5 | | CiviCRM version 4.7 | | |
6 | +--------------------------------------------------------------------+ | |
8c9251b3 | 7 | | Copyright CiviCRM LLC (c) 2004-2018 | |
6050e9ad TO |
8 | +--------------------------------------------------------------------+ |
9 | | This file is a part of CiviCRM. | | |
10 | | | | |
11 | | CiviCRM is free software; you can copy, modify, and distribute it | | |
12 | | under the terms of the GNU Affero General Public License | | |
13 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | | |
14 | | | | |
15 | | CiviCRM is distributed in the hope that it will be useful, but | | |
16 | | WITHOUT ANY WARRANTY; without even the implied warranty of | | |
17 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | |
18 | | See the GNU Affero General Public License for more details. | | |
19 | | | | |
20 | | You should have received a copy of the GNU Affero General Public | | |
21 | | License and the CiviCRM Licensing Exception along | | |
22 | | with this program; if not, contact CiviCRM LLC | | |
23 | | at info[AT]civicrm[DOT]org. If you have questions about the | | |
24 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
25 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | | |
26 | +--------------------------------------------------------------------+ | |
27 | */ | |
28 | ||
29 | namespace Civi\Core\SqlTrigger; | |
30 | ||
31 | use Civi\Core\Event\GenericHookEvent; | |
32 | ||
33 | /** | |
34 | * Build a set of SQL triggers for tracking timestamps on an entity. | |
35 | * | |
36 | * This class is a generalization of CRM-10554 with the aim of enabling CRM-20958. | |
37 | * | |
38 | * @package CRM | |
8c9251b3 | 39 | * @copyright CiviCRM LLC (c) 2004-2018 |
6050e9ad TO |
40 | */ |
41 | class TimestampTriggers { | |
42 | ||
43 | /** | |
44 | * @var string | |
45 | * SQL table name. | |
46 | * Ex: 'civicrm_contact', 'civicrm_activity'. | |
47 | */ | |
48 | private $tableName; | |
49 | ||
50 | /** | |
51 | * @var string | |
52 | * An entity name (from civicrm_custom_group.extends). | |
53 | * Ex: 'Contact', 'Activity'. | |
54 | */ | |
55 | private $customDataEntity; | |
56 | ||
57 | /** | |
58 | * @var string | |
59 | * SQL column name. | |
60 | * Ex: 'created_date'. | |
61 | */ | |
62 | private $createdDate; | |
63 | ||
64 | /** | |
65 | * @var string | |
66 | * SQL column name. | |
67 | * Ex: 'modified_date'. | |
68 | */ | |
69 | private $modifiedDate; | |
70 | ||
71 | /** | |
72 | * @var array | |
73 | * Ex: $relations[0] == array('table' => 'civicrm_bar', 'column' => 'foo_id'); | |
74 | */ | |
75 | private $relations; | |
76 | ||
77 | /** | |
78 | * @param string $tableName | |
79 | * SQL table name. | |
80 | * Ex: 'civicrm_contact', 'civicrm_activity'. | |
81 | * @param string $customDataEntity | |
82 | * An entity name (from civicrm_custom_group.extends). | |
83 | * Ex: 'Contact', 'Activity'. | |
84 | * @return TimestampTriggers | |
85 | */ | |
86 | public static function create($tableName, $customDataEntity) { | |
87 | return new static($tableName, $customDataEntity); | |
88 | } | |
89 | ||
90 | /** | |
91 | * TimestampTriggers constructor. | |
92 | * | |
93 | * @param string $tableName | |
94 | * SQL table name. | |
95 | * Ex: 'civicrm_contact', 'civicrm_activity'. | |
96 | * @param string $customDataEntity | |
97 | * An entity name (from civicrm_custom_group.extends). | |
98 | * Ex: 'Contact', 'Activity'. | |
99 | * @param string $createdDate | |
100 | * SQL column name. | |
101 | * Ex: 'created_date'. | |
102 | * @param string $modifiedDate | |
103 | * SQL column name. | |
104 | * Ex: 'modified_date'. | |
105 | * @param array $relations | |
106 | * Ex: $relations[0] == array('table' => 'civicrm_bar', 'column' => 'foo_id'); | |
107 | */ | |
108 | public function __construct( | |
109 | $tableName, | |
110 | $customDataEntity, | |
111 | $createdDate = 'created_date', | |
112 | $modifiedDate = 'modified_date', | |
113 | $relations = array() | |
114 | ) { | |
115 | $this->tableName = $tableName; | |
116 | $this->customDataEntity = $customDataEntity; | |
117 | $this->createdDate = $createdDate; | |
118 | $this->modifiedDate = $modifiedDate; | |
119 | $this->relations = $relations; | |
120 | } | |
121 | ||
122 | /** | |
123 | * Add our list of triggers to the global list. | |
124 | * | |
125 | * @param \Civi\Core\Event\GenericHookEvent $e | |
126 | * @see \CRM_Utils_Hook::triggerInfo | |
127 | */ | |
128 | public function onTriggerInfo($e) { | |
129 | $this->alterTriggerInfo($e->info, $e->tableName); | |
130 | } | |
131 | ||
132 | /** | |
133 | * Add our list of triggers to the global list. | |
134 | * | |
135 | * @see \CRM_Utils_Hook::triggerInfo | |
136 | * @see \CRM_Core_DAO::triggerRebuild | |
137 | * | |
138 | * @param array $info | |
139 | * See hook_civicrm_triggerInfo. | |
140 | * @param string|NULL $tableFilter | |
141 | * See hook_civicrm_triggerInfo. | |
142 | */ | |
143 | public function alterTriggerInfo(&$info, $tableFilter = NULL) { | |
4ccc3cdd TO |
144 | // If we haven't upgraded yet, then the created_date/modified_date may not exist. |
145 | // In the past, this was a version-based check, but checkFieldExists() | |
146 | // seems more robust. | |
6050e9ad TO |
147 | if (\CRM_Core_Config::isUpgradeMode()) { |
148 | if (!\CRM_Core_DAO::checkFieldExists($this->getTableName(), | |
149 | $this->getCreatedDate()) | |
150 | ) { | |
151 | return; | |
152 | } | |
6050e9ad TO |
153 | } |
154 | ||
155 | if ($tableFilter == NULL || $tableFilter == $this->getTableName()) { | |
156 | $info[] = array( | |
157 | 'table' => array($this->getTableName()), | |
158 | 'when' => 'BEFORE', | |
159 | 'event' => array('INSERT'), | |
160 | 'sql' => "\nSET NEW.{$this->getCreatedDate()} = CURRENT_TIMESTAMP;\n", | |
161 | ); | |
162 | } | |
163 | ||
164 | // Update timestamp when modifying closely related tables | |
165 | $relIdx = \CRM_Utils_Array::index( | |
166 | array('column', 'table'), | |
167 | $this->getAllRelations() | |
168 | ); | |
169 | foreach ($relIdx as $column => $someRelations) { | |
170 | $this->generateTimestampTriggers($info, $tableFilter, | |
171 | array_keys($someRelations), $column); | |
172 | } | |
173 | } | |
174 | ||
175 | /** | |
176 | * Generate triggers to update the timestamp. | |
177 | * | |
178 | * The corresponding civicrm_FOO row is updated on insert/update/delete | |
179 | * to a table that extends civicrm_FOO. | |
180 | * Don't regenerate triggers for all such tables if only asked for one table. | |
181 | * | |
182 | * @param array $info | |
183 | * Reference to the array where generated trigger information is being stored | |
184 | * @param string|null $tableFilter | |
185 | * Name of the table for which triggers are being generated, or NULL if all tables | |
186 | * @param array $relatedTableNames | |
187 | * Array of all core or all custom table names extending civicrm_FOO | |
188 | * @param string $contactRefColumn | |
189 | * 'contact_id' if processing core tables, 'entity_id' if processing custom tables | |
190 | * | |
191 | * @link https://issues.civicrm.org/jira/browse/CRM-15602 | |
192 | * @see triggerInfo | |
193 | */ | |
194 | public function generateTimestampTriggers( | |
195 | &$info, | |
196 | $tableFilter, | |
197 | $relatedTableNames, | |
198 | $contactRefColumn | |
199 | ) { | |
200 | // Safety | |
201 | $contactRefColumn = \CRM_Core_DAO::escapeString($contactRefColumn); | |
202 | ||
203 | // If specific related table requested, just process that one. | |
204 | // (Reply: This feels fishy.) | |
205 | if (in_array($tableFilter, $relatedTableNames)) { | |
206 | $relatedTableNames = array($tableFilter); | |
207 | } | |
208 | ||
209 | // If no specific table requested (include all related tables), | |
210 | // or a specific related table requested (as matched above) | |
211 | if (empty($tableFilter) || isset($relatedTableNames[$tableFilter])) { | |
212 | $info[] = array( | |
213 | 'table' => $relatedTableNames, | |
214 | 'when' => 'AFTER', | |
215 | 'event' => array('INSERT', 'UPDATE'), | |
216 | 'sql' => "\nUPDATE {$this->getTableName()} SET {$this->getModifiedDate()} = CURRENT_TIMESTAMP WHERE id = NEW.$contactRefColumn;\n", | |
217 | ); | |
218 | $info[] = array( | |
219 | 'table' => $relatedTableNames, | |
220 | 'when' => 'AFTER', | |
221 | 'event' => array('DELETE'), | |
222 | 'sql' => "\nUPDATE {$this->getTableName()} SET {$this->getModifiedDate()} = CURRENT_TIMESTAMP WHERE id = OLD.$contactRefColumn;\n", | |
223 | ); | |
224 | } | |
225 | } | |
226 | ||
227 | /** | |
228 | * @return string | |
229 | */ | |
230 | public function getTableName() { | |
231 | return $this->tableName; | |
232 | } | |
233 | ||
234 | /** | |
235 | * @param string $tableName | |
236 | * @return TimestampTriggers | |
237 | */ | |
238 | public function setTableName($tableName) { | |
239 | $this->tableName = $tableName; | |
240 | return $this; | |
241 | } | |
242 | ||
243 | /** | |
244 | * @return string | |
245 | */ | |
246 | public function getCustomDataEntity() { | |
247 | return $this->customDataEntity; | |
248 | } | |
249 | ||
250 | /** | |
251 | * @param string $customDataEntity | |
252 | * @return TimestampTriggers | |
253 | */ | |
254 | public function setCustomDataEntity($customDataEntity) { | |
255 | $this->customDataEntity = $customDataEntity; | |
256 | return $this; | |
257 | } | |
258 | ||
259 | /** | |
260 | * @return string | |
261 | */ | |
262 | public function getCreatedDate() { | |
263 | return $this->createdDate; | |
264 | } | |
265 | ||
266 | /** | |
267 | * @param string $createdDate | |
268 | * @return TimestampTriggers | |
269 | */ | |
270 | public function setCreatedDate($createdDate) { | |
271 | $this->createdDate = $createdDate; | |
272 | return $this; | |
273 | } | |
274 | ||
275 | /** | |
276 | * @return string | |
277 | */ | |
278 | public function getModifiedDate() { | |
279 | return $this->modifiedDate; | |
280 | } | |
281 | ||
282 | /** | |
283 | * @param string $modifiedDate | |
284 | * @return TimestampTriggers | |
285 | */ | |
286 | public function setModifiedDate($modifiedDate) { | |
287 | $this->modifiedDate = $modifiedDate; | |
288 | return $this; | |
289 | } | |
290 | ||
291 | /** | |
292 | * @return array | |
293 | * Each item is an array('table' => string, 'column' => string) | |
294 | */ | |
295 | public function getRelations() { | |
296 | return $this->relations; | |
297 | } | |
298 | ||
299 | /** | |
300 | * @param array $relations | |
301 | * @return TimestampTriggers | |
302 | */ | |
303 | public function setRelations($relations) { | |
304 | $this->relations = $relations; | |
305 | return $this; | |
306 | } | |
307 | ||
308 | /** | |
309 | * Get a list of all tracked relations. | |
310 | * | |
311 | * This is basically the curated list (`$this->relations`) plus any custom data. | |
312 | * | |
313 | * @return array | |
314 | * Each item is an array('table' => string, 'column' => string) | |
315 | */ | |
316 | public function getAllRelations() { | |
317 | $relations = $this->getRelations(); | |
318 | ||
319 | if ($this->getCustomDataEntity()) { | |
320 | $customGroupDAO = \CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity($this->getCustomDataEntity()); | |
321 | $customGroupDAO->is_multiple = 0; | |
322 | $customGroupDAO->find(); | |
323 | while ($customGroupDAO->fetch()) { | |
324 | $relations[] = array( | |
325 | 'table' => $customGroupDAO->table_name, | |
326 | 'column' => 'entity_id', | |
327 | ); | |
328 | } | |
329 | } | |
330 | ||
331 | return $relations; | |
332 | } | |
333 | ||
334 | } |