Merge branch 'develop' into refactor/email

This commit is contained in:
Jim Parry 2019-03-07 19:16:22 -08:00
commit 417b1f9d73
No known key found for this signature in database
GPG Key ID: CED549230775AD5B
235 changed files with 5788 additions and 2091 deletions

View File

@ -1,7 +1,6 @@
language: php
php:
- 7.1
- 7.2
- 7.3
- nightly
@ -9,7 +8,6 @@ php:
matrix:
fast_finish: true
allow_failures:
- php: 7.3
- php: nightly
global:

View File

@ -5,6 +5,7 @@
<br>
## What is CodeIgniter?
CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure.
More information can be found at the [official site](http://codeigniter.com).
@ -35,6 +36,7 @@ framework are exposed.
The user guide updating and deployment is a bit awkward at the moment, but we are working on it!
## Repository Management
We use Github issues to track **BUGS** and to track approved **DEVELOPMENT** work packages.
We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss
FEATURE REQUESTS.
@ -57,6 +59,7 @@ Remember that some components that were part of CodeIgniter 3 are being moved
to optional packages, with their own repository.
## Contributing
We **are** accepting contributions from the community, specifically those identified as part of phase 2.
We will try to manage the process somewhat, by adding a "Help wanted" label to those that we are
@ -68,7 +71,9 @@ We are not looking for out-of-scope contributions, only those that would be cons
Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing.md) section in the user guide
## Server Requirements
PHP version 7.1 or higher is required, with the following extensions installed:
PHP version 7.2 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library
@ -81,4 +86,5 @@ Additionally, make sure that the following extensions are enabled in your PHP:
- xml (enabled by default - don't turn it off)
## Running CodeIgniter Tests
Information on running CodeIgniter test suite can be found in the [README.md](tests/README.md) file in the tests directory.

View File

@ -1,8 +1,8 @@
#CodeIgniter 4 Admin
# CodeIgniter 4 Admin
This folder contains tools or docs useful for project maintainers.
##Repositories inside https://github.com/codeigniter4
## Repositories inside https://github.com/codeigniter4
- **CodeIgniter4** is the main development repository.
It supports issues and pull requests, and has a rule to enforce GPG-signed commits.
@ -35,7 +35,7 @@ This folder contains tools or docs useful for project maintainers.
It is community-maintained, and accepts issues and pull requests.
It could be downloaded, forked or composer-installed.
##Contributor Scripts
## Contributor Scripts
- **setup.sh** installs a git pre-commit hook into a contributor's
local clone of their fork of the `CodeIgniter4` repository.
@ -43,7 +43,7 @@ This folder contains tools or docs useful for project maintainers.
to be added as part of a git commit, ensuring that they conform to the
framework coding style standards, and automatically fixing what can be.
##Maintainer Scripts
## Maintainer Scripts
- **release-config** holds variables used for the maintainer & release building
- **docbot** re-builds the user guide from the RST source for it,
@ -51,7 +51,7 @@ This folder contains tools or docs useful for project maintainers.
repository (if the user running it has maintainer rights on that repo).
See the [writeup](./docbot.md).
##Release Building Scripts
## Release Building Scripts
The release workflow is detailed in its own writeup; these are the main
scripts used by the release manager:
@ -79,7 +79,7 @@ scripts used by the release manager:
Remember to be polite when running it.
##Other Stuff
## Other Stuff
- **release-notes.bb** is a boilerplate for forum announcements of a new release.
It is marked up using [BBcode](https://en.wikipedia.org/wiki/BBCode).

View File

@ -1,6 +1,7 @@
# CodeIgniter 4 Framework
## What is CodeIgniter?
CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure.
More information can be found at the [official site](http://codeigniter.com).
@ -29,6 +30,7 @@ framework are exposed.
The user guide updating and deployment is a bit awkward at the moment, but we are working on it!
## Repository Management
We use Github issues to track **BUGS** and to track approved **DEVELOPMENT** work packages.
We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss
FEATURE REQUESTS.
@ -51,12 +53,14 @@ Remember that some components that were part of CodeIgniter 3 are being moved
to optional packages, with their own repository.
## Contributing
We welcome contributions from the community.
Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing.md) section in the development repository.
## Server Requirements
PHP version 7.1 or higher is required, with the following extensions installed:
PHP version 7.2 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library

View File

@ -5,7 +5,7 @@
"homepage": "https://codeigniter.com",
"license": "MIT",
"require": {
"php": ">=7.1",
"php": ">=7.2",
"ext-curl": "*",
"ext-intl": "*",
"kint-php/kint": "^2.1",
@ -14,7 +14,7 @@
},
"require-dev": {
"codeigniter4/codeigniter4-standard": "^1.0",
"mikey179/vfsStream": "1.6.*",
"mikey179/vfsstream": "1.6.*",
"phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.3"
},

View File

@ -12,7 +12,7 @@ git checkout $branch
echo -e "${BOLD}Build the framework distributable${NORMAL}"
echo -e "${BOLD}Copy the main files/folders...${NORMAL}"
releasable='app public writable README.md contributing.md env license.txt spark'
releasable='app public writable README.md contributing.md env license.txt spark tests/_support'
for fff in $releasable ; do
if [ -d "$fff" ] ; then
rm -rf $fff
@ -23,10 +23,6 @@ done
echo -e "${BOLD}Override as needed...${NORMAL}"
cp -rf ${CI_DIR}/admin/starter/* .
############# this can only happen after composer create-project/update
#echo -e "${BOLD}Fix paths...${NORMAL}"
#sed -i "/public $systemDirectory = 'system';/s/'system'/'vendor/codeigniter4/framework/system'/" app/Config/Paths.php
#---------------------------------------------------
# And finally, get ready for merging
echo -e "${BOLD}Assemble the pieces...${NORMAL}"

View File

@ -12,7 +12,7 @@ git checkout $branch
echo -e "${BOLD}Build the framework distributable${NORMAL}"
echo -e "${BOLD}Copy the main files/folders...${NORMAL}"
releasable='app docs public system writable contributing.md env license.txt spark'
releasable='app docs public system writable contributing.md env license.txt spark tests/_support'
for fff in $releasable ; do
if [ -d "$fff" ] ; then
rm -rf $fff

View File

@ -47,7 +47,7 @@ The user guide updating and deployment is a bit awkward at the moment, but we ar
## Server Requirements
PHP version 7.1 or higher is required, with the following extensions installed:
PHP version 7.2 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library

View File

@ -0,0 +1,77 @@
<?php namespace Config;
/**
* Holds the paths that are used by the system to
* locate the main directories, app, system, etc.
* Modifying these allows you to re-structure your application,
* share a system folder between multiple applications, and more.
*
* All paths are relative to the project's root folder.
*/
class Paths
{
/*
*---------------------------------------------------------------
* SYSTEM FOLDER NAME
*---------------------------------------------------------------
*
* This variable must contain the name of your "system" folder.
* Include the path if the folder is not in the same directory
* as this file.
*/
public $systemDirectory = __DIR__ . '/../../vendor/codeigniter4/framework/system';
/*
*---------------------------------------------------------------
* APPLICATION FOLDER NAME
*---------------------------------------------------------------
*
* If you want this front controller to use a different "app"
* folder than the default one you can set its name here. The folder
* can also be renamed or relocated anywhere on your getServer. If
* you do, use a full getServer path. For more info please see the user guide:
* http://codeigniter.com/user_guide/general/managing_apps.html
*
* NO TRAILING SLASH!
*/
public $appDirectory = __DIR__ . '/..';
/*
* ---------------------------------------------------------------
* WRITABLE DIRECTORY NAME
* ---------------------------------------------------------------
*
* This variable must contain the name of your "writable" directory.
* The writable directory allows you to group all directories that
* need write permission to a single place that can be tucked away
* for maximum security, keeping it out of the app and/or
* system directories.
*/
public $writableDirectory = __DIR__ . '/../../writable';
/*
* ---------------------------------------------------------------
* TESTS DIRECTORY NAME
* ---------------------------------------------------------------
*
* This variable must contain the name of your "tests" directory.
* The writable directory allows you to group all directories that
* need write permission to a single place that can be tucked away
* for maximum security, keeping it out of the app and/or
* system directories.
*/
public $testsDirectory = __DIR__ . '/../../tests';
/*
* ---------------------------------------------------------------
* VIEW DIRECTORY NAME
* ---------------------------------------------------------------
*
* This variable must contain the name of the directory that
* contains the view files used by your application. By
* default this is in `app/Views`. This value
* is used when no value is provided to `Services::renderer()`.
*/
public $viewDirectory = __DIR__ . '/../Views';
}

View File

@ -5,11 +5,11 @@
"homepage": "https://codeigniter.com",
"license": "MIT",
"require": {
"php": ">=7.1",
"php": ">=7.2",
"codeigniter4/framework": "^4@alpha"
},
"require-dev": {
"mikey179/vfsStream": "1.6.*",
"mikey179/vfsstream": "1.6.*",
"phpunit/phpunit": "^7.0"
},
"scripts": {

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/_support/_bootstrap.php"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false">
<testsuites>
<testsuite name="app">
<directory>./tests</directory>
<exclude>./tests/system</exclude>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./system</directory>
<exclude>
<directory>./system</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -1,6 +1,7 @@
# CodeIgniter 4 User Guide
## What is CodeIgniter?
CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure.
More information can be found at the [official site](http://codeigniter.com).

View File

@ -5,7 +5,7 @@
"homepage": "https://codeigniter.com",
"license": "MIT",
"require": {
"php": ">=7.1",
"php": ">=7.2",
"codeigniter4/framework": "^4"
},
"support": {

View File

@ -21,7 +21,7 @@ class App extends BaseConfig
| environments.
|
*/
public $baseURL = '';
public $baseURL = 'http://localhost:8080';
/*
|--------------------------------------------------------------------------

View File

@ -97,6 +97,7 @@ class Cache extends BaseConfig
'password' => null,
'port' => 6379,
'timeout' => 0,
'database' => 0,
];
/*

View File

@ -7,9 +7,9 @@ class Filters extends BaseConfig
// Makes reading things below nicer,
// and simpler to change out script that's used.
public $aliases = [
'csrf' => \App\Filters\CSRF::class,
'toolbar' => \App\Filters\DebugToolbar::class,
'honeypot' => \App\Filters\Honeypot::class,
'csrf' => \CodeIgniter\Filters\CSRF::class,
'toolbar' => \CodeIgniter\Filters\DebugToolbar::class,
'honeypot' => \CodeIgniter\Filters\Honeypot::class,
];
// Always applied before every request

View File

@ -14,6 +14,16 @@ class Modules
*/
public $enabled = true;
/*
|--------------------------------------------------------------------------
| Auto-Discovery Within Composer Packages Enabled?
|--------------------------------------------------------------------------
|
| If true, then auto-discovery will happen across all namespaces loaded
| by Composer, as well as the namespaces configured locally.
*/
public $discoverInComposer = true;
/*
|--------------------------------------------------------------------------
| Auto-discover Rules

View File

@ -35,7 +35,7 @@ class Paths
*
* NO TRAILING SLASH!
*/
public $appDirectory = __DIR__ . '/../../app';
public $appDirectory = __DIR__ . '/..';
/*
* ---------------------------------------------------------------
@ -73,5 +73,5 @@ class Paths
* default this is in `app/Views`. This value
* is used when no value is provided to `Services::renderer()`.
*/
public $viewDirectory = __DIR__ . '/../../app/Views';
public $viewDirectory = __DIR__ . '/../Views';
}

View File

@ -1,64 +0,0 @@
<?php namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Security\Exceptions\SecurityException;
use Config\Services;
class CSRF implements FilterInterface
{
/**
* Do whatever processing this filter needs to do.
* By default it should not return anything during
* normal execution. However, when an abnormal state
* is found, it should return an instance of
* CodeIgniter\HTTP\Response. If it does, script
* execution will end and that Response will be
* sent back to the client, allowing for error pages,
* redirects, etc.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
*
* @return mixed
*/
public function before(RequestInterface $request)
{
if ($request->isCLI())
{
return;
}
$security = Services::security();
try
{
$security->CSRFVerify($request);
}
catch (SecurityException $e)
{
if (config('App')->CSRFRedirect && ! $request->isAJAX())
{
return redirect()->back()->with('error', $e->getMessage());
}
throw $e;
}
}
//--------------------------------------------------------------------
/**
* We don't have anything to do here.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
*
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response)
{
}
//--------------------------------------------------------------------
}

View File

@ -1,38 +0,0 @@
<?php namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
class DebugToolbar implements FilterInterface
{
/**
* We don't need to do anything here.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
*
* @return mixed
*/
public function before(RequestInterface $request)
{
}
//--------------------------------------------------------------------
/**
* If the debug flag is set (CI_DEBUG) then collect performance
* and debug information and display it in a toolbar.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
*
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response)
{
Services::toolbar()->prepare();
}
//--------------------------------------------------------------------
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use CodeIgniter\Honeypot\Exceptions\HoneypotException;
class Honeypot implements FilterInterface
{
/**
* Checks if Honeypot field is empty; if not
* then the requester is a bot
*
* @param CodeIgniter\HTTP\RequestInterface $request
*
* @return mixed
*/
public function before(RequestInterface $request)
{
$honeypot = Services::honeypot(new \Config\Honeypot());
if ($honeypot->hasContent($request))
{
throw HoneypotException::isBot();
}
}
/**
* Attach a honypot to the current response.
*
* @param CodeIgniter\HTTP\RequestInterface $request
* @param CodeIgniter\HTTP\ResponseInterface $response
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response)
{
$honeypot = Services::honeypot(new \Config\Honeypot());
$honeypot->attachHoneypot($response);
}
}

View File

@ -1,46 +0,0 @@
<?php namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
class Throttle implements FilterInterface
{
/**
* This is a demo implementation of using the Throttler class
* to implement rate limiting for your application.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
*
* @return mixed
*/
public function before(RequestInterface $request)
{
$throttler = Services::throttler();
// Restrict an IP address to no more
// than 1 request per second across the
// entire site.
if ($throttler->check($request->getIPAddress(), 60, MINUTE) === false)
{
return Services::response()->setStatusCode(429);
}
}
//--------------------------------------------------------------------
/**
* We don't have anything to do here.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
*
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response)
{
}
//--------------------------------------------------------------------
}

View File

@ -12,8 +12,9 @@
height: 200px;
width: 155px;
display: inline-block;
opacity: 0.08;
opacity: 0.12;
position: absolute;
z-index: 0;
top: 2rem;
left: 50%;
margin-left: -73px;
@ -32,6 +33,8 @@
margin-top: 145px;
margin-bottom: 0;
color: #222;
position: relative;
z-index: 1;
}
.wrap {
max-width: 1024px;
@ -81,15 +84,11 @@
<div class="wrap">
<h1>Welcome to CodeIgniter</h1>
<p class="version">version <?= CodeIgniter\CodeIgniter::CI_VERSION ?></p>
<div class="logo">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="155.000000px" height="200.000000px" viewBox="0 0 155.000000 200.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none">
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)" fill="#ee2600" stroke="none">
<path d="M737 1963 c22 -79 -7 -185 -78 -290 -18 -26 -107 -122 -197 -213
-239 -240 -336 -371 -403 -544 -79 -206 -78 -408 5 -582 64 -134 212 -264 361
-314 l60 -20 -30 22 c-210 152 -229 387 -48 588 25 27 48 50 51 50 4 0 7 -27
@ -104,6 +103,10 @@
</svg>
</div>
<h1>Welcome to CodeIgniter</h1>
<p class="version">version <?= CodeIgniter\CodeIgniter::CI_VERSION ?></p>
<div class="guide">
<p>The page you are looking at is being generated dynamically by CodeIgniter.</p>

View File

@ -5,7 +5,7 @@
"homepage": "https://codeigniter.com",
"license": "MIT",
"require": {
"php": ">=7.1",
"php": ">=7.2",
"ext-curl": "*",
"ext-intl": "*",
"ext-json": "*",
@ -15,7 +15,7 @@
},
"require-dev": {
"codeigniter4/codeigniter4-standard": "^1.0",
"mikey179/vfsStream": "1.6.*",
"mikey179/vfsstream": "1.6.*",
"phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.3"
},

View File

@ -35,7 +35,7 @@ If you change anything that requires a change to documentation then you will nee
### Compatibility
CodeIgniter4 requires PHP 7.1.
CodeIgniter4 requires PHP 7.2.
### Branching

View File

@ -54,7 +54,8 @@ If you've found a critical vulnerability, we'd be happy to credit you in our
Tips for a Good Issue Report
****************************
Use a descriptive subject line (eg parser library chokes on commas) rather than a vague one (eg. your code broke).
Use a descriptive subject line (eg parser library chokes on commas) rather than
a vague one (eg. your code broke).
Address a single issue in a report.
@ -64,12 +65,13 @@ Explain what you expected to happen, and what did happen.
Include error messages and stacktrace, if any.
Include short code segments if they help to explain.
Use a pastebin or dropbox facility to include longer segments of code or screenshots - do not include them in the issue report itself.
Use a pastebin or dropbox facility to include longer segments of code or
screenshots - do not include them in the issue report itself.
This means setting a reasonable expiry for those, until the issue is resolved or closed.
If you know how to fix the issue, you can do so in your own fork & branch, and submit a pull request.
The issue report information above should be part of that.
If your issue report can describe the steps to reproduce the problem, that is great.
If you can include a unit test that reproduces the problem, that is even better, as it gives whoever is fixing
it a clearer target!
If you can include a unit test that reproduces the problem, that is even better,
as it gives whoever is fixing it a clearer target!

View File

@ -64,16 +64,17 @@ Change Log
The change-log, in the user guide root, needs to be kept up-to-date.
Not all changes will need an entry in it, but new classes, major or BC changes
to existing classes, and bug fixes should.
to existing classes should. Once we have a stable release, bug fixes would
appear in the changelog too.
See the `CodeIgniter 4 change log
<https://github.com/codeigniter4/CodeIgniter/blob/develop/user_guide_src/source/changelog.rst>`_
for an example.
The changelog is independently maintained by the framework release manager
Make sure that your PR descriptions help us decide if the contribution should
be highlighted in the next release after it has been merged.
PHP Compatibility
=================
CodeIgniter4 requires PHP 7.1.
CodeIgniter4 requires PHP 7.2.
Backwards Compatibility
=======================
@ -89,7 +90,7 @@ with earlier versions of the framework.
Mergeability
============
Your PRs need to be mergeable before they will be considered.
Your PRs need to be mergeable and GPG-signed before they will be considered.
We suggest that you synchronize your repository's ``develop`` branch with
that in the main repository, and then your feature branch and

View File

@ -2,18 +2,19 @@
CodeIgniter Internals Overview
##############################
This guide should help contributors understand how the core of the framework works, and what needs to be done
when creating new functionality. Specifically, it details the information needed to create new packages for the
core.
This guide should help contributors understand how the core of the framework works,
and what needs to be done when creating new functionality. Specifically, it
details the information needed to create new packages for the core.
Dependencies
============
All packages should be designed to be completely isolated from the rest of the packages. This will allow
them to be used in projects outside of CodeIgniter. Basically, this means that all dependencies should be
kept to a minimum. Any dependencies must be able to be passed into the constructor. If you do need to use one
of the other core packages, you can create that in the constructor using the Services class, as long as you
provide a way for dependencies to override that::
All packages should be designed to be completely isolated from the rest of the
packages, if possible. This will allow them to be used in projects outside of CodeIgniter.
Basically, this means that any dependencies should be kept to a minimum.
Any dependencies must be able to be passed into the constructor. If you do need to use one
of the other core packages, you can create that in the constructor using the
Services class, as long as you provide a way for dependencies to override that::
public function __construct(Foo $foo=null)
{
@ -26,17 +27,18 @@ Type hinting
============
PHP7 provides the ability to `type hint <http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration>`_
method parameters and return types. Use it where possible. Return type hinting is not always practical, but do try to
make it work.
method parameters and return types. Use it where possible. Return type hinting
is not always practical, but do try to make it work.
At this time, we are not using strict type hinting.
Abstractions
============
The amount of abstraction required to implement a solution should be the minimal amount required. Every layer of
abstraction brings additional levels of technical debt and unnecessary complexity. That said, don't be afraid to
use it when it's needed and can help things.
The amount of abstraction required to implement a solution should be the minimal
amount required. Every layer of abstraction brings additional levels of technical
debt and unnecessary complexity. That said, don't be afraid to use it when it's
needed and can help things.
* Don't create a new container class when an array will do just fine.
* Start simple, refactor as necessary to achieve clean separation of code, but don't overdo it.
@ -44,73 +46,93 @@ use it when it's needed and can help things.
Testing
=======
Any new packages submitted to the framework must be accompanied by unit tests. The target is 80%+ coverage of all
classes within the package.
Any new packages submitted to the framework must be accompanied by unit tests.
The target is 80%+ code coverage of all classes within the package.
* Test only public methods, not protected and private unless the method really needs it due to complexity.
* Don't just test that the method works, but test for all fail states, thrown exceptions, and other pathways through your code.
You should be aware of the extra assertions that we have made, provisions for
accessing private properties for testing, and mock services.
We have also made a **CITestStreamFilter** to capture test output.
Do check out similar tests in ``tests/system/``, and read the "Testing" section
in the user guide, before you dig in to your own.
Some testing needs to be done in a separate process, in order to setup the
PHP globals to mimic test situations properly. See
``tests/system/HTTP/ResponseSendTest`` for an example of this.
Namespaces and Files
====================
All new packages should live under the ``CodeIgniter`` namespace. The package itself will need its own sub-namespace
All new packages should live under the ``CodeIgniter`` namespace.
The package itself will need its own sub-namespace
that collects all related files into one grouping, like ``CodeIgniter\HTTP``.
Files MUST be named the same as the class they hold, and they must match the :doc:`Style Guide <styleguide>`, meaning
CamelCase class and file names. The should be in their own directory that matches the sub-namespace under the **system**
directory.
Files MUST be named the same as the class they hold, and they must match the
:doc:`Style Guide <./styleguide.rst>`, meaning CamelCase class and file names.
They should be in their own directory that matches the sub-namespace under the
**system** directory.
The the Router as an example. The Router lives in the ``CodeIgniter\Router`` namespace. It has two classes,
**RouteCollection** and **Router**, which are in the files, **system/Router/RouteCollection.php** and
Take the Router class as an example. The Router lives in the ``CodeIgniter\Router``
namespace. Its two main classes,
**RouteCollection** and **Router**, are in the files **system/Router/RouteCollection.php** and
**system/Router/Router.php** respectively.
Interfaces
----------
Most base classes should have an interface defined for them. At the very least this allows them to be easily mocked
and passed in other classes as a dependency without breaking the type-hinting. The interface names should match
the name of the class with "Interface" appended to it, like ``RouteCollectionInterface``.
Most base classes should have an interface defined for them.
At the very least this allows them to be easily mocked
and passed to other classes as a dependency, without breaking the type-hinting.
The interface names should match the name of the class with "Interface" appended
to it, like ``RouteCollectionInterface``.
The Router package mentioned above includes the
``CodeIgniter\Router\RouterCollectionInterface`` and ``CodeIgniter\Router\RouterInterface``
``CodeIgniter\Router\RouteCollectionInterface`` and ``CodeIgniter\Router\RouterInterface``
interfaces to provide the abstractions for the two classes in the package.
Handlers
--------
When a package supports multiple "drivers", the convention is to place them in a **Handlers** directory, and
name the child classes as Handlers. You will often find that creating a ``BaseHandler`` the child classes can
extend to be beneficial in keeping the code DRY.
When a package supports multiple "drivers", the convention is to place them in
a **Handlers** directory, and name the child classes as Handlers.
You will often find that creating a ``BaseHandler``, that the child classes can
extend, to be beneficial in keeping the code DRY.
See the Log and Session packages for examples.
Configuration
=============
Should the package require user-configurable settings, you should create a new file just for that package under
**application/Config**. The file name should generally match the package name.
Should the package require user-configurable settings, you should create a new
file just for that package under **app/Config**.
The file name should generally match the package name.
Autoloader
==========
All files within the package should be added to **system/Config/AutoloadConfig.php**, in the "classmap" property.
This is only used for core framework files, and helps to minimize file system scans and keep performance high.
All files within the package should be added to **system/Config/AutoloadConfig.php**,
in the "classmap" property. This is only used for core framework files, and helps
to minimize file system scans and keep performance high.
Command-Line Support
====================
CodeIgniter has never been known for it's strong CLI support. However, if your package could benefit from it, create a
new file under **system/Commands**. The class contained within is simply a controller that is intended for CLI
usage only. The ``index()`` method should provide a list of available commands provided by that package.
CodeIgniter has never been known for it's strong CLI support. However, if your
package could benefit from it, create a new file under **system/Commands**.
The class contained within is simply a controller that is intended for CLI
usage only. The ``index()`` method should provide a list of available commands
provided by that package.
Routes must be added to **system/Config/Routes.php** using the ``cli()`` method to ensure it is not accessible
through the browser, but is restricted to the CLI only.
Routes must be added to **system/Config/Routes.php** using the ``cli()`` method
to ensure it is not accessible through the browser, but is restricted to the CLI only.
See the **MigrationsCommand** file for an example.
Documentation
=============
All packages must contain appropriate documentation that matches the tone and style of the rest of the user guide.
In most cases, the top portion of the package's page should be treated in tutorial fashion, while the second
half would be a class reference.
All packages must contain appropriate documentation that matches the tone and
style of the rest of the user guide. In most cases, the top portion of the package's
page should be treated in tutorial fashion, while the second half would be a class reference.

View File

@ -9,24 +9,22 @@ The developer pushing a commit as part of a PR isn't necessarily the person
who committed it originally, if the commit is not signed. This distorts the
commit history and makes it hard to tell where code came from.
If a person "signs" a commit, they are free to use any name, specifically
If a person "signs off" a commit, they are free to use any name, specifically
one not their own. Again, the commit history cannot be relied on to determine
the origin of the code, if one developer is spoofing another. A malicious person
could commit bad code (for instance a virus) and make it look like another
developer created it.
The best solution, while not fool-proof, is to "securely sign" your
commits. Such commits are digitally signed, with a GPG-key, and
commits. Such commits are digitally signed, with a GPG-key
associated with your github account. It still isn't foolproof, because
a malicious developer could create a bogus email and account, but it is
more reliable than an unsigned or a "signed" commit.
more reliable than an unsigned or a "signed-off by" commit.
If you don't sign your commits, we **may** accept your contribution,
assuming it meets usefulness and contribution guidelines, but only
if it isn't critical code and only after checking it carefully.
If code performs an important role, we will insist that it be signed, and if
it is critical code (however we interpret that), we will insist that your
contributions be securely signed.
If code performs an important role, we will insist that it be securely signed.
Read below to find out how to sign your commits :)

View File

@ -25,14 +25,14 @@ which requires all pull requests to be sent to the "develop" branch. This is
where the next planned version will be developed. The "master" branch will
always contain the latest stable version and is kept clean so a "hotfix" (e.g:
an emergency security patch) can be applied to master to create a new version,
without worrying about other features holding it up. For this reason all
without worrying about other features holding it up. For this reason, all
commits need to be made to "develop" and any sent to "master" will be closed
automatically. If you have multiple changes to submit, please place each
change into their own branch on your fork.
One thing at a time: a pull request should only contain one change. That does
not mean only one commit, but one change - however many commits it took. The
reason for this is that if you change X and Y but send a pull request for both
reason for this is that if you change X and Y but send a single pull request for both
at the same time, we might really want X but disagree with Y, meaning we
cannot merge the request. Using the Git-Flow branching model you can create
new branches for both of these features and send two requests.
@ -45,7 +45,8 @@ in your github account. You can make changes to your forked repository, while
you cannot do the same with the shared one - you have to submit pull requests
to it instead.
`Creating a fork <https://help.github.com/articles/fork-a-repo/>`_ is done through the Github website. Navigate to `our
`Creating a fork <https://help.github.com/articles/fork-a-repo/>`_
is done through the Github website. Navigate to `our
repository <https://github.com/codeigniter4/CodeIgniter4>`_,
click the **Fork** button in the top-right of the page, and choose which account or
organization of yours should contain that fork.
@ -70,7 +71,7 @@ Synching
Within your local repository, Git will have created an alias, **origin**, for the
Github repository it is bound to. You want to create an alias for the shared
repository, so that you can "synch" the two, making sure that your repository
repository as well, so that you can "synch" the two, making sure that your repository
includes any other contributions that have been merged by us into the shared repo::
git remote add upstream UPSTREAM_URL
@ -98,8 +99,11 @@ Branching Revisited
The top of this page talked about the **master** and **develop** branches.
The *best practice* for your work is to create a *feature branch* locally,
to hold a group of related changes (source, unit testing, documentation,
change log, etc). This local branch should be named appropriately,
for instance "fix/problem123" or "new/mind-reader".
change log, etc).
This local branch should be named appropriately, for instance
"fix/problem123" or "new/mind-reader". The slashes in these branch names is
optional, and implies a sort of namespacing if used.
For instance, make sure you are in the *develop* branch, and create a
new feature branch, based on *develop*, for a new feature you are creating::
@ -113,7 +117,7 @@ Committing
==========
Your local changes need to be *committed* to save them in your local repository.
This is where `contribution signing <signing>`_ comes in.
This is where `contribution signing <./signing.rst>`_ comes in.
You can have as many commits in a branch as you need to "get it right".
For instance, to commit your work from a debugging session::
@ -179,13 +183,13 @@ Make sure that the PR title is helpful for the maintainers and other developers.
Add any comments appropriate, for instance asking for review.
.. note::
If you do not provide a title for your PR, the odds of it being summarily rejected
If you do not provide a title or description for your PR, the odds of it being summarily rejected
rise astronomically.
When your PR is submitted, a continuous integration task will be triggered,
running all the unit tests as well as any other checking we have configured for it.
If the unit tests fail, or if there are merge conflicts, your PR will not
be mergeable until fixed.
be mergeable until those are fixed.
Fix such changes locally, commit them properly, and then push your branch again.
That will update the PR automatically, and re-run the CI tests. You don't need

6
env
View File

@ -10,6 +10,12 @@
# at the beginning of the line.
#--------------------------------------------------------------------
#--------------------------------------------------------------------
# ENVIRONMENT
#--------------------------------------------------------------------
# CI_ENVIRONMENT = production
#--------------------------------------------------------------------
# APP
#--------------------------------------------------------------------

View File

@ -1,7 +1,7 @@
<?php
// Valid PHP Version?
$minPHPVersion = '7.1';
$minPHPVersion = '7.2';
if (phpversion() < $minPHPVersion)
{
die("Your PHP version must be {$minPHPVersion} or higher to run CodeIgniter. Current version: " . phpversion());
@ -14,6 +14,7 @@ define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR);
// Location of the Paths config file.
// This is the line that might need to be changed, depending on your folder structure.
$pathsPath = FCPATH . '../app/Config/Paths.php';
// ^^^ Change this if you move your application folder
/*
*---------------------------------------------------------------

1
spark
View File

@ -34,6 +34,7 @@ define('FCPATH', __DIR__ . '/public' . DIRECTORY_SEPARATOR);
// Load our paths config file
require 'app/Config/Paths.php';
// ^^^ Change this line if you move your application folder
$paths = new Config\Paths();

View File

@ -1,5 +1,7 @@
<?php namespace CodeIgniter\Autoloader;
use Composer\Autoload\ClassLoader;
/**
* CodeIgniter
*
@ -97,10 +99,11 @@ class Autoloader
* the valid parts that we'll need.
*
* @param \Config\Autoload $config
* @param \Config\Modules $moduleConfig
*
* @return $this
*/
public function initialize(\Config\Autoload $config)
public function initialize(\Config\Autoload $config, \Config\Modules $moduleConfig)
{
// We have to have one or the other, though we don't enforce the need
// to have both present in order to work.
@ -119,7 +122,11 @@ class Autoloader
$this->classmap = $config->classmap;
}
unset($config);
// Should we load through Composer's namespaces, also?
if ($moduleConfig->discoverInComposer)
{
$this->discoverComposerNamespaces();
}
return $this;
}
@ -303,8 +310,8 @@ class Autoloader
/**
* Attempts to load the class from common locations in previous
* version of CodeIgniter, namely 'application/libraries', and
* 'application/Models'.
* version of CodeIgniter, namely 'app/Libraries', and
* 'app/Models'.
*
* @param string $class The class name. This typically should NOT have a namespace.
*
@ -391,5 +398,37 @@ class Autoloader
return $filename;
}
//--------------------------------------------------------------------
/**
* Locates all PSR4 compatible namespaces from Composer.
*/
protected function discoverComposerNamespaces()
{
if (! is_file(COMPOSER_PATH))
{
return false;
}
$composer = include COMPOSER_PATH;
$paths = $composer->getPrefixesPsr4();
unset($composer);
// Get rid of CodeIgniter so we don't have duplicates
if (isset($paths['CodeIgniter\\']))
{
unset($paths['CodeIgniter\\']);
}
// Composer stores paths with trailng slash. We don't.
$newPaths = [];
foreach ($paths as $key => $value)
{
$newPaths[rtrim($key, '\\ ')] = $value;
}
$this->prefixes = array_merge($this->prefixes, $newPaths);
}
}

View File

@ -119,7 +119,8 @@ class FileLocator
{
continue;
}
$path = $this->getNamespaces($prefix);
$path = $this->getNamespaces($prefix);
$filename = implode('/', $segments);
break;
}
@ -201,8 +202,8 @@ class FileLocator
* $locator->search('Config/Routes.php');
* // Assuming PSR4 namespaces include foo and bar, might return:
* [
* 'application/modules/foo/Config/Routes.php',
* 'application/modules/bar/Config/Routes.php',
* 'app/Modules/foo/Config/Routes.php',
* 'app/Modules/bar/Config/Routes.php',
* ]
*
* @param string $path
@ -268,7 +269,9 @@ class FileLocator
{
$path = $this->autoloader->getNamespace($prefix);
return isset($path[0]) ? $path[0] : '';
return isset($path[0])
? rtrim($path[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR
: '';
}
$namespaces = [];
@ -279,7 +282,7 @@ class FileLocator
{
$namespaces[] = [
'prefix' => $prefix,
'path' => $path,
'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
];
}
}

View File

@ -60,6 +60,7 @@ class RedisHandler implements CacheInterface
'password' => null,
'port' => 6379,
'timeout' => 0,
'database' => 0,
];
/**
@ -116,7 +117,12 @@ class RedisHandler implements CacheInterface
if (isset($config['password']) && ! $this->redis->auth($config['password']))
{
// log_message('error', 'Cache: Redis authentication failed.');
log_message('error', 'Cache: Redis authentication failed.');
}
if (isset($config['database']) && ! $this->redis->select($config['database']))
{
log_message('error', 'Cache: Redis select database failed.');
}
}
catch (\RedisException $e)

View File

@ -61,7 +61,7 @@ class CodeIgniter
/**
* The current version of CodeIgniter Framework
*/
const CI_VERSION = '4.0.0-alpha.4';
const CI_VERSION = '4.0.0-beta.1';
/**
* App startup time.
@ -199,7 +199,7 @@ class CodeIgniter
* @param \CodeIgniter\Router\RouteCollectionInterface $routes
* @param boolean $returnResponse
*
* @throws \CodeIgniter\HTTP\RedirectException
* @throws \CodeIgniter\Filters\Exceptions\FilterException
* @throws \Exception
*/
public function run(RouteCollectionInterface $routes = null, bool $returnResponse = false)
@ -234,7 +234,7 @@ class CodeIgniter
{
return $this->handleRequest($routes, $cacheConfig, $returnResponse);
}
catch (Router\RedirectException $e)
catch (\CodeIgniter\Filters\Exceptions\FilterException $e)
{
$logger = Services::logger();
$logger->info('REDIRECTED ROUTE at ' . $e->getMessage());
@ -533,7 +533,7 @@ class CodeIgniter
*
* @throws \Exception
*
* @return boolean
* @return bool|\CodeIgniter\HTTP\ResponseInterface
*/
public function displayCache($config)
{
@ -564,7 +564,9 @@ class CodeIgniter
$this->response->setBody($output);
return $this->response;
};
}
return false;
}
//--------------------------------------------------------------------
@ -574,7 +576,7 @@ class CodeIgniter
*
* @param integer $time
*
* @return $this
* @return void
*/
public static function cache(int $time)
{

View File

@ -117,7 +117,7 @@ class MigrateRollback extends BaseCommand
}
try
{
if (! $this->isAllNamespace())
if (! $this->isAllNamespace($params))
{
$namespace = $params['-n'] ?? CLI::getOption('n');
$runner->version(0, $namespace);

View File

@ -48,7 +48,7 @@ use CodeIgniter\CLI\CLI;
*/
class Serve extends BaseCommand
{
protected $minPHPVersion = '7.1';
protected $minPHPVersion = '7.2';
protected $group = 'CodeIgniter';
protected $name = 'serve';

View File

@ -102,6 +102,35 @@ if (! function_exists('config'))
//--------------------------------------------------------------------
if (! function_exists('db_connnect'))
{
/**
* Grabs a database connection and returns it to the user.
*
* This is a convenience wrapper for \Config\Database::connect()
* and supports the same parameters. Namely:
*
* When passing in $db, you may pass any of the following to connect:
* - group name
* - existing connection instance
* - array of database configuration values
*
* If $getShared === false then a new connection instance will be provided,
* otherwise it will all calls will return the same instance.
*
* @param \CodeIgniter\Database\ConnectionInterface|array|string $db
* @param boolean $getShared
*
* @return \CodeIgniter\Database\BaseConnection
*/
function db_connect($db = null, bool $getShared = true)
{
return \Config\Database::connect($db, $getShared);
}
}
//--------------------------------------------------------------------
if (! function_exists('view'))
{
/**
@ -540,7 +569,7 @@ if (! function_exists('helper'))
* both in and out of the 'helpers' directory of a namespaced directory.
*
* Will load ALL helpers of the matching name, in the following order:
* 1. application/Helpers
* 1. app/Helpers
* 2. {namespace}/Helpers
* 3. system/Helpers
*
@ -571,44 +600,62 @@ if (! function_exists('helper'))
$filename .= '_helper';
}
$paths = $loader->search('Helpers/' . $filename);
if (! empty($paths))
// If the file is namespaced, we'll just grab that
// file and not search for any others
if (strpos($filename, '\\') !== false)
{
foreach ($paths as $path)
$path = $loader->locateFile($filename, 'Helpers');
if (empty($path))
{
if (strpos($path, APPPATH) === 0)
throw \CodeIgniter\Files\Exceptions\FileNotFoundException::forFileNotFound($filename);
}
$includes[] = $path;
}
// No namespaces, so search in all available locations
else
{
$paths = $loader->search('Helpers/' . $filename);
if (! empty($paths))
{
foreach ($paths as $path)
{
// @codeCoverageIgnoreStart
$appHelper = $path;
// @codeCoverageIgnoreEnd
}
elseif (strpos($path, SYSTEMPATH) === 0)
{
$systemHelper = $path;
}
else
{
$localIncludes[] = $path;
if (strpos($path, APPPATH) === 0)
{
// @codeCoverageIgnoreStart
$appHelper = $path;
// @codeCoverageIgnoreEnd
}
elseif (strpos($path, SYSTEMPATH) === 0)
{
$systemHelper = $path;
}
else
{
$localIncludes[] = $path;
}
}
}
}
// App-level helpers should override all others
if (! empty($appHelper))
{
// @codeCoverageIgnoreStart
$includes[] = $appHelper;
// @codeCoverageIgnoreEnd
}
// App-level helpers should override all others
if (! empty($appHelper))
{
// @codeCoverageIgnoreStart
$includes[] = $appHelper;
// @codeCoverageIgnoreEnd
}
// All namespaced files get added in next
$includes = array_merge($includes, $localIncludes);
// All namespaced files get added in next
$includes = array_merge($includes, $localIncludes);
// And the system default one should be added in last.
if (! empty($systemHelper))
{
$includes[] = $systemHelper;
// And the system default one should be added in last.
if (! empty($systemHelper))
{
$includes[] = $systemHelper;
}
}
}

View File

@ -207,7 +207,7 @@ class BaseService
if ($init_autoloader)
{
static::autoloader()->initialize(new \Config\Autoload());
static::autoloader()->initialize(new \Config\Autoload(), new \Config\Modules());
}
}

View File

@ -97,6 +97,16 @@ class Config
//--------------------------------------------------------------------
/**
* Resets the instances array
*/
public static function reset()
{
static::$instances = [];
}
//--------------------------------------------------------------------
/**
* Find configuration class and create instance
*

View File

@ -466,7 +466,7 @@ class Services extends BaseService
*
* @return \CodeIgniter\View\Parser
*/
public static function parser($viewPath = APPPATH . 'Views/', $config = null, bool $getShared = true)
public static function parser($viewPath = null, $config = null, bool $getShared = true)
{
if ($getShared)
{
@ -478,6 +478,12 @@ class Services extends BaseService
$config = new \Config\View();
}
if (is_null($viewPath))
{
$paths = config('Paths');
$viewPath = $paths->viewDirectory;
}
return new \CodeIgniter\View\Parser($config, $viewPath, static::locator(true), CI_DEBUG, static::logger(true));
}
@ -808,7 +814,7 @@ class Services extends BaseService
if (is_null($config))
{
$config = new \Config\Validation();
$config = config('Validation');
}
return new \CodeIgniter\Validation\Validation($config, static::renderer());

View File

@ -35,7 +35,7 @@
* @since Version 3.0.0
* @filesource
*/
use CodeIgniter\Config\Services;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Validation\Validation;
@ -106,7 +106,7 @@ class Controller
* @param ResponseInterface $response
* @param \Psr\Log\LoggerInterface $logger
*
* @throws \CodeIgniter\HTTP\RedirectException
* @throws \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
@ -135,7 +135,7 @@ class Controller
* considered secure for. Only with HSTS header.
* Default value is 1 year.
*
* @throws \CodeIgniter\HTTP\RedirectException
* @throws \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public function forceHTTPS(int $duration = 31536000)
{
@ -179,8 +179,8 @@ class Controller
* A shortcut to performing validation on input data. If validation
* is not successful, a $errors property will be set on this class.
*
* @param array $rules
* @param array $messages An array of custom error messages
* @param array|string $rules
* @param array $messages An array of custom error messages
*
* @return boolean
*/

View File

@ -664,7 +664,7 @@ class BaseBuilder
$op = $this->getOperator($k);
$k = trim(str_replace($op, '', $k));
$bind = $this->setBind($k, $v);
$bind = $this->setBind($k, $v, $escape);
if (empty($op))
{
@ -813,8 +813,8 @@ class BaseBuilder
$not = ($not) ? ' NOT' : '';
$where_in = array_values($values);
$this->binds[$ok] = $where_in;
$where_in = array_values($values);
$ok = $this->setBind($ok, $where_in, $escape);
$prefix = empty($this->QBWhere) ? $this->groupGetType('') : $this->groupGetType($type);
@ -955,19 +955,19 @@ class BaseBuilder
if ($side === 'none')
{
$bind = $this->setBind($k, $v);
$bind = $this->setBind($k, $v, $escape);
}
elseif ($side === 'before')
{
$bind = $this->setBind($k, "%$v");
$bind = $this->setBind($k, "%$v", $escape);
}
elseif ($side === 'after')
{
$bind = $this->setBind($k, "$v%");
$bind = $this->setBind($k, "$v%", $escape);
}
else
{
$bind = $this->setBind($k, "%$v%");
$bind = $this->setBind($k, "%$v%", $escape);
}
$like_statement = $this->_like_statement($prefix, $k, $not, $bind, $insensitiveSearch);
@ -1345,7 +1345,7 @@ class BaseBuilder
{
if ($escape)
{
$bind = $this->setBind($k, $v);
$bind = $this->setBind($k, $v, $escape);
$this->QBSet[$this->db->protectIdentifiers($k, false, $escape)] = ":$bind:";
}
else
@ -1399,11 +1399,32 @@ class BaseBuilder
$this->resetSelect();
}
return $select;
return $this->compileFinalQuery($select);
}
//--------------------------------------------------------------------
/**
* Returns a finalized, compiled query string with the bindings
* inserted and prefixes swapped out.
*
* @param string $sql
*
* @return mixed|string
*/
protected function compileFinalQuery(string $sql): string
{
$query = new Query($this->db);
$query->setQuery($sql, $this->binds, false);
if (! empty($this->db->swapPre) && ! empty($this->db->DBPrefix))
{
$query->swapPrefix($this->db->DBPrefix, $this->db->swapPre);
}
return $query->getQuery();
}
/**
* Get
*
@ -1423,7 +1444,10 @@ class BaseBuilder
{
$this->limit($limit, $offset);
}
$result = $returnSQL ? $this->getCompiledSelect() : $this->db->query($this->compileSelect(), $this->binds);
$result = $returnSQL
? $this->getCompiledSelect()
: $this->db->query($this->compileSelect(), $this->binds, false);
if ($reset === true)
{
@ -1461,7 +1485,7 @@ class BaseBuilder
return $sql;
}
$query = $this->db->query($sql);
$query = $this->db->query($sql, null, false);
if (empty($query->getResult()))
{
return 0;
@ -1510,7 +1534,7 @@ class BaseBuilder
return $sql;
}
$result = $this->db->query($sql, $this->binds);
$result = $this->db->query($sql, $this->binds, false);
if ($reset === true)
{
@ -1559,7 +1583,7 @@ class BaseBuilder
$this->limit($limit, $offset);
}
$result = $this->db->query($this->compileSelect(), $this->binds);
$result = $this->db->query($this->compileSelect(), $this->binds, false);
$this->resetSelect();
return $result;
@ -1624,7 +1648,7 @@ class BaseBuilder
}
else
{
$this->db->query($sql, $this->binds);
$this->db->query($sql, $this->binds, false);
$affected_rows += $this->db->affectedRows();
}
}
@ -1696,7 +1720,7 @@ class BaseBuilder
$clean = [];
foreach ($row as $k => $value)
{
$clean[] = ':' . $this->setBind($k, $value) . ':';
$clean[] = ':' . $this->setBind($k, $value, $escape) . ':';
}
$row = $clean;
@ -1741,7 +1765,7 @@ class BaseBuilder
$this->resetWrite();
}
return $sql;
return $this->compileFinalQuery($sql);
}
//--------------------------------------------------------------------
@ -1779,7 +1803,7 @@ class BaseBuilder
{
$this->resetWrite();
$result = $this->db->query($sql, $this->binds);
$result = $this->db->query($sql, $this->binds, false);
// Clear our binds so we don't eat up memory
$this->binds = [];
@ -1868,7 +1892,7 @@ class BaseBuilder
$this->resetWrite();
return $returnSQL ? $sql : $this->db->query($sql, $this->binds);
return $returnSQL ? $sql : $this->db->query($sql, $this->binds, false);
}
//--------------------------------------------------------------------
@ -1931,7 +1955,7 @@ class BaseBuilder
$this->resetWrite();
}
return $sql;
return $this->compileFinalQuery($sql);
}
//--------------------------------------------------------------------
@ -1981,7 +2005,7 @@ class BaseBuilder
{
$this->resetWrite();
if ($this->db->query($sql, $this->binds))
if ($this->db->query($sql, $this->binds, false))
{
// Clear our binds so we don't eat up memory
$this->binds = [];
@ -2115,7 +2139,7 @@ class BaseBuilder
}
else
{
$this->db->query($sql, $this->binds);
$this->db->query($sql, $this->binds, false);
$affected_rows += $this->db->affectedRows();
}
@ -2203,7 +2227,7 @@ class BaseBuilder
$index_set = true;
}
$bind = $this->setBind($k2, $v2);
$bind = $this->setBind($k2, $v2, $escape);
$clean[$this->db->protectIdentifiers($k2, false, $escape)] = ":$bind:";
}
@ -2242,7 +2266,7 @@ class BaseBuilder
$this->resetWrite();
return $this->db->query($sql);
return $this->db->query($sql, null, false);
}
//--------------------------------------------------------------------
@ -2271,7 +2295,7 @@ class BaseBuilder
$this->resetWrite();
return $this->db->query($sql);
return $this->db->query($sql, null, false);
}
//--------------------------------------------------------------------
@ -2312,7 +2336,7 @@ class BaseBuilder
$sql = $this->delete($table, '', null, $reset);
$this->returnDeleteSQL = false;
return $sql;
return $this->compileFinalQuery($sql);
}
//--------------------------------------------------------------------
@ -2371,7 +2395,7 @@ class BaseBuilder
$this->resetWrite();
}
return ($returnSQL === true) ? $sql : $this->db->query($sql, $this->binds);
return ($returnSQL === true) ? $sql : $this->db->query($sql, $this->binds, false);
}
//--------------------------------------------------------------------
@ -2390,7 +2414,7 @@ class BaseBuilder
$sql = $this->_update($this->QBFrom[0], [$column => "{$column} + {$value}"]);
return $this->db->query($sql, $this->binds);
return $this->db->query($sql, $this->binds, false);
}
//--------------------------------------------------------------------
@ -2409,7 +2433,7 @@ class BaseBuilder
$sql = $this->_update($this->QBFrom[0], [$column => "{$column}-{$value}"]);
return $this->db->query($sql, $this->binds);
return $this->db->query($sql, $this->binds, false);
}
//--------------------------------------------------------------------
@ -2918,17 +2942,24 @@ class BaseBuilder
/**
* Stores a bind value after ensuring that it's unique.
* While it might be nicer to have named keys for our binds array
* with PHP 7+ we get a huge memory/performance gain with indexed
* arrays instead, so lets take advantage of that here.
*
* @param string $key
* @param null $value
* @param string $key
* @param null $value
* @param boolean $escape
*
* @return string
*/
protected function setBind(string $key, $value = null)
protected function setBind(string $key, $value = null, bool $escape = true)
{
if (! array_key_exists($key, $this->binds))
{
$this->binds[$key] = $value;
$this->binds[$key] = [
$value,
$escape,
];
return $key;
}
@ -2937,10 +2968,13 @@ class BaseBuilder
while (array_key_exists($key . $count, $this->binds))
{
++ $count;
++$count;
}
$this->binds[$key . $count] = $value;
$this->binds[$key . $count] = [
$value,
$escape,
];
return $key . $count;
}

View File

@ -596,12 +596,14 @@ abstract class BaseConnection implements ConnectionInterface
* Should automatically handle different connections for read/write
* queries if needed.
*
* @param string $sql
* @param array ...$binds
* @param string $queryClass
* @param string $sql
* @param array ...$binds
* @param boolean $setEscapeFlags
* @param string $queryClass
*
* @return BaseResult|Query|false
*/
public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Database\\Query')
public function query(string $sql, $binds = null, bool $setEscapeFlags = true, $queryClass = 'CodeIgniter\\Database\\Query')
{
if (empty($this->connID))
{
@ -609,13 +611,12 @@ abstract class BaseConnection implements ConnectionInterface
}
$resultClass = str_replace('Connection', 'Result', get_class($this));
/**
* @var Query $query
*/
$query = new $queryClass($this);
$query->setQuery($sql, $binds);
$query->setQuery($sql, $binds, $setEscapeFlags);
if (! empty($this->swapPre) && ! empty($this->DBPrefix))
{
@ -1709,6 +1710,20 @@ abstract class BaseConnection implements ConnectionInterface
//--------------------------------------------------------------------
/**
* Empties our data cache. Especially helpful during testing.
*
* @return $this
*/
public function resetDataCache()
{
$this->dataCache = [];
return $this;
}
//--------------------------------------------------------------------
/**
* Returns the last error code and message.
*

View File

@ -74,6 +74,12 @@ class Config extends BaseConfig
*/
public static function connect($group = null, bool $getShared = true)
{
// If a DB connection is passed in, just pass it back
if ($group instanceof BaseConnection)
{
return $group;
}
if (is_array($group))
{
$config = $group;
@ -135,44 +141,7 @@ class Config extends BaseConfig
*/
public static function forge($group = null)
{
// Allow custom connections to be sent in
if (is_array($group))
{
$config = $group;
$group = 'custom-' . md5(json_encode($config));
}
else
{
$config = config('Database');
}
static::ensureFactory();
if (empty($group))
{
$group = ENVIRONMENT === 'testing' ? 'tests' : $config->defaultGroup;
}
if (is_string($group) && ! isset($config->$group) && ! is_array($config))
{
throw new \InvalidArgumentException($group . ' is not a valid database connection group.');
}
if (! isset(static::$instances[$group]))
{
if (is_array($config))
{
$db = static::connect($config);
}
else
{
$db = static::connect($group);
}
}
else
{
$db = static::$instances[$group];
}
$db = static::connect($group);
return static::$factory->loadForge($db);
}
@ -182,50 +151,13 @@ class Config extends BaseConfig
/**
* Returns a new instance of the Database Utilities class.
*
* @param string|null $group
* @param string|array|null $group
*
* @return BaseUtils
*/
public static function utils(string $group = null)
public static function utils($group = null)
{
// Allow custom connections to be sent in
if (is_array($group))
{
$config = $group;
$group = 'custom-' . md5(json_encode($config));
}
else
{
$config = config('Database');
}
static::ensureFactory();
if (empty($group))
{
$group = ENVIRONMENT === 'testing' ? 'tests' : $config->defaultGroup;
}
if (is_string($group) && ! isset($config->$group) && ! is_array($config))
{
throw new \InvalidArgumentException($group . ' is not a valid database connection group.');
}
if (! isset(static::$instances[$group]))
{
if (is_array($config))
{
$db = static::connect($config);
}
else
{
$db = static::connect($group);
}
}
else
{
$db = static::$instances[$group];
}
$db = static::connect($group);
return static::$factory->loadUtils($db);
}
@ -241,7 +173,7 @@ class Config extends BaseConfig
*/
public static function seeder(string $group = null)
{
$config = new \Config\Database();
$config = config('Database');
return new Seeder($config, static::connect($group));
}

View File

@ -45,4 +45,9 @@ class DataException extends \RuntimeException implements ExceptionInterface
{
return new static(lang('Database.invalidAllowedFields', [$model]));
}
public static function forTableNotFound(string $table)
{
return new static(lang('Database.tableNotFound', [$table]));
}
}

View File

@ -468,7 +468,7 @@ class Forge
if (is_bool($sql))
{
$this->_reset();
$this->reset();
if ($sql === false)
{
if ($this->db->DBDebug)
@ -494,7 +494,7 @@ class Forge
}
}
$this->_reset();
$this->reset();
return $result;
}
@ -730,7 +730,7 @@ class Forge
}
$sqls = $this->_alterTable('ADD', $this->db->DBPrefix . $table, $this->_processFields());
$this->_reset();
$this->reset();
if ($sqls === false)
{
if ($this->db->DBDebug)
@ -806,7 +806,7 @@ class Forge
}
$sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields());
$this->_reset();
$this->reset();
if ($sqls === false)
{
if ($this->db->DBDebug)
@ -817,11 +817,14 @@ class Forge
return false;
}
for ($i = 0, $c = count($sqls); $i < $c; $i++)
if ($sqls !== null)
{
if ($this->db->query($sqls[$i]) === false)
for ($i = 0, $c = count($sqls); $i < $c; $i++)
{
return false;
if ($this->db->query($sqls[$i]) === false)
{
return false;
}
}
}
@ -1251,7 +1254,7 @@ class Forge
*
* @return void
*/
protected function _reset()
public function reset()
{
$this->fields = $this->keys = $this->uniqueKeys = $this->primaryKeys = $this->foreignKeys = [];
}

View File

@ -126,17 +126,37 @@ class MigrationRunner
*/
protected $cliMessages = [];
/**
* Tracks whether we have already ensured
* the table exists or not.
*
* @var boolean
*/
protected $tableChecked = false;
/**
* The full path to locate migration files.
*
* @var string
*/
protected $path;
//--------------------------------------------------------------------
/**
* Constructor.
*
* @param BaseConfig $config
* @param \CodeIgniter\Database\ConnectionInterface $db
* When passing in $db, you may pass any of the following to connect:
* - group name
* - existing connection instance
* - array of database configuration values
*
* @param BaseConfig $config
* @param \CodeIgniter\Database\ConnectionInterface|array|string $db
*
* @throws ConfigException
*/
public function __construct(BaseConfig $config, ConnectionInterface $db = null)
public function __construct(BaseConfig $config, $db = null)
{
$this->enabled = $config->enabled ?? false;
$this->type = $config->type ?? 'timestamp';
@ -147,15 +167,10 @@ class MigrationRunner
$this->namespace = APP_NAMESPACE;
// get default database group
$config = new \Config\Database();
$config = config('Database');
$this->group = $config->defaultGroup;
unset($config);
if (empty($this->table))
{
throw ConfigException::forMissingMigrationsTable();
}
if (! in_array($this->type, ['sequential', 'timestamp']))
{
throw ConfigException::forInvalidMigrationType($this->type);
@ -166,9 +181,7 @@ class MigrationRunner
// If no db connection passed in, use
// default database group.
$this->db = ! empty($db) ? $db : \Config\Database::connect();
$this->ensureTable();
$this->db = db_connect($db);
}
//--------------------------------------------------------------------
@ -192,6 +205,9 @@ class MigrationRunner
{
throw ConfigException::forDisabledMigrations();
}
$this->ensureTable();
// Set Namespace if not null
if (! is_null($namespace))
{
@ -204,6 +220,12 @@ class MigrationRunner
$this->setGroup($group);
}
// Sequential versions need adjusting to 3 places so they can be found later.
if ($this->type === 'sequential')
{
$targetVersion = str_pad($targetVersion, 3, '0', STR_PAD_LEFT);
}
$migrations = $this->findMigrations();
if (empty($migrations))
@ -284,6 +306,8 @@ class MigrationRunner
*/
public function latest(string $namespace = null, string $group = null)
{
$this->ensureTable();
// Set Namespace if not null
if (! is_null($namespace))
{
@ -315,6 +339,8 @@ class MigrationRunner
*/
public function latestAll(string $group = null)
{
$this->ensureTable();
// Set database group if not null
if (! is_null($group))
{
@ -322,7 +348,7 @@ class MigrationRunner
}
// Get all namespaces form PSR4 paths.
$config = new Autoload();
$config = config('Autoload');
$namespaces = $config->psr4;
foreach ($namespaces as $namespace => $path)
@ -361,6 +387,8 @@ class MigrationRunner
*/
public function current(string $group = null)
{
$this->ensureTable();
// Set database group if not null
if (! is_null($group))
{
@ -380,33 +408,59 @@ class MigrationRunner
public function findMigrations()
{
$migrations = [];
// Get namespace location form PSR4 paths.
$config = new Autoload();
helper('filesystem');
$location = $config->psr4[$this->namespace];
// If $this->path contains a valid directory use it.
if (! empty($this->path))
{
$dir = rtrim($this->path, DIRECTORY_SEPARATOR) . '/';
}
// Otherwise, get namespace location form PSR4 paths
// and add Database/Migrations for a standard loation.
else
{
$config = config('Autoload');
// Setting migration directories.
$dir = rtrim($location, DIRECTORY_SEPARATOR) . '/Database/Migrations/';
$location = $config->psr4[$this->namespace];
// Setting migration directories.
$dir = rtrim($location, DIRECTORY_SEPARATOR) . '/Database/Migrations/';
}
// Load all *_*.php files in the migrations path
foreach (glob($dir . '*_*.php') as $file)
// We can't use glob if we want it to be testable....
$files = get_filenames($dir, true);
foreach ($files as $file)
{
if (substr($file, -4) !== '.php')
{
continue;
}
// Remove the extension
$name = basename($file, '.php');
// Filter out non-migration files
if (preg_match($this->regex, $name))
{
// Create migration object using stdClass
$migration = new \stdClass();
// Get migration version number
$migration->version = $this->getMigrationNumber($name);
$migration->name = $this->getMigrationName($name);
$migration->path = $file;
$migration->path = ! empty($this->path) && strpos($file, $this->path) !== 0
? $this->path . $file
: $file;
// Add to migrations[version]
$migrations[$migration->version] = $migration;
}
}
ksort($migrations);
return $migrations;
}
@ -436,7 +490,7 @@ class MigrationRunner
}
// Check if $targetversion file is found
if ($targetversion !== '0' && ! array_key_exists($targetversion, $migrations))
if ((int)$targetversion !== 0 && ! array_key_exists($targetversion, $migrations))
{
if ($this->silent)
{
@ -458,14 +512,14 @@ class MigrationRunner
{
if ($this->type === 'sequential' && abs($migration->version - $loop) > 1)
{
throw new \RuntimeException(lang('Migration.gap') . ' ' . $migration->version);
throw new \RuntimeException(lang('Migrations.gap') . ' ' . $migration->version);
}
// Check if all old migration files are all available to do downgrading
if ($method === 'down')
{
if ($loop <= $history_size && $history_migrations[$loop]['version'] !== $migration->version)
{
throw new \RuntimeException(lang('Migration.gap') . ' ' . $migration->version);
throw new \RuntimeException(lang('Migrations.gap') . ' ' . $migration->version);
}
}
$loop ++;
@ -476,6 +530,22 @@ class MigrationRunner
//--------------------------------------------------------------------
/**
* Sets the path to the base directory that will be used
* when locating migrations. If left null, the value will
* be chosen from $this->namespace's directory.
*
* @param string|null $path
*
* @return $this
*/
public function setPath(string $path = null)
{
$this->path = $path;
return $this;
}
/**
* Set namespace.
* Allows other scripts to modify on the fly as needed.
@ -514,10 +584,14 @@ class MigrationRunner
* Set migration Name.
*
* @param string $name
*
* @return \CodeIgniter\Database\MigrationRunner
*/
public function setName(string $name)
{
$this->name = $name;
return $this;
}
//--------------------------------------------------------------------
@ -531,6 +605,8 @@ class MigrationRunner
*/
public function getHistory(string $group = 'default')
{
$this->ensureTable();
$query = $this->db->table($this->table)
->where('group', $group)
->where('namespace', $this->namespace)
@ -602,6 +678,8 @@ class MigrationRunner
*/
protected function getVersion()
{
$this->ensureTable();
$row = $this->db->table($this->table)
->select('version')
->where('group', $this->group)
@ -675,14 +753,14 @@ class MigrationRunner
* Ensures that we have created our migrations table
* in the database.
*/
protected function ensureTable()
public function ensureTable()
{
if ($this->db->tableExists($this->table))
if ($this->tableChecked || $this->db->tableExists($this->table))
{
return;
}
$forge = \Config\Database::forge();
$forge = \Config\Database::forge($this->db);
$forge->addField([
'version' => [
@ -713,6 +791,8 @@ class MigrationRunner
]);
$forge->createTable($this->table, true);
$this->tableChecked = true;
}
//--------------------------------------------------------------------

View File

@ -105,7 +105,7 @@ class Builder extends BaseBuilder
$sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') + {$value}"]);
return $this->db->query($sql, $this->binds);
return $this->db->query($sql, $this->binds, false);
}
//--------------------------------------------------------------------
@ -124,7 +124,7 @@ class Builder extends BaseBuilder
$sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') - {$value}"]);
return $this->db->query($sql, $this->binds);
return $this->db->query($sql, $this->binds, false);
}
//--------------------------------------------------------------------
@ -162,7 +162,14 @@ class Builder extends BaseBuilder
$table = $this->QBFrom[0];
$set = $this->binds;
$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);

View File

@ -129,17 +129,32 @@ class Query implements QueryInterface
/**
* Sets the raw query string to use for this statement.
*
* @param string $sql
* @param array $binds
* @param string $sql
* @param array $binds
* @param boolean $setEscape
*
* @return mixed
*/
public function setQuery(string $sql, $binds = null)
public function setQuery(string $sql, $binds = null, bool $setEscape = true)
{
$this->originalQueryString = $sql;
if (! is_null($binds))
{
if (! is_array($binds))
{
$binds = [$binds];
}
if ($setEscape)
{
array_walk($binds, function (&$item) {
$item = [
$item,
true,
];
});
}
$this->binds = $binds;
}
@ -407,19 +422,18 @@ class Query implements QueryInterface
foreach ($binds as $placeholder => $value)
{
$escapedValue = $this->db->escape($value);
// $value[1] contains the boolean whether should be escaped or not
$escapedValue = $value[1] ? $this->db->escape($value[0]) : $value[0];
// In order to correctly handle backlashes in saved strings
// we will need to preg_quote, so remove the wrapping escape characters
// otherwise it will get escaped.
if (is_array($value))
if (is_array($value[0]))
{
$escapedValue = '(' . implode(',', $escapedValue) . ')';
}
$replacers[":{$placeholder}:"] = $escapedValue;
// $sql = preg_replace('|:' . $placeholder . '(?!\w)|', $escapedValue, $sql);
}
$sql = strtr($sql, $replacers);
@ -460,7 +474,7 @@ class Query implements QueryInterface
do
{
$c --;
$escapedValue = $this->db->escape($binds[$c]);
$escapedValue = $binds[$c][1] ? $this->db->escape($binds[$c][0]) : $binds[$c[0]];
if (is_array($escapedValue))
{
$escapedValue = '(' . implode(',', $escapedValue) . ')';

View File

@ -7,7 +7,7 @@
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2017 British Columbia Institute of Technology
* 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
@ -29,7 +29,7 @@
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/)
* @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

View File

@ -7,7 +7,7 @@
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2017 British Columbia Institute of Technology
* 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
@ -29,7 +29,7 @@
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/)
* @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
@ -307,6 +307,7 @@ class Connection extends BaseConnection implements ConnectionInterface
throw new DatabaseException(lang('Database.failGetFieldData'));
}
$query = $query->getResultObject();
if (empty($query))
{
return [];
@ -319,7 +320,8 @@ class Connection extends BaseConnection implements ConnectionInterface
$retval[$i]->type = $query[$i]->type;
$retval[$i]->max_length = null;
$retval[$i]->default = $query[$i]->dflt_value;
$retval[$i]->primary_key = isset($query[$i]->pk) ? (int)$query[$i]->pk : 0;
$retval[$i]->primary_key = isset($query[$i]->pk) ? (bool)$query[$i]->pk : false;
$retval[$i]->nullable = isset($query[$i]->notnull) ? ! (bool)$query[$i]->notnull : false;
}
return $retval;

View File

@ -7,7 +7,7 @@
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2017 British Columbia Institute of Technology
* 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
@ -29,7 +29,7 @@
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/)
* @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
@ -152,12 +152,29 @@ class Forge extends \CodeIgniter\Database\Forge
*/
protected function _alterTable($alter_type, $table, $field)
{
if (in_array($alter_type, ['DROP', 'CHANGE'], true))
switch ($alter_type)
{
return false;
}
case 'DROP':
$sqlTable = new Table($this->db, $this);
return parent::_alterTable($alter_type, $table, $field);
$sqlTable->fromTable($table)
->dropColumn($field)
->run();
return '';
break;
case 'CHANGE':
$sqlTable = new Table($this->db, $this);
$sqlTable->fromTable($table)
->modifyColumn($field)
->run();
return null;
break;
default:
return parent::_alterTable($alter_type, $table, $field);
}
}
//--------------------------------------------------------------------

View File

@ -7,7 +7,7 @@
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2017 British Columbia Institute of Technology
* 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
@ -29,7 +29,7 @@
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/)
* @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

View File

@ -7,7 +7,7 @@
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2017 British Columbia Institute of Technology
* 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
@ -29,7 +29,7 @@
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/)
* @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

View File

@ -0,0 +1,343 @@
<?php namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\Exceptions\DataException;
/**
* Class Table
*
* Provides missing features for altering tables that are common
* in other supported databases, but are missing from SQLite.
* These are needed in order to support migrations during testing
* when another database is used as the primary engine, but
* SQLite in memory databases are used for faster test execution.
*
* @package CodeIgniter\Database\SQLite3
*/
class Table
{
/**
* All of the fields this table represents.
*
* @var array
*/
protected $fields = [];
/**
* All of the unique/primary keys in the table.
*
* @var array
*/
protected $keys = [];
/**
* All of the foreign keys in the table.
*
* @var array
*/
protected $foreignKeys = [];
/**
* The name of the table we're working with.
*
* @var string
*/
protected $tableName;
/**
* The name of the table, with database prefix
*
* @var string
*/
protected $prefixedTableName;
/**
* @var Connection
*/
protected $db;
/**
* @var Forge
*/
protected $forge;
/**
* Table constructor.
*
* @param Connection $db
*/
public function __construct(Connection $db, Forge $forge)
{
$this->db = $db;
$this->forge = $forge;
}
/**
* Reads an existing database table and
* collects all of the information needed to
* recreate this table.
*
* @param string $table
*
* @return \CodeIgniter\Database\SQLite3\Table
*/
public function fromTable(string $table)
{
$this->prefixedTableName = $table;
// Remove the prefix, if any, since it's
// already been added by the time we get here...
$prefix = $this->db->DBPrefix;
if (! empty($prefix))
{
if (strpos($table, $prefix) === 0)
{
$table = substr($table, strlen($prefix));
}
}
if (! $this->db->tableExists($this->prefixedTableName))
{
throw DataException::forTableNotFound($this->prefixedTableName);
}
$this->tableName = $table;
$this->fields = $this->formatFields($this->db->getFieldData($table));
$this->keys = array_merge($this->keys, $this->formatKeys($this->db->getIndexData($table)));
$this->foreignKeys = $this->db->getForeignKeyData($table);
return $this;
}
/**
* Called after `fromTable` and any actions, like `dropColumn`, etc,
* to finalize the action. It creates a temp table, creates the new
* table with modifications, and copies the data over to the new table.
*
* @return boolean
*/
public function run(): bool
{
$this->db->query('PRAGMA foreign_keys = OFF');
$this->db->transStart();
$this->forge->renameTable($this->tableName, "temp_{$this->tableName}");
$this->forge->reset();
$this->createTable();
$this->copyData();
$this->forge->dropTable("temp_{$this->tableName}");
$success = $this->db->transComplete();
$this->db->query('PRAGMA foreign_keys = ON');
return $success;
}
/**
* Drops a column from the table.
*
* @param string $column
*
* @return \CodeIgniter\Database\SQLite3\Table
*/
public function dropColumn(string $column)
{
unset($this->fields[$column]);
return $this;
}
/**
* Modifies a field, including changing data type,
* renaming, etc.
*
* @param array $field
*
* @return \CodeIgniter\Database\SQLite3\Table
*/
public function modifyColumn(array $field)
{
$field = $field[0];
$oldName = $field['name'];
unset($field['name']);
$this->fields[$oldName] = $field;
return $this;
}
/**
* Creates the new table based on our current fields.
*/
protected function createTable()
{
$this->dropIndexes();
$this->db->resetDataCache();
// Handle any modified columns.
$fields = [];
foreach ($this->fields as $name => $field)
{
if (isset($field['new_name']))
{
$fields[$field['new_name']] = $field;
continue;
}
$fields[$name] = $field;
}
$this->forge->addField($fields);
// Unique/Index keys
if (is_array($this->keys))
{
foreach ($this->keys as $key)
{
switch ($key['type'])
{
case 'primary':
$this->forge->addPrimaryKey($key['fields']);
break;
case 'unique':
$this->forge->addUniqueKey($key['fields']);
break;
case 'index':
$this->forge->addKey($key['fields']);
break;
}
}
}
// Foreign Keys
return $this->forge->createTable($this->tableName);
}
/**
* Copies data from our old table to the new one,
* taking care map data correctly based on any columns
* that have been renamed.
*/
protected function copyData()
{
$exFields = [];
$newFields = [];
foreach ($this->fields as $name => $details)
{
// Are we modifying the column?
if (isset($details['new_name']))
{
$newFields[] = $details['new_name'];
}
else
{
$newFields[] = $name;
}
$exFields[] = $name;
}
$exFields = implode(', ', $exFields);
$newFields = implode(', ', $newFields);
$this->db->query("INSERT INTO {$this->prefixedTableName}({$newFields}) SELECT {$exFields} FROM {$this->db->DBPrefix}temp_{$this->tableName}");
}
/**
* Converts fields retrieved from the database to
* the format needed for creating fields with Forge.
*
* @param array|boolean $fields
*
* @return array
*/
protected function formatFields($fields)
{
if (! is_array($fields))
{
return $fields;
}
$return = [];
foreach ($fields as $field)
{
$return[$field->name] = [
'type' => $field->type,
'default' => $field->default,
'nullable' => $field->nullable,
];
if ($field->primary_key)
{
$this->keys[$field->name] = [
'fields' => [$field->name],
'type' => 'primary',
];
}
}
return $return;
}
/**
* Converts keys retrieved from the database to
* the format needed to create later.
*
* @param $keys
*
* @return mixed
*/
protected function formatKeys($keys)
{
if (! is_array($keys))
{
return $keys;
}
$return = [];
foreach ($keys as $name => $key)
{
$return[$name] = [
'fields' => $key->fields,
'type' => 'index',
];
}
return $return;
}
/**
* Attempts to drop all indexes and constraints
* from the database for this table.
*/
protected function dropIndexes()
{
if (! is_array($this->keys) || ! count($this->keys))
{
return;
}
foreach ($this->keys as $name => $key)
{
if ($key['type'] === 'primary' || $key['type'] === 'unique')
{
continue;
}
$this->db->query("DROP INDEX IF EXISTS '{$name}'");
}
}
}

View File

@ -7,7 +7,7 @@
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2017 British Columbia Institute of Technology
* 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
@ -29,7 +29,7 @@
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/)
* @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

View File

@ -154,6 +154,9 @@ class Seeder
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;

View File

@ -37,6 +37,7 @@
*/
use CodeIgniter\API\ResponseTrait;
use Config\Paths;
/**
* Exceptions manager
@ -259,7 +260,8 @@ class Exceptions
$path = $this->viewPath;
if (empty($path))
{
$path = APPPATH . 'Views/errors/';
$paths = new Paths();
$path = $paths->viewDirectory . '/errors/';
}
$path = is_cli()

View File

@ -84,15 +84,8 @@ class Logs extends BaseCollector
*/
public function display(): array
{
$logs = $this->collectLogs();
if (empty($logs) || ! is_array($logs))
{
$logs = [];
}
return [
'logs' => $logs,
'logs' => $this->collectLogs(),
];
}
@ -131,11 +124,10 @@ class Logs extends BaseCollector
{
if (! is_null($this->data))
{
return;
return $this->data;
}
$logger = Services::logger(true);
$this->data = $logger->logCache;
return $this->data = Services::logger(true)->logCache ?? [];
}
//--------------------------------------------------------------------

View File

@ -1,6 +1,7 @@
<?php namespace CodeIgniter;
use CodeIgniter\I18n\Time;
use CodeIgniter\Exceptions\CastException;
/**
* CodeIgniter
@ -78,7 +79,7 @@ class Entity
protected $_original = [];
/**
* Holds info whenever prperties have to be casted
* Holds info whenever properties have to be casted
*
* @var boolean
**/
@ -170,7 +171,7 @@ class Entity
continue;
}
if ($onlyChanged && $this->_original[$key] === null && $value === null)
if ($onlyChanged && ! $this->hasPropertyChanged($key, $value))
{
continue;
}
@ -192,6 +193,54 @@ class Entity
//--------------------------------------------------------------------
/**
* Converts the properties of this class into an array. Unlike toArray()
* this will not cast the data or use any magic accessors. It simply
* returns the raw data for use when saving to the model, etc.
*
* @param boolean $onlyChanged
*
* @return array
*/
public function toRawArray(bool $onlyChanged = false): array
{
$return = [];
$properties = get_object_vars($this);
foreach ($properties as $key => $value)
{
if (substr($key, 0, 1) === '_')
{
continue;
}
if ($onlyChanged && ! $this->hasPropertyChanged($key, $value))
{
continue;
}
$return[$key] = $this->$key;
}
return $return;
}
//--------------------------------------------------------------------
/**
* Checks a property to see if it has changed since the entity was created.
*
* @param string $key
* @param null $value
*
* @return boolean
*/
protected function hasPropertyChanged(string $key, $value = null)
{
return ! (($this->_original[$key] === null && $value === null) || $this->_original[$key] === $value);
}
/**
* Magic method to allow retrieval of protected and private
* class properties either by their name, or through a `getCamelCasedProperty()`
@ -267,24 +316,37 @@ class Entity
$value = $this->mutateDate($value);
}
// Array casting requires that we serialize the value
// when setting it so that it can easily be stored
// back to the database.
if (array_key_exists($key, $this->_options['casts']) && $this->_options['casts'][$key] === 'array')
$isNullable = false;
$castTo = false;
if (array_key_exists($key, $this->_options['casts']))
{
$value = serialize($value);
$isNullable = substr($this->_options['casts'][$key], 0, 1) === '?';
$castTo = $isNullable ? substr($this->_options['casts'][$key], 1) : $this->_options['casts'][$key];
}
// JSON casting requires that we JSONize the value
// when setting it so that it can easily be stored
// back to the database.
if (function_exists('json_encode') && array_key_exists($key, $this->_options['casts']) && ($this->_options['casts'][$key] === 'json' || $this->_options['casts'][$key] === 'json-array'))
if (! $isNullable || ! is_null($value))
{
$value = json_encode($value);
// Array casting requires that we serialize the value
// when setting it so that it can easily be stored
// back to the database.
if ($castTo === 'array')
{
$value = serialize($value);
}
// JSON casting requires that we JSONize the value
// when setting it so that it can easily be stored
// back to the database.
if (($castTo === 'json' || $castTo === 'json-array') && function_exists('json_encode'))
{
$value = json_encode($value);
}
}
// if a set* method exists for this key,
// use that method to insert this value.
// *) should be outside $isNullable check - SO maybe wants to do sth with null value automatically
$method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
if (method_exists($this, $method))
{
@ -428,13 +490,13 @@ class Entity
protected function castAs($value, string $type)
{
if(substr($type,0,1) === '?')
if (substr($type, 0, 1) === '?')
{
if($value === null)
if ($value === null)
{
return null;
}
$type = substr($type,1);
$type = substr($type, 1);
}
switch($type)

View File

@ -14,11 +14,6 @@ class ConfigException extends CriticalError
*/
protected $code = 3;
public static function forMissingMigrationsTable()
{
throw new static(lang('Migrations.missingTable'));
}
public static function forInvalidMigrationType(string $type = null)
{
throw new static(lang('Migrations.invalidType', [$type]));

100
system/Filters/CSRF.php Normal file
View File

@ -0,0 +1,100 @@
<?php namespace CodeIgniter\Filters;
/**
* 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
*/
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Security\Exceptions\SecurityException;
use Config\Services;
class CSRF implements FilterInterface
{
/**
* Do whatever processing this filter needs to do.
* By default it should not return anything during
* normal execution. However, when an abnormal state
* is found, it should return an instance of
* CodeIgniter\HTTP\Response. If it does, script
* execution will end and that Response will be
* sent back to the client, allowing for error pages,
* redirects, etc.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
*
* @return mixed
*/
public function before(RequestInterface $request)
{
if ($request->isCLI())
{
return;
}
$security = Services::security();
try
{
$security->CSRFVerify($request);
}
catch (SecurityException $e)
{
if (config('App')->CSRFRedirect && ! $request->isAJAX())
{
return redirect()->back()->with('error', $e->getMessage());
}
throw $e;
}
}
//--------------------------------------------------------------------
/**
* We don't have anything to do here.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
*
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response)
{
}
//--------------------------------------------------------------------
}

View File

@ -0,0 +1,74 @@
<?php namespace CodeIgniter\Filters;
/**
* 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
*/
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
class DebugToolbar implements FilterInterface
{
/**
* We don't need to do anything here.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
*
* @return mixed
*/
public function before(RequestInterface $request)
{
}
//--------------------------------------------------------------------
/**
* If the debug flag is set (CI_DEBUG) then collect performance
* and debug information and display it in a toolbar.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
*
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response)
{
Services::toolbar()->prepare();
}
//--------------------------------------------------------------------
}

View File

@ -119,7 +119,7 @@ class Filters
*/
public function run(string $uri, $position = 'before')
{
$this->initialize($uri);
$this->initialize(strtolower($uri));
foreach ($this->filters[$position] as $alias => $rules)
{
@ -350,7 +350,7 @@ class Filters
foreach ($rules as $path)
{
// Prep it for regex
$path = str_replace('/*', '*', $path);
$path = strtolower(str_replace('/*', '*', $path));
$path = trim(str_replace('*', '.+', $path), '/ ');
// Path doesn't match the URI? continue on...
@ -388,7 +388,7 @@ class Filters
foreach ($rules as $path)
{
// Prep it for regex
$path = str_replace('/*', '*', $path);
$path = strtolower(str_replace('/*', '*', $path));
$path = trim(str_replace('*', '.+', $path), '/ ');
// Path doesn't match the URI? continue on...
@ -434,7 +434,7 @@ class Filters
return;
}
$uri = trim($uri, '/ ');
$uri = strtolower(trim($uri, '/ '));
$matches = [];
@ -446,7 +446,7 @@ class Filters
foreach ($settings['before'] as $path)
{
// Prep it for regex
$path = str_replace('/*', '*', $path);
$path = strtolower(str_replace('/*', '*', $path));
$path = trim(str_replace('*', '.+', $path), '/ ');
if (preg_match('#' . $path . '#', $uri) !== 1)
@ -467,7 +467,7 @@ class Filters
foreach ($settings['after'] as $path)
{
// Prep it for regex
$path = str_replace('/*', '*', $path);
$path = strtolower(str_replace('/*', '*', $path));
$path = trim(str_replace('*', '.+', $path), '/ ');
if (preg_match('#' . $path . '#', $uri) !== 1)
@ -479,6 +479,7 @@ class Filters
}
$this->filters['after'] = array_merge($this->filters['after'], $matches);
$matches = [];
}
}
}

View File

@ -0,0 +1,78 @@
<?php namespace CodeIgniter\Filters;
/**
* 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
*/
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use CodeIgniter\Honeypot\Exceptions\HoneypotException;
class Honeypot implements FilterInterface
{
/**
* Checks if Honeypot field is empty; if not
* then the requester is a bot
*
* @param CodeIgniter\HTTP\RequestInterface $request
*
* @return mixed
*/
public function before(RequestInterface $request)
{
$honeypot = Services::honeypot(new \Config\Honeypot());
if ($honeypot->hasContent($request))
{
throw HoneypotException::isBot();
}
}
/**
* Attach a honypot to the current response.
*
* @param CodeIgniter\HTTP\RequestInterface $request
* @param CodeIgniter\HTTP\ResponseInterface $response
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response)
{
$honeypot = Services::honeypot(new \Config\Honeypot());
$honeypot->attachHoneypot($response);
}
}

View File

@ -683,7 +683,7 @@ class ContentSecurityPolicy
$this->styleSrc[] = 'nonce-' . $nonce;
return "nonce={$nonce}";
return "nonce=\"{$nonce}\"";
}, $body
);
@ -694,7 +694,7 @@ class ContentSecurityPolicy
$this->scriptSrc[] = 'nonce-' . $nonce;
return "nonce={$nonce}";
return "nonce=\"{$nonce}\"";
}, $body
);
@ -799,12 +799,6 @@ class ContentSecurityPolicy
*/
protected function addToHeader(string $name, $values = null)
{
if (empty($values))
{
$this->tempHeaders[$name] = null;
return;
}
if (is_string($values))
{
$values = [$values => 0];

View File

@ -265,20 +265,20 @@ class UploadedFile extends File implements UploadedFileInterface
*/
public function getErrorString()
{
static $errors = [
UPLOAD_ERR_OK => 'The file uploaded with success.',
UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive.',
UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
$errors = [
UPLOAD_ERR_OK => lang('HTTP.uploadErrOk'),
UPLOAD_ERR_INI_SIZE => lang('HTTP.uploadErrIniSize'),
UPLOAD_ERR_FORM_SIZE => lang('HTTP.uploadErrFormSize'),
UPLOAD_ERR_PARTIAL => lang('HTTP.uploadErrPartial'),
UPLOAD_ERR_NO_FILE => lang('HTTP.uploadErrNoFile'),
UPLOAD_ERR_CANT_WRITE => lang('HTTP.uploadErrCantWrite'),
UPLOAD_ERR_NO_TMP_DIR => lang('HTTP.uploadErrNoTmpDir'),
UPLOAD_ERR_EXTENSION => lang('HTTP.uploadErrExtension')
];
$error = is_null($this->error) ? UPLOAD_ERR_OK : $this->error;
return sprintf($errors[$error] ?? 'The file "%s" was not uploaded due to an unknown error.', $this->getName());
return sprintf($errors[$error] ?? lang('HTTP.uploadErrUnknown'), $this->getName());
}
//--------------------------------------------------------------------

View File

@ -91,7 +91,7 @@ class Header
/**
* Gets the raw value of the header. This may return either a string
* of an array, depending on whether the header has mutliple values or not.
* of an array, depending on whether the header has multiple values or not.
*
* @return array|null|string
*/

View File

@ -409,7 +409,7 @@ class Request extends Message implements RequestInterface
}
}
if (empty($value))
if (!isset($value))
{
$value = $this->globals[$method][$index] ?? null;
}

View File

@ -760,7 +760,7 @@ class URI
// URL Decode the value to protect
// from double-encoding a URL.
// Especially useful with the Pager.
$parts[$key] = $this->decode($value);
$parts[$this->decode($key)] = $this->decode($value);
}
$this->query = $parts;

View File

@ -47,7 +47,7 @@ if (! function_exists('now'))
*
* @return integer
*/
function now(string $timezone = null)
function now(string $timezone = null): int
{
$timezone = empty($timezone) ? app_timezone() : $timezone;

View File

@ -65,6 +65,12 @@ if (! function_exists('form_open'))
$action = site_url($action);
}
if(is_array($attributes) && array_key_exists('csrf_id', $attributes))
{
$csrfId = $attributes['csrf_id'];
unset($attributes['csrf_id']);
}
$attributes = stringify_attributes($attributes);
if (stripos($attributes, 'method=') === false)
@ -82,17 +88,16 @@ if (! function_exists('form_open'))
// Add CSRF field if enabled, but leave it out for GET requests and requests to external websites
$before = Services::filters()->getFilters()['before'];
if ((in_array('csrf', $before) || array_key_exists('csrf', $before)) && strpos($action, base_url()) !== false && ! stripos($form, 'method="get"')
)
if ((in_array('csrf', $before) || array_key_exists('csrf', $before)) && strpos($action, base_url()) !== false && ! stripos($form, 'method="get"'))
{
$hidden[csrf_token()] = csrf_hash();
$form .= csrf_field($csrfId ?? null);
}
if (is_array($hidden))
{
foreach ($hidden as $name => $value)
{
$form .= '<input type="hidden" name="' . $name . '" value="' . esc($value, 'html') . '" style="display: none;" />' . "\n";
$form .= form_hidden($name, $value);
}
}
@ -167,7 +172,7 @@ if (! function_exists('form_hidden'))
if (! is_array($value))
{
$form .= '<input type="hidden" name="' . $name . '" value="' . esc($value, 'html') . "\" />\n";
$form .= '<input type="hidden" name="' . $name . '" value="' . esc($value, 'html') . "\" style=\"display:none;\" />\n";
}
else
{

View File

@ -156,10 +156,9 @@ if (! function_exists('img'))
$src = ['src' => $src];
}
//If there is no alt attribute defined, set it to an empty string.
if (! isset($src['alt']))
{
$src['alt'] = '';
$src['alt'] = $attributes['alt'] ?? '';
}
$img = '<img';
@ -184,6 +183,12 @@ if (! function_exists('img'))
}
}
// prevent passing "alt" to stringify_attributes
if (is_array($attributes) && isset($attributes['alt']))
{
unset($attributes['alt']);
}
return $img . stringify_attributes($attributes) . ' />';
}
}

View File

@ -45,7 +45,7 @@ if (! function_exists('number_to_size'))
* @param integer $precision
* @param string $locale
*
* @return string
* @return boolean|string
*/
function number_to_size($num, int $precision = 1, string $locale = null)
{
@ -178,7 +178,7 @@ if (! function_exists('number_to_currency'))
*
* @return string
*/
function number_to_currency($num, string $currency, string $locale = null)
function number_to_currency($num, string $currency, string $locale = null): string
{
return format_number($num, 1, $locale, [
'type' => NumberFormatter::CURRENCY,
@ -202,7 +202,7 @@ if (! function_exists('format_number'))
*
* @return string
*/
function format_number($num, int $precision = 1, string $locale = null, array $options = [])
function format_number($num, int $precision = 1, string $locale = null, array $options = []): string
{
// Locale is either passed in here, negotiated with client, or grabbed from our config file.
$locale = $locale ?? \CodeIgniter\Config\Services::request()->getLocale();
@ -259,14 +259,14 @@ if (! function_exists('number_to_roman'))
*
* @param integer $num it will convert to int
*
* @return string
* @return string|null
*/
function number_to_roman($num)
{
$num = (int) $num;
if ($num < 1 || $num > 3999)
{
return;
return null;
}
$_number_to_roman = function ($num, $th) use (&$_number_to_roman) {

View File

@ -45,7 +45,7 @@ if (! function_exists('sanitize_filename'))
*
* @return string
*/
function sanitize_filename(string $filename)
function sanitize_filename(string $filename): string
{
return Services::security()->sanitizeFilename($filename);
}
@ -61,7 +61,7 @@ if (! function_exists('strip_image_tags'))
* @param string $str
* @return string
*/
function strip_image_tags(string $str)
function strip_image_tags(string $str): string
{
return preg_replace([
'#<img[\s/]+.*?src\s*=\s*(["\'])([^\\1]+?)\\1.*?\>#i',

View File

@ -150,7 +150,7 @@ if (! function_exists('ascii_to_entities'))
{
/*
If the $temp array has a value but we have moved on, then it seems only
fair that we output that entity and restart $temp before continuing. -Paul
fair that we output that entity and restart $temp before continuing.
*/
if (count($temp) === 1)
{
@ -281,7 +281,7 @@ if (! function_exists('word_censor'))
// \w, \b and a few others do not match on a unicode character
// set for performance reasons. As a result words like über
// will not match on a word boundary. Instead, we'll assume that
// a bad word will be bookeneded by any of these characters.
// a bad word will be bookended by any of these characters.
$delim = '[-_\'\"`(){}<>\[\]|!?@#%&,.:;^~*+=\/ 0-9\n\r\t]';
foreach ($censored as $badword)
@ -402,7 +402,7 @@ if (! function_exists('highlight_phrase'))
*
* @param string $str the text string
* @param string $phrase the phrase you'd like to highlight
* @param string $tag_open the openging tag to precede the phrase with
* @param string $tag_open the opening tag to precede the phrase with
* @param string $tag_close the closing tag to end the phrase with
*
* @return string
@ -460,7 +460,7 @@ if (! function_exists('word_wrap'))
* Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor
* will URLs.
*
* @param string $str the text string
* @param string $str the text string
* @param integer $charlim = 76 the number of characters to wrap at
*
* @return string
@ -604,9 +604,9 @@ if (! function_exists('strip_slashes'))
*
* Removes slashes contained in a string or in an array
*
* @param mixed string or array
* @param mixed $str string or array
*
* @return mixed string or array
* @return mixed string or array
*/
function strip_slashes($str)
{
@ -632,7 +632,7 @@ if (! function_exists('strip_quotes'))
*
* Removes single and double quotes from a string
*
* @param string
* @param string $str
*
* @return string
*/
@ -651,7 +651,7 @@ if (! function_exists('quotes_to_entities'))
*
* Converts single and double quotes to entities
*
* @param string
* @param string $str
*
* @return string
*/
@ -677,7 +677,7 @@ if (! function_exists('reduce_double_slashes'))
*
* http://www.some-site.com/index.php
*
* @param string
* @param string $str
*
* @return string
*/
@ -712,7 +712,7 @@ if (! function_exists('reduce_multiples'))
{
$str = preg_replace('#' . preg_quote($character, '#') . '{2,}#', $character, $str);
return ($trim === true) ? trim($str, $character) : $str;
return ($trim) ? trim($str, $character) : $str;
}
}
@ -814,7 +814,7 @@ if (! function_exists('alternator'))
$args = func_get_args();
return $args[($i ++ % count($args))];
return $args[($i++ % count($args))];
}
}
@ -829,13 +829,13 @@ if (! function_exists('excerpt'))
*
* @param string $text String to search the phrase
* @param string $phrase Phrase that will be searched for.
* @param integer $radius The amount of characters returned arround the phrase.
* @param integer $radius The amount of characters returned around the phrase.
* @param string $ellipsis Ending that will be appended
*
* @return string
*
* If no $phrase is passed, will generate an excerpt of $radius characters
* from the begining of $text.
* from the beginning of $text.
*/
function excerpt(string $text, string $phrase = null, int $radius = 100, string $ellipsis = '...'): string
{

View File

@ -334,7 +334,7 @@ if (! function_exists('mailto'))
*/
function mailto($email, string $title = '', $attributes = ''): string
{
if ($title === '')
if (trim($title) === '')
{
$title = $email;
}
@ -360,7 +360,7 @@ if (! function_exists('safe_mailto'))
*/
function safe_mailto($email, string $title = '', $attributes = ''): string
{
if ($title === '')
if (trim($title) === '')
{
$title = $email;
}

View File

@ -73,7 +73,7 @@ if (! function_exists('xml_convert'))
'&apos;',
'&#45;',
];
$str = str_replace($original, $replacements, $str);
$str = str_replace($original, $replacement, $str);
// Decode the temp markers back to entities
$str = preg_replace('/' . $temp . '(\d+);/', '&#\\1;', $str);

View File

@ -190,17 +190,6 @@ class Language
*/
protected function parseLine(string $line, string $locale): array
{
// If there's no possibility of a filename being in the string
// simply return the string, and they can parse the replacement
// without it being in a file.
if (strpos($line, '.') === false)
{
return [
null,
$line,
];
}
$file = substr($line, 0, strpos($line, '.'));
$line = substr($line, strlen($file) + 1);

View File

@ -24,4 +24,5 @@ return [
'failGetForeignKeyData' => 'Failed to get foreign key data from database.',
'parseStringFail' => 'Parsing key string failed.',
'featureUnavailable' => 'This feature is not available for the database you are using.',
'tableNotFound' => 'Table `{0}` was not found in the current database.',
];

View File

@ -1,37 +0,0 @@
<?php
/**
* Email language strings.
*
* @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
*
* @codeCoverageIgnore
*/
return [
'mustBeArray' => 'The email validation method must be passed an array.',
'invalidAddress' => 'Invalid email address: {0}',
'attachmentMissing' => 'Unable to locate the following email attachment: {0}',
'attachmentUnreadable' => 'Unable to open this attachment: {0}',
'noFrom' => 'Cannot send mail with no "From" header.',
'noRecipients' => 'You must include recipients: To, Cc, or Bcc',
'sendFailurePHPMail' => 'Unable to send email using PHP mail(). Your server might not be configured to send mail using this method.',
'sendFailureSendmail' => 'Unable to send email using PHP Sendmail. Your server might not be configured to send mail using this method.',
'sendFailureSmtp' => 'Unable to send email using PHP SMTP. Your server might not be configured to send mail using this method.',
'sent' => 'Your message has been successfully sent using the following protocol: {0, string}',
'noSocket' => 'Unable to open a socket to Sendmail. Please check settings.',
'noHostname' => 'You did not specify a SMTP hostname.',
'SMTPError' => 'The following SMTP error was encountered: {0}',
'noSMTPAuth' => 'Error: You must assign a SMTP username and password.',
'failedSMTPLogin' => 'Failed to send AUTH LOGIN command. Error: {0}',
'SMTPAuthUsername' => 'Failed to authenticate username. Error: {0}',
'SMTPAuthPassword' => 'Failed to authenticate password. Error: {0}',
'SMTPDataFailure' => 'Unable to send data: {0}',
'exitStatus' => 'Exit status code: {0}',
];

View File

@ -64,4 +64,14 @@ return [
'alreadyMoved' => 'The uploaded file has already been moved.',
'invalidFile' => 'The original file is not a valid file.',
'moveFailed' => 'Could not move file {0} to {1} ({2})',
'uploadErrOk' => 'The file uploaded with success.',
'uploadErrIniSize' => 'The file "%s" exceeds your upload_max_filesize ini directive.',
'uploadErrFormSize' => 'The file "%s" exceeds the upload limit defined in your form.',
'uploadErrPartial' => 'The file "%s" was only partially uploaded.',
'uploadErrNoFile' => 'No file was uploaded.',
'uploadErrCantWrite' => 'The file "%s" could not be written on disk.',
'uploadErrNoTmpDir' => 'File could not be uploaded: missing temporary directory.',
'uploadErrExtension' => 'File upload was stopped by a PHP extension.',
'uploadErrUnknown' => 'The file "%s" was not uploaded due to an unknown error.'
];

View File

@ -14,7 +14,7 @@
*/
return [
'invalidCellMethod' => '{class}::{method} is not a valid method."',
'invalidCellMethod' => '{class}::{method} is not a valid method.',
'missingCellParameters' => '{class}::{method} has no params.',
'invalidCellParameter' => '{0} is not a valid param name.',
'noCellClass' => 'No view cell class provided.',

View File

@ -36,6 +36,8 @@
* @filesource
*/
use CodeIgniter\Log\Exceptions\LogException;
/**
* Log error messages to file system
*/
@ -138,7 +140,10 @@ class FileHandler extends BaseHandler implements HandlerInterface
{
if (($result = fwrite($fp, substr($msg, $written))) === false)
{
// if we get this far, we'll never see this during travis-ci
// @codeCoverageIgnoreStart
break;
// @codeCoverageIgnoreEnd
}
}

View File

@ -511,7 +511,7 @@ class Logger implements LoggerInterface
* Cleans the paths of filenames by replacing APPPATH, SYSTEMPATH, FCPATH
* with the actual var. i.e.
*
* /var/www/site/application/Controllers/Home.php
* /var/www/site/app/Controllers/Home.php
* becomes:
* APPPATH/Controllers/Home.php
*

View File

@ -149,7 +149,7 @@ class Model
//--------------------------------------------------------------------
/**
* The column used for insert timestampes
* The column used for insert timestamps
*
* @var string
*/
@ -343,8 +343,6 @@ class Model
$this->tempReturnType = $this->returnType;
$this->tempUseSoftDeletes = $this->useSoftDeletes;
$this->reset();
return $row['data'];
}
@ -454,6 +452,7 @@ class Model
* @param array|object $data
*
* @return boolean
* @throws \ReflectionException
*/
public function save($data)
{
@ -462,7 +461,12 @@ class Model
// them as an array.
if (is_object($data) && ! $data instanceof \stdClass)
{
$data = static::classToArray($data, $this->dateFormat);
$data = static::classToArray($data, $this->primaryKey, $this->dateFormat);
}
if (empty($data))
{
return true;
}
if (is_object($data) && isset($data->{$this->primaryKey}))
@ -481,23 +485,28 @@ class Model
return $response;
}
//--------------------------------------------------------------------
/**
* Takes a class an returns an array of it's public and protected
* properties as an array suitable for use in creates and updates.
*
* @param string|object $data
* @param string|null $primaryKey
* @param string $dateFormat
*
* @return array
* @throws \ReflectionException
*/
public static function classToArray($data, string $dateFormat = 'datetime'): array
public static function classToArray($data, $primaryKey = null, string $dateFormat = 'datetime'): array
{
if (method_exists($data, 'toArray'))
if (method_exists($data, 'toRawArray'))
{
$properties = $data->toArray(true, false);
$properties = $data->toRawArray(true);
// Always grab the primary key otherwise updates will fail.
if (! empty($properties) && ! empty($primaryKey) && ! in_array($primaryKey, $properties))
{
$properties[$primaryKey] = $data->{$primaryKey};
}
}
else
{
@ -569,6 +578,7 @@ class Model
* @param boolean $returnID Whether insert ID should be returned or not.
*
* @return integer|string|boolean
* @throws \ReflectionException
*/
public function insert($data = null, bool $returnID = true)
{
@ -588,7 +598,7 @@ class Model
// them as an array.
if (is_object($data) && ! $data instanceof \stdClass)
{
$data = static::classToArray($data, $this->dateFormat);
$data = static::classToArray($data, $this->primaryKey, $this->dateFormat);
}
// If it's still a stdClass, go ahead and convert to
@ -689,6 +699,7 @@ class Model
* @param array|object $data
*
* @return boolean
* @throws \ReflectionException
*/
public function update($id = null, $data = null)
{
@ -711,7 +722,7 @@ class Model
// them as an array.
if (is_object($data) && ! $data instanceof \stdClass)
{
$data = static::classToArray($data, $this->dateFormat);
$data = static::classToArray($data, $this->primaryKey, $this->dateFormat);
}
// If it's still a stdClass, go ahead and convert to
@ -994,7 +1005,7 @@ class Model
throw DataException::forEmptyDataset('chunk');
}
$rows = $rows->getResult();
$rows = $rows->getResult($this->tempReturnType);
$offset += $size;
@ -1254,27 +1265,63 @@ class Model
$data = (array) $data;
}
$rules = $this->validationRules;
// ValidationRules can be either a string, which is the group name,
// or an array of rules.
if (is_string($this->validationRules))
if (is_string($rules))
{
$valid = $this->validation->run($data, $this->validationRules, $this->DBGroup);
$rules = $this->validation->loadRuleGroup($rules);
}
else
{
// Replace any placeholders (i.e. {id}) in the rules with
// the value found in $data, if exists.
$rules = $this->fillPlaceholders($this->validationRules, $data);
$this->validation->setRules($rules, $this->validationMessages);
$valid = $this->validation->run($data, null, $this->DBGroup);
$rules = $this->cleanValidationRules($rules, $data);
// If no data existed that needs validation
// our job is done here.
if (empty($rules))
{
return true;
}
// Replace any placeholders (i.e. {id}) in the rules with
// the value found in $data, if exists.
$rules = $this->fillPlaceholders($rules, $data);
$this->validation->setRules($rules, $this->validationMessages);
$valid = $this->validation->run($data, null, $this->DBGroup);
return (bool) $valid;
}
//--------------------------------------------------------------------
/**
* Removes any rules that apply to fields that have not been set
* currently so that rules don't block updating when only updating
* a partial row.
*
* @param array $rules
*
* @return array
*/
protected function cleanValidationRules($rules, array $data = null)
{
if (empty($data))
{
return [];
}
foreach ($rules as $field => $rule)
{
if (! array_key_exists($field, $data))
{
unset($rules[$field]);
}
}
return $rules;
}
/**
* Replace any placeholders within the rules with the values that
* match the 'key' of any properties being set. For example, if
@ -1312,6 +1359,13 @@ class Model
{
foreach ($rule as &$row)
{
// Should only be an `errors` array
// which doesn't take placeholders.
if (is_array($row))
{
continue;
}
$row = strtr($row, $replacements);
}
continue;
@ -1436,7 +1490,7 @@ class Model
*/
public function __get(string $name)
{
if (in_array($name, ['primaryKey', 'table', 'returnType']))
if (in_array($name, ['primaryKey', 'table', 'returnType', 'DBGroup']))
{
return $this->{$name};
}

View File

@ -223,7 +223,7 @@ class RouteCollection implements RouteCollectionInterface
* Constructor
*
* @param FileLocator $locator
* @param Config/Modules $moduleConfig
* @param \Config\Modules $moduleConfig
*/
public function __construct(FileLocator $locator, $moduleConfig)
{
@ -393,7 +393,6 @@ class RouteCollection implements RouteCollectionInterface
/**
* Will attempt to discover any additional routes, either through
* the local PSR4 namespaces, or through selected Composer packages.
* (Composer coming soon...)
*/
protected function discoverRoutes()
{
@ -406,9 +405,6 @@ class RouteCollection implements RouteCollectionInterface
// so route files can access it.
$routes = $this;
/*
* Discover Local Files
*/
if ($this->moduleConfig->shouldDiscover('routes'))
{
$files = $this->fileLocator->search('Config/Routes.php');
@ -425,10 +421,6 @@ class RouteCollection implements RouteCollectionInterface
}
}
/*
* Discover Composer files (coming soon)
*/
$this->didDiscover = true;
}
@ -871,17 +863,17 @@ class RouteCollection implements RouteCollectionInterface
$this->delete($name . '/' . $id, $new_name . '::delete/$1', $options);
}
// Web Safe?
// Web Safe? delete needs checking before update because of method name
if (isset($options['websafe']))
{
if (in_array('update', $methods))
{
$this->post($name . '/' . $id, $new_name . '::update/$1', $options);
}
if (in_array('delete', $methods))
{
$this->post($name . '/' . $id . '/delete', $new_name . '::delete/$1', $options);
}
if (in_array('update', $methods))
{
$this->post($name . '/' . $id, $new_name . '::update/$1', $options);
}
}
return $this;
@ -1240,7 +1232,8 @@ class RouteCollection implements RouteCollectionInterface
*/
protected function create(string $verb, string $from, $to, array $options = null)
{
$prefix = is_null($this->group) ? '' : $this->group . '/';
$overwrite = false;
$prefix = is_null($this->group) ? '' : $this->group . '/';
$from = filter_var($prefix . $from, FILTER_SANITIZE_STRING);
@ -1257,10 +1250,12 @@ class RouteCollection implements RouteCollectionInterface
if (! empty($options['hostname']))
{
// @todo determine if there's a way to whitelist hosts?
if (strtolower($_SERVER['HTTP_HOST']) !== strtolower($options['hostname']))
if (isset($_SERVER['HTTP_HOST']) && strtolower($_SERVER['HTTP_HOST']) !== strtolower($options['hostname']))
{
return;
}
$overwrite = true;
}
// Limiting to subdomains?
@ -1272,6 +1267,8 @@ class RouteCollection implements RouteCollectionInterface
{
return;
}
$overwrite = true;
}
// Are we offsetting the binds?
@ -1316,11 +1313,11 @@ class RouteCollection implements RouteCollectionInterface
$name = $options['as'] ?? $from;
// Don't overwrite any existing 'froms' so that auto-discovered routes
// do not overwrite any application/Config/Routes settings. The app
// do not overwrite any app/Config/Routes settings. The app
// routes should always be the "source of truth".
// this works only because discovered routes are added just prior
// to attempting to route the request.
if (isset($this->routes[$verb][$name]))
if (isset($this->routes[$verb][$name]) && ! $overwrite)
{
return;
}
@ -1350,6 +1347,12 @@ class RouteCollection implements RouteCollectionInterface
*/
private function checkSubdomains($subdomains)
{
// CLI calls can't be on subdomain.
if (! isset($_SERVER['HTTP_HOST']))
{
return false;
}
if (is_null($this->currentSubdomain))
{
$this->currentSubdomain = $this->determineCurrentSubdomain();

View File

@ -469,7 +469,7 @@ class Router implements RouterInterface
{
$val = preg_replace('#^' . $key . '$#', $val, $uri);
}
elseif (strpos($key, '/') !== false)
elseif (strpos($val, '/') !== false)
{
$val = str_replace('/', '\\', $val);
}
@ -561,6 +561,8 @@ class Router implements RouterInterface
*/
protected function validateRequest(array $segments)
{
$segments = array_filter($segments);
$c = count($segments);
$directory_override = isset($this->directory);
@ -571,8 +573,7 @@ class Router implements RouterInterface
$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]))
)
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;

View File

@ -209,5 +209,4 @@ abstract class BaseHandler implements \SessionHandlerInterface
return false;
}
}

View File

@ -73,6 +73,16 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface
*/
protected $fileNew;
/**
* @var boolean
*/
protected $matchIP = false;
/**
* @var string
*/
protected $sessionIDRegex;
//--------------------------------------------------------------------
/**
@ -91,14 +101,19 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface
}
else
{
$sessionPath = rtrim(ini_get('session.save_path'), '/\\');
$sessionPath = rtrim(ini_get('session.save_path'), '/\\');
if (! $sessionPath)
{
{
$sessionPath = WRITEPATH . 'session';
}
$this->savePath = $sessionPath;
$this->savePath = $sessionPath;
}
$this->matchIP = $config->sessionMatchIP;
$this->configureSessionIDRegex();
}
//--------------------------------------------------------------------
@ -130,8 +145,8 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface
$this->savePath = $savePath;
$this->filePath = $this->savePath . '/'
. $name // we'll use the session cookie name as a prefix to avoid collisions
. ($this->matchIP ? md5($this->ipAddress) : '');
. $name // we'll use the session cookie name as a prefix to avoid collisions
. ($this->matchIP ? md5($this->ipAddress) : '');
return true;
}
@ -219,7 +234,7 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface
{
// If the two IDs don't match, we have a session_regenerate_id() call
// and we need to close the old handle and open a new one
if ($sessionID !== $this->sessionID && ( ! $this->close() || $this->read($sessionID) === false))
if ($sessionID !== $this->sessionID && (! $this->close() || $this->read($sessionID) === false))
{
return false;
}
@ -302,13 +317,15 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface
{
if ($this->close())
{
return is_file($this->filePath . $session_id) ? (unlink($this->filePath . $session_id) && $this->destroyCookie()) : true;
return is_file($this->filePath . $session_id)
? (unlink($this->filePath . $session_id) && $this->destroyCookie()) : true;
}
elseif ($this->filePath !== null)
{
clearstatcache();
return is_file($this->filePath . $session_id) ? (unlink($this->filePath . $session_id) && $this->destroyCookie()) : true;
return is_file($this->filePath . $session_id)
? (unlink($this->filePath . $session_id) && $this->destroyCookie()) : true;
}
return false;
@ -336,20 +353,28 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface
$ts = time() - $maxlifetime;
$pattern = $this->matchIP === true
? '[0-9a-f]{32}'
: '';
$pattern = sprintf(
'/^%s[0-9a-f]{%d}$/', preg_quote($this->cookieName, '/'), ($this->matchIP === true ? 72 : 40)
'#\A%s' . $pattern . $this->sessionIDRegex . '\z#',
preg_quote($this->cookieName)
);
while (($file = readdir($directory)) !== false)
{
// If the filename doesn't match this pattern, it's either not a session file or is not ours
if (! preg_match($pattern, $file) || ! is_file($this->savePath . '/' . $file) || ($mtime = filemtime($this->savePath . '/' . $file)) === false || $mtime > $ts
if (! preg_match($pattern, $file)
|| ! is_file($this->savePath . DIRECTORY_SEPARATOR . $file)
|| ($mtime = filemtime($this->savePath . DIRECTORY_SEPARATOR . $file)) === false
|| $mtime > $ts
)
{
continue;
}
unlink($this->savePath . '/' . $file);
unlink($this->savePath . DIRECTORY_SEPARATOR . $file);
}
closedir($directory);
@ -358,4 +383,36 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface
}
//--------------------------------------------------------------------
/**
* Configure Session ID regular expression
*/
protected function configureSessionIDRegex()
{
$bitsPerCharacter = (int)ini_get('session.sid_bits_per_character');
$SIDLength = (int)ini_get('session.sid_length');
if (($bits = $SIDLength * $bitsPerCharacter) < 160)
{
// Add as many more characters as necessary to reach at least 160 bits
$SIDLength += (int)ceil((160 % $bits) / $bitsPerCharacter);
ini_set('session.sid_length', $SIDLength);
}
// Yes, 4,5,6 are the only known possible values as of 2016-10-27
switch ($bitsPerCharacter)
{
case 4:
$this->sessionIDRegex = '[0-9a-f]';
break;
case 5:
$this->sessionIDRegex = '[0-9a-v]';
break;
case 6:
$this->sessionIDRegex = '[0-9a-zA-Z,-]';
break;
}
$this->sessionIDRegex .= '{' . $SIDLength . '}';
}
}

View File

@ -94,6 +94,11 @@ class MemcachedHandler extends BaseHandler implements \SessionHandlerInterface
{
$this->keyPrefix .= $this->ipAddress . ':';
}
if(!empty($this->keyPrefix))
{
ini_set('memcached.sess_prefix', $this->keyPrefix);
}
$this->sessionExpiration = $config->sessionExpiration;
}
@ -184,7 +189,7 @@ class MemcachedHandler extends BaseHandler implements \SessionHandlerInterface
return $session_data;
}
return false;
return '';
}
//--------------------------------------------------------------------

View File

@ -42,7 +42,7 @@ use Psr\Log\LoggerAwareTrait;
* Implementation of CodeIgniter session container.
*
* Session configuration is done through session variables and cookie related
* variables in application/config/App.php
* variables in app/config/App.php
*/
class Session implements SessionInterface
{
@ -52,7 +52,7 @@ class Session implements SessionInterface
/**
* Instance of the driver to use.
*
* @var HandlerInterface
* @var \CodeIgniter\Log\Handlers\HandlerInterface
*/
protected $driver;
@ -303,6 +303,11 @@ class Session implements SessionInterface
{
ini_set('session.gc_maxlifetime', (int) $this->sessionExpiration);
}
if(!empty($this->sessionSavePath))
{
ini_set('session.save_path', $this->sessionSavePath);
}
// Security is king
ini_set('session.use_trans_sid', 0);

View File

@ -1,6 +1,41 @@
<?php namespace Tests\Support\Helpers;
<?php namespace CodeIgniter\Test;
/**
* 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
*/
use Tests\Support\DOM\DOMParser;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

View File

@ -1,6 +1,43 @@
<?php namespace Tests\Support\Helpers;
<?php namespace CodeIgniter\Test;
/**
* 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
*/
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\UserAgent;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\URI;
use Config\App;
@ -54,7 +91,7 @@ trait ControllerTester
if (empty($this->request))
{
$this->request = new IncomingRequest($this->appConfig, $this->uri, $this->body);
$this->request = new IncomingRequest($this->appConfig, $this->uri, $this->body, new UserAgent());
}
if (empty($this->response))
@ -73,7 +110,7 @@ trait ControllerTester
* @param string $method
* @param array $params
*
* @return \Tests\Support\Helpers\ControllerResponse
* @return \CodeIgniter\Test\ControllerResponse|\InvalidArgumentException
*/
public function execute(string $method, ...$params)
{

View File

@ -1,4 +1,40 @@
<?php namespace Tests\Support\DOM;
<?php namespace CodeIgniter\Test;
/**
* 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
*/
class DOMParser
{
@ -59,7 +95,7 @@ class DOMParser
*
* @param string $path
*
* @return \Tests\Support\DOM\DOMParser
* @return \CodeIgniter\Test\DOMParser
*/
public function withFile(string $path)
{

View File

@ -3,7 +3,6 @@
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Response;
use PHPUnit\Framework\TestCase;
use Tests\Support\DOM\DOMParser;
class FeatureResponse extends TestCase
{
@ -13,7 +12,7 @@ class FeatureResponse extends TestCase
public $response;
/**
* @var \Tests\Support\DOM\DOMParser
* @var \CodeIgniter\Test\DOMParser
*/
protected $domParser;

View File

@ -304,9 +304,9 @@ class Rules
// 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($data[$str] ?? null);
$present = $this->required($str ?? '');
if ($present === true)
if ($present)
{
return true;
}
@ -356,9 +356,9 @@ class Rules
// 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($data[$str] ?? null);
$present = $this->required($str ?? '');
if ($present === true)
if ($present)
{
return true;
}

Some files were not shown because too many files have changed in this diff Show More