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