First Local Commit - After Clean up.

Signed-off-by: Rick Hays <rhays@haysgang.com>
This commit is contained in:
2019-12-02 14:54:38 -06:00
commit 10412ab7f6
486 changed files with 123242 additions and 0 deletions

275
system/View/Cell.php Normal file
View File

@@ -0,0 +1,275 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\View;
use CodeIgniter\Cache\CacheInterface;
use CodeIgniter\View\Exceptions\ViewException;
use ReflectionMethod;
/**
* Class Cell
*
* A simple class that can call any other class that can be loaded,
* and echo out it's result. Intended for displaying small blocks of
* content within views that can be managed by other libraries and
* not require they are loaded within controller.
*
* Used with the helper function, it's use will look like:
*
* viewCell('\Some\Class::method', 'limit=5 sort=asc', 60, 'cache-name');
*
* Parameters are matched up with the callback method's arguments of the same name:
*
* class Class {
* function method($limit, $sort)
* }
*
* Alternatively, the params will be passed into the callback method as a simple array
* if matching params are not found.
*
* class Class {
* function method(array $params=null)
* }
*
* @package CodeIgniter\View
*/
class Cell
{
/**
* Instance of the current Cache Instance
*
* @var CacheInterface
*/
protected $cache;
//--------------------------------------------------------------------
/**
* Cell constructor.
*
* @param \CodeIgniter\Cache\CacheInterface $cache
*/
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
//--------------------------------------------------------------------
/**
* Render a cell, returning its body as a string.
*
* @param string $library
* @param null $params
* @param integer $ttl
* @param string|null $cacheName
*
* @return string
* @throws \ReflectionException
*/
public function render(string $library, $params = null, int $ttl = 0, string $cacheName = null): string
{
list($class, $method) = $this->determineClass($library);
// Is it cached?
$cacheName = ! empty($cacheName) ? $cacheName : $class . $method . md5(serialize($params));
if (! empty($this->cache) && $output = $this->cache->get($cacheName))
{
return $output;
}
// Not cached - so grab it...
$instance = new $class();
if (! method_exists($instance, $method))
{
throw ViewException::forInvalidCellMethod($class, $method);
}
// Try to match up the parameter list we were provided
// with the parameter name in the callback method.
$paramArray = $this->prepareParams($params);
$refMethod = new ReflectionMethod($instance, $method);
$paramCount = $refMethod->getNumberOfParameters();
$refParams = $refMethod->getParameters();
if ($paramCount === 0)
{
if (! empty($paramArray))
{
throw ViewException::forMissingCellParameters($class, $method);
}
$output = $instance->{$method}();
}
elseif (($paramCount === 1) && (
( ! array_key_exists($refParams[0]->name, $paramArray)) ||
(array_key_exists($refParams[0]->name, $paramArray) && count($paramArray) !== 1) )
)
{
$output = $instance->{$method}($paramArray);
}
else
{
$fireArgs = [];
$method_params = [];
foreach ($refParams as $arg)
{
$method_params[$arg->name] = true;
if (array_key_exists($arg->name, $paramArray))
{
$fireArgs[$arg->name] = $paramArray[$arg->name];
}
}
foreach ($paramArray as $key => $val)
{
if (! isset($method_params[$key]))
{
throw ViewException::forInvalidCellParameter($key);
}
}
$output = $instance->$method(...array_values($fireArgs));
}
// Can we cache it?
if (! empty($this->cache) && $ttl !== 0)
{
$this->cache->save($cacheName, $output, $ttl);
}
return $output;
}
//--------------------------------------------------------------------
/**
* Parses the params attribute. If an array, returns untouched.
* If a string, it should be in the format "key1=value key2=value".
* It will be split and returned as an array.
*
* @param $params
*
* @return array|null
*/
public function prepareParams($params)
{
if (empty($params) || ( ! is_string($params) && ! is_array($params)))
{
return [];
}
if (is_string($params))
{
$new_params = [];
$separator = ' ';
if (strpos($params, ',') !== false)
{
$separator = ',';
}
$params = explode($separator, $params);
unset($separator);
foreach ($params as $p)
{
if (! empty($p))
{
list($key, $val) = explode('=', $p);
$new_params[trim($key)] = trim($val, ', ');
}
}
$params = $new_params;
unset($new_params);
}
if (is_array($params) && empty($params))
{
return [];
}
return $params;
}
//--------------------------------------------------------------------
/**
* Given the library string, attempts to determine the class and method
* to call.
*
* @param string $library
*
* @return array
*/
protected function determineClass(string $library): array
{
// We don't want to actually call static methods
// by default, so convert any double colons.
$library = str_replace('::', ':', $library);
list($class, $method) = explode(':', $library);
if (empty($class))
{
throw ViewException::forNoCellClass();
}
if (! class_exists($class, true))
{
throw ViewException::forInvalidCellClass($class);
}
if (empty($method))
{
$method = 'index';
}
return [
$class,
$method,
];
}
//--------------------------------------------------------------------
}

View File

@@ -0,0 +1,37 @@
<?php namespace CodeIgniter\View\Exceptions;
use CodeIgniter\Exceptions\ExceptionInterface;
use CodeIgniter\Exceptions\FrameworkException;
class ViewException extends FrameworkException implements ExceptionInterface
{
public static function forInvalidCellMethod(string $class, string $method)
{
return new static(lang('View.invalidCellMethod', ['class' => $class, 'method' => $method]));
}
public static function forMissingCellParameters(string $class, string $method)
{
return new static(lang('View.missingCellParameters', ['class' => $class, 'method' => $method]));
}
public static function forInvalidCellParameter(string $key)
{
return new static(lang('View.invalidCellParameter', [$key]));
}
public static function forNoCellClass()
{
return new static(lang('View.noCellClass'));
}
public static function forInvalidCellClass(string $class = null)
{
return new static(lang('View.invalidCellClass', [$class]));
}
public static function forTagSyntaxError(string $output)
{
return new static(lang('View.tagSyntaxError', [$output]));
}
}

362
system/View/Filters.php Normal file
View File

@@ -0,0 +1,362 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\View;
use Config\Services;
use NumberFormatter;
/**
* View filters
*/
class Filters
{
/**
* Returns $value as all lowercase with the first letter capitalized.
*
* @param $value
*
* @return string
*/
public static function capitalize(string $value): string
{
return ucfirst(strtolower($value));
}
//--------------------------------------------------------------------
/**
* Formats a date into the given $format.
*
* @param $value
* @param string $format
*
* @return string
*/
public static function date($value, string $format): string
{
if (is_string($value) && ! is_numeric($value))
{
$value = strtotime($value);
}
return date($format, $value);
}
//--------------------------------------------------------------------
/**
* Given a string or DateTime object, will return the date modified
* by the given value. Returns the value as a unix timestamp
*
* Example:
* my_date|date_modify(+1 day)
*
* @param $value
* @param string $adjustment
*
* @return string
* @internal param string $format
*/
public static function date_modify($value, string $adjustment): string
{
$value = static::date($value, 'Y-m-d H:i:s');
return strtotime($adjustment, strtotime($value));
}
//--------------------------------------------------------------------
/**
* Returns the given default value if $value is empty or undefined.
*
* @param $value
* @param string $default
*
* @return string
*/
public static function default($value, string $default): string
{
return empty($value)
? $default
: $value;
}
//--------------------------------------------------------------------
/**
* Escapes the given value with our `esc()` helper function.
*
* @param $value
* @param string $context
*
* @return string
*/
public static function esc($value, string $context = 'html'): string
{
return esc($value, $context);
}
//--------------------------------------------------------------------
/**
* Returns an excerpt of the given string.
*
* @param string $value
* @param string $phrase
* @param integer $radius
*
* @return string
*/
public static function excerpt(string $value, string $phrase, int $radius = 100): string
{
helper('text');
return excerpt($value, $phrase, $radius);
}
//--------------------------------------------------------------------
/**
* Highlights a given phrase within the text using '<mark></mark>' tags.
*
* @param string $value
* @param string $phrase
*
* @return string
*/
public static function highlight(string $value, string $phrase): string
{
helper('text');
return highlight_phrase($value, $phrase);
}
//--------------------------------------------------------------------
/**
* Highlights code samples with HTML/CSS.
*
* @param $value
*
* @return string
*/
public static function highlight_code($value): string
{
helper('text');
return highlight_code($value);
}
//--------------------------------------------------------------------
/**
* Limits the number of characters to $limit, and trails of with an ellipsis.
* Will break at word break so may be more or less than $limit.
*
* @param $value
* @param integer $limit
*
* @return string
*/
public static function limit_chars($value, int $limit = 500): string
{
helper('text');
return character_limiter($value, $limit);
}
//--------------------------------------------------------------------
/**
* Limits the number of words to $limit, and trails of with an ellipsis.
*
* @param $value
* @param integer $limit
*
* @return string
*/
public static function limit_words($value, int $limit = 100): string
{
helper('text');
return word_limiter($value, $limit);
}
//--------------------------------------------------------------------
/**
* Returns the $value displayed in a localized manner.
*
* @param $value
* @param integer $precision
* @param string $type
* @param string|null $locale
*
* @return string
*/
public static function local_number($value, string $type = 'decimal', int $precision = 4, string $locale = null): string
{
helper('number');
$types = [
'decimal' => NumberFormatter::DECIMAL,
'currency' => NumberFormatter::CURRENCY,
'percent' => NumberFormatter::PERCENT,
'scientific' => NumberFormatter::SCIENTIFIC,
'spellout' => NumberFormatter::SPELLOUT,
'ordinal' => NumberFormatter::ORDINAL,
'duration' => NumberFormatter::DURATION,
];
return format_number($value, $precision, $locale, ['type' => $types[$type]]);
}
//--------------------------------------------------------------------
/**
* Returns the $value displayed as a currency string.
*
* @param $value
* @param string $currency
* @param string|null $locale
*
* @return string
*/
public static function local_currency($value, string $currency, string $locale = null): string
{
helper('number');
$options = [
'type' => NumberFormatter::CURRENCY,
'currency' => $currency,
];
return format_number($value, 2, $locale, $options);
}
/**
* Returns a string with all instances of newline character (\n)
* converted to an HTML <br/> tag.
*
* @param string $value
*
* @return string
*/
public static function nl2br(string $value): string
{
$typography = Services::typography();
return $typography->nl2brExceptPre($value);
}
//--------------------------------------------------------------------
/**
* Takes a body of text and uses the auto_typography() method to
* turn it into prettier, easier-to-read, prose.
*
* @param string $value
*
* @return string
*/
public static function prose(string $value): string
{
$typography = Services::typography();
return $typography->autoTypography($value);
}
//--------------------------------------------------------------------
/**
* Rounds a given $value in one of 3 ways;
*
* - common Normal rounding
* - ceil always rounds up
* - floor always rounds down
*
* @param string $value
* @param mixed $precision
* @param string $type
*
* @return string
*/
public static function round(string $value, $precision = 2, string $type = 'common'): string
{
if (! is_numeric($precision))
{
$type = $precision;
$precision = 2;
}
switch ($type)
{
case 'common':
return round($value, $precision);
break;
case 'ceil':
return ceil($value);
break;
case 'floor':
return floor($value);
break;
}
// Still here, just return the value.
return $value;
}
//--------------------------------------------------------------------
/**
* Returns a "title case" version of the string.
*
* @param string $value
*
* @return string
*/
public static function title(string $value): string
{
return ucwords(strtolower($value));
}
//--------------------------------------------------------------------
}

854
system/View/Parser.php Normal file
View File

@@ -0,0 +1,854 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\View;
use CodeIgniter\Log\Logger;
use CodeIgniter\View\Exceptions\ViewException;
/**
* Class Parser
*
* ClassFormerlyKnownAsTemplateParser
*
* @package CodeIgniter\View
*/
class Parser extends View
{
/**
* Left delimiter character for pseudo vars
*
* @var string
*/
public $leftDelimiter = '{';
/**
* Right delimiter character for pseudo vars
*
* @var string
*/
public $rightDelimiter = '}';
/**
* Stores extracted noparse blocks.
*
* @var array
*/
protected $noparseBlocks = [];
/**
* Stores any plugins registered at run-time.
*
* @var array
*/
protected $plugins = [];
/**
* Stores the context for each data element
* when set by `setData` so the context is respected.
*
* @var array
*/
protected $dataContexts = [];
//--------------------------------------------------------------------
/**
* Constructor
*
* @param \Config\View $config
* @param string $viewPath
* @param mixed $loader
* @param boolean $debug
* @param Logger $logger
*/
public function __construct($config, string $viewPath = null, $loader = null, bool $debug = null, Logger $logger = null)
{
// Ensure user plugins override core plugins.
$this->plugins = $config->plugins ?? [];
parent::__construct($config, $viewPath, $loader, $debug, $logger);
}
//--------------------------------------------------------------------
/**
* Parse a template
*
* Parses pseudo-variables contained in the specified template view,
* replacing them with any data that has already been set.
*
* @param string $view
* @param array $options
* @param boolean $saveData
*
* @return string
*/
public function render(string $view, array $options = null, bool $saveData = null): string
{
$start = microtime(true);
if (is_null($saveData))
{
$saveData = $this->config->saveData;
}
$fileExt = pathinfo($view, PATHINFO_EXTENSION);
$view = empty($fileExt) ? $view . '.php' : $view; // allow Views as .html, .tpl, etc (from CI3)
// Was it cached?
if (isset($options['cache']))
{
$cacheName = $options['cache_name'] ?? str_replace('.php', '', $view);
if ($output = cache($cacheName))
{
$this->logPerformance($start, microtime(true), $view);
return $output;
}
}
$file = $this->viewPath . $view;
if (! is_file($file))
{
$file = $this->loader->locateFile($view, 'Views');
}
// locateFile will return an empty string if the file cannot be found.
if (empty($file))
{
throw ViewException::forInvalidFile($file);
}
$template = file_get_contents($file);
$output = $this->parse($template, $this->data, $options);
$this->logPerformance($start, microtime(true), $view);
if (! $saveData)
{
$this->data = [];
}
// Should we cache?
if (isset($options['cache']))
{
cache()->save($cacheName, $output, (int) $options['cache']);
}
return $output;
}
//--------------------------------------------------------------------
/**
* Parse a String
*
* Parses pseudo-variables contained in the specified string,
* replacing them with any data that has already been set.
*
* @param string $template
* @param array $options
* @param boolean $saveData
*
* @return string
*/
public function renderString(string $template, array $options = null, bool $saveData = null): string
{
$start = microtime(true);
if (is_null($saveData))
{
$saveData = $this->config->saveData;
}
$output = $this->parse($template, $this->data, $options);
$this->logPerformance($start, microtime(true), $this->excerpt($template));
if (! $saveData)
{
$this->data = [];
}
return $output;
}
//--------------------------------------------------------------------
/**
* Sets several pieces of view data at once.
* In the Parser, we need to store the context here
* so that the variable is correctly handled within the
* parsing itself, and contexts (including raw) are respected.
*
* @param array $data
* @param string $context The context to escape it for: html, css, js, url, raw
* If 'raw', no escaping will happen
*
* @return RendererInterface
*/
public function setData(array $data = [], string $context = null): RendererInterface
{
if (! empty($context))
{
foreach ($data as $key => &$value)
{
if (is_array($value))
{
foreach ($value as &$obj)
{
$obj = $this->objectToArray($obj);
}
}
else
{
$value = $this->objectToArray($value);
}
$this->dataContexts[$key] = $context;
}
}
$this->data = array_merge($this->data, $data);
return $this;
}
//--------------------------------------------------------------------
/**
* Parse a template
*
* Parses pseudo-variables contained in the specified template,
* replacing them with the data in the second param
*
* @param string $template
* @param array $data
* @param array $options Future options
* @return string
*/
protected function parse(string $template, array $data = [], array $options = null): string
{
if ($template === '')
{
return '';
}
// Remove any possible PHP tags since we don't support it
// and parseConditionals needs it clean anyway...
$template = str_replace(['<?', '?>'], ['&lt;?', '?&gt;'], $template);
$template = $this->parseComments($template);
$template = $this->extractNoparse($template);
// Replace any conditional code here so we don't have to parse as much
$template = $this->parseConditionals($template);
// Handle any plugins before normal data, so that
// it can potentially modify any template between its tags.
$template = $this->parsePlugins($template);
// loop over the data variables, replacing
// the content as we go.
foreach ($data as $key => $val)
{
$escape = true;
if (is_array($val))
{
$escape = false;
$replace = $this->parsePair($key, $val, $template);
}
else
{
$replace = $this->parseSingle($key, (string) $val);
}
foreach ($replace as $pattern => $content)
{
$template = $this->replaceSingle($pattern, $content, $template, $escape);
}
}
$template = $this->insertNoparse($template);
return $template;
}
//--------------------------------------------------------------------
/**
* Parse a single key/value, extracting it
*
* @param string $key
* @param string $val
* @return array
*/
protected function parseSingle(string $key, string $val): array
{
$pattern = '#' . $this->leftDelimiter . '!?\s*' . preg_quote($key) . '\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*!?' . $this->rightDelimiter . '#ms';
return [$pattern => $val];
}
//--------------------------------------------------------------------
/**
* Parse a tag pair
*
* Parses tag pairs: {some_tag} string... {/some_tag}
*
* @param string $variable
* @param array $data
* @param string $template
* @return array
*/
protected function parsePair(string $variable, array $data, string $template): array
{
// Holds the replacement patterns and contents
// that will be used within a preg_replace in parse()
$replace = [];
// Find all matches of space-flexible versions of {tag}{/tag} so we
// have something to loop over.
preg_match_all(
'#' . $this->leftDelimiter . '\s*' . preg_quote($variable) . '\s*' . $this->rightDelimiter . '(.+?)' .
$this->leftDelimiter . '\s*' . '/' . preg_quote($variable) . '\s*' . $this->rightDelimiter . '#s', $template, $matches, PREG_SET_ORDER
);
/*
* Each match looks like:
*
* $match[0] {tag}...{/tag}
* $match[1] Contents inside the tag
*/
foreach ($matches as $match)
{
// Loop over each piece of $data, replacing
// it's contents so that we know what to replace in parse()
$str = ''; // holds the new contents for this tag pair.
foreach ($data as $row)
{
// Objects that have a `toArray()` method should be
// converted with that method (i.e. Entities)
if (is_object($row) && method_exists($row, 'toArray'))
{
$row = $row->toArray();
}
// Otherwise, cast as an array and it will grab public properties.
else if (is_object($row))
{
$row = (array)$row;
}
$temp = [];
$pairs = [];
$out = $match[1];
foreach ($row as $key => $val)
{
// For nested data, send us back through this method...
if (is_array($val))
{
$pair = $this->parsePair($key, $val, $match[1]);
if (! empty($pair))
{
$pairs[array_keys( $pair )[0]] = true;
$temp = array_merge($temp, $pair);
}
continue;
}
else if (is_object($val))
{
$val = 'Class: ' . get_class($val);
}
else if (is_resource($val))
{
$val = 'Resource';
}
$temp['#' . $this->leftDelimiter . '!?\s*' . preg_quote($key) . '\s*\|*\s*([|\w<>=\(\),:_\-\s\+]+)*\s*!?' . $this->rightDelimiter . '#s'] = $val;
}
// Now replace our placeholders with the new content.
foreach ($temp as $pattern => $content)
{
$out = $this->replaceSingle($pattern, $content, $out, ! isset( $pairs[$pattern] ) );
}
$str .= $out;
}
$replace['#' . $match[0] . '#s'] = $str;
}
return $replace;
}
//--------------------------------------------------------------------
/**
* Removes any comments from the file. Comments are wrapped in {# #} symbols:
*
* {# This is a comment #}
*
* @param string $template
*
* @return string
*/
protected function parseComments(string $template): string
{
return preg_replace('/\{#.*?#\}/s', '', $template);
}
//--------------------------------------------------------------------
/**
* Extracts noparse blocks, inserting a hash in its place so that
* those blocks of the page are not touched by parsing.
*
* @param string $template
*
* @return string
*/
protected function extractNoparse(string $template): string
{
$pattern = '/\{\s*noparse\s*\}(.*?)\{\s*\/noparse\s*\}/ms';
/*
* $matches[][0] is the raw match
* $matches[][1] is the contents
*/
if (preg_match_all($pattern, $template, $matches, PREG_SET_ORDER))
{
foreach ($matches as $match)
{
// Create a hash of the contents to insert in its place.
$hash = md5($match[1]);
$this->noparseBlocks[$hash] = $match[1];
$template = str_replace($match[0], "noparse_{$hash}", $template);
}
}
return $template;
}
//--------------------------------------------------------------------
/**
* Re-inserts the noparsed contents back into the template.
*
* @param string $template
*
* @return string
*/
public function insertNoparse(string $template): string
{
foreach ($this->noparseBlocks as $hash => $replace)
{
$template = str_replace("noparse_{$hash}", $replace, $template);
unset($this->noparseBlocks[$hash]);
}
return $template;
}
//--------------------------------------------------------------------
/**
* Parses any conditionals in the code, removing blocks that don't
* pass so we don't try to parse it later.
*
* Valid conditionals:
* - if
* - elseif
* - else
*
* @param string $template
*
* @return string
*/
protected function parseConditionals(string $template): string
{
$pattern = '/\{\s*(if|elseif)\s*((?:\()?(.*?)(?:\))?)\s*\}/ms';
/**
* For each match:
* [0] = raw match `{if var}`
* [1] = conditional `if`
* [2] = condition `do === true`
* [3] = same as [2]
*/
preg_match_all($pattern, $template, $matches, PREG_SET_ORDER);
foreach ($matches as $match)
{
// Build the string to replace the `if` statement with.
$condition = $match[2];
$statement = $match[1] === 'elseif' ? '<?php elseif (' . $condition . '): ?>' : '<?php if (' . $condition . '): ?>';
$template = str_replace($match[0], $statement, $template);
}
$template = preg_replace('/\{\s*else\s*\}/ms', '<?php else: ?>', $template);
$template = preg_replace('/\{\s*endif\s*\}/ms', '<?php endif; ?>', $template);
// Parse the PHP itself, or insert an error so they can debug
ob_start();
extract($this->data);
try
{
eval('?>' . $template . '<?php ');
}
catch (\ParseError $e)
{
ob_end_clean();
throw ViewException::forTagSyntaxError(str_replace(['?>', '<?php '], '', $template));
}
return ob_get_clean();
}
//--------------------------------------------------------------------
/**
* Over-ride the substitution field delimiters.
*
* @param string $leftDelimiter
* @param string $rightDelimiter
* @return RendererInterface
*/
public function setDelimiters($leftDelimiter = '{', $rightDelimiter = '}'): RendererInterface
{
$this->leftDelimiter = $leftDelimiter;
$this->rightDelimiter = $rightDelimiter;
return $this;
}
//--------------------------------------------------------------------
/**
* Handles replacing a pseudo-variable with the actual content. Will double-check
* for escaping brackets.
*
* @param $pattern
* @param $content
* @param $template
* @param boolean $escape
*
* @return string
*/
protected function replaceSingle($pattern, $content, $template, bool $escape = false): string
{
// Any dollar signs in the pattern will be mis-interpreted, so slash them
$pattern = addcslashes($pattern, '$');
// Replace the content in the template
$template = preg_replace_callback($pattern, function ($matches) use ($content, $escape) {
// Check for {! !} syntax to not-escape this one.
if (strpos($matches[0], '{!') === 0 && substr($matches[0], -2) === '!}')
{
$escape = false;
}
return $this->prepareReplacement($matches, $content, $escape);
}, $template);
return $template;
}
//--------------------------------------------------------------------
/**
* Callback used during parse() to apply any filters to the value.
*
* @param array $matches
* @param string $replace
* @param boolean $escape
*
* @return string
*/
protected function prepareReplacement(array $matches, string $replace, bool $escape = true): string
{
$orig = array_shift($matches);
// Our regex earlier will leave all chained values on a single line
// so we need to break them apart so we can apply them all.
$filters = isset($matches[0]) ? explode('|', $matches[0]) : [];
if ($escape && ! isset($matches[0]))
{
if ($context = $this->shouldAddEscaping($orig))
{
$filters[] = "esc({$context})";
}
}
$replace = $this->applyFilters($replace, $filters);
return $replace;
}
//--------------------------------------------------------------------
/**
* Checks the placeholder the view provided to see if we need to provide any autoescaping.
*
* @param string $key
*
* @return false|string
*/
public function shouldAddEscaping(string $key)
{
$escape = false;
$key = trim(str_replace(['{', '}'], '', $key));
// If the key has a context stored (from setData)
// we need to respect that.
if (array_key_exists($key, $this->dataContexts))
{
if ($this->dataContexts[$key] !== 'raw')
{
return $this->dataContexts[$key];
}
}
// No pipes, then we know we need to escape
elseif (strpos($key, '|') === false)
{
$escape = 'html';
}
// If there's a `noescape` then we're definitely false.
elseif (strpos($key, 'noescape') !== false)
{
$escape = false;
}
// If no `esc` filter is found, then we'll need to add one.
elseif (! preg_match('/\s+esc/', $key))
{
$escape = 'html';
}
return $escape;
}
//--------------------------------------------------------------------
/**
* Given a set of filters, will apply each of the filters in turn
* to $replace, and return the modified string.
*
* @param string $replace
* @param array $filters
*
* @return string
*/
protected function applyFilters(string $replace, array $filters): string
{
// Determine the requested filters
foreach ($filters as $filter)
{
// Grab any parameter we might need to send
preg_match('/\([a-zA-Z0-9\-:_ +,<>=]+\)/', $filter, $param);
// Remove the () and spaces to we have just the parameter left
$param = ! empty($param) ? trim($param[0], '() ') : null;
// Params can be separated by commas to allow multiple parameters for the filter
if (! empty($param))
{
$param = explode(',', $param);
// Clean it up
foreach ($param as &$p)
{
$p = trim($p, ' "');
}
}
else
{
$param = [];
}
// Get our filter name
$filter = ! empty($param) ? trim(strtolower(substr($filter, 0, strpos($filter, '(')))) : trim($filter);
if (! array_key_exists($filter, $this->config->filters))
{
continue;
}
// Filter it....
$replace = $this->config->filters[$filter]($replace, ...$param);
}
return $replace;
}
//--------------------------------------------------------------------
// Plugins
//--------------------------------------------------------------------
/**
* Scans the template for any parser plugins, and attempts to execute them.
* Plugins are notated based on {+ +} opening and closing braces.
*
* When encountered,
*
* @param string $template
*
* @return string
*/
protected function parsePlugins(string $template)
{
foreach ($this->plugins as $plugin => $callable)
{
// Paired tags are enclosed in an array in the config array.
$isPair = is_array($callable);
$callable = $isPair ? array_shift($callable) : $callable;
$pattern = $isPair ? '#{\+\s*' . $plugin . '([\w\d=-_:\+\s()/\"@.]*)?\s*\+}(.+?){\+\s*/' . $plugin . '\s*\+}#ims' : '#{\+\s*' . $plugin . '([\w\d=-_:\+\s()/\"@.]*)?\s*\+}#ims';
/**
* Match tag pairs
*
* Each match is an array:
* $matches[0] = entire matched string
* $matches[1] = all parameters string in opening tag
* $matches[2] = content between the tags to send to the plugin.
*/
preg_match_all($pattern, $template, $matches, PREG_SET_ORDER);
if (empty($matches))
{
continue;
}
foreach ($matches as $match)
{
$params = [];
// Split on "words", but keep quoted groups together, accounting for escaped quotes.
// Note: requires double quotes, not single quotes.
$parts = str_getcsv($match[1], ' ');
foreach ($parts as $part)
{
if (empty($part))
{
continue;
}
if (strpos($part, '=') !== false)
{
list($a, $b) = explode('=', $part);
$params[$a] = $b;
}
else
{
$params[] = $part;
}
}
unset($parts);
$template = $isPair ? str_replace($match[0], $callable($match[2], $params), $template) : str_replace($match[0], $callable($params), $template);
}
}
return $template;
}
/**
* Makes a new plugin available during the parsing of the template.
*
* @param string $alias
* @param callable $callback
*
* @param boolean $isPair
*
* @return $this
*/
public function addPlugin(string $alias, callable $callback, bool $isPair = false)
{
$this->plugins[$alias] = $isPair ? [$callback] : $callback;
return $this;
}
//--------------------------------------------------------------------
/**
* Removes a plugin from the available plugins.
*
* @param string $alias
*
* @return $this
*/
public function removePlugin(string $alias)
{
unset($this->plugins[$alias]);
return $this;
}
/**
* Converts an object to an array, respecting any
* toArray() methods on an object.
*
* @param $value
*
* @return mixed
*/
protected function objectToArray($value)
{
// Objects that have a `toArray()` method should be
// converted with that method (i.e. Entities)
if (is_object($value) && method_exists($value, 'toArray'))
{
$value = $value->toArray();
}
// Otherwise, cast as an array and it will grab public properties.
else if (is_object($value))
{
$value = (array)$value;
}
return $value;
}
//--------------------------------------------------------------------
}

173
system/View/Plugins.php Normal file
View File

@@ -0,0 +1,173 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\View;
/**
* View plugins
*/
class Plugins
{
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return string|\CodeIgniter\HTTP\URI
*/
public static function currentURL(array $params = [])
{
return current_url();
}
//--------------------------------------------------------------------
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return \CodeIgniter\HTTP\URI|mixed|string
*/
public static function previousURL(array $params = [])
{
return previous_url();
}
//--------------------------------------------------------------------
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return string
*/
public static function mailto(array $params = []): string
{
$email = $params['email'] ?? '';
$title = $params['title'] ?? '';
$attrs = $params['attributes'] ?? '';
return mailto($email, $title, $attrs);
}
//--------------------------------------------------------------------
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return string
*/
public static function safeMailto(array $params = []): string
{
$email = $params['email'] ?? '';
$title = $params['title'] ?? '';
$attrs = $params['attributes'] ?? '';
return safe_mailto($email, $title, $attrs);
}
//--------------------------------------------------------------------
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return string
*/
public static function lang(array $params = []): string
{
$line = array_shift($params);
return lang($line, $params);
}
//--------------------------------------------------------------------
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return string
*/
public static function ValidationErrors(array $params = []): string
{
$validator = \Config\Services::validation();
if (empty($params))
{
return $validator->listErrors();
}
return $validator->showError($params['field']);
}
//--------------------------------------------------------------------
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return string|false
*/
public static function route(array $params = [])
{
return route_to(...$params);
}
//--------------------------------------------------------------------
/**
* Wrap helper function to use as view plugin.
*
* @param array $params
*
* @return string
*/
public static function siteURL(array $params = []): string
{
return site_url(...$params);
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\View;
/**
* Interface RendererInterface
*
* The interface used for displaying Views and/or theme files.
*
* @package CodeIgniter\View
*/
interface RendererInterface
{
/**
* Builds the output based upon a file name and any
* data that has already been set.
*
* @param string $view
* @param array $options Reserved for 3rd-party uses since
* it might be needed to pass additional info
* to other template engines.
* @param boolean $saveData If true, will save data for use with any other calls,
* if false, will clean the data after displaying the view,
* if not specified, use the config setting.
*
* @return string
*/
public function render(string $view, array $options = null, bool $saveData = false): string;
//--------------------------------------------------------------------
/**
* Builds the output based upon a string and any
* data that has already been set.
*
* @param string $view The view contents
* @param array $options Reserved for 3rd-party uses since
* it might be needed to pass additional info
* to other template engines.
* @param boolean $saveData If true, will save data for use with any other calls,
* if false, will clean the data after displaying the view,
* if not specified, use the config setting.
*
* @return string
*/
public function renderString(string $view, array $options = null, bool $saveData = false): string;
//--------------------------------------------------------------------
/**
* Sets several pieces of view data at once.
*
* @param array $data
* @param string $context The context to escape it for: html, css, js, url
* If 'raw', no escaping will happen
*
* @return RendererInterface
*/
public function setData(array $data = [], string $context = null);
//--------------------------------------------------------------------
/**
* Sets a single piece of view data.
*
* @param string $name
* @param mixed $value
* @param string $context The context to escape it for: html, css, js, url
* If 'raw' no escaping will happen
*
* @return RendererInterface
*/
public function setVar(string $name, $value = null, string $context = null);
//--------------------------------------------------------------------
/**
* Removes all of the view data from the system.
*
* @return RendererInterface
*/
public function resetData();
//--------------------------------------------------------------------
}

583
system/View/Table.php Normal file
View File

@@ -0,0 +1,583 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014 - 2019, British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 1.3.1
* @filesource
*/
namespace CodeIgniter\View;
use CodeIgniter\Database\BaseResult;
/**
* HTML Table Generating Class
*
* Lets you create tables manually or from database result objects, or arrays.
*
* @package CodeIgniter
* @subpackage Libraries
* @category HTML Tables
* @author EllisLab Dev Team
*/
class Table
{
/**
* Data for table rows
*
* @var array
*/
public $rows = [];
/**
* Data for table heading
*
* @var array
*/
public $heading = [];
/**
* Data for table footing
*
* @var array
*/
public $footing = [];
/**
* Whether or not to automatically create the table header
*
* @var boolean
*/
public $autoHeading = true;
/**
* Table caption
*
* @var string
*/
public $caption = null;
/**
* Table layout template
*
* @var array
*/
public $template = null;
/**
* Newline setting
*
* @var string
*/
public $newline = "\n";
/**
* Contents of empty cells
*
* @var string
*/
public $emptyCells = '';
/**
* Callback for custom table layout
*
* @var function
*/
public $function = null;
/**
* Set the template from the table config file if it exists
*
* @param array $config (default: array())
* @return void
*/
public function __construct($config = [])
{
// initialize config
foreach ($config as $key => $val)
{
$this->template[$key] = $val;
}
}
// --------------------------------------------------------------------
/**
* Set the template
*
* @param array $template
* @return boolean
*/
public function setTemplate($template)
{
if (! is_array($template))
{
return false;
}
$this->template = $template;
return true;
}
// --------------------------------------------------------------------
/**
* Set the table heading
*
* Can be passed as an array or discreet params
*
* @param mixed
* @return Table
*/
public function setHeading($args = [])
{
$this->heading = $this->_prepArgs(func_get_args());
return $this;
}
/**
* Set the table footing
*
* Can be passed as an array or discreet params
*
* @param mixed
* @return Table
*/
public function setFooting($args = [])
{
$this->footing = $this->_prepArgs(func_get_args());
return $this;
}
// --------------------------------------------------------------------
/**
* Set columns. Takes a one-dimensional array as input and creates
* a multi-dimensional array with a depth equal to the number of
* columns. This allows a single array with many elements to be
* displayed in a table that has a fixed column count.
*
* @param array $array
* @param integer $columnLimit
* @return array
*/
public function makeColumns($array = [], $columnLimit = 0)
{
if (! is_array($array) || count($array) === 0 || ! is_int($columnLimit))
{
return false;
}
// Turn off the auto-heading feature since it's doubtful we
// will want headings from a one-dimensional array
$this->autoHeading = false;
if ($columnLimit === 0)
{
return $array;
}
$new = [];
do
{
$temp = array_splice($array, 0, $columnLimit);
if (count($temp) < $columnLimit)
{
for ($i = count($temp); $i < $columnLimit; $i ++)
{
$temp[] = '&nbsp;';
}
}
$new[] = $temp;
}
while (count($array) > 0);
return $new;
}
// --------------------------------------------------------------------
/**
* Set "empty" cells
*
* Can be passed as an array or discreet params
*
* @param mixed $value
* @return Table
*/
public function setEmpty($value)
{
$this->emptyCells = $value;
return $this;
}
// --------------------------------------------------------------------
/**
* Add a table row
*
* Can be passed as an array or discreet params
*
* @param mixed
* @return Table
*/
public function addRow($args = [])
{
$this->rows[] = $this->_prepArgs(func_get_args());
return $this;
}
// --------------------------------------------------------------------
/**
* Prep Args
*
* Ensures a standard associative array format for all cell data
*
* @param array
* @return array
*/
protected function _prepArgs($args)
{
// If there is no $args[0], skip this and treat as an associative array
// This can happen if there is only a single key, for example this is passed to table->generate
// array(array('foo'=>'bar'))
if (isset($args[0]) && count($args) === 1 && is_array($args[0]) && ! isset($args[0]['data']))
{
$args = $args[0];
}
foreach ($args as $key => $val)
{
is_array($val) || $args[$key] = ['data' => $val];
}
return $args;
}
// --------------------------------------------------------------------
/**
* Add a table caption
*
* @param string $caption
* @return Table
*/
public function setCaption($caption)
{
$this->caption = $caption;
return $this;
}
// --------------------------------------------------------------------
/**
* Generate the table
*
* @param mixed $tableData
* @return string
*/
public function generate($tableData = null)
{
// The table data can optionally be passed to this function
// either as a database result object or an array
if (! empty($tableData))
{
if ($tableData instanceof BaseResult)
{
$this->_setFromDBResult($tableData);
}
elseif (is_array($tableData))
{
$this->_setFromArray($tableData);
}
}
// Is there anything to display? No? Smite them!
if (empty($this->heading) && empty($this->rows))
{
return 'Undefined table data';
}
// Compile and validate the template date
$this->_compileTemplate();
// Validate a possibly existing custom cell manipulation function
if (isset($this->function) && ! is_callable($this->function))
{
$this->function = null;
}
// Build the table!
$out = $this->template['table_open'] . $this->newline;
// Add any caption here
if ($this->caption)
{
$out .= '<caption>' . $this->caption . '</caption>' . $this->newline;
}
// Is there a table heading to display?
if (! empty($this->heading))
{
$out .= $this->template['thead_open'] . $this->newline . $this->template['heading_row_start'] . $this->newline;
foreach ($this->heading as $heading)
{
$temp = $this->template['heading_cell_start'];
foreach ($heading as $key => $val)
{
if ($key !== 'data')
{
$temp = str_replace('<th', '<th ' . $key . '="' . $val . '"', $temp);
}
}
$out .= $temp . (isset($heading['data']) ? $heading['data'] : '') . $this->template['heading_cell_end'];
}
$out .= $this->template['heading_row_end'] . $this->newline . $this->template['thead_close'] . $this->newline;
}
// Build the table rows
if (! empty($this->rows))
{
$out .= $this->template['tbody_open'] . $this->newline;
$i = 1;
foreach ($this->rows as $row)
{
// We use modulus to alternate the row colors
$name = fmod($i ++, 2) ? '' : 'alt_';
$out .= $this->template['row_' . $name . 'start'] . $this->newline;
foreach ($row as $cell)
{
$temp = $this->template['cell_' . $name . 'start'];
foreach ($cell as $key => $val)
{
if ($key !== 'data')
{
$temp = str_replace('<td', '<td ' . $key . '="' . $val . '"', $temp);
}
}
$cell = isset($cell['data']) ? $cell['data'] : '';
$out .= $temp;
if ($cell === '' || $cell === null)
{
$out .= $this->emptyCells;
}
elseif (isset($this->function))
{
$out .= call_user_func($this->function, $cell);
}
else
{
$out .= $cell;
}
$out .= $this->template['cell_' . $name . 'end'];
}
$out .= $this->template['row_' . $name . 'end'] . $this->newline;
}
}
// Any table footing to display?
if (! empty($this->footing))
{
$out .= $this->template['tfoot_open'] . $this->newline . $this->template['footing_row_start'] . $this->newline;
foreach ($this->footing as $footing)
{
$temp = $this->template['footing_cell_start'];
foreach ($footing as $key => $val)
{
if ($key !== 'data')
{
$temp = str_replace('<th', '<th ' . $key . '="' . $val . '"', $temp);
}
}
$out .= $temp . (isset($footing['data']) ? $footing['data'] : '') . $this->template['footing_cell_end'];
}
$out .= $this->template['footing_row_end'] . $this->newline . $this->template['tfoot_close'] . $this->newline;
}
$out .= $this->template['tbody_close'] . $this->newline;
// And finally, close off the table
$out .= $this->template['table_close'];
// Clear table class properties before generating the table
$this->clear();
return $out;
}
// --------------------------------------------------------------------
/**
* Clears the table arrays. Useful if multiple tables are being generated
*
* @return Table
*/
public function clear()
{
$this->rows = [];
$this->heading = [];
$this->footing = [];
$this->autoHeading = true;
$this->caption = null;
return $this;
}
// --------------------------------------------------------------------
/**
* Set table data from a database result object
*
* @param BaseResult $object Database result object
* @return void
*/
protected function _setFromDBResult($object)
{
// First generate the headings from the table column names
if ($this->autoHeading === true && empty($this->heading))
{
$this->heading = $this->_prepArgs($object->getFieldNames());
}
foreach ($object->getResultArray() as $row)
{
$this->rows[] = $this->_prepArgs($row);
}
}
// --------------------------------------------------------------------
/**
* Set table data from an array
*
* @param array $data
* @return void
*/
protected function _setFromArray($data)
{
if ($this->autoHeading === true && empty($this->heading))
{
$this->heading = $this->_prepArgs(array_shift($data));
}
foreach ($data as &$row)
{
$this->rows[] = $this->_prepArgs($row);
}
}
// --------------------------------------------------------------------
/**
* Compile Template
*
* @return void
*/
protected function _compileTemplate()
{
if ($this->template === null)
{
$this->template = $this->_defaultTemplate();
return;
}
$this->temp = $this->_defaultTemplate();
foreach (['table_open', 'thead_open', 'thead_close', 'heading_row_start', 'heading_row_end', 'heading_cell_start', 'heading_cell_end', 'tbody_open', 'tbody_close', 'row_start', 'row_end', 'cell_start', 'cell_end', 'row_alt_start', 'row_alt_end', 'cell_alt_start', 'cell_alt_end', 'table_close'] as $val)
{
if (! isset($this->template[$val]))
{
$this->template[$val] = $this->temp[$val];
}
}
}
// --------------------------------------------------------------------
/**
* Default Template
*
* @return array
*/
protected function _defaultTemplate()
{
return [
'table_open' => '<table border="0" cellpadding="4" cellspacing="0">',
'thead_open' => '<thead>',
'thead_close' => '</thead>',
'heading_row_start' => '<tr>',
'heading_row_end' => '</tr>',
'heading_cell_start' => '<th>',
'heading_cell_end' => '</th>',
'tfoot_open' => '<tfoot>',
'tfoot_close' => '</tfoot>',
'footing_row_start' => '<tr>',
'footing_row_end' => '</tr>',
'footing_cell_start' => '<td>',
'footing_cell_end' => '</td>',
'tbody_open' => '<tbody>',
'tbody_close' => '</tbody>',
'row_start' => '<tr>',
'row_end' => '</tr>',
'cell_start' => '<td>',
'cell_end' => '</td>',
'row_alt_start' => '<tr>',
'row_alt_end' => '</tr>',
'cell_alt_start' => '<td>',
'cell_alt_end' => '</td>',
'table_close' => '</table>',
];
}
// --------------------------------------------------------------------
}

542
system/View/View.php Normal file
View File

@@ -0,0 +1,542 @@
<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\View;
use CodeIgniter\View\Exceptions\ViewException;
use Config\Services;
use Psr\Log\LoggerInterface;
/**
* Class View
*
* @package CodeIgniter\View
*/
class View implements RendererInterface
{
/**
* Data that is made available to the Views.
*
* @var array
*/
protected $data = [];
/**
* The base directory to look in for our Views.
*
* @var string
*/
protected $viewPath;
/**
* The render variables
*
* @var array
*/
protected $renderVars = [];
/**
* Instance of FileLocator for when
* we need to attempt to find a view
* that's not in standard place.
*
* @var \CodeIgniter\Autoloader\FileLocator
*/
protected $loader;
/**
* Logger instance.
*
* @var \CodeIgniter\Log\Logger
*/
protected $logger;
/**
* Should we store performance info?
*
* @var boolean
*/
protected $debug = false;
/**
* Cache stats about our performance here,
* when CI_DEBUG = true
*
* @var array
*/
protected $performanceData = [];
/**
* @var \Config\View
*/
protected $config;
/**
* Whether data should be saved between renders.
*
* @var boolean
*/
protected $saveData;
/**
* Number of loaded views
*
* @var integer
*/
protected $viewsCount = 0;
/**
* The name of the layout being used, if any.
* Set by the `extend` method used within views.
*
* @var string
*/
protected $layout;
/**
* Holds the sections and their data.
*
* @var array
*/
protected $sections = [];
/**
* The name of the current section being rendered,
* if any.
*
* @var string
*/
protected $currentSection;
//--------------------------------------------------------------------
/**
* Constructor
*
* @param \Config\View $config
* @param string $viewPath
* @param mixed $loader
* @param boolean $debug
* @param LoggerInterface $logger
*/
public function __construct($config, string $viewPath = null, $loader = null, bool $debug = null, LoggerInterface $logger = null)
{
$this->config = $config;
$this->viewPath = rtrim($viewPath, '/ ') . '/';
$this->loader = is_null($loader) ? Services::locator() : $loader;
$this->logger = is_null($logger) ? Services::logger() : $logger;
$this->debug = is_null($debug) ? CI_DEBUG : $debug;
$this->saveData = $config->saveData ?? null;
}
//--------------------------------------------------------------------
/**
* Builds the output based upon a file name and any
* data that has already been set.
*
* Valid $options:
* - cache number of seconds to cache for
* - cache_name Name to use for cache
*
* @param string $view
* @param array $options
* @param boolean $saveData
*
* @return string
*/
public function render(string $view, array $options = null, bool $saveData = null): string
{
$this->renderVars['start'] = microtime(true);
// Store the results here so even if
// multiple views are called in a view, it won't
// clean it unless we mean it to.
if ($saveData !== null)
{
$this->saveData = $saveData;
}
$fileExt = pathinfo($view, PATHINFO_EXTENSION);
$realPath = empty($fileExt) ? $view . '.php' : $view; // allow Views as .html, .tpl, etc (from CI3)
$this->renderVars['view'] = $realPath;
$this->renderVars['options'] = $options;
// Was it cached?
if (isset($this->renderVars['options']['cache']))
{
$this->renderVars['cacheName'] = $this->renderVars['options']['cache_name'] ?? str_replace('.php', '', $this->renderVars['view']);
if ($output = cache($this->renderVars['cacheName']))
{
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
return $output;
}
}
$this->renderVars['file'] = $this->viewPath . $this->renderVars['view'];
if (! is_file($this->renderVars['file']))
{
$this->renderVars['file'] = $this->loader->locateFile($this->renderVars['view'], 'Views', empty($fileExt) ? 'php' : $fileExt);
}
// locateFile will return an empty string if the file cannot be found.
if (empty($this->renderVars['file']))
{
throw ViewException::forInvalidFile($this->renderVars['view']);
}
// Make our view data available to the view.
extract($this->data);
if (! $this->saveData)
{
$this->data = [];
}
ob_start();
include($this->renderVars['file']); // PHP will be processed
$output = ob_get_contents();
@ob_end_clean();
// When using layouts, the data has already been stored
// in $this->sections, and no other valid output
// is allowed in $output so we'll overwrite it.
if (! is_null($this->layout) && empty($this->currentSection))
{
$layoutView = $this->layout;
$this->layout = null;
$output = $this->render($layoutView, $options, $saveData);
}
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
if ($this->debug && (! isset($options['debug']) || $options['debug'] === true))
{
$toolbarCollectors = config(\Config\Toolbar::class)->collectors;
if (in_array(\CodeIgniter\Debug\Toolbar\Collectors\Views::class, $toolbarCollectors))
{
// Clean up our path names to make them a little cleaner
foreach (['APPPATH', 'SYSTEMPATH', 'ROOTPATH'] as $path)
{
if (strpos($this->renderVars['file'], constant($path)) === 0)
{
$this->renderVars['file'] = str_replace(constant($path), $path . '/', $this->renderVars['file']);
break;
}
}
$this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file'];
$output = '<!-- DEBUG-VIEW START ' . $this->renderVars['file'] . ' -->' . PHP_EOL
. $output . PHP_EOL
. '<!-- DEBUG-VIEW ENDED ' . $this->renderVars['file'] . ' -->' . PHP_EOL;
}
}
// Should we cache?
if (isset($this->renderVars['options']['cache']))
{
cache()->save($this->renderVars['cacheName'], $output, (int) $this->renderVars['options']['cache']);
}
return $output;
}
//--------------------------------------------------------------------
/**
* Builds the output based upon a string and any
* data that has already been set.
* Cache does not apply, because there is no "key".
*
* @param string $view The view contents
* @param array $options Reserved for 3rd-party uses since
* it might be needed to pass additional info
* to other template engines.
* @param boolean $saveData If true, will save data for use with any other calls,
* if false, will clean the data after displaying the view,
* if not specified, use the config setting.
*
* @return string
*/
public function renderString(string $view, array $options = null, bool $saveData = null): string
{
$start = microtime(true);
if (is_null($saveData))
{
$saveData = $this->config->saveData;
}
extract($this->data);
if (! $saveData)
{
$this->data = [];
}
ob_start();
$incoming = '?>' . $view;
eval($incoming);
$output = ob_get_contents();
@ob_end_clean();
$this->logPerformance($start, microtime(true), $this->excerpt($view));
return $output;
}
//--------------------------------------------------------------------
/**
* Extract first bit of a long string and add ellipsis
*
* @param string $string
* @param integer $length
* @return string
*/
public function excerpt(string $string, int $length = 20): string
{
return (strlen($string) > $length) ? substr($string, 0, $length - 3) . '...' : $string;
}
//--------------------------------------------------------------------
/**
* Sets several pieces of view data at once.
*
* @param array $data
* @param string $context The context to escape it for: html, css, js, url
* If null, no escaping will happen
*
* @return RendererInterface
*/
public function setData(array $data = [], string $context = null): RendererInterface
{
if (! empty($context))
{
$data = \esc($data, $context);
}
$this->data = array_merge($this->data, $data);
return $this;
}
//--------------------------------------------------------------------
/**
* Sets a single piece of view data.
*
* @param string $name
* @param mixed $value
* @param string $context The context to escape it for: html, css, js, url
* If null, no escaping will happen
*
* @return RendererInterface
*/
public function setVar(string $name, $value = null, string $context = null): RendererInterface
{
if (! empty($context))
{
$value = \esc($value, $context);
}
$this->data[$name] = $value;
return $this;
}
//--------------------------------------------------------------------
/**
* Removes all of the view data from the system.
*
* @return RendererInterface
*/
public function resetData(): RendererInterface
{
$this->data = [];
return $this;
}
//--------------------------------------------------------------------
/**
* Returns the current data that will be displayed in the view.
*
* @return array
*/
public function getData(): array
{
return $this->data;
}
//--------------------------------------------------------------------
/**
* Specifies that the current view should extend an existing layout.
*
* @param string $layout
*
* @return void
*/
public function extend(string $layout)
{
$this->layout = $layout;
}
//--------------------------------------------------------------------
/**
* Starts holds content for a section within the layout.
*
* @param string $name
*/
public function section(string $name)
{
$this->currentSection = $name;
ob_start();
}
//--------------------------------------------------------------------
/**
*
*
* @throws \Zend\Escaper\Exception\RuntimeException
*/
public function endSection()
{
$contents = ob_get_clean();
if (empty($this->currentSection))
{
throw new \RuntimeException('View themes, no current section.');
}
// Ensure an array exists so we can store multiple entries for this.
if (! array_key_exists($this->currentSection, $this->sections))
{
$this->sections[$this->currentSection] = [];
}
$this->sections[$this->currentSection][] = $contents;
$this->currentSection = null;
}
//--------------------------------------------------------------------
/**
* Renders a section's contents.
*
* @param string $sectionName
*/
public function renderSection(string $sectionName)
{
if (! isset($this->sections[$sectionName]))
{
echo '';
return;
}
foreach ($this->sections[$sectionName] as $contents)
{
echo $contents;
}
}
//--------------------------------------------------------------------
/**
* Used within layout views to include additional views.
*
* @param string $view
* @param array|null $options
* @param null $saveData
*
* @return string
*/
public function include(string $view, array $options = null, $saveData = null): string
{
return $this->render($view, $options, $saveData);
}
//--------------------------------------------------------------------
/**
* Returns the performance data that might have been collected
* during the execution. Used primarily in the Debug Toolbar.
*
* @return array
*/
public function getPerformanceData(): array
{
return $this->performanceData;
}
//--------------------------------------------------------------------
/**
* Logs performance data for rendering a view.
*
* @param float $start
* @param float $end
* @param string $view
*/
protected function logPerformance(float $start, float $end, string $view)
{
if (! $this->debug)
{
return;
}
$this->performanceData[] = [
'start' => $start,
'end' => $end,
'view' => $view,
];
}
//--------------------------------------------------------------------
}