diff --git a/framework/base/Controller.php b/framework/base/Controller.php index 3069ce3..9219904 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -304,9 +304,13 @@ class Controller extends Component */ public function render($view, $params = array()) { - $viewFile = $this->findViewFile($view); + $output = Yii::$app->getView()->render($view, $params, $this); $layoutFile = $this->findLayoutFile(); - return Yii::$app->getView()->render($this, $viewFile, $params, $layoutFile); + if ($layoutFile !== false) { + return Yii::$app->getView()->renderFile($layoutFile, array('content' => $output), $this); + } else { + return $output; + } } /** @@ -319,7 +323,7 @@ class Controller extends Component */ public function renderPartial($view, $params = array()) { - return $this->renderFile($this->findViewFile($view), $params); + return Yii::$app->getView()->render($view, $params, $this); } /** @@ -331,7 +335,7 @@ class Controller extends Component */ public function renderFile($file, $params = array()) { - return Yii::$app->getView()->render($this, $file, $params); + return Yii::$app->getView()->renderFile($file, $params, $this); } /** @@ -346,46 +350,6 @@ class Controller extends Component } /** - * Finds the view file based on the given view name. - * - * A view name can be specified in one of the following formats: - * - * - path alias (e.g. "@app/views/site/index"); - * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes. - * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application. - * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash. - * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently - * active module. - * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]]. - * - * If the view name does not contain a file extension, it will use the default one `.php`. - * - * @param string $view the view name or the path alias of the view file. - * @return string the view file path. Note that the file may not exist. - * @throws InvalidParamException if the view file is an invalid path alias - */ - protected function findViewFile($view) - { - if (strncmp($view, '@', 1) === 0) { - // e.g. "@app/views/common" - $file = Yii::getAlias($view); - } elseif (strncmp($view, '/', 1) !== 0) { - // e.g. "index" - $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view; - } elseif (strncmp($view, '//', 2) !== 0) { - // e.g. "/site/index" - $file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } else { - // e.g. "//layouts/main" - $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } - if (FileHelper::getExtension($file) === '') { - $file .= '.php'; - } - return $file; - } - - /** * Finds the applicable layout file. * * This method locates an applicable layout file via two steps. diff --git a/framework/base/View.php b/framework/base/View.php index 9911cd7..709b761 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -39,6 +39,12 @@ class View extends Component * If not set, it means theming is not enabled. */ public $theme; + /** + * @var array a list of named output clips. You can call [[beginClip()]] and [[endClip()]] + * to capture small fragments of a view. They can be later accessed at somewhere else + * through this property. + */ + public $clips; /** * @var Widget[] the widgets that are currently not ended @@ -61,35 +67,29 @@ class View extends Component } /** - * Renders a view file under a context with an optional layout. + * Renders a view. * - * This method is similar to [[renderFile()]] except that it will update [[context]] - * with the provided $context parameter. It will also apply layout to the rendering result - * of the view file if $layoutFile is given. + * This method will call [[findViewFile()]] to convert the view name into the corresponding view + * file path, and it will then call [[renderFile()]] to render the view. * - * Theming and localization will be performed for the view file and the layout file, if possible. - * - * @param object $context the context object for rendering the file. This could be a controller, a widget, - * or any other object that serves as the rendering context of the view file. In the view file, - * it can be accessed through the [[context]] property. - * @param string $viewFile the view file. This can be a file path or a path alias. + * @param string $view the view name. Please refer to [[findViewFile()]] on how to specify this parameter. * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. - * @param string|boolean $layoutFile the layout file. This can be a file path or a path alias. - * If it is false, it means no layout should be applied. + * @param object $context the context that the view should use for rendering the view. If null, + * existing [[context]] will be used. * @return string the rendering result - * @throws InvalidParamException if the view file or the layout file does not exist. + * @throws InvalidParamException if the view cannot be resolved or the view file does not exist. + * @see renderFile + * @see findViewFile */ - public function render($view, $params = array()) + public function render($view, $params = array(), $context = null) { - $viewFile = $this->findViewFile($this->context, $view); - return $this->renderFile($viewFile, $params); + $viewFile = $this->findViewFile($context, $view); + return $this->renderFile($viewFile, $params, $context); } /** * Renders a view file. * - * This method renders the specified view file under the existing [[context]]. - * * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long * as it is available. * @@ -99,12 +99,14 @@ class View extends Component * Otherwise, it will simply include the view file as a normal PHP file, capture its output and * return it as a string. * - * @param string $viewFile the view file. This can be a file path or a path alias. + * @param string $viewFile the view file. This can be either a file path or a path alias. * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @param object $context the context that the view should use for rendering the view. If null, + * existing [[context]] will be used. * @return string the rendering result * @throws InvalidParamException if the view file does not exist */ - public function renderFile($viewFile, $params = array()) + public function renderFile($viewFile, $params = array(), $context = null) { $viewFile = Yii::getAlias($viewFile); if (is_file($viewFile)) { @@ -116,11 +118,18 @@ class View extends Component throw new InvalidParamException("The view file does not exist: $viewFile"); } + $oldContext = $this->context; + $this->context = $context; + if ($this->renderer !== null) { - return $this->renderer->render($this, $viewFile, $params); + $output = $this->renderer->render($this, $viewFile, $params); } else { - return $this->renderPhpFile($viewFile, $params); + $output = $this->renderPhpFile($viewFile, $params); } + + $this->context = $oldContext; + + return $output; } /** @@ -156,36 +165,36 @@ class View extends Component * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash. * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently * active module. - * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]]. + * - relative path (e.g. "index"): the actual view file will be looked for under [[Controller::viewPath|viewPath]] + * of the context object, assuming the context is either a [[Controller]] or a [[Widget]]. * * If the view name does not contain a file extension, it will use the default one `.php`. * + * @param object $context the view context object * @param string $view the view name or the path alias of the view file. * @return string the view file path. Note that the file may not exist. - * @throws InvalidParamException if the view file is an invalid path alias + * @throws InvalidParamException if the view file is an invalid path alias or the context cannot be + * used to determine the actual view file corresponding to the specified view. */ protected function findViewFile($context, $view) { - if (FileHelper::getExtension($view) === '') { - $view .= '.php'; - } - - if (strncmp($view, '//', 2) === 0) { + if (strncmp($view, '@', 1) === 0) { + // e.g. "@app/views/main" + $file = Yii::getAlias($view); + } elseif (strncmp($view, '//', 2) === 0) { // e.g. "//layouts/main" $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); } elseif (strncmp($view, '/', 1) === 0) { // e.g. "/site/index" - $file = Yii::$app->controller->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } elseif (strncmp($view, '@', 1) !== 0) { - // e.g. "index" or "view/item" - if (method_exists($context, 'getViewPath')) { - $file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view; - } else { - $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . $view; - } + $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); + } elseif ($context instanceof Controller || $context instanceof Widget) { + /** @var $context Controller|Widget */ + $file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view; + } else { + throw new InvalidParamException("Unable to resolve the view file for '$view'."); } - return $file; + return FileHelper::getExtension($file) === '' ? $file . '.php' : $file; } /** @@ -260,26 +269,31 @@ class View extends Component } } - // - // /** - // * Begins recording a clip. - // * This method is a shortcut to beginning [[yii\widgets\Clip]] - // * @param string $id the clip ID. - // * @param array $properties initial property values for [[yii\widgets\Clip]] - // */ - // public function beginClip($id, $properties = array()) - // { - // $properties['id'] = $id; - // $this->beginWidget('yii\widgets\Clip', $properties); - // } - // - // /** - // * Ends recording a clip. - // */ - // public function endClip() - // { - // $this->endWidget(); - // } + /** + * Begins recording a clip. + * This method is a shortcut to beginning [[yii\widgets\Clip]] + * @param string $id the clip ID. + * @param boolean $renderInPlace whether to render the clip content in place. + * Defaults to false, meaning the captured clip will not be displayed. + * @return \yii\widgets\Clip the Clip widget instance + */ + public function beginClip($id, $renderInPlace = false) + { + return $this->beginWidget('yii\widgets\Clip', array( + 'id' => $id, + 'renderInPlace' => $renderInPlace, + 'view' => $this, + )); + } + + /** + * Ends recording a clip. + */ + public function endClip() + { + $this->endWidget(); + } + // // /** // * Begins fragment caching. @@ -322,33 +336,33 @@ class View extends Component // $this->endWidget(); // } // - // /** - // * Begins the rendering of content that is to be decorated by the specified view. - // * @param mixed $view the name of the view that will be used to decorate the content. The actual view script - // * is resolved via {@link getViewFile}. If this parameter is null (default), - // * the default layout will be used as the decorative view. - // * Note that if the current controller does not belong to - // * any module, the default layout refers to the application's {@link CWebApplication::layout default layout}; - // * If the controller belongs to a module, the default layout refers to the module's - // * {@link CWebModule::layout default layout}. - // * @param array $params the variables (name=>value) to be extracted and made available in the decorative view. - // * @see endContent - // * @see yii\widgets\ContentDecorator - // */ - // public function beginContent($view, $params = array()) - // { - // $this->beginWidget('yii\widgets\ContentDecorator', array( - // 'view' => $view, - // 'params' => $params, - // )); - // } - // - // /** - // * Ends the rendering of content. - // * @see beginContent - // */ - // public function endContent() - // { - // $this->endWidget(); - // } + /** + * Begins the rendering of content that is to be decorated by the specified view. + * @param mixed $view the name of the view that will be used to decorate the content. The actual view script + * is resolved via {@link getViewFile}. If this parameter is null (default), + * the default layout will be used as the decorative view. + * Note that if the current controller does not belong to + * any module, the default layout refers to the application's {@link CWebApplication::layout default layout}; + * If the controller belongs to a module, the default layout refers to the module's + * {@link CWebModule::layout default layout}. + * @param array $params the variables (name=>value) to be extracted and made available in the decorative view. + * @see endContent + * @see yii\widgets\ContentDecorator + */ + public function beginContent($view, $params = array()) + { + $this->beginWidget('yii\widgets\ContentDecorator', array( + 'view' => $view, + 'params' => $params, + )); + } + + /** + * Ends the rendering of content. + * @see beginContent + */ + public function endContent() + { + $this->endWidget(); + } } \ No newline at end of file diff --git a/framework/base/Widget.php b/framework/base/Widget.php index a9fe092..6b92f09 100644 --- a/framework/base/Widget.php +++ b/framework/base/Widget.php @@ -80,8 +80,7 @@ class Widget extends Component */ public function render($view, $params = array()) { - $file = $this->findViewFile($view); - return Yii::$app->getView()->render($this, $file, $params); + return Yii::$app->getView()->render($view, $params, $this); } /** @@ -93,7 +92,7 @@ class Widget extends Component */ public function renderFile($file, $params = array()) { - return Yii::$app->getView()->render($this, $file, $params); + return Yii::$app->getView()->renderFile($file, $params, $this); } /** @@ -107,44 +106,4 @@ class Widget extends Component $class = new \ReflectionClass($className); return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views'; } - - /** - * Finds the view file based on the given view name. - * - * The view name can be specified in one of the following formats: - * - * - path alias (e.g. "@app/views/site/index"); - * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes. - * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application. - * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash. - * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently - * active module. - * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]]. - * - * If the view name does not contain a file extension, it will use the default one `.php`. - * - * @param string $view the view name or the path alias of the view file. - * @return string the view file path. Note that the file may not exist. - * @throws InvalidParamException if the view file is an invalid path alias - */ - public function findViewFile($view) - { - if (strncmp($view, '@', 1) === 0) { - // e.g. "@app/views/common" - $file = Yii::getAlias($view); - } elseif (strncmp($view, '/', 1) !== 0) { - // e.g. "index" - $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view; - } elseif (strncmp($view, '//', 2) !== 0 && Yii::$app->controller !== null) { - // e.g. "/site/index" - $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } else { - // e.g. "//layouts/main" - $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } - if (FileHelper::getExtension($file) === '') { - $file .= '.php'; - } - return $file; - } } \ No newline at end of file diff --git a/framework/widgets/Clip.php b/framework/widgets/Clip.php new file mode 100644 index 0000000..d540b24 --- /dev/null +++ b/framework/widgets/Clip.php @@ -0,0 +1,57 @@ + + * @since 2.0 + */ +class Clip extends Widget +{ + /** + * @var string the ID of this clip. + */ + public $id; + /** + * @var View the view object for keeping the clip. If not set, the view registered with the application + * will be used. + */ + public $view; + /** + * @var boolean whether to render the clip content in place. Defaults to false, + * meaning the captured clip will not be displayed. + */ + public $renderInPlace = false; + + /** + * Starts recording a clip. + */ + public function init() + { + ob_start(); + ob_implicit_flush(false); + } + + /** + * Ends recording a clip. + * This method stops output buffering and saves the rendering result as a named clip in the controller. + */ + public function run() + { + $clip = ob_get_clean(); + if ($this->renderClip) { + echo $clip; + } + $view = $this->view !== null ? $this->view : Yii::$app->getView(); + $view->clips[$this->id] = $clip; + } +} \ No newline at end of file diff --git a/framework/widgets/ContentDecorator.php b/framework/widgets/ContentDecorator.php new file mode 100644 index 0000000..0087698 --- /dev/null +++ b/framework/widgets/ContentDecorator.php @@ -0,0 +1,81 @@ + + * @link http://www.yiiframework.com/ + * @copyright 2008-2013 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CContentDecorator decorates the content it encloses with the specified view. + * + * CContentDecorator is mostly used to implement nested layouts, i.e., a layout + * is embedded within another layout. {@link CBaseController} defines a pair of + * convenient methods to use CContentDecorator: + *
+ * $this->beginContent('path/to/view');
+ * // ... content to be decorated
+ * $this->endContent();
+ * 
+ * + * The property {@link view} specifies the name of the view that is used to + * decorate the content. In the view, the content being decorated may be + * accessed with variable $content. + * + * @author Qiang Xue + * @package system.web.widgets + * @since 1.0 + */ +class CContentDecorator extends COutputProcessor +{ + /** + * @var mixed the name of the view that will be used to decorate the captured content. + * If this property is null (default value), the default layout will be used as + * the decorative view. Note that if the current controller does not belong to + * any module, the default layout refers to the application's {@link CWebApplication::layout default layout}; + * If the controller belongs to a module, the default layout refers to the module's + * {@link CWebModule::layout default layout}. + */ + public $view; + /** + * @var array the variables (name=>value) to be extracted and made available in the decorative view. + */ + public $data=array(); + + /** + * Processes the captured output. + * This method decorates the output with the specified {@link view}. + * @param string $output the captured output to be processed + */ + public function processOutput($output) + { + $output=$this->decorate($output); + parent::processOutput($output); + } + + /** + * Decorates the content by rendering a view and embedding the content in it. + * The content being embedded can be accessed in the view using variable $content + * The decorated content will be displayed directly. + * @param string $content the content to be decorated + * @return string the decorated content + */ + protected function decorate($content) + { + $owner=$this->getOwner(); + if($this->view===null) + $viewFile=Yii::app()->getController()->getLayoutFile(null); + else + $viewFile=$owner->getViewFile($this->view); + if($viewFile!==false) + { + $data=$this->data; + $data['content']=$content; + return $owner->renderFile($viewFile,$data,true); + } + else + return $content; + } +}