Extension.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\twig;
  8. use yii\base\InvalidCallException;
  9. use yii\helpers\Inflector;
  10. use yii\helpers\StringHelper;
  11. use yii\helpers\Url;
  12. use yii\web\AssetBundle;
  13. /**
  14. * Extension provides Yii-specific syntax for Twig templates.
  15. *
  16. * @author Andrey Grachov <andrey.grachov@gmail.com>
  17. * @author Alexander Makarov <sam@rmcreative.ru>
  18. */
  19. class Extension extends \Twig_Extension
  20. {
  21. /**
  22. * @var array used namespaces
  23. */
  24. protected $namespaces = [];
  25. /**
  26. * @var array used class aliases
  27. */
  28. protected $aliases = [];
  29. /**
  30. * @var array used widgets
  31. */
  32. protected $widgets = [];
  33. /**
  34. * Creates new instance
  35. *
  36. * @param array $uses namespaces and classes to use in the template
  37. */
  38. public function __construct(array $uses = [])
  39. {
  40. $this->addUses($uses);
  41. }
  42. /**
  43. * @inheritdoc
  44. */
  45. public function getNodeVisitors()
  46. {
  47. return [
  48. new Optimizer(),
  49. new GetAttrAdjuster(),
  50. ];
  51. }
  52. /**
  53. * @inheritdoc
  54. */
  55. public function getFunctions()
  56. {
  57. $options = [
  58. 'is_safe' => ['html'],
  59. ];
  60. $functions = [
  61. new \Twig_SimpleFunction('use', [$this, 'addUses'], $options),
  62. new \Twig_SimpleFunction('*_begin', [$this, 'beginWidget'], $options),
  63. new \Twig_SimpleFunction('*_end', [$this, 'endWidget'], $options),
  64. new \Twig_SimpleFunction('widget_end', [$this, 'endWidget'], $options),
  65. new \Twig_SimpleFunction('*_widget', [$this, 'widget'], $options),
  66. new \Twig_SimpleFunction('path', [$this, 'path']),
  67. new \Twig_SimpleFunction('url', [$this, 'url']),
  68. new \Twig_SimpleFunction('void', function(){}),
  69. new \Twig_SimpleFunction('set', [$this, 'setProperty']),
  70. ];
  71. $options = array_merge($options, [
  72. 'needs_context' => true,
  73. ]);
  74. $functions[] = new \Twig_SimpleFunction('register_*', [$this, 'registerAsset'], $options);
  75. $functions[] = new \Twig_SimpleFunction('register_asset_bundle', [$this, 'registerAssetBundle'], $options);
  76. foreach (['begin_page', 'end_page', 'begin_body', 'end_body', 'head'] as $helper) {
  77. $functions[] = new \Twig_SimpleFunction($helper, [$this, 'viewHelper'], $options);
  78. }
  79. return $functions;
  80. }
  81. /**
  82. * Function for registering an asset
  83. *
  84. * ```
  85. * {{ use('yii/web/JqueryAsset') }}
  86. * {{ register_jquery_asset() }}
  87. * ```
  88. *
  89. * @param array $context context information
  90. * @param string $asset asset name
  91. * @return mixed
  92. */
  93. public function registerAsset($context, $asset)
  94. {
  95. return $this->resolveAndCall($asset, 'register', [
  96. isset($context['this']) ? $context['this'] : null,
  97. ]);
  98. }
  99. /**
  100. * Function for additional syntax of registering asset bundles
  101. *
  102. * ```
  103. * {{ register_asset_bundle('yii/web/JqueryAsset') }}
  104. * ```
  105. *
  106. * @param array $context context information
  107. * @param string $bundle asset bundle class fully qualified name
  108. * @param bool $return indicates if AssetBundle should be returned
  109. *
  110. * @return void|AssetBundle
  111. * @since 2.0.4
  112. */
  113. public function registerAssetBundle($context, $bundle, $return = false)
  114. {
  115. $bundle = str_replace('/', '\\', $bundle);
  116. $bundle = $this->call($bundle, 'register', [
  117. isset($context['this']) ? $context['this'] : null,
  118. ]);
  119. if ($return) {
  120. return $bundle;
  121. }
  122. }
  123. /**
  124. * Function for *_begin syntax support
  125. *
  126. * @param string $widget widget name
  127. * @param array $config widget config
  128. * @return mixed
  129. */
  130. public function beginWidget($widget, $config = [])
  131. {
  132. $widget = $this->resolveClassName($widget);
  133. $this->widgets[] = $widget;
  134. return $this->call($widget, 'begin', [
  135. $config,
  136. ]);
  137. }
  138. /**
  139. * Function for *_end syntax support
  140. *
  141. * @param string $widget widget name
  142. */
  143. public function endWidget($widget = null)
  144. {
  145. if ($widget === null) {
  146. if (empty($this->widgets)) {
  147. throw new InvalidCallException('Unexpected end_widget() call. A matching begin_widget() is not found.');
  148. }
  149. $this->call(array_pop($this->widgets), 'end');
  150. } else {
  151. array_pop($this->widgets);
  152. $this->resolveAndCall($widget, 'end');
  153. }
  154. }
  155. /**
  156. * Function for *_widget syntax support
  157. *
  158. * @param string $widget widget name
  159. * @param array $config widget config
  160. * @return mixed
  161. */
  162. public function widget($widget, $config = [])
  163. {
  164. return $this->resolveAndCall($widget, 'widget', [
  165. $config,
  166. ]);
  167. }
  168. /**
  169. * Used for 'begin_page', 'end_page', 'begin_body', 'end_body', 'head'
  170. *
  171. * @param array $context context information
  172. * @param string $name
  173. */
  174. public function viewHelper($context, $name = null)
  175. {
  176. if ($name !== null && isset($context['this'])) {
  177. $this->call($context['this'], Inflector::variablize($name));
  178. }
  179. }
  180. /**
  181. * Resolves a method from widget and asset syntax and calls it
  182. *
  183. * @param string $className class name
  184. * @param string $method method name
  185. * @param array $arguments
  186. * @return mixed
  187. */
  188. public function resolveAndCall($className, $method, $arguments = null)
  189. {
  190. return $this->call($this->resolveClassName($className), $method, $arguments);
  191. }
  192. /**
  193. * Calls a method
  194. *
  195. * @param string $className class name
  196. * @param string $method method name
  197. * @param array $arguments
  198. * @return mixed
  199. */
  200. public function call($className, $method, $arguments = null)
  201. {
  202. $callable = [$className, $method];
  203. if ($arguments === null) {
  204. return call_user_func($callable);
  205. } else {
  206. return call_user_func_array($callable, $arguments);
  207. }
  208. }
  209. /**
  210. * Resolves class name from widget and asset syntax
  211. *
  212. * @param string $className class name
  213. * @return string
  214. */
  215. public function resolveClassName($className)
  216. {
  217. $className = Inflector::id2camel($className, '_');
  218. if (isset($this->aliases[$className])) {
  219. return $this->aliases[$className];
  220. }
  221. foreach ($this->namespaces as $namespace) {
  222. $resolvedClassName = $namespace . '\\' . $className;
  223. if (class_exists($resolvedClassName)) {
  224. return $this->aliases[$className] = $resolvedClassName;
  225. }
  226. }
  227. return $className;
  228. }
  229. /**
  230. * Adds namespaces and aliases from constructor
  231. *
  232. * @param array $args namespaces and classes to use in the template
  233. */
  234. public function addUses($args)
  235. {
  236. foreach ((array)$args as $key => $value) {
  237. $value = str_replace('/', '\\', $value);
  238. if (is_int($key)) {
  239. // namespace or class import
  240. if (class_exists($value)) {
  241. // class import
  242. $this->aliases[StringHelper::basename($value)] = $value;
  243. } else {
  244. // namespace
  245. $this->namespaces[] = $value;
  246. }
  247. } else {
  248. // aliased class import
  249. $this->aliases[$key] = $value;
  250. }
  251. }
  252. }
  253. /**
  254. * Generates relative URL
  255. *
  256. * @param string $path the parameter to be used to generate a valid URL
  257. * @param array $args arguments
  258. * @return string the generated relative URL
  259. */
  260. public function path($path, $args = [])
  261. {
  262. if (is_array($path)) {
  263. $path = array_merge($path, $args);
  264. } elseif ($args !== []) {
  265. $path = array_merge([$path], $args);
  266. }
  267. return Url::to($path);
  268. }
  269. /**
  270. * Generates absolute URL
  271. *
  272. * @param string $path the parameter to be used to generate a valid URL
  273. * @param array $args arguments
  274. * @return string the generated absolute URL
  275. */
  276. public function url($path, $args = [])
  277. {
  278. if (is_array($path)) {
  279. $path = array_merge($path, $args);
  280. } elseif ($args !== []) {
  281. $path = array_merge([$path], $args);
  282. }
  283. return Url::to($path, true);
  284. }
  285. /**
  286. * Sets object property
  287. *
  288. * @param \stdClass $object
  289. * @param string $property
  290. * @param mixed $value
  291. */
  292. public function setProperty($object, $property, $value)
  293. {
  294. $object->$property = $value;
  295. }
  296. /**
  297. * @inheritdoc
  298. */
  299. public function getName()
  300. {
  301. return 'yii2-twig';
  302. }
  303. }