2 namespace Civi\Angular
;
5 * Manage Angular resources.
7 * @package Civi\Angular
12 * @var \CRM_Core_Resources
14 protected $res = NULL;
18 * Each item has some combination of these keys:
20 * The Civi extension which defines the Angular module.
21 * - js: array(string $relativeFilePath)
22 * List of JS files (relative to the extension).
23 * - css: array(string $relativeFilePath)
24 * List of CSS files (relative to the extension).
25 * - partials: array(string $relativeFilePath)
26 * A list of partial-HTML folders (relative to the extension).
27 * This will be mapped to "~/moduleName" by crmResource.
28 * - settings: array(string $key => mixed $value)
29 * List of settings to preload.
31 protected $modules = NULL;
34 * @var \CRM_Utils_Cache_Interface
40 * Array(string $name => ChangeSet $change).
42 protected $changeSets = NULL;
45 * @param \CRM_Core_Resources $res
46 * The resource manager.
49 public function __construct($res, \CRM_Utils_Cache_Interface
$cache = NULL) {
51 $this->cache
= $cache ?
$cache : new \
CRM_Utils_Cache_Arraycache([]);
55 * Get a list of AngularJS modules which should be autoloaded.
58 * Each item has some combination of these keys:
60 * The Civi extension which defines the Angular module.
61 * - js: array(string $relativeFilePath)
62 * List of JS files (relative to the extension).
63 * - css: array(string $relativeFilePath)
64 * List of CSS files (relative to the extension).
65 * - partials: array(string $relativeFilePath)
66 * A list of partial-HTML folders (relative to the extension).
67 * This will be mapped to "~/moduleName" by crmResource.
68 * - settings: array(string $key => mixed $value)
69 * List of settings to preload.
71 public function getModules() {
72 if ($this->modules
=== NULL) {
73 $config = \CRM_Core_Config
::singleton();
76 // Note: It would be nice to just glob("$civicrm_root/ang/*.ang.php"), but at time
77 // of writing CiviMail and CiviCase have special conditionals.
80 $angularModules['angularFileUpload'] = include "$civicrm_root/ang/angularFileUpload.ang.php";
81 $angularModules['crmApp'] = include "$civicrm_root/ang/crmApp.ang.php";
82 $angularModules['crmAttachment'] = include "$civicrm_root/ang/crmAttachment.ang.php";
83 $angularModules['crmAutosave'] = include "$civicrm_root/ang/crmAutosave.ang.php";
84 $angularModules['crmCxn'] = include "$civicrm_root/ang/crmCxn.ang.php";
85 // $angularModules['crmExample'] = include "$civicrm_root/ang/crmExample.ang.php";
86 $angularModules['crmResource'] = include "$civicrm_root/ang/crmResource.ang.php";
87 $angularModules['crmRouteBinder'] = include "$civicrm_root/ang/crmRouteBinder.ang.php";
88 $angularModules['crmUi'] = include "$civicrm_root/ang/crmUi.ang.php";
89 $angularModules['crmUtil'] = include "$civicrm_root/ang/crmUtil.ang.php";
90 $angularModules['dialogService'] = include "$civicrm_root/ang/dialogService.ang.php";
91 $angularModules['ngRoute'] = include "$civicrm_root/ang/ngRoute.ang.php";
92 $angularModules['ngSanitize'] = include "$civicrm_root/ang/ngSanitize.ang.php";
93 $angularModules['ui.utils'] = include "$civicrm_root/ang/ui.utils.ang.php";
94 $angularModules['ui.bootstrap'] = include "$civicrm_root/ang/ui.bootstrap.ang.php";
95 $angularModules['ui.sortable'] = include "$civicrm_root/ang/ui.sortable.ang.php";
96 $angularModules['unsavedChanges'] = include "$civicrm_root/ang/unsavedChanges.ang.php";
97 $angularModules['statuspage'] = include "$civicrm_root/ang/crmStatusPage.ang.php";
99 foreach (\CRM_Core_Component
::getEnabledComponents() as $component) {
100 $angularModules = array_merge($angularModules, $component->getAngularModules());
102 \CRM_Utils_Hook
::angularModules($angularModules);
103 foreach (array_keys($angularModules) as $module) {
104 if (!isset($angularModules[$module]['basePages'])) {
105 $angularModules[$module]['basePages'] = ['civicrm/a'];
108 $this->modules
= $this->resolvePatterns($angularModules);
111 return $this->modules
;
115 * Get the descriptor for an Angular module.
117 * @param string $name
120 * Details about the module:
121 * - ext: string, the name of the Civi extension which defines the module
122 * - js: array(string $relativeFilePath).
123 * - css: array(string $relativeFilePath).
124 * - partials: array(string $relativeFilePath).
127 public function getModule($name) {
128 $modules = $this->getModules();
129 if (!isset($modules[$name])) {
130 throw new \
Exception("Unrecognized Angular module");
132 return $modules[$name];
136 * Resolve a full list of Angular dependencies.
138 * @param array $names
139 * List of Angular modules.
140 * Ex: array('crmMailing').
142 * List of Angular modules, include all dependencies.
143 * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute').
145 public function resolveDependencies($names) {
146 $allModules = $this->getModules();
149 while (($missingModules = array_diff($result, array_keys($visited))) && !empty($missingModules)) {
150 foreach ($missingModules as $module) {
151 $visited[$module] = 1;
152 if (!isset($allModules[$module])) {
153 \Civi
::log()->warning('Unrecognized Angular module {name}. Please ensure that all Angular modules are declared.', [
155 'civi.tag' => 'deprecated',
158 elseif (isset($allModules[$module]['requires'])) {
159 $result = array_unique(array_merge($result, $allModules[$module]['requires']));
168 * Get a list of Angular modules that should be loaded on the given
171 * @param string $basePage
172 * The name of the base-page for which we want a list of moudles.
174 * List of Angular modules.
175 * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute').
177 public function resolveDefaultModules($basePage) {
178 $modules = $this->getModules();
180 foreach ($modules as $moduleName => $module) {
181 if (in_array($basePage, $module['basePages']) ||
in_array('*', $module['basePages'])) {
182 $result[] = $moduleName;
189 * Convert any globs in an Angular module to file names.
191 * @param array $modules
192 * List of Angular modules.
194 * Updated list of Angular modules
196 protected function resolvePatterns($modules) {
199 foreach ($modules as $moduleKey => $module) {
200 foreach (['js', 'css', 'partials'] as $fileset) {
201 if (!isset($module[$fileset])) {
204 $module[$fileset] = $this->res
->glob($module['ext'], $module[$fileset]);
206 $newModules[$moduleKey] = $module;
213 * Get the partial HTML documents for a module (unfiltered).
215 * @param string $name
216 * Angular module name.
218 * Array(string $extFilePath => string $html)
220 * Invalid partials configuration.
222 public function getRawPartials($name) {
223 $module = $this->getModule($name);
225 if (isset($module['partials'])) {
226 foreach ($module['partials'] as $partialDir) {
227 $partialDir = $this->res
->getPath($module['ext']) . '/' . $partialDir;
228 $files = \CRM_Utils_File
::findFiles($partialDir, '*.html', TRUE);
229 foreach ($files as $file) {
230 $filename = '~/' . $name . '/' . $file;
231 $result[$filename] = file_get_contents($partialDir . '/' . $file);
240 * Get the partial HTML documents for a module.
242 * @param string $name
243 * Angular module name.
245 * Array(string $extFilePath => string $html)
247 * Invalid partials configuration.
249 public function getPartials($name) {
250 $cacheKey = "angular-partials_$name";
251 $cacheValue = $this->cache
->get($cacheKey);
252 if ($cacheValue === NULL) {
253 $cacheValue = ChangeSet
::applyResourceFilters($this->getChangeSets(), 'partials', $this->getRawPartials($name));
254 $this->cache
->set($cacheKey, $cacheValue);
260 * Get list of translated strings for a module.
262 * @param string $name
263 * Angular module name.
265 * Translated strings: array(string $orig => string $translated).
267 public function getTranslatedStrings($name) {
268 $module = $this->getModule($name);
270 $strings = $this->getStrings($name);
271 foreach ($strings as $string) {
272 // TODO: should we pass translation domain based on $module[ext] or $module[tsDomain]?
273 // It doesn't look like client side really supports the domain right now...
274 $translated = ts($string, [
275 'domain' => [$module['ext'], NULL],
277 if ($translated != $string) {
278 $result[$string] = $translated;
285 * Get list of translatable strings for a module.
287 * @param string $name
288 * Angular module name.
290 * Translatable strings.
292 public function getStrings($name) {
293 $module = $this->getModule($name);
295 if (isset($module['js'])) {
296 foreach ($module['js'] as $file) {
297 $strings = $this->res
->getStrings()->get(
299 $this->res
->getPath($module['ext'], $file),
302 $result = array_unique(array_merge($result, $strings));
305 $partials = $this->getPartials($name);
306 foreach ($partials as $partial) {
307 $result = array_unique(array_merge($result, \CRM_Utils_JS
::parseStrings($partial)));
313 * Get resources for one or more modules.
315 * @param string|array $moduleNames
316 * List of module names.
317 * @param string $resType
318 * Type of resource ('js', 'css', 'settings').
319 * @param string $refType
320 * Type of reference to the resource ('cacheUrl', 'rawUrl', 'path', 'settings').
322 * List of URLs or paths.
323 * @throws \CRM_Core_Exception
325 public function getResources($moduleNames, $resType, $refType) {
327 $moduleNames = (array) $moduleNames;
328 foreach ($moduleNames as $moduleName) {
329 $module = $this->getModule($moduleName);
330 if (isset($module[$resType])) {
331 foreach ($module[$resType] as $file) {
333 if (is_string($file) && preg_match(';^(assetBuilder|ext)://;', $file)) {
334 $refTypeSuffix = '-' . parse_url($file, PHP_URL_SCHEME
);
337 switch ($refType . $refTypeSuffix) {
339 $result[] = $this->res
->getPath($module['ext'], $file);
343 $result[] = $this->res
->getUrl($module['ext'], $file);
347 $result[] = $this->res
->getUrl($module['ext'], $file, TRUE);
350 case 'path-assetBuilder':
351 $assetName = parse_url($file, PHP_URL_HOST
) . parse_url($file, PHP_URL_PATH
);
353 parse_str('' . parse_url($file, PHP_URL_QUERY
), $assetParams);
354 $result[] = \Civi
::service('asset_builder')->getPath($assetName, $assetParams);
357 case 'rawUrl-assetBuilder':
358 case 'cacheUrl-assetBuilder':
359 $assetName = parse_url($file, PHP_URL_HOST
) . parse_url($file, PHP_URL_PATH
);
361 parse_str('' . parse_url($file, PHP_URL_QUERY
), $assetParams);
362 $result[] = \Civi
::service('asset_builder')->getUrl($assetName, $assetParams);
366 $result[] = $this->res
->getPath(parse_url($file, PHP_URL_HOST
), ltrim(parse_url($file, PHP_URL_PATH
), '/'));
370 $result[] = $this->res
->getUrl(parse_url($file, PHP_URL_HOST
), ltrim(parse_url($file, PHP_URL_PATH
), '/'));
374 $result[] = $this->res
->getUrl(parse_url($file, PHP_URL_HOST
), ltrim(parse_url($file, PHP_URL_PATH
), '/'), TRUE);
379 if (!empty($module[$resType])) {
380 $result[$moduleName] = $module[$resType];
385 throw new \
CRM_Core_Exception("Unrecognized resource format");
391 return ChangeSet
::applyResourceFilters($this->getChangeSets(), $resType, $result);
396 * Array(string $name => ChangeSet $changeSet).
398 public function getChangeSets() {
399 if ($this->changeSets
=== NULL) {
400 $this->changeSets
= [];
401 \CRM_Utils_Hook
::alterAngular($this);
403 return $this->changeSets
;
407 * @param ChangeSet $changeSet
408 * @return \Civi\Angular\Manager
410 public function add($changeSet) {
411 $this->changeSets
[$changeSet->getName()] = $changeSet;