<?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;

/**
 * Class ContentSecurityPolicy
 *
 * Provides tools for working with the Content-Security-Policy header
 * to help defeat XSS attacks.
 *
 * @see     http://www.w3.org/TR/CSP/
 * @see     http://www.html5rocks.com/en/tutorials/security/content-security-policy/
 * @see     http://content-security-policy.com/
 * @see     https://www.owasp.org/index.php/Content_Security_Policy
 * @package CodeIgniter\HTTP
 */
class ContentSecurityPolicy
{

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $baseURI = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $childSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array
	 */
	protected $connectSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $defaultSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $fontSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $formAction = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $frameAncestors = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $imageSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $mediaSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $objectSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $pluginTypes = [];

	/**
	 * Used for security enforcement
	 *
	 * @var string
	 */
	protected $reportURI = null;

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $sandbox = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $scriptSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $styleSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var array|string
	 */
	protected $manifestSrc = [];

	/**
	 * Used for security enforcement
	 *
	 * @var boolean
	 */
	protected $upgradeInsecureRequests = false;

	/**
	 * Used for security enforcement
	 *
	 * @var boolean
	 */
	protected $reportOnly = false;

	/**
	 * Used for security enforcement
	 *
	 * @var array
	 */
	protected $validSources = [
		'self',
		'none',
		'unsafe-inline',
		'unsafe-eval',
	];

	/**
	 * Used for security enforcement
	 *
	 * @var array
	 */
	protected $nonces = [];

	/**
	 * An array of header info since we have
	 * to build ourself before passing to Response.
	 *
	 * @var array
	 */
	protected $tempHeaders = [];

	/**
	 * An array of header info to build
	 * that should only be reported.
	 *
	 * @var array
	 */
	protected $reportOnlyHeaders = [];

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

	/**
	 * ContentSecurityPolicy constructor.
	 *
	 * Stores our default values from the Config file.
	 *
	 * @param \Config\ContentSecurityPolicy $config
	 */
	public function __construct(\Config\ContentSecurityPolicy $config)
	{
		foreach ($config as $setting => $value)
		{
			if (isset($this->{$setting}))
			{
				$this->{$setting} = $value;
			}
		}
	}

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

	/**
	 * Compiles and sets the appropriate headers in the request.
	 *
	 * Should be called just prior to sending the response to the user agent.
	 *
	 * @param ResponseInterface $response
	 */
	public function finalize(ResponseInterface &$response)
	{
		$this->generateNonces($response);

		$this->buildHeaders($response);
	}

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

	/**
	 * If TRUE, nothing will be restricted. Instead all violations will
	 * be reported to the reportURI for monitoring. This is useful when
	 * you are just starting to implement the policy, and will help
	 * determine what errors need to be addressed before you turn on
	 * all filtering.
	 *
	 * @param boolean|true $value
	 *
	 * @return $this
	 */
	public function reportOnly(bool $value = true)
	{
		$this->reportOnly = $value;

		return $this;
	}

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

	/**
	 * Adds a new base_uri value. Can be either a URI class or a simple string.
	 *
	 * base_uri restricts the URLs that can appear in a page’s <base> element.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-base-uri
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addBaseURI($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'baseURI', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for a form's action. Can be either
	 * a URI class or a simple string.
	 *
	 * child-src lists the URLs for workers and embedded frame contents.
	 * For example: child-src https://youtube.com would enable embedding
	 * videos from YouTube but not from other origins.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-child-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addChildSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'childSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for a form's action. Can be either
	 * a URI class or a simple string.
	 *
	 * connect-src limits the origins to which you can connect
	 * (via XHR, WebSockets, and EventSource).
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-connect-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addConnectSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'connectSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for a form's action. Can be either
	 * a URI class or a simple string.
	 *
	 * default_src is the URI that is used for many of the settings when
	 * no other source has been set.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-default-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function setDefaultSrc($uri, ?bool $explicitReporting = null)
	{
		$this->defaultSrc = [(string) $uri => $explicitReporting ?? $this->reportOnly];

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for a form's action. Can be either
	 * a URI class or a simple string.
	 *
	 * font-src specifies the origins that can serve web fonts.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-font-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addFontSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'fontSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for a form's action. Can be either
	 * a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-form-action
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addFormAction($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'formAction', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new resource that should allow embedding the resource using
	 * <frame>, <iframe>, <object>, <embed>, or <applet>
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-frame-ancestors
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addFrameAncestor($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'frameAncestors', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for valid image sources. Can be either
	 * a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-img-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addImageSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'imageSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for valid video and audio. Can be either
	 * a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-media-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addMediaSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'mediaSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for manifest sources. Can be either
	 * a URI class or simple string.
	 *
	 * @see https://www.w3.org/TR/CSP/#directive-manifest-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addManifestSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'manifestSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for Flash and other plugin sources. Can be either
	 * a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-object-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addObjectSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'objectSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Limits the types of plugins that can be used. Can be either
	 * a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-plugin-types
	 *
	 * @param string|array $mime              One or more plugin mime types, separate by spaces
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addPluginType($mime, ?bool $explicitReporting = null)
	{
		$this->addOption($mime, 'pluginTypes', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Specifies a URL where a browser will send reports when a content
	 * security policy is violated. Can be either a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-report-uri
	 *
	 * @param string $uri
	 *
	 * @return $this
	 */
	public function setReportURI(string $uri)
	{
		$this->reportURI = $uri;

		return $this;
	}

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

	/**
	 * specifies an HTML sandbox policy that the user agent applies to
	 * the protected resource.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-sandbox
	 *
	 * @param string|array $flags             An array of sandbox flags that can be added to the directive.
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addSandbox($flags, ?bool $explicitReporting = null)
	{
		$this->addOption($flags, 'sandbox', $explicitReporting ?? $this->reportOnly);
		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for javascript file sources. Can be either
	 * a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-connect-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addScriptSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'scriptSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Adds a new valid endpoint for CSS file sources. Can be either
	 * a URI class or a simple string.
	 *
	 * @see http://www.w3.org/TR/CSP/#directive-connect-src
	 *
	 * @param string|array $uri
	 * @param boolean|null $explicitReporting
	 *
	 * @return $this
	 */
	public function addStyleSrc($uri, ?bool $explicitReporting = null)
	{
		$this->addOption($uri, 'styleSrc', $explicitReporting ?? $this->reportOnly);

		return $this;
	}

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

	/**
	 * Sets whether the user agents should rewrite URL schemes, changing
	 * HTTP to HTTPS.
	 *
	 * @param boolean $value
	 *
	 * @return $this
	 */
	public function upgradeInsecureRequests(bool $value = true)
	{
		$this->upgradeInsecureRequests = $value;

		return $this;
	}

	//--------------------------------------------------------------------
	// Utility
	//--------------------------------------------------------------------

	/**
	 * DRY method to add an string or array to a class property.
	 *
	 * @param string|array $options
	 * @param string       $target
	 * @param boolean|null $explicitReporting
	 */
	protected function addOption($options, string $target, ?bool $explicitReporting = null)
	{
		// Ensure we have an array to work with...
		if (is_string($this->{$target}))
		{
			$this->{$target} = [$this->{$target}];
		}

		if (is_array($options))
		{
			foreach ($options as $opt)
			{
				$this->{$target}[$opt] = $explicitReporting ?? $this->reportOnly;
			}
		}
		else
		{
			$this->{$target}[$options] = $explicitReporting ?? $this->reportOnly;
		}
	}

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

	/**
	 * Scans the body of the request message and replaces any nonce
	 * placeholders with actual nonces, that we'll then add to our
	 * headers.
	 *
	 * @param ResponseInterface|\CodeIgniter\HTTP\Response $response
	 */
	protected function generateNonces(ResponseInterface &$response)
	{
		$body = $response->getBody();

		if (empty($body))
		{
			return;
		}

		if (! is_array($this->styleSrc))
		{
			$this->styleSrc = [$this->styleSrc];
		}
		if (! is_array($this->scriptSrc))
		{
			$this->scriptSrc = [$this->scriptSrc];
		}

		// Replace style placeholders with nonces
		$body = preg_replace_callback(
				'/{csp-style-nonce}/', function ($matches) {
					$nonce = bin2hex(random_bytes(12));

					$this->styleSrc[] = 'nonce-' . $nonce;

					return "nonce=\"{$nonce}\"";
				}, $body
		);

		// Replace script placeholders with nonces
		$body = preg_replace_callback(
				'/{csp-script-nonce}/', function ($matches) {
					$nonce = bin2hex(random_bytes(12));

					$this->scriptSrc[] = 'nonce-' . $nonce;

					return "nonce=\"{$nonce}\"";
				}, $body
		);

		$response->setBody($body);
	}

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

	/**
	 * Based on the current state of the elements, will add the appropriate
	 * Content-Security-Policy and Content-Security-Policy-Report-Only headers
	 * with their values to the response object.
	 *
	 * @param ResponseInterface|\CodeIgniter\HTTP\Response $response
	 */
	protected function buildHeaders(ResponseInterface &$response)
	{
		// Ensure both headers are available and arrays...
		$response->setHeader('Content-Security-Policy', []);
		$response->setHeader('Content-Security-Policy-Report-Only', []);

		$directives = [
			'base-uri'        => 'baseURI',
			'child-src'       => 'childSrc',
			'connect-src'     => 'connectSrc',
			'default-src'     => 'defaultSrc',
			'font-src'        => 'fontSrc',
			'form-action'     => 'formAction',
			'frame-ancestors' => 'frameAncestors',
			'img-src'         => 'imageSrc',
			'media-src'       => 'mediaSrc',
			'object-src'      => 'objectSrc',
			'plugin-types'    => 'pluginTypes',
			'script-src'      => 'scriptSrc',
			'style-src'       => 'styleSrc',
			'manifest-src'    => 'manifestSrc',
			'sandbox'         => 'sandbox',
			'report-uri'      => 'reportURI',
		];

		// inject default base & default URIs if needed
		if (empty($this->baseURI))
		{
			$this->baseURI = 'self';
		}
		if (empty($this->defaultSrc))
		{
			$this->defaultSrc = 'self';
		}

		foreach ($directives as $name => $property)
		{
			// base_uri
			if (! empty($this->{$property}))
			{
				$this->addToHeader($name, $this->{$property});
			}
		}

		// Compile our own header strings here since if we just
		// append it to the response, it will be joined with
		// commas, not semi-colons as we need.
		if (! empty($this->tempHeaders))
		{
			$header = '';
			foreach ($this->tempHeaders as $name => $value)
			{
				$header .= " {$name} {$value};";
			}
			// add token only if needed
			if ($this->upgradeInsecureRequests)
			{
				$header .= ' upgrade-insecure-requests;';
			}

			$response->appendHeader('Content-Security-Policy', $header);
		}

		if (! empty($this->reportOnlyHeaders))
		{
			$header = '';
			foreach ($this->reportOnlyHeaders as $name => $value)
			{
				$header .= " {$name} {$value};";
			}
			$response->appendHeader('Content-Security-Policy-Report-Only', $header);
		}

		$this->tempHeaders       = [];
		$this->reportOnlyHeaders = [];
	}

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

	/**
	 * Adds a directive and it's options to the appropriate header. The $values
	 * array might have options that are geared toward either the regular or the
	 * reportOnly header, since it's viable to have both simultaneously.
	 *
	 * @param string            $name
	 * @param array|string|null $values
	 */
	protected function addToHeader(string $name, $values = null)
	{
		if (is_string($values))
		{
			$values = [$values => 0];
		}

		$sources       = [];
		$reportSources = [];

		foreach ($values as $value => $reportOnly)
		{
			if (is_numeric($value) && is_string($reportOnly) && ! empty($reportOnly))
			{
				$value      = $reportOnly;
				$reportOnly = 0;
			}

			if ($reportOnly === true)
			{
				$reportSources[] = in_array($value, $this->validSources) ? "'{$value}'" : $value;
			}
			else
			{
				if (strpos($value, 'nonce-') === 0)
				{
					$sources[] = "'{$value}'";
				}
				else
				{
					$sources[] = in_array($value, $this->validSources) ? "'{$value}'" : $value;
				}
			}
		}

		if (! empty($sources))
		{
			$this->tempHeaders[$name] = implode(' ', $sources);
		}

		if (! empty($reportSources))
		{
			$this->reportOnlyHeaders[$name] = implode(' ', $reportSources);
		}
	}

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


<?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\Postgre;

use CodeIgniter\Database\PreparedQueryInterface;
use CodeIgniter\Database\BasePreparedQuery;

class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface
{

	/**
	 * Stores the name this query can be
	 * used under by postgres. Only used internally.
	 *
	 * @var string
	 */
	protected $name;

	/**
	 * The result resource from a successful
	 * pg_exec. Or false.
	 *
	 * @var
	 */
	protected $result;

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

	/**
	 * 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.
	 *                        Unused in the MySQLi driver.
	 *
	 * @return mixed
	 * @throws \Exception
	 */
	public function _prepare(string $sql, array $options = [])
	{
		$this->name = random_int(1, 10000000000000000);

		$sql = $this->parameterize($sql);

		// Update the query object since the parameters are slightly different
		// than what was put in.
		$this->query->setQuery($sql);

		if (! $this->statement = pg_prepare($this->db->connID, $this->name, $sql))
		{
			$this->errorCode   = 0;
			$this->errorString = pg_last_error($this->db->connID);
		}

		return $this;
	}

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

	/**
	 * 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 boolean
	 */
	public function _execute(array $data): bool
	{
		if (is_null($this->statement))
		{
			throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
		}

		$this->result = pg_execute($this->db->connID, $this->name, $data);

		return (bool) $this->result;
	}

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

	/**
	 * Returns the result object for the prepared query.
	 *
	 * @return mixed
	 */
	public function _getResult()
	{
		return $this->result;
	}

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

	/**
	 * Replaces the ? placeholders with $1, $2, etc parameters for use
	 * within the prepared query.
	 *
	 * @param string $sql
	 *
	 * @return string
	 */
	public function parameterize(string $sql): string
	{
		// Track our current value
		$count = 0;

		$sql = preg_replace_callback('/\?/', function ($matches) use (&$count) {
			$count ++;
			return "\${$count}";
		}, $sql);

		return $sql;
	}

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


<?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 4.0.0
 * @filesource
 */

namespace CodeIgniter\Debug\Toolbar\Collectors;

/**
 * Base Toolbar collector
 */
class BaseCollector
{

	/**
	 * Whether this collector has data that can
	 * be displayed in the Timeline.
	 *
	 * @var boolean
	 */
	protected $hasTimeline = false;

	/**
	 * Whether this collector needs to display
	 * content in a tab or not.
	 *
	 * @var boolean
	 */
	protected $hasTabContent = false;

	/**
	 * Whether this collector needs to display
	 * a label or not.
	 *
	 * @var boolean
	 */
	protected $hasLabel = false;

	/**
	 * Whether this collector has data that
	 * should be shown in the Vars tab.
	 *
	 * @var boolean
	 */
	protected $hasVarData = false;

	/**
	 * The 'title' of this Collector.
	 * Used to name things in the toolbar HTML.
	 *
	 * @var string
	 */
	protected $title = '';

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

	/**
	 * Gets the Collector's title.
	 *
	 * @param  boolean $safe
	 * @return string
	 */
	public function getTitle(bool $safe = false): string
	{
		if ($safe)
		{
			return str_replace(' ', '-', strtolower($this->title));
		}

		return $this->title;
	}

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

	/**
	 * Returns any information that should be shown next to the title.
	 *
	 * @return string
	 */
	public function getTitleDetails(): string
	{
		return '';
	}

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

	/**
	 * Does this collector need it's own tab?
	 *
	 * @return boolean
	 */
	public function hasTabContent(): bool
	{
		return (bool) $this->hasTabContent;
	}

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

	/**
	 * Does this collector have a label?
	 *
	 * @return boolean
	 */
	public function hasLabel(): bool
	{
		return (bool) $this->hasLabel;
	}

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

	/**
	 * Does this collector have information for the timeline?
	 *
	 * @return boolean
	 */
	public function hasTimelineData(): bool
	{
		return (bool) $this->hasTimeline;
	}

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

	/**
	 * Grabs the data for the timeline, properly formatted,
	 * or returns an empty array.
	 *
	 * @return array
	 */
	public function timelineData(): array
	{
		if (! $this->hasTimeline)
		{
			return [];
		}

		return $this->formatTimelineData();
	}

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

	/**
	 * Does this Collector have data that should be shown in the
	 * 'Vars' tab?
	 *
	 * @return boolean
	 */
	public function hasVarData(): bool
	{
		return (bool) $this->hasVarData;
	}

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

	/**
	 * Gets a collection of data that should be shown in the 'Vars' tab.
	 * The format is an array of sections, each with their own array
	 * of key/value pairs:
	 *
	 *  $data = [
	 *      'section 1' => [
	 *          'foo' => 'bar,
	 *          'bar' => 'baz'
	 *      ],
	 *      'section 2' => [
	 *          'foo' => 'bar,
	 *          'bar' => 'baz'
	 *      ],
	 *  ];
	 *
	 * @return null
	 */
	public function getVarData()
	{
		return null;
	}

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

	/**
	 * Child classes should implement this to return the timeline data
	 * formatted for correct usage.
	 *
	 * Timeline data should be formatted into arrays that look like:
	 *
	 *  [
	 *      'name'      => 'Database::Query',
	 *      'component' => 'Database',
	 *      'start'     => 10       // milliseconds
	 *      'duration'  => 15       // milliseconds
	 *  ]
	 *
	 * @return array
	 */
	protected function formatTimelineData(): array
	{
		return [];
	}

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

	/**
	 * Returns the data of this collector to be formatted in the toolbar
	 *
	 * @return array|string
	 */
	public function display()
	{
		return [];
	}

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

	/**
	 * Clean Path
	 *
	 * This makes nicer looking paths for the error output.
	 *
	 * @param string $file
	 *
	 * @return string
	 */
	public function cleanPath(string $file): string
	{
		if (strpos($file, APPPATH) === 0)
		{
			$file = 'APPPATH/' . substr($file, strlen(APPPATH));
		}
		elseif (strpos($file, SYSTEMPATH) === 0)
		{
			$file = 'SYSTEMPATH/' . substr($file, strlen(SYSTEMPATH));
		}
		elseif (strpos($file, FCPATH) === 0)
		{
			$file = 'FCPATH/' . substr($file, strlen(FCPATH));
		}

		return $file;
	}

	/**
	 * Gets the "badge" value for the button.
	 *
	 * @return null
	 */
	public function getBadgeValue()
	{
		return null;
	}

	/**
	 * Does this collector have any data collected?
	 *
	 * If not, then the toolbar button won't get shown.
	 *
	 * @return boolean
	 */
	public function isEmpty(): bool
	{
		return false;
	}

	/**
	 * Returns the HTML to display the icon. Should either
	 * be SVG, or a base-64 encoded.
	 *
	 * Recommended dimensions are 24px x 24px
	 *
	 * @return string
	 */
	public function icon(): string
	{
		return '';
	}

	public function getAsArray(): array
	{
		return [
			'title'           => $this->getTitle(),
			'titleSafe'       => $this->getTitle(true),
			'titleDetails'    => $this->getTitleDetails(),
			'display'         => $this->display(),
			'badgeValue'      => $this->getBadgeValue(),
			'isEmpty'         => $this->isEmpty(),
			'hasTabContent'   => $this->hasTabContent(),
			'hasLabel'        => $this->hasLabel(),
			'icon'            => $this->icon(),
			'hasTimelineData' => $this->hasTimelineData(),
			'timelineData'    => $this->timelineData(),
		];
	}

}


<?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 4.0.0
 * @filesource
 */

namespace CodeIgniter\Debug\Toolbar\Collectors;

use CodeIgniter\Config\Services;

/**
 * Loags collector
 */
class Logs extends BaseCollector
{

	/**
	 * Whether this collector has data that can
	 * be displayed in the Timeline.
	 *
	 * @var boolean
	 */
	protected $hasTimeline = false;

	/**
	 * Whether this collector needs to display
	 * content in a tab or not.
	 *
	 * @var boolean
	 */
	protected $hasTabContent = true;

	/**
	 * The 'title' of this Collector.
	 * Used to name things in the toolbar HTML.
	 *
	 * @var string
	 */
	protected $title = 'Logs';

	/**
	 * Our collected data.
	 *
	 * @var array
	 */
	protected $data;

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

	/**
	 * Returns the data of this collector to be formatted in the toolbar
	 *
	 * @return array
	 */
	public function display(): array
	{
		return [
			'logs' => $this->collectLogs(),
		];
	}

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

	/**
	 * Does this collector actually have any data to display?
	 *
	 * @return boolean
	 */
	public function isEmpty(): bool
	{
		$this->collectLogs();

		return empty($this->data);
	}

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

	/**
	 * Display the icon.
	 *
	 * Icon from https://icons8.com - 1em package
	 *
	 * @return string
	 */
	public function icon(): string
	{
		return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACYSURBVEhLYxgFJIHU1FSjtLS0i0D8AYj7gEKMEBkqAaAFF4D4ERCvAFrwH4gDoFIMKSkpFkB+OTEYqgUTACXfA/GqjIwMQyD9H2hRHlQKJFcBEiMGQ7VgAqCBvUgK32dmZspCpagGGNPT0/1BLqeF4bQHQJePpiIwhmrBBEADR1MRfgB0+WgqAmOoFkwANHA0FY0CUgEDAwCQ0PUpNB3kqwAAAABJRU5ErkJggg==';
	}

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

	/**
	 * Ensures the data has been collected.
	 */
	protected function collectLogs()
	{
		if (! is_null($this->data))
		{
			return $this->data;
		}

		return $this->data = Services::logger(true)->logCache ?? [];
	}

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


<?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\Cache\CacheInterface;
use CodeIgniter\View\Exceptions\ViewException;
use ReflectionMethod;

/**
 * Class Cell
 *
 * A simple class that can call any other class that can be loaded,
 * and echo out it's result. Intended for displaying small blocks of
 * content within views that can be managed by other libraries and
 * not require they are loaded within controller.
 *
 * Used with the helper function, it's use will look like:
 *
 *         viewCell('\Some\Class::method', 'limit=5 sort=asc', 60, 'cache-name');
 *
 * Parameters are matched up with the callback method's arguments of the same name:
 *
 *         class Class {
 *             function method($limit, $sort)
 *         }
 *
 * Alternatively, the params will be passed into the callback method as a simple array
 * if matching params are not found.
 *
 *         class Class {
 *             function method(array $params=null)
 *         }
 *
 * @package CodeIgniter\View
 */
class Cell
{

	/**
	 * Instance of the current Cache Instance
	 *
	 * @var CacheInterface
	 */
	protected $cache;

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

	/**
	 * Cell constructor.
	 *
	 * @param \CodeIgniter\Cache\CacheInterface $cache
	 */
	public function __construct(CacheInterface $cache)
	{
		$this->cache = $cache;
	}

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

	/**
	 * @param string      $library
	 * @param null        $params
	 * @param integer     $ttl
	 * @param string|null $cacheName
	 *
	 * @return string
	 * @throws \ReflectionException
	 */
	public function render(string $library, $params = null, int $ttl = 0, string $cacheName = null): string
	{
		list($class, $method) = $this->determineClass($library);

		// Is it cached?
		$cacheName = ! empty($cacheName) ? $cacheName : $class . $method . md5(serialize($params));

		if (! empty($this->cache) && $output = $this->cache->get($cacheName))
		{
			return $output;
		}

		// Not cached - so grab it...
		$instance = new $class();

		if (! method_exists($instance, $method))
		{
			throw ViewException::forInvalidCellMethod($class, $method);
		}

		// Try to match up the parameter list we were provided
		// with the parameter name in the callback method.
		$paramArray = $this->prepareParams($params);
		$refMethod  = new ReflectionMethod($instance, $method);
		$paramCount = $refMethod->getNumberOfParameters();
		$refParams  = $refMethod->getParameters();

		if ($paramCount === 0)
		{
			if (! empty($paramArray))
			{
				throw ViewException::forMissingCellParameters($class, $method);
			}

			$output = $instance->{$method}();
		}
		elseif (($paramCount === 1) && (
				( ! array_key_exists($refParams[0]->name, $paramArray)) ||
				(array_key_exists($refParams[0]->name, $paramArray) && count($paramArray) !== 1) )
		)
		{
			$output = $instance->{$method}($paramArray);
		}
		else
		{
			$fireArgs      = [];
			$method_params = [];

			foreach ($refParams as $arg)
			{
				$method_params[$arg->name] = true;
				if (array_key_exists($arg->name, $paramArray))
				{
					$fireArgs[$arg->name] = $paramArray[$arg->name];
				}
			}

			foreach ($paramArray as $key => $val)
			{
				if (! isset($method_params[$key]))
				{
					throw ViewException::forInvalidCellParameter($key);
				}
			}

			$output = $instance->$method(...array_values($fireArgs));
		}
		// Can we cache it?
		if (! empty($this->cache) && $ttl !== 0)
		{
			$this->cache->save($cacheName, $output, $ttl);
		}
		return $output;
	}

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

	/**
	 * Parses the params attribute. If an array, returns untouched.
	 * If a string, it should be in the format "key1=value key2=value".
	 * It will be split and returned as an array.
	 *
	 * @param $params
	 *
	 * @return array|null
	 */
	public function prepareParams($params)
	{
		if (empty($params) || ( ! is_string($params) && ! is_array($params)))
		{
			return [];
		}

		if (is_string($params))
		{
			$new_params = [];
			$separator  = ' ';

			if (strpos($params, ',') !== false)
			{
				$separator = ',';
			}

			$params = explode($separator, $params);
			unset($separator);

			foreach ($params as $p)
			{
				if (! empty($p))
				{
					list($key, $val)        = explode('=', $p);
					$new_params[trim($key)] = trim($val, ', ');
				}
			}

			$params = $new_params;

			unset($new_params);
		}

		if (is_array($params) && empty($params))
		{
			return [];
		}

		return $params;
	}

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

	/**
	 * Given the library string, attempts to determine the class and method
	 * to call.
	 *
	 * @param string $library
	 *
	 * @return array
	 */
	protected function determineClass(string $library): array
	{
		// We don't want to actually call static methods
		// by default, so convert any double colons.
		$library = str_replace('::', ':', $library);

		list($class, $method) = explode(':', $library);

		if (empty($class))
		{
			throw ViewException::forNoCellClass();
		}

		if (! class_exists($class, true))
		{
			throw ViewException::forInvalidCellClass($class);
		}

		if (empty($method))
		{
			$method = 'index';
		}

		return [
			$class,
			$method,
		];
	}

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


<?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;

/**
 * Class BaseResult
 */
abstract class BaseResult implements ResultInterface
{

	/**
	 * Connection ID
	 *
	 * @var resource|object
	 */
	public $connID;

	/**
	 * Result ID
	 *
	 * @var resource|object
	 */
	public $resultID;

	/**
	 * Result Array
	 *
	 * @var array[]
	 */
	public $resultArray = [];

	/**
	 * Result Object
	 *
	 * @var object[]
	 */
	public $resultObject = [];

	/**
	 * Custom Result Object
	 *
	 * @var object[]
	 */
	public $customResultObject = [];

	/**
	 * Current Row index
	 *
	 * @var integer
	 */
	public $currentRow = 0;

	/**
	 * Number of rows
	 *
	 * @var integer
	 */
	public $numRows;

	/**
	 * Row data
	 *
	 * @var array
	 */
	public $rowData;

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

	/**
	 * Constructor
	 *
	 * @param object|resource $connID
	 * @param object|resource $resultID
	 */
	public function __construct(&$connID, &$resultID)
	{
		$this->connID   = $connID;
		$this->resultID = $resultID;
	}

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

	/**
	 * Retrieve the results of the query. Typically an array of
	 * individual data rows, which can be either an 'array', an
	 * 'object', or a custom class name.
	 *
	 * @param string $type The row type. Either 'array', 'object', or a class name to use
	 *
	 * @return array
	 */
	public function getResult(string $type = 'object'): array
	{
		if ($type === 'array')
		{
			return $this->getResultArray();
		}
		elseif ($type === 'object')
		{
			return $this->getResultObject();
		}

		return $this->getCustomResultObject($type);
	}

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

	/**
	 * Returns the results as an array of custom objects.
	 *
	 * @param string $className The name of the class to use.
	 *
	 * @return mixed
	 */
	public function getCustomResultObject(string $className)
	{
		if (isset($this->customResultObject[$className]))
		{
			return $this->customResultObject[$className];
		}

		if (is_bool($this->resultID) || ! $this->resultID || $this->numRows === 0)
		{
			return [];
		}

		// Don't fetch the result set again if we already have it
		$_data = null;
		if (($c = count($this->resultArray)) > 0)
		{
			$_data = 'result_array';
		}
		elseif (($c = count($this->resultObject)) > 0)
		{
			$_data = 'result_object';
		}

		if ($_data !== null)
		{
			for ($i = 0; $i < $c; $i ++)
			{
				$this->customResultObject[$className][$i] = new $className();

				foreach ($this->{$_data}[$i] as $key => $value)
				{
					$this->customResultObject[$className][$i]->$key = $value;
				}
			}

			return $this->customResultObject[$className];
		}

		is_null($this->rowData) || $this->dataSeek(0);
		$this->customResultObject[$className] = [];

		while ($row = $this->fetchObject($className))
		{
			$this->customResultObject[$className][] = $row;
		}

		return $this->customResultObject[$className];
	}

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

	/**
	 * Returns the results as an array of arrays.
	 *
	 * If no results, an empty array is returned.
	 *
	 * @return array
	 */
	public function getResultArray(): array
	{
		if (! empty($this->resultArray))
		{
			return $this->resultArray;
		}

		// In the event that query caching is on, the result_id variable
		// will not be a valid resource so we'll simply return an empty
		// array.
		if (is_bool($this->resultID) || ! $this->resultID || $this->numRows === 0)
		{
			return [];
		}

		if ($this->resultObject)
		{
			foreach ($this->resultObject as $row)
			{
				$this->resultArray[] = (array) $row;
			}

			return $this->resultArray;
		}

		is_null($this->rowData) || $this->dataSeek(0);
		while ($row = $this->fetchAssoc())
		{
			$this->resultArray[] = $row;
		}

		return $this->resultArray;
	}

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

	/**
	 * Returns the results as an array of objects.
	 *
	 * If no results, an empty array is returned.
	 *
	 * @return array
	 */
	public function getResultObject(): array
	{
		if (! empty($this->resultObject))
		{
			return $this->resultObject;
		}

		// In the event that query caching is on, the result_id variable
		// will not be a valid resource so we'll simply return an empty
		// array.
		if (is_bool($this->resultID) || ! $this->resultID || $this->numRows === 0)
		{
			return [];
		}

		if ($this->resultArray)
		{
			foreach ($this->resultArray as $row)
			{
				$this->resultObject[] = (object) $row;
			}

			return $this->resultObject;
		}

		is_null($this->rowData) || $this->dataSeek(0);
		while ($row = $this->fetchObject())
		{
			$this->resultObject[] = $row;
		}

		return $this->resultObject;
	}

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

	/**
	 * Wrapper object to return a row as either an array, an object, or
	 * a custom class.
	 *
	 * If row doesn't exist, returns null.
	 *
	 * @param mixed   $n    The index of the results to return
	 * @param string  $type The type of result object. 'array', 'object' or class name.
	 *
	 * @return mixed
	 */
	public function getRow($n = 0, string $type = 'object')
	{
		if (! is_numeric($n))
		{
			// We cache the row data for subsequent uses
			is_array($this->rowData) || $this->rowData = $this->getRowArray(0);

			// array_key_exists() instead of isset() to allow for NULL values
			if (empty($this->rowData) || ! array_key_exists($n, $this->rowData))
			{
				return null;
			}

			return $this->rowData[$n];
		}

		if ($type === 'object')
		{
			return $this->getRowObject($n);
		}
		elseif ($type === 'array')
		{
			return $this->getRowArray($n);
		}

		return $this->getCustomRowObject($n, $type);
	}

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

	/**
	 * Returns a row as a custom class instance.
	 *
	 * If row doesn't exists, returns null.
	 *
	 * @param integer $n
	 * @param string  $className
	 *
	 * @return mixed
	 */
	public function getCustomRowObject(int $n, string $className)
	{
		isset($this->customResultObject[$className]) || $this->getCustomResultObject($className);

		if (empty($this->customResultObject[$className]))
		{
			return null;
		}

		if ($n !== $this->currentRow && isset($this->customResultObject[$className][$n]))
		{
			$this->currentRow = $n;
		}

		return $this->customResultObject[$className][$this->currentRow];
	}

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

	/**
	 * Returns a single row from the results as an array.
	 *
	 * If row doesn't exist, returns null.
	 *
	 * @param integer $n
	 *
	 * @return mixed
	 */
	public function getRowArray(int $n = 0)
	{
		$result = $this->getResultArray();
		if (empty($result))
		{
			return null;
		}

		if ($n !== $this->currentRow && isset($result[$n]))
		{
			$this->currentRow = $n;
		}

		return $result[$this->currentRow];
	}

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

	/**
	 * Returns a single row from the results as an object.
	 *
	 * If row doesn't exist, returns null.
	 *
	 * @param integer $n
	 *
	 * @return mixed
	 */
	public function getRowObject(int $n = 0)
	{
		$result = $this->getResultObject();
		if (empty($result))
		{
			return null;
		}

		if ($n !== $this->customResultObject && isset($result[$n]))
		{
			$this->currentRow = $n;
		}

		return $result[$this->currentRow];
	}

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

	/**
	 * Assigns an item into a particular column slot.
	 *
	 * @param mixed $key
	 * @param mixed $value
	 *
	 * @return mixed
	 */
	public function setRow($key, $value = null)
	{
		// We cache the row data for subsequent uses
		if (! is_array($this->rowData))
		{
			$this->rowData = $this->getRowArray(0);
		}

		if (is_array($key))
		{
			foreach ($key as $k => $v)
			{
				$this->rowData[$k] = $v;
			}

			return;
		}

		if ($key !== '' && $value !== null)
		{
			$this->rowData[$key] = $value;
		}
	}

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

	/**
	 * Returns the "first" row of the current results.
	 *
	 * @param string $type
	 *
	 * @return mixed
	 */
	public function getFirstRow(string $type = 'object')
	{
		$result = $this->getResult($type);

		return (empty($result)) ? null : $result[0];
	}

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

	/**
	 * Returns the "last" row of the current results.
	 *
	 * @param string $type
	 *
	 * @return mixed
	 */
	public function getLastRow(string $type = 'object')
	{
		$result = $this->getResult($type);

		return (empty($result)) ? null : $result[count($result) - 1];
	}

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

	/**
	 * Returns the "next" row of the current results.
	 *
	 * @param string $type
	 *
	 * @return mixed
	 */
	public function getNextRow(string $type = 'object')
	{
		$result = $this->getResult($type);
		if (empty($result))
		{
			return null;
		}

		return isset($result[$this->currentRow + 1]) ? $result[++ $this->currentRow] : null;
	}

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

	/**
	 * Returns the "previous" row of the current results.
	 *
	 * @param string $type
	 *
	 * @return mixed
	 */
	public function getPreviousRow(string $type = 'object')
	{
		$result = $this->getResult($type);
		if (empty($result))
		{
			return null;
		}

		if (isset($result[$this->currentRow - 1]))
		{
			-- $this->currentRow;
		}

		return $result[$this->currentRow];
	}

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

	/**
	 * Returns an unbuffered row and move the pointer to the next row.
	 *
	 * @param string $type
	 *
	 * @return mixed
	 */
	public function getUnbufferedRow(string $type = 'object')
	{
		if ($type === 'array')
		{
			return $this->fetchAssoc();
		}
		elseif ($type === 'object')
		{
			return $this->fetchObject();
		}

		return $this->fetchObject($type);
	}

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

	/**
	 * Gets the number of fields in the result set.
	 *
	 * @return integer
	 */
	abstract public function getFieldCount(): int;

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

	/**
	 * Generates an array of column names in the result set.
	 *
	 * @return array
	 */
	abstract public function getFieldNames(): array;

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

	/**
	 * Generates an array of objects representing field meta-data.
	 *
	 * @return array
	 */
	abstract public function getFieldData(): array;

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

	/**
	 * Frees the current result.
	 *
	 * @return void
	 */
	abstract public function freeResult();

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

	/**
	 * Moves the internal pointer to the desired offset. This is called
	 * internally before fetching results to make sure the result set
	 * starts at zero.
	 *
	 * @param integer $n
	 *
	 * @return mixed
	 */
	abstract public function dataSeek(int $n = 0);

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

	/**
	 * Returns the result set as an array.
	 *
	 * Overridden by driver classes.
	 *
	 * @return mixed
	 */
	abstract protected function fetchAssoc();

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

	/**
	 * Returns the result set as an object.
	 *
	 * Overridden by child classes.
	 *
	 * @param string $className
	 *
	 * @return object
	 */
	abstract protected function fetchObject(string $className = 'stdClass');

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


<?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;

/**
 * Class Negotiate
 *
 * Provides methods to negotiate with the HTTP headers to determine the best
 * type match between what the application supports and what the requesting
 * getServer wants.
 *
 * @see     http://tools.ietf.org/html/rfc7231#section-5.3
 * @package CodeIgniter\HTTP
 */
class Negotiate
{

	/**
	 * Request
	 *
	 * @var \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\IncomingRequest
	 */
	protected $request;

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

	/**
	 * Constructor
	 *
	 * @param \CodeIgniter\HTTP\RequestInterface $request
	 */
	public function __construct(RequestInterface $request = null)
	{
		if (! is_null($request))
		{
			$this->request = $request;
		}
	}

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

	/**
	 * Stores the request instance to grab the headers from.
	 *
	 * @param RequestInterface $request
	 *
	 * @return $this
	 */
	public function setRequest(RequestInterface $request)
	{
		$this->request = $request;

		return $this;
	}

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

	/**
	 * Determines the best content-type to use based on the $supported
	 * types the application says it supports, and the types requested
	 * by the client.
	 *
	 * If no match is found, the first, highest-ranking client requested
	 * type is returned.
	 *
	 * @param array   $supported
	 * @param boolean $strictMatch If TRUE, will return an empty string when no match found.
	 *                             If FALSE, will return the first supported element.
	 *
	 * @return string
	 */
	public function media(array $supported, bool $strictMatch = false): string
	{
		return $this->getBestMatch($supported, $this->request->getHeaderLine('accept'), true, $strictMatch);
	}

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

	/**
	 * Determines the best charset to use based on the $supported
	 * types the application says it supports, and the types requested
	 * by the client.
	 *
	 * If no match is found, the first, highest-ranking client requested
	 * type is returned.
	 *
	 * @param array $supported
	 *
	 * @return string
	 */
	public function charset(array $supported): string
	{
		$match = $this->getBestMatch($supported, $this->request->getHeaderLine('accept-charset'), false, true);

		// If no charset is shown as a match, ignore the directive
		// as allowed by the RFC, and tell it a default value.
		if (empty($match))
		{
			return 'utf-8';
		}

		return $match;
	}

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

	/**
	 * Determines the best encoding type to use based on the $supported
	 * types the application says it supports, and the types requested
	 * by the client.
	 *
	 * If no match is found, the first, highest-ranking client requested
	 * type is returned.
	 *
	 * @param array $supported
	 *
	 * @return string
	 */
	public function encoding(array $supported = []): string
	{
		array_push($supported, 'identity');

		return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-encoding'));
	}

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

	/**
	 * Determines the best language to use based on the $supported
	 * types the application says it supports, and the types requested
	 * by the client.
	 *
	 * If no match is found, the first, highest-ranking client requested
	 * type is returned.
	 *
	 * @param array $supported
	 *
	 * @return string
	 */
	public function language(array $supported): string
	{
		return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language'));
	}

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	// Utility Methods
	//--------------------------------------------------------------------

	/**
	 * Does the grunt work of comparing any of the app-supported values
	 * against a given Accept* header string.
	 *
	 * Portions of this code base on Aura.Accept library.
	 *
	 * @param array   $supported    App-supported values
	 * @param string  $header       header string
	 * @param boolean $enforceTypes If TRUE, will compare media types and sub-types.
	 * @param boolean $strictMatch  If TRUE, will return empty string on no match.
	 *                              If FALSE, will return the first supported element.
	 *
	 * @return string Best match
	 */
	protected function getBestMatch(array $supported, string $header = null, bool $enforceTypes = false, bool $strictMatch = false): string
	{
		if (empty($supported))
		{
			throw HTTPException::forEmptySupportedNegotiations();
		}

		if (empty($header))
		{
			return $strictMatch ? '' : $supported[0];
		}

		$acceptable = $this->parseHeader($header);

		foreach ($acceptable as $accept)
		{
			// if acceptable quality is zero, skip it.
			if ($accept['q'] === 0.0)
			{
				continue;
			}

			// if acceptable value is "anything", return the first available
			if ($accept['value'] === '*' || $accept['value'] === '*/*')
			{
				return $supported[0];
			}

			// If an acceptable value is supported, return it
			foreach ($supported as $available)
			{
				if ($this->match($accept, $available, $enforceTypes))
				{
					return $available;
				}
			}
		}

		// No matches? Return the first supported element.
		return $strictMatch ? '' : $supported[0];
	}

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

	/**
	 * Parses an Accept* header into it's multiple values.
	 *
	 * This is based on code from Aura.Accept library.
	 *
	 * @param string $header
	 *
	 * @return array
	 */
	public function parseHeader(string $header): array
	{
		$results    = [];
		$acceptable = explode(',', $header);

		foreach ($acceptable as $value)
		{
			$pairs = explode(';', $value);

			$value = $pairs[0];

			unset($pairs[0]);

			$parameters = [];

			foreach ($pairs as $pair)
			{
				$param = [];
				preg_match(
						'/^(?P<name>.+?)=(?P<quoted>"|\')?(?P<value>.*?)(?:\k<quoted>)?$/', $pair, $param
				);
				$parameters[trim($param['name'])] = trim($param['value']);
			}

			$quality = 1.0;

			if (array_key_exists('q', $parameters))
			{
				$quality = $parameters['q'];
				unset($parameters['q']);
			}

			$results[] = [
				'value'  => trim($value),
				'q'      => (float) $quality,
				'params' => $parameters,
			];
		}

		// Sort to get the highest results first
		usort($results, function ($a, $b) {
			if ($a['q'] === $b['q'])
			{
				$a_ast = substr_count($a['value'], '*');
				$b_ast = substr_count($b['value'], '*');

				// '*/*' has lower precedence than 'text/*',
				// and 'text/*' has lower priority than 'text/plain'
				//
				// This seems backwards, but needs to be that way
				// due to the way PHP7 handles ordering or array
				// elements created by reference.
				if ($a_ast > $b_ast)
				{
					return 1;
				}

				// If the counts are the same, but one element
				// has more params than another, it has higher precedence.
				//
				// This seems backwards, but needs to be that way
				// due to the way PHP7 handles ordering or array
				// elements created by reference.
				if ($a_ast === $b_ast)
				{
					return count($b['params']) - count($a['params']);
				}

				return 0;
			}

			// Still here? Higher q values have precedence.
			return ($a['q'] > $b['q']) ? -1 : 1;
		});

		return $results;
	}

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

	/**
	 * Match-maker
	 *
	 * @param  array   $acceptable
	 * @param  string  $supported
	 * @param  boolean $enforceTypes
	 * @return boolean
	 */
	protected function match(array $acceptable, string $supported, bool $enforceTypes = false): bool
	{
		$supported = $this->parseHeader($supported);
		if (is_array($supported) && count($supported) === 1)
		{
			$supported = $supported[0];
		}

		// Is it an exact match?
		if ($acceptable['value'] === $supported['value'])
		{
			return $this->matchParameters($acceptable, $supported);
		}

		// Do we need to compare types/sub-types? Only used
		// by negotiateMedia().
		if ($enforceTypes)
		{
			return $this->matchTypes($acceptable, $supported);
		}

		return false;
	}

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

	/**
	 * Checks two Accept values with matching 'values' to see if their
	 * 'params' are the same.
	 *
	 * @param array $acceptable
	 * @param array $supported
	 *
	 * @return boolean
	 */
	protected function matchParameters(array $acceptable, array $supported): bool
	{
		if (count($acceptable['params']) !== count($supported['params']))
		{
			return false;
		}

		foreach ($supported['params'] as $label => $value)
		{
			if (! isset($acceptable['params'][$label]) ||
					$acceptable['params'][$label] !== $value)
			{
				return false;
			}
		}

		return true;
	}

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

	/**
	 * Compares the types/subtypes of an acceptable Media type and
	 * the supported string.
	 *
	 * @param array $acceptable
	 * @param array $supported
	 *
	 * @return boolean
	 */
	public function matchTypes(array $acceptable, array $supported): bool
	{
		list($aType, $aSubType) = explode('/', $acceptable['value']);
		list($sType, $sSubType) = explode('/', $supported['value']);

		// If the types don't match, we're done.
		if ($aType !== $sType)
		{
			return false;
		}

		// If there's an asterisk, we're cool
		if ($aSubType === '*')
		{
			return true;
		}

		// Otherwise, subtypes must match also.
		return $aSubType === $sSubType;
	}

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


<?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\MySQLi;

use CodeIgniter\Database\BaseResult;
use CodeIgniter\Database\ResultInterface;

/**
 * Result for MySQLi
 */
class Result extends BaseResult implements ResultInterface
{

	/**
	 * Gets the number of fields in the result set.
	 *
	 * @return integer
	 */
	public function getFieldCount(): int
	{
		return $this->resultID->field_count;
	}

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

	/**
	 * Generates an array of column names in the result set.
	 *
	 * @return array
	 */
	public function getFieldNames(): array
	{
		$fieldNames = [];
		$this->resultID->field_seek(0);
		while ($field = $this->resultID->fetch_field())
		{
			$fieldNames[] = $field->name;
		}

		return $fieldNames;
	}

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

	/**
	 * Generates an array of objects representing field meta-data.
	 *
	 * @return array
	 */
	public function getFieldData(): array
	{
		$retVal    = [];
		$fieldData = $this->resultID->fetch_fields();

		foreach ($fieldData as $i => $data)
		{
			$retVal[$i]              = new \stdClass();
			$retVal[$i]->name        = $data->name;
			$retVal[$i]->type        = $data->type;
			$retVal[$i]->max_length  = $data->max_length;
			$retVal[$i]->primary_key = (int) ($data->flags & 2);
			$retVal[$i]->default     = $data->def;
		}

		return $retVal;
	}

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

	/**
	 * Frees the current result.
	 *
	 * @return void
	 */
	public function freeResult()
	{
		if (is_object($this->resultID))
		{
			$this->resultID->free();
			$this->resultID = false;
		}
	}

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

	/**
	 * Moves the internal pointer to the desired offset. This is called
	 * internally before fetching results to make sure the result set
	 * starts at zero.
	 *
	 * @param integer $n
	 *
	 * @return mixed
	 */
	public function dataSeek(int $n = 0)
	{
		return $this->resultID->data_seek($n);
	}

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

	/**
	 * Returns the result set as an array.
	 *
	 * Overridden by driver classes.
	 *
	 * @return mixed
	 */
	protected function fetchAssoc()
	{
		return $this->resultID->fetch_assoc();
	}

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

	/**
	 * Returns the result set as an object.
	 *
	 * Overridden by child classes.
	 *
	 * @param string $className
	 *
	 * @return object
	 */
	protected function fetchObject(string $className = 'stdClass')
	{
		return $this->resultID->fetch_object($className);
	}

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


<?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\CLI;

use CodeIgniter\CodeIgniter;

/**
 * Console
 */
class Console
{

	/**
	 * Main CodeIgniter instance.
	 *
	 * @var CodeIgniter
	 */
	protected $app;

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

	/**
	 * Console constructor.
	 *
	 * @param \CodeIgniter\CodeIgniter $app
	 */
	public function __construct(CodeIgniter $app)
	{
		$this->app = $app;
	}

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

	/**
	 * Runs the current command discovered on the CLI.
	 *
	 * @param boolean $useSafeOutput
	 *
	 * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\Response|\CodeIgniter\HTTP\ResponseInterface|mixed
	 * @throws \Exception
	 */
	public function run(bool $useSafeOutput = false)
	{
		$path = CLI::getURI() ?: 'list';

		// Set the path for the application to route to.
		$this->app->setPath("ci{$path}");

		return $this->app->useSafeOutput($useSafeOutput)->run();
	}

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

	/**
	 * Displays basic information about the Console.
	 */
	public function showHeader()
	{
		CLI::newLine(1);

		CLI::write(CLI::color('CodeIgniter CLI Tool', 'green')
				. ' - Version ' . CodeIgniter::CI_VERSION
				. ' - Server-Time: ' . date('Y-m-d H:i:sa'));

		CLI::newLine(1);
	}

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


<?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\Pager;

use CodeIgniter\Pager\Exceptions\PagerException;
use Config\Services;
use CodeIgniter\View\RendererInterface;

/**
 * Class Pager
 *
 * The Pager class provides semi-automatic and manual methods for creating
 * pagination links and reading the current url's query variable, "page"
 * to determine the current page. This class can support multiple
 * paginations on a single page.
 *
 * @package CodeIgniter\Pager
 */
class Pager implements PagerInterface
{

	/**
	 * The group data.
	 *
	 * @var array
	 */
	protected $groups = [];

	/**
	 * URI segment for groups if provided.
	 *
	 * @var array
	 */
	protected $segment = [];

	/**
	 * Our configuration instance.
	 *
	 * @var \Config\Pager
	 */
	protected $config;

	/**
	 * The view engine to render the links with.
	 *
	 * @var RendererInterface
	 */
	protected $view;

	/**
	 * List of only permitted queries
	 *
	 * @var array
	 */
	protected $only = [];

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

	public function __construct($config, RendererInterface $view)
	{
		$this->config = $config;
		$this->view   = $view;
	}

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

	/**
	 * Handles creating and displaying the
	 *
	 * @param string $group
	 * @param string $template The output template alias to render.
	 *
	 * @return string
	 */
	public function links(string $group = 'default', string $template = 'default_full'): string
	{
		$this->ensureGroup($group);

		return $this->displayLinks($group, $template);
	}

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

	/**
	 * Creates simple Next/Previous links, instead of full pagination.
	 *
	 * @param string $group
	 * @param string $template
	 *
	 * @return string
	 */
	public function simpleLinks(string $group = 'default', string $template = 'default_simple'): string
	{
		$this->ensureGroup($group);

		return $this->displayLinks($group, $template);
	}

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

	/**
	 * Allows for a simple, manual, form of pagination where all of the data
	 * is provided by the user. The URL is the current URI.
	 *
	 * @param integer $page
	 * @param integer $perPage
	 * @param integer $total
	 * @param string  $template The output template alias to render.
	 * @param integer $segment  (if page number is provided by URI segment)
	 *
	 * @return string
	 */
	public function makeLinks(int $page, int $perPage, int $total, string $template = 'default_full', int $segment = 0): string
	{
		$name = time();

		$this->store($name, $page, $perPage, $total, $segment);

		return $this->displayLinks($name, $template);
	}

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

	/**
	 * Does the actual work of displaying the view file. Used internally
	 * by links(), simpleLinks(), and makeLinks().
	 *
	 * @param string $group
	 * @param string $template
	 *
	 * @return string
	 */
	protected function displayLinks(string $group, string $template): string
	{
		$pager = new PagerRenderer($this->getDetails($group));

		if (! array_key_exists($template, $this->config->templates))
		{
			throw PagerException::forInvalidTemplate($template);
		}

		return $this->view->setVar('pager', $pager)
						->render($this->config->templates[$template]);
	}

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

	/**
	 * Stores a set of pagination data for later display. Most commonly used
	 * by the model to automate the process.
	 *
	 * @param string  $group
	 * @param integer $page
	 * @param integer $perPage
	 * @param integer $total
	 * @param integer $segment
	 *
	 * @return $this
	 */
	public function store(string $group, int $page, int $perPage, int $total, int $segment = 0)
	{
		$this->segment[$group] = $segment;

		$this->ensureGroup($group);

		$this->groups[$group]['currentPage'] = $page;
		$this->groups[$group]['perPage']     = $perPage;
		$this->groups[$group]['total']       = $total;
		$this->groups[$group]['pageCount']   = (int)ceil($total / $perPage);

		return $this;
	}

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

	/**
	 * Sets the path that an aliased group of links will use.
	 *
	 * @param string $group
	 * @param string $path
	 *
	 * @return mixed
	 */
	public function setPath(string $path, string $group = 'default')
	{
		$this->ensureGroup($group);

		$this->groups[$group]['uri']->setPath($path);

		return $this;
	}

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

	/**
	 * Returns the total number of pages.
	 *
	 * @param string|null $group
	 *
	 * @return integer
	 */
	public function getPageCount(string $group = 'default'): int
	{
		$this->ensureGroup($group);

		return $this->groups[$group]['pageCount'];
	}

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

	/**
	 * Returns the number of the current page of results.
	 *
	 * @param string|null $group
	 *
	 * @return integer
	 */
	public function getCurrentPage(string $group = 'default'): int
	{
		$this->ensureGroup($group);

		return $this->groups[$group]['currentPage'];
	}

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

	/**
	 * Tells whether this group of results has any more pages of results.
	 *
	 * @param string|null $group
	 *
	 * @return boolean
	 */
	public function hasMore(string $group = 'default'): bool
	{
		$this->ensureGroup($group);

		return ($this->groups[$group]['currentPage'] * $this->groups[$group]['perPage']) < $this->groups[$group]['total'];
	}

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

	/**
	 * Returns the last page, if we have a total that we can calculate with.
	 *
	 * @param string $group
	 *
	 * @return integer|null
	 */
	public function getLastPage(string $group = 'default')
	{
		$this->ensureGroup($group);

		if (! is_numeric($this->groups[$group]['total']) || ! is_numeric($this->groups[$group]['perPage']))
		{
			return null;
		}

		return (int)ceil($this->groups[$group]['total'] / $this->groups[$group]['perPage']);
	}

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

	/**
	 * Determines the first page # that should be shown.
	 *
	 * @param string $group
	 *
	 * @return integer
	 */
	public function getFirstPage(string $group = 'default'): int
	{
		$this->ensureGroup($group);

		// @todo determine based on a 'surroundCount' value
		return 1;
	}

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

	/**
	 * Returns the URI for a specific page for the specified group.
	 *
	 * @param integer|null $page
	 * @param string       $group
	 * @param boolean      $returnObject
	 *
	 * @return string|\CodeIgniter\HTTP\URI
	 */
	public function getPageURI(int $page = null, string $group = 'default', bool $returnObject = false)
	{
		$this->ensureGroup($group);

		/**
		 * @var \CodeIgniter\HTTP\URI $uri
		 */
		$uri = $this->groups[$group]['uri'];

		$segment = $this->segment[$group] ?? 0;

		if ($segment)
		{
			$uri->setSegment($segment, $page);
		}
		else
		{
			$uri->addQuery('page', $page);
		}

		if ($this->only)
		{
			$query = array_intersect_key($_GET, array_flip($this->only));

			if (! $segment)
			{
				$query['page'] = $page;
			}

			$uri->setQueryArray($query);
		}

		return $returnObject === true ? $uri : (string) $uri;
	}

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

	/**
	 * Returns the full URI to the next page of results, or null.
	 *
	 * @param string  $group
	 * @param boolean $returnObject
	 *
	 * @return string|null
	 */
	public function getNextPageURI(string $group = 'default', bool $returnObject = false)
	{
		$this->ensureGroup($group);

		$last = $this->getLastPage($group);
		$curr = $this->getCurrentPage($group);
		$page = null;

		if (! empty($last) && ! empty($curr) && $last === $curr)
		{
			return null;
		}

		if ($last > $curr)
		{
			$page = $curr + 1;
		}

		return $this->getPageURI($page, $group, $returnObject);
	}

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

	/**
	 * Returns the full URL to the previous page of results, or null.
	 *
	 * @param string  $group
	 * @param boolean $returnObject
	 *
	 * @return string|null
	 */
	public function getPreviousPageURI(string $group = 'default', bool $returnObject = false)
	{
		$this->ensureGroup($group);

		$first = $this->getFirstPage($group);
		$curr  = $this->getCurrentPage($group);
		$page  = null;

		if (! empty($first) && ! empty($curr) && $first === $curr)
		{
			return null;
		}

		if ($first < $curr)
		{
			$page = $curr - 1;
		}

		return $this->getPageURI($page, $group, $returnObject);
	}

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

	/**
	 * Returns the number of results per page that should be shown.
	 *
	 * @param string $group
	 *
	 * @return integer
	 */
	public function getPerPage(string $group = 'default'): int
	{
		$this->ensureGroup($group);

		return (int) $this->groups[$group]['perPage'];
	}

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

	/**
	 * Returns an array with details about the results, including
	 * total, per_page, current_page, last_page, next_url, prev_url, from, to.
	 * Does not include the actual data. This data is suitable for adding
	 * a 'data' object to with the result set and converting to JSON.
	 *
	 * @param string $group
	 *
	 * @return array
	 */
	public function getDetails(string $group = 'default'): array
	{
		if (! array_key_exists($group, $this->groups))
		{
			throw PagerException::forInvalidPaginationGroup($group);
		}

		$newGroup = $this->groups[$group];

		$newGroup['next']     = $this->getNextPageURI($group);
		$newGroup['previous'] = $this->getPreviousPageURI($group);
		$newGroup['segment']  = $this->segment[$group] ?? 0;

		return $newGroup;
	}

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

	/**
	 * Sets only allowed queries on pagination links.
	 *
	 * @param array $queries
	 *
	 * @return Pager
	 */
	public function only(array $queries):Pager
	{
		$this->only = $queries;

		return $this;
	}

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

	/**
	 * Ensures that an array exists for the group specified.
	 *
	 * @param string $group
	 */
	protected function ensureGroup(string $group)
	{
		if (array_key_exists($group, $this->groups))
		{
			return;
		}

		$this->groups[$group] = [
			'uri'       => clone Services::request()->uri,
			'hasMore'   => false,
			'total'     => null,
			'perPage'   => $this->config->perPage,
			'pageCount' => 1,
		];

		if (array_key_exists($group, $this->segment))
		{
			try
			{
				$this->groups[$group]['currentPage'] = $this->groups[$group]['uri']->getSegment($this->segment[$group]);
			}
			catch (\CodeIgniter\HTTP\Exceptions\HTTPException $e)
			{
				$this->groups[$group]['currentPage'] = 1;
			}
		}
		else
		{
			$this->groups[$group]['currentPage'] = $_GET['page_' . $group] ?? $_GET['page'] ?? 1;
		}

		if ($_GET)
		{
			$this->groups[$group]['uri'] = $this->groups[$group]['uri']->setQueryArray($_GET);
		}
	}

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


<?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\I18n;

use DateTime;
use IntlCalendar;

/**
 * Class TimeDifference
 *
 * @package CodeIgniter\I18n
 */
class TimeDifference
{
	/**
	 * The timestamp of the "current" time.
	 *
	 * @var integer
	 */
	protected $currentTime;

	/**
	 * The timestamp to compare the current time to.
	 *
	 * @var integer
	 */
	protected $testTime;

	/**
	 * @var float
	 */
	protected $eras = 0;

	/**
	 * @var float
	 */
	protected $years = 0;
	/**
	 * @var float
	 */
	protected $months = 0;
	/**
	 * @var integer
	 */
	protected $weeks = 0;
	/**
	 * @var integer
	 */
	protected $days = 0;
	/**
	 * @var integer
	 */
	protected $hours = 0;
	/**
	 * @var integer
	 */
	protected $minutes = 0;
	/**
	 * @var integer
	 */
	protected $seconds = 0;

	/**
	 * Difference in seconds.
	 *
	 * @var integer
	 */
	protected $difference;

	/**
	 * Note: both parameters are required to be in the same timezone. No timezone
	 * shifting is done internally.
	 *
	 * @param DateTime $currentTime
	 * @param DateTime $testTime
	 */
	public function __construct(DateTime $currentTime, DateTime $testTime)
	{
		$this->difference = $currentTime->getTimestamp() - $testTime->getTimestamp();

		$current = IntlCalendar::fromDateTime($currentTime->format('Y-m-d H:i:s'));
		$time    = IntlCalendar::fromDateTime($testTime->format('Y-m-d H:i:s'))
						->getTime();

		$this->currentTime = $current;
		$this->testTime    = $time;
	}

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

	/**
	 * Returns the number of years of difference between the two.
	 *
	 * @param boolean $raw
	 *
	 * @return float|integer
	 */
	public function getYears(bool $raw = false)
	{
		if ($raw)
		{
			return $this->difference / YEAR;
		}

		$time = clone($this->currentTime);
		return $time->fieldDifference($this->testTime, IntlCalendar::FIELD_YEAR);
	}

	/**
	 * Returns the number of months difference between the two dates.
	 *
	 * @param boolean $raw
	 *
	 * @return float|integer
	 */
	public function getMonths(bool $raw = false)
	{
		if ($raw)
		{
			return $this->difference / MONTH;
		}

		$time = clone($this->currentTime);
		return $time->fieldDifference($this->testTime, IntlCalendar::FIELD_MONTH);
	}

	/**
	 * Returns the number of weeks difference between the two dates.
	 *
	 * @param boolean $raw
	 *
	 * @return float|integer
	 */
	public function getWeeks(bool $raw = false)
	{
		if ($raw)
		{
			return $this->difference / WEEK;
		}

		$time = clone($this->currentTime);
		return (int)($time->fieldDifference($this->testTime, IntlCalendar::FIELD_DAY_OF_YEAR) / 7);
	}

	/**
	 * Returns the number of days difference between the two dates.
	 *
	 * @param boolean $raw
	 *
	 * @return float|integer
	 */
	public function getDays(bool $raw = false)
	{
		if ($raw)
		{
			return $this->difference / DAY;
		}

		$time = clone($this->currentTime);
		return $time->fieldDifference($this->testTime, IntlCalendar::FIELD_DAY_OF_YEAR);
	}

	/**
	 * Returns the number of hours difference between the two dates.
	 *
	 * @param boolean $raw
	 *
	 * @return float|integer
	 */
	public function getHours(bool $raw = false)
	{
		if ($raw)
		{
			return $this->difference / HOUR;
		}

		$time = clone($this->currentTime);
		return $time->fieldDifference($this->testTime, IntlCalendar::FIELD_HOUR_OF_DAY);
	}

	/**
	 * Returns the number of minutes difference between the two dates.
	 *
	 * @param boolean $raw
	 *
	 * @return float|integer
	 */
	public function getMinutes(bool $raw = false)
	{
		if ($raw)
		{
			return $this->difference / MINUTE;
		}

		$time = clone($this->currentTime);
		return $time->fieldDifference($this->testTime, IntlCalendar::FIELD_MINUTE);
	}

	/**
	 * Returns the number of seconds difference between the two dates.
	 *
	 * @param boolean $raw
	 *
	 * @return integer
	 */
	public function getSeconds(bool $raw = false)
	{
		if ($raw)
		{
			return $this->difference;
		}

		$time = clone($this->currentTime);
		return $time->fieldDifference($this->testTime, IntlCalendar::FIELD_SECOND);
	}

	/**
	 * Convert the time to human readable format
	 *
	 * @param string|null $locale
	 *
	 * @return string
	 */
	public function humanize(string $locale = null): string
	{
		$current = clone($this->currentTime);

		$years   = $current->fieldDifference($this->testTime, IntlCalendar::FIELD_YEAR);
		$months  = $current->fieldDifference($this->testTime, IntlCalendar::FIELD_MONTH);
		$days    = $current->fieldDifference($this->testTime, IntlCalendar::FIELD_DAY_OF_YEAR);
		$hours   = $current->fieldDifference($this->testTime, IntlCalendar::FIELD_HOUR_OF_DAY);
		$minutes = $current->fieldDifference($this->testTime, IntlCalendar::FIELD_MINUTE);

		$phrase = null;

		if ($years !== 0)
		{
			$phrase = lang('Time.years', [abs($years)], $locale);
			$before = $years < 0;
		}
		else if ($months !== 0)
		{
			$phrase = lang('Time.months', [abs($months)], $locale);
			$before = $months < 0;
		}
		else if ($days !== 0 && (abs($days) >= 7))
		{
			$weeks  = ceil($days / 7);
			$phrase = lang('Time.weeks', [abs($weeks)], $locale);
			$before = $days < 0;
		}
		else if ($days !== 0)
		{
			$phrase = lang('Time.days', [abs($days)], $locale);
			$before = $days < 0;
		}
		else if ($hours !== 0)
		{
			$phrase = lang('Time.hours', [abs($hours)], $locale);
			$before = $hours < 0;
		}
		else if ($minutes !== 0)
		{
			$phrase = lang('Time.minutes', [abs($minutes)], $locale);
			$before = $minutes < 0;
		}
		else
		{
			return lang('Time.now', [], $locale);
		}

		return $before
			? lang('Time.ago', [$phrase], $locale)
			: lang('Time.inFuture', [$phrase], $locale);
	}

	/**
	 * Allow property-like access to our calculated values.
	 *
	 * @param $name
	 *
	 * @return mixed
	 */
	public function __get($name)
	{
		$name   = ucfirst(strtolower($name));
		$method = "get{$name}";

		if (method_exists($this, $method))
		{
			return $this->{$method}($name);
		}

		return null;
	}
}


<?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;

/**
 * Database Connection Factory
 *
 * Creates and returns an instance of the appropriate DatabaseConnection
 *
 * @package CodeIgniter\Database
 */
class Database
{

	/**
	 * Maintains an array of the instances of all connections
	 * that have been created. Helps to keep track of all open
	 * connections for performance monitoring, logging, etc.
	 *
	 * @var array
	 */
	protected $connections = [];

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

	/**
	 * Parses the connection binds and returns an instance of
	 * the driver ready to go.
	 *
	 * @param array  $params
	 * @param string $alias
	 *
	 * @return   mixed
	 * @internal param bool $useBuilder
	 */
	public function load(array $params = [], string $alias)
	{
		// No DB specified? Beat them senseless...
		if (empty($params['DBDriver']))
		{
			throw new \InvalidArgumentException('You have not selected a database type to connect to.');
		}

		$className = strpos($params['DBDriver'], '\\') === false
			? '\CodeIgniter\Database\\' . $params['DBDriver'] . '\\Connection'
			: $params['DBDriver'] . '\\Connection';

		$class = new $className($params);

		// Store the connection
		$this->connections[$alias] = $class;

		return $this->connections[$alias];
	}

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

	/**
	 * Creates a new Forge instance for the current database type.
	 *
	 * @param ConnectionInterface|BaseConnection $db
	 *
	 * @return mixed
	 */
	public function loadForge(ConnectionInterface $db)
	{
		$className = strpos($db->DBDriver, '\\') === false ? '\CodeIgniter\Database\\' . $db->DBDriver . '\\Forge' : $db->DBDriver . '\\Forge';

		// Make sure a connection exists
		if (! $db->connID)
		{
			$db->initialize();
		}

		$class = new $className($db);

		return $class;
	}

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

	/**
	 * Loads the Database Utilities class.
	 *
	 * @param ConnectionInterface|BaseConnection $db
	 *
	 * @return mixed
	 */
	public function loadUtils(ConnectionInterface $db)
	{
		$className = strpos($db->DBDriver, '\\') === false ? '\CodeIgniter\Database\\' . $db->DBDriver . '\\Utils' : $db->DBDriver . '\\Utils';

		// Make sure a connection exists
		if (! $db->connID)
		{
			$db->initialize();
		}

		$class = new $className($db);

		return $class;
	}

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