Commit | Line | Data |
---|---|---|
4ed867e0 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
fee14197 | 4 | | CiviCRM version 5 | |
4ed867e0 | 5 | +--------------------------------------------------------------------+ |
6b83d5bd | 6 | | Copyright CiviCRM LLC (c) 2004-2019 | |
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 | ||
28 | namespace Civi\Core; | |
29 | ||
30 | /** | |
31 | * Class SqlTriggers | |
32 | * @package Civi\Core | |
33 | * | |
34 | * This class manages creation and destruction of SQL triggers. | |
35 | */ | |
36 | class 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) { | |
c64f69d9 | 56 | $info = []; |
4ed867e0 TO |
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 | ||
c64f69d9 | 85 | $triggers = []; |
4ed867e0 TO |
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) { | |
c64f69d9 | 101 | $tables = [$value['table']]; |
4ed867e0 TO |
102 | } |
103 | else { | |
104 | $tables = $value['table']; | |
105 | } | |
106 | ||
107 | if (is_string($value['event']) == TRUE) { | |
c64f69d9 | 108 | $events = [strtolower($value['event'])]; |
4ed867e0 TO |
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])) { | |
c64f69d9 | 118 | $triggers[$tableName] = []; |
4ed867e0 TO |
119 | } |
120 | ||
121 | foreach ($events as $eventName) { | |
c64f69d9 CW |
122 | $template_params = ['{tableName}', '{eventName}']; |
123 | $template_values = [$tableName, $eventName]; | |
4ed867e0 TO |
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])) { | |
c64f69d9 | 135 | $triggers[$tableName][$eventName] = []; |
4ed867e0 TO |
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 | |
c64f69d9 CW |
142 | $triggers[$tableName][$eventName][$whenName] = [ |
143 | 'variables' => [], | |
144 | 'sql' => [], | |
145 | ]; | |
4ed867e0 TO |
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) { | |
c64f69d9 | 184 | $info = []; |
4ed867e0 TO |
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 | */ | |
c64f69d9 | 204 | public function enqueueQuery($triggerSQL, $params = []) { |
9307ddf7 TO |
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. | |
c64f69d9 | 209 | \CRM_Core_Session::setStatus(ts('The mysql commands you need to run are stored in %1', [ |
34f3bbd9 SL |
210 | 1 => $this->getFile(), |
211 | ]), | |
9307ddf7 TO |
212 | '', |
213 | 'alert', | |
c64f69d9 | 214 | ['expires' => 0] |
9307ddf7 TO |
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 | } |