Commit | Line | Data |
---|---|---|
e10c0223 C |
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 | * Upgrade logic for FiveThirtyFour */ | |
14 | class CRM_Upgrade_Incremental_php_FiveThirtyFour extends CRM_Upgrade_Incremental_Base { | |
15 | ||
16 | /** | |
17 | * Compute any messages which should be displayed beforeupgrade. | |
18 | * | |
19 | * Note: This function is called iteratively for each upcoming | |
20 | * revision to the database. | |
21 | * | |
22 | * @param string $preUpgradeMessage | |
23 | * @param string $rev | |
24 | * a version number, e.g. '4.4.alpha1', '4.4.beta3', '4.4.0'. | |
25 | * @param null $currentVer | |
26 | */ | |
27 | public function setPreUpgradeMessage(&$preUpgradeMessage, $rev, $currentVer = NULL) { | |
3d839125 | 28 | if ($rev === '5.34.alpha1') { |
29 | $xoauth2Value = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_MailSettings', 'protocol', 'IMAP_XOAUTH2'); | |
30 | if (!empty($xoauth2Value)) { | |
31 | if ($this->isXOAUTH2InUse($xoauth2Value)) { | |
45d97834 | 32 | $preUpgradeMessage .= '<p>' . $this->getXOAuth2Warning() . '</p>'; |
3d839125 | 33 | } |
34 | } | |
35 | } | |
8daf06d6 TO |
36 | |
37 | if ($rev === '5.34.alpha1') { | |
38 | if (extension_loaded('mcrypt') && !empty(self::findSmtpPasswords())) { | |
39 | // NOTE: We don't re-encrypt automatically because the old "civicrm.settings.php" lacks a good key, and we don't keep the old encryption because the format is ambiguous. | |
40 | // The admin may forget to re-enable. That's OK -- this only affects 1 field, this is a secondary defense, and (in the future) we can remind the admin via status-checks. | |
aa7a6575 TO |
41 | $preUpgradeMessage .= '<p>' . ts('This system has an encrypted SMTP password. Encryption in v5.34+ requires CIVICRM_CRED_KEYS. You may <a href="%1" target="_blank">setup CIVICRM_CRED_KEYS</a> before or after upgrading. If you choose to wait, then the SMTP password will be stored as plain-text until you setup CIVICRM_CRED_KEYS.', [ |
42 | 1 => 'https://docs.civicrm.org/sysadmin/en/latest/upgrade/version-specific/#smtp-password', | |
43 | ]) . '</p>'; | |
8daf06d6 TO |
44 | } |
45 | foreach ($GLOBALS['civicrm_setting'] ?? [] as $entity => $overrides) { | |
46 | if (extension_loaded('mcrypt') && !empty($overrides['mailing_backend']['smtpPassword']) && $overrides['mailing_backend']['outBound_option'] == 0) { | |
47 | // This is a fairly unlikely situation. I'm sure it's *useful* to set smtpPassword via $civicrm_setting (eg for dev or multitenant). | |
48 | // But historically it had to follow the rules of CRM_Utils_Crypt: | |
49 | // - For non-mcrypt servers, that was easy/plaintext. That'll work just as well going forward. We don't show any warnings about that. | |
50 | // - For mcrypt servers, the value had to be encrypted. It's not easy to pick the right value for that. Maybe someone with multitenant would have had | |
51 | // enough incentive to figure this out... but they'd probably get stymied by the fact that each tenant has a different SITE_KEY. | |
52 | // All of which is to say: if someone has gotten into a valid+working scenario of overriding smtpPassword on an mcrypt-enabled system, then they're | |
53 | // savvy enough to figure out the migration details. We just need to point them at the problem. | |
54 | $settingPath = sprintf('$civicrm_setting[%s][%s][%s]', var_export($entity, 1), var_export('mailing_backend', 1), var_export('smtpPassword', 1)); | |
55 | $prose = ts('This system has a PHP override for the SMTP password (%1). The override was most likely encrypted with an old mechanism. After upgrading, you must verify and/or revise this setting.', [ | |
56 | 1 => $settingPath, | |
57 | ]); | |
58 | $preUpgradeMessage = '<p>' . $prose . '</p>'; | |
59 | } | |
60 | } | |
61 | } | |
e10c0223 C |
62 | } |
63 | ||
64 | /** | |
65 | * Compute any messages which should be displayed after upgrade. | |
66 | * | |
67 | * @param string $postUpgradeMessage | |
68 | * alterable. | |
69 | * @param string $rev | |
70 | * an intermediate version; note that setPostUpgradeMessage is called repeatedly with different $revs. | |
71 | */ | |
72 | public function setPostUpgradeMessage(&$postUpgradeMessage, $rev) { | |
3d839125 | 73 | if ($rev === '5.34.alpha1') { |
74 | $xoauth2Value = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_MailSettings', 'protocol', 'IMAP_XOAUTH2'); | |
75 | if (!empty($xoauth2Value)) { | |
76 | if ($this->isXOAUTH2InUse($xoauth2Value)) { | |
45d97834 | 77 | $postUpgradeMessage .= '<div class="crm-error"><ul><li>' . $this->getXOAuth2Warning() . '</li></ul></div>'; |
3d839125 | 78 | } |
79 | } | |
80 | } | |
e10c0223 C |
81 | } |
82 | ||
83 | /* | |
84 | * Important! All upgrade functions MUST add a 'runSql' task. | |
85 | * Uncomment and use the following template for a new upgrade version | |
86 | * (change the x in the function name): | |
87 | */ | |
88 | ||
6a62a64e | 89 | /** |
90 | * Upgrade function. | |
91 | * | |
92 | * @param string $rev | |
93 | */ | |
94 | public function upgrade_5_34_alpha1(string $rev): void { | |
95 | $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); | |
8daf06d6 | 96 | |
aa7a6575 | 97 | if (!empty(self::findSmtpPasswords())) { |
8daf06d6 TO |
98 | $this->addTask('Migrate SMTP password', 'migrateSmtpPasswords'); |
99 | } | |
100 | ||
6a62a64e | 101 | $this->addTask('core-issue#365 - Add created_date to civicrm_action_schedule', 'addColumn', |
102 | 'civicrm_action_schedule', 'created_date', "timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'When was the schedule reminder created.'"); | |
103 | ||
104 | $this->addTask('core-issue#365 - Add modified_date to civicrm_action_schedule', 'addColumn', | |
105 | 'civicrm_action_schedule', 'modified_date', "timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When was the schedule reminder created.'"); | |
106 | ||
107 | $this->addTask('core-issue#365 - Add effective_start_date to civicrm_action_schedule', 'addColumn', | |
108 | 'civicrm_action_schedule', 'effective_start_date', "timestamp NULL COMMENT 'Earliest date to consider start events from.'"); | |
109 | ||
110 | $this->addTask('core-issue#365 - Add effective_end_date to civicrm_action_schedule', 'addColumn', | |
111 | 'civicrm_action_schedule', 'effective_end_date', "timestamp NULL COMMENT 'Latest date to consider end events from.'"); | |
c6d30d7f | 112 | |
113 | $this->addTask('Set defaults and required on financial type boolean fields', 'updateFinancialTypeTable'); | |
114 | $this->addTask('Set defaults and required on pledge fields', 'updatePledgeTable'); | |
3d839125 | 115 | |
116 | $this->addTask('Remove never used IMAP_XOAUTH2 option value', 'removeUnusedXOAUTH2'); | |
6a62a64e | 117 | } |
e10c0223 | 118 | |
8daf06d6 TO |
119 | /** |
120 | * @return array | |
121 | * A list of "civicrm_setting" records which have | |
122 | * SMTP passwords, or NULL. | |
123 | */ | |
124 | protected static function findSmtpPasswords() { | |
125 | $query = CRM_Utils_SQL_Select::from('civicrm_setting') | |
126 | ->where('name = "mailing_backend"'); | |
127 | ||
128 | $matches = []; | |
129 | foreach ($query->execute()->fetchAll() as $setting) { | |
130 | $value = unserialize($setting['value']); | |
131 | if (!empty($value['smtpPassword'])) { | |
132 | $matches[] = $setting; | |
133 | } | |
134 | } | |
135 | ||
136 | return $matches; | |
137 | } | |
138 | ||
139 | /** | |
140 | * Find any SMTP passwords. Remove the CRM_Utils_Crypt encryption. | |
141 | * | |
142 | * Note: This task is only enqueued if mcrypt is active. | |
143 | * | |
144 | * @param \CRM_Queue_TaskContext $ctx | |
145 | * | |
146 | * @return bool | |
147 | */ | |
148 | public static function migrateSmtpPasswords(CRM_Queue_TaskContext $ctx) { | |
149 | $settings = self::findSmtpPasswords(); | |
aa7a6575 TO |
150 | $cryptoToken = new \Civi\Crypto\CryptoToken(\Civi\Crypto\CryptoRegistry::createDefaultRegistry()); |
151 | ||
8daf06d6 TO |
152 | foreach ($settings as $setting) { |
153 | $value = unserialize($setting['value']); | |
aa7a6575 TO |
154 | $plain = CRM_Utils_Crypt::decrypt($value['smtpPassword']); |
155 | $value['smtpPassword'] = $cryptoToken->encrypt($plain, 'CRED'); | |
8daf06d6 TO |
156 | CRM_Core_DAO::executeQuery('UPDATE civicrm_setting SET value = %2 WHERE id = %1', [ |
157 | 1 => [$setting['id'], 'Positive'], | |
158 | 2 => [serialize($value), 'String'], | |
159 | ]); | |
160 | } | |
161 | return TRUE; | |
162 | } | |
163 | ||
c6d30d7f | 164 | /** |
165 | * Update financial type table to reflect recent schema changes. | |
166 | * | |
167 | * @param \CRM_Queue_TaskContext $ctx | |
168 | * | |
169 | * @return bool | |
170 | */ | |
171 | public static function updateFinancialTypeTable(CRM_Queue_TaskContext $ctx): bool { | |
172 | // Make sure there are no existing NULL values in the fields we are about to make required. | |
173 | CRM_Core_DAO::executeQuery(' | |
174 | UPDATE civicrm_financial_type | |
175 | SET is_active = COALESCE(is_active, 0), | |
176 | is_reserved = COALESCE(is_reserved, 0), | |
177 | is_deductible = COALESCE(is_deductible, 0) | |
178 | WHERE is_reserved IS NULL OR is_active IS NULL OR is_deductible IS NULL | |
179 | '); | |
180 | CRM_Core_DAO::executeQuery(" | |
181 | ALTER TABLE civicrm_financial_type | |
182 | MODIFY COLUMN `is_deductible` tinyint(4) DEFAULT 0 NOT NULL COMMENT 'Is this financial type tax-deductible? If true, contributions of this type may be fully OR partially deductible - non-deductible amount is stored in the Contribution record.', | |
183 | MODIFY COLUMN `is_reserved` tinyint(4) DEFAULT 0 NOT NULL COMMENT 'Is this a predefined system object?', | |
184 | MODIFY COLUMN `is_active` tinyint(4) DEFAULT 1 NOT NULL COMMENT 'Is this property active?' | |
185 | "); | |
186 | ||
187 | return TRUE; | |
188 | } | |
189 | ||
190 | /** | |
191 | * Update pledge table to reflect recent schema changes making fields required. | |
192 | * | |
193 | * @param \CRM_Queue_TaskContext $ctx | |
194 | * | |
195 | * @return bool | |
196 | */ | |
197 | public static function updatePledgeTable(CRM_Queue_TaskContext $ctx): bool { | |
198 | // Make sure there are no existing NULL values in the fields we are about to make required. | |
199 | CRM_Core_DAO::executeQuery(' | |
200 | UPDATE civicrm_pledge | |
201 | SET is_test = COALESCE(is_test, 0), | |
202 | frequency_unit = COALESCE(frequency_unit, "month"), | |
203 | # Cannot imagine this would be null but if it were... | |
204 | installments = COALESCE(installments, 0), | |
205 | # this does not seem plausible either. | |
206 | status_id = COALESCE(status_id, 1) | |
207 | WHERE is_test IS NULL OR frequency_unit IS NULL OR installments IS NULL OR status_id IS NULL | |
208 | '); | |
209 | CRM_Core_DAO::executeQuery(" | |
210 | ALTER TABLE civicrm_pledge | |
211 | MODIFY COLUMN `frequency_unit` varchar(8) DEFAULT 'month' NOT NULL COMMENT 'Time units for recurrence of pledge payments.', | |
212 | MODIFY COLUMN `installments` int(10) unsigned DEFAULT 1 NOT NULL COMMENT 'Total number of payments to be made.', | |
213 | MODIFY COLUMN `status_id` int(10) unsigned NOT NULL COMMENT 'Implicit foreign key to civicrm_option_values in the pledge_status option group.', | |
214 | MODIFY COLUMN `is_test` tinyint(4) DEFAULT 0 NOT NULL | |
215 | "); | |
216 | return TRUE; | |
217 | } | |
e10c0223 | 218 | |
3d839125 | 219 | /** |
220 | * This option value was never used, but check anyway if someone happens | |
221 | * to be using it and then ask them to report what they're doing with it. | |
222 | * There's no way to send a message to the user during the task, so we have | |
223 | * to check it here and also as a pre/post upgrade message. | |
224 | * Similar to removeGooglePlusOption from 5.23 except there we know some | |
225 | * people would have used it. | |
226 | */ | |
227 | public static function removeUnusedXOAUTH2(CRM_Queue_TaskContext $ctx) { | |
228 | $xoauth2Value = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_MailSettings', 'protocol', 'IMAP_XOAUTH2'); | |
229 | if (!empty($xoauth2Value)) { | |
230 | if (!self::isXOAUTH2InUse($xoauth2Value)) { | |
231 | CRM_Core_DAO::executeQuery("DELETE ov FROM civicrm_option_value ov | |
232 | INNER JOIN civicrm_option_group og | |
233 | ON (og.name = 'mail_protocol' AND ov.option_group_id = og.id) | |
234 | WHERE ov.value = %1", | |
235 | [1 => [$xoauth2Value, 'Positive']]); | |
236 | } | |
237 | } | |
238 | return TRUE; | |
239 | } | |
240 | ||
241 | /** | |
242 | * Determine if option value is enabled or used in mail settings. | |
243 | * @return bool | |
244 | */ | |
245 | private static function isXOAUTH2InUse($xoauth2Value) { | |
246 | $enabled = (bool) CRM_Core_DAO::SingleValueQuery("SELECT ov.is_active FROM civicrm_option_value ov | |
247 | INNER JOIN civicrm_option_group og | |
248 | ON (og.name = 'mail_protocol' AND ov.option_group_id = og.id) | |
249 | WHERE ov.value = %1", | |
250 | [1 => [$xoauth2Value, 'Positive']]); | |
251 | $usedInMailSettings = (bool) CRM_Core_DAO::SingleValueQuery("SELECT id FROM civicrm_mail_settings WHERE protocol = %1", [1 => [$xoauth2Value, 'Positive']]); | |
252 | return $enabled || $usedInMailSettings; | |
253 | } | |
254 | ||
45d97834 TO |
255 | /** |
256 | * @return string | |
257 | */ | |
258 | private function getXOAuth2Warning():string { | |
259 | // Leaving out ts() since it's unlikely this message will ever | |
260 | // be displayed to anyone. | |
261 | return strtr( | |
262 | 'This system has enabled "IMAP_XOAUTH2" which was experimentally declared in CiviCRM v5.24. CiviCRM v5.33+ includes a supported replacement ("oauth-client"), and the experimental "IMAP_XOAUTH2" should be removed. Please visit %1 to discuss.', | |
263 | [ | |
264 | '%1' => '<a target="_blank" href="https://lab.civicrm.org/dev/core/-/issues/2264">dev/core#2264</a>', | |
265 | ] | |
266 | ); | |
267 | } | |
268 | ||
e10c0223 | 269 | } |