CRM-15832 - CRM_Core_Resources - Move translateScript to class. Add HTML support.
authorTim Otten <totten@civicrm.org>
Fri, 16 Jan 2015 04:05:40 +0000 (20:05 -0800)
committerTim Otten <totten@civicrm.org>
Sat, 17 Jan 2015 03:38:15 +0000 (19:38 -0800)
For the moment, it looks like the existing JS string-extractor actually
works for Angular-style HTML partials.  This is not entirely a coincidence
(since our Angular convention was purposefully derived from our JS
convention), but luck/circumstance is a big factor, and this may not work in
the long-run.  This commit makes it easy to use different string-extractors
for different file-types.

CRM/Core/Resources.php
CRM/Core/Resources/Strings.php [new file with mode: 0644]
tests/phpunit/CRM/Core/Resources/StringsTest.php [new file with mode: 0644]
tests/phpunit/CRM/Utils/HTMLTest.php [new file with mode: 0644]

index dd0f937618b258d48728771d55358a96102351e4..7707c12652f2006ee6b58caa86cd2440829b89e9 100644 (file)
@@ -58,9 +58,9 @@ class CRM_Core_Resources {
   private $extMapper = NULL;
 
   /**
-   * @var CRM_Utils_Cache_Interface
+   * @var CRM_Core_Resources_Strings
    */
-  private $cache = NULL;
+  private $strings = NULL;
 
   /**
    * @var array free-form data tree
@@ -135,7 +135,7 @@ class CRM_Core_Resources {
    */
   public function __construct($extMapper, $cache, $cacheCodeKey = NULL) {
     $this->extMapper = $extMapper;
-    $this->cache = $cache;
+    $this->strings = new CRM_Core_Resources_Strings($cache);
     $this->cacheCodeKey = $cacheCodeKey;
     if ($cacheCodeKey !== NULL) {
       $this->cacheCode = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, $cacheCodeKey);
@@ -165,7 +165,9 @@ class CRM_Core_Resources {
    */
   public function addScriptFile($ext, $file, $weight = self::DEFAULT_WEIGHT, $region = self::DEFAULT_REGION, $translate = TRUE) {
     if ($translate) {
-      $this->translateScript($ext, $file);
+      // For each extension, maintain one cache record which
+      // includes parsed (translatable) strings for all its files.
+      $this->addString($this->strings->get($ext, $this->getPath($ext, $file), 'text/javascript'));
     }
     // Look for non-minified version if we are in debug mode
     if (CRM_Core_Config::singleton()->debug && strpos($file, '.min.js') !== FALSE) {
@@ -579,37 +581,15 @@ class CRM_Core_Resources {
    * @return CRM_Core_Resources
    */
   public function flushStrings() {
-    $this->cache->flush();
+    $this->strings->flush();
     return $this;
   }
 
   /**
-   * Translate strings in a javascript file
-   *
-   * @param string $ext
-   *   extension name.
-   * @param string $file
-   *   file path.
-   * @return void
-   */
-  private function translateScript($ext, $file) {
-    // For each extension, maintain one cache record which
-    // includes parsed (translatable) strings for all its JS files.
-    $stringsByFile = $this->cache->get($ext); // array($file => array(...strings...))
-    if (!$stringsByFile) {
-      $stringsByFile = array();
-    }
-    if (!isset($stringsByFile[$file])) {
-      $filePath = $this->getPath($ext, $file);
-      if ($filePath && is_readable($filePath)) {
-        $stringsByFile[$file] = CRM_Utils_JS::parseStrings(file_get_contents($filePath));
-      }
-      else {
-        $stringsByFile[$file] = array();
-      }
-      $this->cache->set($ext, $stringsByFile);
-    }
-    $this->addString($stringsByFile[$file]);
+   * @return CRM_Core_Resources_Strings
+   */
+  public function getStrings() {
+    return $this->strings;
   }
 
   /**
diff --git a/CRM/Core/Resources/Strings.php b/CRM/Core/Resources/Strings.php
new file mode 100644 (file)
index 0000000..2ac048e
--- /dev/null
@@ -0,0 +1,110 @@
+<?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        |
+ +--------------------------------------------------------------------+
+*/
+
+/**
+ * Manage translatable strings on behalf of resource files.
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2014
+ * $Id$
+ */
+class CRM_Core_Resources_Strings {
+
+  /**
+   * @var CRM_Utils_Cache_Interface|NULL
+   */
+  private $cache = NULL;
+
+  /**
+   * @param CRM_Utils_Cache_Interface $cache
+   *   Localization cache.
+   */
+  public function __construct($cache) {
+    $this->cache = $cache;
+  }
+
+  /**
+   * Flush the cache of translated strings.
+   */
+  public function flush() {
+    $this->cache->flush();
+  }
+
+  /**
+   * Get the strings from a file, using a cache if available.
+   *
+   * @param string $bucket
+   *   The name of a cache-row which includes strings for this file.
+   * @param string $file
+   *   File path.
+   * @param string $format
+   *   Type of file (e.g. 'text/javascript', 'text/html').
+   * @return array
+   *   List of translatable strings.
+   */
+  public function get($bucket, $file, $format) {
+    $stringsByFile = $this->cache->get($bucket); // array($file => array(...strings...))
+    if (!$stringsByFile) {
+      $stringsByFile = array();
+    }
+    if (!isset($stringsByFile[$file])) {
+      if ($file && is_readable($file)) {
+        $stringsByFile[$file] = $this->extract($file, $format);
+      }
+      else {
+        $stringsByFile[$file] = array();
+      }
+      $this->cache->set($bucket, $stringsByFile);
+    }
+    return $stringsByFile[$file];
+  }
+
+  /**
+   * Extract a list of strings from a file.
+   *
+   * @param string $file
+   *   File path.
+   * @param string $format
+   *   Type of file (e.g. 'text/javascript', 'text/html').
+   * @return array
+   *   List of translatable strings.
+   * @throws Exception
+   */
+  public function extract($file, $format) {
+    switch ($format) {
+      case 'text/javascript':
+        return CRM_Utils_JS::parseStrings(file_get_contents($file));
+
+      case 'text/html':
+        // Magic! The JS parser works with HTML! See CRM_Utils_HTMLTest.
+        return CRM_Utils_JS::parseStrings(file_get_contents($file));
+
+      default:
+        throw new Exception("Cannot extract strings: Unrecognized file type.");
+    }
+  }
+}
diff --git a/tests/phpunit/CRM/Core/Resources/StringsTest.php b/tests/phpunit/CRM/Core/Resources/StringsTest.php
new file mode 100644 (file)
index 0000000..730c2c9
--- /dev/null
@@ -0,0 +1,63 @@
+<?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';
+
+/**
+ * Tests for parsing translatable strings in HTML content.
+ */
+class CRM_Core_Resources_StringsTest extends CiviUnitTestCase {
+
+  /**
+   * Get strings from files.
+   */
+  public function testGet() {
+    $basedir = $this->createExamples();
+    $strings = new CRM_Core_Resources_Strings(
+      new CRM_Utils_Cache_Arraycache(NULL)
+    );
+    $this->assertEquals(
+      array('Hello from Javascript'),
+      $strings->get('example', "$basedir/hello.js", "text/javascript")
+    );
+    $this->assertEquals(
+      array('Hello from HTML'),
+      $strings->get('example', "$basedir/hello.html", "text/html")
+    );
+  }
+
+  /**
+   * @return string
+   *   Path to the example dir.
+   */
+  public function createExamples() {
+    $basedir = rtrim($this->createTempDir('ext-'), '/');
+    file_put_contents("$basedir/hello.js", "alert(ts('Hello from Javascript'));");
+    file_put_contents("$basedir/hello.html", "<div>{{ts('Hello from HTML')}}</div>");
+    return $basedir;
+  }
+}
diff --git a/tests/phpunit/CRM/Utils/HTMLTest.php b/tests/phpunit/CRM/Utils/HTMLTest.php
new file mode 100644 (file)
index 0000000..7e24fa9
--- /dev/null
@@ -0,0 +1,125 @@
+<?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';
+
+/**
+ * Tests for parsing translatable strings in HTML content.
+ */
+class CRM_Utils_HTMLTest extends CiviUnitTestCase {
+  /**
+   * @return array
+   */
+  public function translateExamples() {
+    $cases = array();
+    $cases[] = array(
+      '',
+      array(),
+    );
+    $cases[] = array(// missing ts
+      '<div>Hello world</div>',
+      array(),
+    );
+    $cases[] = array(// text, no arg
+      '<div>{{ts("Hello world")}}</div>',
+      array('Hello world'),
+    );
+    $cases[] = array(// text, no arg, alternate text
+      '<div>{{ts("Good morning, Dave")}}</div>',
+      array('Good morning, Dave'),
+    );
+    $cases[] = array(// text, with arg
+      '<div>{{ts("Hello world", {1: "whiz"})}}</div>',
+      array('Hello world'),
+    );
+    $cases[] = array(// text, not really ts(), no arg
+      '<div>{{clients("Hello world")}}</div>',
+      array(),
+    );
+    $cases[] = array(// text, not really ts(), with arg
+      '<div>{{clients("Hello world", {1: "whiz"})}}</div>',
+      array(),
+    );
+    $cases[] = array(// two strings, duplicate
+      '<div>{{ts("Hello world")}}</div> <p>{{ts("Hello world")}}</p>',
+      array('Hello world'),
+    );
+    $cases[] = array(// two strings, addition
+      '<div>{{ts("Hello world") + "-" + ts("How do you do?")}}</p>',
+      array('Hello world', 'How do you do?'),
+    );
+    $cases[] = array(// two strings, separate calls
+      '<div>{{ts("Hello world")}}</div> <p>{{ts("How do you do?")}}</p>',
+      array('Hello world', 'How do you do?'),
+    );
+    $cases[] = array(// single quoted
+      '<div>{{ts(\'Hello world\')}}</div>',
+      array('Hello world'),
+    );
+    $cases[] = array(// unclear string
+      '<div>{{ts(message)}}</div>',
+      array(),
+    );
+    $cases[] = array(// ts() within a string
+      '<div>{{ts("Does the ts(\'example\') notation work?")}}</div>',
+      array('Does the ts(\'example\') notation work?'),
+    );
+    $cases[] = array(// attribute, no arg
+      '<div crm-title="ts("Hello world")"></div>',
+      array('Hello world'),
+    );
+    $cases[] = array(// attribute, with arg
+      '<div crm-title="ts("Hello world", {1: "whiz"})"></div>',
+      array('Hello world'),
+    );
+    $cases[] = array(// attribute, two strings, with arg
+      '<div crm-title="ts("Hello world", {1: "whiz"}) + ts("How do you do, %1?", {2: "funky"})"></div>',
+      array('Hello world', 'How do you do, %1?'),
+    );
+    $cases[] = array(// trick question! Not used on Smarty templates.
+      '<div>{ts}Hello world{/ts}</div>',
+      array(),
+    );
+
+    return $cases;
+  }
+
+  /**
+   * @param string $html
+   *   Example HTML input.
+   * @param array $expectedStrings
+   *   List of expected strings.
+   * @dataProvider translateExamples
+   */
+  public function testParseStrings($html, $expectedStrings) {
+    // Magic! The JS parser works with HTML!
+    $actualStrings = CRM_Utils_JS::parseStrings($html);
+    sort($expectedStrings);
+    sort($actualStrings);
+    $this->assertEquals($expectedStrings, $actualStrings);
+  }
+}