<?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\Session\Handlers;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Exceptions\SessionException;

/**
 * Session handler using Redis for persistence
 */
class RedisHandler extends BaseHandler implements \SessionHandlerInterface
{

	/**
	 * phpRedis instance
	 *
	 * @var resource
	 */
	protected $redis;

	/**
	 * Key prefix
	 *
	 * @var string
	 */
	protected $keyPrefix = 'ci_session:';

	/**
	 * Lock key
	 *
	 * @var string
	 */
	protected $lockKey;

	/**
	 * Key exists flag
	 *
	 * @var boolean
	 */
	protected $keyExists = false;

	/**
	 * Number of seconds until the session ends.
	 *
	 * @var integer
	 */
	protected $sessionExpiration = 7200;

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

	/**
	 * Constructor
	 *
	 * @param BaseConfig $config
	 *
	 * @throws \Exception
	 */
	public function __construct(BaseConfig $config, string $ipAddress)
	{
		parent::__construct($config, $ipAddress);

		if (empty($this->savePath))
		{
			throw SessionException::forEmptySavepath();
		}
		elseif (preg_match('#(?:tcp://)?([^:?]+)(?:\:(\d+))?(\?.+)?#', $this->savePath, $matches))
		{
			isset($matches[3]) || $matches[3] = ''; // Just to avoid undefined index notices below

			$this->savePath = [
				'host'     => $matches[1],
				'port'     => empty($matches[2]) ? null : $matches[2],
				'password' => preg_match('#auth=([^\s&]+)#', $matches[3], $match) ? $match[1] : null,
				'database' => preg_match('#database=(\d+)#', $matches[3], $match) ? (int) $match[1] : null,
				'timeout'  => preg_match('#timeout=(\d+\.\d+)#', $matches[3], $match) ? (float) $match[1] : null,
			];

			preg_match('#prefix=([^\s&]+)#', $matches[3], $match) && $this->keyPrefix = $match[1];
		}
		else
		{
			throw SessionException::forInvalidSavePathFormat($this->savePath);
		}

		if ($this->matchIP === true)
		{
			$this->keyPrefix .= $this->ipAddress . ':';
		}

				$this->sessionExpiration = $config->sessionExpiration;
	}

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

	/**
	 * Open
	 *
	 * Sanitizes save_path and initializes connection.
	 *
	 * @param  string $save_path Server path
	 * @param  string $name      Session cookie name, unused
	 * @return boolean
	 */
	public function open($save_path, $name): bool
	{
		if (empty($this->savePath))
		{
			return false;
		}

		$redis = new \Redis();

		if (! $redis->connect($this->savePath['host'], $this->savePath['port'], $this->savePath['timeout']))
		{
			$this->logger->error('Session: Unable to connect to Redis with the configured settings.');
		}
		elseif (isset($this->savePath['password']) && ! $redis->auth($this->savePath['password']))
		{
			$this->logger->error('Session: Unable to authenticate to Redis instance.');
		}
		elseif (isset($this->savePath['database']) && ! $redis->select($this->savePath['database']))
		{
			$this->logger->error('Session: Unable to select Redis database with index ' . $this->savePath['database']);
		}
		else
		{
			$this->redis = $redis;
			return true;
		}

		return false;
	}

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

	/**
	 * Read
	 *
	 * Reads session data and acquires a lock
	 *
	 * @param string $sessionID Session ID
	 *
	 * @return string|false	Serialized session data
	 */
	public function read($sessionID): string
	{
		if (isset($this->redis) && $this->lockSession($sessionID))
		{
			// Needed by write() to detect session_regenerate_id() calls
			$this->sessionID = $sessionID;

			$session_data                               = $this->redis->get($this->keyPrefix . $sessionID);
			is_string($session_data) ? $this->keyExists = true : $session_data = '';

			$this->fingerprint = md5($session_data);

			return $session_data;
		}

		return '';
	}

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

	/**
	 * Write
	 *
	 * Writes (create / update) session data
	 *
	 * @param string $sessionID   Session ID
	 * @param string $sessionData Serialized session data
	 *
	 * @return boolean
	 */
	public function write($sessionID, $sessionData): bool
	{
		if (! isset($this->redis))
		{
			return false;
		}
		// Was the ID regenerated?
		elseif ($sessionID !== $this->sessionID)
		{
			if (! $this->releaseLock() || ! $this->lockSession($sessionID))
			{
				return false;
			}

			$this->keyExists = false;
			$this->sessionID = $sessionID;
		}

		if (isset($this->lockKey))
		{
			$this->redis->setTimeout($this->lockKey, 300);

			if ($this->fingerprint !== ($fingerprint = md5($sessionData)) || $this->keyExists === false)
			{
				if ($this->redis->set($this->keyPrefix . $sessionID, $sessionData, $this->sessionExpiration))
				{
					$this->fingerprint = $fingerprint;
					$this->keyExists   = true;
					return true;
				}

				return false;
			}

			return $this->redis->setTimeout($this->keyPrefix . $sessionID, $this->sessionExpiration);
		}

		return false;
	}

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

	/**
	 * Close
	 *
	 * Releases locks and closes connection.
	 *
	 * @return boolean
	 */
	public function close(): bool
	{
		if (isset($this->redis))
		{
			try
			{
				if ($this->redis->ping() === '+PONG')
				{
					isset($this->lockKey) && $this->redis->delete($this->lockKey);

					if (! $this->redis->close())
					{
						return false;
					}
				}
			}
			catch (\RedisException $e)
			{
				$this->logger->error('Session: Got RedisException on close(): ' . $e->getMessage());
			}

			$this->redis = null;

			return true;
		}

		return true;
	}

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

	/**
	 * Destroy
	 *
	 * Destroys the current session.
	 *
	 * @param string $sessionID
	 *
	 * @return boolean
	 */
	public function destroy($sessionID): bool
	{
		if (isset($this->redis, $this->lockKey))
		{
			if (($result = $this->redis->delete($this->keyPrefix . $sessionID)) !== 1)
			{
				$this->logger->debug('Session: Redis::delete() expected to return 1, got ' . var_export($result, true) . ' instead.');
			}

			return $this->destroyCookie();
		}

		return false;
	}

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

	/**
	 * Garbage Collector
	 *
	 * Deletes expired sessions
	 *
	 * @param  integer $maxlifetime Maximum lifetime of sessions
	 * @return boolean
	 */
	public function gc($maxlifetime): bool
	{
		// Not necessary, Redis takes care of that.
		return true;
	}

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

	/**
	 * Get lock
	 *
	 * Acquires an (emulated) lock.
	 *
	 * @param string $sessionID Session ID
	 *
	 * @return boolean
	 */
	protected function lockSession(string $sessionID): bool
	{
		// PHP 7 reuses the SessionHandler object on regeneration,
		// so we need to check here if the lock key is for the
		// correct session ID.
		if ($this->lockKey === $this->keyPrefix . $sessionID . ':lock')
		{
			return $this->redis->setTimeout($this->lockKey, 300);
		}

		// 30 attempts to obtain a lock, in case another request already has it
		$lock_key = $this->keyPrefix . $sessionID . ':lock';
		$attempt  = 0;

		do
		{
			if (($ttl = $this->redis->ttl($lock_key)) > 0)
			{
				sleep(1);
				continue;
			}

			if (! $this->redis->setex($lock_key, 300, time()))
			{
				$this->logger->error('Session: Error while trying to obtain lock for ' . $this->keyPrefix . $sessionID);
				return false;
			}

			$this->lockKey = $lock_key;
			break;
		}
		while (++ $attempt < 30);

		if ($attempt === 30)
		{
			log_message('error', 'Session: Unable to obtain lock for ' . $this->keyPrefix . $sessionID . ' after 30 attempts, aborting.');
			return false;
		}
		elseif ($ttl === -1)
		{
			log_message('debug', 'Session: Lock for ' . $this->keyPrefix . $sessionID . ' had no TTL, overriding.');
		}

		$this->lock = true;
		return true;
	}

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

	/**
	 * Release lock
	 *
	 * Releases a previously acquired lock
	 *
	 * @return boolean
	 */
	protected function releaseLock(): bool
	{
		if (isset($this->redis, $this->lockKey) && $this->lock)
		{
			if (! $this->redis->delete($this->lockKey))
			{
				$this->logger->error('Session: Error while trying to free lock for ' . $this->lockKey);
				return false;
			}

			$this->lockKey = null;
			$this->lock    = false;
		}

		return true;
	}

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


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

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

class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface
{

	/**
	 * The SQLite3Result resource, 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
	 */
	public function _prepare(string $sql, array $options = [])
	{
		if (! ($this->statement = $this->db->connID->prepare($sql)))
		{
			$this->errorCode   = $this->db->connID->lastErrorCode();
			$this->errorString = $this->db->connID->lastErrorMsg();
		}

		return $this;
	}

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

	/**
	 * Takes a new set of data and runs it against the currently
	 * prepared query. Upon success, will return a Results object.
	 *
	 * @todo finalize()
	 *
	 * @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.');
		}

		foreach ($data as $key => $item)
		{
			// Determine the type string
			if (is_integer($item))
			{
				$bindType = SQLITE3_INTEGER;
			}
			elseif (is_float($item))
			{
				$bindType = SQLITE3_FLOAT;
			}
			else
			{
				$bindType = SQLITE3_TEXT;
			}

			// Bind it
			$this->statement->bindValue($key + 1, $item, $bindType);
		}

		$this->result = $this->statement->execute();

		return $this->result !== false;
	}

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

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

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

}


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

/**
 * Timers collector
 */
class Timers extends BaseCollector
{

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

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

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

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

	/**
	 * Child classes should implement this to return the timeline data
	 * formatted for correct usage.
	 *
	 * @return array
	 */
	protected function formatTimelineData(): array
	{
		$data = [];

		$benchmark = Services::timer(true);
		$rows      = $benchmark->getTimers(6);

		foreach ($rows as $name => $info)
		{
			if ($name === 'total_execution')
			{
				continue;
			}

			$data[] = [
				'name'      => ucwords(str_replace('_', ' ', $name)),
				'component' => 'Timer',
				'start'     => $info['start'],
				'duration'  => $info['end'] - $info['start'],
			];
		}

		return $data;
	}

}


<?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\BaseConnection;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Database\Exceptions\DatabaseException;

/**
 * Connection for Postgre
 */
class Connection extends BaseConnection implements ConnectionInterface
{

	/**
	 * Database driver
	 *
	 * @var string
	 */
	public $DBDriver = 'postgre';

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

	/**
	 * Database schema
	 *
	 * @var string
	 */
	public $schema = 'public';

	/**
	 * Identifier escape character
	 *
	 * @var string
	 */
	public $escapeChar = '"';

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

	/**
	 * Connect to the database.
	 *
	 * @param  boolean $persistent
	 * @return mixed
	 */
	public function connect(bool $persistent = false)
	{
		if (empty($this->DSN))
		{
			$this->buildDSN();
		}

		// Strip pgsql if exists
		if (mb_strpos($this->DSN, 'pgsql:') === 0)
		{
			$this->DSN = mb_substr($this->DSN, 6);
		}

		// Convert semicolons to spaces.
		$this->DSN = str_replace(';', ' ', $this->DSN);

		$this->connID = $persistent === true ? pg_pconnect($this->DSN) : pg_connect($this->DSN);

		if ($this->connID !== false)
		{
			if ($persistent === true && pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD && pg_ping($this->connID) === false
			)
			{
				return false;
			}

			empty($this->schema) || $this->simpleQuery("SET search_path TO {$this->schema},public");

			if ($this->setClientEncoding($this->charset) === false)
			{
				return false;
			}
		}

		return $this->connID;
	}

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

	/**
	 * Keep or establish the connection if no queries have been sent for
	 * a length of time exceeding the server's idle timeout.
	 *
	 * @return void
	 */
	public function reconnect()
	{
		if (pg_ping($this->connID) === false)
		{
			$this->connID = false;
		}
	}

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

	/**
	 * Close the database connection.
	 *
	 * @return void
	 */
	protected function _close()
	{
		pg_close($this->connID);
	}

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

	/**
	 * Select a specific database table to use.
	 *
	 * @param string $databaseName
	 *
	 * @return boolean
	 */
	public function setDatabase(string $databaseName): bool
	{
		return false;
	}

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

	/**
	 * Returns a string containing the version of the database being used.
	 *
	 * @return string
	 */
	public function getVersion(): string
	{
		if (isset($this->dataCache['version']))
		{
			return $this->dataCache['version'];
		}

		if (! $this->connID || ( $pgVersion = pg_version($this->connID)) === false)
		{
			$this->initialize();
		}

		return isset($pgVersion['server']) ? $this->dataCache['version'] = $pgVersion['server'] : false;
	}

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

	/**
	 * Executes the query against the database.
	 *
	 * @param string $sql
	 *
	 * @return resource
	 */
	public function execute(string $sql)
	{
		return pg_query($this->connID, $sql);
	}

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

	/**
	 * Returns the total number of rows affected by this query.
	 *
	 * @return int
	 */
	public function affectedRows(): int
	{
		return pg_affected_rows($this->resultID);
	}

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

	/**
	 * "Smart" Escape String
	 *
	 * Escapes data based on type
	 *
	 * @param  mixed $str
	 * @return mixed
	 */
	public function escape($str)
	{
		if (! $this->connID)
		{
			$this->initialize();
		}

		if (is_string($str) || ( is_object($str) && method_exists($str, '__toString')))
		{
			return pg_escape_literal($this->connID, $str);
		}
		elseif (is_bool($str))
		{
			return $str ? 'TRUE' : 'FALSE';
		}

		return parent::escape($str);
	}

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

	/**
	 * Platform-dependant string escape
	 *
	 * @param  string $str
	 * @return string
	 */
	protected function _escapeString(string $str): string
	{
		if (! $this->connID)
		{
			$this->initialize();
		}

		return pg_escape_string($this->connID, $str);
	}

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

	/**
	 * Generates the SQL for listing tables in a platform-dependent manner.
	 *
	 * @param boolean $prefixLimit
	 *
	 * @return string
	 */
	protected function _listTables(bool $prefixLimit = false): string
	{
		$sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \'' . $this->schema . "'";

		if ($prefixLimit !== false && $this->DBPrefix !== '')
		{
			return $sql . ' AND "table_name" LIKE \''
					. $this->escapeLikeString($this->DBPrefix) . "%' "
					. sprintf($this->likeEscapeStr, $this->likeEscapeChar);
		}

		return $sql;
	}

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

	/**
	 * Generates a platform-specific query string so that the column names can be fetched.
	 *
	 * @param string $table
	 *
	 * @return string
	 */
	protected function _listColumns(string $table = ''): string
	{
		return 'SELECT "column_name"
			FROM "information_schema"."columns"
			WHERE LOWER("table_name") = '
				. $this->escape($this->DBPrefix . strtolower($table));
	}

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

	/**
	 * Returns an array of objects with field data
	 *
	 * @param  string $table
	 * @return \stdClass[]
	 * @throws DatabaseException
	 */
	public function _fieldData(string $table): array
	{
		$sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default"
			FROM "information_schema"."columns"
			WHERE LOWER("table_name") = '
				. $this->escape(strtolower($table));

		if (($query = $this->query($sql)) === false)
		{
			throw new DatabaseException(lang('Database.failGetFieldData'));
		}
		$query = $query->getResultObject();

		$retVal = [];
		for ($i = 0, $c = count($query); $i < $c; $i ++)
		{
			$retVal[$i]             = new \stdClass();
			$retVal[$i]->name       = $query[$i]->column_name;
			$retVal[$i]->type       = $query[$i]->data_type;
			$retVal[$i]->default    = $query[$i]->column_default;
			$retVal[$i]->max_length = $query[$i]->character_maximum_length > 0 ? $query[$i]->character_maximum_length : $query[$i]->numeric_precision;
		}

		return $retVal;
	}

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

	/**
	 * Returns an array of objects with index data
	 *
	 * @param  string $table
	 * @return \stdClass[]
	 * @throws DatabaseException
	 */
	public function _indexData(string $table): array
	{
		$sql = 'SELECT "indexname", "indexdef"
			FROM "pg_indexes"
			WHERE LOWER("tablename") = ' . $this->escape(strtolower($table)) . '
			AND "schemaname" = ' . $this->escape('public');

		if (($query = $this->query($sql)) === false)
		{
			throw new DatabaseException(lang('Database.failGetIndexData'));
		}
		$query = $query->getResultObject();

		$retVal = [];
		foreach ($query as $row)
		{
			$obj         = new \stdClass();
			$obj->name   = $row->indexname;
			$_fields     = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef)));
			$obj->fields = array_map(function ($v) {
				return trim($v);
			}, $_fields);

			if (strpos($row->indexdef, 'CREATE UNIQUE INDEX pk') === 0)
			{
				$obj->type = 'PRIMARY';
			}
			else
			{
				$obj->type = (strpos($row->indexdef, 'CREATE UNIQUE') === 0) ? 'UNIQUE' : 'INDEX';
			}

			$retVal[$obj->name] = $obj;
		}

		return $retVal;
	}

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

	/**
	 * Returns an array of objects with Foreign key data
	 *
	 * @param  string $table
	 * @return \stdClass[]
	 * @throws DatabaseException
	 */
	public function _foreignKeyData(string $table): array
	{
		$sql = 'SELECT
                            tc.constraint_name, tc.table_name, kcu.column_name,
                            ccu.table_name AS foreign_table_name,
                            ccu.column_name AS foreign_column_name
                        FROM information_schema.table_constraints AS tc
                        JOIN information_schema.key_column_usage AS kcu
                            ON tc.constraint_name = kcu.constraint_name
                        JOIN information_schema.constraint_column_usage AS ccu
                            ON ccu.constraint_name = tc.constraint_name
                        WHERE constraint_type = ' . $this->escape('FOREIGN KEY') . ' AND
                            tc.table_name = ' . $this->escape($table);

		if (($query = $this->query($sql)) === false)
		{
			throw new DatabaseException(lang('Database.failGetForeignKeyData'));
		}
		$query = $query->getResultObject();

		$retVal = [];
		foreach ($query as $row)
		{
			$obj                     = new \stdClass();
			$obj->constraint_name    = $row->constraint_name;
			$obj->table_name         = $row->table_name;
			$obj->foreign_table_name = $row->foreign_table_name;
			$retVal[]                = $obj;
		}

		return $retVal;
	}

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

	/**
	 * Returns the last error code and message.
	 *
	 * Must return an array with keys 'code' and 'message':
	 *
	 *  return ['code' => null, 'message' => null);
	 *
	 * @return array
	 */
	public function error(): array
	{
		return [
			'code'    => '',
			'message' => pg_last_error($this->connID),
		];
	}

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

	/**
	 * Insert ID
	 *
	 * @return integer
	 */
	public function insertID(): int
	{
		$v = pg_version($this->connID);
		// 'server' key is only available since PostgreSQL 7.4
		$v = $v['server'] ?? 0;

		$table  = func_num_args() > 0 ? func_get_arg(0) : null;
		$column = func_num_args() > 1 ? func_get_arg(1) : null;

		if ($table === null && $v >= '8.1')
		{
			$sql = 'SELECT LASTVAL() AS ins_id';
		}
		elseif ($table !== null)
		{
			if ($column !== null && $v >= '8.0')
			{
				$sql   = "SELECT pg_get_serial_sequence('{$table}', '{$column}') AS seq";
				$query = $this->query($sql);
				$query = $query->getRow();
				$seq   = $query->seq;
			}
			else
			{
				// seq_name passed in table parameter
				$seq = $table;
			}

			$sql = "SELECT CURRVAL('{$seq}') AS ins_id";
		}
		else
		{
			return pg_last_oid($this->resultID);
		}

		$query = $this->query($sql);
		$query = $query->getRow();
		return (int) $query->ins_id;
	}

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

	/**
	 * Build a DSN from the provided parameters
	 *
	 * @return void
	 */
	protected function buildDSN()
	{
		$this->DSN === '' || $this->DSN = '';

		// If UNIX sockets are used, we shouldn't set a port
		if (strpos($this->hostname, '/') !== false)
		{
			$this->port = '';
		}

		$this->hostname === '' || $this->DSN = "host={$this->hostname} ";

		if (! empty($this->port) && ctype_digit($this->port))
		{
			$this->DSN .= "port={$this->port} ";
		}

		if ($this->username !== '')
		{
			$this->DSN .= "user={$this->username} ";

			// An empty password is valid!
			// password must be set to null to ignore it.

			$this->password === null || $this->DSN .= "password='{$this->password}' ";
		}

		$this->database === '' || $this->DSN .= "dbname={$this->database} ";

		// We don't have these options as elements in our standard configuration
		// array, but they might be set by parse_url() if the configuration was
		// provided via string> Example:
		//
		// postgre://username:password@localhost:5432/database?connect_timeout=5&sslmode=1
		foreach (['connect_timeout', 'options', 'sslmode', 'service'] as $key)
		{
			if (isset($this->{$key}) && is_string($this->{$key}) && $this->{$key} !== '')
			{
				$this->DSN .= "{$key}='{$this->{$key}}' ";
			}
		}

		$this->DSN = rtrim($this->DSN);
	}

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

	/**
	 * Set client encoding
	 *
	 * @param  string $charset The client encoding to which the data will be converted.
	 * @return boolean
	 */
	protected function setClientEncoding(string $charset): bool
	{
		return pg_set_client_encoding($this->connID, $charset) === 0;
	}

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

	/**
	 * Begin Transaction
	 *
	 * @return boolean
	 */
	protected function _transBegin(): bool
	{
		return (bool) pg_query($this->connID, 'BEGIN');
	}

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

	/**
	 * Commit Transaction
	 *
	 * @return boolean
	 */
	protected function _transCommit(): bool
	{
		return (bool) pg_query($this->connID, 'COMMIT');
	}

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

	/**
	 * Rollback Transaction
	 *
	 * @return boolean
	 */
	protected function _transRollback(): bool
	{
		return (bool) pg_query($this->connID, 'ROLLBACK');
	}

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


<?php namespace Config;

use CodeIgniter\Config\BaseConfig;

class Format extends BaseConfig
{
	/*
	|--------------------------------------------------------------------------
	| Available Response Formats
	|--------------------------------------------------------------------------
	|
	| When you perform content negotiation with the request, these are the
	| available formats that your application supports. This is currently
	| only used with the API\ResponseTrait. A valid Formatter must exist
	| for the specified format.
	|
	| These formats are only checked when the data passed to the respond()
	| method is an array.
	|
	*/
	public $supportedResponseFormats = [
		'application/json',
		'application/xml', // machine-readable XML
		'text/xml', // human-readable XML
	];

	/*
	|--------------------------------------------------------------------------
	| Formatters
	|--------------------------------------------------------------------------
	|
	| Lists the class to use to format responses with of a particular type.
	| For each mime type, list the class that should be used. Formatters
	| can be retrieved through the getFormatter() method.
	|
	*/
	public $formatters = [
		'application/json' => \CodeIgniter\Format\JSONFormatter::class,
		'application/xml'  => \CodeIgniter\Format\XMLFormatter::class,
		'text/xml'         => \CodeIgniter\Format\XMLFormatter::class,
	];

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

	/**
	 * A Factory method to return the appropriate formatter for the given mime type.
	 *
	 * @param string $mime
	 *
	 * @return \CodeIgniter\Format\FormatterInterface
	 */
	public function getFormatter(string $mime)
	{
		if (! array_key_exists($mime, $this->formatters))
		{
			throw new \InvalidArgumentException('No Formatter defined for mime type: ' . $mime);
		}

		$class = $this->formatters[$mime];

		if (! class_exists($class))
		{
			throw new \BadMethodCallException($class . ' is not a valid Formatter.');
		}

		return new $class();
	}

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

}


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

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use CodeIgniter\Honeypot\Exceptions\HoneypotException;

/**
 * Honeypot filter
 */
class Honeypot implements FilterInterface
{

	/**
	 * Checks if Honeypot field is empty; if not
	 * then the requester is a bot
	 *
	 * @param \CodeIgniter\HTTP\RequestInterface $request
	 *
	 * @return void
	 *
	 * @throws \CodeIgniter\Honeypot\Exceptions\HoneypotException
	 */
	public function before(RequestInterface $request)
	{
		$honeypot = Services::honeypot(new \Config\Honeypot());
		if ($honeypot->hasContent($request))
		{
			throw HoneypotException::isBot();
		}
	}

	/**
	 * Attach a honeypot to the current response.
	 *
	 * @param  \CodeIgniter\HTTP\RequestInterface  $request
	 * @param  \CodeIgniter\HTTP\ResponseInterface $response
	 *
	 * @return void
	 */
	public function after(RequestInterface $request, ResponseInterface $response)
	{
		$honeypot = Services::honeypot(new \Config\Honeypot());
		$honeypot->attachHoneypot($response);
	}

}


<?php

/**
 * CodeIgniter
 *
 * An open source application development framework for PHP
 *
 * This content is released under the MIT License (MIT)
 *
 * Copyright (c) 2014-2019 British Columbia Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @package    CodeIgniter
 * @author     CodeIgniter Dev Team
 * @copyright  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT    MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Images\Handlers;

use CodeIgniter\Images\Exceptions\ImageException;

/**
 * Class ImageMagickHandler
 *
 * To make this library as compatible as possible with the broadest
 * number of installations, we do not use the Imagick extension,
 * but simply use the command line version.
 *
 * hmm - the width & height accessors at the end use the imagick extension.
 *
 * FIXME - This needs conversion & unit testing, to use the imagick extension
 *
 * @package CodeIgniter\Images\Handlers
 */
class ImageMagickHandler extends BaseHandler
{

	public $version;

	/**
	 * Stores image resource in memory.
	 *
	 * @var
	 */
	protected $resource;

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

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

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

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

	/**
	 * Handles the actual resizing of the image.
	 *
	 * @param boolean $maintainRatio
	 *
	 * @return ImageMagickHandler
	 * @throws \Exception
	 */
	public function _resize(bool $maintainRatio = false)
	{
		$source      = ! empty($this->resource) ? $this->resource : $this->image->getPathname();
		$destination = $this->getResourcePath();

		$escape = '\\';
		if (stripos(PHP_OS, 'WIN') === 0)
		{
			$escape = '';
		}

		$action = $maintainRatio === true ? ' -resize ' . $this->width . 'x' . $this->height . ' "' . $source . '" "' . $destination . '"' : ' -resize ' . $this->width . 'x' . $this->height . "{$escape}! \"" . $source . '" "' . $destination . '"';

		$this->process($action);

		return $this;
	}

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

	/**
	 * Crops the image.
	 *
	 * @return boolean|\CodeIgniter\Images\Handlers\ImageMagickHandler
	 * @throws \Exception
	 */
	public function _crop()
	{
		$source      = ! empty($this->resource) ? $this->resource : $this->image->getPathname();
		$destination = $this->getResourcePath();

		$action = ' -crop ' . $this->width . 'x' . $this->height . '+' . $this->xAxis . '+' . $this->yAxis . ' "' . $source . '" "' . $destination . '"';

		$this->process($action);

		return $this;
	}

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

	/**
	 * Handles the rotation of an image resource.
	 * Doesn't save the image, but replaces the current resource.
	 *
	 * @param integer $angle
	 *
	 * @return $this
	 * @throws \Exception
	 */
	protected function _rotate(int $angle)
	{
		$angle = '-rotate ' . $angle;

		$source      = ! empty($this->resource) ? $this->resource : $this->image->getPathname();
		$destination = $this->getResourcePath();

		$action = ' ' . $angle . ' "' . $source . '" "' . $destination . '"';

		$this->process($action);

		return $this;
	}

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

	/**
	 * Flattens transparencies, default white background
	 *
	 * @param integer $red
	 * @param integer $green
	 * @param integer $blue
	 *
	 * @return $this
	 * @throws \Exception
	 */
	public function _flatten(int $red = 255, int $green = 255, int $blue = 255)
	{
		$flatten = "-background RGB({$red},{$green},{$blue}) -flatten";

		$source      = ! empty($this->resource) ? $this->resource : $this->image->getPathname();
		$destination = $this->getResourcePath();

		$action = ' ' . $flatten . ' "' . $source . '" "' . $destination . '"';

		$this->process($action);

		return $this;
	}

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

	/**
	 * Flips an image along it's vertical or horizontal axis.
	 *
	 * @param string $direction
	 *
	 * @return $this
	 * @throws \Exception
	 */
	public function _flip(string $direction)
	{
		$angle = $direction === 'horizontal' ? '-flop' : '-flip';

		$source      = ! empty($this->resource) ? $this->resource : $this->image->getPathname();
		$destination = $this->getResourcePath();

		$action = ' ' . $angle . ' "' . $source . '" "' . $destination . '"';

		$this->process($action);

		return $this;
	}

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

	/**
	 * Get GD version
	 *
	 * @return string
	 */
	public function getVersion(): string
	{
		$result = $this->process('-version');

		// The first line has the version in it...
		preg_match('/(ImageMagick\s[\S]+)/', $result[0], $matches);

		return str_replace('ImageMagick ', '', $matches[0]);
	}

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

	/**
	 * Handles all of the grunt work of resizing, etc.
	 *
	 * @param string  $action
	 * @param integer $quality
	 *
	 * @return ImageMagickHandler|boolean
	 */
	protected function process(string $action, int $quality = 100)
	{
		// Do we have a vaild library path?
		if (empty($this->config->libraryPath))
		{
			throw ImageException::forInvalidImageLibraryPath($this->config->libraryPath);
		}

		if (! preg_match('/convert$/i', $this->config->libraryPath))
		{
			$this->config->libraryPath = rtrim($this->config->libraryPath, '/') . '/convert';
		}

		$cmd  = $this->config->libraryPath;
		$cmd .= $action === '-version' ? ' ' . $action : ' -quality ' . $quality . ' ' . $action;

		$retval = 1;
		// exec() might be disabled
		if (function_usable('exec'))
		{
			@exec($cmd, $output, $retval);
		}

		// Did it work?
		if ($retval > 0)
		{
			throw ImageException::forImageProcessFailed();
		}

		return $output;
	}

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

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

		// If no new resource has been created, then we're
		// simply copy the existing one.
		if (empty($this->resource))
		{
			$name = basename($target);
			$path = pathinfo($target, PATHINFO_DIRNAME);

			return $this->image->copy($path, $name);
		}

		// Copy the file through ImageMagick so that it has
		// a chance to convert file format.
		$action = '"' . $this->resource . '" "' . $target . '"';

		$result = $this->process($action, $quality);

		unlink($this->resource);

		return $result;
	}

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

	/**
	 * Get Image Resource
	 *
	 * This simply creates an image resource handle
	 * based on the type of image being processed.
	 * Since ImageMagick is used on the cli, we need to
	 * ensure we have a temporary file on the server
	 * that we can use.
	 *
	 * To ensure we can use all features, like transparency,
	 * during the process, we'll use a PNG as the temp file type.
	 *
	 * @return resource|boolean
	 * @throws \Exception
	 */
	protected function getResourcePath()
	{
		if (! is_null($this->resource))
		{
			return $this->resource;
		}

		$this->resource = WRITEPATH . 'cache/' . time() . '_' . bin2hex(random_bytes(10)) . '.png';

		return $this->resource;
	}

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

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

		// Reverse the vertical offset
		// When the image is positioned at the bottom
		// we don't want the vertical offset to push it
		// further down. We want the reverse, so we'll
		// invert the offset. Note: The horizontal
		// offset flips itself automatically
		if ($options['vAlign'] === 'bottom')
		{
			$options['vOffset'] = $options['vOffset'] * -1;
		}

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

		// Font
		if (! empty($options['fontPath']))
		{
			$cmd .= " -font '{$options['fontPath']}'";
		}

		if (isset($options['hAlign']) && isset($options['vAlign']))
		{
			switch ($options['hAlign'])
			{
				case 'left':
					$xAxis   = $options['hOffset'] + $options['padding'];
					$yAxis   = $options['vOffset'] + $options['padding'];
					$gravity = $options['vAlign'] === 'top' ? 'NorthWest' : 'West';
					if ($options['vAlign'] === 'bottom')
					{
						$gravity = 'SouthWest';
						$yAxis   = $options['vOffset'] - $options['padding'];
					}
					break;
				case 'center':
					$xAxis   = $options['hOffset'] + $options['padding'];
					$yAxis   = $options['vOffset'] + $options['padding'];
					$gravity = $options['vAlign'] === 'top' ? 'North' : 'Center';
					if ($options['vAlign'] === 'bottom')
					{
						$yAxis   = $options['vOffset'] - $options['padding'];
						$gravity = 'South';
					}
					break;
				case 'right':
					$xAxis   = $options['hOffset'] - $options['padding'];
					$yAxis   = $options['vOffset'] + $options['padding'];
					$gravity = $options['vAlign'] === 'top' ? 'NorthEast' : 'East';
					if ($options['vAlign'] === 'bottom')
					{
						$gravity = 'SouthEast';
						$yAxis   = $options['vOffset'] - $options['padding'];
					}
					break;
			}

			$xAxis = $xAxis >= 0 ? '+' . $xAxis : $xAxis;
			$yAxis = $yAxis >= 0 ? '+' . $yAxis : $yAxis;

			$cmd .= " -gravity {$gravity} -geometry {$xAxis}{$yAxis}";
		}

		// Color
		if (isset($options['color']))
		{
			list($r, $g, $b) = sscanf("#{$options['color']}", '#%02x%02x%02x');

			$cmd .= " -fill 'rgba({$r},{$g},{$b},{$options['opacity']})'";
		}

		// Font Size - use points....
		if (isset($options['fontSize']))
		{
			$cmd .= " -pointsize {$options['fontSize']}";
		}

		// Text
		$cmd .= " -annotate 0 '{$text}'";

		$source      = ! empty($this->resource) ? $this->resource : $this->image->getPathname();
		$destination = $this->getResourcePath();

		$cmd = " '{$source}' {$cmd} '{$destination}'";

		$this->process($cmd);
	}

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

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

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

}


<?php

/**
 * CodeIgniter
 *
 * An open source application development framework for PHP
 *
 * This content is released under the MIT License (MIT)
 *
 * Copyright (c) 2014-2019 British Columbia Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @package    CodeIgniter
 * @author     CodeIgniter Dev Team
 * @copyright  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Commands\Database;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Database\Seeder;

/**
 * Runs the specified Seeder file to populate the database
 * with some data.
 *
 * @package CodeIgniter\Commands
 */
class Seed extends BaseCommand
{

	/**
	 * The group the command is lumped under
	 * when listing commands.
	 *
	 * @var string
	 */
	protected $group = 'Database';

	/**
	 * The Command's name
	 *
	 * @var string
	 */
	protected $name = 'db:seed';

	/**
	 * the Command's short description
	 *
	 * @var string
	 */
	protected $description = 'Runs the specified seeder to populate known data into the database.';

	/**
	 * the Command's usage
	 *
	 * @var string
	 */
	protected $usage = 'db:seed [seeder_name]';

	/**
	 * the Command's Arguments
	 *
	 * @var array
	 */
	protected $arguments = [
		'seeder_name' => 'The seeder name to run',
	];

	/**
	 * the Command's Options
	 *
	 * @var array
	 */
	protected $options = [];

	/**
	 * Runs all of the migrations in reverse order, until they have
	 * all been un-applied.
	 *
	 * @param array $params
	 */
	public function run(array $params = [])
	{
		$seeder = new Seeder(new \Config\Database());

		$seedName = array_shift($params);

		if (empty($seedName))
		{
			$seedName = CLI::prompt(lang('Migrations.migSeeder'), 'DatabaseSeeder');
		}

		if (empty($seedName))
		{
			CLI::error(lang('Migrations.migMissingSeeder'));
			return;
		}

		try
		{
			$seeder->call($seedName);
		}
		catch (\Exception $e)
		{
			$this->showError($e);
		}
	}

}


<?php

/**
 * CodeIgniter
 *
 * An open source application development framework for PHP
 *
 * This content is released under the MIT License (MIT)
 *
 * Copyright (c) 2014-2019 British Columbia Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @package    CodeIgniter
 * @author     CodeIgniter Dev Team
 * @copyright  2014-2019 British Columbia Institute of Technology (https://bcit.ca/)
 * @license    https://opensource.org/licenses/MIT	MIT License
 * @link       https://codeigniter.com
 * @since      Version 3.0.0
 * @filesource
 */

namespace CodeIgniter\Database;

use CodeIgniter\Database\Exceptions\DatabaseException;

/**
 * Class Forge
 */
class Forge
{

	/**
	 * The active database connection.
	 *
	 * @var BaseConnection
	 */
	protected $db;

	/**
	 * List of fields.
	 *
	 * @var array
	 */
	protected $fields = [];

	/**
	 * List of keys.
	 *
	 * @var array
	 */
	protected $keys = [];

	/**
	 * List of unique keys.
	 *
	 * @var array
	 */
	protected $uniqueKeys = [];

	/**
	 * List of primary keys.
	 *
	 * @var array
	 */
	protected $primaryKeys = [];

	/**
	 * List of foreign keys.
	 *
	 * @var array
	 */
	protected $foreignKeys = [];

	/**
	 * Character set used.
	 *
	 * @var string
	 */
	protected $charset = '';

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

	/**
	 * CREATE DATABASE statement
	 *
	 * @var string
	 */
	protected $createDatabaseStr = 'CREATE DATABASE %s';

	/**
	 * DROP DATABASE statement
	 *
	 * @var string
	 */
	protected $dropDatabaseStr = 'DROP DATABASE %s';

	/**
	 * CREATE TABLE statement
	 *
	 * @var string
	 */
	protected $createTableStr = "%s %s (%s\n)";

	/**
	 * CREATE TABLE IF statement
	 *
	 * @var string
	 */
	protected $createTableIfStr = 'CREATE TABLE IF NOT EXISTS';

	/**
	 * CREATE TABLE keys flag
	 *
	 * Whether table keys are created from within the
	 * CREATE TABLE statement.
	 *
	 * @var boolean
	 */
	protected $createTableKeys = false;

	/**
	 * DROP TABLE IF EXISTS statement
	 *
	 * @var string
	 */
	protected $dropTableIfStr = 'DROP TABLE IF EXISTS';

	/**
	 * RENAME TABLE statement
	 *
	 * @var string
	 */
	protected $renameTableStr = 'ALTER TABLE %s RENAME TO %s;';

	/**
	 * UNSIGNED support
	 *
	 * @var boolean|array
	 */
	protected $unsigned = true;

	/**
	 * NULL value representation in CREATE/ALTER TABLE statements
	 *
	 * @var string
	 */
	protected $null = '';

	/**
	 * DEFAULT value representation in CREATE/ALTER TABLE statements
	 *
	 * @var string
	 */
	protected $default = ' DEFAULT ';

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

	/**
	 * Constructor.
	 *
	 * @param \CodeIgniter\Database\ConnectionInterface $db
	 */
	public function __construct(ConnectionInterface $db)
	{
		$this->db = &$db;
	}

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

	/**
	 * Provides access to the forge's current database connection.
	 *
	 * @return ConnectionInterface
	 */
	public function getConnection()
	{
		return $this->db;
	}

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

	/**
	 * Create database
	 *
	 * @param string $db_name
	 *
	 * @return boolean
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function createDatabase(string $db_name): bool
	{
		if ($this->createDatabaseStr === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('This feature is not available for the database you are using.');
			}

			return false;
		}
		elseif (! $this->db->query(sprintf($this->createDatabaseStr, $db_name, $this->db->charset, $this->db->DBCollat))
		)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('Unable to create the specified database.');
			}

			return false;
		}

		if (! empty($this->db->dataCache['db_names']))
		{
			$this->db->dataCache['db_names'][] = $db_name;
		}

		return true;
	}

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

	/**
	 * Drop database
	 *
	 * @param string $db_name
	 *
	 * @return boolean
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function dropDatabase(string $db_name): bool
	{
		if ($this->dropDatabaseStr === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('This feature is not available for the database you are using.');
			}

			return false;
		}
		elseif (! $this->db->query(sprintf($this->dropDatabaseStr, $db_name)))
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('Unable to drop the specified database.');
			}

			return false;
		}

		if (! empty($this->db->dataCache['db_names']))
		{
			$key = array_search(strtolower($db_name), array_map('strtolower', $this->db->dataCache['db_names']), true);
			if ($key !== false)
			{
				unset($this->db->dataCache['db_names'][$key]);
			}
		}

		return true;
	}

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

	/**
	 * Add Key
	 *
	 * @param string|array $key
	 * @param boolean      $primary
	 * @param boolean      $unique
	 *
	 * @return Forge
	 */
	public function addKey($key, bool $primary = false, bool $unique = false)
	{
		if ($primary === true)
		{
			foreach ((array)$key as $one)
			{
				$this->primaryKeys[] = $one;
			}
		}
		else
		{
			$this->keys[] = $key;
			if ($unique === true)
			{
				$this->uniqueKeys[] = ($c = count($this->keys)) ? $c - 1 : 0;
			}
		}

		return $this;
	}

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

	/**
	 * Add Primary Key
	 *
	 * @param string|array $key
	 *
	 * @return Forge
	 */
	public function addPrimaryKey($key)
	{
		return $this->addKey($key, true);
	}

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

	/**
	 * Add Unique Key
	 *
	 * @param string|array $key
	 *
	 * @return Forge
	 */
	public function addUniqueKey($key)
	{
		return $this->addKey($key, false, true);
	}

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

	/**
	 * Add Field
	 *
	 * @param array|string $field
	 *
	 * @return Forge
	 */
	public function addField($field)
	{
		if (is_string($field))
		{
			if ($field === 'id')
			{
				$this->addField([
					'id' => [
						'type'           => 'INT',
						'constraint'     => 9,
						'auto_increment' => true,
					],
				]);
				$this->addKey('id', true);
			}
			else
			{
				if (strpos($field, ' ') === false)
				{
					throw new \InvalidArgumentException('Field information is required for that operation.');
				}

				$this->fields[] = $field;
			}
		}

		if (is_array($field))
		{
			$this->fields = array_merge($this->fields, $field);
		}

		return $this;
	}

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

	/**
	 * Add Foreign Key
	 *
	 * @param string  $fieldName
	 * @param string  $tableName
	 * @param string  $tableField
	 * @param string  $onUpdate
	 * @param string  $onDelete
	 *
	 * @return \CodeIgniter\Database\Forge
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function addForeignKey(string $fieldName = '', string $tableName = '', string $tableField = '', string $onUpdate = '', string $onDelete = '')
	{
		if (! isset($this->fields[$fieldName]))
		{
			throw new DatabaseException(lang('Database.fieldNotExists', [$fieldName]));
		}

		$this->foreignKeys[$fieldName] = [
			'table'    => $tableName,
			'field'    => $tableField,
			'onDelete' => strtoupper($onDelete),
			'onUpdate' => strtoupper($onUpdate),
		];

		return $this;
	}

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

	/**
	 * Foreign Key Drop
	 *
	 * @param string $table        Table name
	 * @param string $foreign_name Foreign name
	 *
	 * @return boolean|\CodeIgniter\Database\BaseResult|\CodeIgniter\Database\Query|false|mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function dropForeignKey(string $table, string $foreign_name)
	{
		$sql = sprintf($this->dropConstraintStr, $this->db->escapeIdentifiers($this->db->DBPrefix . $table),
			$this->db->escapeIdentifiers($this->db->DBPrefix . $foreign_name));

		if ($sql === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('This feature is not available for the database you are using.');
			}

			return false;
		}

		return $this->db->query($sql);
	}

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

	/**
	 * Create Table
	 *
	 * @param string  $table         Table name
	 * @param boolean $if_not_exists Whether to add IF NOT EXISTS condition
	 * @param array   $attributes    Associative array of table attributes
	 *
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function createTable(string $table, bool $if_not_exists = false, array $attributes = [])
	{
		if ($table === '')
		{
			throw new \InvalidArgumentException('A table name is required for that operation.');
		}

		$table = $this->db->DBPrefix . $table;

		if (count($this->fields) === 0)
		{
			throw new \RuntimeException('Field information is required.');
		}

		$sql = $this->_createTable($table, $if_not_exists, $attributes);

		if (is_bool($sql))
		{
			$this->reset();
			if ($sql === false)
			{
				if ($this->db->DBDebug)
				{
					throw new DatabaseException('This feature is not available for the database you are using.');
				}

				return false;
			}
		}

		if (($result = $this->db->query($sql)) !== false)
		{
			empty($this->db->dataCache['table_names']) || ($this->db->dataCache['table_names'][] = $table);

			// Most databases don't support creating indexes from within the CREATE TABLE statement
			if (! empty($this->keys))
			{
				for ($i = 0, $sqls = $this->_processIndexes($table), $c = count($sqls); $i < $c; $i++)
				{
					$this->db->query($sqls[$i]);
				}
			}
		}

		$this->reset();

		return $result;
	}

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

	/**
	 * Create Table
	 *
	 * @param string  $table         Table name
	 * @param boolean $if_not_exists Whether to add 'IF NOT EXISTS' condition
	 * @param array   $attributes    Associative array of table attributes
	 *
	 * @return mixed
	 */
	protected function _createTable(string $table, bool $if_not_exists, array $attributes)
	{
		// For any platforms that don't support Create If Not Exists...
		if ($if_not_exists === true && $this->createTableIfStr === false)
		{
			if ($this->db->tableExists($table))
			{
				return true;
			}

			$if_not_exists = false;
		}

		$sql = ($if_not_exists) ? sprintf($this->createTableIfStr, $this->db->escapeIdentifiers($table))
			: 'CREATE TABLE';

		$columns = $this->_processFields(true);
		for ($i = 0, $c = count($columns); $i < $c; $i++)
		{
			$columns[$i] = ($columns[$i]['_literal'] !== false) ? "\n\t" . $columns[$i]['_literal']
				: "\n\t" . $this->_processColumn($columns[$i]);
		}

		$columns = implode(',', $columns);

		$columns .= $this->_processPrimaryKeys($table);
		$columns .= $this->_processForeignKeys($table);

		// Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL)
		if ($this->createTableKeys === true)
		{
			$columns .= $this->_processIndexes($table);
		}

		// createTableStr will usually have the following format: "%s %s (%s\n)"
		$sql = sprintf($this->createTableStr . '%s', $sql, $this->db->escapeIdentifiers($table), $columns,
			$this->_createTableAttributes($attributes));

		return $sql;
	}

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

	/**
	 * CREATE TABLE attributes
	 *
	 * @param array $attributes Associative array of table attributes
	 *
	 * @return string
	 */
	protected function _createTableAttributes(array $attributes): string
	{
		$sql = '';

		foreach (array_keys($attributes) as $key)
		{
			if (is_string($key))
			{
				$sql .= ' ' . strtoupper($key) . ' ' . $this->db->escape($attributes[$key]);
			}
		}

		return $sql;
	}

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

	/**
	 * Drop Table
	 *
	 * @param string  $table_name Table name
	 * @param boolean $if_exists  Whether to add an IF EXISTS condition
	 * @param boolean $cascade    Whether to add an CASCADE condition
	 *
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function dropTable(string $table_name, bool $if_exists = false, bool $cascade = false)
	{
		if ($table_name === '')
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('A table name is required for that operation.');
			}

			return false;
		}

		// If the prefix is already starting the table name, remove it...
		if (! empty($this->db->DBPrefix) && strpos($table_name, $this->db->DBPrefix) === 0)
		{
			$table_name = substr($table_name, strlen($this->db->DBPrefix));
		}

		if (($query = $this->_dropTable($this->db->DBPrefix . $table_name, $if_exists, $cascade)) === true)
		{
			return true;
		}

		$query = $this->db->query($query);

		// Update table list cache
		if ($query && ! empty($this->db->dataCache['table_names']))
		{
			$key = array_search(strtolower($this->db->DBPrefix . $table_name),
				array_map('strtolower', $this->db->dataCache['table_names']), true);
			if ($key !== false)
			{
				unset($this->db->dataCache['table_names'][$key]);
			}
		}

		return $query;
	}

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

	/**
	 * Drop Table
	 *
	 * Generates a platform-specific DROP TABLE string
	 *
	 * @param string  $table     Table name
	 * @param boolean $if_exists Whether to add an IF EXISTS condition
	 * @param boolean $cascade   Whether to add an CASCADE condition
	 *
	 * @return string
	 */
	protected function _dropTable(string $table, bool $if_exists, bool $cascade): string
	{
		$sql = 'DROP TABLE';

		if ($if_exists)
		{
			if ($this->dropTableIfStr === false)
			{
				if (! $this->db->tableExists($table))
				{
					return true;
				}
			}
			else
			{
				$sql = sprintf($this->dropTableIfStr, $this->db->escapeIdentifiers($table));
			}
		}

		$sql = $sql . ' ' . $this->db->escapeIdentifiers($table);

		return $sql;
	}

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

	/**
	 * Rename Table
	 *
	 * @param string $table_name     Old table name
	 * @param string $new_table_name New table name
	 *
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function renameTable(string $table_name, string $new_table_name)
	{
		if ($table_name === '' || $new_table_name === '')
		{
			throw new \InvalidArgumentException('A table name is required for that operation.');
		}
		elseif ($this->renameTableStr === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('This feature is not available for the database you are using.');
			}

			return false;
		}

		$result = $this->db->query(sprintf($this->renameTableStr,
				$this->db->escapeIdentifiers($this->db->DBPrefix . $table_name),
				$this->db->escapeIdentifiers($this->db->DBPrefix . $new_table_name))
		);

		if ($result && ! empty($this->db->dataCache['table_names']))
		{
			$key = array_search(strtolower($this->db->DBPrefix . $table_name),
				array_map('strtolower', $this->db->dataCache['table_names']), true);
			if ($key !== false)
			{
				$this->db->dataCache['table_names'][$key] = $this->db->DBPrefix . $new_table_name;
			}
		}

		return $result;
	}

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

	/**
	 * Column Add
	 *
	 * @param string        $table Table name
	 * @param string|array  $field Column definition
	 *
	 * @return boolean
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function addColumn(string $table, $field): bool
	{
		// Work-around for literal column definitions
		is_array($field) || $field = [$field];

		foreach (array_keys($field) as $k)
		{
			$this->addField([$k => $field[$k]]);
		}

		$sqls = $this->_alterTable('ADD', $this->db->DBPrefix . $table, $this->_processFields());
		$this->reset();
		if ($sqls === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('This feature is not available for the database you are using.');
			}

			return false;
		}

		for ($i = 0, $c = count($sqls); $i < $c; $i++)
		{
			if ($this->db->query($sqls[$i]) === false)
			{
				return false;
			}
		}

		return true;
	}

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

	/**
	 * Column Drop
	 *
	 * @param string $table       Table name
	 * @param string $column_name Column name
	 *
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function dropColumn(string $table, string $column_name)
	{
		$sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $column_name);
		if ($sql === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('This feature is not available for the database you are using.');
			}

			return false;
		}

		return $this->db->query($sql);
	}

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

	/**
	 * Column Modify
	 *
	 * @param string        $table Table name
	 * @param string|array  $field Column definition
	 *
	 * @return boolean
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function modifyColumn(string $table, $field): bool
	{
		// Work-around for literal column definitions
		is_array($field) || $field = [$field];

		foreach (array_keys($field) as $k)
		{
			$this->addField([$k => $field[$k]]);
		}

		if (count($this->fields) === 0)
		{
			throw new \RuntimeException('Field information is required');
		}

		$sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields());
		$this->reset();
		if ($sqls === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('This feature is not available for the database you are using.');
			}

			return false;
		}

		if ($sqls !== null)
		{
			for ($i = 0, $c = count($sqls); $i < $c; $i++)
			{
				if ($this->db->query($sqls[$i]) === false)
				{
					return false;
				}
			}
		}

		return true;
	}

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

	/**
	 * ALTER TABLE
	 *
	 * @param string $alter_type ALTER type
	 * @param string $table      Table name
	 * @param mixed  $field      Column definition
	 *
	 * @return string|string[]
	 */
	protected function _alterTable(string $alter_type, string $table, $field)
	{
		$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' ';

		// DROP has everything it needs now.
		if ($alter_type === 'DROP')
		{
			return $sql . 'DROP COLUMN ' . $this->db->escapeIdentifiers($field);
		}

		$sql .= ($alter_type === 'ADD') ? 'ADD ' : $alter_type . ' COLUMN ';

		$sqls = [];
		foreach ($field as $data)
		{
			$sqls[] = $sql
					  . ($data['_literal'] !== false ? $data['_literal'] : $this->_processColumn($data));
		}

		return $sqls;
	}

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

	/**
	 * Process fields
	 *
	 * @param boolean $create_table
	 *
	 * @return array
	 */
	protected function _processFields(bool $create_table = false): array
	{
		$fields = [];

		foreach ($this->fields as $key => $attributes)
		{
			if (is_int($key) && ! is_array($attributes))
			{
				$fields[] = ['_literal' => $attributes];
				continue;
			}

			$attributes = array_change_key_case($attributes, CASE_UPPER);

			if ($create_table === true && empty($attributes['TYPE']))
			{
				continue;
			}

			isset($attributes['TYPE']) && $this->_attributeType($attributes);

			$field = [
				'name'           => $key,
				'new_name'       => isset($attributes['NAME']) ? $attributes['NAME'] : null,
				'type'           => isset($attributes['TYPE']) ? $attributes['TYPE'] : null,
				'length'         => '',
				'unsigned'       => '',
				'null'           => '',
				'unique'         => '',
				'default'        => '',
				'auto_increment' => '',
				'_literal'       => false,
			];

			isset($attributes['TYPE']) && $this->_attributeUnsigned($attributes, $field);

			if ($create_table === false)
			{
				if (isset($attributes['AFTER']))
				{
					$field['after'] = $attributes['AFTER'];
				}
				elseif (isset($attributes['FIRST']))
				{
					$field['first'] = (bool)$attributes['FIRST'];
				}
			}

			$this->_attributeDefault($attributes, $field);

			if (isset($attributes['NULL']))
			{
				if ($attributes['NULL'] === true)
				{
					$field['null'] = empty($this->null) ? '' : ' ' . $this->null;
				}
				else
				{
					$field['null'] = ' NOT NULL';
				}
			}
			elseif ($create_table === true)
			{
				$field['null'] = ' NOT NULL';
			}

			$this->_attributeAutoIncrement($attributes, $field);
			$this->_attributeUnique($attributes, $field);

			if (isset($attributes['COMMENT']))
			{
				$field['comment'] = $this->db->escape($attributes['COMMENT']);
			}

			if (isset($attributes['TYPE']) && ! empty($attributes['CONSTRAINT']))
			{
				if (is_array($attributes['CONSTRAINT']))
				{
					$attributes['CONSTRAINT'] = $this->db->escape($attributes['CONSTRAINT']);
					$attributes['CONSTRAINT'] = implode(',', $attributes['CONSTRAINT']);
				}

				$field['length'] = '(' . $attributes['CONSTRAINT'] . ')';
			}

			$fields[] = $field;
		}

		return $fields;
	}

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

	/**
	 * Process column
	 *
	 * @param array $field
	 *
	 * @return string
	 */
	protected function _processColumn(array $field): string
	{
		return $this->db->escapeIdentifiers($field['name'])
			   . ' ' . $field['type'] . $field['length']
			   . $field['unsigned']
			   . $field['default']
			   . $field['null']
			   . $field['auto_increment']
			   . $field['unique'];
	}

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

	/**
	 * Field attribute TYPE
	 *
	 * Performs a data type mapping between different databases.
	 *
	 * @param array &$attributes
	 *
	 * @return void
	 */
	protected function _attributeType(array &$attributes)
	{
		// Usually overridden by drivers
	}

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

	/**
	 * Field attribute UNSIGNED
	 *
	 * Depending on the unsigned property value:
	 *
	 *    - TRUE will always set $field['unsigned'] to 'UNSIGNED'
	 *    - FALSE will always set $field['unsigned'] to ''
	 *    - array(TYPE) will set $field['unsigned'] to 'UNSIGNED',
	 *        if $attributes['TYPE'] is found in the array
	 *    - array(TYPE => UTYPE) will change $field['type'],
	 *        from TYPE to UTYPE in case of a match
	 *
	 * @param array &$attributes
	 * @param array &$field
	 *
	 * @return null|void
	 */
	protected function _attributeUnsigned(array &$attributes, array &$field)
	{
		if (empty($attributes['UNSIGNED']) || $attributes['UNSIGNED'] !== true)
		{
			return;
		}

		// Reset the attribute in order to avoid issues if we do type conversion
		$attributes['UNSIGNED'] = false;

		if (is_array($this->unsigned))
		{
			foreach (array_keys($this->unsigned) as $key)
			{
				if (is_int($key) && strcasecmp($attributes['TYPE'], $this->unsigned[$key]) === 0)
				{
					$field['unsigned'] = ' UNSIGNED';

					return;
				}
				elseif (is_string($key) && strcasecmp($attributes['TYPE'], $key) === 0)
				{
					$field['type'] = $key;

					return;
				}
			}

			return;
		}

		$field['unsigned'] = ($this->unsigned === true) ? ' UNSIGNED' : '';
	}

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

	/**
	 * Field attribute DEFAULT
	 *
	 * @param array &$attributes
	 * @param array &$field
	 *
	 * @return null|void
	 */
	protected function _attributeDefault(array &$attributes, array &$field)
	{
		if ($this->default === false)
		{
			return;
		}

		if (array_key_exists('DEFAULT', $attributes))
		{
			if ($attributes['DEFAULT'] === null)
			{
				$field['default'] = empty($this->null) ? '' : $this->default . $this->null;

				// Override the NULL attribute if that's our default
				$attributes['NULL'] = true;
				$field['null']      = empty($this->null) ? '' : ' ' . $this->null;
			}
			else
			{
				$field['default'] = $this->default . $this->db->escape($attributes['DEFAULT']);
			}
		}
	}

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

	/**
	 * Field attribute UNIQUE
	 *
	 * @param array &$attributes
	 * @param array &$field
	 *
	 * @return void
	 */
	protected function _attributeUnique(array &$attributes, array &$field)
	{
		if (! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === true)
		{
			$field['unique'] = ' UNIQUE';
		}
	}

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

	/**
	 * Field attribute AUTO_INCREMENT
	 *
	 * @param array &$attributes
	 * @param array &$field
	 *
	 * @return void
	 */
	protected function _attributeAutoIncrement(array &$attributes, array &$field)
	{
		if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true
			&& stripos($field['type'], 'int') !== false
		)
		{
			$field['auto_increment'] = ' AUTO_INCREMENT';
		}
	}

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

	/**
	 * Process primary keys
	 *
	 * @param string $table Table name
	 *
	 * @return string
	 */
	protected function _processPrimaryKeys(string $table): string
	{
		$sql = '';

		for ($i = 0, $c = count($this->primaryKeys); $i < $c; $i++)
		{
			if (! isset($this->fields[$this->primaryKeys[$i]]))
			{
				unset($this->primaryKeys[$i]);
			}
		}

		if (count($this->primaryKeys) > 0)
		{
			$sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers('pk_' . $table)
					. ' PRIMARY KEY(' . implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)) . ')';
		}

		return $sql;
	}

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

	/**
	 * Process indexes
	 *
	 * @param string $table
	 *
	 * @return array
	 */
	protected function _processIndexes(string $table)
	{
		$sqls = [];

		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
		{
			$this->keys[$i] = (array)$this->keys[$i];

			for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
			{
				if (! isset($this->fields[$this->keys[$i][$i2]]))
				{
					unset($this->keys[$i][$i2]);
				}
			}
			if (count($this->keys[$i]) <= 0)
			{
				continue;
			}

			if (in_array($i, $this->uniqueKeys))
			{
				$sqls[] = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table)
						  . ' ADD CONSTRAINT ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
						  . ' UNIQUE (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
				continue;
			}

			$sqls[] = 'CREATE INDEX ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
					  . ' ON ' . $this->db->escapeIdentifiers($table)
					  . ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
		}

		return $sqls;
	}

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

	/**
	 * Process foreign keys
	 *
	 * @param string $table Table name
	 *
	 * @return string
	 */
	protected function _processForeignKeys(string $table): string
	{
		$sql = '';

		$allowActions = [
			'CASCADE',
			'SET NULL',
			'NO ACTION',
			'RESTRICT',
			'SET DEFAULT',
		];

		if (count($this->foreignKeys) > 0)
		{
			foreach ($this->foreignKeys as $field => $fkey)
			{
				$name_index = $table . '_' . $field . '_foreign';

				$sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers($name_index)
					. ' FOREIGN KEY(' . $this->db->escapeIdentifiers($field) . ') REFERENCES ' . $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['table']) . ' (' . $this->db->escapeIdentifiers($fkey['field']) . ')';

				if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions))
				{
					$sql .= ' ON DELETE ' . $fkey['onDelete'];
				}

				if ($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions))
				{
					$sql .= ' ON UPDATE ' . $fkey['onUpdate'];
				}
			}
		}

		return $sql;
	}

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

	/**
	 * Reset
	 *
	 * Resets table creation vars
	 *
	 * @return void
	 */
	public function reset()
	{
		$this->fields = $this->keys = $this->uniqueKeys = $this->primaryKeys = $this->foreignKeys = [];
	}

}


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

use CodeIgniter\Config\Services;

/**
 * Class Language
 *
 * @package CodeIgniter\Language
 */
class Language
{

	/**
	 * Stores the retrieved language lines
	 * from files for faster retrieval on
	 * second use.
	 *
	 * @var array
	 */
	protected $language = [];

	/**
	 * The current language/locale to work with.
	 *
	 * @var string
	 */
	protected $locale;

	/**
	 * Boolean value whether the intl
	 * libraries exist on the system.
	 *
	 * @var boolean
	 */
	protected $intlSupport = false;

	/**
	 * Stores filenames that have been
	 * loaded so that we don't load them again.
	 *
	 * @var array
	 */
	protected $loadedFiles = [];

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

	public function __construct(string $locale)
	{
		$this->locale = $locale;

		if (class_exists('\MessageFormatter'))
		{
			$this->intlSupport = true;
		};
	}

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

	/**
	 * Sets the current locale to use when performing string lookups.
	 *
	 * @param string $locale
	 *
	 * @return $this
	 */
	public function setLocale(string $locale = null)
	{
		if (! is_null($locale))
		{
			$this->locale = $locale;
		}

		return $this;
	}

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

	/**
	 * @return string
	 */
	public function getLocale(): string
	{
		return $this->locale;
	}

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

	/**
	 * Parses the language string for a file, loads the file, if necessary,
	 * getting the line.
	 *
	 * @param string $line Line.
	 * @param array  $args Arguments.
	 *
	 * @return string|string[] Returns line.
	 */
	public function getLine(string $line, array $args = [])
	{
		// ignore requests with no file specified
		if (! strpos($line, '.'))
		{
			return $line;
		}

		// Parse out the file name and the actual alias.
		// Will load the language file and strings.
		[
			$file,
			$parsedLine,
		] = $this->parseLine($line, $this->locale);

		$output = $this->language[$this->locale][$file][$parsedLine] ?? null;

		if ($output === null && strpos($this->locale, '-'))
		{
			[$locale] = explode('-', $this->locale, 2);

			[
				$file,
				$parsedLine,
			] = $this->parseLine($line, $locale);

			$output = $this->language[$locale][$file][$parsedLine] ?? null;
		}

		// if still not found, try English
		if (empty($output))
		{
			$this->parseLine($line, 'en');
			$output = $this->language['en'][$file][$parsedLine] ?? null;
		}

		$output = $output ?? $line;

		if (! empty($args))
		{
			$output = $this->formatMessage($output, $args);
		}

		return $output;
	}

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

	/**
	 * Parses the language string which should include the
	 * filename as the first segment (separated by period).
	 *
	 * @param string $line
	 * @param string $locale
	 *
	 * @return array
	 */
	protected function parseLine(string $line, string $locale): array
	{
		$file = substr($line, 0, strpos($line, '.'));
		$line = substr($line, strlen($file) + 1);

		if (! isset($this->language[$locale][$file]) || ! array_key_exists($line, $this->language[$locale][$file]))
		{
			$this->load($file, $locale);
		}

		return [
			$file,
			$line,
		];
	}

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

	/**
	 * Advanced message formatting.
	 *
	 * @param string|array $message Message.
	 * @param array	       $args    Arguments.
	 *
	 * @return string|array Returns formatted message.
	 */
	protected function formatMessage($message, array $args = [])
	{
		if (! $this->intlSupport || ! $args)
		{
			return $message;
		}

		if (is_array($message))
		{
			foreach ($message as $index => $value)
			{
				$message[$index] = $this->formatMessage($value, $args);
			}
			return $message;
		}

		return \MessageFormatter::formatMessage($this->locale, $message, $args);
	}

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

	/**
	 * Loads a language file in the current locale. If $return is true,
	 * will return the file's contents, otherwise will merge with
	 * the existing language lines.
	 *
	 * @param string  $file
	 * @param string  $locale
	 * @param boolean $return
	 *
	 * @return array|null
	 */
	protected function load(string $file, string $locale, bool $return = false)
	{
		if (! array_key_exists($locale, $this->loadedFiles))
		{
			$this->loadedFiles[$locale] = [];
		}

		if (in_array($file, $this->loadedFiles[$locale]))
		{
			// Don't load it more than once.
			return [];
		}

		if (! array_key_exists($locale, $this->language))
		{
			$this->language[$locale] = [];
		}

		if (! array_key_exists($file, $this->language[$locale]))
		{
			$this->language[$locale][$file] = [];
		}

		$path = "Language/{$locale}/{$file}.php";

		$lang = $this->requireFile($path);

		if ($return)
		{
			return $lang;
		}

		$this->loadedFiles[$locale][] = $file;

		// Merge our string
		$this->language[$locale][$file] = $lang;
	}

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

	/**
	 * A simple method for including files that can be
	 * overridden during testing.
	 *
	 * @param string $path
	 *
	 * @return array
	 */
	protected function requireFile(string $path): array
	{
		$files   = Services::locator()->search($path);
		$strings = [];

		foreach ($files as $file)
		{
			// On some OS's we were seeing failures
			// on this command returning boolean instead
			// of array during testing, so we've removed
			// the require_once for now.
			if (is_file($file))
			{
				$strings[] = require $file;
			}
		}

		if (isset($strings[1]))
		{
			$strings = array_replace_recursive(...$strings);
		}
		elseif (isset($strings[0]))
		{
			$strings = $strings[0];
		}

		return $strings;
	}

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