<?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 Psr\Log\LoggerAwareTrait;

/**
 * Base class for session handling
 */
abstract class BaseHandler implements \SessionHandlerInterface
{

	use LoggerAwareTrait;

	/**
	 * The Data fingerprint.
	 *
	 * @var boolean
	 */
	protected $fingerprint;

	/**
	 * Lock placeholder.
	 *
	 * @var mixed
	 */
	protected $lock = false;

	/**
	 * Cookie prefix
	 *
	 * @var string
	 */
	protected $cookiePrefix = '';

	/**
	 * Cookie domain
	 *
	 * @var string
	 */
	protected $cookieDomain = '';

	/**
	 * Cookie path
	 *
	 * @var string
	 */
	protected $cookiePath = '/';

	/**
	 * Cookie secure?
	 *
	 * @var boolean
	 */
	protected $cookieSecure = false;

	/**
	 * Cookie name to use
	 *
	 * @var string
	 */
	protected $cookieName;

	/**
	 * Match IP addresses for cookies?
	 *
	 * @var boolean
	 */
	protected $matchIP = false;

	/**
	 * Current session ID
	 *
	 * @var string
	 */
	protected $sessionID;

	/**
	 * The 'save path' for the session
	 * varies between
	 *
	 * @var string
	 */
	protected $savePath;

	/**
	 * @var string
	 */
	protected $ipAddress;

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

	/**
	 * Constructor
	 *
	 * @param BaseConfig $config
	 */
	public function __construct($config, string $ipAddress)
	{
		$this->cookiePrefix = $config->cookiePrefix;
		$this->cookieDomain = $config->cookieDomain;
		$this->cookiePath   = $config->cookiePath;
		$this->cookieSecure = $config->cookieSecure;
		$this->cookieName   = $config->sessionCookieName;
		$this->matchIP      = $config->sessionMatchIP;
		$this->savePath     = $config->sessionSavePath;
		$this->ipAddress    = $ipAddress;
	}

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

	/**
	 * Internal method to force removal of a cookie by the client
	 * when session_destroy() is called.
	 *
	 * @return boolean
	 */
	protected function destroyCookie(): bool
	{
		return setcookie(
				$this->cookieName, null, 1, $this->cookiePath, $this->cookieDomain, $this->cookieSecure, true
		);
	}

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

	/**
	 * A dummy method allowing drivers with no locking functionality
	 * (databases other than PostgreSQL and MySQL) to act as if they
	 * do acquire a lock.
	 *
	 * @param string $sessionID
	 *
	 * @return boolean
	 */
	protected function lockSession(string $sessionID): bool
	{
		$this->lock = true;
		return true;
	}

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

	/**
	 * Releases the lock, if any.
	 *
	 * @return boolean
	 */
	protected function releaseLock(): bool
	{
		$this->lock = false;

		return true;
	}

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

	/**
	 * Fail
	 *
	 * Drivers other than the 'files' one don't (need to) use the
	 * session.save_path INI setting, but that leads to confusing
	 * error messages emitted by PHP when open() or write() fail,
	 * as the message contains session.save_path ...
	 * To work around the problem, the drivers will call this method
	 * so that the INI is set just in time for the error message to
	 * be properly generated.
	 *
	 * @return boolean
	 */
	protected function fail(): bool
	{
		ini_set('session.save_path', $this->savePath);

		return false;
	}
}


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

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

/**
 * CI Help command for the spark script.
 *
 * Lists the basic usage information for the spark script,
 * and provides a way to list help for other commands.
 *
 * @package CodeIgniter\Commands
 */
class ListCommands extends BaseCommand
{

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

	/**
	 * The Command's name
	 *
	 * @var string
	 */
	protected $name = 'list';

	/**
	 * the Command's short description
	 *
	 * @var string
	 */
	protected $description = 'Lists the available commands.';

	/**
	 * the Command's usage
	 *
	 * @var string
	 */
	protected $usage = 'list';

	/**
	 * the Command's Arguments
	 *
	 * @var array
	 */
	protected $arguments = [];

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

	/**
	 * The length of the longest command name.
	 * Used during display in columns.
	 *
	 * @var integer
	 */
	protected $maxFirstLength = 0;

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

	/**
	 * Displays the help for the spark cli script itself.
	 *
	 * @param array $params
	 */
	public function run(array $params)
	{
		$commands = $this->commands->getCommands();

		$this->describeCommands($commands);

		CLI::newLine();
	}

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

	/**
	 * Displays the commands on the CLI.
	 *
	 * @param array $commands
	 */
	protected function describeCommands(array $commands = [])
	{
		ksort($commands);

		// Sort into buckets by group
		$sorted         = [];
		$maxTitleLength = 0;

		foreach ($commands as $title => $command)
		{
			if (! isset($sorted[$command['group']]))
			{
				$sorted[$command['group']] = [];
			}

			$sorted[$command['group']][$title] = $command;

			$maxTitleLength = max($maxTitleLength, strlen($title));
		}

		ksort($sorted);

		// Display it all...
		foreach ($sorted as $group => $items)
		{
			CLI::newLine();
			CLI::write($group);

			foreach ($items as $title => $item)
			{
				$title = $this->padTitle($title, $maxTitleLength, 2, 2);

				$out = CLI::color($title, 'yellow');

				if (isset($item['description']))
				{
					$out .= CLI::wrap($item['description'], 125, strlen($title));
				}

				CLI::write($out);
			}
		}
	}

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

	/**
	 * Pads our string out so that all titles are the same length to nicely line up descriptions.
	 *
	 * @param string  $item
	 * @param integer $max
	 * @param integer $extra  // How many extra spaces to add at the end
	 * @param integer $indent
	 *
	 * @return string
	 */
	protected function padTitle(string $item, int $max, int $extra = 2, int $indent = 0): string
	{
		$max += $extra + $indent;

		$item = str_repeat(' ', $indent) . $item;
		$item = str_pad($item, $max);

		return $item;
	}

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


<?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 BaseUtils
 */
abstract class BaseUtils
{

	/**
	 * Database object
	 *
	 * @var object
	 */
	protected $db;

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

	/**
	 * List databases statement
	 *
	 * @var string
	 */
	protected $listDatabases = false;

	/**
	 * OPTIMIZE TABLE statement
	 *
	 * @var string
	 */
	protected $optimizeTable = false;

	/**
	 * REPAIR TABLE statement
	 *
	 * @var string
	 */
	protected $repairTable = false;

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

	/**
	 * Class constructor
	 *
	 * @param ConnectionInterface|object $db
	 */
	public function __construct(ConnectionInterface &$db)
	{
		$this->db = & $db;
	}

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

	/**
	 * List databases
	 *
	 * @return array|boolean
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function listDatabases()
	{
		// Is there a cached result?
		if (isset($this->db->dataCache['db_names']))
		{
			return $this->db->dataCache['db_names'];
		}
		elseif ($this->listDatabases === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('Unsupported feature of the database platform you are using.');
			}
			return false;
		}

		$this->db->dataCache['db_names'] = [];

		$query = $this->db->query($this->listDatabases);
		if ($query === false)
		{
			return $this->db->dataCache['db_names'];
		}

		for ($i = 0, $query = $query->getResultArray(), $c = count($query); $i < $c; $i ++)
		{
			$this->db->dataCache['db_names'][] = current($query[$i]);
		}

		return $this->db->dataCache['db_names'];
	}

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

	/**
	 * Determine if a particular database exists
	 *
	 * @param  string $database_name
	 * @return boolean
	 */
	public function databaseExists(string $database_name): bool
	{
		return in_array($database_name, $this->listDatabases());
	}

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

	/**
	 * Optimize Table
	 *
	 * @param  string $table_name
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function optimizeTable(string $table_name)
	{
		if ($this->optimizeTable === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('Unsupported feature of the database platform you are using.');
			}
			return false;
		}

		$query = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($table_name)));
		if ($query !== false)
		{
			$query = $query->getResultArray();
			return current($query);
		}

		return false;
	}

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

	/**
	 * Optimize Database
	 *
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function optimizeDatabase()
	{
		if ($this->optimizeTable === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('Unsupported feature of the database platform you are using.');
			}
			return false;
		}

		$result = [];
		foreach ($this->db->listTables() as $table_name)
		{
			$res = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($table_name)));
			if (is_bool($res))
			{
				return $res;
			}

			// Build the result array...

			$res  = $res->getResultArray();

			// Postgre & SQLite3 returns empty array
			if (empty($res))
			{
				$key = $table_name;
			}
			else
			{
				$res  = current($res);
				$key = str_replace($this->db->database . '.', '', current($res));
				$keys = array_keys($res);
				unset($res[$keys[0]]);
			}

			$result[$key] = $res;
		}

		return $result;
	}

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

	/**
	 * Repair Table
	 *
	 * @param  string $table_name
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function repairTable(string $table_name)
	{
		if ($this->repairTable === false)
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('Unsupported feature of the database platform you are using.');
			}
			return false;
		}

		$query = $this->db->query(sprintf($this->repairTable, $this->db->escapeIdentifiers($table_name)));
		if (is_bool($query))
		{
			return $query;
		}

		$query = $query->getResultArray();
		return current($query);
	}

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

	/**
	 * Generate CSV from a query result object
	 *
	 * @param ResultInterface $query     Query result object
	 * @param string          $delim     Delimiter (default: ,)
	 * @param string          $newline   Newline character (default: \n)
	 * @param string          $enclosure Enclosure (default: ")
	 *
	 * @return string
	 */
	public function getCSVFromResult(ResultInterface $query, string $delim = ',', string $newline = "\n", string $enclosure = '"')
	{
		$out = '';
		// First generate the headings from the table column names
		foreach ($query->getFieldNames() as $name)
		{
			$out .= $enclosure . str_replace($enclosure, $enclosure . $enclosure, $name) . $enclosure . $delim;
		}

		$out = substr($out, 0, -strlen($delim)) . $newline;

		// Next blast through the result array and build out the rows
		while ($row = $query->getUnbufferedRow('array'))
		{
			$line = [];
			foreach ($row as $item)
			{
				$line[] = $enclosure . str_replace($enclosure, $enclosure . $enclosure, $item) . $enclosure;
			}
			$out .= implode($delim, $line) . $newline;
		}

		return $out;
	}

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

	/**
	 * Generate XML data from a query result object
	 *
	 * @param ResultInterface $query  Query result object
	 * @param array           $params Any preferences
	 *
	 * @return string
	 */
	public function getXMLFromResult(ResultInterface $query, array $params = []): string
	{
		// Set our default values
		foreach (['root' => 'root', 'element' => 'element', 'newline' => "\n", 'tab' => "\t"] as $key => $val)
		{
			if (! isset($params[$key]))
			{
				$params[$key] = $val;
			}
		}

		// Create variables for convenience
		extract($params);

		// Load the xml helper
		helper('xml');
		// Generate the result
		$xml = '<' . $root . '>' . $newline;
		while ($row = $query->getUnbufferedRow())
		{
			$xml .= $tab . '<' . $element . '>' . $newline;
			foreach ($row as $key => $val)
			{
				$val = (!empty($val)) ? xml_convert($val) : '';
				$xml .= $tab . $tab . '<' . $key . '>' . $val . '</' . $key . '>' . $newline;
			}
			$xml .= $tab . '</' . $element . '>' . $newline;
		}

		return $xml . '</' . $root . '>' . $newline;
	}

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

	/**
	 * Database Backup
	 *
	 * @param  array|string $params
	 * @return mixed
	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
	 */
	public function backup($params = [])
	{
		// If the parameters have not been submitted as an
		// array then we know that it is simply the table
		// name, which is a valid short cut.
		if (is_string($params))
		{
			$params = ['tables' => $params];
		}

		// Set up our default preferences
		$prefs = [
			'tables'             => [],
			'ignore'             => [],
			'filename'           => '',
			'format'             => 'gzip', // gzip, zip, txt
			'add_drop'           => true,
			'add_insert'         => true,
			'newline'            => "\n",
			'foreign_key_checks' => true,
		];

		// Did the user submit any preferences? If so set them....
		if (! empty($params))
		{
			foreach ($prefs as $key => $val)
			{
				if (isset($params[$key]))
				{
					$prefs[$key] = $params[$key];
				}
			}
		}

		// Are we backing up a complete database or individual tables?
		// If no table names were submitted we'll fetch the entire table list
		if (empty($prefs['tables']))
		{
			$prefs['tables'] = $this->db->listTables();
		}

		// Validate the format
		if (! in_array($prefs['format'], ['gzip', 'zip', 'txt'], true))
		{
			$prefs['format'] = 'txt';
		}

		// Is the encoder supported? If not, we'll either issue an
		// error or use plain text depending on the debug settings
		if (($prefs['format'] === 'gzip' && ! function_exists('gzencode'))
			|| ( $prefs['format'] === 'zip' && ! function_exists('gzcompress')))
		{
			if ($this->db->DBDebug)
			{
				throw new DatabaseException('The file compression format you chose is not supported by your server.');
			}

			$prefs['format'] = 'txt';
		}

		// Was a Zip file requested?
		if ($prefs['format'] === 'zip')
		{
			// Set the filename if not provided (only needed with Zip files)
			if ($prefs['filename'] === '')
			{
				$prefs['filename'] = (count($prefs['tables']) === 1 ? $prefs['tables'] : $this->db->database)
					. date('Y-m-d_H-i', time()) . '.sql';
			}
			else
			{
				// If they included the .zip file extension we'll remove it
				if (preg_match('|.+?\.zip$|', $prefs['filename']))
				{
					$prefs['filename'] = str_replace('.zip', '', $prefs['filename']);
				}

				// Tack on the ".sql" file extension if needed
				if (! preg_match('|.+?\.sql$|', $prefs['filename']))
				{
					$prefs['filename'] .= '.sql';
				}
			}

			// Load the Zip class and output it
			//          $CI =& get_instance();
			//          $CI->load->library('zip');
			//          $CI->zip->add_data($prefs['filename'], $this->_backup($prefs));
			//          return $CI->zip->get_zip();
		}
		elseif ($prefs['format'] === 'txt') // Was a text file requested?
		{
			return $this->_backup($prefs);
		}
		elseif ($prefs['format'] === 'gzip') // Was a Gzip file requested?
		{
			return gzencode($this->_backup($prefs));
		}

		return;
	}

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

	/**
	 * Platform dependent version of the backup function.
	 *
	 * @param array|null $prefs
	 *
	 * @return mixed
	 */
	abstract public function _backup(array $prefs = 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\CLI;

use Psr\Log\LoggerInterface;

/**
 * Class BaseCommand
 *
 * @property $group
 * @property $name
 * @property $description
 *
 * @package CodeIgniter\CLI
 */
abstract class BaseCommand
{

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

	/**
	 * The Command's name
	 *
	 * @var string
	 */
	protected $name;

	/**
	 * the Command's usage description
	 *
	 * @var string
	 */
	protected $usage;

	/**
	 * the Command's short description
	 *
	 * @var string
	 */
	protected $description;

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

	/**
	 * the Command's Arguments description
	 *
	 * @var array
	 */
	protected $arguments = [];

	/**
	 * The Logger to use for a command
	 *
	 * @var \Psr\Log\LoggerInterface
	 */
	protected $logger;

	/**
	 * Instance of the CommandRunner controller
	 * so commands can call other commands.
	 *
	 * @var \CodeIgniter\CLI\CommandRunner
	 */
	protected $commands;

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

	/**
	 * BaseCommand constructor.
	 *
	 * @param \Psr\Log\LoggerInterface       $logger
	 * @param \CodeIgniter\CLI\CommandRunner $commands
	 */
	public function __construct(LoggerInterface $logger, CommandRunner $commands)
	{
		$this->logger   = $logger;
		$this->commands = $commands;
	}

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

	/**
	 * Actually execute a command.
	 * This has to be over-ridden in any concrete implementation.
	 *
	 * @param array $params
	 */
	abstract public function run(array $params);

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

	/**
	 * Can be used by a command to run other commands.
	 *
	 * @param string $command
	 * @param array  $params
	 *
	 * @return mixed
	 * @throws \ReflectionException
	 */
	protected function call(string $command, array $params = [])
	{
		// The CommandRunner will grab the first element
		// for the command name.
		array_unshift($params, $command);

		return $this->commands->index($params);
	}

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

	/**
	 * A simple method to display an error with line/file,
	 * in child commands.
	 *
	 * @param \Exception $e
	 */
	protected function showError(\Exception $e)
	{
		CLI::newLine();
		CLI::error($e->getMessage());
		CLI::write($e->getFile() . ' - ' . $e->getLine());
		CLI::newLine();
	}

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

	/**
	 * Makes it simple to access our protected properties.
	 *
	 * @param string $key
	 *
	 * @return mixed
	 */
	public function __get(string $key)
	{
		if (isset($this->$key))
		{
			return $this->$key;
		}

		return null;
	}

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

	/**
	 * show Help include (usage,arguments,description,options)
	 */
	public function showHelp()
	{
		// 4 spaces instead of tab
		$tab = '   ';
		CLI::write(lang('CLI.helpDescription'), 'yellow');
		CLI::write($tab . $this->description);
		CLI::newLine();

		CLI::write(lang('CLI.helpUsage'), 'yellow');
		$usage = empty($this->usage) ? $this->name . ' [arguments]' : $this->usage;
		CLI::write($tab . $usage);
		CLI::newLine();

		$pad = max($this->getPad($this->options, 6), $this->getPad($this->arguments, 6));

		if (! empty($this->arguments))
		{
			CLI::write(lang('CLI.helpArguments'), 'yellow');
			foreach ($this->arguments as $argument => $description)
			{
				CLI::write($tab . CLI::color(str_pad($argument, $pad), 'green') . $description, 'yellow');
			}
			CLI::newLine();
		}

		if (! empty($this->options))
		{
			CLI::write(lang('CLI.helpOptions'), 'yellow');
			foreach ($this->options as $option => $description)
			{
				CLI::write($tab . CLI::color(str_pad($option, $pad), 'green') . $description, 'yellow');
			}
			CLI::newLine();
		}
	}

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

	/**
	 * Get pad for $key => $value array output
	 *
	 * @param array   $array
	 * @param integer $pad
	 *
	 * @return integer
	 */
	public function getPad(array $array, int $pad): int
	{
		$max = 0;
		foreach ($array as $key => $value)
		{
			$max = max($max, strlen($key));
		}
		return $max + $pad;
	}

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


<?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\CLI\CLI;
use CodeIgniter\Config\BaseConfig;

/**
 * Class Seeder
 */
class Seeder
{

	/**
	 * The name of the database group to use.
	 *
	 * @var string
	 */
	protected $DBGroup;

	/**
	 * Where we can find the Seed files.
	 *
	 * @var string
	 */
	protected $seedPath;

	/**
	 * An instance of the main Database configuration
	 *
	 * @var BaseConfig
	 */
	protected $config;

	/**
	 * Database Connection instance
	 *
	 * @var BaseConnection
	 */
	protected $db;

	/**
	 * Database Forge instance.
	 *
	 * @var Forge
	 */
	protected $forge;

	/**
	 * If true, will not display CLI messages.
	 *
	 * @var boolean
	 */
	protected $silent = false;

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

	/**
	 * Seeder constructor.
	 *
	 * @param BaseConfig     $config
	 * @param BaseConnection $db
	 */
	public function __construct(BaseConfig $config, BaseConnection $db = null)
	{
		$this->seedPath = $config->filesPath ?? APPPATH . 'Database/';

		if (empty($this->seedPath))
		{
			throw new \InvalidArgumentException('Invalid filesPath set in the Config\Database.');
		}

		$this->seedPath = rtrim($this->seedPath, '/') . '/Seeds/';

		if (! is_dir($this->seedPath))
		{
			throw new \InvalidArgumentException('Unable to locate the seeds directory. Please check Config\Database::filesPath');
		}

		$this->config = & $config;

		if (is_null($db))
		{
			$db = \Config\Database::connect($this->DBGroup);
		}

		$this->db = & $db;
	}

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

	/**
	 * Loads the specified seeder and runs it.
	 *
	 * @param string $class
	 *
	 * @throws \InvalidArgumentException
	 */
	public function call(string $class)
	{
		if (empty($class))
		{
			throw new \InvalidArgumentException('No Seeder was specified.');
		}

		$path = str_replace('.php', '', $class) . '.php';

		// If we have namespaced class, simply try to load it.
		if (strpos($class, '\\') !== false)
		{
			$seeder = new $class($this->config);
		}
		// Otherwise, try to load the class manually.
		else
		{
			$path = $this->seedPath . $path;

			if (! is_file($path))
			{
				throw new \InvalidArgumentException('The specified Seeder is not a valid file: ' . $path);
			}

			// Assume the class has the correct namespace
			$class = APP_NAMESPACE . '\Database\Seeds\\' . $class;

			if (! class_exists($class, false))
			{
				require_once $path;
			}

			$seeder = new $class($this->config);
		}

		$seeder->run();

		unset($seeder);

		if (is_cli() && ! $this->silent)
		{
			CLI::write("Seeded: {$class}", 'green');
		}
	}

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

	/**
	 * Sets the location of the directory that seed files can be located in.
	 *
	 * @param string $path
	 *
	 * @return Seeder
	 */
	public function setPath(string $path)
	{
		$this->seedPath = rtrim($path, '/') . '/';

		return $this;
	}

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

	/**
	 * Sets the silent treatment.
	 *
	 * @param boolean $silent
	 *
	 * @return Seeder
	 */
	public function setSilent(bool $silent)
	{
		$this->silent = $silent;

		return $this;
	}

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

	/**
	 * Run the database seeds. This is where the magic happens.
	 *
	 * Child classes must implement this method and take care
	 * of inserting their data here.
	 *
	 * @return mixed
	 */
	public function run()
	{
	}

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


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

use Config\Database;

/**
 * Validation Rules.
 *
 * @package CodeIgniter\Validation
 */
class Rules
{
	//--------------------------------------------------------------------

	/**
	 * The value does not match another field in $data.
	 *
	 * @param string $str
	 * @param string $field
	 * @param array  $data  Other field/value pairs
	 *
	 * @return boolean
	 */
	public function differs(string $str = null, string $field, array $data): bool
	{
		return array_key_exists($field, $data) ? ($str !== $data[$field]) : false;
	}

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

	/**
	 * Equals the static value provided.
	 *
	 * @param string $str
	 * @param string $val
	 *
	 * @return boolean
	 */
	public function equals(string $str = null, string $val): bool
	{
		return $str === $val;
	}

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

	/**
	 * Returns true if $str is $val characters long.
	 * $val = "5" (one) | "5,8,12" (multiple values)
	 *
	 * @param string $str
	 * @param string $val
	 * @param array  $data
	 *
	 * @return boolean
	 */
	public function exact_length(string $str = null, string $val, array $data): bool
	{
		$val = explode(',', $val);
		foreach ($val as $tmp)
		{
			if (is_numeric($tmp) && (int)$tmp === mb_strlen($str))
			{
				return true;
			}
		}

		return false;
	}

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

	/**
	 * Greater than
	 *
	 * @param string $str
	 * @param string $min
	 * @param array  $data
	 *
	 * @return boolean
	 */
	public function greater_than(string $str = null, string $min, array $data): bool
	{
		return is_numeric($str) ? ($str > $min) : false;
	}

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

	/**
	 * Equal to or Greater than
	 *
	 * @param string $str
	 * @param string $min
	 * @param array  $data
	 *
	 * @return boolean
	 */
	public function greater_than_equal_to(string $str = null, string $min, array $data): bool
	{
		return is_numeric($str) ? ($str >= $min) : false;
	}

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

	/**
	 * Value should be within an array of values
	 *
	 * @param  string $value
	 * @param  string $list
	 * @param  array  $data
	 * @return boolean
	 */
	public function in_list(string $value = null, string $list, array $data): bool
	{
		$list = explode(',', $list);
		$list = array_map(function ($value) {
			return trim($value);
		}, $list);
		return in_array($value, $list, true);
	}

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

	/**
	 * Checks the database to see if the given value is unique. Can
	 * ignore a single record by field/value to make it useful during
	 * record updates.
	 *
	 * Example:
	 *    is_unique[table.field,ignore_field,ignore_value]
	 *    is_unique[users.email,id,5]
	 *
	 * @param string $str
	 * @param string $field
	 * @param array  $data
	 *
	 * @return boolean
	 */
	public function is_unique(string $str = null, string $field, array $data): bool
	{
		// Grab any data for exclusion of a single row.
		list($field, $ignoreField, $ignoreValue) = array_pad(explode(',', $field), 3, null);

		// Break the table and field apart
		sscanf($field, '%[^.].%[^.]', $table, $field);

		$db = Database::connect($data['DBGroup'] ?? null);

		$row = $db->table($table)
				  ->select('1')
				  ->where($field, $str)
				  ->limit(1);

		if (! empty($ignoreField) && ! empty($ignoreValue))
		{
			$row = $row->where("{$ignoreField} !=", $ignoreValue);
		}

		return (bool) ($row->get()
						->getRow() === null);
	}

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

	/**
	 * Less than
	 *
	 * @param string $str
	 * @param string $max
	 *
	 * @return boolean
	 */
	public function less_than(string $str = null, string $max): bool
	{
		return is_numeric($str) ? ($str < $max) : false;
	}

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

	/**
	 * Equal to or Less than
	 *
	 * @param string $str
	 * @param string $max
	 *
	 * @return boolean
	 */
	public function less_than_equal_to(string $str = null, string $max): bool
	{
		return is_numeric($str) ? ($str <= $max) : false;
	}

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

	/**
	 * Matches the value of another field in $data.
	 *
	 * @param string $str
	 * @param string $field
	 * @param array  $data  Other field/value pairs
	 *
	 * @return boolean
	 */
	public function matches(string $str = null, string $field, array $data): bool
	{
		return array_key_exists($field, $data) ? ($str === $data[$field]) : false;
	}

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

	/**
	 * Returns true if $str is $val or fewer characters in length.
	 *
	 * @param string $str
	 * @param string $val
	 * @param array  $data
	 *
	 * @return boolean
	 */
	public function max_length(string $str = null, string $val, array $data): bool
	{
		return ($val >= mb_strlen($str));
	}

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

	/**
	 * Returns true if $str is at least $val length.
	 *
	 * @param string $str
	 * @param string $val
	 * @param array  $data
	 *
	 * @return boolean
	 */
	public function min_length(string $str = null, string $val, array $data): bool
	{
		return ($val <= mb_strlen($str));
	}

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

	/**
	 * Does not equal the static value provided.
	 *
	 * @param string $str
	 * @param string $val
	 *
	 * @return boolean
	 */
	public function not_equals(string $str = null, string $val): bool
	{
		return $str !== $val;
	}

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

	/**
	 * Required
	 *
	 * @param mixed $str Value
	 *
	 * @return boolean          True if valid, false if not
	 */
	public function required($str = null): bool
	{
		if (is_object($str))
		{
			return true;
		}

		return is_array($str) ? ! empty($str) : (trim($str) !== '');
	}

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

	/**
	 * The field is required when any of the other required fields are present
	 * in the data.
	 *
	 * Example (field is required when the password field is present):
	 *
	 *     required_with[password]
	 *
	 * @param $str
	 * @param string $fields List of fields that we should check if present
	 * @param array  $data   Complete list of fields from the form
	 *
	 * @return boolean
	 */
	public function required_with($str = null, string $fields, array $data): bool
	{
		$fields = explode(',', $fields);

		// If the field is present we can safely assume that
		// the field is here, no matter whether the corresponding
		// search field is present or not.
		$present = $this->required($str ?? '');

		if ($present)
		{
			return true;
		}

		// Still here? Then we fail this test if
		// any of the fields are present in $data
		// as $fields is the lis
		$requiredFields = [];

		foreach ($fields as $field)
		{
			if (array_key_exists($field, $data))
			{
				$requiredFields[] = $field;
			}
		}

		// Remove any keys with empty values since, that means they
		// weren't truly there, as far as this is concerned.
		$requiredFields = array_filter($requiredFields, function ($item) use ($data) {
			return ! empty($data[$item]);
		});

		return empty($requiredFields);
	}

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

	/**
	 * The field is required when all of the other fields are present
	 * in the data but not required.
	 *
	 * Example (field is required when the id or email field is missing):
	 *
	 *     required_without[id,email]
	 *
	 * @param $str
	 * @param string $fields
	 * @param array  $data
	 *
	 * @return boolean
	 */
	public function required_without($str = null, string $fields, array $data): bool
	{
		$fields = explode(',', $fields);

		// If the field is present we can safely assume that
		// the field is here, no matter whether the corresponding
		// search field is present or not.
		$present = $this->required($str ?? '');

		if ($present)
		{
			return true;
		}

		// Still here? Then we fail this test if
		// any of the fields are not present in $data
		foreach ($fields as $field)
		{
			if (! array_key_exists($field, $data))
			{
				return 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\Postgre;

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

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

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

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

	/**
	 * Generates an array of column names in the result set.
	 *
	 * @return array
	 */
	public function getFieldNames(): array
	{
		$fieldNames = [];
		for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i ++)
		{
			$fieldNames[] = pg_field_name($this->resultID, $i);
		}

		return $fieldNames;
	}

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

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

		for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i ++)
		{
			$retVal[$i]             = new \stdClass();
			$retVal[$i]->name       = pg_field_name($this->resultID, $i);
			$retVal[$i]->type       = pg_field_type($this->resultID, $i);
			$retVal[$i]->max_length = pg_field_size($this->resultID, $i);
			// $retVal[$i]->primary_key = (int)($fieldData[$i]->flags & 2);
			// $retVal[$i]->default     = $fieldData[$i]->def;
		}

		return $retVal;
	}

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

	/**
	 * Frees the current result.
	 *
	 * @return void
	 */
	public function freeResult()
	{
		if (is_resource($this->resultID))
		{
			pg_free_result($this->resultID);
			$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 pg_result_seek($this->resultID, $n);
	}

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

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

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

	/**
	 * Returns the result set as an object.
	 *
	 * Overridden by child classes.
	 *
	 * @param string $className
	 *
	 * @return object
	 */
	protected function fetchObject(string $className = 'stdClass')
	{
		return pg_fetch_object($this->resultID, null, $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\Router;

use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Router\Exceptions\RedirectException;
use CodeIgniter\Router\Exceptions\RouterException;

/**
 * Request router.
 */
class Router implements RouterInterface
{

	/**
	 * A RouteCollection instance.
	 *
	 * @var RouteCollection
	 */
	protected $collection;

	/**
	 * Sub-directory that contains the requested controller class.
	 * Primarily used by 'autoRoute'.
	 *
	 * @var string
	 */
	protected $directory;

	/**
	 * The name of the controller class.
	 *
	 * @var string
	 */
	protected $controller;

	/**
	 * The name of the method to use.
	 *
	 * @var string
	 */
	protected $method;

	/**
	 * An array of binds that were collected
	 * so they can be sent to closure routes.
	 *
	 * @var array
	 */
	protected $params = [];

	/**
	 * The name of the front controller.
	 *
	 * @var string
	 */
	protected $indexPage = 'index.php';

	/**
	 * Whether dashes in URI's should be converted
	 * to underscores when determining method names.
	 *
	 * @var boolean
	 */
	protected $translateURIDashes = false;

	/**
	 * The route that was matched for this request.
	 *
	 * @var array|null
	 */
	protected $matchedRoute = null;

	/**
	 * The options set for the matched route.
	 *
	 * @var array|null
	 */
	protected $matchedRouteOptions = null;

	/**
	 * The locale that was detected in a route.
	 *
	 * @var string
	 */
	protected $detectedLocale = null;

	/**
	 * The filter info from Route Collection
	 * if the matched route should be filtered.
	 *
	 * @var string
	 */
	protected $filterInfo;

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

	/**
	 * Stores a reference to the RouteCollection object.
	 *
	 * @param RouteCollectionInterface $routes
	 */
	public function __construct(RouteCollectionInterface $routes)
	{
		$this->collection = $routes;

		$this->controller = $this->collection->getDefaultController();
		$this->method     = $this->collection->getDefaultMethod();
	}

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

	/**
	 * @param string|null $uri
	 *
	 * @return mixed|string
	 * @throws \CodeIgniter\Router\Exceptions\RedirectException
	 * @throws \CodeIgniter\Exceptions\PageNotFoundException
	 */
	public function handle(string $uri = null)
	{
		$this->translateURIDashes = $this->collection->shouldTranslateURIDashes();

		// If we cannot find a URI to match against, then
		// everything runs off of it's default settings.
		if (empty($uri))
		{
			return strpos($this->controller, '\\') === false
				? $this->collection->getDefaultNamespace() . $this->controller
				: $this->controller;
		}

		if ($this->checkRoutes($uri))
		{
			if ($this->collection->isFiltered($this->matchedRoute[0]))
			{
				$this->filterInfo = $this->collection->getFilterForRoute($this->matchedRoute[0]);
			}

			return $this->controller;
		}

		// Still here? Then we can try to match the URI against
		// Controllers/directories, but the application may not
		// want this, like in the case of API's.
		if (! $this->collection->shouldAutoRoute())
		{
			throw new PageNotFoundException("Can't find a route for '{$uri}'.");
		}

		$this->autoRoute($uri);

		return $this->controllerName();
	}

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

	/**
	 * Returns the filter info for the matched route, if any.
	 *
	 * @return string
	 */
	public function getFilter()
	{
		return $this->filterInfo;
	}

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

	/**
	 * Returns the name of the matched controller.
	 *
	 * @return mixed
	 */
	public function controllerName()
	{
		return $this->translateURIDashes
			? str_replace('-', '_', $this->controller)
			: $this->controller;
	}

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

	/**
	 * Returns the name of the method to run in the
	 * chosen container.
	 *
	 * @return mixed
	 */
	public function methodName(): string
	{
		return $this->translateURIDashes
			? str_replace('-', '_', $this->method)
			: $this->method;
	}

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

	/**
	 * Returns the 404 Override settings from the Collection.
	 * If the override is a string, will split to controller/index array.
	 */
	public function get404Override()
	{
		$route = $this->collection->get404Override();

		if (is_string($route))
		{
			$routeArray = explode('::', $route);

			return [
				$routeArray[0], // Controller
				$routeArray[1] ?? 'index',   // Method
			];
		}

		if (is_callable($route))
		{
			return $route;
		}

		return null;
	}

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

	/**
	 * Returns the binds that have been matched and collected
	 * during the parsing process as an array, ready to send to
	 * instance->method(...$params).
	 *
	 * @return mixed
	 */
	public function params(): array
	{
		return $this->params;
	}

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

	/**
	 * Returns the name of the sub-directory the controller is in,
	 * if any. Relative to APPPATH.'Controllers'.
	 *
	 * Only used when auto-routing is turned on.
	 *
	 * @return string
	 */
	public function directory(): string
	{
		return ! empty($this->directory) ? $this->directory : '';
	}

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

	/**
	 * Returns the routing information that was matched for this
	 * request, if a route was defined.
	 *
	 * @return array|null
	 */
	public function getMatchedRoute()
	{
		return $this->matchedRoute;
	}

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

	/**
	 * Returns all options set for the matched route
	 *
	 * @return array|null
	 */
	public function getMatchedRouteOptions()
	{
		return $this->matchedRouteOptions;
	}

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

	/**
	 * Sets the value that should be used to match the index.php file. Defaults
	 * to index.php but this allows you to modify it in case your are using
	 * something like mod_rewrite to remove the page. This allows you to set
	 * it a blank.
	 *
	 * @param $page
	 *
	 * @return mixed
	 */
	public function setIndexPage($page): self
	{
		$this->indexPage = $page;

		return $this;
	}

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

	/**
	 * Tells the system whether we should translate URI dashes or not
	 * in the URI from a dash to an underscore.
	 *
	 * @param boolean|false $val
	 *
	 * @return $this
	 */
	public function setTranslateURIDashes(bool $val = false): self
	{
		$this->translateURIDashes = $val;

		return $this;
	}

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

	/**
	 * Returns true/false based on whether the current route contained
	 * a {locale} placeholder.
	 *
	 * @return boolean
	 */
	public function hasLocale()
	{
		return (bool) $this->detectedLocale;
	}

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

	/**
	 * Returns the detected locale, if any, or null.
	 *
	 * @return string
	 */
	public function getLocale()
	{
		return $this->detectedLocale;
	}

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

	/**
	 * Compares the uri string against the routes that the
	 * RouteCollection class defined for us, attempting to find a match.
	 * This method will modify $this->controller, etal as needed.
	 *
	 * @param string $uri The URI path to compare against the routes
	 *
	 * @return boolean Whether the route was matched or not.
	 * @throws \CodeIgniter\Router\Exceptions\RedirectException
	 */
	protected function checkRoutes(string $uri): bool
	{
		$routes = $this->collection->getRoutes($this->collection->getHTTPVerb());

		$uri = $uri === '/'
			? $uri
			: ltrim($uri, '/ ');

		// Don't waste any time
		if (empty($routes))
		{
			return false;
		}

		// Loop through the route array looking for wildcards
		foreach ($routes as $key => $val)
		{
			$key = $key === '/'
				? $key
				: ltrim($key, '/ ');

			// Are we dealing with a locale?
			if (strpos($key, '{locale}') !== false)
			{
				$localeSegment = array_search('{locale}', preg_split('/[\/]*((^[a-zA-Z0-9])|\(([^()]*)\))*[\/]+/m', $key));

				// Replace it with a regex so it
				// will actually match.
				$key = str_replace('{locale}', '[^/]+', $key);
			}

			// Does the RegEx match?
			if (preg_match('#^' . $key . '$#', $uri, $matches))
			{
				// Store our locale so CodeIgniter object can
				// assign it to the Request.
				if (isset($localeSegment))
				{
					// The following may be inefficient, but doesn't upset NetBeans :-/
					$temp                 = (explode('/', $uri));
					$this->detectedLocale = $temp[$localeSegment];
					unset($localeSegment);
				}

				// Are we using Closures? If so, then we need
				// to collect the params into an array
				// so it can be passed to the controller method later.
				if (! is_string($val) && is_callable($val))
				{
					$this->controller = $val;

					// Remove the original string from the matches array
					array_shift($matches);

					$this->params = $matches;

					$this->matchedRoute = [
						$key,
						$val,
					];

					$this->matchedRouteOptions = $this->collection->getRoutesOptions($key);

					return true;
				}
				// Are we using the default method for back-references?

				// Support resource route when function with subdirectory
				// ex: $routes->resource('Admin/Admins');
				if (strpos($val, '$') !== false && strpos($key, '(') !== false && strpos($key, '/') !== false)
				{
					$replacekey = str_replace('/(.*)', '', $key);
					$val        = preg_replace('#^' . $key . '$#', $val, $uri);
					$val        = str_replace($replacekey, str_replace('/', '\\', $replacekey), $val);
				}
				elseif (strpos($val, '$') !== false && strpos($key, '(') !== false)
				{
					$val = preg_replace('#^' . $key . '$#', $val, $uri);
				}
				elseif (strpos($val, '/') !== false)
				{
					[
						$controller,
						$method,
					] = explode( '::', $val );

					// Only replace slashes in the controller, not in the method.
					$controller = str_replace('/', '\\', $controller);

					$val = $controller . '::' . $method;
				}

				// Is this route supposed to redirect to another?
				if ($this->collection->isRedirect($key))
				{
					throw RedirectException::forUnableToRedirect($val, $this->collection->getRedirectCode($key));
				}

				$this->setRequest(explode('/', $val));

				$this->matchedRoute = [
					$key,
					$val,
				];

				$this->matchedRouteOptions = $this->collection->getRoutesOptions($key);

				return true;
			}
		}

		return false;
	}

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

	/**
	 * Attempts to match a URI path against Controllers and directories
	 * found in APPPATH/Controllers, to find a matching route.
	 *
	 * @param string $uri
	 */
	public function autoRoute(string $uri)
	{
		$segments = explode('/', $uri);

		$segments = $this->validateRequest($segments);

		// If we don't have any segments left - try the default controller;
		// WARNING: Directories get shifted out of the segments array.
		if (empty($segments))
		{
			$this->setDefaultController();
		}
		// If not empty, then the first segment should be the controller
		else
		{
			$this->controller = ucfirst(array_shift($segments));
		}

		// Use the method name if it exists.
		// If it doesn't, no biggie - the default method name
		// has already been set.
		if (! empty($segments))
		{
			$this->method = array_shift($segments);
		}

		if (! empty($segments))
		{
			$this->params = $segments;
		}

		// Load the file so that it's available for CodeIgniter.
		$file = APPPATH . 'Controllers/' . $this->directory . $this->controllerName() . '.php';
		if (is_file($file))
		{
			include_once $file;
		}

		// Ensure the controller stores the fully-qualified class name
		// We have to check for a length over 1, since by default it will be '\'
		if (strpos($this->controller, '\\') === false && strlen($this->collection->getDefaultNamespace()) > 1)
		{
			$this->controller = str_replace('/', '\\', $this->collection->getDefaultNamespace() . $this->directory . $this->controllerName());
		}
	}

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

	/**
	 * Attempts to validate the URI request and determine the controller path.
	 *
	 * @param array $segments URI segments
	 *
	 * @return array URI segments
	 */
	protected function validateRequest(array $segments): array
	{
		$segments = array_filter($segments);
		$segments = array_values($segments);

		$c                  = count($segments);
		$directory_override = isset($this->directory);

		// Loop through our segments and return as soon as a controller
		// is found or when such a directory doesn't exist
		while ($c -- > 0)
		{
			$test = $this->directory . ucfirst($this->translateURIDashes === true ? str_replace('-', '_', $segments[0]) : $segments[0]
			);

			if (! is_file(APPPATH . 'Controllers/' . $test . '.php') && $directory_override === false && is_dir(APPPATH . 'Controllers/' . $this->directory . ucfirst($segments[0])))
			{
				$this->setDirectory(array_shift($segments), true);
				continue;
			}

			return $segments;
		}

		// This means that all segments were actually directories
		return $segments;
	}

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

	/**
	 * Sets the sub-directory that the controller is in.
	 *
	 * @param string|null   $dir
	 * @param boolean|false $append
	 */
	protected function setDirectory(string $dir = null, bool $append = false)
	{
		$dir = ucfirst($dir);

		if ($append !== true || empty($this->directory))
		{
			$this->directory = str_replace('.', '', trim($dir, '/')) . '/';
		}
		else
		{
			$this->directory .= str_replace('.', '', trim($dir, '/')) . '/';
		}
	}

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

	/**
	 * Set request route
	 *
	 * Takes an array of URI segments as input and sets the class/method
	 * to be called.
	 *
	 * @param array $segments URI segments
	 */
	protected function setRequest(array $segments = [])
	{
		// If we don't have any segments - try the default controller;
		if (empty($segments))
		{
			$this->setDefaultController();

			return;
		}

		list($controller, $method) = array_pad(explode('::', $segments[0]), 2, null);

		$this->controller = $controller;

		// $this->method already contains the default method name,
		// so don't overwrite it with emptiness.
		if (! empty($method))
		{
			$this->method = $method;
		}

		array_shift($segments);

		$this->params = $segments;
	}

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

	/**
	 * Sets the default controller based on the info set in the RouteCollection.
	 */
	protected function setDefaultController()
	{
		if (empty($this->controller))
		{
			throw RouterException::forMissingDefaultRoute();
		}

		// Is the method being specified?
		if (sscanf($this->controller, '%[^/]/%s', $class, $this->method) !== 2)
		{
			$this->method = 'index';
		}

		if (! is_file(APPPATH . 'Controllers/' . $this->directory . ucfirst($class) . '.php'))
		{
			return;
		}

		$this->controller = ucfirst($class);

		log_message('info', 'Used the default controller.');
	}

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


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

/**
 * Builder for Postgre
 */
class Builder extends BaseBuilder
{

	/**
	 * ORDER BY random keyword
	 *
	 * @var array
	 */
	protected $randomKeyword = [
		'RANDOM()',
	];

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

	/**
	 * ORDER BY
	 *
	 * @param string  $orderBy
	 * @param string  $direction ASC, DESC or RANDOM
	 * @param boolean $escape
	 *
	 * @return BaseBuilder
	 */
	public function orderBy(string $orderBy, string $direction = '', bool $escape = null)
	{
		$direction = strtoupper(trim($direction));
		if ($direction === 'RANDOM')
		{
			if (! is_float($orderBy) && ctype_digit((string) $orderBy))
			{
				$orderBy = (float) ($orderBy > 1 ? "0.{$orderBy}" : $orderBy);
			}

			if (is_float($orderBy))
			{
				$this->db->simpleQuery("SET SEED {$orderBy}");
			}

			$orderBy   = $this->randomKeyword[0];
			$direction = '';
			$escape    = false;
		}

		return parent::orderBy($orderBy, $direction, $escape);
	}

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

	/**
	 * Increments a numeric column by the specified value.
	 *
	 * @param string  $column
	 * @param integer $value
	 *
	 * @return mixed
	 */
	public function increment(string $column, int $value = 1)
	{
		$column = $this->db->protectIdentifiers($column);

		$sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') + {$value}"]);

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

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

	/**
	 * Decrements a numeric column by the specified value.
	 *
	 * @param string  $column
	 * @param integer $value
	 *
	 * @return mixed
	 */
	public function decrement(string $column, int $value = 1)
	{
		$column = $this->db->protectIdentifiers($column);

		$sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') - {$value}"]);

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

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

	/**
	 * Replace
	 *
	 * Compiles an replace into string and runs the query.
	 * Because PostgreSQL doesn't support the replace into command,
	 * we simply do a DELETE and an INSERT on the first key/value
	 * combo, assuming that it's either the primary key or a unique key.
	 *
	 * @param array   $set       An associative array of insert values
	 * @param boolean $returnSQL
	 *
	 * @return   mixed
	 * @throws   DatabaseException
	 * @internal param true $bool returns the generated SQL, false executes the query.
	 */
	public function replace(array $set = null, bool $returnSQL = false)
	{
		if ($set !== null)
		{
			$this->set($set);
		}

		if (! $this->QBSet)
		{
			if (CI_DEBUG)
			{
				throw new DatabaseException('You must use the "set" method to update an entry.');
			}
			return false;
		}

		$table = $this->QBFrom[0];

		$set = $this->binds;

		// We need to grab out the actual values from
		// the way binds are stored with escape flag.
		array_walk($set, function (&$item) {
			$item = $item[0];
		});

		$keys   = array_keys($set);
		$values = array_values($set);

		$builder = $this->db->table($table);
		$exists  = $builder->where("$keys[0] = $values[0]", null, false)->get()->getFirstRow();

		if (empty($exists))
		{
			$result = $builder->insert($set);
		}
		else
		{
			array_pop($set);
			$result = $builder->update($set, "$keys[0] = $values[0]");
		}

		unset($builder);
		$this->resetWrite();

		return $result;
	}

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

	/**
	 * Delete
	 *
	 * Compiles a delete string and runs the query
	 *
	 * @param mixed   $where
	 * @param integer $limit
	 * @param boolean $reset_data
	 * @param boolean $returnSQL
	 *
	 * @return   mixed
	 * @throws   DatabaseException
	 * @internal param the $mixed where clause
	 * @internal param the $mixed limit clause
	 * @internal param $bool
	 */
	public function delete($where = '', int $limit = null, bool $reset_data = true, bool $returnSQL = false)
	{
		if (! empty($limit) || ! empty($this->QBLimit))
		{
			throw new DatabaseException('PostgreSQL does not allow LIMITs on DELETE queries.');
		}

		return parent::delete($where, $limit, $reset_data, $returnSQL);
	}

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

	/**
	 * LIMIT string
	 *
	 * Generates a platform-specific LIMIT clause.
	 *
	 * @param string $sql SQL Query
	 *
	 * @return string
	 */
	protected function _limit(string $sql): string
	{
		return $sql . ' LIMIT ' . $this->QBLimit . ($this->QBOffset ? " OFFSET {$this->QBOffset}" : '');
	}

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

	/**
	 * Update statement
	 *
	 * Generates a platform-specific update string from the supplied data
	 *
	 * @param string $table
	 * @param array  $values
	 *
	 * @return   string
	 * @throws   DatabaseException
	 * @internal param the $string table name
	 * @internal param the $array update data
	 */
	protected function _update(string $table, array $values): string
	{
		if (! empty($this->QBLimit))
		{
			throw new DatabaseException('Postgres does not support LIMITs with UPDATE queries.');
		}

		$this->QBOrderBy = [];
		return parent::_update($table, $values);
	}

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

	/**
	 * Update_Batch statement
	 *
	 * Generates a platform-specific batch update string from the supplied data
	 *
	 * @param string $table  Table name
	 * @param array  $values Update data
	 * @param string $index  WHERE key
	 *
	 * @return string
	 */
	protected function _updateBatch(string $table, array $values, string $index): string
	{
		$ids = [];
		foreach ($values as $key => $val)
		{
			$ids[] = $val[$index];

			foreach (array_keys($val) as $field)
			{
				if ($field !== $index)
				{
					$final[$field][] = "WHEN {$val[$index]} THEN {$val[$field]}";
				}
			}
		}

		$cases = '';
		foreach ($final as $k => $v)
		{
			$cases .= "{$k} = (CASE {$index}\n"
					. implode("\n", $v)
					. "\nELSE {$k} END), ";
		}

		$this->where("{$index} IN(" . implode(',', $ids) . ')', null, false);

		return "UPDATE {$table} SET " . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere');
	}

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

	/**
	 * Delete statement
	 *
	 * Generates a platform-specific delete string from the supplied data
	 *
	 * @param string $table The table name
	 *
	 * @return string
	 */
	protected function _delete(string $table): string
	{
		$this->QBLimit = false;
		return parent::_delete($table);
	}

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

	/**
	 * Truncate statement
	 *
	 * Generates a platform-specific truncate string from the supplied data
	 *
	 * If the database does not support the truncate() command,
	 * then this method maps to 'DELETE FROM table'
	 *
	 * @param string $table The table name
	 *
	 * @return string
	 */
	protected function _truncate(string $table): string
	{
		return 'TRUNCATE ' . $table . ' RESTART IDENTITY';
	}

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

	/**
	 * Platform independent LIKE statement builder.
	 *
	 * In PostgreSQL, the ILIKE operator will perform case insensitive
	 * searches according to the current locale.
	 *
	 * @see https://www.postgresql.org/docs/9.2/static/functions-matching.html
	 *
	 * @param string    $prefix
	 * @param string    $column
	 * @param string    $not
	 * @param string    $bind
	 * @param boolean   $insensitiveSearch
	 *
	 * @return string     $like_statement
	 */
	public function _like_statement(string $prefix = null, string $column, string $not = null, string $bind, bool $insensitiveSearch = false): string
	{
		$op = $insensitiveSearch === true ? 'ILIKE' : 'LIKE';

		return "{$prefix} {$column} {$not} {$op} :{$bind}:";
	}

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


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

use SplFileInfo;
use CodeIgniter\Files\Exceptions\FileException;
use CodeIgniter\Files\Exceptions\FileNotFoundException;

/**
 * Wrapper for PHP's built-in SplFileInfo, with goodies.
 *
 * @package CodeIgniter\Files
 */
class File extends SplFileInfo
{

	/**
	 * The files size in bytes
	 *
	 * @var float
	 */
	protected $size;

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

	/**
	 * Run our SplFileInfo constructor with an optional verification
	 * that the path is really a file.
	 *
	 * @param string  $path
	 * @param boolean $checkFile
	 */
	public function __construct(string $path, bool $checkFile = false)
	{
		if ($checkFile && ! is_file($path))
		{
			throw FileNotFoundException::forFileNotFound($path);
		}

		parent::__construct($path);
	}

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

	/**
	 * Retrieve the file size.
	 *
	 * Implementations SHOULD return the value stored in the "size" key of
	 * the file in the $_FILES array if available, as PHP calculates this based
	 * on the actual size transmitted.
	 *
	 * @param string $unit The unit to return:
	 *      - b   Bytes
	 *      - kb  Kilobytes
	 *      - mb  Megabytes
	 *
	 * @return integer|null The file size in bytes or null if unknown.
	 */
	public function getSize(string $unit = 'b')
	{
		if (is_null($this->size))
		{
			$this->size = filesize($this->getPathname());
		}

		switch (strtolower($unit))
		{
			case 'kb':
				return number_format($this->size / 1024, 3);
			case 'mb':
				return number_format(($this->size / 1024) / 1024, 3);
		}

		return (int) $this->size;
	}

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

	/**
	 * Attempts to determine the file extension based on the trusted
	 * getType() method. If the mime type is unknown, will return null.
	 *
	 * @return string|null
	 */
	public function guessExtension(): ?string
	{
		return \Config\Mimes::guessExtensionFromType($this->getMimeType());
	}

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

	/**
	 * Retrieve the media type of the file. SHOULD not use information from
	 * the $_FILES array, but should use other methods to more accurately
	 * determine the type of file, like finfo, or mime_content_type().
	 *
	 * @return string|null The media type we determined it to be.
	 */
	public function getMimeType(): string
	{
		$finfo    = finfo_open(FILEINFO_MIME_TYPE);
		$mimeType = finfo_file($finfo, $this->getRealPath());
		finfo_close($finfo);
		return $mimeType;
	}

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

	/**
	 * Generates a random names based on a simple hash and the time, with
	 * the correct file extension attached.
	 *
	 * @return string
	 */
	public function getRandomName(): string
	{
		return time() . '_' . bin2hex(random_bytes(10)) . '.' . $this->getExtension();
	}

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

	/**
	 * Moves a file to a new location.
	 *
	 * @param string      $targetPath
	 * @param string|null $name
	 * @param boolean     $overwrite
	 *
	 * @return \CodeIgniter\Files\File
	 */
	public function move(string $targetPath, string $name = null, bool $overwrite = false)
	{
		$targetPath  = rtrim($targetPath, '/') . '/';
		$name        = $name ?? $this->getBaseName();
		$destination = $overwrite ? $targetPath . $name : $this->getDestination($targetPath . $name);

		$oldName = empty($this->getRealPath()) ? $this->getPath() : $this->getRealPath();

		if (! @rename($oldName, $destination))
		{
			$error = error_get_last();
			throw FileException::forUnableToMove($this->getBasename(), $targetPath, strip_tags($error['message']));
		}

		@chmod($targetPath, 0777 & ~umask());

		return new File($destination);
	}

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

	/**
	 * Returns the destination path for the move operation where overwriting is not expected.
	 *
	 * First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the
	 * last element is an integer as there may be cases that the delimiter may be present in the filename.
	 * For the all other cases, it appends an integer starting from zero before the file's extension.
	 *
	 * @param string  $destination
	 * @param string  $delimiter
	 * @param integer $i
	 *
	 * @return string
	 */
	public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string
	{
		while (is_file($destination))
		{
			$info = pathinfo($destination);
			if (strpos($info['filename'], $delimiter) !== false)
			{
				$parts = explode($delimiter, $info['filename']);
				if (is_numeric(end($parts)))
				{
					$i = end($parts);
					array_pop($parts);
					array_push($parts, ++ $i);
					$destination = $info['dirname'] . '/' . implode($delimiter, $parts) . '.' . $info['extension'];
				}
				else
				{
					$destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++ $i . '.' . $info['extension'];
				}
			}
			else
			{
				$destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++ $i . '.' . $info['extension'];
			}
		}
		return $destination;
	}

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


<?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\Database\Query;

/**
 * Collector for the Database tab of the Debug Toolbar.
 */
class Database extends BaseCollector
{

	/**
	 * Whether this collector has timeline data.
	 *
	 * @var boolean
	 */
	protected $hasTimeline = true;

	/**
	 * Whether this collector should display its own tab.
	 *
	 * @var boolean
	 */
	protected $hasTabContent = true;

	/**
	 * Whether this collector has data for the Vars tab.
	 *
	 * @var boolean
	 */
	protected $hasVarData = false;

	/**
	 * The name used to reference this collector in the toolbar.
	 *
	 * @var string
	 */
	protected $title = 'Database';

	/**
	 * Array of database connections.
	 *
	 * @var array
	 */
	protected $connections;

	/**
	 * The query instances that have been collected
	 * through the DBQuery Event.
	 *
	 * @var array
	 */
	protected static $queries = [];

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

	/**
	 * Constructor
	 */
	public function __construct()
	{
		$this->connections = \Config\Database::getConnections();
	}

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

	/**
	 * The static method used during Events to collect
	 * data.
	 *
	 * @param \CodeIgniter\Database\Query $query
	 *
	 * @internal param $ array \CodeIgniter\Database\Query
	 */
	public static function collect(Query $query)
	{
		$config = config('Toolbar');

		// Provide default in case it's not set
		$max = $config->maxQueries ?: 100;

		if (count(static::$queries) < $max)
		{
			static::$queries[] = $query;
		}
	}

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

	/**
	 * Returns timeline data formatted for the toolbar.
	 *
	 * @return array The formatted data or an empty array.
	 */
	protected function formatTimelineData(): array
	{
		$data = [];

		foreach ($this->connections as $alias => $connection)
		{
			// Connection Time
			$data[] = [
				'name'      => 'Connecting to Database: "' . $alias . '"',
				'component' => 'Database',
				'start'     => $connection->getConnectStart(),
				'duration'  => $connection->getConnectDuration(),
			];
		}

		foreach (static::$queries as $query)
		{
			$data[] = [
				'name'      => 'Query',
				'component' => 'Database',
				'start'     => $query->getStartTime(true),
				'duration'  => $query->getDuration(),
			];
		}

		return $data;
	}

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

	/**
	 * Returns the data of this collector to be formatted in the toolbar
	 *
	 * @return array
	 */
	public function display(): array
	{
		// Key words we want bolded
		$highlight = [
			'SELECT',
			'DISTINCT',
			'FROM',
			'WHERE',
			'AND',
			'LEFT JOIN',
			'ORDER BY',
			'GROUP BY',
			'LIMIT',
			'INSERT',
			'INTO',
			'VALUES',
			'UPDATE',
			'OR ',
			'HAVING',
			'OFFSET',
			'NOT IN',
			'IN',
			'LIKE',
			'NOT LIKE',
			'COUNT',
			'MAX',
			'MIN',
			'ON',
			'AS',
			'AVG',
			'SUM',
			'(',
			')',
		];

		$data = [
			'queries' => [],
		];

		foreach (static::$queries as $query)
		{
			$sql = $query->getQuery();

			foreach ($highlight as $term)
			{
				$sql = str_replace($term, "<strong>{$term}</strong>", $sql);
			}

			$data['queries'][] = [
				'duration' => ($query->getDuration(5) * 1000) . ' ms',
				'sql'      => $sql,
			];
		}

		return $data;
	}

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

	/**
	 * Gets the "badge" value for the button.
	 *
	 * @return integer
	 */
	public function getBadgeValue(): int
	{
		return count(static::$queries);
	}

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

	/**
	 * Information to be displayed next to the title.
	 *
	 * @return string The number of queries (in parentheses) or an empty string.
	 */
	public function getTitleDetails(): string
	{
		return '(' . count(static::$queries) . ' Queries across ' . ($countConnection = count($this->connections)) . ' Connection' .
				($countConnection > 1 ? 's' : '') . ')';
	}

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

	/**
	 * Does this collector have any data collected?
	 *
	 * @return boolean
	 */
	public function isEmpty(): bool
	{
		return empty(static::$queries);
	}

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

	/**
	 * Display the icon.
	 *
	 * Icon from https://icons8.com - 1em package
	 *
	 * @return string
	 */
	public function icon(): string
	{
		return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADMSURBVEhLY6A3YExLSwsA4nIycQDIDIhRWEBqamo/UNF/SjDQjF6ocZgAKPkRiFeEhoYyQ4WIBiA9QAuWAPEHqBAmgLqgHcolGQD1V4DMgHIxwbCxYD+QBqcKINseKo6eWrBioPrtQBq/BcgY5ht0cUIYbBg2AJKkRxCNWkDQgtFUNJwtABr+F6igE8olGQD114HMgHIxAVDyAhA/AlpSA8RYUwoeXAPVex5qHCbIyMgwBCkAuQJIY00huDBUz/mUlBQDqHGjgBjAwAAACexpph6oHSQAAAAASUVORK5CYII=';
	}

}


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

/**
 * Class FileLocator
 *
 * Allows loading non-class files in a namespaced manner.
 * Works with Helpers, Views, etc.
 *
 * @package CodeIgniter
 */
class FileLocator
{
	/**
	 * @var \CodeIgniter\Autoloader\Autoloader
	 */
	protected $autoloader;

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

	/**
	 * Constructor
	 *
	 * @param Autoloader $autoloader
	 */
	public function __construct(Autoloader $autoloader)
	{
		$this->autoloader = $autoloader;
	}

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

	/**
	 * Attempts to locate a file by examining the name for a namespace
	 * and looking through the PSR-4 namespaced files that we know about.
	 *
	 * @param string $file   The namespaced file to locate
	 * @param string $folder The folder within the namespace that we should look for the file.
	 * @param string $ext    The file extension the file should have.
	 *
	 * @return string|false The path to the file, or false if not found.
	 */
	public function locateFile(string $file, string $folder = null, string $ext = 'php')
	{
		$file = $this->ensureExt($file, $ext);

		// Clears the folder name if it is at the beginning of the filename
		if (! empty($folder) && ($pos = strpos($file, $folder)) === 0)
		{
			$file = substr($file, strlen($folder . '/'));
		}

		// Is not namespaced? Try the application folder.
		if (strpos($file, '\\') === false)
		{
			return $this->legacyLocate($file, $folder);
		}

		// Standardize slashes to handle nested directories.
		$file = strtr($file, '/', '\\');

		$segments = explode('\\', $file);

		// The first segment will be empty if a slash started the filename.
		if (empty($segments[0]))
		{
			unset($segments[0]);
		}

		$path     = '';
		$prefix   = '';
		$filename = '';

		// Namespaces always comes with arrays of paths
		$namespaces = $this->autoloader->getNamespace();

		while (! empty($segments))
		{
			$prefix .= empty($prefix)
				? ucfirst(array_shift($segments))
				: '\\' . ucfirst(array_shift($segments));

			if (empty($namespaces[$prefix]))
			{
				continue;
			}
			$path = $this->getNamespaces($prefix);

			$filename = implode('/', $segments);
			break;
		}

		// IF we have a folder name, then the calling function
		// expects this file to be within that folder, like 'Views',
		// or 'libraries'.
		if (! empty($folder) && strpos($path . $filename, '/' . $folder . '/') === false)
		{
			$filename = $folder . '/' . $filename;
		}

		$path .= $filename;

		return is_file($path) ? $path : false;
	}

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

	/**
	 * Examines a file and returns the fully qualified domain name.
	 *
	 * @param string $file
	 *
	 * @return string
	 */
	public function getClassname(string $file) : string
	{
		$php        = file_get_contents($file);
		$tokens     = token_get_all($php);
		$count      = count($tokens);
		$dlm        = false;
		$namespace  = '';
		$class_name = '';

		for ($i = 2; $i < $count; $i++)
		{
			if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === 'phpnamespace' || $tokens[$i - 2][1] === 'namespace')) || ($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $tokens[$i][0] === T_STRING))
			{
				if (! $dlm)
				{
					$namespace = 0;
				}
				if (isset($tokens[$i][1]))
				{
					$namespace = $namespace ? $namespace . '\\' . $tokens[$i][1] : $tokens[$i][1];
					$dlm       = true;
				}
			}
			elseif ($dlm && ($tokens[$i][0] !== T_NS_SEPARATOR) && ($tokens[$i][0] !== T_STRING))
			{
				$dlm = false;
			}
			if (($tokens[$i - 2][0] === T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] === 'phpclass'))
				&& $tokens[$i - 1][0] === T_WHITESPACE
				&& $tokens[$i][0] === T_STRING)
			{
				$class_name = $tokens[$i][1];
				break;
			}
		}

		if (empty( $class_name ))
		{
			return '';
		}

		return $namespace . '\\' . $class_name;
	}

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

	/**
	 * Searches through all of the defined namespaces looking for a file.
	 * Returns an array of all found locations for the defined file.
	 *
	 * Example:
	 *
	 *  $locator->search('Config/Routes.php');
	 *  // Assuming PSR4 namespaces include foo and bar, might return:
	 *  [
	 *      'app/Modules/foo/Config/Routes.php',
	 *      'app/Modules/bar/Config/Routes.php',
	 *  ]
	 *
	 * @param string $path
	 * @param string $ext
	 *
	 * @return array
	 */
	public function search(string $path, string $ext = 'php'): array
	{
		$path = $this->ensureExt($path, $ext);

		$foundPaths = [];

		foreach ($this->getNamespaces() as $namespace)
		{
			if (is_file($namespace['path'] . $path))
			{
				$foundPaths[] = $namespace['path'] . $path;
			}
		}

		// Remove any duplicates
		$foundPaths = array_unique($foundPaths);

		return $foundPaths;
	}

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

	/**
	 * Ensures a extension is at the end of a filename
	 *
	 * @param string $path
	 * @param string $ext
	 *
	 * @return string
	 */
	protected function ensureExt(string $path, string $ext): string
	{
		if ($ext)
		{
			$ext = '.' . $ext;

			if (substr($path, -strlen($ext)) !== $ext)
			{
				$path .= $ext;
			}
		}

		return $path;
	}

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

	/**
	 * @param string|null $prefix
	 *
	 * @return array|string
	 */
	protected function getNamespaces(string $prefix = null)
	{
		if ($prefix)
		{
			$path = $this->autoloader->getNamespace($prefix);

			return isset($path[0])
				? rtrim($path[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR
				: '';
		}

		$namespaces = [];

		foreach ($this->autoloader->getNamespace() as $prefix => $paths)
		{
			foreach ($paths as $path)
			{
				$namespaces[] = [
					'prefix' => $prefix,
					'path'   => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
				];
			}
		}

		return $namespaces;
	}

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

	/**
	 * Find the qualified name of a file according to
	 * the namespace of the first matched namespace path.
	 *
	 * @param string $path
	 *
	 * @return string|false The qualified name or false if the path is not found
	 */
	public function findQualifiedNameFromPath(string $path)
	{
		$path = realpath($path);

		if (! $path)
		{
			return false;
		}

		foreach ($this->getNamespaces() as $namespace)
		{
			$namespace['path'] = realpath($namespace['path']);

			if (empty($namespace['path']))
			{
				continue;
			}

			if (mb_strpos($path, $namespace['path']) === 0)
			{
				$className = '\\' . $namespace['prefix'] . '\\' .
						ltrim(str_replace('/', '\\', mb_substr(
							$path, mb_strlen($namespace['path']))
						), '\\');
				// Remove the file extension (.php)
				$className = mb_substr($className, 0, -4);

				return $className;
			}
		}

		return false;
	}

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

	/**
	 * Scans the defined namespaces, returning a list of all files
	 * that are contained within the subpath specified by $path.
	 *
	 * @param string $path
	 *
	 * @return array
	 */
	public function listFiles(string $path): array
	{
		if (empty($path))
		{
			return [];
		}

		$files = [];
		helper('filesystem');

		foreach ($this->getNamespaces() as $namespace)
		{
			$fullPath = realpath($namespace['path'] . $path);

			if (! is_dir($fullPath))
			{
				continue;
			}

			$tempFiles = get_filenames($fullPath, true);

			if (! empty($tempFiles))
			{
				$files = array_merge($files, $tempFiles);
			}
		}

		return $files;
	}

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

	/**
	 * Scans the provided namespace, returning a list of all files
	 * that are contained within the subpath specified by $path.
	 *
	 * @param string $prefix
	 * @param string $path
	 *
	 * @return array
	 */
	public function listNamespaceFiles(string $prefix, string $path): array
	{
		if (empty($path) || empty($prefix))
		{
			return [];
		}

		$files = [];
		helper('filesystem');

		// autoloader->getNamespace($prefix) returns an array of paths for that namespace
		foreach ($this->autoloader->getNamespace($prefix) as $namespacePath)
		{
			$fullPath = realpath($namespacePath . $path);

			if (! is_dir($fullPath))
			{
				continue;
			}

			$tempFiles = get_filenames($fullPath, true);

			if (! empty($tempFiles))
			{
				$files = array_merge($files, $tempFiles);
			}
		}

		return $files;
	}

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

	/**
	 * Checks the application folder to see if the file can be found.
	 * Only for use with filenames that DO NOT include namespacing.
	 *
	 * @param string      $file
	 * @param string|null $folder
	 *
	 * @return string|false The path to the file, or false if not found.
	 */
	protected function legacyLocate(string $file, string $folder = null)
	{
		$paths = [
			APPPATH,
			SYSTEMPATH,
		];

		foreach ($paths as $path)
		{
			$path .= empty($folder) ? $file : $folder . '/' . $file;

			if (is_file($path))
			{
				return $path;
			}
		}

		return false;
	}
}