ViewRenderer.php 9.22 KB
Newer Older
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Carsten Brandt committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5 6 7
 * @license http://www.yiiframework.com/license/
 */

8
namespace yii\smarty;
9

Qiang Xue committed
10 11
use Yii;
use Smarty;
12 13
use yii\web\View;
use yii\base\Widget;
14
use yii\base\ViewRenderer as BaseViewRenderer;
15 16
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
17 18 19 20 21

/**
 * SmartyViewRenderer allows you to use Smarty templates in views.
 *
 * @author Alexander Makarov <sam@rmcreative.ru>
22
 * @author Henrik Maier <hwmaier@gmail.com>
23 24
 * @since 2.0
 */
25
class ViewRenderer extends BaseViewRenderer
26
{
27 28 29 30 31 32 33 34
    /**
     * @var string the directory or path alias pointing to where Smarty cache will be stored.
     */
    public $cachePath = '@runtime/Smarty/cache';
    /**
     * @var string the directory or path alias pointing to where Smarty compiled templates will be stored.
     */
    public $compilePath = '@runtime/Smarty/compile';
35 36 37 38 39 40 41 42 43 44 45 46 47

    /**
     * @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' => []];
48
    /**
49
     * @var Smarty The Smarty object used for rendering
50
     */
51
    protected $smarty;
52

53 54 55 56 57 58 59 60 61 62
    /**
     * @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';
63

64 65 66 67

    /**
     * Instantiates and configures the Smarty object.
     */
68 69 70 71 72
    public function init()
    {
        $this->smarty = new Smarty();
        $this->smarty->setCompileDir(Yii::getAlias($this->compilePath));
        $this->smarty->setCacheDir(Yii::getAlias($this->cachePath));
73

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
        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'];
112
    }
113

114
    /**
115 116
     * The directory can be specified in Yii's standard convention
     * using @, // and / prefixes or no prefix for view relative directories.
117
     *
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
     * @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.
146
     *
147 148 149 150
     * 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.
151
     *
152 153 154 155
     * @param string $method
     * @param array $args
     * @throws InvalidConfigException
     * @throws \BadMethodCallException
156 157
     * @return string
     */
158
    public function __call($method, $args)
159
    {
160 161 162 163 164 165 166 167 168 169 170 171 172 173
        $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.');
            }
174
        }
175

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
        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;
        }
    }
215

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    /**
     * 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()
231
    }
232

233 234 235 236 237 238
    /**
     * Renders a view file.
     *
     * This method is invoked by [[View]] whenever it tries to render a view.
     * Child classes must implement this method to render the given view file.
     *
239 240 241
     * @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.
242 243 244 245
     * @return string the rendering result
     */
    public function render($view, $file, $params)
    {
246
        /* @var $template \Smarty_Internal_Template */
247 248 249 250
        $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;
251

252 253
        $template->assign('app', \Yii::$app);
        $template->assign('this', $view);
254

255 256
        return $template->fetch();
    }
257 258 259 260 261 262 263 264 265 266 267 268 269 270

    /**
     * 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);
271
        return is_file($file) ? $file : false;
272
    }
Zander Baldwin committed
273
}