praiadeseselle/wire/core/TemplateFile.php

622 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php namespace ProcessWire;
/**
* ProcessWire TemplateFile
*
* A template file that will be loaded and executed as PHP and its output returned.
*
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* https://processwire.com
*
* @property bool $halt Set to true to halt during render, or use method $this->halt();
* @property-read string $filename Primary file to render.
* @property-read array $prependFilename Optional file name(s) used for prepend.
* @property-read array $appendFilename Optional file name(s) used for append.
* @property-read string $currentFilename Current file being rendered (whether primary, prepend, append).
* @property-read bool $trim Whether or not leading/trailing whitespace is trimmed from output (3.0.154+).
* @method string render()
* @method bool fileFailed($filename, \Exception $e)
*
*/
class TemplateFile extends WireData {
/**
* The full path and filename to the PHP template file
*
* @var string
*
*/
protected $filename;
/**
* The current filename being rendered (whether prepend, main, append, etc.)
*
* @var string
*
*/
protected $currentFilename;
/**
* Optional filenames that are prepended to the render
*
* @var array
*
*/
protected $prependFilename = array();
/**
* Optional filenames that are appended to the render
*
* @var array
*
*/
protected $appendFilename = array();
/**
* The saved directory location before render() was called
*
* @var string
*
*/
protected $savedDir;
/**
* Directory to change to before rendering
*
* If not set, it will change to the directory that the $filename is in.
* If false, no directories will be changed.
*
* @var null|string|bool
*
*/
protected $chdir = null;
/**
* Saved ProcessWire instance
*
* @var ProcessWire
*
*/
protected $savedInstance;
/**
* Throw exception when main template file doesnt exist?
*
* @var bool
*
*/
protected $throwExceptions = true;
/**
* Whether or not the template file called $this->halt()
*
* @var bool
*
*/
protected $halt = false;
/**
* Last tracked profile event
*
* @var mixed
*
*/
protected $profilerEvent = null;
/**
* @var WireProfilerInterface|null
*
*/
protected $profiler = null;
/**
* Return value from rendered file
*
* @var null|mixed
*
*/
protected $returnValue = null;
/**
* Trim leading/trailing whitespace from rendered output?
*
* @var bool
*
*/
protected $trim = true;
/**
* Stack of files that are currently being rendered
*
* @var array
*
*/
static protected $renderStack = array();
/**
* DEPRECATED: Variables that will be applied globally to this and all other TemplateFile instances
*
*/
static protected $globals = array();
/**
* Output buffer starting level, set by first TemplateFile instance that gets created
*
* @var null|int
*
*/
static protected $obStartLevel = null;
/**
* Construct the template file
*
* @param string $filename Full path and filename to the PHP template file
*
*/
public function __construct($filename = '') {
parent::__construct();
if(self::$obStartLevel === null) self::$obStartLevel = ob_get_level();
if($filename) $this->setFilename($filename);
}
/**
* Sets the template file name, replacing whatever was set in the constructor
*
* @param string $filename Full path and filename to the PHP template file
* @return bool true on success, false if file doesn't exist
* @throws WireException if file doesn't exist (unless throwExceptions is disabled)
*
*/
public function setFilename($filename) {
if(empty($filename)) return false;
if(is_file($filename)) {
$this->filename = $filename;
return true;
} else {
$error = "Filename doesn't exist: $filename";
if($this->throwExceptions) throw new WireException($error);
$this->error($error);
$this->filename = $filename; // in case it will exist when render() is called
return false;
}
}
/**
* Set a file to prepend to the template file at render time
*
* @param string $filename
* @return bool Returns true on success, false if file doesn't exist.
* @throws WireException if file doesn't exist (unless throwExceptions is disabled)
*
*/
public function setPrependFilename($filename) {
if(empty($filename)) return false;
if(is_file($filename)) {
$this->prependFilename[] = $filename;
return true;
} else {
$error = "Append filename doesn't exist: $filename";
if($this->throwExceptions) throw new WireException($error);
$this->error($error);
return false;
}
}
/**
* Set a file to append to the template file at render time
*
* @param string $filename
* @return bool Returns true on success false if file doesn't exist.
* @throws WireException if file doesn't exist (unless throwExceptions is disabled)
*
*/
public function setAppendFilename($filename) {
if(empty($filename)) return false;
if(is_file($filename)) {
$this->appendFilename[] = $filename;
return true;
} else {
$error = "Prepend filename doesn't exist: $filename";
if($this->throwExceptions) throw new WireException($error);
$this->error($error);
return false;
}
}
/**
* Call this with boolean false to disable exceptions when file doesnt exist
*
* @param bool $throwExceptions
*
*/
public function setThrowExceptions($throwExceptions) {
$this->throwExceptions = $throwExceptions ? true : false;
}
/**
* Set whether rendered output should have leading/trailing whitespace trimmed
*
* By default whitespace is trimmed so you would call `$templateFile->setTrim(false);` to disable.
*
* @param bool $trim
* @since 3.0.154
*
*/
public function setTrim($trim) {
$this->trim = (bool) $trim;
}
/**
* Set the directory to temporarily change to during rendering
*
* If not set, it changes to the directory that $filename is in.
* To disable TemplateFile from changing any directories, set to false (3.0.154+).
*
* @param string|bool $chdir
*
*/
public function setChdir($chdir) {
$this->chdir = $chdir;
}
/**
* Sets a variable to be globally accessable to all other TemplateFile instances (deprecated)
*
* Note, to set a variable for just this instance, use the set() as inherted from WireData.
*
* #pw-internal
*
* @param string $name
* @param mixed $value
* @param bool $overwrite Should the value be overwritten if it already exists? (default true)
* @deprecated
*
*/
public function setGlobal($name, $value, $overwrite = true) {
// set template variable that will apply across all instances of Template
if(!$overwrite && isset(self::$globals[$name])) return;
self::$globals[$name] = $value;
}
/**
* Render the template: execute it and return its output
*
* @return string The output of the Template File
* @throws WireException|\Exception Throws WireException if file not exist + any exceptions thrown by included file(s)
*
*/
public function ___render() {
/** @noinspection PhpIncludeInspection */
if(!$this->filename) return '';
if(!file_exists($this->filename)) {
$error = "Template file does not exist: $this->filename";
if($this->throwExceptions) throw new WireException($error);
$this->error($error);
return '';
}
$this->renderReady();
// make API variables available to PHP file
$fuel = array_merge($this->getArray(), self::$globals); // so that script can foreach all vars to see what's there
extract($fuel);
ob_start();
try {
// include prepend files
foreach($this->prependFilename as $_filename) {
if($this->halt) break;
$this->fileReady($_filename);
require($_filename);
$this->fileFinished();
}
} catch(\Exception $e) {
if($this->fileFailed($this->currentFilename, $e)) throw $this->renderFailed($e);
}
if($this->halt) {
// if prepend file indicates we should halt, then do not render next file
$this->returnValue = 0;
} else {
// include main file to render
try {
$this->fileReady($this->filename);
$this->returnValue = require($this->filename);
$this->fileFinished();
} catch(\Exception $e) {
if($this->fileFailed($this->filename, $e)) throw $this->renderFailed($e);
}
}
try {
// include append files
foreach($this->appendFilename as $_filename) {
if($this->halt) break;
$this->fileReady($_filename);
require($_filename);
$this->fileFinished();
}
} catch(\Exception $e) {
if($this->fileFailed($this->currentFilename, $e)) throw $this->renderFailed($e);
}
$out = ob_get_contents();
ob_end_clean();
$this->renderFinished();
if($this->trim) $out = trim($out);
if(!strlen($out) && !$this->halt && $this->returnValue && $this->returnValue !== 1) {
return $this->returnValue;
}
return $out;
}
/**
* Prepare to nclude specific file (whether prepend, main or append)
*
* @param string $filename
* @since 3.0.154
*
*/
protected function fileReady($filename) {
$this->currentFilename = $filename;
if($this->profiler) {
$f = str_replace($this->wire()->config->paths->root, '/', $filename);
$this->profilerEvent = $this->profiler->start($f, $this);
}
self::pushRenderStack($filename);
}
/**
* Clean up after include specific file
*
* @since 3.0.154
*
*/
protected function fileFinished() {
$this->currentFilename = '';
if($this->profiler && $this->profilerEvent) {
$this->profiler->stop($this->profilerEvent);
}
self::popRenderStack();
}
/**
* Called when render of specific file failed with Exception
*
* #pw-hooker
*
* @param string $filename
* @param \Exception $e
* @return bool True if Exception $e should be thrown, false if it should be ignored
* @since 3.0.154
*
*/
protected function ___fileFailed($filename, \Exception $e) {
$this->fileFinished();
return true;
}
/**
* Prepare to render
*
* Called right before render about to start
*
* @since 3.0.154
*
*/
protected function renderReady() {
// ensure that wire() functions in template file map to correct ProcessWire instance
$this->savedInstance = ProcessWire::getCurrentInstance();
ProcessWire::setCurrentInstance($this->wire());
$this->profiler = $this->wire()->profiler;
if($this->chdir !== false) {
$cwd = getcwd();
if($this->chdir) {
$chdir = $this->chdir;
} else {
$chdir = dirname($this->filename);
}
if($chdir === $cwd) {
// already in required directory
$this->savedDir = '';
} else {
// change to new directory
$this->savedDir = $cwd;
chdir($chdir);
}
}
}
/**
* Cleanup after render
*
* @since 3.0.154
*
*/
protected function renderFinished() {
if($this->currentFilename) {
$this->fileFinished();
}
if($this->savedDir && $this->chdir !== false) {
chdir($this->savedDir);
}
ProcessWire::setCurrentInstance($this->savedInstance);
}
/**
* Called when overall render failed
*
* @param \Exception $e
* @return \Exception
* @since 3.0.154
*
*/
protected function renderFailed(\Exception $e) {
$this->renderFinished();
return $e;
}
/**
* Set the current filename being rendered
*
* @param string $filename
* @deprecated Moved to fileReady() and fileFinished()
*
*/
protected function setCurrentFilename($filename) {
if(strlen($filename)) {
$this->fileReady($filename);
} else {
$this->fileFinished();
}
}
/**
* Get an array of all variables accessible (locally scoped) to the PHP template file
*
* @return array
*
*/
public function getArray() {
return array_merge($this->wire()->fuel->getArray(), parent::getArray());
}
/**
* Get a set property from the template file, typically to check if a template has access to a given variable
*
* @param string $key
* @return mixed Returns the value of the requested property, or NULL if it doesn't exist
*
*/
public function get($key) {
if($key === 'filename') return $this->filename;
if($key === 'appendFilename' || $key === 'appendFilenames') return $this->appendFilename;
if($key === 'prependFilename' || $key === 'prependFilenames') return $this->prependFilename;
if($key === 'currentFilename') return $this->currentFilename;
if($key === 'halt') return $this->halt;
if($key === 'trim') return $this->trim;
if($value = parent::get($key)) return $value;
if(isset(self::$globals[$key])) return self::$globals[$key];
return null;
}
/**
* Set a property
*
* @param string $key
* @param mixed $value
* @return $this|WireData
*
*/
public function set($key, $value) {
if($key === 'halt') {
$this->halt($value);
return $this;
}
return parent::set($key, $value);
}
/**
* Push a filename onto the render stack
*
* #pw-internal
*
* @param string $filename
*
*/
public static function pushRenderStack($filename) {
self::$renderStack[] = $filename;
}
/**
* Pop last file off of render stack
*
* #pw-internal
*
* @return string|null item that was removed, or null if none found
*
*/
public static function popRenderStack() {
$result = array_pop(self::$renderStack);
return $result;
}
/**
* Get the current render stack
*
* This contains the files currently being rendered from first to last
*
* @return array
*
*/
public static function getRenderStack() {
return self::$renderStack;
}
/**
* Clear out all pending output buffers
*
* @since 3.0.175
* @return int Number of output buffers cleaned
*
*/
public static function clearAll() {
$n = 0;
if(self::$obStartLevel !== null) {
while(ob_get_level() > self::$obStartLevel) {
ob_end_clean();
$n++;
}
}
return $n;
}
/**
* The string value of a TemplateFile is its PHP template filename OR its class name if no filename is set
*
* @return string
*
*/
public function __toString() {
if(!$this->filename) return $this->className();
return $this->filename;
}
/**
* This method can be called by any template file to stop further render inclusions
*
* This is preferable to doing an exit; or die() from your template file(s), as it only halts the rendering
* of output and doesn't halt the rest of ProcessWire.
*
* Can be called from prepend/append files as well.
*
* USAGE from template file is: return $this->halt();
*
* @param bool $halt
* @return $this
*
*/
protected function halt($halt = true) {
$this->halt = $halt ? true : false;
return $this;
}
}