add trashes

This commit is contained in:
Diskette Guy
2025-07-21 15:40:51 +07:00
parent c8f2944770
commit 07171f5b5a
848 changed files with 134166 additions and 0 deletions
@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use Laminas\Escaper\Escaper;
use Laminas\Escaper\Exception\ExceptionInterface;
use Laminas\Escaper\Exception\InvalidArgumentException as EscaperInvalidArgumentException;
use Laminas\Escaper\Exception\RuntimeException;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
/**
* AUTOLOADER CONFIGURATION
*
* This file defines the namespaces and class maps so the Autoloader
* can find the files as needed.
*/
class AutoloadConfig
{
/**
* -------------------------------------------------------------------
* Namespaces
* -------------------------------------------------------------------
* This maps the locations of any namespaces in your application to
* their location on the file system. These are used by the autoloader
* to locate files the first time they have been instantiated.
*
* The '/app' and '/system' directories are already mapped for you.
* you may change the name of the 'App' namespace if you wish,
* but this should be done prior to creating any namespaced classes,
* else you will need to modify all of those classes for this to work.
*
* @var array<string, list<string>|string>
*/
public $psr4 = [];
/**
* -------------------------------------------------------------------
* Class Map
* -------------------------------------------------------------------
* The class map provides a map of class names and their exact
* location on the drive. Classes loaded in this manner will have
* slightly faster performance because they will not have to be
* searched for within one or more directories as they would if they
* were being autoloaded through a namespace.
*
* @var array<string, string>
*/
public $classmap = [];
/**
* -------------------------------------------------------------------
* Files
* -------------------------------------------------------------------
* The files array provides a list of paths to __non-class__ files
* that will be autoloaded. This can be useful for bootstrap operations
* or for loading functions.
*
* @var list<string>
*/
public $files = [];
/**
* -------------------------------------------------------------------
* Namespaces
* -------------------------------------------------------------------
* This maps the locations of any namespaces in your application to
* their location on the file system. These are used by the autoloader
* to locate files the first time they have been instantiated.
*
* Do not change the name of the CodeIgniter namespace or your application
* will break.
*
* @var array<string, string>
*/
protected $corePsr4 = [
'CodeIgniter' => SYSTEMPATH,
'Config' => APPPATH . 'Config',
];
/**
* -------------------------------------------------------------------
* Class Map
* -------------------------------------------------------------------
* The class map provides a map of class names and their exact
* location on the drive. Classes loaded in this manner will have
* slightly faster performance because they will not have to be
* searched for within one or more directories as they would if they
* were being autoloaded through a namespace.
*
* @var array<class-string, string>
*/
protected $coreClassmap = [
AbstractLogger::class => SYSTEMPATH . 'ThirdParty/PSR/Log/AbstractLogger.php',
InvalidArgumentException::class => SYSTEMPATH . 'ThirdParty/PSR/Log/InvalidArgumentException.php',
LoggerAwareInterface::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareInterface.php',
LoggerAwareTrait::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareTrait.php',
LoggerInterface::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerInterface.php',
LoggerTrait::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerTrait.php',
LogLevel::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LogLevel.php',
NullLogger::class => SYSTEMPATH . 'ThirdParty/PSR/Log/NullLogger.php',
ExceptionInterface::class => SYSTEMPATH . 'ThirdParty/Escaper/Exception/ExceptionInterface.php',
EscaperInvalidArgumentException::class => SYSTEMPATH . 'ThirdParty/Escaper/Exception/InvalidArgumentException.php',
RuntimeException::class => SYSTEMPATH . 'ThirdParty/Escaper/Exception/RuntimeException.php',
Escaper::class => SYSTEMPATH . 'ThirdParty/Escaper/Escaper.php',
];
/**
* -------------------------------------------------------------------
* Core Files
* -------------------------------------------------------------------
* List of files from the framework to be autoloaded early.
*
* @var array<int, string>
*/
protected $coreFiles = [];
/**
* Constructor.
*
* Merge the built-in and developer-configured psr4 and classmap,
* with preference to the developer ones.
*/
public function __construct()
{
if (isset($_SERVER['CI_ENVIRONMENT']) && $_SERVER['CI_ENVIRONMENT'] === 'testing') {
$this->psr4['Tests\Support'] = SUPPORTPATH;
$this->classmap['CodeIgniter\Log\TestLogger'] = SYSTEMPATH . 'Test/TestLogger.php';
$this->classmap['CIDatabaseTestCase'] = SYSTEMPATH . 'Test/CIDatabaseTestCase.php';
}
$this->psr4 = array_merge($this->corePsr4, $this->psr4);
$this->classmap = array_merge($this->coreClassmap, $this->classmap);
$this->files = [...$this->coreFiles, ...$this->files];
}
}
@@ -0,0 +1,301 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use CodeIgniter\Autoloader\FileLocatorInterface;
use CodeIgniter\Exceptions\ConfigException;
use CodeIgniter\Exceptions\RuntimeException;
use Config\Encryption;
use Config\Modules;
use ReflectionClass;
use ReflectionException;
/**
* Class BaseConfig
*
* Not intended to be used on its own, this class will attempt to
* automatically populate the child class' properties with values
* from the environment.
*
* These can be set within the .env file.
*
* @phpstan-consistent-constructor
* @see \CodeIgniter\Config\BaseConfigTest
*/
class BaseConfig
{
/**
* An optional array of classes that will act as Registrars
* for rapidly setting config class properties.
*
* @var array
*/
public static $registrars = [];
/**
* Whether to override properties by Env vars and Registrars.
*/
public static bool $override = true;
/**
* Has module discovery completed?
*
* @var bool
*/
protected static $didDiscovery = false;
/**
* Is module discovery running or not?
*/
protected static bool $discovering = false;
/**
* The processing Registrar file for error message.
*/
protected static string $registrarFile = '';
/**
* The modules configuration.
*
* @var Modules|null
*/
protected static $moduleConfig;
public static function __set_state(array $array)
{
static::$override = false;
$obj = new static();
static::$override = true;
$properties = array_keys(get_object_vars($obj));
foreach ($properties as $property) {
$obj->{$property} = $array[$property];
}
return $obj;
}
/**
* @internal For testing purposes only.
* @testTag
*/
public static function setModules(Modules $modules): void
{
static::$moduleConfig = $modules;
}
/**
* @internal For testing purposes only.
* @testTag
*/
public static function reset(): void
{
static::$registrars = [];
static::$override = true;
static::$didDiscovery = false;
static::$moduleConfig = null;
}
/**
* Will attempt to get environment variables with names
* that match the properties of the child class.
*
* The "shortPrefix" is the lowercase-only config class name.
*/
public function __construct()
{
static::$moduleConfig ??= new Modules();
if (! static::$override) {
return;
}
$this->registerProperties();
$properties = array_keys(get_object_vars($this));
$prefix = static::class;
$slashAt = strrpos($prefix, '\\');
$shortPrefix = strtolower(substr($prefix, $slashAt === false ? 0 : $slashAt + 1));
foreach ($properties as $property) {
$this->initEnvValue($this->{$property}, $property, $prefix, $shortPrefix);
if ($this instanceof Encryption && $property === 'key') {
if (str_starts_with($this->{$property}, 'hex2bin:')) {
// Handle hex2bin prefix
$this->{$property} = hex2bin(substr($this->{$property}, 8));
} elseif (str_starts_with($this->{$property}, 'base64:')) {
// Handle base64 prefix
$this->{$property} = base64_decode(substr($this->{$property}, 7), true);
}
}
}
}
/**
* Initialization an environment-specific configuration setting
*
* @param array|bool|float|int|string|null $property
*
* @return void
*/
protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix)
{
if (is_array($property)) {
foreach (array_keys($property) as $key) {
$this->initEnvValue($property[$key], "{$name}.{$key}", $prefix, $shortPrefix);
}
} elseif (($value = $this->getEnvValue($name, $prefix, $shortPrefix)) !== false && $value !== null) {
if ($value === 'false') {
$value = false;
} elseif ($value === 'true') {
$value = true;
}
if (is_bool($value)) {
$property = $value;
return;
}
$value = trim($value, '\'"');
if (is_int($property)) {
$value = (int) $value;
} elseif (is_float($property)) {
$value = (float) $value;
}
// If the default value of the property is `null` and the type is not
// `string`, TypeError will happen.
// So cannot set `declare(strict_types=1)` in this file.
$property = $value;
}
}
/**
* Retrieve an environment-specific configuration setting
*
* @return string|null
*/
protected function getEnvValue(string $property, string $prefix, string $shortPrefix)
{
$shortPrefix = ltrim($shortPrefix, '\\');
$underscoreProperty = str_replace('.', '_', $property);
switch (true) {
case array_key_exists("{$shortPrefix}.{$property}", $_ENV):
return $_ENV["{$shortPrefix}.{$property}"];
case array_key_exists("{$shortPrefix}_{$underscoreProperty}", $_ENV):
return $_ENV["{$shortPrefix}_{$underscoreProperty}"];
case array_key_exists("{$shortPrefix}.{$property}", $_SERVER):
return $_SERVER["{$shortPrefix}.{$property}"];
case array_key_exists("{$shortPrefix}_{$underscoreProperty}", $_SERVER):
return $_SERVER["{$shortPrefix}_{$underscoreProperty}"];
case array_key_exists("{$prefix}.{$property}", $_ENV):
return $_ENV["{$prefix}.{$property}"];
case array_key_exists("{$prefix}_{$underscoreProperty}", $_ENV):
return $_ENV["{$prefix}_{$underscoreProperty}"];
case array_key_exists("{$prefix}.{$property}", $_SERVER):
return $_SERVER["{$prefix}.{$property}"];
case array_key_exists("{$prefix}_{$underscoreProperty}", $_SERVER):
return $_SERVER["{$prefix}_{$underscoreProperty}"];
default:
$value = getenv("{$shortPrefix}.{$property}");
$value = $value === false ? getenv("{$shortPrefix}_{$underscoreProperty}") : $value;
$value = $value === false ? getenv("{$prefix}.{$property}") : $value;
$value = $value === false ? getenv("{$prefix}_{$underscoreProperty}") : $value;
return $value === false ? null : $value;
}
}
/**
* Provides external libraries a simple way to register one or more
* options into a config file.
*
* @return void
*
* @throws ReflectionException
*/
protected function registerProperties()
{
if (! static::$moduleConfig->shouldDiscover('registrars')) {
return;
}
if (! static::$didDiscovery) {
// Discovery must be completed before the first instantiation of any Config class.
if (static::$discovering) {
throw new ConfigException(
'During Auto-Discovery of Registrars,'
. ' "' . static::class . '" executes Auto-Discovery again.'
. ' "' . clean_path(static::$registrarFile) . '" seems to have bad code.',
);
}
static::$discovering = true;
/** @var FileLocatorInterface */
$locator = service('locator');
$registrarsFiles = $locator->search('Config/Registrar.php');
foreach ($registrarsFiles as $file) {
// Saves the file for error message.
static::$registrarFile = $file;
$className = $locator->findQualifiedNameFromPath($file);
if ($className === false) {
continue;
}
static::$registrars[] = new $className();
}
static::$didDiscovery = true;
static::$discovering = false;
}
$shortName = (new ReflectionClass($this))->getShortName();
// Check the registrar class for a method named after this class' shortName
foreach (static::$registrars as $callable) {
// ignore non-applicable registrars
if (! method_exists($callable, $shortName)) {
continue; // @codeCoverageIgnore
}
$properties = $callable::$shortName();
if (! is_array($properties)) {
throw new RuntimeException('Registrars must return an array of properties and their values.');
}
foreach ($properties as $property => $value) {
if (isset($this->{$property}) && is_array($this->{$property}) && is_array($value)) {
$this->{$property} = array_merge($this->{$property}, $value);
} else {
$this->{$property} = $value;
}
}
}
}
}
@@ -0,0 +1,432 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use CodeIgniter\Autoloader\Autoloader;
use CodeIgniter\Autoloader\FileLocator;
use CodeIgniter\Autoloader\FileLocatorCached;
use CodeIgniter\Autoloader\FileLocatorInterface;
use CodeIgniter\Cache\CacheInterface;
use CodeIgniter\Cache\ResponseCache;
use CodeIgniter\CLI\Commands;
use CodeIgniter\CodeIgniter;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Database\MigrationRunner;
use CodeIgniter\Debug\Exceptions;
use CodeIgniter\Debug\Iterator;
use CodeIgniter\Debug\Timer;
use CodeIgniter\Debug\Toolbar;
use CodeIgniter\Email\Email;
use CodeIgniter\Encryption\EncrypterInterface;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Filters\Filters;
use CodeIgniter\Format\Format;
use CodeIgniter\Honeypot\Honeypot;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\ContentSecurityPolicy;
use CodeIgniter\HTTP\CURLRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\Negotiate;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Request;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\SiteURIFactory;
use CodeIgniter\HTTP\URI;
use CodeIgniter\Images\Handlers\BaseHandler;
use CodeIgniter\Language\Language;
use CodeIgniter\Log\Logger;
use CodeIgniter\Pager\Pager;
use CodeIgniter\Router\RouteCollection;
use CodeIgniter\Router\RouteCollectionInterface;
use CodeIgniter\Router\Router;
use CodeIgniter\Security\Security;
use CodeIgniter\Session\Session;
use CodeIgniter\Superglobals;
use CodeIgniter\Throttle\Throttler;
use CodeIgniter\Typography\Typography;
use CodeIgniter\Validation\ValidationInterface;
use CodeIgniter\View\Cell;
use CodeIgniter\View\Parser;
use CodeIgniter\View\RendererInterface;
use CodeIgniter\View\View;
use Config\App;
use Config\Autoload;
use Config\Cache;
use Config\ContentSecurityPolicy as CSPConfig;
use Config\Encryption;
use Config\Exceptions as ConfigExceptions;
use Config\Filters as ConfigFilters;
use Config\Format as ConfigFormat;
use Config\Honeypot as ConfigHoneyPot;
use Config\Images;
use Config\Migrations;
use Config\Modules;
use Config\Optimize;
use Config\Pager as ConfigPager;
use Config\Services as AppServices;
use Config\Session as ConfigSession;
use Config\Toolbar as ConfigToolbar;
use Config\Validation as ConfigValidation;
use Config\View as ConfigView;
/**
* Services Configuration file.
*
* Services are simply other classes/libraries that the system uses
* to do its job. This is used by CodeIgniter to allow the core of the
* framework to be swapped out easily without affecting the usage within
* the rest of your application.
*
* This is used in place of a Dependency Injection container primarily
* due to its simplicity, which allows a better long-term maintenance
* of the applications built on top of CodeIgniter. A bonus side-effect
* is that IDEs are able to determine what class you are calling
* whereas with DI Containers there usually isn't a way for them to do this.
*
* Warning: To allow overrides by service providers do not use static calls,
* instead call out to \Config\Services (imported as AppServices).
*
* @see http://blog.ircmaxell.com/2015/11/simple-easy-risk-and-change.html
* @see http://www.infoq.com/presentations/Simple-Made-Easy
*
* @method static CacheInterface cache(Cache $config = null, $getShared = true)
* @method static CLIRequest clirequest(App $config = null, $getShared = true)
* @method static CodeIgniter codeigniter(App $config = null, $getShared = true)
* @method static Commands commands($getShared = true)
* @method static void createRequest(App $config, bool $isCli = false)
* @method static ContentSecurityPolicy csp(CSPConfig $config = null, $getShared = true)
* @method static CURLRequest curlrequest($options = [], ResponseInterface $response = null, App $config = null, $getShared = true)
* @method static Email email($config = null, $getShared = true)
* @method static EncrypterInterface encrypter(Encryption $config = null, $getShared = false)
* @method static Exceptions exceptions(ConfigExceptions $config = null, $getShared = true)
* @method static Filters filters(ConfigFilters $config = null, $getShared = true)
* @method static Format format(ConfigFormat $config = null, $getShared = true)
* @method static Honeypot honeypot(ConfigHoneyPot $config = null, $getShared = true)
* @method static BaseHandler image($handler = null, Images $config = null, $getShared = true)
* @method static IncomingRequest incomingrequest(?App $config = null, bool $getShared = true)
* @method static Iterator iterator($getShared = true)
* @method static Language language($locale = null, $getShared = true)
* @method static Logger logger($getShared = true)
* @method static MigrationRunner migrations(Migrations $config = null, ConnectionInterface $db = null, $getShared = true)
* @method static Negotiate negotiator(RequestInterface $request = null, $getShared = true)
* @method static Pager pager(ConfigPager $config = null, RendererInterface $view = null, $getShared = true)
* @method static Parser parser($viewPath = null, ConfigView $config = null, $getShared = true)
* @method static RedirectResponse redirectresponse(App $config = null, $getShared = true)
* @method static View renderer($viewPath = null, ConfigView $config = null, $getShared = true)
* @method static IncomingRequest|CLIRequest request(App $config = null, $getShared = true)
* @method static ResponseInterface response(App $config = null, $getShared = true)
* @method static ResponseCache responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true)
* @method static Router router(RouteCollectionInterface $routes = null, Request $request = null, $getShared = true)
* @method static RouteCollection routes($getShared = true)
* @method static Security security(App $config = null, $getShared = true)
* @method static Session session(ConfigSession $config = null, $getShared = true)
* @method static SiteURIFactory siteurifactory(App $config = null, Superglobals $superglobals = null, $getShared = true)
* @method static Superglobals superglobals(array $server = null, array $get = null, bool $getShared = true)
* @method static Throttler throttler($getShared = true)
* @method static Timer timer($getShared = true)
* @method static Toolbar toolbar(ConfigToolbar $config = null, $getShared = true)
* @method static Typography typography($getShared = true)
* @method static URI uri($uri = null, $getShared = true)
* @method static ValidationInterface validation(ConfigValidation $config = null, $getShared = true)
* @method static Cell viewcell($getShared = true)
*/
class BaseService
{
/**
* Cache for instance of any services that
* have been requested as a "shared" instance.
* Keys should be lowercase service names.
*
* @var array<string, object> [key => instance]
*/
protected static $instances = [];
/**
* Factory method list.
*
* @var array<string, (callable(mixed ...$params): object)> [key => callable]
*/
protected static array $factories = [];
/**
* Mock objects for testing which are returned if exist.
*
* @var array<string, object> [key => instance]
*/
protected static $mocks = [];
/**
* Have we already discovered other Services?
*
* @var bool
*/
protected static $discovered = false;
/**
* A cache of other service classes we've found.
*
* @var array
*
* @deprecated 4.5.0 No longer used.
*/
protected static $services = [];
/**
* A cache of the names of services classes found.
*
* @var list<string>
*/
private static array $serviceNames = [];
/**
* Simple method to get an entry fast.
*
* @param string $key Identifier of the entry to look for.
*
* @return object|null Entry.
*/
public static function get(string $key): ?object
{
return static::$instances[$key] ?? static::__callStatic($key, []);
}
/**
* Sets an entry.
*
* @param string $key Identifier of the entry.
*/
public static function set(string $key, object $value): void
{
if (isset(static::$instances[$key])) {
throw new InvalidArgumentException('The entry for "' . $key . '" is already set.');
}
static::$instances[$key] = $value;
}
/**
* Overrides an existing entry.
*
* @param string $key Identifier of the entry.
*/
public static function override(string $key, object $value): void
{
static::$instances[$key] = $value;
}
/**
* Returns a shared instance of any of the class' services.
*
* $key must be a name matching a service.
*
* @param array|bool|float|int|object|string|null ...$params
*
* @return object
*/
protected static function getSharedInstance(string $key, ...$params)
{
$key = strtolower($key);
// Returns mock if exists
if (isset(static::$mocks[$key])) {
return static::$mocks[$key];
}
if (! isset(static::$instances[$key])) {
// Make sure $getShared is false
$params[] = false;
static::$instances[$key] = AppServices::$key(...$params);
}
return static::$instances[$key];
}
/**
* The Autoloader class is the central class that handles our
* spl_autoload_register method, and helper methods.
*
* @return Autoloader
*/
public static function autoloader(bool $getShared = true)
{
if ($getShared) {
if (empty(static::$instances['autoloader'])) {
static::$instances['autoloader'] = new Autoloader();
}
return static::$instances['autoloader'];
}
return new Autoloader();
}
/**
* The file locator provides utility methods for looking for non-classes
* within namespaced folders, as well as convenience methods for
* loading 'helpers', and 'libraries'.
*
* @return FileLocatorInterface
*/
public static function locator(bool $getShared = true)
{
if ($getShared) {
if (empty(static::$instances['locator'])) {
$cacheEnabled = class_exists(Optimize::class)
&& (new Optimize())->locatorCacheEnabled;
if ($cacheEnabled) {
static::$instances['locator'] = new FileLocatorCached(new FileLocator(static::autoloader()));
} else {
static::$instances['locator'] = new FileLocator(static::autoloader());
}
}
return static::$mocks['locator'] ?? static::$instances['locator'];
}
return new FileLocator(static::autoloader());
}
/**
* Provides the ability to perform case-insensitive calling of service
* names.
*
* @return object|null
*/
public static function __callStatic(string $name, array $arguments)
{
if (isset(static::$factories[$name])) {
return static::$factories[$name](...$arguments);
}
$service = static::serviceExists($name);
if ($service === null) {
return null;
}
return $service::$name(...$arguments);
}
/**
* Check if the requested service is defined and return the declaring
* class. Return null if not found.
*/
public static function serviceExists(string $name): ?string
{
static::buildServicesCache();
$services = array_merge(self::$serviceNames, [Services::class]);
$name = strtolower($name);
foreach ($services as $service) {
if (method_exists($service, $name)) {
static::$factories[$name] = [$service, $name];
return $service;
}
}
return null;
}
/**
* Reset shared instances and mocks for testing.
*
* @return void
*
* @testTag only available to test code
*/
public static function reset(bool $initAutoloader = true)
{
static::$mocks = [];
static::$instances = [];
static::$factories = [];
if ($initAutoloader) {
static::autoloader()->initialize(new Autoload(), new Modules());
}
}
/**
* Resets any mock and shared instances for a single service.
*
* @return void
*
* @testTag only available to test code
*/
public static function resetSingle(string $name)
{
$name = strtolower($name);
unset(static::$mocks[$name], static::$instances[$name]);
}
/**
* Inject mock object for testing.
*
* @param object $mock
*
* @return void
*
* @testTag only available to test code
*/
public static function injectMock(string $name, $mock)
{
static::$instances[$name] = $mock;
static::$mocks[strtolower($name)] = $mock;
}
/**
* Resets the service cache.
*/
public static function resetServicesCache(): void
{
self::$serviceNames = [];
static::$discovered = false;
}
protected static function buildServicesCache(): void
{
if (! static::$discovered) {
if ((new Modules())->shouldDiscover('services')) {
$locator = static::locator();
$files = $locator->search('Config/Services');
$systemPath = static::autoloader()->getNamespace('CodeIgniter')[0];
// Get instances of all service classes and cache them locally.
foreach ($files as $file) {
// Does not search `CodeIgniter` namespace to prevent from loading twice.
if (str_starts_with($file, $systemPath)) {
continue;
}
$classname = $locator->findQualifiedNameFromPath($file);
if ($classname === false) {
continue;
}
if ($classname !== Services::class) {
self::$serviceNames[] = $classname;
}
}
}
static::$discovered = true;
}
}
}
@@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use CodeIgniter\Exceptions\InvalidArgumentException;
/**
* Environment-specific configuration
*
* @see \CodeIgniter\Config\DotEnvTest
*/
class DotEnv
{
/**
* The directory where the .env file can be located.
*
* @var string
*/
protected $path;
/**
* Builds the path to our file.
*/
public function __construct(string $path, string $file = '.env')
{
$this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
}
/**
* The main entry point, will load the .env file and process it
* so that we end up with all settings in the PHP environment vars
* (i.e. getenv(), $_ENV, and $_SERVER)
*/
public function load(): bool
{
$vars = $this->parse();
return $vars !== null;
}
/**
* Parse the .env file into an array of key => value
*/
public function parse(): ?array
{
// We don't want to enforce the presence of a .env file, they should be optional.
if (! is_file($this->path)) {
return null;
}
// Ensure the file is readable
if (! is_readable($this->path)) {
throw new InvalidArgumentException("The .env file is not readable: {$this->path}");
}
$vars = [];
$lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
// Is it a comment?
if (str_starts_with(trim($line), '#')) {
continue;
}
// If there is an equal sign, then we know we are assigning a variable.
if (str_contains($line, '=')) {
[$name, $value] = $this->normaliseVariable($line);
$vars[$name] = $value;
$this->setVariable($name, $value);
}
}
return $vars;
}
/**
* Sets the variable into the environment. Will parse the string
* first to look for {name}={value} pattern, ensure that nested
* variables are handled, and strip it of single and double quotes.
*
* @return void
*/
protected function setVariable(string $name, string $value = '')
{
if (getenv($name, true) === false) {
putenv("{$name}={$value}");
}
if (empty($_ENV[$name])) {
$_ENV[$name] = $value;
}
if (empty($_SERVER[$name])) {
$_SERVER[$name] = $value;
}
}
/**
* Parses for assignment, cleans the $name and $value, and ensures
* that nested variables are handled.
*/
public function normaliseVariable(string $name, string $value = ''): array
{
// Split our compound string into its parts.
if (str_contains($name, '=')) {
[$name, $value] = explode('=', $name, 2);
}
$name = trim($name);
$value = trim($value);
// Sanitize the name
$name = preg_replace('/^export[ \t]++(\S+)/', '$1', $name);
$name = str_replace(['\'', '"'], '', $name);
// Sanitize the value
$value = $this->sanitizeValue($value);
$value = $this->resolveNestedVariables($value);
return [$name, $value];
}
/**
* Strips quotes from the environment variable value.
*
* This was borrowed from the excellent phpdotenv with very few changes.
* https://github.com/vlucas/phpdotenv
*
* @throws InvalidArgumentException
*/
protected function sanitizeValue(string $value): string
{
if ($value === '') {
return $value;
}
// Does it begin with a quote?
if (strpbrk($value[0], '"\'') !== false) {
// value starts with a quote
$quote = $value[0];
$regexPattern = sprintf(
'/^
%1$s # match a quote at the start of the value
( # capturing sub-pattern used
(?: # we do not need to capture this
[^%1$s\\\\] # any character other than a quote or backslash
|\\\\\\\\ # or two backslashes together
|\\\\%1$s # or an escaped quote e.g \"
)* # as many characters that match the previous rules
) # end of the capturing sub-pattern
%1$s # and the closing quote
.*$ # and discard any string after the closing quote
/mx',
$quote,
);
$value = preg_replace($regexPattern, '$1', $value);
$value = str_replace("\\{$quote}", $quote, $value);
$value = str_replace('\\\\', '\\', $value);
} else {
$parts = explode(' #', $value, 2);
$value = trim($parts[0]);
// Unquoted values cannot contain whitespace
if (preg_match('/\s+/', $value) > 0) {
throw new InvalidArgumentException('.env values containing spaces must be surrounded by quotes.');
}
}
return $value;
}
/**
* Resolve the nested variables.
*
* Look for ${varname} patterns in the variable value and replace with an existing
* environment variable.
*
* This was borrowed from the excellent phpdotenv with very few changes.
* https://github.com/vlucas/phpdotenv
*/
protected function resolveNestedVariables(string $value): string
{
if (str_contains($value, '$')) {
$value = preg_replace_callback(
'/\${([a-zA-Z0-9_\.]+)}/',
function ($matchedPatterns) {
$nestedVariable = $this->getVariable($matchedPatterns[1]);
if ($nestedVariable === null) {
return $matchedPatterns[0];
}
return $nestedVariable;
},
$value,
);
}
return $value;
}
/**
* Search the different places for environment variables and return first value found.
*
* This was borrowed from the excellent phpdotenv with very few changes.
* https://github.com/vlucas/phpdotenv
*
* @return string|null
*/
protected function getVariable(string $name)
{
switch (true) {
case array_key_exists($name, $_ENV):
return $_ENV[$name];
case array_key_exists($name, $_SERVER):
return $_SERVER[$name];
default:
$value = getenv($name);
// switch getenv default to null
return $value === false ? null : $value;
}
}
}
@@ -0,0 +1,564 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use CodeIgniter\Autoloader\FileLocatorInterface;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Model;
/**
* Factories for creating instances.
*
* Factories allow dynamic loading of components by their path
* and name. The "shared instance" implementation provides a
* large performance boost and helps keep code clean of lengthy
* instantiation checks.
*
* @method static BaseConfig|null config(...$arguments)
* @method static Model|null models(string $alias, array $options = [], ?ConnectionInterface &$conn = null)
* @see \CodeIgniter\Config\FactoriesTest
*/
final class Factories
{
/**
* Store of component-specific options, usually
* from CodeIgniter\Config\Factory.
*
* @var array<string, array<string, bool|string|null>>
*/
private static array $options = [];
/**
* Explicit options for the Config
* component to prevent logic loops.
*
* @var array<string, bool|string|null>
*/
private static array $configOptions = [
'component' => 'config',
'path' => 'Config',
'instanceOf' => null,
'getShared' => true,
'preferApp' => true,
];
/**
* Mapping of class aliases to their true Fully Qualified Class Name (FQCN).
*
* Class aliases can be:
* - FQCN. E.g., 'App\Lib\SomeLib'
* - short classname. E.g., 'SomeLib'
* - short classname with sub-directories. E.g., 'Sub/SomeLib'
*
* [component => [alias => FQCN]]
*
* @var array<string, array<string, class-string>>
*/
private static array $aliases = [];
/**
* Store for instances of any component that
* has been requested as "shared".
*
* A multi-dimensional array with components as
* keys to the array of name-indexed instances.
*
* [component => [FQCN => instance]]
*
* @var array<string, array<class-string, object>>
*/
private static array $instances = [];
/**
* Whether the component instances are updated?
*
* @var array<string, true> [component => true]
*
* @internal For caching only
*/
private static array $updated = [];
/**
* Define the class to load. You can *override* the concrete class.
*
* @param string $component Lowercase, plural component name
* @param string $alias Class alias. See the $aliases property.
* @param class-string $classname FQCN to be loaded
*/
public static function define(string $component, string $alias, string $classname): void
{
$component = strtolower($component);
if (isset(self::$aliases[$component][$alias])) {
if (self::$aliases[$component][$alias] === $classname) {
return;
}
throw new InvalidArgumentException(
'Already defined in Factories: ' . $component . ' ' . $alias . ' -> ' . self::$aliases[$component][$alias],
);
}
if (! class_exists($classname)) {
throw new InvalidArgumentException('No such class: ' . $classname);
}
// Force a configuration to exist for this component.
// Otherwise, getOptions() will reset the component.
self::getOptions($component);
self::$aliases[$component][$alias] = $classname;
self::$updated[$component] = true;
}
/**
* Loads instances based on the method component name. Either
* creates a new instance or returns an existing shared instance.
*
* @return object|null
*/
public static function __callStatic(string $component, array $arguments)
{
$component = strtolower($component);
// First argument is the class alias, second is options
$alias = trim(array_shift($arguments), '\\ ');
$options = array_shift($arguments) ?? [];
// Determine the component-specific options
$options = array_merge(self::getOptions($component), $options);
if (! $options['getShared']) {
if (isset(self::$aliases[$options['component']][$alias])) {
$class = self::$aliases[$options['component']][$alias];
return new $class(...$arguments);
}
// Try to locate the class
$class = self::locateClass($options, $alias);
if ($class !== null) {
return new $class(...$arguments);
}
return null;
}
// Check for an existing definition
$instance = self::getDefinedInstance($options, $alias, $arguments);
if ($instance !== null) {
return $instance;
}
// Try to locate the class
if (($class = self::locateClass($options, $alias)) === null) {
return null;
}
self::createInstance($options['component'], $class, $arguments);
self::setAlias($options['component'], $alias, $class);
return self::$instances[$options['component']][$class];
}
/**
* Simple method to get the shared instance fast.
*/
public static function get(string $component, string $alias): ?object
{
if (isset(self::$aliases[$component][$alias])) {
$class = self::$aliases[$component][$alias];
if (isset(self::$instances[$component][$class])) {
return self::$instances[$component][$class];
}
}
return self::__callStatic($component, [$alias]);
}
/**
* Gets the defined instance. If not exists, creates new one.
*
* @return object|null
*/
private static function getDefinedInstance(array $options, string $alias, array $arguments)
{
// The alias is already defined.
if (isset(self::$aliases[$options['component']][$alias])) {
$class = self::$aliases[$options['component']][$alias];
// Need to verify if the shared instance matches the request
if (self::verifyInstanceOf($options, $class)) {
// Check for an existing instance
if (isset(self::$instances[$options['component']][$class])) {
return self::$instances[$options['component']][$class];
}
self::createInstance($options['component'], $class, $arguments);
return self::$instances[$options['component']][$class];
}
}
// Try to locate the class
if (($class = self::locateClass($options, $alias)) === null) {
return null;
}
// Check for an existing instance for the class
if (isset(self::$instances[$options['component']][$class])) {
self::setAlias($options['component'], $alias, $class);
return self::$instances[$options['component']][$class];
}
return null;
}
/**
* Creates the shared instance.
*/
private static function createInstance(string $component, string $class, array $arguments): void
{
self::$instances[$component][$class] = new $class(...$arguments);
self::$updated[$component] = true;
}
/**
* Sets alias
*/
private static function setAlias(string $component, string $alias, string $class): void
{
self::$aliases[$component][$alias] = $class;
self::$updated[$component] = true;
// If a short classname is specified, also register FQCN to share the instance.
if (! isset(self::$aliases[$component][$class]) && ! self::isNamespaced($alias)) {
self::$aliases[$component][$class] = $class;
}
}
/**
* Is the component Config?
*
* @param string $component Lowercase, plural component name
*/
private static function isConfig(string $component): bool
{
return $component === 'config';
}
/**
* Finds a component class
*
* @param array $options The array of component-specific directives
* @param string $alias Class alias. See the $aliases property.
*/
private static function locateClass(array $options, string $alias): ?string
{
// Check for low-hanging fruit
if (
class_exists($alias, false)
&& self::verifyPreferApp($options, $alias)
&& self::verifyInstanceOf($options, $alias)
) {
return $alias;
}
// Determine the relative class names we need
$basename = self::getBasename($alias);
$appname = self::isConfig($options['component'])
? 'Config\\' . $basename
: rtrim(APP_NAMESPACE, '\\') . '\\' . $options['path'] . '\\' . $basename;
// If an App version was requested then see if it verifies
if (
// preferApp is used only for no namespaced class.
! self::isNamespaced($alias)
&& $options['preferApp'] && class_exists($appname)
&& self::verifyInstanceOf($options, $alias)
) {
return $appname;
}
// If we have ruled out an App version and the class exists then try it
if (class_exists($alias) && self::verifyInstanceOf($options, $alias)) {
return $alias;
}
// Have to do this the hard way...
/** @var FileLocatorInterface */
$locator = service('locator');
// Check if the class alias was namespaced
if (self::isNamespaced($alias)) {
if (! $file = $locator->locateFile($alias, $options['path'])) {
return null;
}
$files = [$file];
}
// No namespace? Search for it
// Check all namespaces, prioritizing App and modules
elseif (($files = $locator->search($options['path'] . DIRECTORY_SEPARATOR . $alias)) === []) {
return null;
}
// Check all files for a valid class
foreach ($files as $file) {
$class = $locator->findQualifiedNameFromPath($file);
if ($class !== false && self::verifyInstanceOf($options, $class)) {
return $class;
}
}
return null;
}
/**
* Is the class alias namespaced or not?
*
* @param string $alias Class alias. See the $aliases property.
*/
private static function isNamespaced(string $alias): bool
{
return str_contains($alias, '\\');
}
/**
* Verifies that a class & config satisfy the "preferApp" option
*
* @param array $options The array of component-specific directives
* @param string $alias Class alias. See the $aliases property.
*/
private static function verifyPreferApp(array $options, string $alias): bool
{
// Anything without that restriction passes
if (! $options['preferApp']) {
return true;
}
// Special case for Config since its App namespace is actually \Config
if (self::isConfig($options['component'])) {
return str_starts_with($alias, 'Config');
}
return str_starts_with($alias, APP_NAMESPACE);
}
/**
* Verifies that a class & config satisfy the "instanceOf" option
*
* @param array $options The array of component-specific directives
* @param string $alias Class alias. See the $aliases property.
*/
private static function verifyInstanceOf(array $options, string $alias): bool
{
// Anything without that restriction passes
if (! $options['instanceOf']) {
return true;
}
return is_a($alias, $options['instanceOf'], true);
}
/**
* Returns the component-specific configuration
*
* @param string $component Lowercase, plural component name
*
* @return array<string, bool|string|null>
*
* @internal For testing only
* @testTag
*/
public static function getOptions(string $component): array
{
$component = strtolower($component);
// Check for a stored version
if (isset(self::$options[$component])) {
return self::$options[$component];
}
$values = self::isConfig($component)
// Handle Config as a special case to prevent logic loops
? self::$configOptions
// Load values from the best Factory configuration (will include Registrars)
: config('Factory')->{$component} ?? [];
// The setOptions() reset the component. So getOptions() may reset
// the component.
return self::setOptions($component, $values);
}
/**
* Normalizes, stores, and returns the configuration for a specific component
*
* @param string $component Lowercase, plural component name
* @param array $values option values
*
* @return array<string, bool|string|null> The result after applying defaults and normalization
*/
public static function setOptions(string $component, array $values): array
{
$component = strtolower($component);
// Allow the config to replace the component name, to support "aliases"
$values['component'] = strtolower($values['component'] ?? $component);
// Reset this component so instances can be rediscovered with the updated config
self::reset($values['component']);
// If no path was available then use the component
$values['path'] = trim($values['path'] ?? ucfirst($values['component']), '\\ ');
// Add defaults for any missing values
$values = array_merge(Factory::$default, $values);
// Store the result to the supplied name and potential alias
self::$options[$component] = $values;
self::$options[$values['component']] = $values;
return $values;
}
/**
* Resets the static arrays, optionally just for one component
*
* @param string|null $component Lowercase, plural component name
*
* @return void
*/
public static function reset(?string $component = null)
{
if ($component !== null) {
unset(
self::$options[$component],
self::$aliases[$component],
self::$instances[$component],
self::$updated[$component],
);
return;
}
self::$options = [];
self::$aliases = [];
self::$instances = [];
self::$updated = [];
}
/**
* Helper method for injecting mock instances
*
* @param string $component Lowercase, plural component name
* @param string $alias Class alias. See the $aliases property.
*
* @return void
*
* @internal For testing only
* @testTag
*/
public static function injectMock(string $component, string $alias, object $instance)
{
$component = strtolower($component);
// Force a configuration to exist for this component
self::getOptions($component);
$class = $instance::class;
self::$instances[$component][$class] = $instance;
self::$aliases[$component][$alias] = $class;
if (self::isConfig($component)) {
if (self::isNamespaced($alias)) {
self::$aliases[$component][self::getBasename($alias)] = $class;
} else {
self::$aliases[$component]['Config\\' . $alias] = $class;
}
}
}
/**
* Gets a basename from a class alias, namespaced or not.
*
* @internal For testing only
* @testTag
*/
public static function getBasename(string $alias): string
{
// Determine the basename
if ($basename = strrchr($alias, '\\')) {
return substr($basename, 1);
}
return $alias;
}
/**
* Gets component data for caching.
*
* @return array{
* options: array<string, bool|string|null>,
* aliases: array<string, class-string>,
* instances: array<class-string, object>,
* }
*
* @internal For caching only
*/
public static function getComponentInstances(string $component): array
{
if (! isset(self::$aliases[$component])) {
return [
'options' => [],
'aliases' => [],
'instances' => [],
];
}
return [
'options' => self::$options[$component],
'aliases' => self::$aliases[$component],
'instances' => self::$instances[$component],
];
}
/**
* Sets component data
*
* @internal For caching only
*/
public static function setComponentInstances(string $component, array $data): void
{
self::$options[$component] = $data['options'];
self::$aliases[$component] = $data['aliases'];
self::$instances[$component] = $data['instances'];
unset(self::$updated[$component]);
}
/**
* Whether the component instances are updated?
*
* @internal For caching only
*/
public static function isUpdated(string $component): bool
{
return isset(self::$updated[$component]);
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
/**
* Factories Configuration file.
*
* Provides overriding directives for how
* Factories should handle discovery and
* instantiation of specific components.
* Each property should correspond to the
* lowercase, plural component name.
*/
class Factory extends BaseConfig
{
/**
* Supplies a default set of options to merge for
* all unspecified factory components.
*
* @var array
*/
public static $default = [
'component' => null,
'path' => null,
'instanceOf' => null,
'getShared' => true,
'preferApp' => true,
];
/**
* Specifies that Models should always favor child
* classes to allow easy extension of module Models.
*
* @var array
*/
public $models = [
'preferApp' => true,
];
}
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use CodeIgniter\Filters\Cors;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\ForceHTTPS;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\PageCache;
use CodeIgniter\Filters\PerformanceMetrics;
use CodeIgniter\Filters\SecureHeaders;
/**
* Filters configuration
*/
class Filters extends BaseConfig
{
/**
* Configures aliases for Filter classes to
* make reading things nicer and simpler.
*
* @var array<string, class-string|list<class-string>>
*
* [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
*/
public array $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'cors' => Cors::class,
'forcehttps' => ForceHTTPS::class,
'pagecache' => PageCache::class,
'performance' => PerformanceMetrics::class,
];
/**
* List of special required filters.
*
* The filters listed here are special. They are applied before and after
* other kinds of filters, and always applied even if a route does not exist.
*
* Filters set by default provide framework functionality. If removed,
* those functions will no longer work.
*
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
*
* @var array{before: list<string>, after: list<string>}
*/
public array $required = [
'before' => [
'forcehttps', // Force Global Secure Requests
'pagecache', // Web Page Caching
],
'after' => [
'pagecache', // Web Page Caching
'performance', // Performance Metrics
'toolbar', // Debug Toolbar
],
];
/**
* List of filter aliases that are always
* applied before and after every request.
*
* @var array<string, array<string, array<string, string>>>|array<string, list<string>>
*/
public array $globals = [
'before' => [
// 'honeypot',
// 'csrf',
// 'invalidchars',
],
'after' => [
// 'honeypot',
// 'secureheaders',
],
];
/**
* List of filter aliases that works on a
* particular HTTP method (GET, POST, etc.).
*
* Example:
* 'POST' => ['foo', 'bar']
*
* If you use this, you should disable auto-routing because auto-routing
* permits any HTTP method to access a controller. Accessing the controller
* with a method you don't expect could bypass the filter.
*
* @var array<string, list<string>>
*/
public array $methods = [];
/**
* List of filter aliases that should run on any
* before or after URI patterns.
*
* Example:
* 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
*
* @var array<string, array<string, list<string>>>
*/
public array $filters = [];
}
@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
/**
* Describes foreign characters for transliteration with the text helper.
*/
class ForeignCharacters
{
/**
* The list of foreign characters.
*
* @var array<string, string>
*/
public $characterList = [
'/ä|æ|ǽ/' => 'ae',
'/ö|œ/' => 'oe',
'/ü/' => 'ue',
'/Ä/' => 'Ae',
'/Ü/' => 'Ue',
'/Ö/' => 'Oe',
'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|А/' => 'A',
'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a',
'/Б/' => 'B',
'/б/' => 'b',
'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
'/ç|ć|ĉ|ċ|č/' => 'c',
'/Д/' => 'D',
'/д/' => 'd',
'/Ð|Ď|Đ|Δ/' => 'Dj',
'/ð|ď|đ|δ/' => 'dj',
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E',
'/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε|ẽ|ẻ|ẹ|ề|ế|ễ|ể|ệ|е|э/' => 'e',
'/Ф/' => 'F',
'/ф/' => 'f',
'/Ĝ|Ğ|Ġ|Ģ|Γ|Г|Ґ/' => 'G',
'/ĝ|ğ|ġ|ģ|γ|г|ґ/' => 'g',
'/Ĥ|Ħ/' => 'H',
'/ĥ|ħ/' => 'h',
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I',
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ|ỉ|ị|и|ы|ї/' => 'i',
'/Ĵ/' => 'J',
'/ĵ/' => 'j',
'/Ķ|Κ|К/' => 'K',
'/ķ|κ|к/' => 'k',
'/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ|Л/' => 'L',
'/ĺ|ļ|ľ|ŀ|ł|λ|л/' => 'l',
'/М/' => 'M',
'/м/' => 'm',
'/Ñ|Ń|Ņ|Ň|Ν|Н/' => 'N',
'/ñ|ń|ņ|ň|ʼn|ν|н/' => 'n',
'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ|Ỏ|Ọ|Ồ|Ố|Ỗ|Ổ|Ộ|Ờ|Ớ|Ỡ|Ở|Ợ|О/' => 'O',
'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ|ỏ|ọ|ồ|ố|ỗ|ổ|ộ|ờ|ớ|ỡ|ở|ợ|о/' => 'o',
'/П/' => 'P',
'/п/' => 'p',
'/Ŕ|Ŗ|Ř|Ρ|Р/' => 'R',
'/ŕ|ŗ|ř|ρ|р/' => 'r',
'/Ś|Ŝ|Ş|Ș|Š|Σ|С/' => 'S',
'/ś|ŝ|ş|ș|š|ſ|σ|ς|с/' => 's',
'/Ț|Ţ|Ť|Ŧ|τ|Т/' => 'T',
'/ț|ţ|ť|ŧ|т/' => 't',
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|Ũ|Ủ|Ụ|Ừ|Ứ|Ữ|Ử|Ự|У/' => 'U',
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ|ủ|ụ|ừ|ứ|ữ|ử|ự|у/' => 'u',
'/Ƴ|Ɏ|Ỵ|Ẏ|Ӳ|Ӯ|Ў|Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ|Ỳ|Ỹ|Ỷ|Ỵ|Й/' => 'Y',
'/ẙ|ʏ|ƴ|ɏ|ỵ|ẏ|ӳ|ӯ|ў|ý|ÿ|ŷ|ỳ|ỹ|ỷ|ỵ|й/' => 'y',
'/В/' => 'V',
'/в/' => 'v',
'/Ŵ/' => 'W',
'/ŵ/' => 'w',
'/Ź|Ż|Ž|Ζ|З/' => 'Z',
'/ź|ż|ž|ζ|з/' => 'z',
'/Æ|Ǽ/' => 'AE',
'/ß/' => 'ss',
'/IJ/' => 'IJ',
'/ij/' => 'ij',
'/Œ/' => 'OE',
'/ƒ/' => 'f',
'/ξ/' => 'ks',
'/π/' => 'p',
'/β/' => 'v',
'/μ/' => 'm',
'/ψ/' => 'ps',
'/Ё/' => 'Yo',
'/ё/' => 'yo',
'/Є/' => 'Ye',
'/є/' => 'ye',
'/Ї/' => 'Yi',
'/Ж/' => 'Zh',
'/ж/' => 'zh',
'/Х/' => 'Kh',
'/х/' => 'kh',
'/Ц/' => 'Ts',
'/ц/' => 'ts',
'/Ч/' => 'Ch',
'/ч/' => 'ch',
'/Ш/' => 'Sh',
'/ш/' => 'sh',
'/Щ/' => 'Shch',
'/щ/' => 'shch',
'/Ъ|ъ|Ь|ь/' => '',
'/Ю/' => 'Yu',
'/ю/' => 'yu',
'/Я/' => 'Ya',
'/я/' => 'ya',
];
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
/**
* Publisher Configuration
*
* Defines basic security restrictions for the Publisher class
* to prevent abuse by injecting malicious files into a project.
*/
class Publisher extends BaseConfig
{
/**
* A list of allowed destinations with a (pseudo-)regex
* of allowed files for each destination.
* Attempts to publish to directories not in this list will
* result in a PublisherException. Files that do no fit the
* pattern will cause copy/merge to fail.
*
* @var array<string, string>
*/
public $restrictions = [
ROOTPATH => '*',
FCPATH => '#\.(?css|js|map|htm?|xml|json|webmanifest|tff|eot|woff?|gif|jpe?g|tiff?|png|webp|bmp|ico|svg)$#i',
];
/**
* Disables Registrars to prevent modules from altering the restrictions.
*/
final protected function registerProperties(): void
{
}
}
@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
/**
* Routing configuration
*/
class Routing extends BaseConfig
{
/**
* For Defined Routes.
* An array of files that contain route definitions.
* Route files are read in order, with the first match
* found taking precedence.
*
* Default: APPPATH . 'Config/Routes.php'
*
* @var list<string>
*/
public array $routeFiles = [
APPPATH . 'Config/Routes.php',
];
/**
* For Defined Routes and Auto Routing.
* The default namespace to use for Controllers when no other
* namespace has been specified.
*
* Default: 'App\Controllers'
*/
public string $defaultNamespace = 'App\Controllers';
/**
* For Auto Routing.
* The default controller to use when no other controller has been
* specified.
*
* Default: 'Home'
*/
public string $defaultController = 'Home';
/**
* For Defined Routes and Auto Routing.
* The default method to call on the controller when no other
* method has been set in the route.
*
* Default: 'index'
*/
public string $defaultMethod = 'index';
/**
* For Auto Routing.
* Whether to translate dashes in URIs for controller/method to underscores.
* Primarily useful when using the auto-routing.
*
* Default: false
*/
public bool $translateURIDashes = false;
/**
* Sets the class/method that should be called if routing doesn't
* find a match. It can be the controller/method name like: Users::index
*
* This setting is passed to the Router class and handled there.
*
* If you want to use a closure, you will have to set it in the
* routes file by calling:
*
* $routes->set404Override(function() {
* // Do something here
* });
*
* Example:
* public $override404 = 'App\Errors::show404';
*/
public ?string $override404 = null;
/**
* If TRUE, the system will attempt to match the URI against
* Controllers by matching each segment against folders/files
* in APPPATH/Controllers, when a match wasn't found against
* defined routes.
*
* If FALSE, will stop searching and do NO automatic routing.
*/
public bool $autoRoute = false;
/**
* For Defined Routes.
* If TRUE, will enable the use of the 'prioritize' option
* when defining routes.
*
* Default: false
*/
public bool $prioritize = false;
/**
* For Defined Routes.
* If TRUE, matched multiple URI segments will be passed as one parameter.
*
* Default: false
*/
public bool $multipleSegmentsOneParam = false;
/**
* For Auto Routing (Improved).
* Map of URI segments and namespaces.
*
* The key is the first URI segment. The value is the controller namespace.
* E.g.,
* [
* 'blog' => 'Acme\Blog\Controllers',
* ]
*
* @var array<string, string>
*/
public array $moduleRoutes = [];
/**
* For Auto Routing (Improved).
* Whether to translate dashes in URIs for controller/method to CamelCase.
* E.g., blog-controller -> BlogController
*
* If you enable this, $translateURIDashes is ignored.
*
* Default: false
*/
public bool $translateUriToCamelCase = false;
}
@@ -0,0 +1,866 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use CodeIgniter\Cache\CacheFactory;
use CodeIgniter\Cache\CacheInterface;
use CodeIgniter\Cache\ResponseCache;
use CodeIgniter\CLI\Commands;
use CodeIgniter\CodeIgniter;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Database\MigrationRunner;
use CodeIgniter\Debug\Exceptions;
use CodeIgniter\Debug\Iterator;
use CodeIgniter\Debug\Timer;
use CodeIgniter\Debug\Toolbar;
use CodeIgniter\Email\Email;
use CodeIgniter\Encryption\EncrypterInterface;
use CodeIgniter\Encryption\Encryption;
use CodeIgniter\Filters\Filters;
use CodeIgniter\Format\Format;
use CodeIgniter\Honeypot\Honeypot;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\ContentSecurityPolicy;
use CodeIgniter\HTTP\CURLRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\Negotiate;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Request;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\SiteURIFactory;
use CodeIgniter\HTTP\URI;
use CodeIgniter\HTTP\UserAgent;
use CodeIgniter\Images\Handlers\BaseHandler;
use CodeIgniter\Language\Language;
use CodeIgniter\Log\Logger;
use CodeIgniter\Pager\Pager;
use CodeIgniter\Router\RouteCollection;
use CodeIgniter\Router\RouteCollectionInterface;
use CodeIgniter\Router\Router;
use CodeIgniter\Security\Security;
use CodeIgniter\Session\Handlers\BaseHandler as SessionBaseHandler;
use CodeIgniter\Session\Handlers\Database\MySQLiHandler;
use CodeIgniter\Session\Handlers\Database\PostgreHandler;
use CodeIgniter\Session\Handlers\DatabaseHandler;
use CodeIgniter\Session\Session;
use CodeIgniter\Superglobals;
use CodeIgniter\Throttle\Throttler;
use CodeIgniter\Typography\Typography;
use CodeIgniter\Validation\Validation;
use CodeIgniter\Validation\ValidationInterface;
use CodeIgniter\View\Cell;
use CodeIgniter\View\Parser;
use CodeIgniter\View\RendererInterface;
use CodeIgniter\View\View;
use Config\App;
use Config\Cache;
use Config\ContentSecurityPolicy as ContentSecurityPolicyConfig;
use Config\ContentSecurityPolicy as CSPConfig;
use Config\Database;
use Config\Email as EmailConfig;
use Config\Encryption as EncryptionConfig;
use Config\Exceptions as ExceptionsConfig;
use Config\Filters as FiltersConfig;
use Config\Format as FormatConfig;
use Config\Honeypot as HoneypotConfig;
use Config\Images;
use Config\Logger as LoggerConfig;
use Config\Migrations;
use Config\Modules;
use Config\Pager as PagerConfig;
use Config\Paths;
use Config\Routing;
use Config\Security as SecurityConfig;
use Config\Services as AppServices;
use Config\Session as SessionConfig;
use Config\Toolbar as ToolbarConfig;
use Config\Validation as ValidationConfig;
use Config\View as ViewConfig;
use InvalidArgumentException;
use Locale;
/**
* Services Configuration file.
*
* Services are simply other classes/libraries that the system uses
* to do its job. This is used by CodeIgniter to allow the core of the
* framework to be swapped out easily without affecting the usage within
* the rest of your application.
*
* This is used in place of a Dependency Injection container primarily
* due to its simplicity, which allows a better long-term maintenance
* of the applications built on top of CodeIgniter. A bonus side-effect
* is that IDEs are able to determine what class you are calling
* whereas with DI Containers there usually isn't a way for them to do this.
*
* @see http://blog.ircmaxell.com/2015/11/simple-easy-risk-and-change.html
* @see http://www.infoq.com/presentations/Simple-Made-Easy
* @see \CodeIgniter\Config\ServicesTest
*/
class Services extends BaseService
{
/**
* The cache class provides a simple way to store and retrieve
* complex data for later.
*
* @return CacheInterface
*/
public static function cache(?Cache $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('cache', $config);
}
$config ??= config(Cache::class);
return CacheFactory::getHandler($config);
}
/**
* The CLI Request class provides for ways to interact with
* a command line request.
*
* @return CLIRequest
*
* @internal
*/
public static function clirequest(?App $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('clirequest', $config);
}
$config ??= config(App::class);
return new CLIRequest($config);
}
/**
* CodeIgniter, the core of the framework.
*
* @return CodeIgniter
*/
public static function codeigniter(?App $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('codeigniter', $config);
}
$config ??= config(App::class);
return new CodeIgniter($config);
}
/**
* The commands utility for running and working with CLI commands.
*
* @return Commands
*/
public static function commands(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('commands');
}
return new Commands();
}
/**
* Content Security Policy
*
* @return ContentSecurityPolicy
*/
public static function csp(?CSPConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('csp', $config);
}
$config ??= config(ContentSecurityPolicyConfig::class);
return new ContentSecurityPolicy($config);
}
/**
* The CURL Request class acts as a simple HTTP client for interacting
* with other servers, typically through APIs.
*
* @return CURLRequest
*/
public static function curlrequest(array $options = [], ?ResponseInterface $response = null, ?App $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('curlrequest', $options, $response, $config);
}
$config ??= config(App::class);
$response ??= new Response($config);
return new CURLRequest(
$config,
new URI($options['baseURI'] ?? null),
$response,
$options,
);
}
/**
* The Email class allows you to send email via mail, sendmail, SMTP.
*
* @param array|EmailConfig|null $config
*
* @return Email
*/
public static function email($config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('email', $config);
}
if (empty($config) || (! is_array($config) && ! $config instanceof EmailConfig)) {
$config = config(EmailConfig::class);
}
return new Email($config);
}
/**
* The Encryption class provides two-way encryption.
*
* @param bool $getShared
*
* @return EncrypterInterface Encryption handler
*/
public static function encrypter(?EncryptionConfig $config = null, $getShared = false)
{
if ($getShared === true) {
return static::getSharedInstance('encrypter', $config);
}
$config ??= config(EncryptionConfig::class);
$encryption = new Encryption($config);
return $encryption->initialize($config);
}
/**
* The Exceptions class holds the methods that handle:
*
* - set_exception_handler
* - set_error_handler
* - register_shutdown_function
*
* @return Exceptions
*/
public static function exceptions(
?ExceptionsConfig $config = null,
bool $getShared = true,
) {
if ($getShared) {
return static::getSharedInstance('exceptions', $config);
}
$config ??= config(ExceptionsConfig::class);
return new Exceptions($config);
}
/**
* Filters allow you to run tasks before and/or after a controller
* is executed. During before filters, the request can be modified,
* and actions taken based on the request, while after filters can
* act on or modify the response itself before it is sent to the client.
*
* @return Filters
*/
public static function filters(?FiltersConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('filters', $config);
}
$config ??= config(FiltersConfig::class);
return new Filters($config, AppServices::get('request'), AppServices::get('response'));
}
/**
* The Format class is a convenient place to create Formatters.
*
* @return Format
*/
public static function format(?FormatConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('format', $config);
}
$config ??= config(FormatConfig::class);
return new Format($config);
}
/**
* The Honeypot provides a secret input on forms that bots should NOT
* fill in, providing an additional safeguard when accepting user input.
*
* @return Honeypot
*/
public static function honeypot(?HoneypotConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('honeypot', $config);
}
$config ??= config(HoneypotConfig::class);
return new Honeypot($config);
}
/**
* Acts as a factory for ImageHandler classes and returns an instance
* of the handler. Used like service('image')->withFile($path)->rotate(90)->save();
*
* @return BaseHandler
*/
public static function image(?string $handler = null, ?Images $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('image', $handler, $config);
}
$config ??= config(Images::class);
assert($config instanceof Images);
$handler = $handler !== null && $handler !== '' && $handler !== '0' ? $handler : $config->defaultHandler;
$class = $config->handlers[$handler];
return new $class($config);
}
/**
* The Iterator class provides a simple way of looping over a function
* and timing the results and memory usage. Used when debugging and
* optimizing applications.
*
* @return Iterator
*/
public static function iterator(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('iterator');
}
return new Iterator();
}
/**
* Responsible for loading the language string translations.
*
* @return Language
*/
public static function language(?string $locale = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('language', $locale)->setLocale($locale);
}
if (AppServices::get('request') instanceof IncomingRequest) {
$requestLocale = AppServices::get('request')->getLocale();
} else {
$requestLocale = Locale::getDefault();
}
// Use '?:' for empty string check
$locale = $locale !== null && $locale !== '' && $locale !== '0' ? $locale : $requestLocale;
return new Language($locale);
}
/**
* The Logger class is a PSR-3 compatible Logging class that supports
* multiple handlers that process the actual logging.
*
* @return Logger
*/
public static function logger(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('logger');
}
return new Logger(config(LoggerConfig::class));
}
/**
* Return the appropriate Migration runner.
*
* @return MigrationRunner
*/
public static function migrations(?Migrations $config = null, ?ConnectionInterface $db = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('migrations', $config, $db);
}
$config ??= config(Migrations::class);
return new MigrationRunner($config, $db);
}
/**
* The Negotiate class provides the content negotiation features for
* working the request to determine correct language, encoding, charset,
* and more.
*
* @return Negotiate
*/
public static function negotiator(?RequestInterface $request = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('negotiator', $request);
}
$request ??= AppServices::get('request');
return new Negotiate($request);
}
/**
* Return the ResponseCache.
*
* @return ResponseCache
*/
public static function responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('responsecache', $config, $cache);
}
$config ??= config(Cache::class);
$cache ??= AppServices::get('cache');
return new ResponseCache($config, $cache);
}
/**
* Return the appropriate pagination handler.
*
* @return Pager
*/
public static function pager(?PagerConfig $config = null, ?RendererInterface $view = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('pager', $config, $view);
}
$config ??= config(PagerConfig::class);
$view ??= AppServices::renderer(null, null, false);
return new Pager($config, $view);
}
/**
* The Parser is a simple template parser.
*
* @return Parser
*/
public static function parser(?string $viewPath = null, ?ViewConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('parser', $viewPath, $config);
}
$viewPath = $viewPath !== null && $viewPath !== '' && $viewPath !== '0' ? $viewPath : (new Paths())->viewDirectory;
$config ??= config(ViewConfig::class);
return new Parser($config, $viewPath, AppServices::get('locator'), CI_DEBUG, AppServices::get('logger'));
}
/**
* The Renderer class is the class that actually displays a file to the user.
* The default View class within CodeIgniter is intentionally simple, but this
* service could easily be replaced by a template engine if the user needed to.
*
* @return View
*/
public static function renderer(?string $viewPath = null, ?ViewConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('renderer', $viewPath, $config);
}
$viewPath = $viewPath !== null && $viewPath !== '' && $viewPath !== '0' ? $viewPath : (new Paths())->viewDirectory;
$config ??= config(ViewConfig::class);
return new View($config, $viewPath, AppServices::get('locator'), CI_DEBUG, AppServices::get('logger'));
}
/**
* Returns the current Request object.
*
* createRequest() injects IncomingRequest or CLIRequest.
*
* @return CLIRequest|IncomingRequest
*
* @deprecated The parameter $config and $getShared are deprecated.
*/
public static function request(?App $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('request', $config);
}
// @TODO remove the following code for backward compatibility
return AppServices::incomingrequest($config, $getShared);
}
/**
* Create the current Request object, either IncomingRequest or CLIRequest.
*
* This method is called from CodeIgniter::getRequestObject().
*
* @internal
*/
public static function createRequest(App $config, bool $isCli = false): void
{
if ($isCli) {
$request = AppServices::clirequest($config);
} else {
$request = AppServices::incomingrequest($config);
// guess at protocol if needed
$request->setProtocolVersion($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1');
}
// Inject the request object into Services.
static::$instances['request'] = $request;
}
/**
* The IncomingRequest class models an HTTP request.
*
* @return IncomingRequest
*
* @internal
*/
public static function incomingrequest(?App $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('request', $config);
}
$config ??= config(App::class);
return new IncomingRequest(
$config,
AppServices::get('uri'),
'php://input',
new UserAgent(),
);
}
/**
* The Response class models an HTTP response.
*
* @return ResponseInterface
*/
public static function response(?App $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('response', $config);
}
$config ??= config(App::class);
return new Response($config);
}
/**
* The Redirect class provides nice way of working with redirects.
*
* @return RedirectResponse
*/
public static function redirectresponse(?App $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('redirectresponse', $config);
}
$config ??= config(App::class);
$response = new RedirectResponse($config);
$response->setProtocolVersion(AppServices::get('request')->getProtocolVersion());
return $response;
}
/**
* The Routes service is a class that allows for easily building
* a collection of routes.
*
* @return RouteCollection
*/
public static function routes(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('routes');
}
return new RouteCollection(AppServices::get('locator'), new Modules(), config(Routing::class));
}
/**
* The Router class uses a RouteCollection's array of routes, and determines
* the correct Controller and Method to execute.
*
* @return Router
*/
public static function router(?RouteCollectionInterface $routes = null, ?Request $request = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('router', $routes, $request);
}
$routes ??= AppServices::get('routes');
$request ??= AppServices::get('request');
return new Router($routes, $request);
}
/**
* The Security class provides a few handy tools for keeping the site
* secure, most notably the CSRF protection tools.
*
* @return Security
*/
public static function security(?SecurityConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('security', $config);
}
$config ??= config(SecurityConfig::class);
return new Security($config);
}
/**
* Return the session manager.
*
* @return Session
*/
public static function session(?SessionConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('session', $config);
}
$config ??= config(SessionConfig::class);
$logger = AppServices::get('logger');
$driverName = $config->driver;
if ($driverName === DatabaseHandler::class) {
$DBGroup = $config->DBGroup ?? config(Database::class)->defaultGroup;
$driverPlatform = Database::connect($DBGroup)->getPlatform();
if ($driverPlatform === 'MySQLi') {
$driverName = MySQLiHandler::class;
} elseif ($driverPlatform === 'Postgre') {
$driverName = PostgreHandler::class;
}
}
if (! class_exists($driverName) || ! is_a($driverName, SessionBaseHandler::class, true)) {
throw new InvalidArgumentException(sprintf(
'Invalid session handler "%s" provided.',
$driverName,
));
}
/** @var SessionBaseHandler $driver */
$driver = new $driverName($config, AppServices::get('request')->getIPAddress());
$driver->setLogger($logger);
$session = new Session($driver, $config);
$session->setLogger($logger);
if (session_status() === PHP_SESSION_NONE) {
// PHP Session emits the headers according to `session.cache_limiter`.
// See https://www.php.net/manual/en/function.session-cache-limiter.php.
// The headers are not managed by CI's Response class.
// So, we remove CI's default Cache-Control header.
AppServices::get('response')->removeHeader('Cache-Control');
$session->start();
}
return $session;
}
/**
* The Factory for SiteURI.
*
* @return SiteURIFactory
*/
public static function siteurifactory(
?App $config = null,
?Superglobals $superglobals = null,
bool $getShared = true,
) {
if ($getShared) {
return static::getSharedInstance('siteurifactory', $config, $superglobals);
}
$config ??= config('App');
$superglobals ??= AppServices::get('superglobals');
return new SiteURIFactory($config, $superglobals);
}
/**
* Superglobals.
*
* @return Superglobals
*/
public static function superglobals(
?array $server = null,
?array $get = null,
bool $getShared = true,
) {
if ($getShared) {
return static::getSharedInstance('superglobals', $server, $get);
}
return new Superglobals($server, $get);
}
/**
* The Throttler class provides a simple method for implementing
* rate limiting in your applications.
*
* @return Throttler
*/
public static function throttler(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('throttler');
}
return new Throttler(AppServices::get('cache'));
}
/**
* The Timer class provides a simple way to Benchmark portions of your
* application.
*
* @return Timer
*/
public static function timer(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('timer');
}
return new Timer();
}
/**
* Return the debug toolbar.
*
* @return Toolbar
*/
public static function toolbar(?ToolbarConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('toolbar', $config);
}
$config ??= config(ToolbarConfig::class);
return new Toolbar($config);
}
/**
* The URI class provides a way to model and manipulate URIs.
*
* @param string|null $uri The URI string
*
* @return URI The current URI if $uri is null.
*/
public static function uri(?string $uri = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('uri', $uri);
}
if ($uri === null) {
$appConfig = config(App::class);
$factory = AppServices::siteurifactory($appConfig, AppServices::get('superglobals'));
return $factory->createFromGlobals();
}
return new URI($uri);
}
/**
* The Validation class provides tools for validating input data.
*
* @return ValidationInterface
*/
public static function validation(?ValidationConfig $config = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('validation', $config);
}
$config ??= config(ValidationConfig::class);
return new Validation($config, AppServices::get('renderer'));
}
/**
* View cells are intended to let you insert HTML into view
* that has been generated by any callable in the system.
*
* @return Cell
*/
public static function viewcell(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('viewcell');
}
return new Cell(AppServices::get('cache'));
}
/**
* The Typography class provides a way to format text in semantically relevant ways.
*
* @return Typography
*/
public static function typography(bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('typography');
}
return new Typography();
}
}
@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Config;
use CodeIgniter\View\ViewDecoratorInterface;
/**
* View configuration
*
* @phpstan-type parser_callable (callable(mixed): mixed)
* @phpstan-type parser_callable_string (callable(mixed): mixed)&string
*/
class View extends BaseConfig
{
/**
* When false, the view method will clear the data between each
* call.
*
* @var bool
*/
public $saveData = true;
/**
* Parser Filters map a filter name with any PHP callable. When the
* Parser prepares a variable for display, it will chain it
* through the filters in the order defined, inserting any parameters.
*
* To prevent potential abuse, all filters MUST be defined here
* in order for them to be available for use within the Parser.
*
* @psalm-suppress UndefinedDocblockClass
*
* @var array<string, string>
* @phpstan-var array<string, parser_callable_string>
*/
public $filters = [];
/**
* Parser Plugins provide a way to extend the functionality provided
* by the core Parser by creating aliases that will be replaced with
* any callable. Can be single or tag pair.
*
* @psalm-suppress UndefinedDocblockClass
*
* @var array<string, callable|list<string>|string>
* @phpstan-var array<string, list<parser_callable_string>|parser_callable_string|parser_callable>
*/
public $plugins = [];
/**
* Built-in View filters.
*
* @psalm-suppress UndefinedDocblockClass
*
* @var array<string, string>
* @phpstan-var array<string, parser_callable_string>
*/
protected $coreFilters = [
'abs' => '\abs',
'capitalize' => '\CodeIgniter\View\Filters::capitalize',
'date' => '\CodeIgniter\View\Filters::date',
'date_modify' => '\CodeIgniter\View\Filters::date_modify',
'default' => '\CodeIgniter\View\Filters::default',
'esc' => '\CodeIgniter\View\Filters::esc',
'excerpt' => '\CodeIgniter\View\Filters::excerpt',
'highlight' => '\CodeIgniter\View\Filters::highlight',
'highlight_code' => '\CodeIgniter\View\Filters::highlight_code',
'limit_words' => '\CodeIgniter\View\Filters::limit_words',
'limit_chars' => '\CodeIgniter\View\Filters::limit_chars',
'local_currency' => '\CodeIgniter\View\Filters::local_currency',
'local_number' => '\CodeIgniter\View\Filters::local_number',
'lower' => '\strtolower',
'nl2br' => '\CodeIgniter\View\Filters::nl2br',
'number_format' => '\number_format',
'prose' => '\CodeIgniter\View\Filters::prose',
'round' => '\CodeIgniter\View\Filters::round',
'strip_tags' => '\strip_tags',
'title' => '\CodeIgniter\View\Filters::title',
'upper' => '\strtoupper',
];
/**
* Built-in View plugins.
*
* @psalm-suppress UndefinedDocblockClass
*
* @var array<string, callable|list<string>|string>
* @phpstan-var array<string, array<parser_callable_string>|parser_callable_string|parser_callable>
*/
protected $corePlugins = [
'csp_script_nonce' => '\CodeIgniter\View\Plugins::cspScriptNonce',
'csp_style_nonce' => '\CodeIgniter\View\Plugins::cspStyleNonce',
'current_url' => '\CodeIgniter\View\Plugins::currentURL',
'previous_url' => '\CodeIgniter\View\Plugins::previousURL',
'mailto' => '\CodeIgniter\View\Plugins::mailto',
'safe_mailto' => '\CodeIgniter\View\Plugins::safeMailto',
'lang' => '\CodeIgniter\View\Plugins::lang',
'validation_errors' => '\CodeIgniter\View\Plugins::validationErrors',
'route' => '\CodeIgniter\View\Plugins::route',
'siteURL' => '\CodeIgniter\View\Plugins::siteURL',
];
/**
* View Decorators are class methods that will be run in sequence to
* have a chance to alter the generated output just prior to caching
* the results.
*
* All classes must implement CodeIgniter\View\ViewDecoratorInterface
*
* @var list<class-string<ViewDecoratorInterface>>
*/
public array $decorators = [];
/**
* Merge the built-in and developer-configured filters and plugins,
* with preference to the developer ones.
*/
public function __construct()
{
$this->filters = array_merge($this->coreFilters, $this->filters);
$this->plugins = array_merge($this->corePlugins, $this->plugins);
parent::__construct();
}
}