CRM-19612 Dedupe: dodge problems introduced by query union
[civicrm-core.git] / Civi / Core / SqlTriggers.php
CommitLineData
4ed867e0
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
3b8eef99 6 | Copyright CiviCRM LLC (c) 2004-2017 |
4ed867e0
TO
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
28namespace Civi\Core;
29
30/**
31 * Class SqlTriggers
32 * @package Civi\Core
33 *
34 * This class manages creation and destruction of SQL triggers.
35 */
36class SqlTriggers {
37
9307ddf7
TO
38 /**
39 * The name of the output file.
40 *
41 * @var string|NULL
42 */
43 private $file = NULL;
44
4ed867e0
TO
45 /**
46 * Build a list of triggers via hook and add them to (err, reconcile them
47 * with) the database.
48 *
49 * @param string $tableName
50 * the specific table requiring a rebuild; or NULL to rebuild all tables.
51 * @param bool $force
52 *
53 * @see CRM-9716
54 */
55 public function rebuild($tableName = NULL, $force = FALSE) {
56 $info = array();
57
58 $logging = new \CRM_Logging_Schema();
59 $logging->triggerInfo($info, $tableName, $force);
60
61 \CRM_Core_I18n_Schema::triggerInfo($info, $tableName);
62 \CRM_Contact_BAO_Contact::triggerInfo($info, $tableName);
63
64 \CRM_Utils_Hook::triggerInfo($info, $tableName);
65
66 // drop all existing triggers on all tables
67 $logging->dropTriggers($tableName);
68
69 // now create the set of new triggers
70 $this->createTriggers($info, $tableName);
71 }
72
73 /**
74 * @param array $info
75 * per hook_civicrm_triggerInfo.
76 * @param string $onlyTableName
77 * the specific table requiring a rebuild; or NULL to rebuild all tables.
78 */
79 public function createTriggers(&$info, $onlyTableName = NULL) {
80 // Validate info array, should probably raise errors?
81 if (is_array($info) == FALSE) {
82 return;
83 }
84
85 $triggers = array();
86
87 // now enumerate the tables and the events and collect the same set in a different format
88 foreach ($info as $value) {
89
90 // clean the incoming data, skip malformed entries
91 // TODO: malformed entries should raise errors or get logged.
92 if (isset($value['table']) == FALSE ||
93 isset($value['event']) == FALSE ||
94 isset($value['when']) == FALSE ||
95 isset($value['sql']) == FALSE
96 ) {
97 continue;
98 }
99
100 if (is_string($value['table']) == TRUE) {
101 $tables = array($value['table']);
102 }
103 else {
104 $tables = $value['table'];
105 }
106
107 if (is_string($value['event']) == TRUE) {
108 $events = array(strtolower($value['event']));
109 }
110 else {
111 $events = array_map('strtolower', $value['event']);
112 }
113
114 $whenName = strtolower($value['when']);
115
116 foreach ($tables as $tableName) {
117 if (!isset($triggers[$tableName])) {
118 $triggers[$tableName] = array();
119 }
120
121 foreach ($events as $eventName) {
122 $template_params = array('{tableName}', '{eventName}');
123 $template_values = array($tableName, $eventName);
124
125 $sql = str_replace($template_params,
126 $template_values,
127 $value['sql']
128 );
129 $variables = str_replace($template_params,
130 $template_values,
131 \CRM_Utils_Array::value('variables', $value)
132 );
133
134 if (!isset($triggers[$tableName][$eventName])) {
135 $triggers[$tableName][$eventName] = array();
136 }
137
138 if (!isset($triggers[$tableName][$eventName][$whenName])) {
139 // We're leaving out cursors, conditions, and handlers for now
140 // they are kind of dangerous in this context anyway
141 // better off putting them in stored procedures
142 $triggers[$tableName][$eventName][$whenName] = array(
143 'variables' => array(),
144 'sql' => array(),
145 );
146 }
147
148 if ($variables) {
149 $triggers[$tableName][$eventName][$whenName]['variables'][] = $variables;
150 }
151
152 $triggers[$tableName][$eventName][$whenName]['sql'][] = $sql;
153 }
154 }
155 }
156
157 // now spit out the sql
158 foreach ($triggers as $tableName => $tables) {
159 if ($onlyTableName != NULL && $onlyTableName != $tableName) {
160 continue;
161 }
162 foreach ($tables as $eventName => $events) {
163 foreach ($events as $whenName => $parts) {
164 $varString = implode("\n", $parts['variables']);
165 $sqlString = implode("\n", $parts['sql']);
166 $validName = \CRM_Core_DAO::shortenSQLName($tableName, 48, TRUE);
167 $triggerName = "{$validName}_{$whenName}_{$eventName}";
168 $triggerSQL = "CREATE TRIGGER $triggerName $whenName $eventName ON $tableName FOR EACH ROW BEGIN $varString $sqlString END";
169
7a29e455
TO
170 $this->enqueueQuery("DROP TRIGGER IF EXISTS $triggerName");
171 $this->enqueueQuery($triggerSQL);
4ed867e0
TO
172 }
173 }
174 }
175 }
176
177 /**
178 * Wrapper function to drop triggers.
179 *
180 * @param string $tableName
181 * the specific table requiring a rebuild; or NULL to rebuild all tables.
182 */
183 public function dropTriggers($tableName = NULL) {
184 $info = array();
185
186 $logging = new \CRM_Logging_Schema();
187 $logging->triggerInfo($info, $tableName);
188
189 // drop all existing triggers on all tables
190 $logging->dropTriggers($tableName);
191 }
192
9307ddf7
TO
193 /**
194 * Enqueue a query which alters triggers.
195 *
196 * As this requires a high permission level we funnel the queries through here to
197 * facilitate them being taken 'offline'.
198 *
199 * @param string $triggerSQL
200 * The sql to run to create or drop the triggers.
201 * @param array $params
202 * Optional parameters to interpolate into the string.
203 */
204 public function enqueueQuery($triggerSQL, $params = array()) {
205 if (\Civi::settings()->get('logging_no_trigger_permission')) {
206
207 if (!file_exists($this->getFile())) {
208 // Ugh. Need to let user know somehow. This is the first change.
209 \CRM_Core_Session::setStatus(ts('The mysql commands you need to run are stored in %1', array(
210 1 => $this->getFile(),
211 )),
212 '',
213 'alert',
214 array('expires' => 0)
215 );
216 }
217
218 $buf = "\n";
219 $buf .= "DELIMITER //\n";
220 $buf .= \CRM_Core_DAO::composeQuery($triggerSQL, $params) . " //\n";
221 $buf .= "DELIMITER ;\n";
222 file_put_contents($this->getFile(), $buf, FILE_APPEND);
223 }
224 else {
225 \CRM_Core_DAO::executeQuery($triggerSQL, $params, TRUE, NULL, FALSE, FALSE);
226 }
227 }
228
229 /**
230 * @return NULL|string
231 */
232 public function getFile() {
233 if ($this->file === NULL) {
234 $prefix = 'trigger' . \CRM_Utils_Request::id();
235 $config = \CRM_Core_Config::singleton();
236 $this->file = "{$config->configAndLogDir}CiviCRM." . $prefix . md5($config->dsn) . '.sql';
237 }
238 return $this->file;
239 }
240
4ed867e0 241}