Merge pull request #15764 from civicrm/5.20
[civicrm-core.git] / api / v3 / Extension.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
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 define('API_V3_EXTENSION_DELIMITER', ',');
29
30
31 /**
32 * This provides an api interface for CiviCRM extension management.
33 *
34 * @package CiviCRM_APIv3
35 */
36
37 /**
38 * Install an extension.
39 *
40 * @param array $params
41 * Input parameters.
42 * - key: string, eg "com.example.myextension"
43 * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2")
44 * - path: string, e.g. "/var/www/extensions/*"
45 *
46 * Using 'keys' should be more performant than making multiple API calls with 'key'
47 *
48 * @return array
49 */
50 function civicrm_api3_extension_install($params) {
51 $keys = _civicrm_api3_getKeys($params);
52 if (!$keys) {
53 return civicrm_api3_create_success();
54 }
55
56 try {
57 $manager = CRM_Extension_System::singleton()->getManager();
58 $manager->install($manager->findInstallRequirements($keys));
59 }
60 catch (CRM_Extension_Exception $e) {
61 return civicrm_api3_create_error($e->getMessage());
62 }
63
64 return civicrm_api3_create_success();
65 }
66
67 /**
68 * Spec function for getfields
69 * @param $fields
70 */
71 function _civicrm_api3_extension_install_spec(&$fields) {
72 $fields['keys'] = [
73 'title' => 'Extension Key(s)',
74 'api.aliases' => ['key'],
75 'type' => CRM_Utils_Type::T_STRING,
76 'description' => 'Fully qualified name of one or more extensions',
77 ];
78 $fields['path'] = [
79 'title' => 'Extension Path',
80 'type' => CRM_Utils_Type::T_STRING,
81 'description' => 'The path to the extension. May use wildcard ("*").',
82 ];
83 }
84
85 /**
86 * Upgrade an extension - runs upgrade_N hooks and system.flush.
87 *
88 * @return array
89 * API result
90 */
91 function civicrm_api3_extension_upgrade() {
92 CRM_Core_Invoke::rebuildMenuAndCaches(TRUE);
93 $queue = CRM_Extension_Upgrades::createQueue();
94 $runner = new CRM_Queue_Runner([
95 'title' => 'Extension Upgrades',
96 'queue' => $queue,
97 'errorMode' => CRM_Queue_Runner::ERROR_ABORT,
98 ]);
99
100 try {
101 $result = $runner->runAll();
102 }
103 catch (CRM_Extension_Exception $e) {
104 return civicrm_api3_create_error($e->getMessage());
105 }
106
107 if ($result === TRUE) {
108 return civicrm_api3_create_success();
109 }
110 else {
111 return $result;
112 }
113 }
114
115 /**
116 * Enable an extension.
117 *
118 * @param array $params
119 * Input parameters.
120 * - key: string, eg "com.example.myextension"
121 * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2")
122 * - path: string, e.g. "/var/www/vendor/foo/myext" or "/var/www/vendor/*"
123 *
124 * Using 'keys' should be more performant than making multiple API calls with 'key'
125 *
126 * @return array
127 */
128 function civicrm_api3_extension_enable($params) {
129 $keys = _civicrm_api3_getKeys($params);
130 if (count($keys) == 0) {
131 return civicrm_api3_create_success();
132 }
133
134 $manager = CRM_Extension_System::singleton()->getManager();
135 $manager->enable($manager->findInstallRequirements($keys));
136 return civicrm_api3_create_success();
137 }
138
139 /**
140 * Spec function for getfields
141 * @param $fields
142 */
143 function _civicrm_api3_extension_enable_spec(&$fields) {
144 _civicrm_api3_extension_install_spec($fields);
145 }
146
147 /**
148 * Disable an extension.
149 *
150 * @param array $params
151 * Input parameters.
152 * - key: string, eg "com.example.myextension"
153 * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2")
154 * - path: string, e.g. "/var/www/vendor/foo/myext" or "/var/www/vendor/*"
155 *
156 * Using 'keys' should be more performant than making multiple API calls with 'key'
157 *
158 * @return array
159 */
160 function civicrm_api3_extension_disable($params) {
161 $keys = _civicrm_api3_getKeys($params);
162 if (count($keys) == 0) {
163 return civicrm_api3_create_success();
164 }
165
166 CRM_Extension_System::singleton()->getManager()->disable($keys);
167 return civicrm_api3_create_success();
168 }
169
170 /**
171 * Spec function for getfields
172 * @param $fields
173 */
174 function _civicrm_api3_extension_disable_spec(&$fields) {
175 _civicrm_api3_extension_install_spec($fields);
176 }
177
178 /**
179 * Uninstall an extension.
180 *
181 * @param array $params
182 * Input parameters.
183 * - key: string, eg "com.example.myextension"
184 * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2")
185 * - path: string, e.g. "/var/www/vendor/foo/myext" or "/var/www/vendor/*"
186 *
187 * Using 'keys' should be more performant than making multiple API calls with 'key'
188 *
189 * @todo: removeFiles as optional param
190 *
191 * @return array
192 */
193 function civicrm_api3_extension_uninstall($params) {
194 $keys = _civicrm_api3_getKeys($params);
195 if (count($keys) == 0) {
196 return civicrm_api3_create_success();
197 }
198
199 CRM_Extension_System::singleton()->getManager()->uninstall($keys);
200 return civicrm_api3_create_success();
201 }
202
203 /**
204 * Spec function for getfields
205 * @param $fields
206 */
207 function _civicrm_api3_extension_uninstall_spec(&$fields) {
208 _civicrm_api3_extension_install_spec($fields);
209 //$fields['removeFiles'] = array(
210 // 'title' => 'Remove files',
211 // 'description' => 'Whether to remove the source tree. Default FALSE.',
212 // 'type' => CRM_Utils_Type::T_BOOLEAN,
213 //);
214 }
215
216 /**
217 * Download and install an extension.
218 *
219 * @param array $params
220 * Input parameters.
221 * - key: string, eg "com.example.myextension"
222 * - url: string eg "http://repo.com/myextension-1.0.zip"
223 *
224 * @throws API_Exception
225 * @return array
226 * API result
227 */
228 function civicrm_api3_extension_download($params) {
229 if (!array_key_exists('url', $params)) {
230 if (!CRM_Extension_System::singleton()->getBrowser()->isEnabled()) {
231 throw new API_Exception('Automatic downloading is disabled. Try adding parameter "url"');
232 }
233 if ($reqs = CRM_Extension_System::singleton()->getBrowser()->checkRequirements()) {
234 $first = array_shift($reqs);
235 throw new API_Exception($first['message']);
236 }
237 if ($info = CRM_Extension_System::singleton()->getBrowser()->getExtension($params['key'])) {
238 if ($info->downloadUrl) {
239 $params['url'] = $info->downloadUrl;
240 }
241 }
242 }
243
244 if (!array_key_exists('url', $params)) {
245 throw new API_Exception('Cannot resolve download url for extension. Try adding parameter "url"');
246 }
247
248 if (!isset($info)) {
249 $info = NULL;
250 }
251 foreach (CRM_Extension_System::singleton()->getDownloader()->checkRequirements($info) as $requirement) {
252 return civicrm_api3_create_error($requirement['message']);
253 }
254
255 if (!CRM_Extension_System::singleton()->getDownloader()->download($params['key'], $params['url'])) {
256 return civicrm_api3_create_error('Download failed - ZIP file is unavailable or malformed');
257 }
258 CRM_Extension_System::singleton()->getCache()->flush();
259 CRM_Extension_System::singleton(TRUE);
260 if (CRM_Utils_Array::value('install', $params, TRUE)) {
261 CRM_Extension_System::singleton()->getManager()->install([$params['key']]);
262 }
263
264 return civicrm_api3_create_success();
265 }
266
267 /**
268 * Spec function for getfields
269 * @param $fields
270 */
271 function _civicrm_api3_extension_download_spec(&$fields) {
272 $fields['key'] = [
273 'title' => 'Extension Key',
274 'api.required' => 1,
275 'type' => CRM_Utils_Type::T_STRING,
276 'description' => 'Fully qualified name of the extension',
277 ];
278 $fields['url'] = [
279 'title' => 'Download URL',
280 'type' => CRM_Utils_Type::T_STRING,
281 'description' => 'Optional as the system can determine the url automatically for public extensions',
282 ];
283 $fields['install'] = [
284 'title' => 'Auto-install',
285 'type' => CRM_Utils_Type::T_STRING,
286 'description' => 'Automatically install the downloaded extension',
287 'api.default' => TRUE,
288 ];
289 }
290
291 /**
292 * Download and install an extension.
293 *
294 * @param array $params
295 * Input parameters.
296 * - local: bool, whether to rescan local filesystem (default: TRUE)
297 * - remote: bool, whether to rescan remote repository (default: TRUE)
298 *
299 * @return array
300 * API result
301 */
302 function civicrm_api3_extension_refresh($params) {
303 $system = CRM_Extension_System::singleton(TRUE);
304
305 if ($params['local']) {
306 $system->getManager()->refresh();
307 // force immediate scan
308 $system->getManager()->getStatuses();
309 }
310
311 if ($params['remote']) {
312 if ($system->getBrowser()->isEnabled() && empty($system->getBrowser()->checkRequirements)) {
313 $system->getBrowser()->refresh();
314 // force immediate download
315 $system->getBrowser()->getExtensions();
316 }
317 }
318
319 return civicrm_api3_create_success();
320 }
321
322 /**
323 * Spec function for getfields
324 * @param $fields
325 */
326 function _civicrm_api3_extension_refresh_spec(&$fields) {
327 $fields['local'] = [
328 'title' => 'Rescan Local',
329 'api.default' => 1,
330 'type' => CRM_Utils_Type::T_BOOLEAN,
331 'description' => 'Whether to rescan the local filesystem (default TRUE)',
332 ];
333 $fields['remote'] = [
334 'title' => 'Rescan Remote',
335 'api.default' => 1,
336 'type' => CRM_Utils_Type::T_BOOLEAN,
337 'description' => 'Whether to rescan the remote repository (default TRUE)',
338 ];
339 }
340
341 /**
342 * Get a list of available extensions.
343 *
344 * @param array $params
345 *
346 * @return array
347 * API result
348 */
349 function civicrm_api3_extension_get($params) {
350 $full_names = _civicrm_api3_getKeys($params, 'full_name');
351 $keys = _civicrm_api3_getKeys($params, 'key');
352 $keys = array_merge($full_names, $keys);
353 $statuses = CRM_Extension_System::singleton()->getManager()->getStatuses();
354 $mapper = CRM_Extension_System::singleton()->getMapper();
355 $result = [];
356 $id = 0;
357 foreach ($statuses as $key => $status) {
358 try {
359 $obj = $mapper->keyToInfo($key);
360 }
361 catch (CRM_Extension_Exception $ex) {
362 CRM_Core_Session::setStatus(ts('Failed to read extension (%1). Please refresh the extension list.', [1 => $key]));
363 continue;
364 }
365 $info = CRM_Extension_System::createExtendedInfo($obj);
366 // backward compatibility with indexing scheme
367 $info['id'] = $id++;
368 if (!empty($keys)) {
369 if (in_array($key, $keys)) {
370 $result[] = $info;
371 }
372 }
373 else {
374 $result[] = $info;
375 }
376 }
377
378 // These fields have been filtered already, and they have special semantics.
379 unset($params['key']);
380 unset($params['keys']);
381 unset($params['full_name']);
382
383 $filterableFields = ['id', 'type', 'status', 'path'];
384 return _civicrm_api3_basic_array_get('Extension', $params, $result, 'id', $filterableFields);
385 }
386
387 /**
388 * Get a list of remotely available extensions.
389 *
390 * @param array $params
391 *
392 * @return array
393 * API result
394 */
395 function civicrm_api3_extension_getremote($params) {
396 $extensions = CRM_Extension_System::singleton()->getBrowser()->getExtensions();
397 $result = [];
398 $id = 0;
399 foreach ($extensions as $key => $obj) {
400 $info = [];
401 // backward compatibility with indexing scheme
402 $info['id'] = $id++;
403 $info = array_merge($info, (array) $obj);
404 $result[] = $info;
405 }
406 return _civicrm_api3_basic_array_get('Extension', $params, $result, 'id', CRM_Utils_Array::value('return', $params, []));
407 }
408
409 /**
410 * Determine the list of extension keys.
411 *
412 * @param array $params
413 * @param string $key
414 * API request params with 'keys' or 'path'.
415 * - keys: A comma-delimited list of extension names
416 * - path: An absolute directory path. May append '*' to match all sub-directories.
417 *
418 * @return array
419 */
420 function _civicrm_api3_getKeys($params, $key = 'keys') {
421 if ($key == 'path') {
422 return CRM_Extension_System::singleton()->getMapper()->getKeysByPath($params['path']);
423 }
424 if (isset($params[$key])) {
425 if (is_array($params[$key])) {
426 return $params[$key];
427 }
428 if ($params[$key] == '') {
429 return [];
430 }
431 return explode(API_V3_EXTENSION_DELIMITER, $params[$key]);
432 }
433 else {
434 return [];
435 }
436 }