<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.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 (CI_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,
		];
	}

	//--------------------------------------------------------------------
}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT    MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Images\Handlers;

use CodeIgniter\Images\Exceptions\ImageException;
use CodeIgniter\Images\Image;
use CodeIgniter\Images\ImageHandlerInterface;

/**
 * Base image handling implementation
 */
abstract class BaseHandler implements ImageHandlerInterface
{

	/**
	 * @var \Config\Images
	 */
	protected $config;

	/**
	 * The image/file instance
	 * d
	 *
	 * @var \CodeIgniter\Images\Image
	 */
	protected $image           = null;
	protected $width           = 0;
	protected $height          = 0;
	protected $filePermissions = 0644;
	protected $xAxis           = 0;
	protected $yAxis           = 0;
	protected $masterDim       = 'auto';

	/**
	 * Default options for text watermarking.
	 *
	 * @var array
	 */
	protected $textDefaults = [
		'fontPath'     => null,
		'fontSize'     => 16,
		'color'        => 'ffffff',
		'opacity'      => 1.0,
		'vAlign'       => 'bottom',
		'hAlign'       => 'center',
		'vOffset'      => 0,
		'hOffset'      => 0,
		'padding'      => 0,
		'withShadow'   => false,
		'shadowColor'  => '000000',
		'shadowOffset' => 3,
	];

	/**
	 * Temporary image used by the different engines.
	 *
	 * @var resource
	 */
	protected $resource;

	//--------------------------------------------------------------------

	public function __construct($config = null)
	{
		$this->config = $config;
	}

	//--------------------------------------------------------------------

	/**
	 * Sets another image for this handler to work on.
	 * Keeps us from needing to continually instantiate the handler.
	 *
	 * @param string $path
	 *
	 * @return $this
	 */
	public function withFile(string $path)
	{
		// Clear out the old resource so that
		// it doesn't try to use a previous image
		$this->resource = null;

		$this->image = new Image($path, true);

		$this->image->getProperties(false);
		$this->width  = $this->image->origWidth;
		$this->height = $this->image->origHeight;

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Make the image resource object if needed
	 */
	protected function ensureResource()
	{
		if ($this->resource === null)
		{
			$path = $this->image->getPathname();
			// if valid image type, make corresponding image resource
			switch ($this->image->imageType)
			{
				case IMAGETYPE_GIF:
					$this->resource = imagecreatefromgif($path);
					break;
				case IMAGETYPE_JPEG:
					$this->resource = imagecreatefromjpeg($path);
					break;
				case IMAGETYPE_PNG:
					$this->resource = imagecreatefrompng($path);
					break;
			}
		}
	}

	//--------------------------------------------------------------------

	/**
	 * Returns the image instance.
	 *
	 * @return \CodeIgniter\Images\Image
	 */
	public function getFile()
	{
		return $this->image;
	}

	//--------------------------------------------------------------------

	/**
	 * Returns the temporary image used during the image processing.
	 * Good for extending the system or doing things this library
	 * is not intended to do.
	 *
	 * @return resource
	 */
	public function getResource()
	{
		$this->ensureResource();
		return $this->resource;
	}

	//--------------------------------------------------------------------

	/**
	 * Resize the image
	 *
	 * @param integer $width
	 * @param integer $height
	 * @param boolean $maintainRatio If true, will get the closest match possible while keeping aspect ratio true.
	 * @param string  $masterDim
	 *
	 * @return BaseHandler
	 */
	public function resize(int $width, int $height, bool $maintainRatio = false, string $masterDim = 'auto')
	{
		// If the target width/height match the source, then we have nothing to do here.
		if ($this->image->origWidth === $width && $this->image->origHeight === $height)
		{
			return $this;
		}

		$this->width  = $width;
		$this->height = $height;

		if ($maintainRatio)
		{
			$this->masterDim = $masterDim;
			$this->reproportion();
		}

		return $this->_resize($maintainRatio);
	}

	//--------------------------------------------------------------------

	/**
	 * Crops the image to the desired height and width. If one of the height/width values
	 * is not provided, that value will be set the appropriate value based on offsets and
	 * image dimensions.
	 *
	 * @param integer|null $width
	 * @param integer|null $height
	 * @param integer|null $x             X-axis coord to start cropping from the left of image
	 * @param integer|null $y             Y-axis coord to start cropping from the top of image
	 * @param boolean      $maintainRatio
	 * @param string       $masterDim
	 *
	 * @return mixed
	 */
	public function crop(int $width = null, int $height = null, int $x = null, int $y = null, bool $maintainRatio = false, string $masterDim = 'auto')
	{
		$this->width  = $width;
		$this->height = $height;
		$this->xAxis  = $x;
		$this->yAxis  = $y;

		if ($maintainRatio)
		{
			$this->masterDim = $masterDim;
			$this->reproportion();
		}

		$result = $this->_crop();

		$this->xAxis = null;
		$this->yAxis = null;

		return $result;
	}

	//--------------------------------------------------------------------

	/**
	 * Rotates the image on the current canvas.
	 *
	 * @param float $angle
	 *
	 * @return mixed
	 */
	public function rotate(float $angle)
	{
		// Allowed rotation values
		$degs = [
			90,
			180,
			270,
		];

		if ($angle === '' || ! in_array($angle, $degs))
		{
			throw ImageException::forMissingAngle();
		}

		// cast angle as an int, for our use
		$angle = (int) $angle;

		// Reassign the width and height
		if ($angle === 90 || $angle === 270)
		{
			$temp         = $this->height;
			$this->width  = $this->height;
			$this->height = $temp;
		}

		// Call the Handler-specific version.
		$this->_rotate($angle);

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Flattens transparencies, default white background
	 *
	 * @param integer $red
	 * @param integer $green
	 * @param integer $blue
	 *
	 * @return mixed
	 */
	public function flatten(int $red = 255, int $green = 255, int $blue = 255)
	{
		$this->width  = $this->image->origWidth;
		$this->height = $this->image->origHeight;

		return $this->_flatten();
	}

	//--------------------------------------------------------------------

	/**
	 * Handler-specific method to flattening an image's transparencies.
	 *
	 * @param integer $red
	 * @param integer $green
	 * @param integer $blue
	 *
	 * @return   mixed
	 * @internal param int $angle
	 */
	protected abstract function _flatten(int $red = 255, int $green = 255, int $blue = 255);

	//--------------------------------------------------------------------

	/**
	 * Handler-specific method to handle rotating an image in 90 degree increments.
	 *
	 * @param integer $angle
	 *
	 * @return mixed
	 */
	protected abstract function _rotate(int $angle);

	//--------------------------------------------------------------------

	/**
	 * Flips an image either horizontally or vertically.
	 *
	 * @param string $dir Either 'vertical' or 'horizontal'
	 *
	 * @return $this
	 */
	public function flip(string $dir = 'vertical')
	{
		$dir = strtolower($dir);

		if ($dir !== 'vertical' && $dir !== 'horizontal')
		{
			throw ImageException::forInvalidDirection($dir);
		}

		return $this->_flip($dir);
	}

	//--------------------------------------------------------------------

	/**
	 * Handler-specific method to handle flipping an image along its
	 * horizontal or vertical axis.
	 *
	 * @param string $direction
	 *
	 * @return mixed
	 */
	protected abstract function _flip(string $direction);

	//--------------------------------------------------------------------

	/**
	 * Overlays a string of text over the image.
	 *
	 * Valid options:
	 *
	 *  - color         Text Color (hex number)
	 *  - shadowColor   Color of the shadow (hex number)
	 *  - hAlign        Horizontal alignment: left, center, right
	 *  - vAlign        Vertical alignment: top, middle, bottom
	 *  - hOffset
	 *  - vOffset
	 *  - fontPath
	 *  - fontSize
	 *  - shadowOffset
	 *
	 * @param string $text
	 * @param array  $options
	 *
	 * @return $this
	 */
	public function text(string $text, array $options = [])
	{
		$options                = array_merge($this->textDefaults, $options);
		$options['color']       = trim($options['color'], '# ');
		$options['shadowColor'] = trim($options['shadowColor'], '# ');

		$this->_text($text, $options);

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Handler-specific method for overlaying text on an image.
	 *
	 * @param string $text
	 * @param array  $options
	 */
	protected abstract function _text(string $text, array $options = []);

	//--------------------------------------------------------------------

	/**
	 * Reads the EXIF information from the image and modifies the orientation
	 * so that displays correctly in the browser. This is especially an issue
	 * with images taken by smartphones who always store the image up-right,
	 * but set the orientation flag to display it correctly.
	 *
	 * @param boolean $silent If true, will ignore exceptions when PHP doesn't support EXIF.
	 *
	 * @return $this
	 */
	public function reorient(bool $silent = false)
	{
		$orientation = $this->getEXIF('Orientation', $silent);

		switch ($orientation)
		{
			case 2:
				return $this->flip('horizontal');
				break;
			case 3:
				return $this->rotate(180);
				break;
			case 4:
				return $this->rotate(180)
								->flip('horizontal');
				break;
			case 5:
				return $this->rotate(270)
								->flip('horizontal');
				break;
			case 6:
				return $this->rotate(270);
				break;
			case 7:
				return $this->rotate(90)
								->flip('horizontal');
				break;
			case 8:
				return $this->rotate(90);
				break;
			default:
				return $this;
		}
	}

	//--------------------------------------------------------------------

	/**
	 * Retrieve the EXIF information from the image, if possible. Returns
	 * an array of the information, or null if nothing can be found.
	 *
	 * @param string|null $key    If specified, will only return this piece of EXIF data.
	 *
	 * @param boolean     $silent If true, will not throw our own exceptions.
	 *
	 * @return mixed
	 */
	public function getEXIF(string $key = null, bool $silent = false)
	{
		if (! function_exists('exif_read_data'))
		{
			if ($silent)
			{
				return null;
			}
		}

		$exif = exif_read_data($this->image->getPathname());
		if (! is_null($key) && is_array($exif))
		{
			$exif = $exif[$key] ?? false;
		}

		return $exif;
	}

	//--------------------------------------------------------------------

	/**
	 * Combine cropping and resizing into a single command.
	 *
	 * Supported positions:
	 *  - top-left
	 *  - top
	 *  - top-right
	 *  - left
	 *  - center
	 *  - right
	 *  - bottom-left
	 *  - bottom
	 *  - bottom-right
	 *
	 * @param integer $width
	 * @param integer $height
	 * @param string  $position
	 *
	 * @return boolean
	 */
	public function fit(int $width, int $height = null, string $position = 'center')
	{
		$origWidth  = $this->image->origWidth;
		$origHeight = $this->image->origHeight;

		list($cropWidth, $cropHeight) = $this->calcAspectRatio($width, $height, $origWidth, $origHeight);

		if (is_null($height))
		{
			$height = ceil(($width / $cropWidth) * $cropHeight);
		}

		list($x, $y) = $this->calcCropCoords($width, $height, $origWidth, $origHeight, $position);

		return $this->crop($cropWidth, $cropHeight, $x, $y)
						->resize($width, $height);
	}

	//--------------------------------------------------------------------

	/**
	 *
	 *
	 * @param $width
	 * @param null       $height
	 * @param $origWidth
	 * @param $origHeight
	 *
	 * @return array
	 */
	protected function calcAspectRatio($width, $height = null, $origWidth, $origHeight): array
	{
		// If $height is null, then we have it easy.
		// Calc based on full image size and be done.
		if (is_null($height))
		{
			$height = ($width / $origWidth) * $origHeight;

			return [
				$width,
				(int) $height,
			];
		}

		$xRatio = $width / $origWidth;
		$yRatio = $height / $origHeight;

		if ($xRatio > $yRatio)
		{
			return [
				(int) ($origWidth * $yRatio),
				(int) ($origHeight * $yRatio),
			];
		}

		return [
			(int) ($origWidth * $xRatio),
			(int) ($origHeight * $xRatio),
		];
	}

	//--------------------------------------------------------------------

	/**
	 * Based on the position, will determine the correct x/y coords to
	 * crop the desired portion from the image.
	 *
	 * @param $width
	 * @param $height
	 * @param $origWidth
	 * @param $origHeight
	 * @param $position
	 *
	 * @return array
	 */
	protected function calcCropCoords($width, $height, $origWidth, $origHeight, $position): array
	{
		$position = strtolower($position);
		$x        = $y = 0;

		switch ($position)
		{
			case 'top-left':
				$x = 0;
				$y = 0;
				break;
			case 'top':
				$x = floor(($origWidth - $width) / 2);
				$y = 0;
				break;
			case 'top-right':
				$x = $origWidth - $width;
				$y = 0;
				break;
			case 'left':
				$x = 0;
				$y = floor(($origHeight - $height) / 2);
				break;
			case 'center':
				$x = floor(($origWidth - $width) / 2);
				$y = floor(($origHeight - $height) / 2);
				break;
			case 'right':
				$x = ($origWidth - $width);
				$y = floor(($origHeight - $height) / 2);
				break;
			case 'bottom-left':
				$x = 0;
				$y = $origHeight - $height;
				break;
			case 'bottom':
				$x = floor(($origWidth - $width) / 2);
				$y = $origHeight - $height;
				break;
			case 'bottom-right':
				$x = ($origWidth - $width);
				$y = $origHeight - $height;
				break;
		}

		return [
			$x,
			$y,
		];
	}

	//--------------------------------------------------------------------

	/**
	 * Get the version of the image library in use.
	 *
	 * @return string
	 */
	public abstract function getVersion();

	//--------------------------------------------------------------------

	/**
	 * Saves any changes that have been made to file.
	 *
	 * Example:
	 *    $image->resize(100, 200, true)
	 *          ->save($target);
	 *
	 * @param string  $target
	 * @param integer $quality
	 *
	 * @return mixed
	 */
	public abstract function save(string $target = null, int $quality = 90);

	//--------------------------------------------------------------------

	/**
	 * Does the driver-specific processing of the image.
	 *
	 * @param string $action
	 *
	 * @return mixed
	 */
	protected abstract function process(string $action);

	//--------------------------------------------------------------------

	/**
	 * Provide access to the Image class' methods if they don't exist
	 * on the handler itself.
	 *
	 * @param string $name
	 * @param array  $args
	 *
	 * @return mixed
	 */
	public function __call(string $name, array $args = [])
	{
		if (method_exists($this->image, $name))
		{
			return $this->image->$name(...$args);
		}
	}

	//--------------------------------------------------------------------

	/**
	 * Re-proportion Image Width/Height
	 *
	 * When creating thumbs, the desired width/height
	 * can end up warping the image due to an incorrect
	 * ratio between the full-sized image and the thumb.
	 *
	 * This function lets us re-proportion the width/height
	 * if users choose to maintain the aspect ratio when resizing.
	 *
	 * @return void
	 */
	protected function reproportion()
	{
		if (($this->width === 0 && $this->height === 0) ||
				$this->image->origWidth === 0 ||
				$this->image->origHeight === 0 ||
				( ! ctype_digit((string) $this->width) && ! ctype_digit((string) $this->height)) ||
				! ctype_digit((string) $this->image->origWidth) ||
				! ctype_digit((string) $this->image->origHeight)
		)
		{
			return;
		}

		// Sanitize
		$this->width  = (int) $this->width;
		$this->height = (int) $this->height;

		if ($this->masterDim !== 'width' && $this->masterDim !== 'height')
		{
			if ($this->width > 0 && $this->height > 0)
			{
				$this->masterDim = ((($this->image->origHeight / $this->image->origWidth) - ($this->height / $this->width)) < 0) ? 'width' : 'height';
			}
			else
			{
				$this->masterDim = ($this->height === 0) ? 'width' : 'height';
			}
		}
		elseif (($this->masterDim === 'width' && $this->width === 0) || ($this->masterDim === 'height' && $this->height === 0)
		)
		{
			return;
		}

		if ($this->masterDim === 'width')
		{
			$this->height = (int) ceil($this->width * $this->image->origHeight / $this->image->origWidth);
		}
		else
		{
			$this->width = (int) ceil($this->image->origWidth * $this->height / $this->image->origHeight);
		}
	}

	//--------------------------------------------------------------------
	// accessor for testing; not part of interface
	public function getWidth()
	{
		return ($this->resource !== null) ? $this->_getWidth() : $this->width;
	}

	// accessor for testing; not part of interface
	public function getHeight()
	{
		return ($this->resource !== null) ? $this->_getHeight() : $this->height;
	}

}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Cache\Handlers;

use CodeIgniter\Cache\CacheInterface;

/**
 * Cache handler for WinCache from Microsoft & IIS.
 * Windows-only, so not testable on travis-ci.
 * Unusable methods flagged for code coverage ignoring.
 */
class WincacheHandler implements CacheInterface
{

	/**
	 * Prefixed to all cache names.
	 *
	 * @var string
	 */
	protected $prefix;

	//--------------------------------------------------------------------

	public function __construct($config)
	{
		$this->prefix = $config->prefix ?: '';
	}

	//--------------------------------------------------------------------

	/**
	 * Takes care of any handler-specific setup that must be done.
	 *
	 * @codeCoverageIgnore
	 */
	public function initialize()
	{
		// Nothing to see here...
	}

	//--------------------------------------------------------------------

	/**
	 * Attempts to fetch an item from the cache store.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param string $key Cache item name
	 *
	 * @return mixed
	 */
	public function get(string $key)
	{
		$key = $this->prefix . $key;

		$success = false;
		$data    = wincache_ucache_get($key, $success);

		// Success returned by reference from wincache_ucache_get()
		return ($success) ? $data : null;
	}

	//--------------------------------------------------------------------

	/**
	 * Saves an item to the cache store.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param string  $key   Cache item name
	 * @param mixed   $value The data to save
	 * @param integer $ttl   Time To Live, in seconds (default 60)
	 *
	 * @return mixed
	 */
	public function save(string $key, $value, int $ttl = 60)
	{
		$key = $this->prefix . $key;

		return wincache_ucache_set($key, $value, $ttl);
	}

	//--------------------------------------------------------------------

	/**
	 * Deletes a specific item from the cache store.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param string $key Cache item name
	 *
	 * @return mixed
	 */
	public function delete(string $key)
	{
		$key = $this->prefix . $key;

		return wincache_ucache_delete($key);
	}

	//--------------------------------------------------------------------

	/**
	 * Performs atomic incrementation of a raw stored value.
	 *
	 * @codeCoverageIgnore
	 * @param              string  $key    Cache ID
	 * @param              integer $offset Step/value to increase by
	 *
	 * @return mixed
	 */
	public function increment(string $key, int $offset = 1)
	{
		$key = $this->prefix . $key;

		$success = false;
		$value   = wincache_ucache_inc($key, $offset, $success);

		return ($success === true) ? $value : false;
	}

	//--------------------------------------------------------------------

	/**
	 * Performs atomic decrementation of a raw stored value.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param string  $key    Cache ID
	 * @param integer $offset Step/value to increase by
	 *
	 * @return mixed
	 */
	public function decrement(string $key, int $offset = 1)
	{
		$key = $this->prefix . $key;

		$success = false;
		$value   = wincache_ucache_dec($key, $offset, $success);

		return ($success === true) ? $value : false;
	}

	//--------------------------------------------------------------------

	/**
	 * Will delete all items in the entire cache.
	 *
	 * @codeCoverageIgnore
	 *
	 * @return mixed
	 */
	public function clean()
	{
		return wincache_ucache_clear();
	}

	//--------------------------------------------------------------------

	/**
	 * Returns information on the entire cache.
	 *
	 * @codeCoverageIgnore
	 *
	 * The information returned and the structure of the data
	 * varies depending on the handler.
	 *
	 * @return mixed
	 */
	public function getCacheInfo()
	{
		return wincache_ucache_info(true);
	}

	//--------------------------------------------------------------------

	/**
	 * Returns detailed information about the specific item in the cache.
	 *
	 * @codeCoverageIgnore
	 * @param   string  $key Cache item name.
	 *
	 * @return mixed
	 */
	public function getMetaData(string $key)
	{
		$key = $this->prefix . $key;

		if ($stored = wincache_ucache_info(false, $key))
		{
			$age      = $stored['ucache_entries'][1]['age_seconds'];
			$ttl      = $stored['ucache_entries'][1]['ttl_seconds'];
			$hitcount = $stored['ucache_entries'][1]['hitcount'];

			return [
				'expire'   => $ttl - $age,
				'hitcount' => $hitcount,
				'age'      => $age,
				'ttl'      => $ttl,
			];
		}

		return false;
	}

	//--------------------------------------------------------------------

	/**
	 * Determines if the driver is supported on this system.
	 *
	 * @return boolean
	 */
	public function isSupported(): bool
	{
		return (extension_loaded('wincache') && ini_get('wincache.ucenabled'));
	}

	//--------------------------------------------------------------------
}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Commands\Server;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;

/**
 * Launch the PHP development server
 *
 * Not testable, as it throws phpunit for a loop :-/
 *
 * @package CodeIgniter\Commands\Server
 *
 * @codeCoverageIgnore
 */
class Serve extends BaseCommand
{
	/**
	 * Minimum PHP version
	 *
	 * @var string
	 */
	protected $minPHPVersion = '7.2';

	/**
	 * Group
	 *
	 * @var string
	 */
	protected $group = 'CodeIgniter';

	/**
	 * Name
	 *
	 * @var string
	 */
	protected $name = 'serve';

	/**
	 * Description
	 *
	 * @var string
	 */
	protected $description = 'Launches the CodeIgniter PHP-Development Server.';

	/**
	 * Usage
	 *
	 * @var string
	 */
	protected $usage = 'serve';

	/**
	 * Arguments
	 *
	 * @var array
	 */
	protected $arguments = [];

	/**
	 * Options
	 *
	 * @var array
	 */
	protected $options = [
		'-php'  => 'The PHP Binary [default: "PHP_BINARY"]',
		'-host' => 'The HTTP Host [default: "localhost"]',
		'-port' => 'The HTTP Host Port [default: "8080"]',
	];

	/**
	 * Run the server
	 *
	 * @param array $params Parameters
	 *
	 * @return void
	 */
	public function run(array $params)
	{
		// Valid PHP Version?
		if (phpversion() < $this->minPHPVersion)
		{
			die('Your PHP version must be ' . $this->minPHPVersion .
				' or higher to run CodeIgniter. Current version: ' . phpversion());
		}

		// Collect any user-supplied options and apply them.
		$php  = CLI::getOption('php') ?? PHP_BINARY;
		$host = CLI::getOption('host') ?? 'localhost';
		$port = CLI::getOption('port') ?? '8080';

		// Get the party started.
		CLI::write('CodeIgniter development server started on http://' . $host . ':' . $port, 'green');
		CLI::write('Press Control-C to stop.');

		// Set the Front Controller path as Document Root.
		$docroot = escapeshellarg(FCPATH);

		// Mimic Apache's mod_rewrite functionality with user settings.
		$rewrite = escapeshellarg(__DIR__ . '/rewrite.php');

		// Call PHP's built-in webserver, making sure to set our
		// base path to the public folder, and to use the rewrite file
		// to ensure our environment is set and it simulates basic mod_rewrite.
		passthru($php . ' -S ' . $host . ':' . $port . ' -t ' . $docroot . ' ' . $rewrite);
	}

}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.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;
		}

		$view = str_replace('.php', '', $view);

		// Was it cached?
		if (isset($options['cache']))
		{
			$cacheName = $options['cache_name'] ?: $view;

			if ($output = cache($cacheName))
			{
				$this->logPerformance($start, microtime(true), $view);
				return $output;
			}
		}

		$view = $view . '.php';
		$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(['<?', '?>'], ['<?', '?>'], $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;
	}

	//--------------------------------------------------------------------
}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT    MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Images\Handlers;

use CodeIgniter\Images\Exceptions\ImageException;

/**
 * Image handler for GD package
 */
class GDHandler extends BaseHandler
{

	public $version;

	public function __construct($config = null)
	{
		parent::__construct($config);

		// We should never see this, so can't test it
		// @codeCoverageIgnoreStart
		if (! extension_loaded('gd'))
		{
			throw ImageException::forMissingExtension('GD');
		}
		// @codeCoverageIgnoreEnd
	}

	//--------------------------------------------------------------------

	/**
	 * Handles the rotation of an image resource.
	 * Doesn't save the image, but replaces the current resource.
	 *
	 * @param integer $angle
	 *
	 * @return boolean
	 */
	protected function _rotate(int $angle): bool
	{
		// Create the image handle
		$srcImg = $this->createImage();

		// Set the background color
		// This won't work with transparent PNG files so we are
		// going to have to figure out how to determine the color
		// of the alpha channel in a future release.

		$white = imagecolorallocate($srcImg, 255, 255, 255);

		// Rotate it!
		$destImg = imagerotate($srcImg, $angle, $white);

		// Kill the file handles
		imagedestroy($srcImg);

		$this->resource = $destImg;

		return true;
	}

	//--------------------------------------------------------------------

	/**
	 * Flattens transparencies
	 *
	 * @param integer $red
	 * @param integer $green
	 * @param integer $blue
	 *
	 * @return $this
	 */
	public function _flatten(int $red = 255, int $green = 255, int $blue = 255)
	{
		$srcImg = $this->createImage();

		if (function_exists('imagecreatetruecolor'))
		{
			$create = 'imagecreatetruecolor';
			$copy   = 'imagecopyresampled';
		}
		else
		{
			$create = 'imagecreate';
			$copy   = 'imagecopyresized';
		}
		$dest = $create($this->width, $this->height);

		$matte = imagecolorallocate($dest, $red, $green, $blue);

		imagefilledrectangle($dest, 0, 0, $this->width, $this->height, $matte);
		imagecopy($dest, $srcImg, 0, 0, 0, 0, $this->width, $this->height);

		// Kill the file handles
		imagedestroy($srcImg);

		$this->resource = $dest;

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Flips an image along it's vertical or horizontal axis.
	 *
	 * @param string $direction
	 *
	 * @return $this
	 */
	public function _flip(string $direction)
	{
		$srcImg = $this->createImage();

		$width  = $this->image->origWidth;
		$height = $this->image->origHeight;

		if ($direction === 'horizontal')
		{
			for ($i = 0; $i < $height; $i ++)
			{
				$left  = 0;
				$right = $width - 1;

				while ($left < $right)
				{
					$cl = imagecolorat($srcImg, $left, $i);
					$cr = imagecolorat($srcImg, $right, $i);

					imagesetpixel($srcImg, $left, $i, $cr);
					imagesetpixel($srcImg, $right, $i, $cl);

					$left ++;
					$right --;
				}
			}
		}
		else
		{
			for ($i = 0; $i < $width; $i ++)
			{
				$top    = 0;
				$bottom = $height - 1;

				while ($top < $bottom)
				{
					$ct = imagecolorat($srcImg, $i, $top);
					$cb = imagecolorat($srcImg, $i, $bottom);

					imagesetpixel($srcImg, $i, $top, $cb);
					imagesetpixel($srcImg, $i, $bottom, $ct);

					$top ++;
					$bottom --;
				}
			}
		}

		$this->resource = $srcImg;

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Get GD version
	 *
	 * @return mixed
	 */
	public function getVersion()
	{
		if (function_exists('gd_info'))
		{
			$gd_version = @gd_info();

			return preg_replace('/\D/', '', $gd_version['GD Version']);
		}

		return false;
	}

	//--------------------------------------------------------------------

	/**
	 * Resizes the image.
	 *
	 * @return boolean|\CodeIgniter\Images\Handlers\GDHandler
	 */
	public function _resize()
	{
		return $this->process('resize');
	}

	//--------------------------------------------------------------------

	/**
	 * Crops the image.
	 *
	 * @return boolean|\CodeIgniter\Images\Handlers\GDHandler
	 */
	public function _crop()
	{
		return $this->process('crop');
	}

	//--------------------------------------------------------------------

	/**
	 * Handles all of the grunt work of resizing, etc.
	 *
	 * @param string $action
	 *
	 * @return $this|bool
	 */
	protected function process(string $action)
	{
		$origWidth  = $this->image->origWidth;
		$origHeight = $this->image->origHeight;

		if ($action === 'crop')
		{
			// Reassign the source width/height if cropping
			$origWidth  = $this->width;
			$origHeight = $this->height;

			// Modify the "original" width/height to the new
			// values so that methods that come after have the
			// correct size to work with.
			$this->image->origHeight = $this->height;
			$this->image->origWidth  = $this->width;
		}

		// Create the image handle
		$src = $this->createImage();

		if (function_exists('imagecreatetruecolor'))
		{
			$create = 'imagecreatetruecolor';
			$copy   = 'imagecopyresampled';
		}
		else
		{
			$create = 'imagecreate';
			$copy   = 'imagecopyresized';
		}

		$dest = $create($this->width, $this->height);

		if ($this->image->imageType === IMAGETYPE_PNG) // png we can actually preserve transparency
		{
			imagealphablending($dest, false);
			imagesavealpha($dest, true);
		}

		$copy($dest, $src, 0, 0, $this->xAxis, $this->yAxis, $this->width, $this->height, $origWidth, $origHeight);

		imagedestroy($src);
		$this->resource = $dest;

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Saves any changes that have been made to file. If no new filename is
	 * provided, the existing image is overwritten, otherwise a copy of the
	 * file is made at $target.
	 *
	 * Example:
	 *    $image->resize(100, 200, true)
	 *          ->save();
	 *
	 * @param string|null $target
	 * @param integer     $quality
	 *
	 * @return boolean
	 */
	public function save(string $target = null, int $quality = 90): bool
	{
		$target = empty($target) ? $this->image->getPathname() : $target;

		switch ($this->image->imageType)
		{
			case IMAGETYPE_GIF:
				if (! function_exists('imagegif'))
				{
					throw ImageException::forInvalidImageCreate(lang('images.gifNotSupported'));
				}

				if (! @imagegif($this->resource, $target))
				{
					throw ImageException::forSaveFailed();
				}
				break;
			case IMAGETYPE_JPEG:
				if (! function_exists('imagejpeg'))
				{
					throw ImageException::forInvalidImageCreate(lang('images.jpgNotSupported'));
				}

				if (! @imagejpeg($this->resource, $target, $quality))
				{
					throw ImageException::forSaveFailed();
				}
				break;
			case IMAGETYPE_PNG:
				if (! function_exists('imagepng'))
				{
					throw ImageException::forInvalidImageCreate(lang('images.pngNotSupported'));
				}

				if (! @imagepng($this->resource, $target))
				{
					throw ImageException::forSaveFailed();
				}
				break;
			default:
				throw ImageException::forInvalidImageCreate();
				break;
		}

		imagedestroy($this->resource);

		chmod($target, $this->filePermissions);

		return true;
	}

	//--------------------------------------------------------------------

	/**
	 * Create Image Resource
	 *
	 * This simply creates an image resource handle
	 * based on the type of image being processed
	 *
	 * @param string    $path
	 * @param string    $imageType
	 *
	 * @return resource|boolean
	 */
	protected function createImage(string $path = '', string $imageType = '')
	{
		if ($this->resource !== null)
		{
			return $this->resource;
		}

		if ($path === '')
		{
			$path = $this->image->getPathname();
		}

		if ($imageType === '')
		{
			$imageType = $this->image->imageType;
		}

		switch ($imageType)
		{
			case IMAGETYPE_GIF:
				if (! function_exists('imagecreatefromgif'))
				{
					throw ImageException::forInvalidImageCreate(lang('images.gifNotSupported'));
				}

				return imagecreatefromgif($path);
			case IMAGETYPE_JPEG:
				if (! function_exists('imagecreatefromjpeg'))
				{
					throw ImageException::forInvalidImageCreate(lang('images.jpgNotSupported'));
				}

				return imagecreatefromjpeg($path);
			case IMAGETYPE_PNG:
				if (! function_exists('imagecreatefrompng'))
				{
					throw ImageException::forInvalidImageCreate(lang('images.pngNotSupported'));
				}

				return imagecreatefrompng($path);
			default:
				throw ImageException::forInvalidImageCreate('Ima');
		}
	}

	//--------------------------------------------------------------------

	/**
	 * @param string $text
	 * @param array  $options
	 *
	 * @return void
	 */
	protected function _text(string $text, array $options = [])
	{
		// Reverse the vertical offset
		// When the image is positioned at the bottom
		// we don't want the vertical offset to push it
		// further down. We want the reverse, so we'll
		// invert the offset. Note: The horizontal
		// offset flips itself automatically

		if ($options['vAlign'] === 'bottom')
		{
			$options['vOffset'] = $options['vOffset'] * -1;
		}

		if ($options['hAlign'] === 'right')
		{
			$options['hOffset'] = $options['hOffset'] * -1;
		}

		// Set font width and height
		// These are calculated differently depending on
		// whether we are using the true type font or not
		if (! empty($options['fontPath']))
		{
			if (function_exists('imagettfbbox'))
			{
				$temp = imagettfbbox($options['fontSize'], 0, $options['fontPath'], $text);
				$temp = $temp[2] - $temp[0];

				$fontwidth = $temp / strlen($text);
			}
			else
			{
				$fontwidth = $options['fontSize'] - ($options['fontSize'] / 4);
			}

			$fontheight = $options['fontSize'];
		}
		else
		{
			$fontwidth  = imagefontwidth($options['fontSize']);
			$fontheight = imagefontheight($options['fontSize']);
		}

		$options['fontheight'] = $fontheight;
		$options['fontwidth']  = $fontwidth;

		// Set base X and Y axis values
		$xAxis = $options['hOffset'] + $options['padding'];
		$yAxis = $options['vOffset'] + $options['padding'];

		// Set vertical alignment
		if ($options['vAlign'] === 'middle')
		{
			// Don't apply padding when you're in the middle of the image.
			$yAxis += ($this->image->origHeight / 2) + ($fontheight / 2) - $options['padding'];
		}
		elseif ($options['vAlign'] === 'bottom')
		{
			$yAxis = ($this->image->origHeight - $fontheight - $options['shadowOffset'] - ($fontheight / 2)) - $yAxis;
		}

		// Set horizontal alignment
		if ($options['hAlign'] === 'right')
		{
			$xAxis += ($this->image->origWidth - ($fontwidth * strlen($text)) - $options['shadowOffset']) - (2 * $options['padding']);
		}
		elseif ($options['hAlign'] === 'center')
		{
			$xAxis += floor(($this->image->origWidth - ($fontwidth * strlen($text))) / 2);
		}

		$options['xAxis'] = $xAxis;
		$options['yAxis'] = $yAxis;

		if ($options['withShadow'])
		{
			// Offset from text
			$options['xShadow'] = $xAxis + $options['shadowOffset'];
			$options['yShadow'] = $yAxis + $options['shadowOffset'];

			$this->textOverlay($text, $options, true);
		}

		$this->textOverlay($text, $options, false);
	}

	//--------------------------------------------------------------------

	/**
	 * Handler-specific method for overlaying text on an image.
	 *
	 * @param string  $text
	 * @param array   $options
	 * @param boolean $isShadow Whether we are drawing the dropshadow or actual text
	 */
	protected function textOverlay(string $text, array $options = [], bool $isShadow = false)
	{
		$src = $this->createImage();

		/* Set RGB values for shadow
		 *
		 * Get the rest of the string and split it into 2-length
		 * hex values:
		 */
		$opacity = ($options['opacity'] * 127);

		// Allow opacity to be applied to the text
		imagealphablending($src, true);

		$color = $isShadow ? $options['shadowColor'] : $options['color'];
		$color = str_split(substr($color, 0, 6), 2);
		$color = imagecolorclosestalpha($src, hexdec($color[0]), hexdec($color[1]), hexdec($color[2]), $opacity);

		$xAxis = $isShadow ? $options['xShadow'] : $options['xAxis'];
		$yAxis = $isShadow ? $options['yShadow'] : $options['yAxis'];

		// Add the shadow to the source image
		if (! empty($options['fontPath']))
		{
			// We have to add fontheight because imagettftext locates the bottom left corner, not top-left corner.
			imagettftext($src, $options['fontSize'], 0, $xAxis, $yAxis + $options['fontheight'], $color, $options['fontPath'], $text);
		}
		else
		{
			imagestring($src, $options['fontSize'], $xAxis, $yAxis, $text, $color);
		}

		$this->resource = $src;
	}

	//--------------------------------------------------------------------

	public function _getWidth()
	{
		return imagesx($this->resource);
	}

	public function _getHeight()
	{
		return imagesy($this->resource);
	}

}


<?php namespace App\Controllers;

/**
 * Class BaseController
 *
 * BaseController provides a convenient place for loading components
 * and performing functions that are needed by all your controllers.
 * Extend this class in any new controllers:
 *     class Home extends BaseController
 *
 * For security be sure to declare any new methods as protected or private.
 *
 * @package CodeIgniter
 */

use CodeIgniter\Controller;

class BaseController extends Controller
{

	/**
	 * An array of helpers to be loaded automatically upon
	 * class instantiation. These helpers will be available
	 * to all other controllers that extend BaseController.
	 *
	 * @var array
	 */
	protected $helpers = [ ];

	/**
	 * Constructor.
	 *
	 */
	public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
	{
		// Do Not Edit This Line
		parent::initController($request, $response, $logger);

		//--------------------------------------------------------------------
		// Preload any models, libraries, etc, here.
		//--------------------------------------------------------------------
		// E.g.:
		// $this->session = \Config\Services::session();

	}
}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\HTTP;

use CodeIgniter\HTTP\Exceptions\HTTPException;

/**
 * An HTTP message
 */
class Message
{

	/**
	 * List of all HTTP request headers.
	 *
	 * @var array
	 */
	protected $headers = [];

	/**
	 * Holds a map of lower-case header names
	 * and their normal-case key as it is in $headers.
	 * Used for case-insensitive header access.
	 *
	 * @var array
	 */
	protected $headerMap = [];

	/**
	 * Protocol version
	 *
	 * @var string
	 */
	protected $protocolVersion;

	/**
	 * List of valid protocol versions
	 *
	 * @var array
	 */
	protected $validProtocolVersions = [
		'1.0',
		'1.1',
		'2',
	];

	/**
	 * Message body
	 *
	 * @var string
	 */
	protected $body;

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	// Body
	//--------------------------------------------------------------------

	/**
	 * Returns the Message's body.
	 *
	 * @return mixed
	 */
	public function getBody()
	{
		return $this->body;
	}

	//--------------------------------------------------------------------

	/**
	 * Sets the body of the current message.
	 *
	 * @param $data
	 *
	 * @return Message|Response
	 */
	public function setBody($data)
	{
		$this->body = $data;

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Appends data to the body of the current message.
	 *
	 * @param $data
	 *
	 * @return Message|Response
	 */
	public function appendBody($data)
	{
		$this->body .= (string) $data;

		return $this;
	}

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	// Headers
	//--------------------------------------------------------------------

	/**
	 * Populates the $headers array with any headers the getServer knows about.
	 */
	public function populateHeaders()
	{
		$contentType = $_SERVER['CONTENT_TYPE'] ?? getenv('CONTENT_TYPE');
		if (! empty($contentType))
		{
			$this->setHeader('Content-Type', $contentType);
		}
		unset($contentType);

		foreach ($_SERVER as $key => $val)
		{
			if (sscanf($key, 'HTTP_%s', $header) === 1)
			{
				// take SOME_HEADER and turn it into Some-Header
				$header = str_replace('_', ' ', strtolower($header));
				$header = str_replace(' ', '-', ucwords($header));

				$this->setHeader($header, $_SERVER[$key]);

				// Add us to the header map so we can find them case-insensitively
				$this->headerMap[strtolower($header)] = $header;
			}
		}
	}

	//--------------------------------------------------------------------

	/**
	 * Returns an array containing all headers.
	 *
	 * @return array        An array of the request headers
	 */
	public function getHeaders(): array
	{
		// If no headers are defined, but the user is
		// requesting it, then it's likely they want
		// it to be populated so do that...
		if (empty($this->headers))
		{
			$this->populateHeaders();
		}

		return $this->headers;
	}

	//--------------------------------------------------------------------

	/**
	 * Returns a single header object. If multiple headers with the same
	 * name exist, then will return an array of header objects.
	 *
	 * @param string $name
	 *
	 * @return array|\CodeIgniter\HTTP\Header
	 */
	public function getHeader(string $name)
	{
		$orig_name = $this->getHeaderName($name);

		if (! isset($this->headers[$orig_name]))
		{
			return null;
		}

		return $this->headers[$orig_name];
	}

	//--------------------------------------------------------------------

	/**
	 * Determines whether a header exists.
	 *
	 * @param string    $name
	 *
	 * @return boolean
	 */
	public function hasHeader(string $name): bool
	{
		$orig_name = $this->getHeaderName($name);

		return isset($this->headers[$orig_name]);
	}

	//--------------------------------------------------------------------

	/**
	 * Retrieves a comma-separated string of the values for a single header.
	 *
	 * This method returns all of the header values of the given
	 * case-insensitive header name as a string concatenated together using
	 * a comma.
	 *
	 * NOTE: Not all header values may be appropriately represented using
	 * comma concatenation. For such headers, use getHeader() instead
	 * and supply your own delimiter when concatenating.
	 *
	 * @param string $name
	 *
	 * @return string
	 */
	public function getHeaderLine(string $name): string
	{
		$orig_name = $this->getHeaderName($name);

		if (! array_key_exists($orig_name, $this->headers))
		{
			return '';
		}

		return $this->headers[$orig_name]->getValueLine();
	}

	//--------------------------------------------------------------------

	/**
	 * Sets a header and it's value.
	 *
	 * @param string            $name
	 * @param array|null|string $value
	 *
	 * @return Message|Response
	 */
	public function setHeader(string $name, $value)
	{
		if (! isset($this->headers[$name]))
		{
			$this->headers[$name] = new Header($name, $value);

			$this->headerMap[strtolower($name)] = $name;

			return $this;
		}

		if (! is_array($this->headers[$name]))
		{
			$this->headers[$name] = [$this->headers[$name]];
		}

		if (isset($this->headers[$name]))
		{
			$this->headers[$name] = new Header($name, $value);
		}

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Removes a header from the list of headers we track.
	 *
	 * @param string $name
	 *
	 * @return Message
	 */
	public function removeHeader(string $name)
	{
		$orig_name = $this->getHeaderName($name);

		unset($this->headers[$orig_name]);
		unset($this->headerMap[strtolower($name)]);

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Adds an additional header value to any headers that accept
	 * multiple values (i.e. are an array or implement ArrayAccess)
	 *
	 * @param string $name
	 * @param string $value
	 *
	 * @return Message
	 */
	public function appendHeader(string $name, string $value)
	{
		$orig_name = $this->getHeaderName($name);

		$this->headers[$orig_name]->appendValue($value);

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Adds an additional header value to any headers that accept
	 * multiple values (i.e. are an array or implement ArrayAccess)
	 *
	 * @param string $name
	 * @param string $value
	 *
	 * @return Message
	 */
	public function prependHeader(string $name, string $value)
	{
		$orig_name = $this->getHeaderName($name);

		$this->headers[$orig_name]->prependValue($value);

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Returns the HTTP Protocol Version.
	 *
	 * @return string
	 */
	public function getProtocolVersion(): string
	{
		return $this->protocolVersion ?? '1.1';
	}

	//--------------------------------------------------------------------

	/**
	 * Sets the HTTP protocol version.
	 *
	 * @param string $version
	 *
	 * @return Message
	 */
	public function setProtocolVersion(string $version)
	{
		if (! is_numeric($version))
		{
			$version = substr($version, strpos($version, '/') + 1);
		}

		if (! in_array($version, $this->validProtocolVersions))
		{
			throw HTTPException::forInvalidHTTPProtocol(implode(', ', $this->validProtocolVersions));
		}

		$this->protocolVersion = $version;

		return $this;
	}

	//--------------------------------------------------------------------

	/**
	 * Takes a header name in any case, and returns the
	 * normal-case version of the header.
	 *
	 * @param string $name
	 *
	 * @return string
	 */
	protected function getHeaderName(string $name): string
	{
		$lower_name = strtolower($name);

		return $this->headerMap[$lower_name] ?? $name;
	}

	//--------------------------------------------------------------------
}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Database;

use CodeIgniter\Database\MySQLi\Connection;
use CodeIgniter\Events\Events;

/**
 * Base prepared query
 */
abstract class BasePreparedQuery implements PreparedQueryInterface
{

	/**
	 * The prepared statement itself.
	 *
	 * @var resource|\mysqli_stmt
	 */
	protected $statement;

	/**
	 * The error code, if any.
	 *
	 * @var integer
	 */
	protected $errorCode;

	/**
	 * The error message, if any.
	 *
	 * @var string
	 */
	protected $errorString;

	/**
	 * Holds the prepared query object
	 * that is cloned during execute.
	 *
	 * @var Query
	 */
	protected $query;

	/**
	 * A reference to the db connection to use.
	 *
	 * @var BaseConnection|MySQLi\Connection
	 */
	protected $db;

	//--------------------------------------------------------------------

	public function __construct(ConnectionInterface $db)
	{
		$this->db = &$db;
	}

	//--------------------------------------------------------------------

	/**
	 * Prepares the query against the database, and saves the connection
	 * info necessary to execute the query later.
	 *
	 * NOTE: This version is based on SQL code. Child classes should
	 * override this method.
	 *
	 * @param string $sql
	 * @param array  $options    Passed to the connection's prepare statement.
	 * @param string $queryClass
	 *
	 * @return mixed
	 */
	public function prepare(string $sql, array $options = [], string $queryClass = 'CodeIgniter\\Database\\Query')
	{
		// We only supports positional placeholders (?)
		// in order to work with the execute method below, so we
		// need to replace our named placeholders (:name)
		$sql = preg_replace('/:[^\s,)]+/', '?', $sql);

		/**
		 * @var \CodeIgniter\Database\Query $query
		 */
		$query = new $queryClass($this->db);

		$query->setQuery($sql);

		if (! empty($this->db->swapPre) && ! empty($this->db->DBPrefix))
		{
			$query->swapPrefix($this->db->DBPrefix, $this->db->swapPre);
		}

		$this->query = $query;

		return $this->_prepare($query->getOriginalQuery(), $options);
	}

	//--------------------------------------------------------------------

	/**
	 * The database-dependent portion of the prepare statement.
	 *
	 * @param string $sql
	 * @param array  $options Passed to the connection's prepare statement.
	 *
	 * @return mixed
	 */
	abstract public function _prepare(string $sql, array $options = []);

	//--------------------------------------------------------------------

	/**
	 * Takes a new set of data and runs it against the currently
	 * prepared query. Upon success, will return a Results object.
	 *
	 * @param array $data
	 *
	 * @return ResultInterface
	 */
	public function execute(...$data)
	{
		// Execute the Query.
		$startTime = microtime(true);

		$result = $this->_execute($data);

		// Update our query object
		$query = clone $this->query;
		$query->setBinds($data);

		$query->setDuration($startTime);

		// Let others do something with this query
		Events::trigger('DBQuery', $query);

		// Return a result object
		$resultClass = str_replace('PreparedQuery', 'Result', get_class($this));

		$resultID = $this->_getResult();

		return new $resultClass($this->db->connID, $resultID);
	}

	//--------------------------------------------------------------------

	/**
	 * The database dependant version of the execute method.
	 *
	 * @param array $data
	 *
	 * @return boolean
	 */
	abstract public function _execute(array $data): bool;

	//--------------------------------------------------------------------

	/**
	 * Returns the result object for the prepared query.
	 *
	 * @return mixed
	 */
	abstract public function _getResult();

	//--------------------------------------------------------------------

	/**
	 * Explicitly closes the statement.
	 *
	 * @return null|void
	 */
	public function close()
	{
		if (! is_object($this->statement))
		{
			return;
		}

		$this->statement->close();
	}

	//--------------------------------------------------------------------

	/**
	 * Returns the SQL that has been prepared.
	 *
	 * @return string
	 */
	public function getQueryString(): string
	{
		if (! $this->query instanceof QueryInterface)
		{
			throw new \BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.');
		}

		return $this->query->getQuery();
	}

	//--------------------------------------------------------------------

	/**
	 * A helper to determine if any error exists.
	 *
	 * @return boolean
	 */
	public function hasError(): bool
	{
		return ! empty($this->errorString);
	}

	//--------------------------------------------------------------------

	/**
	 * Returns the error code created while executing this statement.
	 *
	 * @return integer
	 */
	public function getErrorCode(): int
	{
		return $this->errorCode;
	}

	//--------------------------------------------------------------------

	/**
	 * Returns the error message created while executing this statement.
	 *
	 * @return string
	 */
	public function getErrorMessage(): string
	{
		return $this->errorString;
	}

	//--------------------------------------------------------------------
}


<?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
 *
 * 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  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\View;

 /**
  * View plugins
  */
class Plugins
{

	/**
	 * @param array $params
	 *
	 * @return string|\CodeIgniter\HTTP\URI
	 */
	public static function currentURL(array $params = [])
	{
		return current_url();
	}

	//--------------------------------------------------------------------

	/**
	 * @param array $params
	 *
	 * @return \CodeIgniter\HTTP\URI|mixed|string
	 */
	public static function previousURL(array $params = [])
	{
		return previous_url();
	}

	//--------------------------------------------------------------------

	/**
	 * @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);
	}

	//--------------------------------------------------------------------

	/**
	 * @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);
	}

	//--------------------------------------------------------------------

	/**
	 * @param array $params
	 *
	 * @return string
	 */
	public static function lang(array $params = []): string
	{
		$line = array_shift($params);

		return lang($line, $params);
	}

	//--------------------------------------------------------------------

	/**
	 * @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']);
	}

	//--------------------------------------------------------------------

	/**
	 * @param array $params
	 *
	 * @return string|false
	 */
	public static function route(array $params = [])
	{
		return route_to(...$params);
	}

	//--------------------------------------------------------------------

	/**
	 * @param array $params
	 *
	 * @return string
	 */
	public static function siteURL(array $params = []): string
	{
		return site_url(...$params);
	}
}