Api4 generated code improvements
[civicrm-core.git] / Civi / Angular / Manager.php
CommitLineData
16072ce1
TO
1<?php
2namespace Civi\Angular;
3
4/**
5 * Manage Angular resources.
6 *
7 * @package Civi\Angular
8 */
9class Manager {
10
11 /**
12 * @var \CRM_Core_Resources
13 */
14 protected $res = NULL;
15
16 /**
cc101011 17 * Modules.
18 *
19 * @var array|null
16072ce1
TO
20 * Each item has some combination of these keys:
21 * - ext: string
8671b4f2 22 * The Civi extension which defines the Angular module.
16072ce1 23 * - js: array(string $relativeFilePath)
8671b4f2 24 * List of JS files (relative to the extension).
16072ce1 25 * - css: array(string $relativeFilePath)
8671b4f2 26 * List of CSS files (relative to the extension).
16072ce1 27 * - partials: array(string $relativeFilePath)
8671b4f2
TO
28 * A list of partial-HTML folders (relative to the extension).
29 * This will be mapped to "~/moduleName" by crmResource.
1da632e0
TO
30 * - settings: array(string $key => mixed $value)
31 * List of settings to preload.
16072ce1
TO
32 */
33 protected $modules = NULL;
34
53d00841
TO
35 /**
36 * @var \CRM_Utils_Cache_Interface
37 */
38 protected $cache;
39
6dc348de
TO
40 /**
41 * @var array
42 * Array(string $name => ChangeSet $change).
43 */
44 protected $changeSets = NULL;
45
16072ce1
TO
46 /**
47 * @param \CRM_Core_Resources $res
48 * The resource manager.
34f3bbd9 49 * @param $cache
16072ce1 50 */
53d00841 51 public function __construct($res, \CRM_Utils_Cache_Interface $cache = NULL) {
16072ce1 52 $this->res = $res;
c33f1df1 53 $this->cache = $cache ? $cache : new \CRM_Utils_Cache_ArrayCache([]);
16072ce1
TO
54 }
55
56 /**
fe482240 57 * Get a list of AngularJS modules which should be autoloaded.
16072ce1
TO
58 *
59 * @return array
8671b4f2
TO
60 * Each item has some combination of these keys:
61 * - ext: string
62 * The Civi extension which defines the Angular module.
63 * - js: array(string $relativeFilePath)
64 * List of JS files (relative to the extension).
65 * - css: array(string $relativeFilePath)
66 * List of CSS files (relative to the extension).
67 * - partials: array(string $relativeFilePath)
68 * A list of partial-HTML folders (relative to the extension).
69 * This will be mapped to "~/moduleName" by crmResource.
1da632e0
TO
70 * - settings: array(string $key => mixed $value)
71 * List of settings to preload.
16072ce1
TO
72 */
73 public function getModules() {
74 if ($this->modules === NULL) {
1da632e0 75 $config = \CRM_Core_Config::singleton();
8456e727 76 global $civicrm_root;
16072ce1 77
8456e727
TO
78 // Note: It would be nice to just glob("$civicrm_root/ang/*.ang.php"), but at time
79 // of writing CiviMail and CiviCase have special conditionals.
16072ce1 80
c64f69d9 81 $angularModules = [];
8456e727 82 $angularModules['angularFileUpload'] = include "$civicrm_root/ang/angularFileUpload.ang.php";
b9a4dfbd 83 $angularModules['checklist-model'] = include "$civicrm_root/ang/checklist-model.ang.php";
8456e727
TO
84 $angularModules['crmApp'] = include "$civicrm_root/ang/crmApp.ang.php";
85 $angularModules['crmAttachment'] = include "$civicrm_root/ang/crmAttachment.ang.php";
86 $angularModules['crmAutosave'] = include "$civicrm_root/ang/crmAutosave.ang.php";
87 $angularModules['crmCxn'] = include "$civicrm_root/ang/crmCxn.ang.php";
88 // $angularModules['crmExample'] = include "$civicrm_root/ang/crmExample.ang.php";
89 $angularModules['crmResource'] = include "$civicrm_root/ang/crmResource.ang.php";
67341f3c 90 $angularModules['crmRouteBinder'] = include "$civicrm_root/ang/crmRouteBinder.ang.php";
8456e727
TO
91 $angularModules['crmUi'] = include "$civicrm_root/ang/crmUi.ang.php";
92 $angularModules['crmUtil'] = include "$civicrm_root/ang/crmUtil.ang.php";
93 $angularModules['dialogService'] = include "$civicrm_root/ang/dialogService.ang.php";
94 $angularModules['ngRoute'] = include "$civicrm_root/ang/ngRoute.ang.php";
95 $angularModules['ngSanitize'] = include "$civicrm_root/ang/ngSanitize.ang.php";
96 $angularModules['ui.utils'] = include "$civicrm_root/ang/ui.utils.ang.php";
e88c001e 97 $angularModules['ui.bootstrap'] = include "$civicrm_root/ang/ui.bootstrap.ang.php";
8456e727
TO
98 $angularModules['ui.sortable'] = include "$civicrm_root/ang/ui.sortable.ang.php";
99 $angularModules['unsavedChanges'] = include "$civicrm_root/ang/unsavedChanges.ang.php";
100 $angularModules['statuspage'] = include "$civicrm_root/ang/crmStatusPage.ang.php";
c0f7f681 101
16072ce1
TO
102 foreach (\CRM_Core_Component::getEnabledComponents() as $component) {
103 $angularModules = array_merge($angularModules, $component->getAngularModules());
104 }
105 \CRM_Utils_Hook::angularModules($angularModules);
8da6c9b8
TO
106 foreach (array_keys($angularModules) as $module) {
107 if (!isset($angularModules[$module]['basePages'])) {
c64f69d9 108 $angularModules[$module]['basePages'] = ['civicrm/a'];
8da6c9b8
TO
109 }
110 }
16072ce1
TO
111 $this->modules = $this->resolvePatterns($angularModules);
112 }
113
114 return $this->modules;
115 }
116
117 /**
118 * Get the descriptor for an Angular module.
119 *
120 * @param string $name
121 * Module name.
122 * @return array
123 * Details about the module:
124 * - ext: string, the name of the Civi extension which defines the module
125 * - js: array(string $relativeFilePath).
126 * - css: array(string $relativeFilePath).
127 * - partials: array(string $relativeFilePath).
128 * @throws \Exception
129 */
130 public function getModule($name) {
131 $modules = $this->getModules();
132 if (!isset($modules[$name])) {
133 throw new \Exception("Unrecognized Angular module");
134 }
135 return $modules[$name];
136 }
137
8da6c9b8
TO
138 /**
139 * Resolve a full list of Angular dependencies.
140 *
141 * @param array $names
142 * List of Angular modules.
143 * Ex: array('crmMailing').
144 * @return array
145 * List of Angular modules, include all dependencies.
146 * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute').
147 */
148 public function resolveDependencies($names) {
149 $allModules = $this->getModules();
c64f69d9 150 $visited = [];
8da6c9b8
TO
151 $result = $names;
152 while (($missingModules = array_diff($result, array_keys($visited))) && !empty($missingModules)) {
153 foreach ($missingModules as $module) {
154 $visited[$module] = 1;
155 if (!isset($allModules[$module])) {
c64f69d9 156 \Civi::log()->warning('Unrecognized Angular module {name}. Please ensure that all Angular modules are declared.', [
8da6c9b8
TO
157 'name' => $module,
158 'civi.tag' => 'deprecated',
c64f69d9 159 ]);
8da6c9b8
TO
160 }
161 elseif (isset($allModules[$module]['requires'])) {
162 $result = array_unique(array_merge($result, $allModules[$module]['requires']));
163 }
164 }
165 }
166 sort($result);
167 return $result;
168 }
169
170 /**
171 * Get a list of Angular modules that should be loaded on the given
172 * base-page.
173 *
174 * @param string $basePage
175 * The name of the base-page for which we want a list of moudles.
176 * @return array
177 * List of Angular modules.
178 * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute').
179 */
180 public function resolveDefaultModules($basePage) {
181 $modules = $this->getModules();
c64f69d9 182 $result = [];
8da6c9b8
TO
183 foreach ($modules as $moduleName => $module) {
184 if (in_array($basePage, $module['basePages']) || in_array('*', $module['basePages'])) {
185 $result[] = $moduleName;
186 }
187 }
188 return $result;
189 }
190
16072ce1
TO
191 /**
192 * Convert any globs in an Angular module to file names.
193 *
194 * @param array $modules
195 * List of Angular modules.
196 * @return array
197 * Updated list of Angular modules
198 */
199 protected function resolvePatterns($modules) {
c64f69d9 200 $newModules = [];
16072ce1
TO
201
202 foreach ($modules as $moduleKey => $module) {
c64f69d9 203 foreach (['js', 'css', 'partials'] as $fileset) {
16072ce1
TO
204 if (!isset($module[$fileset])) {
205 continue;
206 }
207 $module[$fileset] = $this->res->glob($module['ext'], $module[$fileset]);
208 }
209 $newModules[$moduleKey] = $module;
210 }
211
212 return $newModules;
213 }
214
215 /**
6dc348de 216 * Get the partial HTML documents for a module (unfiltered).
16072ce1
TO
217 *
218 * @param string $name
219 * Angular module name.
220 * @return array
221 * Array(string $extFilePath => string $html)
a2dc0f82
TO
222 * @throws \Exception
223 * Invalid partials configuration.
16072ce1 224 */
6dc348de 225 public function getRawPartials($name) {
16072ce1 226 $module = $this->getModule($name);
c64f69d9 227 $result = [];
16072ce1 228 if (isset($module['partials'])) {
a2dc0f82
TO
229 foreach ($module['partials'] as $partialDir) {
230 $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir;
231 $files = \CRM_Utils_File::findFiles($partialDir, '*.html', TRUE);
232 foreach ($files as $file) {
233 $filename = '~/' . $name . '/' . $file;
234 $result[$filename] = file_get_contents($partialDir . '/' . $file);
235 }
16072ce1 236 }
6dc348de 237 return $result;
16072ce1
TO
238 }
239 return $result;
240 }
241
6dc348de
TO
242 /**
243 * Get the partial HTML documents for a module.
244 *
245 * @param string $name
246 * Angular module name.
247 * @return array
248 * Array(string $extFilePath => string $html)
249 * @throws \Exception
250 * Invalid partials configuration.
251 */
252 public function getPartials($name) {
8b25b1fe 253 $cacheKey = "angular-partials_$name";
6dc348de
TO
254 $cacheValue = $this->cache->get($cacheKey);
255 if ($cacheValue === NULL) {
256 $cacheValue = ChangeSet::applyResourceFilters($this->getChangeSets(), 'partials', $this->getRawPartials($name));
257 $this->cache->set($cacheKey, $cacheValue);
258 }
259 return $cacheValue;
260 }
261
16072ce1
TO
262 /**
263 * Get list of translated strings for a module.
264 *
265 * @param string $name
266 * Angular module name.
267 * @return array
268 * Translated strings: array(string $orig => string $translated).
269 */
270 public function getTranslatedStrings($name) {
e3d90d6c 271 $module = $this->getModule($name);
c64f69d9 272 $result = [];
16072ce1
TO
273 $strings = $this->getStrings($name);
274 foreach ($strings as $string) {
275 // TODO: should we pass translation domain based on $module[ext] or $module[tsDomain]?
276 // It doesn't look like client side really supports the domain right now...
c64f69d9
CW
277 $translated = ts($string, [
278 'domain' => [$module['ext'], NULL],
279 ]);
16072ce1
TO
280 if ($translated != $string) {
281 $result[$string] = $translated;
282 }
283 }
284 return $result;
285 }
286
287 /**
288 * Get list of translatable strings for a module.
289 *
290 * @param string $name
291 * Angular module name.
292 * @return array
293 * Translatable strings.
294 */
295 public function getStrings($name) {
296 $module = $this->getModule($name);
c64f69d9 297 $result = [];
16072ce1
TO
298 if (isset($module['js'])) {
299 foreach ($module['js'] as $file) {
300 $strings = $this->res->getStrings()->get(
301 $module['ext'],
302 $this->res->getPath($module['ext'], $file),
303 'text/javascript'
304 );
305 $result = array_unique(array_merge($result, $strings));
306 }
307 }
53d00841
TO
308 $partials = $this->getPartials($name);
309 foreach ($partials as $partial) {
310 $result = array_unique(array_merge($result, \CRM_Utils_JS::parseStrings($partial)));
16072ce1
TO
311 }
312 return $result;
313 }
314
315 /**
27a90ef6
TO
316 * Get resources for one or more modules.
317 *
318 * @param string|array $moduleNames
319 * List of module names.
320 * @param string $resType
1da632e0 321 * Type of resource ('js', 'css', 'settings').
27a90ef6 322 * @param string $refType
1da632e0 323 * Type of reference to the resource ('cacheUrl', 'rawUrl', 'path', 'settings').
16072ce1 324 * @return array
27a90ef6
TO
325 * List of URLs or paths.
326 * @throws \CRM_Core_Exception
16072ce1 327 */
27a90ef6 328 public function getResources($moduleNames, $resType, $refType) {
c64f69d9 329 $result = [];
27a90ef6
TO
330 $moduleNames = (array) $moduleNames;
331 foreach ($moduleNames as $moduleName) {
332 $module = $this->getModule($moduleName);
333 if (isset($module[$resType])) {
334 foreach ($module[$resType] as $file) {
e5c376e7
TO
335 $refTypeSuffix = '';
336 if (is_string($file) && preg_match(';^(assetBuilder|ext)://;', $file)) {
337 $refTypeSuffix = '-' . parse_url($file, PHP_URL_SCHEME);
338 }
339
340 switch ($refType . $refTypeSuffix) {
27a90ef6
TO
341 case 'path':
342 $result[] = $this->res->getPath($module['ext'], $file);
343 break;
16072ce1 344
27a90ef6
TO
345 case 'rawUrl':
346 $result[] = $this->res->getUrl($module['ext'], $file);
347 break;
348
349 case 'cacheUrl':
350 $result[] = $this->res->getUrl($module['ext'], $file, TRUE);
351 break;
352
e5c376e7
TO
353 case 'path-assetBuilder':
354 $assetName = parse_url($file, PHP_URL_HOST) . parse_url($file, PHP_URL_PATH);
c64f69d9 355 $assetParams = [];
e5c376e7
TO
356 parse_str('' . parse_url($file, PHP_URL_QUERY), $assetParams);
357 $result[] = \Civi::service('asset_builder')->getPath($assetName, $assetParams);
358 break;
359
360 case 'rawUrl-assetBuilder':
361 case 'cacheUrl-assetBuilder':
362 $assetName = parse_url($file, PHP_URL_HOST) . parse_url($file, PHP_URL_PATH);
c64f69d9 363 $assetParams = [];
e5c376e7
TO
364 parse_str('' . parse_url($file, PHP_URL_QUERY), $assetParams);
365 $result[] = \Civi::service('asset_builder')->getUrl($assetName, $assetParams);
366 break;
367
368 case 'path-ext':
369 $result[] = $this->res->getPath(parse_url($file, PHP_URL_HOST), ltrim(parse_url($file, PHP_URL_PATH), '/'));
370 break;
371
372 case 'rawUrl-ext':
373 $result[] = $this->res->getUrl(parse_url($file, PHP_URL_HOST), ltrim(parse_url($file, PHP_URL_PATH), '/'));
374 break;
375
376 case 'cacheUrl-ext':
377 $result[] = $this->res->getUrl(parse_url($file, PHP_URL_HOST), ltrim(parse_url($file, PHP_URL_PATH), '/'), TRUE);
378 break;
379
1da632e0 380 case 'settings':
5438399c 381 case 'requires':
1da632e0
TO
382 if (!empty($module[$resType])) {
383 $result[$moduleName] = $module[$resType];
384 }
385 break;
386
27a90ef6
TO
387 default:
388 throw new \CRM_Core_Exception("Unrecognized resource format");
389 }
390 }
16072ce1
TO
391 }
392 }
6dc348de
TO
393
394 return ChangeSet::applyResourceFilters($this->getChangeSets(), $resType, $result);
395 }
396
397 /**
398 * @return array
399 * Array(string $name => ChangeSet $changeSet).
400 */
401 public function getChangeSets() {
402 if ($this->changeSets === NULL) {
c64f69d9 403 $this->changeSets = [];
6dc348de
TO
404 \CRM_Utils_Hook::alterAngular($this);
405 }
406 return $this->changeSets;
407 }
408
409 /**
410 * @param ChangeSet $changeSet
411 * @return \Civi\Angular\Manager
412 */
413 public function add($changeSet) {
414 $this->changeSets[$changeSet->getName()] = $changeSet;
415 return $this;
16072ce1 416 }
8671b4f2 417
16072ce1 418}