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