You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PlexShare/fuel/core/classes/view.php

646 lines
15 KiB

7 years ago
<?php
/**
* Part of the Fuel framework.
*
* @package Fuel
* @version 1.8
* @author Fuel Development Team
* @license MIT License
* @copyright 2010 - 2016 Fuel Development Team
* @link http://fuelphp.com
*/
namespace Fuel\Core;
/**
* View class
*
* Acts as an object wrapper for HTML pages with embedded PHP, called "views".
* Variables can be assigned with the view object and referenced locally within
* the view.
*
* @package Fuel
* @category Core
* @link http://docs.fuelphp.com/classes/view.html
*/
class View
{
/**
* @var array Global view data
*/
protected static $global_data = array();
/**
* @var array Holds a list of specific filter rules for global variables
*/
protected static $global_filter = array();
/**
* @var array Current active search paths
*/
protected $request_paths = array();
/**
* @var bool Whether to auto-filter the view's data
*/
protected $auto_filter = true;
/**
* @var bool Whether to filter closures
*/
protected $filter_closures = true;
/**
* @var array Holds a list of specific filter rules for local variables
*/
protected $local_filter = array();
/**
* @var string The view's filename
*/
protected $file_name = null;
/**
* @var array The view's data
*/
protected $data = array();
/**
* @var string The view file extension
*/
protected $extension = 'php';
/**
* @var Request active request when the View was created
*/
protected $active_request = null;
/**
* @var string active language at the time the object was created
*/
protected $active_language = null;
/**
* Returns a new View object. If you do not define the "file" parameter,
* you must call [static::set_filename].
*
* $view = View::forge($file);
*
* @param string $file view filename
* @param object $data array of values
* @param bool $auto_filter
* @return View
*/
public static function forge($file = null, $data = null, $auto_filter = null)
{
return new static($file, $data, $auto_filter);
}
/**
* Sets the initial view filename and local data.
*
* $view = new View($file);
*
* @param string $file view filename
* @param object $data array of values
* @param bool $filter
* @uses View::set_filename
*/
public function __construct($file = null, $data = null, $filter = null)
{
if (is_object($data) === true)
{
$data = get_object_vars($data);
}
elseif ($data and ! is_array($data))
{
throw new \InvalidArgumentException('The data parameter only accepts objects and arrays.');
}
$this->auto_filter = is_null($filter) ? \Config::get('security.auto_filter_output', true) : $filter;
$this->filter_closures = \Config::get('filter_closures', true);
if ($file !== null)
{
$this->set_filename($file);
}
if ($data !== null)
{
// Add the values to the current data
$this->data = $data;
}
// store the current request search paths to deal with out-of-context rendering
if (class_exists('Request', false) and $active = \Request::active() and \Request::main() !== $active)
{
$this->request_paths = $active->get_paths();
}
isset($active) and $this->active_request = $active;
// store the active language, so we can render the view in the correct language later
$this->active_language = \Config::get('language', 'en');
}
/**
* Magic method, searches for the given variable and returns its value.
* Local variables will be returned before global variables.
*
* $value = $view->foo;
*
* @param string $key variable name
* @return mixed
* @throws \OutOfBoundsException
*/
public function & __get($key)
{
return $this->get($key);
}
/**
* Magic method, calls [static::set] with the same parameters.
*
* $view->foo = 'something';
*
* @param string $key variable name
* @param mixed $value value
* @return void
*/
public function __set($key, $value)
{
$this->set($key, $value);
}
/**
* Magic method, determines if a variable is set.
*
* isset($view->foo);
*
* [!!] `null` variables are not considered to be set by [isset](http://php.net/isset).
*
* @param string $key variable name
* @return boolean
*/
public function __isset($key)
{
return (isset($this->data[$key]) or isset(static::$global_data[$key]));
}
/**
* Magic method, unsets a given variable.
*
* unset($view->foo);
*
* @param string $key variable name
* @return void
*/
public function __unset($key)
{
unset($this->data[$key], static::$global_data[$key]);
}
/**
* Magic method, returns the output of [static::render].
*
* @return string
* @uses View::render
*/
public function __toString()
{
try
{
return $this->render();
}
catch (\Exception $e)
{
\Errorhandler::exception_handler($e);
return '';
}
}
/**
* Captures the output that is generated when a view is included.
* The view data will be extracted to make local variables.
*
* $output = $this->process_file();
*
* @param bool $file_override File override
* @return string
*/
protected function process_file($file_override = false)
{
$clean_room = function($__file_name, array $__data)
{
extract($__data, EXTR_REFS);
// Capture the view output
ob_start();
try
{
// Load the view within the current scope
include $__file_name;
}
catch (\Exception $e)
{
// Delete the output buffer
ob_end_clean();
// Re-throw the exception
throw $e;
}
// Get the captured output and close the buffer
return ob_get_clean();
};
// import and process the view file
$result = $clean_room($file_override ?: $this->file_name, $data = $this->get_data());
// disable sanitization on objects that support it
$this->unsanitize($data);
// return the result
return $result;
}
/**
* Retrieves all the data, both local and global. It filters the data if
* necessary.
*
* $data = $this->get_data();
*
* @param string $scope local/glocal/all
* @return array view data
*/
protected function get_data($scope = 'all')
{
$filter_closures = $this->filter_closures;
$clean_it = function ($data, $rules, $auto_filter) use ($filter_closures)
{
foreach ($data as $key => &$value)
{
$filter = array_key_exists($key, $rules) ? $rules[$key] : null;
$filter = is_null($filter) ? $auto_filter : $filter;
if ($filter)
{
if ($filter_closures and $value instanceOf \Closure)
{
$value = $value();
}
$value = \Security::clean($value, null, 'security.output_filter');
}
}
return $data;
};
$data = array();
if ( ! empty($this->data) and ($scope === 'all' or $scope === 'local'))
{
$data += $clean_it($this->data, $this->local_filter, $this->auto_filter);
}
if ( ! empty(static::$global_data) and ($scope === 'all' or $scope === 'global'))
{
$data += $clean_it(static::$global_data, static::$global_filter, $this->auto_filter);
}
return $data;
}
/**
* disable sanitation on any objects in the data that support it
*
* @param mixed
* @return mixed
*/
protected function unsanitize($var)
{
// deal with objects that can be sanitized
if ($var instanceOf \Sanitization)
{
$var->unsanitize();
}
// deal with array's or array emulating objects
elseif (is_array($var) or ($var instanceOf \Traversable and $var instanceOf \ArrayAccess))
{
// recurse on array values
foreach($var as $key => $value)
{
$var[$key] = $this->unsanitize($value);
}
}
}
/**
* Sets a global variable, similar to [static::set], except that the
* variable will be accessible to all views.
*
* View::set_global($name, $value);
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @param bool $filter whether to filter the data or not
* @return void
*/
public static function set_global($key, $value = null, $filter = null)
{
if (is_array($key))
{
foreach ($key as $name => $value)
{
if ($filter !== null)
{
static::$global_filter[$name] = $filter;
}
static::$global_data[$name] = $value;
}
}
else
{
if ($filter !== null)
{
static::$global_filter[$key] = $filter;
}
static::$global_data[$key] = $value;
}
}
/**
* Assigns a global variable by reference, similar to [static::bind], except
* that the variable will be accessible to all views.
*
* View::bind_global($key, $value);
*
* @param string $key variable name
* @param mixed $value referenced variable
* @param bool $filter whether to filter the data or not
* @return void
*/
public static function bind_global($key, &$value, $filter = null)
{
if ($filter !== null)
{
static::$global_filter[$key] = $filter;
}
static::$global_data[$key] =& $value;
}
/**
* Sets whether to filter the data or not.
*
* $view->auto_filter(false);
*
* @param bool $filter whether to auto filter or not
* @return View
*/
public function auto_filter($filter = true)
{
if (func_num_args() == 0)
{
return $this->auto_filter;
}
$this->auto_filter = $filter;
return $this;
}
/**
* Sets the view filename.
*
* $view->set_filename($file);
*
* @param string $file view filename
* @return View
* @throws \FuelException
*/
public function set_filename($file)
{
// strip the extension from it
$pathinfo = pathinfo($file);
if ( ! empty($pathinfo['extension']))
{
$this->extension = $pathinfo['extension'];
$file = substr($file, 0, strlen($this->extension)*-1 - 1);
}
// set find_file's one-time-only search paths
\Finder::instance()->flash($this->request_paths);
// locate the view file
if (($path = \Finder::search('views', $file, '.'.$this->extension, false, false)) === false)
{
throw new \FuelException('The requested view could not be found: '.\Fuel::clean_path($file).'.'.$this->extension);
}
// Store the file path locally
$this->file_name = $path;
return $this;
}
/**
* Searches for the given variable and returns its value.
* Local variables will be returned before global variables.
*
* $value = $view->get('foo', 'bar');
*
* If the key is not given or null, the entire data array is returned.
*
* If a default parameter is not given and the variable does not
* exist, it will throw an OutOfBoundsException.
*
* @param string $key The variable name
* @param mixed $default The default value to return
* @return mixed
* @throws \OutOfBoundsException
*/
public function &get($key = null, $default = null)
{
if (func_num_args() === 0 or $key === null)
{
return $this->data;
}
elseif (strpos($key, '.') === false)
{
if (array_key_exists($key, $this->data))
{
return $this->data[$key];
}
elseif (array_key_exists($key, static::$global_data))
{
return static::$global_data[$key];
}
}
else
{
if (($result = \Arr::get($this->data, $key, \Arr::get(static::$global_data, $key, '__KEY__LOOKUP__MISS__'))) !== '__KEY__LOOKUP__MISS__')
{
return $result;
}
}
if (is_null($default) and func_num_args() === 1)
{
throw new \OutOfBoundsException('View variable is not set: '.$key);
}
else
{
// assign it first, you can't return a return value by reference directly!
$default = \Fuel::value($default);
return $default;
}
}
/**
* Assigns a variable by name. Assigned values will be available as a
* variable within the view file:
*
* // This value can be accessed as $foo within the view
* $view->set('foo', 'my value');
*
* You can also use an array to set several values at once:
*
* // Create the values $food and $beverage in the view
* $view->set(array('food' => 'bread', 'beverage' => 'water'));
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @param bool $filter whether to filter the data or not
* @return $this
*/
public function set($key, $value = null, $filter = null)
{
if (is_array($key))
{
foreach ($key as $name => $value)
{
$this->set($name, $value, $filter);
}
}
else
{
if ($filter !== null)
{
$this->local_filter[$key] = $filter;
}
if (strpos($key, '.') === false)
{
$this->data[$key] = $value;
}
else
{
\Arr::set($this->data, $key, $value);
}
}
return $this;
}
/**
* The same as set(), except this defaults to not-encoding the variable
* on output.
*
* $view->set_safe('foo', 'bar');
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @return $this
*/
public function set_safe($key, $value = null)
{
return $this->set($key, $value, false);
}
/**
* Assigns a value by reference. The benefit of binding is that values can
* be altered without re-setting them. It is also possible to bind variables
* before they have values. Assigned values will be available as a
* variable within the view file:
*
* // This reference can be accessed as $ref within the view
* $view->bind('ref', $bar);
*
* @param string $key variable name
* @param mixed $value referenced variable
* @param bool $filter Whether to filter the var on output
* @return $this
*/
public function bind($key, &$value, $filter = null)
{
if ($filter !== null)
{
$this->local_filter[$key] = $filter;
}
$this->data[$key] =& $value;
return $this;
}
/**
* Renders the view object to a string. Global and local data are merged
* and extracted to create local variables within the view file.
*
* $output = $view->render();
*
* [!!] Global variables with the same key name as local variables will be
* overwritten by the local variable.
*
* @param $file string view filename
* @return string
* @throws \FuelException
* @uses static::capture
*/
public function render($file = null)
{
// reactivate the correct request
if (class_exists('Request', false))
{
$current_request = \Request::active();
\Request::active($this->active_request);
}
// store the current language, and set the correct render language
if ($this->active_language)
{
$current_language = \Config::get('language', 'en');
\Config::set('language', $this->active_language);
}
// override the view filename if needed
if ($file !== null)
{
$this->set_filename($file);
}
// and make sure we have one
if (empty($this->file_name))
{
throw new \FuelException('You must set the file to use within your view before rendering');
}
// combine local and global data and capture the output
$return = $this->process_file();
// restore the current language setting
$this->active_language and \Config::set('language', $current_language);
// and the active request class
if (isset($current_request))
{
\Request::active($current_request);
}
return $return;
}
}