Code.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\g11n\catalog\adapter;
  9. use RecursiveIteratorIterator;
  10. use RecursiveDirectoryIterator;
  11. use lithium\core\ConfigException;
  12. use lithium\template\view\Compiler;
  13. /**
  14. * The `Code` class is an adapter which treats files containing code as just another source
  15. * of globalized data.
  16. *
  17. * In fact it allows for extracting messages which are needed to build
  18. * message catalog templates. Currently only code written in PHP is supported through a parser
  19. * using the built-in tokenizer.
  20. *
  21. * @see lithium\g11n\Message
  22. * @see lithium\template\View
  23. */
  24. class Code extends \lithium\g11n\catalog\Adapter {
  25. /**
  26. * Constructor.
  27. *
  28. * @param array $config Available configuration options are:
  29. * - `'path'`: The path to the directory holding the data.
  30. * - `'scope'`: Scope to use.
  31. */
  32. public function __construct(array $config = array()) {
  33. $defaults = array('path' => null, 'scope' => null);
  34. parent::__construct($config + $defaults);
  35. }
  36. /**
  37. * Initializer. Checks if the configured path exists.
  38. *
  39. * @return void
  40. * @throws \Exception
  41. */
  42. protected function _init() {
  43. parent::_init();
  44. if (!is_dir($this->_config['path'])) {
  45. $message = "Code directory does not exist at path `{$this->_config['path']}`.";
  46. throw new ConfigException($message);
  47. }
  48. }
  49. /**
  50. * Reads data.
  51. *
  52. * @param string $category A category. `'messageTemplate'` is the only category supported.
  53. * @param string $locale A locale identifier.
  54. * @param string $scope The scope for the current operation.
  55. * @return array Returns the message template. If the scope is not equal to the current scope
  56. * or `$category` is not `'messageTemplate'` null is returned.
  57. */
  58. public function read($category, $locale, $scope) {
  59. if ($scope !== $this->_config['scope']) {
  60. return null;
  61. }
  62. $path = $this->_config['path'];
  63. switch ($category) {
  64. case 'messageTemplate':
  65. return $this->_readMessageTemplate($path);
  66. default:
  67. return null;
  68. }
  69. }
  70. /**
  71. * Extracts data from files within configured path recursively.
  72. *
  73. * @param string $path Base path to start extracting from.
  74. * @return array
  75. */
  76. protected function _readMessageTemplate($path) {
  77. $base = new RecursiveDirectoryIterator($path);
  78. $iterator = new RecursiveIteratorIterator($base);
  79. $data = array();
  80. foreach ($iterator as $item) {
  81. $file = $item->getPathname();
  82. switch (pathinfo($file, PATHINFO_EXTENSION)) {
  83. case 'php':
  84. $data += $this->_parsePhp($file);
  85. break;
  86. }
  87. }
  88. return $data;
  89. }
  90. /**
  91. * Parses a PHP file for messages marked as translatable. Recognized as message
  92. * marking are `$t()` and `$tn()` which are implemented in the `View` class. This
  93. * is a rather simple and stupid parser but also fast and easy to grasp. It doesn't
  94. * actively attempt to detect and work around syntax errors in marker functions.
  95. *
  96. * @see lithium\g11n\Message::aliases()
  97. * @param string $file Absolute path to a PHP file.
  98. * @return array
  99. */
  100. protected function _parsePhp($file) {
  101. $contents = file_get_contents($file);
  102. $contents = Compiler::compile($contents);
  103. $defaults = array(
  104. 'ids' => array(),
  105. 'open' => false,
  106. 'position' => 0,
  107. 'occurrence' => array('file' => $file, 'line' => null)
  108. );
  109. extract($defaults);
  110. $data = array();
  111. if (strpos($contents, '$t(') === false && strpos($contents, '$tn(') === false) {
  112. return $data;
  113. }
  114. $tokens = token_get_all($contents);
  115. unset($contents);
  116. foreach ($tokens as $key => $token) {
  117. if (!is_array($token)) {
  118. $token = array(0 => null, 1 => $token, 2 => null);
  119. }
  120. if ($open) {
  121. if ($position >= ($open === 'singular' ? 1 : 2)) {
  122. $data = $this->_merge($data, array(
  123. 'id' => $ids['singular'],
  124. 'ids' => $ids,
  125. 'occurrences' => array($occurrence)
  126. ));
  127. extract($defaults, EXTR_OVERWRITE);
  128. } elseif ($token[0] === T_CONSTANT_ENCAPSED_STRING) {
  129. $ids[$ids ? 'plural' : 'singular'] = $token[1];
  130. $position++;
  131. }
  132. } else {
  133. if (isset($tokens[$key + 1]) && $tokens[$key + 1] === '(') {
  134. if ($token[1] === '$t') {
  135. $open = 'singular';
  136. } elseif ($token[1] === '$tn') {
  137. $open = 'plural';
  138. } else {
  139. continue;
  140. }
  141. $occurrence['line'] = $token[2];
  142. }
  143. }
  144. }
  145. return $data;
  146. }
  147. /**
  148. * Merges an item into given data and removes quotation marks
  149. * from the beginning and end of message strings.
  150. *
  151. * @see lithium\g11n\catalog\Adapter::_merge()
  152. * @param array $data Data to merge item into.
  153. * @param array $item Item to merge into $data.
  154. * @return array The merged data.
  155. */
  156. protected function _merge(array $data, array $item) {
  157. $filter = function ($value) use (&$filter) {
  158. if (is_array($value)) {
  159. return array_map($filter, $value);
  160. }
  161. return substr($value, 1, -1);
  162. };
  163. $fields = array('id', 'ids', 'translated');
  164. foreach ($fields as $field) {
  165. if (isset($item[$field])) {
  166. $item[$field] = $filter($item[$field]);
  167. }
  168. }
  169. return parent::_merge($data, $item);
  170. }
  171. }
  172. ?>