CRM-15832 - getAngularModules - Move from CRM_Core_Page_Angular to Civi\Angular\Manager
authorTim Otten <totten@civicrm.org>
Fri, 16 Jan 2015 06:28:28 +0000 (22:28 -0800)
committerTim Otten <totten@civicrm.org>
Sat, 17 Jan 2015 12:16:06 +0000 (04:16 -0800)
CRM/Core/Page/Angular.php
Civi/Angular/Manager.php [new file with mode: 0644]
Civi/Core/Container.php
tests/phpunit/CRM/Core/Page/AngularTest.php [deleted file]
tests/phpunit/Civi/Angular/ManagerTest.php [new file with mode: 0644]

index 3bdac40234e47e133ffaf4fd727af37be98284b3..2e950361f2c13f4b14ea130fbded6c457eef0208 100644 (file)
@@ -17,6 +17,12 @@ class CRM_Core_Page_Angular extends CRM_Core_Page {
    */
   protected $res;
 
+
+  /**
+   * @var Civi\Angular\Manager
+   */
+  protected $angular;
+
   /**
    * @param string $title
    *   Title of the page.
@@ -28,6 +34,7 @@ class CRM_Core_Page_Angular extends CRM_Core_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');
   }
 
   /**
@@ -47,7 +54,7 @@ class CRM_Core_Page_Angular extends CRM_Core_Page {
    * 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
@@ -65,101 +72,15 @@ class CRM_Core_Page_Angular extends CRM_Core_Page {
     $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;
   }
-
 }
diff --git a/Civi/Angular/Manager.php b/Civi/Angular/Manager.php
new file mode 100644 (file)
index 0000000..852a0e6
--- /dev/null
@@ -0,0 +1,256 @@
+<?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;
+  }
+}
index fa56a554c7ff86840275021cc06829b29d44cdb7..4fc052b79bc5df7d491af0a5064ff4fccb861640 100644 (file)
@@ -66,6 +66,12 @@ class Container {
     //      }
     //    }
 
+    $container->setDefinition('angular', new Definition(
+      '\Civi\Angular\Manager',
+      array()
+    ))
+      ->setFactoryService(self::SELF)->setFactoryMethod('createAngularManager');
+
     $container->setDefinition('dispatcher', new Definition(
       '\Symfony\Component\EventDispatcher\EventDispatcher',
       array()
@@ -86,6 +92,13 @@ class Container {
     return $container;
   }
 
+  /**
+   * @return \Civi\Angular\Manager
+   */
+  public function createAngularManager() {
+    return new \Civi\Angular\Manager(\CRM_Core_Resources::singleton());
+  }
+
   /**
    * @return \Symfony\Component\EventDispatcher\EventDispatcher
    */
diff --git a/tests/phpunit/CRM/Core/Page/AngularTest.php b/tests/phpunit/CRM/Core/Page/AngularTest.php
deleted file mode 100644 (file)
index f0b88f9..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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.');
-  }
-}
diff --git a/tests/phpunit/Civi/Angular/ManagerTest.php b/tests/phpunit/Civi/Angular/ManagerTest.php
new file mode 100644 (file)
index 0000000..28ce0d5
--- /dev/null
@@ -0,0 +1,118 @@
+<?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.
+  }
+}