Merge pull request #10340 from Stoob/master
[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 /**
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";
86 $angularModules['crmUi'] = include "$civicrm_root/ang/crmUi.ang.php";
87 $angularModules['crmUtil'] = include "$civicrm_root/ang/crmUtil.ang.php";
88 $angularModules['dialogService'] = include "$civicrm_root/ang/dialogService.ang.php";
89 $angularModules['ngRoute'] = include "$civicrm_root/ang/ngRoute.ang.php";
90 $angularModules['ngSanitize'] = include "$civicrm_root/ang/ngSanitize.ang.php";
91 $angularModules['ui.utils'] = include "$civicrm_root/ang/ui.utils.ang.php";
e88c001e 92 $angularModules['ui.bootstrap'] = include "$civicrm_root/ang/ui.bootstrap.ang.php";
8456e727
TO
93 $angularModules['ui.sortable'] = include "$civicrm_root/ang/ui.sortable.ang.php";
94 $angularModules['unsavedChanges'] = include "$civicrm_root/ang/unsavedChanges.ang.php";
95 $angularModules['statuspage'] = include "$civicrm_root/ang/crmStatusPage.ang.php";
c0f7f681 96
16072ce1
TO
97 foreach (\CRM_Core_Component::getEnabledComponents() as $component) {
98 $angularModules = array_merge($angularModules, $component->getAngularModules());
99 }
100 \CRM_Utils_Hook::angularModules($angularModules);
8da6c9b8
TO
101 foreach (array_keys($angularModules) as $module) {
102 if (!isset($angularModules[$module]['basePages'])) {
103 $angularModules[$module]['basePages'] = array('civicrm/a');
104 }
105 }
16072ce1
TO
106 $this->modules = $this->resolvePatterns($angularModules);
107 }
108
109 return $this->modules;
110 }
111
112 /**
113 * Get the descriptor for an Angular module.
114 *
115 * @param string $name
116 * Module name.
117 * @return array
118 * Details about the module:
119 * - ext: string, the name of the Civi extension which defines the module
120 * - js: array(string $relativeFilePath).
121 * - css: array(string $relativeFilePath).
122 * - partials: array(string $relativeFilePath).
123 * @throws \Exception
124 */
125 public function getModule($name) {
126 $modules = $this->getModules();
127 if (!isset($modules[$name])) {
128 throw new \Exception("Unrecognized Angular module");
129 }
130 return $modules[$name];
131 }
132
8da6c9b8
TO
133 /**
134 * Resolve a full list of Angular dependencies.
135 *
136 * @param array $names
137 * List of Angular modules.
138 * Ex: array('crmMailing').
139 * @return array
140 * List of Angular modules, include all dependencies.
141 * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute').
142 */
143 public function resolveDependencies($names) {
144 $allModules = $this->getModules();
145 $visited = array();
146 $result = $names;
147 while (($missingModules = array_diff($result, array_keys($visited))) && !empty($missingModules)) {
148 foreach ($missingModules as $module) {
149 $visited[$module] = 1;
150 if (!isset($allModules[$module])) {
151 \Civi::log()->warning('Unrecognized Angular module {name}. Please ensure that all Angular modules are declared.', array(
152 'name' => $module,
153 'civi.tag' => 'deprecated',
154 ));
155 }
156 elseif (isset($allModules[$module]['requires'])) {
157 $result = array_unique(array_merge($result, $allModules[$module]['requires']));
158 }
159 }
160 }
161 sort($result);
162 return $result;
163 }
164
165 /**
166 * Get a list of Angular modules that should be loaded on the given
167 * base-page.
168 *
169 * @param string $basePage
170 * The name of the base-page for which we want a list of moudles.
171 * @return array
172 * List of Angular modules.
173 * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute').
174 */
175 public function resolveDefaultModules($basePage) {
176 $modules = $this->getModules();
177 $result = array();
178 foreach ($modules as $moduleName => $module) {
179 if (in_array($basePage, $module['basePages']) || in_array('*', $module['basePages'])) {
180 $result[] = $moduleName;
181 }
182 }
183 return $result;
184 }
185
16072ce1
TO
186 /**
187 * Convert any globs in an Angular module to file names.
188 *
189 * @param array $modules
190 * List of Angular modules.
191 * @return array
192 * Updated list of Angular modules
193 */
194 protected function resolvePatterns($modules) {
195 $newModules = array();
196
197 foreach ($modules as $moduleKey => $module) {
198 foreach (array('js', 'css', 'partials') as $fileset) {
199 if (!isset($module[$fileset])) {
200 continue;
201 }
202 $module[$fileset] = $this->res->glob($module['ext'], $module[$fileset]);
203 }
204 $newModules[$moduleKey] = $module;
205 }
206
207 return $newModules;
208 }
209
210 /**
6dc348de 211 * Get the partial HTML documents for a module (unfiltered).
16072ce1
TO
212 *
213 * @param string $name
214 * Angular module name.
215 * @return array
216 * Array(string $extFilePath => string $html)
a2dc0f82
TO
217 * @throws \Exception
218 * Invalid partials configuration.
16072ce1 219 */
6dc348de 220 public function getRawPartials($name) {
16072ce1
TO
221 $module = $this->getModule($name);
222 $result = array();
223 if (isset($module['partials'])) {
a2dc0f82
TO
224 foreach ($module['partials'] as $partialDir) {
225 $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir;
226 $files = \CRM_Utils_File::findFiles($partialDir, '*.html', TRUE);
227 foreach ($files as $file) {
228 $filename = '~/' . $name . '/' . $file;
229 $result[$filename] = file_get_contents($partialDir . '/' . $file);
230 }
16072ce1 231 }
6dc348de 232 return $result;
16072ce1
TO
233 }
234 return $result;
235 }
236
6dc348de
TO
237 /**
238 * Get the partial HTML documents for a module.
239 *
240 * @param string $name
241 * Angular module name.
242 * @return array
243 * Array(string $extFilePath => string $html)
244 * @throws \Exception
245 * Invalid partials configuration.
246 */
247 public function getPartials($name) {
248 $cacheKey = "angular-partials::$name";
249 $cacheValue = $this->cache->get($cacheKey);
250 if ($cacheValue === NULL) {
251 $cacheValue = ChangeSet::applyResourceFilters($this->getChangeSets(), 'partials', $this->getRawPartials($name));
252 $this->cache->set($cacheKey, $cacheValue);
253 }
254 return $cacheValue;
255 }
256
16072ce1
TO
257 /**
258 * Get list of translated strings for a module.
259 *
260 * @param string $name
261 * Angular module name.
262 * @return array
263 * Translated strings: array(string $orig => string $translated).
264 */
265 public function getTranslatedStrings($name) {
e3d90d6c 266 $module = $this->getModule($name);
16072ce1
TO
267 $result = array();
268 $strings = $this->getStrings($name);
269 foreach ($strings as $string) {
270 // TODO: should we pass translation domain based on $module[ext] or $module[tsDomain]?
271 // It doesn't look like client side really supports the domain right now...
e3d90d6c
TO
272 $translated = ts($string, array(
273 'domain' => array($module['ext'], NULL),
274 ));
16072ce1
TO
275 if ($translated != $string) {
276 $result[$string] = $translated;
277 }
278 }
279 return $result;
280 }
281
282 /**
283 * Get list of translatable strings for a module.
284 *
285 * @param string $name
286 * Angular module name.
287 * @return array
288 * Translatable strings.
289 */
290 public function getStrings($name) {
291 $module = $this->getModule($name);
292 $result = array();
293 if (isset($module['js'])) {
294 foreach ($module['js'] as $file) {
295 $strings = $this->res->getStrings()->get(
296 $module['ext'],
297 $this->res->getPath($module['ext'], $file),
298 'text/javascript'
299 );
300 $result = array_unique(array_merge($result, $strings));
301 }
302 }
53d00841
TO
303 $partials = $this->getPartials($name);
304 foreach ($partials as $partial) {
305 $result = array_unique(array_merge($result, \CRM_Utils_JS::parseStrings($partial)));
16072ce1
TO
306 }
307 return $result;
308 }
309
310 /**
27a90ef6
TO
311 * Get resources for one or more modules.
312 *
313 * @param string|array $moduleNames
314 * List of module names.
315 * @param string $resType
1da632e0 316 * Type of resource ('js', 'css', 'settings').
27a90ef6 317 * @param string $refType
1da632e0 318 * Type of reference to the resource ('cacheUrl', 'rawUrl', 'path', 'settings').
16072ce1 319 * @return array
27a90ef6
TO
320 * List of URLs or paths.
321 * @throws \CRM_Core_Exception
16072ce1 322 */
27a90ef6 323 public function getResources($moduleNames, $resType, $refType) {
16072ce1 324 $result = array();
27a90ef6
TO
325 $moduleNames = (array) $moduleNames;
326 foreach ($moduleNames as $moduleName) {
327 $module = $this->getModule($moduleName);
328 if (isset($module[$resType])) {
329 foreach ($module[$resType] as $file) {
330 switch ($refType) {
331 case 'path':
332 $result[] = $this->res->getPath($module['ext'], $file);
333 break;
16072ce1 334
27a90ef6
TO
335 case 'rawUrl':
336 $result[] = $this->res->getUrl($module['ext'], $file);
337 break;
338
339 case 'cacheUrl':
340 $result[] = $this->res->getUrl($module['ext'], $file, TRUE);
341 break;
342
1da632e0 343 case 'settings':
5438399c 344 case 'requires':
1da632e0
TO
345 if (!empty($module[$resType])) {
346 $result[$moduleName] = $module[$resType];
347 }
348 break;
349
27a90ef6
TO
350 default:
351 throw new \CRM_Core_Exception("Unrecognized resource format");
352 }
353 }
16072ce1
TO
354 }
355 }
6dc348de
TO
356
357 return ChangeSet::applyResourceFilters($this->getChangeSets(), $resType, $result);
358 }
359
360 /**
361 * @return array
362 * Array(string $name => ChangeSet $changeSet).
363 */
364 public function getChangeSets() {
365 if ($this->changeSets === NULL) {
366 $this->changeSets = array();
367 \CRM_Utils_Hook::alterAngular($this);
368 }
369 return $this->changeSets;
370 }
371
372 /**
373 * @param ChangeSet $changeSet
374 * @return \Civi\Angular\Manager
375 */
376 public function add($changeSet) {
377 $this->changeSets[$changeSet->getName()] = $changeSet;
378 return $this;
16072ce1 379 }
8671b4f2 380
16072ce1 381}