addModules('moduleFoo') * ->useApp(); // Optional, if Civi's routing is desired (full-page apps only) * ``` * * @link https://docs.angularjs.org/guide/bootstrap */ class AngularLoader { /** * The weight to assign to any Angular JS module files. */ const DEFAULT_MODULE_WEIGHT = 200; /** * The resource manager. * * Do not use publicly. Inject your own copy! * * @var \CRM_Core_Resources */ protected $res; /** * The Angular module manager. * * Do not use publicly. Inject your own copy! * * @var \Civi\Angular\Manager */ protected $angular; /** * The region of the page into which JavaScript will be loaded. * * @var string */ protected $region; /** * @var string * Ex: 'civicrm/a'. */ protected $pageName; /** * @var array * A list of modules to load. */ protected $modules; /** * @var array|null */ protected $crmApp = NULL; /** * AngularLoader constructor. */ public function __construct() { $this->res = \CRM_Core_Resources::singleton(); $this->angular = \Civi::service('angular'); $this->region = \CRM_Utils_Request::retrieve('snippet', 'String') ? 'ajax-snippet' : 'html-header'; $this->pageName = \CRM_Utils_System::currentPath(); $this->modules = []; } /** * Calling this method from outside this class is deprecated. * * Use the `angularjs.loader` service instead. * * @deprecated * @return $this */ public function load() { \CRM_Core_Error::deprecatedFunctionWarning('angularjs.loader service'); return $this->loadAngularResources(); } /** * Load scripts, styles & settings for the active modules. * * @return $this * @throws \CRM_Core_Exception */ private function loadAngularResources() { $angular = $this->getAngular(); $res = $this->getRes(); if ($this->crmApp !== NULL) { $this->addModules($this->crmApp['modules']); $this->res->addSetting([ 'crmApp' => [ 'defaultRoute' => $this->crmApp['defaultRoute'], ], ]); // If trying to load an Angular page via AJAX, the route must be passed as a // URL parameter, since the server doesn't receive information about // URL fragments (i.e, what comes after the #). $this->res->addSetting([ 'angularRoute' => $this->crmApp['activeRoute'], ]); } $moduleNames = $this->findActiveModules(); if (!$this->isAllModules($moduleNames)) { $assetParams = ['modules' => implode(',', $moduleNames)]; } else { // The module list will be "all modules that the user can see". $assetParams = ['nonce' => md5(implode(',', $moduleNames))]; } $res->addSettingsFactory(function () use (&$moduleNames, $angular, $res, $assetParams) { // Merge static settings with the results of settingsFactory functions $settingsByModule = $angular->getResources($moduleNames, 'settings', 'settings'); foreach ($angular->getResources($moduleNames, 'settingsFactory', 'settingsFactory') as $moduleName => $factory) { $settingsByModule[$moduleName] = array_merge($settingsByModule[$moduleName] ?? [], $factory()); } // Add clientside permissions $permissions = []; $toCheck = $angular->getResources($moduleNames, 'permissions', 'permissions'); foreach ($toCheck as $perms) { foreach ((array) $perms as $perm) { if (!isset($permissions[$perm])) { $permissions[$perm] = \CRM_Core_Permission::check($perm); } } } // TODO optimization; client-side caching return array_merge($settingsByModule, ['permissions' => $permissions], [ 'resourceUrls' => \CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(), 'angular' => [ 'modules' => $moduleNames, 'requires' => $angular->getResources($moduleNames, 'requires', 'requires'), 'cacheCode' => $res->getCacheCode(), 'bundleUrl' => \Civi::service('asset_builder')->getUrl('angular-modules.json', $assetParams), ], ]); }); $res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->getRegion(), FALSE); $headOffset = 0; $config = \CRM_Core_Config::singleton(); if ($config->debug) { // FIXME: The `resetLocationProviderHashPrefix.js` has to stay in sync with `\Civi\Angular\Page\Modules::buildAngularModules()`. $res->addScriptFile('civicrm', 'ang/resetLocationProviderHashPrefix.js', 101, $this->getRegion(), FALSE); foreach ($moduleNames as $moduleName) { foreach ($this->angular->getResources($moduleName, 'css', 'cacheUrl') as $url) { $res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); } foreach ($this->angular->getResources($moduleName, 'js', 'cacheUrl') as $url) { $res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); // addScriptUrl() bypasses the normal string-localization of addScriptFile(), // but that's OK because all Angular strings (JS+HTML) will load via crmResource. } } } else { // Note: addScriptUrl() bypasses the normal string-localization of addScriptFile(), // but that's OK because all Angular strings (JS+HTML) will load via crmResource. // $aggScriptUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=js&r=' . $res->getCacheCode(), FALSE, NULL, FALSE); $aggScriptUrl = \Civi::service('asset_builder')->getUrl('angular-modules.js', $assetParams); $res->addScriptUrl($aggScriptUrl, 120, $this->getRegion()); // FIXME: The following CSS aggregator doesn't currently handle path-adjustments - which can break icons. //$aggStyleUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=css&r=' . $res->getCacheCode(), FALSE, NULL, FALSE); //$aggStyleUrl = \Civi::service('asset_builder')->getUrl('angular-modules.css', $assetParams); //$res->addStyleUrl($aggStyleUrl, 120, $this->getRegion()); foreach ($this->angular->getResources($moduleNames, 'css', 'cacheUrl') as $url) { $res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); } } // Add bundles foreach ($this->angular->getResources($moduleNames, 'bundles', 'bundles') as $bundles) { $res->addBundle($bundles); } return $this; } /** * Use Civi's generic "application" module. * * This is suitable for use on a basic, standalone Angular page * like `civicrm/a`. (If you need to integrate Angular with pre-existing, * non-Angular pages... then this probably won't help.) * * The Angular bootstrap process requires an HTML directive like * `