*/
protected $res;
+
+ /**
+ * @var Civi\Angular\Manager
+ */
+ protected $angular;
+
/**
* @param string $title
* Title of the page.
public function __construct($title = NULL, $mode = NULL, $res = NULL) {
parent::__construct($title, $mode);
$this->res = CRM_Core_Resources::singleton();
+ $this->angular = Civi\Core\Container::singleton()->get('angular');
}
/**
* Register resources required by Angular.
*/
public function registerResources() {
- $modules = $this->getAngularModules();
+ $modules = $this->angular->getModules();
$this->res->addSettingsFactory(function () use (&$modules) {
// TODO optimization; client-side caching
$this->res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, 'html-header', FALSE);
$this->res->addScriptFile('civicrm', 'bower_components/angular-route/angular-route.min.js', 110, 'html-header', FALSE);
$headOffset = 0;
- foreach ($modules as $module) {
- if (!empty($module['css'])) {
- foreach ($module['css'] as $file) {
- $this->res->addStyleFile($module['ext'], $file, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header', TRUE);
- }
- }
- if (!empty($module['js'])) {
- foreach ($module['js'] as $file) {
- $this->res->addScriptFile($module['ext'], $file, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header', TRUE);
- }
+ foreach ($modules as $moduleName => $module) {
+ foreach ($this->angular->getStyleUrls($moduleName) as $url) {
+ $this->res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header');
}
- }
- }
-
- /**
- * Get a list of AngularJS modules which should be autoloaded
- *
- * @return array
- * (string $name => array('ext' => string $key, 'js' => array $paths, 'css' => array $paths))
- */
- public function getAngularModules() {
- $angularModules = array();
- $angularModules['angularFileUpload'] = array(
- 'ext' => 'civicrm',
- 'js' => array('bower_components/angular-file-upload/angular-file-upload.min.js'),
- );
- $angularModules['crmApp'] = array(
- 'ext' => 'civicrm',
- 'js' => array('js/angular-crmApp.js'),
- );
- $angularModules['crmAttachment'] = array(
- 'ext' => 'civicrm',
- 'js' => array('js/angular-crmAttachment.js'),
- 'css' => array('css/angular-crmAttachment.css'),
- 'partials' => array('partials/crmAttachment/*.html'),
- );
- $angularModules['crmUi'] = array(
- 'ext' => 'civicrm',
- 'js' => array('js/angular-crm-ui.js', 'packages/ckeditor/ckeditor.js'),
- 'partials' => array('partials/crmUi/*.html'),
- );
- $angularModules['crmUtil'] = array(
- 'ext' => 'civicrm',
- 'js' => array('js/angular-crm-util.js'),
- );
- // https://github.com/jwstadler/angular-jquery-dialog-service
- $angularModules['dialogService'] = array(
- 'ext' => 'civicrm',
- 'js' => array('bower_components/angular-jquery-dialog-service/dialog-service.js'),
- );
- $angularModules['ngSanitize'] = array(
- 'ext' => 'civicrm',
- 'js' => array('js/angular-sanitize.js'),
- );
- $angularModules['ui.utils'] = array(
- 'ext' => 'civicrm',
- 'js' => array('bower_components/angular-ui-utils/ui-utils.min.js'),
- );
- $angularModules['ui.sortable'] = array(
- 'ext' => 'civicrm',
- 'js' => array('bower_components/angular-ui-sortable/sortable.min.js'),
- );
- $angularModules['unsavedChanges'] = array(
- 'ext' => 'civicrm',
- 'js' => array('bower_components/angular-unsavedChanges/dist/unsavedChanges.min.js'),
- );
-
- foreach (CRM_Core_Component::getEnabledComponents() as $component) {
- $angularModules = array_merge($angularModules, $component->getAngularModules());
- }
- CRM_Utils_Hook::angularModules($angularModules);
- $angularModules = $this->resolvePatterns($angularModules);
- return $angularModules;
- }
-
- /**
- * @param array $modules
- * List of Angular modules.
- * @return array
- * Updated list of Angular modules
- */
- public function resolvePatterns($modules) {
- $newModules = array();
-
- foreach ($modules as $moduleKey => $module) {
- foreach (array('js', 'css', 'partials') as $fileset) {
- if (!isset($module[$fileset])) {
- continue;
- }
- $module[$fileset] = $this->res->glob($module['ext'], $module[$fileset]);
+ foreach ($this->angular->getScriptUrls($moduleName) as $url) {
+ $this->res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header');
+ // addScriptUrl() bypasses the normal string-localization of addScriptFile(),
+ // but that's OK because all Angular strings (JS+HTML) will load via crmResource.
}
- $newModules[$moduleKey] = $module;
}
-
- return $newModules;
}
-
}
--- /dev/null
+<?php
+namespace Civi\Angular;
+
+/**
+ * Manage Angular resources.
+ *
+ * @package Civi\Angular
+ */
+class Manager {
+
+ /**
+ * @var \CRM_Core_Resources
+ */
+ protected $res = NULL;
+
+ /**
+ * @var array|NULL
+ * Each item has some combination of these keys:
+ * - ext: string
+ * - js: array(string $relativeFilePath)
+ * - css: array(string $relativeFilePath)
+ * - partials: array(string $relativeFilePath)
+ */
+ protected $modules = NULL;
+
+ /**
+ * @param \CRM_Core_Resources $res
+ * The resource manager.
+ */
+ public function __construct($res) {
+ $this->res = $res;
+ }
+
+ /**
+ * Get a list of AngularJS modules which should be autoloaded
+ *
+ * @return array
+ * (string $name => array('ext' => string $key, 'js' => array $paths, 'css' => array $paths))
+ */
+ public function getModules() {
+ if ($this->modules === NULL) {
+
+ $angularModules = array();
+ $angularModules['angularFileUpload'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('bower_components/angular-file-upload/angular-file-upload.min.js'),
+ );
+ $angularModules['crmApp'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('js/angular-crmApp.js'),
+ );
+ $angularModules['crmAttachment'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('js/angular-crmAttachment.js'),
+ 'css' => array('css/angular-crmAttachment.css'),
+ 'partials' => array('partials/crmAttachment/*.html'),
+ );
+ $angularModules['crmUi'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('js/angular-crm-ui.js', 'packages/ckeditor/ckeditor.js'),
+ 'partials' => array('partials/crmUi/*.html'),
+ );
+ $angularModules['crmUtil'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('js/angular-crm-util.js'),
+ );
+ // https://github.com/jwstadler/angular-jquery-dialog-service
+ $angularModules['dialogService'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('bower_components/angular-jquery-dialog-service/dialog-service.js'),
+ );
+ $angularModules['ngSanitize'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('js/angular-sanitize.js'),
+ );
+ $angularModules['ui.utils'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('bower_components/angular-ui-utils/ui-utils.min.js'),
+ );
+ $angularModules['ui.sortable'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('bower_components/angular-ui-sortable/sortable.min.js'),
+ );
+ $angularModules['unsavedChanges'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('bower_components/angular-unsavedChanges/dist/unsavedChanges.min.js'),
+ );
+
+ foreach (\CRM_Core_Component::getEnabledComponents() as $component) {
+ $angularModules = array_merge($angularModules, $component->getAngularModules());
+ }
+ \CRM_Utils_Hook::angularModules($angularModules);
+ $this->modules = $this->resolvePatterns($angularModules);
+ }
+
+ return $this->modules;
+ }
+
+ /**
+ * Get the descriptor for an Angular module.
+ *
+ * @param string $name
+ * Module name.
+ * @return array
+ * Details about the module:
+ * - ext: string, the name of the Civi extension which defines the module
+ * - js: array(string $relativeFilePath).
+ * - css: array(string $relativeFilePath).
+ * - partials: array(string $relativeFilePath).
+ * @throws \Exception
+ */
+ public function getModule($name) {
+ $modules = $this->getModules();
+ if (!isset($modules[$name])) {
+ throw new \Exception("Unrecognized Angular module");
+ }
+ return $modules[$name];
+ }
+
+ /**
+ * Convert any globs in an Angular module to file names.
+ *
+ * @param array $modules
+ * List of Angular modules.
+ * @return array
+ * Updated list of Angular modules
+ */
+ protected function resolvePatterns($modules) {
+ $newModules = array();
+
+ foreach ($modules as $moduleKey => $module) {
+ foreach (array('js', 'css', 'partials') as $fileset) {
+ if (!isset($module[$fileset])) {
+ continue;
+ }
+ $module[$fileset] = $this->res->glob($module['ext'], $module[$fileset]);
+ }
+ $newModules[$moduleKey] = $module;
+ }
+
+ return $newModules;
+ }
+
+ /**
+ * Get the partial HTML documents for a module.
+ *
+ * @param string $name
+ * Angular module name.
+ * @return array
+ * Array(string $extFilePath => string $html)
+ */
+ public function getPartials($name) {
+ $module = $this->getModule($name);
+ $result = array();
+ if (isset($module['partials'])) {
+ foreach ($module['partials'] as $file) {
+ $filename = $name . '/' . $file;
+ $result[$filename] = file_get_contents($this->res->getPath($module['ext'], $file));
+ }
+ }
+ return $result;
+ }
+
+
+ /**
+ * Get list of translated strings for a module.
+ *
+ * @param string $name
+ * Angular module name.
+ * @return array
+ * Translated strings: array(string $orig => string $translated).
+ */
+ public function getTranslatedStrings($name) {
+ $result = array();
+ $strings = $this->getStrings($name);
+ foreach ($strings as $string) {
+ // TODO: should we pass translation domain based on $module[ext] or $module[tsDomain]?
+ // It doesn't look like client side really supports the domain right now...
+ $translated = ts($string);
+ if ($translated != $string) {
+ $result[$string] = $translated;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Get list of translatable strings for a module.
+ *
+ * @param string $name
+ * Angular module name.
+ * @return array
+ * Translatable strings.
+ */
+ public function getStrings($name) {
+ $module = $this->getModule($name);
+ $result = array();
+ if (isset($module['js'])) {
+ foreach ($module['js'] as $file) {
+ $strings = $this->res->getStrings()->get(
+ $module['ext'],
+ $this->res->getPath($module['ext'], $file),
+ 'text/javascript'
+ );
+ $result = array_unique(array_merge($result, $strings));
+ }
+ }
+ if (isset($module['partials'])) {
+ foreach ($module['partials'] as $file) {
+ $strings = $this->res->getStrings()->get(
+ $module['ext'],
+ $this->res->getPath($module['ext'], $file),
+ 'text/html'
+ );
+ $result = array_unique(array_merge($result, $strings));
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $name
+ * Module name.
+ * @return array
+ * List of URLs.
+ * @throws \Exception
+ */
+ public function getScriptUrls($name) {
+ $module = $this->getModule($name);
+ $result = array();
+ if (isset($module['js'])) {
+ foreach ($module['js'] as $file) {
+ $result[] = $this->res->getUrl($module['ext'], $file, TRUE);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $name
+ * Module name.
+ * @return array
+ * List of URLs.
+ * @throws \Exception
+ */
+ public function getStyleUrls($name) {
+ $module = $this->getModule($name);
+ $result = array();
+ if (isset($module['css'])) {
+ foreach ($module['css'] as $file) {
+ $result[] = $this->res->getUrl($module['ext'], $file, TRUE);
+ }
+ }
+ return $result;
+ }
+}
// }
// }
+ $container->setDefinition('angular', new Definition(
+ '\Civi\Angular\Manager',
+ array()
+ ))
+ ->setFactoryService(self::SELF)->setFactoryMethod('createAngularManager');
+
$container->setDefinition('dispatcher', new Definition(
'\Symfony\Component\EventDispatcher\EventDispatcher',
array()
return $container;
}
+ /**
+ * @return \Civi\Angular\Manager
+ */
+ public function createAngularManager() {
+ return new \Civi\Angular\Manager(\CRM_Core_Resources::singleton());
+ }
+
/**
* @return \Symfony\Component\EventDispatcher\EventDispatcher
*/
+++ /dev/null
-<?php
-/*
- +--------------------------------------------------------------------+
- | CiviCRM version 4.6 |
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2014 |
- +--------------------------------------------------------------------+
- | This file is a part of CiviCRM. |
- | |
- | CiviCRM is free software; you can copy, modify, and distribute it |
- | under the terms of the GNU Affero General Public License |
- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
- | |
- | CiviCRM is distributed in the hope that it will be useful, but |
- | WITHOUT ANY WARRANTY; without even the implied warranty of |
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
- | See the GNU Affero General Public License for more details. |
- | |
- | You should have received a copy of the GNU Affero General Public |
- | License and the CiviCRM Licensing Exception along |
- | with this program; if not, contact CiviCRM LLC |
- | at info[AT]civicrm[DOT]org. If you have questions about the |
- | GNU Affero General Public License or the licensing of CiviCRM, |
- | see the CiviCRM license FAQ at http://civicrm.org/licensing |
- +--------------------------------------------------------------------+
-*/
-
-require_once 'CiviTest/CiviUnitTestCase.php';
-
-/**
- * Test the Angular base page.
- */
-class CRM_Core_Page_AngularTest extends CiviUnitTestCase {
-
- /**
- * @inheritDoc
- */
- protected function setUp() {
- $this->useTransaction(TRUE);
- parent::setUp();
- }
-
- /**
- * Ensure that valid partials appear on the example module (crmUi).
- */
- public function testPartialPattern() {
- $this->createLoggedInUser();
- $page = new CRM_Core_Page_Angular();
- $angularModules = $page->getAngularModules();
- $matches = preg_grep(':/tabset.html$:', $angularModules['crmUi']['partials']);
- $this->assertTrue(count($matches) > 0,
- 'Expect to find example tabset.html. If it has been reorganized, then update this test with a different example.');
- }
-}
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.6 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2014 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+*/
+
+namespace Civi\Angular;
+
+require_once 'CiviTest/CiviUnitTestCase.php';
+
+/**
+ * Test the Angular base page.
+ */
+class ManagerTest extends \CiviUnitTestCase {
+
+ /**
+ * @var Manager
+ */
+ protected $angular;
+
+ /**
+ * @var \CRM_Core_Resources
+ */
+ protected $res;
+
+ /**
+ * @inheritDoc
+ */
+ protected function setUp() {
+ $this->useTransaction(TRUE);
+ parent::setUp();
+ $this->createLoggedInUser();
+ $this->res = \CRM_Core_Resources::singleton();
+ $this->angular = new Manager($this->res);
+ }
+
+ /**
+ * Modules appear to be well-defined.
+ */
+ public function testGetModules() {
+ $modules = $this->angular->getModules();
+
+ $counts = array(
+ 'js' => 0,
+ 'css' => 0,
+ 'partials' => 0,
+ );
+
+ foreach ($modules as $module) {
+ $this->assertTrue(is_array($module));
+ $this->assertTrue(is_string($module['ext']));
+ if (isset($module['js'])) {
+ $this->assertTrue(is_array($module['js']));
+ foreach ($module['js'] as $file) {
+ $this->assertTrue(file_exists($this->res->getPath($module['ext'], $file)));
+ $counts['js']++;
+ }
+ }
+ if (isset($module['css'])) {
+ $this->assertTrue(is_array($module['css']));
+ foreach ($module['css'] as $file) {
+ $this->assertTrue(file_exists($this->res->getPath($module['ext'], $file)));
+ $counts['css']++;
+ }
+ }
+ if (isset($module['partials'])) {
+ $this->assertTrue(is_array($module['partials']));
+ foreach ($module['partials'] as $file) {
+ $this->assertTrue(file_exists($this->res->getPath($module['ext'], $file)));
+ $counts['partials']++;
+ }
+ }
+ }
+
+ $this->assertTrue($counts['js'] > 0, 'Expect to find at least one JS file');
+ $this->assertTrue($counts['css'] > 0, 'Expect to find at least one CSS file');
+ $this->assertTrue($counts['partials'] > 0, 'Expect to find at least one partial HTML file');
+ }
+
+ /**
+ * Get HTML fragments from an example module.
+ */
+ public function testGetPartials() {
+ $partials = $this->angular->getPartials('crmMailing');
+ $this->assertRegExp('/\<form.*name="crmMailing"/', $partials['crmMailing/partials/crmMailing/edit.html']);
+ // If crmMailing changes, feel free to use a different example.
+ }
+
+ /**
+ * Get a translatable string from an example module.
+ */
+ public function testGetStrings() {
+ $strings = $this->angular->getStrings('crmMailing');
+ $this->assertTrue(in_array('Save Draft', $strings));
+ // If crmMailing changes, feel free to use a different example.
+ }
+}