Use ?? operator instead of CRM_Utils_Array::value() in array assignments
[civicrm-core.git] / api / v3 / System.php
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 * This api exposes CiviCRM system functionality.
14 *
15 * Includes caching, logging, and checking system functionality.
16 *
17 * @package CiviCRM_APIv3
18 */
19
20 /**
21 * Flush all system caches.
22 *
23 * @param array $params
24 * Input parameters.
25 * - triggers: bool, whether to drop/create SQL triggers; default: FALSE
26 * - session: bool, whether to reset the CiviCRM session data; default: FALSE
27 *
28 * @return array
29 */
30 function civicrm_api3_system_flush($params) {
31 CRM_Core_Invoke::rebuildMenuAndCaches(
32 CRM_Utils_Array::value('triggers', $params, FALSE),
33 CRM_Utils_Array::value('session', $params, FALSE)
34 );
35 return civicrm_api3_create_success();
36 }
37
38 /**
39 * Adjust Metadata for Flush action.
40 *
41 * The metadata is used for setting defaults, documentation & validation.
42 *
43 * @param array $params
44 * Array of parameters determined by getfields.
45 */
46 function _civicrm_api3_system_flush_spec(&$params) {
47 $params['triggers'] = [
48 'title' => 'Triggers',
49 'description' => 'rebuild triggers (boolean)',
50 'type' => CRM_Utils_Type::T_BOOLEAN,
51 ];
52 $params['session'] = [
53 'title' => 'Sessions',
54 'description' => 'refresh sessions (boolean)',
55 'type' => CRM_Utils_Type::T_BOOLEAN,
56 ];
57 }
58
59 /**
60 * System.Check API specification (optional).
61 *
62 * This is used for documentation and validation.
63 *
64 * @param array $spec
65 * Description of fields supported by this API call.
66 *
67 * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
68 */
69 function _civicrm_api3_system_check_spec(&$spec) {
70 $spec['id'] = [
71 'title' => 'ID',
72 'description' => 'Not a real identifier - do not use',
73 'type' => CRM_Utils_Type::T_INT,
74 ];
75 $spec['name'] = [
76 'title' => 'Name',
77 'description' => 'Unique identifier',
78 'type' => CRM_Utils_Type::T_STRING,
79 ];
80 $spec['title'] = [
81 'title' => 'Title',
82 'description' => 'Short title text',
83 'type' => CRM_Utils_Type::T_STRING,
84 ];
85 $spec['message'] = [
86 'title' => 'Message',
87 'description' => 'Long description html',
88 'type' => CRM_Utils_Type::T_STRING,
89 ];
90 $spec['help'] = [
91 'title' => 'Help',
92 'description' => 'Optional extra help (html string)',
93 'type' => CRM_Utils_Type::T_STRING,
94 ];
95 $spec['severity'] = [
96 'title' => 'Severity',
97 'description' => 'Psr\Log\LogLevel string',
98 'type' => CRM_Utils_Type::T_STRING,
99 'options' => array_combine(CRM_Utils_Check::getSeverityList(), CRM_Utils_Check::getSeverityList()),
100 ];
101 $spec['severity_id'] = [
102 'title' => 'Severity ID',
103 'description' => 'Integer representation of Psr\Log\LogLevel',
104 'type' => CRM_Utils_Type::T_INT,
105 'options' => CRM_Utils_Check::getSeverityList(),
106 ];
107 $spec['is_visible'] = [
108 'title' => 'is visible',
109 'description' => '0 if message has been hidden by the user',
110 'type' => CRM_Utils_Type::T_BOOLEAN,
111 ];
112 $spec['hidden_until'] = [
113 'title' => 'Hidden_until',
114 'description' => 'When will hidden message be visible again?',
115 'type' => CRM_Utils_Type::T_DATE,
116 ];
117 }
118
119 /**
120 * System Check API.
121 *
122 * @param array $params
123 *
124 * @return array
125 * API result descriptor; return items are alert codes/messages
126 * @see civicrm_api3_create_success
127 * @see civicrm_api3_create_error
128 * @throws API_Exception
129 */
130 function civicrm_api3_system_check($params) {
131 // array(array('name'=> $, 'severity'=>$, ...))
132 $id = 1;
133 $returnValues = $fields = [];
134 _civicrm_api3_system_check_spec($fields);
135
136 // array(CRM_Utils_Check_Message)
137 $messages = CRM_Utils_Check::checkAll();
138
139 foreach ($messages as $msg) {
140 $returnValues[] = $msg->toArray() + ['id' => $id++];
141 }
142
143 return _civicrm_api3_basic_array_get('systemCheck', $params, $returnValues, "id", array_keys($fields));
144 }
145
146 /**
147 * Log entry to system log table.
148 *
149 * @param array $params
150 *
151 * @return array
152 */
153 function civicrm_api3_system_log($params) {
154 $log = new CRM_Utils_SystemLogger();
155 // This part means fields with separate db storage are accepted as params which kind of seems more intuitive to me
156 // because I felt like not doing this required a bunch of explanation in the spec function - but perhaps other won't see it as helpful?
157 if (!isset($params['context'])) {
158 $params['context'] = [];
159 }
160 $specialFields = ['contact_id', 'hostname'];
161 foreach ($specialFields as $specialField) {
162 if (isset($params[$specialField]) && !isset($params['context'])) {
163 $params['context'][$specialField] = $params[$specialField];
164 }
165 }
166 $returnValues = $log->log($params['level'], $params['message'], $params['context']);
167 return civicrm_api3_create_success($returnValues, $params, 'System', 'Log');
168 }
169
170 /**
171 * Metadata for log function.
172 *
173 * @param array $params
174 */
175 function _civicrm_api3_system_log_spec(&$params) {
176 $params['level'] = [
177 'title' => 'Log Level',
178 'description' => 'Log level as described in PSR3 (info, debug, warning etc)',
179 'type' => CRM_Utils_Type::T_STRING,
180 'api.required' => TRUE,
181 ];
182 $params['message'] = [
183 'title' => 'Log Message',
184 'description' => 'Standardised message string, you can also ',
185 'type' => CRM_Utils_Type::T_STRING,
186 'api.required' => TRUE,
187 ];
188 $params['context'] = [
189 'title' => 'Log Context',
190 'description' => 'An array of additional data to store.',
191 'type' => CRM_Utils_Type::T_LONGTEXT,
192 'api.default' => [],
193 ];
194 $params['contact_id'] = [
195 'title' => 'Log Contact ID',
196 'description' => 'Optional ID of relevant contact',
197 'type' => CRM_Utils_Type::T_INT,
198 ];
199 $params['hostname'] = [
200 'title' => 'Log Hostname',
201 'description' => 'Optional name of host',
202 'type' => CRM_Utils_Type::T_STRING,
203 ];
204 }
205
206 /**
207 * System.Get API.
208 *
209 * @param array $params
210 *
211 * @return array
212 */
213 function civicrm_api3_system_get($params) {
214 $config = CRM_Core_Config::singleton();
215 $returnValues = [
216 [
217 // deprecated in favor of civi.version
218 'version' => CRM_Utils_System::version(),
219 // deprecated in favor of cms.type
220 'uf' => CIVICRM_UF,
221 'php' => [
222 'version' => phpversion(),
223 'time' => time(),
224 'tz' => date_default_timezone_get(),
225 'sapi' => php_sapi_name(),
226 'extensions' => get_loaded_extensions(),
227 'ini' => _civicrm_api3_system_get_redacted_ini(),
228 ],
229 'mysql' => [
230 'version' => CRM_Core_DAO::singleValueQuery('SELECT @@version'),
231 'time' => CRM_Core_DAO::singleValueQuery('SELECT unix_timestamp()'),
232 'vars' => _civicrm_api3_system_get_redacted_mysql(),
233 ],
234 'cms' => [
235 'version' => $config->userSystem->getVersion(),
236 'type' => CIVICRM_UF,
237 'modules' => CRM_Core_Module::collectStatuses($config->userSystem->getModules()),
238 ],
239 'civi' => [
240 'version' => CRM_Utils_System::version(),
241 'dev' => (bool) CRM_Utils_System::isDevelopment(),
242 'components' => array_keys(CRM_Core_Component::getEnabledComponents()),
243 'extensions' => preg_grep('/^uninstalled$/', CRM_Extension_System::singleton()->getManager()->getStatuses(), PREG_GREP_INVERT),
244 'multidomain' => CRM_Core_DAO::singleValueQuery('SELECT count(*) FROM civicrm_domain') > 1,
245 'settings' => _civicrm_api3_system_get_redacted_settings(),
246 'exampleUrl' => CRM_Utils_System::url('civicrm/example', NULL, TRUE, NULL, FALSE),
247 ],
248 'http' => [
249 'software' => $_SERVER['SERVER_SOFTWARE'] ?? NULL,
250 'forwarded' => !empty($_SERVER['HTTP_X_FORWARDED_FOR']) || !empty($_SERVER['X_FORWARDED_PROTO']),
251 'port' => (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443) ? 'Standard' : 'Nonstandard',
252 ],
253 'os' => [
254 'type' => php_uname('s'),
255 'release' => php_uname('r'),
256 'version' => php_uname('v'),
257 'machine' => php_uname('m'),
258 ],
259 ],
260 ];
261
262 return civicrm_api3_create_success($returnValues, $params, 'System', 'get');
263 }
264
265 /**
266 * Generate a sanitized/anonymized/redacted dump of the PHP configuration.
267 *
268 * Some INI fields contain site-identifying information (SII) -- e.g. URLs,
269 * hostnames, file paths, IP addresses, passwords, or free-form comments
270 * could be used to identify a site or gain access to its resources.
271 *
272 * A number of INI fields have been examined to determine whether they
273 * contain SII. Approved fields are put in a whitelist; all other fields
274 * are redacted.
275 *
276 * Redaction hides the substance of a field but does not completely omit
277 * all information. Consider the field 'mail.log' - setting this field
278 * has a functional effect (it enables or disables the logging behavior)
279 * and also points to particular file. Empty values (FALSE/NULL/0/"")
280 * will pass through redaction, but all other values will be replaced
281 * by a string (eg "REDACTED"). This roughly indicates whether the
282 * option is enabled/disabled without giving away its content.
283 *
284 * @return array
285 */
286 function _civicrm_api3_system_get_redacted_ini() {
287 static $whitelist = NULL;
288 if ($whitelist === NULL) {
289 $whitelist = _civicrm_api3_system_get_whitelist(__DIR__ . '/System/ini-whitelist.txt');
290 }
291
292 $inis = ini_get_all(NULL, FALSE);
293 $result = [];
294 foreach ($inis as $k => $v) {
295 if (empty($v) || in_array($k, $whitelist)) {
296 $result[$k] = $v;
297 }
298 else {
299 $result[$k] = 'REDACTED';
300 }
301 }
302
303 return $result;
304 }
305
306 /**
307 * Generate ae sanitized/anonymized/redacted dump of MySQL configuration.
308 *
309 * @return array
310 * @see _civicrm_api3_system_get_redacted_ini
311 */
312 function _civicrm_api3_system_get_redacted_mysql() {
313 static $whitelist = NULL;
314 if ($whitelist === NULL) {
315 $whitelist = _civicrm_api3_system_get_whitelist(__DIR__ . '/System/mysql-whitelist.txt');
316 }
317
318 $inis = ini_get_all(NULL, FALSE);
319 $result = [];
320 $dao = CRM_Core_DAO::executeQuery('SHOW VARIABLES');
321 while ($dao->fetch()) {
322 if (empty($dao->Variable_name) || in_array($dao->Variable_name, $whitelist)) {
323 $result[$dao->Variable_name] = $dao->Value;
324 }
325 else {
326 $result[$dao->Variable_name] = 'REDACTED';
327 }
328 }
329
330 return $result;
331 }
332
333 /**
334 * Get redacted settings.
335 *
336 * @return array
337 * @throws CiviCRM_API3_Exception
338 */
339 function _civicrm_api3_system_get_redacted_settings() {
340 static $whitelist = NULL;
341 if ($whitelist === NULL) {
342 $whitelist = _civicrm_api3_system_get_whitelist(__DIR__ . '/System/setting-whitelist.txt');
343 }
344
345 $apiResult = civicrm_api3('Setting', 'get', []);
346 $result = [];
347 foreach ($apiResult['values'] as $settings) {
348 foreach ($settings as $key => $value) {
349 if (in_array($key, $whitelist)) {
350 $result[$key] = $value;
351 }
352 }
353 }
354
355 return $result;
356 }
357
358 /**
359 * Read a whitelist.
360 *
361 * @param string $whitelistFile
362 * Name of a file. Each line is a field name. Comments begin with "#".
363 * @return array
364 */
365 function _civicrm_api3_system_get_whitelist($whitelistFile) {
366 $whitelist = array_filter(
367 explode("\n", file_get_contents($whitelistFile)),
368 function ($k) {
369 return !empty($k) && !preg_match('/^\s*#/', $k);
370 }
371 );
372 return $whitelist;
373 }
374
375 /**
376 * Update log table structures.
377 *
378 * This updates the engine type if defined in the hook and changes the field type
379 * for log_conn_id to reflect CRM-18193.
380 */
381 function civicrm_api3_system_updatelogtables($params) {
382 $schema = new CRM_Logging_Schema();
383 $updatedTablesCount = $schema->updateLogTableSchema($params);
384 return civicrm_api3_create_success($updatedTablesCount);
385 }
386
387 /**
388 * Update log table structures.
389 *
390 * This updates the engine type if defined in the hook and changes the field type
391 * for log_conn_id to reflect CRM-18193.
392 *
393 * @param array $params
394 *
395 * @return array
396 *
397 * @throws \API_Exception
398 */
399 function civicrm_api3_system_utf8conversion($params) {
400 if (CRM_Core_BAO_SchemaHandler::migrateUtf8mb4($params['is_revert'])) {
401 return civicrm_api3_create_success(1);
402 }
403 throw new API_Exception('Conversion failed');
404 }
405
406 /**
407 * Metadata for conversion function.
408 *
409 * @param array $params
410 */
411 function _civicrm_api3_system_utf8conversion_spec(&$params) {
412 $params['is_revert'] = [
413 'title' => ts('Revert back from UTF8MB4 to UTF8?'),
414 'type' => CRM_Utils_Type::T_BOOLEAN,
415 'api.default' => FALSE,
416 ];
417 }
418
419 /**
420 * Adjust Metadata for Flush action.
421 *
422 * The metadata is used for setting defaults, documentation & validation.
423 *
424 * @param array $params
425 * Array of parameters determined by getfields.
426 */
427 function _civicrm_api3_system_updatelogtables_spec(&$params) {
428 $params['updateChangedEngineConfig'] = [
429 'title' => 'Update Engine Config if changed?',
430 'description' => 'By default, we only update if the ENGINE has changed, set this to TRUE to update if the ENGINE_CONFIG has changed.',
431 'type' => CRM_Utils_Type::T_BOOLEAN,
432 'api.default' => FALSE,
433 ];
434 $params['forceEngineMigration'] = [
435 'title' => 'Force storage engine to upgrade to InnoDB?',
436 'description' => 'Older versions of CiviCRM used the ARCHIVE engine by default. Set this to TRUE to migrate the engine to the new default.',
437 'type' => CRM_Utils_Type::T_BOOLEAN,
438 'api.default' => FALSE,
439 ];
440 }
441
442 /**
443 * Update indexes.
444 *
445 * This adds any indexes that exist in the schema but not the database.
446 *
447 * @param array $params
448 *
449 * @return array
450 */
451 function civicrm_api3_system_updateindexes(array $params):array {
452 $tables = empty($params['tables']) ? FALSE : (array) $params['tables'];
453 CRM_Core_BAO_SchemaHandler::createMissingIndices(CRM_Core_BAO_SchemaHandler::getMissingIndices(TRUE, $tables));
454 return civicrm_api3_create_success(1);
455 }
456
457 /**
458 * Declare metadata for api System.getmissingindices
459 *
460 * @param array $params
461 */
462 function _civicrm_api3_system_updateindexes_spec(array &$params) {
463 $params['tables'] = [
464 'type' => CRM_Utils_Type::T_STRING,
465 'api.default' => FALSE,
466 'title' => ts('Optional tables filter'),
467 ];
468 }
469
470 /**
471 * Get an array of indices that should be defined but are not.
472 *
473 * @param array $params
474 *
475 * @return array
476 */
477 function civicrm_api3_system_getmissingindices($params) {
478 $tables = empty($params['tables']) ? FALSE : (array) $params['tables'];
479 $indices = CRM_Core_BAO_SchemaHandler::getMissingIndices(FALSE, $tables);
480 return civicrm_api3_create_success($indices);
481 }
482
483 /**
484 * Declare metadata for api System.getmissingindices
485 *
486 * @param array $params
487 */
488 function _civicrm_api3_system_getmissingindices_spec(&$params) {
489 $params['tables'] = [
490 'type' => CRM_Utils_Type::T_STRING,
491 'api.default' => FALSE,
492 'title' => ts('Optional tables filter'),
493 ];
494 }
495
496 /**
497 * Creates missing log tables.
498 *
499 * CRM-20838 - This adds any missing log tables into the database.
500 */
501 function civicrm_api3_system_createmissinglogtables() {
502 $schema = new CRM_Logging_Schema();
503 $missingLogTables = $schema->getMissingLogTables();
504 if (!empty($missingLogTables)) {
505 foreach ($missingLogTables as $tableName) {
506 $schema->fixSchemaDifferencesFor($tableName);
507 }
508 }
509 return civicrm_api3_create_success(1);
510 }
511
512 /**
513 * Rebuild Multilingual Schema
514 *
515 */
516 function civicrm_api3_system_rebuildmultilingualschema() {
517 $domain = new CRM_Core_DAO_Domain();
518 $domain->find(TRUE);
519
520 if ($domain->locales) {
521 $locales = explode(CRM_Core_DAO::VALUE_SEPARATOR, $domain->locales);
522 CRM_Core_I18n_Schema::rebuildMultilingualSchema($locales);
523 return civicrm_api3_create_success(1);
524 }
525 else {
526 throw new API_Exception('Cannot call rebuild Multilingual schema on non Multilingual database');
527 }
528 }