scope = new Loco_mvc_ViewParams( $args ); $this->cwd = loco_plugin_root().'/tpl'; } /** * Change base path for template paths * @param string path relative to current directory * @return Loco_mvc_View */ public function cd( $path ){ if( $path && '/' === $path{0} ){ $this->cwd = untrailingslashit( loco_plugin_root().'/tpl'.$path ); } else { $this->cwd = untrailingslashit( $this->cwd.'/'.$path ); } return $this; } /** * @internal * Clean up if something ubruptly stopped rendering before graceful end */ public function __destruct(){ if( $this->block ){ ob_end_clean(); } } /** * Render error screen HTML * @return string */ public static function renderError( Loco_error_Exception $e ){ $view = new Loco_mvc_View; try { $view->set( 'error', $e ); return $view->render( $e->getTemplate() ); } catch( Exception $e ){ return '

'.esc_html( $e->getMessage() ).'

'; } } /** * @internal * Make this view a child of another template. i.e. decorate this with that. * Parent will have access to original argument scope, but separate from now on * @return Loco_mvc_View the parent view */ private function extend( $tpl ){ $this->parent = new Loco_mvc_View; $this->parent->cwd = $this->cwd; $this->parent->setTemplate( $tpl ); return $this->parent; } /** * @internal * After start is called any captured output will be placed in the named variable * @return void */ private function start( $name ){ $this->stop(); $this->scope[$name] = null; $this->block = $name; } /** * @internal * When stop is called, buffered output is saved into current variable for output by parent template, or at end of script. * @return void */ private function stop(){ $content = ob_get_contents(); ob_clean(); if( $b = $this->block ){ if( isset($this->scope[$b]) ){ $content = $this->scope[$b].$content; } $this->scope[$b] = new _LocoViewBuffer($content); $this->block = null; } $this->block = '_trash'; } /** * implement IteratorAggregate::getIterator */ public function getIterator(){ return $this->scope; } /** * @return mixed */ public function __get( $prop ){ return isset($this->scope[$prop]) ? $this->scope[$prop] : null; } /** * @return bool */ public function has( $prop ){ return isset( $this->scope[$prop] ); } /** * Set a view argument * @return Loco_mvc_View */ public function set( $prop, $value ){ $this->scope[$prop] = $value; return $this; } /** * Main entry to rendering complete template * @param string template name excluding extension * @param array extra arguments to set in view scope * @return string */ public function render( $tpl, array $args = null, Loco_mvc_View $parent = null ){ if( $this->block ){ return $this->fork()->render( $tpl, $args, $this ); } $this->setTemplate($tpl); if( $parent && $this->template === $parent->template ){ throw new Loco_error_Exception('Avoiding infinite loop'); } if( is_array($args) ){ foreach( $args as $prop => $value ){ $this->set($prop, $value); } } ob_start(); $content = $this->buffer(); ob_end_clean(); return $content; } /** * Do actual render of currently validated template path * @return string content not captured in sub-blocks */ private function buffer(){ $this->start('_trash'); $this->execTemplate( $this->template ); $this->stop(); $this->block = null; // decorate via parent view if there is one if( $this->parent ){ $this->parent->scope = clone $this->scope; $this->parent->set('_content', $this->_trash ); return $this->parent->buffer(); } // else at the root of view chain return (string) $this->_trash; } /** * Set current template * @param string path tro template, excluding file extension */ public function setTemplate( $tpl ){ $file = new Loco_fs_File( $tpl.'.php' ); $file->normalize( $this->cwd ); if( ! $file->exists() ){ $debug = str_replace( loco_plugin_root().'/', '', $file->getPath() ); throw new Loco_error_Exception( 'Template not found: '.$debug ); } $this->cwd = $file->dirname(); $this->template = $file->getPath(); } /** * @return Loco_mvc_View */ private function fork(){ $view = new Loco_mvc_View; $view->cwd = $this->cwd; $view->scope = clone $this->scope; return $view; } /** * Do actual runtime template include */ private function execTemplate( $template ){ $params = $this->scope; extract( $params->getArrayCopy() ); include $template; } /** * Link generator * return Loco_mvc_ViewParams */ public function route( $action, array $args = array() ){ return new Loco_mvc_ViewParams( array( 'href' => Loco_mvc_AdminRouter::generate( $action, $args ), ) ); } /** * Shorthand for `echo esc_html( sprintf( ...` * @return void */ private static function e( $text ){ if( 1 < func_num_args() ){ $args = func_get_args(); $text = call_user_func_array( 'sprintf', $args ); } echo htmlspecialchars( $text, ENT_COMPAT, 'UTF-8' ); return ''; } } /** * @internal */ class _LocoViewBuffer { private $s; public function __construct( $s ){ $this->s = $s; } public function __toString(){ return $this->s; } }