Browse Source

Fixes #4553: Smarty enhancements

tags/2.0.0-rc
Alexander Makarov 10 years ago
parent
commit
739f3b13e4
  1. 174
      docs/guide/tutorial-template-engines.md
  2. 22
      extensions/smarty/CHANGELOG.md
  3. 429
      extensions/smarty/Extension.php
  4. 220
      extensions/smarty/ViewRenderer.php
  5. 45
      tests/unit/extensions/smarty/ViewRendererTest.php
  6. 3
      tests/unit/extensions/smarty/views/changeTitle.tpl
  7. 5
      tests/unit/extensions/smarty/views/extends1.tpl
  8. 5
      tests/unit/extensions/smarty/views/extends2.tpl
  9. 5
      tests/unit/extensions/smarty/views/extends3.tpl
  10. 9
      tests/unit/extensions/smarty/views/form.tpl
  11. 16
      tests/unit/extensions/smarty/views/layout.tpl

174
docs/guide/tutorial-template-engines.md

@ -106,9 +106,9 @@ Aliased class import:
{{ use({'alias' => '/app/widgets/MyWidget'}) }}
```
#### Referencing other views
#### Referencing other templates
There are two ways of referencing views in `include` and `extends` statements:
There are two ways of referencing templates in `include` and `extends` statements:
```
{% include "comment.twig" %}
@ -118,8 +118,8 @@ There are two ways of referencing views in `include` and `extends` statements:
{% extends "@app/views/layouts/2columns.twig" %}
```
In the first case the view will be searched relatively to the path current view is in. For `comment.twig` and `post.twig`
that means these will be searched in the same directory as the view that's rendered currently.
In the first case the view will be searched relatively to the current template path. For `comment.twig` and `post.twig`
that means these will be searched in the same directory as the currently rendered template.
In the second case we're using path aliases. All the Yii aliases such as `@app` are available by default.
@ -264,8 +264,9 @@ Then in the template you can apply filter using the following syntax:
Smarty
------
To use Smarty, you need to create templates in files that have the `.tpl` extension (or use another file extension but configure the component accordingly). Unlike standard view files, when using Smarty you must include the extension in your `$this->render()`
or `$this->renderPartial()` controller calls:
To use Smarty, you need to create templates in files that have the `.tpl` extension (or use another file extension but
configure the component accordingly). Unlike standard view files, when using Smarty you must include the extension in
your `$this->render()` or `$this->renderPartial()` controller calls:
```php
return $this->render('renderer.tpl', ['username' => 'Alex']);
@ -277,20 +278,173 @@ The best resource to learn Smarty template syntax is its official documentation
[www.smarty.net](http://www.smarty.net/docs/en/). Additionally there are Yii-specific syntax extensions
described below.
#### Additional functions
#### Setting object properties
There's a special function called `set` that allows you to set common properties of the view and controller. Currently
available properties are `title`, `theme` and `layout`:
```
{set title="My Page"}
{set theme="frontend"}
{set layout="main.tpl"}
```
For title there's dedicated block as well:
```
{title}My Page{/title}
```
#### Setting meta tags
Meta tags could be set like to following:
```
{meta keywords="Yii,PHP,Smarty,framework"}
```
There's also dedicated block for description:
```
{description}This is my page about Smarty extension{/description}
```
#### Calling object methods
Sometimes you need calling
#### Importing static classes, using widgets as functions and blocks
You can import additional static classes right in the template:
```
{use class="yii\helpers\Html"}
{Html::mailto('eugenia@example.com')}
```
If you want you can set custom alias:
```
{use class="yii\helpers\Html" as="Markup"}
{Markup::mailto('eugenia@example.com')}
```
Extension helps using widgets in convenient way converting their syntax to function calls or blocks. For regular widgets
function could be used like the following:
```
{use class='@yii\grid\GridView' type='function'}
{GridView dataProvider=$provider}
```
For widgets with `begin` and `end` methods such as ActiveForm it's better to use block:
```
{use class='yii\widgets\ActiveForm' type='block'}
{ActiveForm assign='form' id='login-form' action='/form-handler' options=['class' => 'form-horizontal']}
{$form->field($model, 'firstName')}
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</div>
{/ActiveForm}
```
If you're using particular widget a lot, it is a good idea to declare it in application config and remove `{use class`
call from templates:
Yii adds the following construct to the standard Smarty syntax:
```php
'components' => [
'view' => [
// ...
'renderers' => [
'tpl' => [
'class' => 'yii\smarty\ViewRenderer',
'widgets' => [
'blocks' => [
'ActiveForm' => '\yii\widgets\ActiveForm',
],
],
],
],
],
],
```
#### Referencing other templates
There are two main ways of referencing templates in `include` and `extends` statements:
```
{include 'comment.tpl'}
{extends 'post.tpl'}
{include '@app/views/snippets/avatar.tpl'}
{extends '@app/views/layouts/2columns.tpl'}
```
In the first case the view will be searched relatively to the current template path. For `comment.tpl` and `post.tpl`
that means these will be searched in the same directory as the currently rendered template.
In the second case we're using path aliases. All the Yii aliases such as `@app` are available by default.
#### CSS, JavaScript and asset bundles
In order to register JavaScript and CSS files the following syntax could be used:
```
{registerJsFile url='http://maps.google.com/maps/api/js?sensor=false' position='POS_END'}
{registerCssFile url='@assets/css/normalizer.css'}
```
If you need JavaScript and CSS directly in the template there are convenient blocks:
```
{registerJs key='show' position='POS_LOAD'}
$("span.show").replaceWith('<div class="show">');
{/registerJs}
{registerCss}
div.header {
background-color: #3366bd;
color: white;
}
{/registerCss}
```
Asset bundles could be registered the following way:
```
{use class="yii\web\JqueryAsset"}
{JqueryAsset::register($this)|void}
```
Here we're using `void` modifier because we don't need method call result.
#### URLs
There are two functions you can use for building URLs:
```php
<a href="{path route='blog/view' alias=$post.alias}">{$post.title}</a>
<a href="{url route='blog/view' alias=$post.alias}">{$post.title}</a>
```
Internally, the `path()` function calls Yii's `Url::to()` method.
`path` generates relative URL while `url` generates absolute one. Internally both are using [[\yii\helpers\Url]].
#### Additional variables
Within Smarty templates, you can also make use of these variables:
Within Smarty templates the following variables are always defined:
- `$app`, which equates to `\Yii::$app`
- `$this`, which equates to the current `View` object
#### Accessing config params
Yii parameters that are available in your application through `Yii::$app->params->something` could be used the following
way:
```
`{#something#}`
```

22
extensions/smarty/CHANGELOG.md

@ -4,8 +4,26 @@ Yii Framework 2 smarty extension Change Log
2.0.0-rc under development
--------------------------
- no changes in this release.
- Enh #4619 (samdark, hwmaier)
- New functions:
- `url` generates absolute URL.
- `set` allows setting commonly used view paramters: `title`, `theme` and `layout`.
- `meta` registers meta tag.
- `registerJsFile` registers JavaScript file.
- `registerCssFile` registers CSS file.
- `use` allows importing classes to the template and optionally provides these as functions and blocks.
- New blocks:
- `title`.
- `description`.
- `registerJs`.
- `registerCss`.
- New modifier `void` that allows calling functions and ignoring result.
- Moved most of Yii custom syntax into `\yii\smarty\Extension` class that could be extended via `extensionClass` property.
- Added ability to set Smarty options via config using `options`.
- Added `imports` property that accepts an array of classes imported into template namespace.
- Added `widgets` property that can be used to import widgets as Smarty tags.
- `Yii::$app->params['paramKey']` values are now accessible as Smarty config variables `{#paramKey#}`.
- Added ability to use Yii aliases in `extends` and `require`.
2.0.0-beta April 13, 2014
-------------------------

429
extensions/smarty/Extension.php

@ -0,0 +1,429 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\smarty;
use Smarty;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;
use yii\web\View;
/**
* Extension provides Yii-specific syntax for Smarty templates.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Henrik Maier <hwmaier@gmail.com>
*/
class Extension
{
/**
* @var ViewRenderer
*/
protected $viewRenderer;
/**
* @var Smarty
*/
protected $smarty;
/**
* @param ViewRenderer $viewRenderer
* @param Smarty $smarty
*/
public function __construct($viewRenderer, $smarty)
{
$this->viewRenderer = $viewRenderer;
$smarty = $this->smarty = $smarty;
$smarty->registerPlugin('function', 'path', [$this, 'functionPath']);
$smarty->registerPlugin('function', 'url', [$this, 'functionUrl']);
$smarty->registerPlugin('function', 'set', [$this, 'functionSet']);
$smarty->registerPlugin('function', 'meta', [$this, 'functionMeta']);
$smarty->registerPlugin('function', 'registerJsFile', [$this, 'functionRegisterJsFile']);
$smarty->registerPlugin('function', 'registerCssFile', [$this, 'functionRegisterCssFile']);
$smarty->registerPlugin('block', 'title', [$this, 'blockTitle']);
$smarty->registerPlugin('block', 'description', [$this, 'blockDescription']);
$smarty->registerPlugin('block', 'registerJs', [$this, 'blockJavaScript']);
$smarty->registerPlugin('block', 'registerCss', [$this, 'blockCss']);
$smarty->registerPlugin('compiler', 'use', [$this, 'compilerUse']);
$smarty->registerPlugin('modifier', 'void', [$this, 'modifierVoid']);
}
/**
* Smarty template function to get relative URL for using in links
*
* Usage is the following:
*
* {path route='blog/view' alias=$post.alias user=$user.id}
*
* where route is Yii route and the rest of parameters are passed as is.
*
* @param array $params
* @param \Smarty_Internal_Template $template
*
* @return string
*/
public function functionPath($params, \Smarty_Internal_Template $template)
{
if (!isset($params['route'])) {
trigger_error("path: missing 'route' parameter");
}
array_unshift($params, $params['route']) ;
unset($params['route']);
return Url::to($params, true);
}
/**
* Smarty template function to get absolute URL for using in links
*
* Usage is the following:
*
* {path route='blog/view' alias=$post.alias user=$user.id}
*
* where route is Yii route and the rest of parameters are passed as is.
*
* @param array $params
* @param \Smarty_Internal_Template $template
*
* @return string
*/
public function functionUrl($params, \Smarty_Internal_Template $template)
{
if (!isset($params['route'])) {
trigger_error("path: missing 'route' parameter");
}
array_unshift($params, $params['route']) ;
unset($params['route']);
return Url::to($params, true);
}
/**
* Smarty compiler function plugin
* Usage is the following:
*
* {use class="app\assets\AppAsset"}
* {use class="yii\helpers\Html"}
* {use class='yii\widgets\ActiveForm' type='block'}
* {use class='@app\widgets\MyWidget' as='my_widget' type='function'}
*
* Supported attributes: class, as, type. Type defaults to 'static'.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function compilerUse($params, $template)
{
if (!isset($params['class'])) {
trigger_error("use: missing 'class' parameter");
}
// Compiler plugin parameters may include quotes, so remove them
foreach ($params as $key => $value) {
$params[$key] = trim($value, '\'""');
}
$class = $params['class'];
$alias = ArrayHelper::getValue($params, 'as', basename($params['class']));
$type = ArrayHelper::getValue($params, 'type', 'static');
// Register the class during compile time
$this->smarty->registerClass($alias, $class);
if ($type === 'block') {
// Register widget tag during compile time
$this->viewRenderer->widgets['blocks'][$alias] = $class;
$this->smarty->registerPlugin('block', $alias, [$this->viewRenderer, '_widget_block__' . $alias]);
// Inject code to re-register widget tag during run-time
return <<<PHP
<?php
\$_smarty_tpl->getGlobal('_viewRenderer')->widgets['blocks']['$alias'] = '$class';
try {
\$_smarty_tpl->registerPlugin('block', '$alias', [\$_smarty_tpl->getGlobal('_viewRenderer'), '_widget_block__$alias']);
}
catch (SmartyException \$e) {
/* Ignore already registered exception during first execution after compilation */
}
?>
PHP;
} elseif ($type === 'function') {
// Register widget tag during compile time
$this->viewRenderer->widgets['functions'][$alias] = $class;
$this->smarty->registerPlugin('function', $alias, [$this->viewRenderer, '_widget_function__' . $alias]);
// Inject code to re-register widget tag during run-time
return <<<PHP
<?php
\$_smarty_tpl->getGlobal('_viewRenderer')->widgets['functions']['$alias'] = '$class';
try {
\$_smarty_tpl->registerPlugin('function', '$alias', [\$_smarty_tpl->getGlobal('_viewRenderer'), '_widget_function__$alias']);
}
catch (SmartyException \$e) {
/* Ignore already registered exception during first execution after compilation */
}
?>
PHP;
}
}
/**
* Smarty modifier plugin
* Converts any output to void
* @param mixed $arg
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function modifierVoid($arg)
{
return;
}
/**
* Smarty function plugin
* Usage is the following:
*
* {set title="My Page"}
* {set theme="frontend"}
* {set layout="main.tpl"}
*
* Supported attributes: title, theme, layout
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionSet($params, $template)
{
if (isset($params['title'])) {
$template->tpl_vars['this']->value->title = Yii::$app->getView()->title = ArrayHelper::remove($params, 'title');
}
if (isset($params['theme'])) {
$template->tpl_vars['this']->value->theme = Yii::$app->getView()->theme = ArrayHelper::remove($params, 'theme');
}
if (isset($params['layout'])) {
Yii::$app->controller->layout = ArrayHelper::remove($params, 'layout');
}
// We must have consumed all allowed parameters now, otherwise raise error
if (!empty($params)) {
trigger_error('set: Unsupported parameter attribute');
}
}
/**
* Smarty function plugin
* Usage is the following:
*
* {meta keywords="Yii,PHP,Smarty,framework"}
*
* Supported attributes: any; all attributes are passed as
* parameter array to Yii's registerMetaTag function.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionMeta($params, $template)
{
$key = isset($params['name']) ? $params['name'] : null;
Yii::$app->getView()->registerMetaTag($params, $key);
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {title} Web Site Login {/title}
*
* Supported attributes: none.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockTitle($params, $content, $template, &$repeat)
{
if ($content !== null) {
Yii::$app->getView()->title = $content;
}
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {description}
* The text between the opening and closing tags is added as
* meta description tag to the page output.
* {/description}
*
* Supported attributes: none.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockDescription($params, $content, $template, &$repeat)
{
if ($content !== null) {
// Clean-up whitespace and newlines
$content = preg_replace('/\s+/', ' ', trim($content));
Yii::$app->getView()->registerMetaTag(['name' => 'description',
'content' => $content],
'description');
}
}
/**
* Smarty function plugin
* Usage is the following:
*
* {registerJsFile url='http://maps.google.com/maps/api/js?sensor=false' position='POS_END'}
*
* Supported attributes: url, key, depends, position and valid HTML attributes for the script tag.
* Refer to Yii documentation for details.
* The position attribute is passed as text without the class prefix.
* Default is 'POS_END'.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionRegisterJsFile($params, $template)
{
if (!isset($params['url'])) {
trigger_error("registerJsFile: missing 'url' parameter");
}
$url = ArrayHelper::remove($params, 'url');
$key = ArrayHelper::remove($params, 'key', null);
$depends = ArrayHelper::remove($params, 'depends', null);
if (isset($params['position']))
$params['position'] = $this->getViewConstVal($params['position'], View::POS_END);
Yii::$app->getView()->registerJsFile($url, $depends, $params, $key);
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {registerJs key='show' position='POS_LOAD'}
* $("span.show").replaceWith('<div class="show">');
* {/registerJs}
*
* Supported attributes: key, position. Refer to Yii documentation for details.
* The position attribute is passed as text without the class prefix.
* Default is 'POS_READY'.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockJavaScript($params, $content, $template, &$repeat)
{
if ($content !== null) {
$key = isset($params['key']) ? $params['key'] : null;
$position = isset($params['position']) ? $params['position'] : null;
Yii::$app->getView()->registerJs($content,
$this->getViewConstVal($position, View::POS_READY),
$key);
}
}
/**
* Smarty function plugin
* Usage is the following:
*
* {registerCssFile url='@assets/css/normalizer.css'}
*
* Supported attributes: url, key, depends and valid HTML attributes for the link tag.
* Refer to Yii documentation for details.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionRegisterCssFile($params, $template)
{
if (!isset($params['url'])) {
trigger_error("registerCssFile: missing 'url' parameter");
}
$url = ArrayHelper::remove($params, 'url');
$key = ArrayHelper::remove($params, 'key', null);
$depends = ArrayHelper::remove($params, 'depends', null);
Yii::$app->getView()->registerCssFile($url, $depends, $params, $key);
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {registerCss}
* div.header {
* background-color: #3366bd;
* color: white;
* }
* {/registerCss}
*
* Supported attributes: key and valid HTML attributes for the style tag.
* Refer to Yii documentation for details.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockCss($params, $content, $template, &$repeat)
{
if ($content !== null) {
$key = isset($params['key']) ? $params['key'] : null;
Yii::$app->getView()->registerCss($content, $params, $key);
}
}
/**
* Helper function to convert a textual constant identifier to a View class
* integer constant value.
*
* @param string $string Constant identifier name
* @param integer $default Default value
* @return mixed
*/
protected function getViewConstVal($string, $default)
{
$val = @constant('yii\web\View::' . $string);
return isset($val) ? $val : $default;
}
}

220
extensions/smarty/ViewRenderer.php

@ -9,14 +9,17 @@ namespace yii\smarty;
use Yii;
use Smarty;
use yii\base\View;
use yii\web\View;
use yii\base\Widget;
use yii\base\ViewRenderer as BaseViewRenderer;
use yii\helpers\Url;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* SmartyViewRenderer allows you to use Smarty templates in views.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Henrik Maier <hwmaier@gmail.com>
* @since 2.0
*/
class ViewRenderer extends BaseViewRenderer
@ -29,45 +32,202 @@ class ViewRenderer extends BaseViewRenderer
* @var string the directory or path alias pointing to where Smarty compiled templates will be stored.
*/
public $compilePath = '@runtime/Smarty/compile';
/**
* @var array Add additional directories to Smarty's search path for plugins.
*/
public $pluginDirs = [];
/**
* @var array Class imports similar to the use tag
*/
public $imports = [];
/**
* @var array Widget declarations
*/
public $widgets = ['functions' => [], 'blocks' => []];
/**
* @var Smarty
* @var Smarty The Smarty object used for rendering
*/
public $smarty;
protected $smarty;
/**
* @var array additional Smarty options
* @see http://www.smarty.net/docs/en/api.variables.tpl
*/
public $options = [];
/**
* @var string extension class name
*/
public $extensionClass = '\yii\smarty\Extension';
/**
* Instantiates and configures the Smarty object.
*/
public function init()
{
$this->smarty = new Smarty();
$this->smarty->setCompileDir(Yii::getAlias($this->compilePath));
$this->smarty->setCacheDir(Yii::getAlias($this->cachePath));
$this->smarty->registerPlugin('function', 'path', [$this, 'smarty_function_path']);
foreach ($this->options as $key => $value) {
$this->smarty->$key = $value;
}
$this->smarty->setTemplateDir([
dirname(Yii::$app->getView()->getViewFile()),
Yii::$app->getViewPath(),
]);
// Add additional plugin dirs from configuration array, apply Yii's dir convention
foreach ($this->pluginDirs as &$dir) {
$dir = $this->resolveTemplateDir($dir);
}
$this->smarty->addPluginsDir($this->pluginDirs);
if (isset($this->imports)) {
foreach(($this->imports) as $tag => $class) {
$this->smarty->registerClass($tag, $class);
}
}
// Register block widgets specified in configuration array
if (isset($this->widgets['blocks'])) {
foreach(($this->widgets['blocks']) as $tag => $class) {
$this->smarty->registerPlugin('block', $tag, [$this, '_widget_block__' . $tag]);
$this->smarty->registerClass($tag, $class);
}
}
// Register function widgets specified in configuration array
if (isset($this->widgets['functions'])) {
foreach(($this->widgets['functions']) as $tag => $class) {
$this->smarty->registerPlugin('function', $tag, [$this, '_widget_func__' . $tag]);
$this->smarty->registerClass($tag, $class);
}
}
new $this->extensionClass($this, $this->smarty);
$this->smarty->default_template_handler_func = [$this, 'aliasHandler'];
}
/**
* Smarty template function to get a path for using in links
*
* Usage is the following:
* The directory can be specified in Yii's standard convention
* using @, // and / prefixes or no prefix for view relative directories.
*
* {path route='blog/view' alias=$post.alias user=$user.id}
*
* where route is Yii route and the rest of parameters are passed as is.
* @param string $dir directory name to be resolved
* @return string the resolved directory name
*/
protected function resolveTemplateDir($dir)
{
if (strncmp($dir, '@', 1) === 0) {
// e.g. "@app/views/dir"
$dir = Yii::getAlias($dir);
} elseif (strncmp($dir, '//', 2) === 0) {
// e.g. "//layouts/dir"
$dir = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($dir, '/');
} elseif (strncmp($dir, '/', 1) === 0) {
// e.g. "/site/dir"
if (Yii::$app->controller !== null) {
$dir = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($dir, '/');
} else {
// No controller, what to do?
}
} else {
// relative to view file
$dir = dirname(Yii::$app->getView()->getViewFile()) . DIRECTORY_SEPARATOR . $dir;
}
return $dir;
}
/**
* Mechanism to pass a widget's tag name to the callback function.
*
* @param $params
* @param \Smarty_Internal_Template $template
* Using a magic function call would not be necessary if Smarty would
* support closures. Smarty closure support is announced for 3.2,
* until its release magic function calls are used to pass the
* tag name to the callback.
*
* @param string $method
* @param array $args
* @throws InvalidConfigException
* @throws \BadMethodCallException
* @return string
*/
public function smarty_function_path($params, \Smarty_Internal_Template $template)
public function __call($method, $args)
{
if (!isset($params['route'])) {
trigger_error("path: missing 'route' parameter");
$methodInfo = explode('__', $method);
if (count($methodInfo) === 2) {
$alias = $methodInfo[1];
if (isset($this->widgets['functions'][$alias])) {
if (($methodInfo[0] === '_widget_func') && (count($args) === 2)) {
return $this->widgetFunction($this->widgets['functions'][$alias], $args[0], $args[1]);
}
} elseif (isset($this->widgets['blocks'][$alias])) {
if (($methodInfo[0] === '_widget_block') && (count($args) === 4)) {
return $this->widgetBlock($this->widgets['blocks'][$alias], $args[0], $args[1], $args[2], $args[3]);
}
} else {
throw new InvalidConfigException('Widget "' . $alias . '" not declared.');
}
}
array_unshift($params, $params['route']) ;
unset($params['route']);
throw new \BadMethodCallException('Method does not exist: ' . $method);
}
/**
* Smarty plugin callback function to support widget as Smarty blocks.
* This function is not called directly by Smarty but through a
* magic __call wrapper.
*
* Example usage is the following:
*
* {ActiveForm assign='form' id='login-form'}
* {$form->field($model, 'username')}
* {$form->field($model, 'password')->passwordInput()}
* <div class="form-group">
* <input type="submit" value="Login" class="btn btn-primary" />
* </div>
* {/ActiveForm}
*/
private function widgetBlock($class, $params, $content, \Smarty_Internal_Template $template, &$repeat)
{
// Check if this is the opening ($content is null) or closing tag.
if ($content === null) {
$params['class'] = $class;
// Figure out where to put the result of the widget call, if any
$assign = ArrayHelper::remove($params, 'assign', false);
ob_start();
ob_implicit_flush(false);
$widget = Yii::createObject($params);
Widget::$stack[] = $widget;
if ($assign) {
$template->assign($assign, $widget);
}
} else {
$widget = array_pop(Widget::$stack);
echo $content;
$out = $widget->run();
return ob_get_clean() . $out;
}
}
return Url::to($params);
/**
* Smarty plugin callback function to support widgets as Smarty functions.
* This function is not called directly by Smarty but through a
* magic __call wrapper.
*
* Example usage is the following:
*
* {GridView dataProvider=$provider}
*
*/
private function widgetFunction($class, $params, \Smarty_Internal_Template $template)
{
$repeat = false;
$this->widgetBlock($class, $params, null, $template, $repeat); // $widget->init(...)
return $this->widgetBlock($class, $params, '', $template, $repeat); // $widget->run()
}
/**
@ -79,17 +239,35 @@ class ViewRenderer extends BaseViewRenderer
* @param View $view the view object used for rendering the file.
* @param string $file the view file.
* @param array $params the parameters to be passed to the view file.
*
* @return string the rendering result
*/
public function render($view, $file, $params)
{
/* @var $template \Smarty_Internal_Template */
$template = $this->smarty->createTemplate($file, null, null, empty($params) ? null : $params, true);
$template = $this->smarty->createTemplate($file, null, null, empty($params) ? null : $params, false);
// Make Yii params available as smarty config variables
$template->config_vars = Yii::$app->params;
$template->assign('app', \Yii::$app);
$template->assign('this', $view);
return $template->fetch();
}
/**
* Resolves Yii alias into file path
*
* @param string $type
* @param string $name
* @param string $content
* @param string $modified
* @param Smarty $smarty
* @return bool|string path to file or false if it's not found
*/
public function aliasHandler($type, $name, &$content, &$modified, Smarty $smarty)
{
$file = Yii::getAlias($name);
return is_file($file) ? $file : false;
}
}

45
tests/unit/extensions/smarty/ViewRendererTest.php

@ -10,6 +10,7 @@ namespace yiiunit\extensions\smarty;
use yii\web\AssetManager;
use yii\web\View;
use Yii;
use yiiunit\data\base\Singer;
use yiiunit\TestCase;
/**
@ -41,6 +42,47 @@ class ViewRendererTest extends TestCase
$this->assertEquals('test view Hello World!.', $content);
}
public function testLayoutAssets()
{
$view = $this->mockView();
$content = $view->renderFile('@yiiunit/extensions/smarty/views/layout.tpl');
$this->assertEquals(1, preg_match('#<script src="/assets/[0-9a-z]+/jquery\\.js"></script>\s*</body>#', $content), 'Content does not contain the jquery js:' . $content);
}
public function testChangeTitle()
{
$view = $this->mockView();
$view->title = 'Original title';
$content = $view->renderFile('@yiiunit/extensions/smarty/views/changeTitle.tpl');
$this->assertTrue(strpos($content, 'New title') !== false, 'New title should be there:' . $content);
$this->assertFalse(strpos($content, 'Original title') !== false, 'Original title should not be there:' . $content);
}
public function testForm()
{
$view = $this->mockView();
$model = new Singer();
$content = $view->renderFile('@yiiunit/extensions/smarty/views/form.tpl', ['model' => $model]);
$this->assertEquals(1, preg_match('#<form id="login-form" class="form-horizontal" action="/form-handler" method="post">.*?</form>#s', $content), 'Content does not contain form:' . $content);
}
public function testInheritance()
{
$view = $this->mockView();
$content = $view->renderFile('@yiiunit/extensions/smarty/views/extends2.tpl');
$this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content);
$this->assertTrue(strpos($content, 'extends2 block') !== false, 'extends2 block should be there:' . $content);
$this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content);
$content = $view->renderFile('@yiiunit/extensions/smarty/views/extends3.tpl');
$this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content);
$this->assertTrue(strpos($content, 'extends3 block') !== false, 'extends3 block should be there:' . $content);
$this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content);
}
/**
* @return View
*/
@ -50,6 +92,9 @@ class ViewRendererTest extends TestCase
'renderers' => [
'tpl' => [
'class' => 'yii\smarty\ViewRenderer',
'options' => [
'force_compile' => true, // always recompile templates, don't do it in production
],
],
],
'assetManager' => $this->mockAssetManager(),

3
tests/unit/extensions/smarty/views/changeTitle.tpl

@ -0,0 +1,3 @@
{set title='New title'}
<title>{$this->title}</title>

5
tests/unit/extensions/smarty/views/extends1.tpl

@ -0,0 +1,5 @@
Hello, I'm inheritance test!
{block name=test}
extends1 block
{/block}

5
tests/unit/extensions/smarty/views/extends2.tpl

@ -0,0 +1,5 @@
{extends file="@yiiunit/extensions/smarty/views/extends1.tpl"}
{block name=test}
extends2 block
{/block}

5
tests/unit/extensions/smarty/views/extends3.tpl

@ -0,0 +1,5 @@
{extends file="@yiiunit/extensions/smarty/views/extends1.tpl"}
{block name=test}
extends3 block
{/block}

9
tests/unit/extensions/smarty/views/form.tpl

@ -0,0 +1,9 @@
{use class='yii\widgets\ActiveForm' type='block'}
{ActiveForm assign='form' id='login-form' action='/form-handler' options=['class' => 'form-horizontal']}
{$form->field($model, 'firstName')}
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</div>
{/ActiveForm}

16
tests/unit/extensions/smarty/views/layout.tpl

@ -0,0 +1,16 @@
{use class="yii\web\JqueryAsset"}
{JqueryAsset::register($this)|void}
{$this->beginPage()}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="{$app->charset}"/>
<title>{$this->title|escape}</title>
{$this->head()}
</head>
<body>
{$this->beginBody()}
body
{$this->endBody()}
</body>
{$this->endPage()}
Loading…
Cancel
Save