mirror of
https://github.com/codeigniter4/CodeIgniter4.git
synced 2025-02-20 11:44:28 +08:00
Merge remote-tracking branch 'origin/develop' into 4.4
This commit is contained in:
commit
3ffee44dab
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [v4.3.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.5) (2023-05-21)
|
||||
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.4...v4.3.5)
|
||||
|
||||
### SECURITY
|
||||
|
||||
* *Remote Code Execution Vulnerability in Validation Placeholders* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-m6m8-6gq8-c9fj) for more information.
|
||||
* fix: Session::stop() does not destroy session by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7503
|
||||
|
||||
### Fixed Bugs
|
||||
|
||||
* docs: remove incorrect @property in ResponseTrait by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7495
|
||||
* fix: validation error when a closure is used in combination with permit_empty or if_exist rules by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7492
|
||||
* fix: standardize behavior of `make:cell` and `Cells` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/7481
|
||||
* fix: PostgreSQL getVersion() logic by @marekmosna in https://github.com/codeigniter4/CodeIgniter4/pull/7488
|
||||
* fix: PostgreSQL getVersion() output by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7509
|
||||
|
||||
### Enhancements
|
||||
|
||||
* feat: user guide dark mode by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7463
|
||||
|
||||
### Refactoring
|
||||
|
||||
* refactor: Entity variable by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7499
|
||||
|
||||
## [v4.3.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.4) (2023-04-27)
|
||||
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.3...v4.3.4)
|
||||
|
||||
|
@ -27,6 +27,7 @@ class Generators extends BaseConfig
|
||||
*/
|
||||
public array $views = [
|
||||
'make:cell' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
|
||||
'make:cell_view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
|
||||
'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php',
|
||||
'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php',
|
||||
'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php',
|
||||
|
@ -65,11 +65,6 @@ parameters:
|
||||
count: 3
|
||||
path: system/Database/MySQLi/PreparedQuery.php
|
||||
|
||||
-
|
||||
message: "#^Strict comparison using \\=\\=\\= between array<string, int|string|null> and false will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
path: system/Database/Postgre/Connection.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property CodeIgniter\\\\Database\\\\BaseConnection\\:\\:\\$schema\\.$#"
|
||||
count: 2
|
||||
|
@ -235,15 +235,15 @@ trait GeneratorTrait
|
||||
$component = singular($this->component);
|
||||
|
||||
/**
|
||||
* @see https://regex101.com/r/a5KNCR/1
|
||||
* @see https://regex101.com/r/a5KNCR/2
|
||||
*/
|
||||
$pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)/i', $component);
|
||||
$pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)$/i', $component);
|
||||
|
||||
if (preg_match($pattern, $class, $matches) === 1) {
|
||||
$class = $matches[1] . ucfirst($matches[2]);
|
||||
}
|
||||
|
||||
if ($this->enabledSuffixing && $this->getOption('suffix') && ! strripos($class, $component)) {
|
||||
if ($this->enabledSuffixing && $this->getOption('suffix') && preg_match($pattern, $class) !== 1) {
|
||||
$class .= ucfirst($component);
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class CodeIgniter
|
||||
/**
|
||||
* The current version of CodeIgniter Framework
|
||||
*/
|
||||
public const CI_VERSION = '4.3.4';
|
||||
public const CI_VERSION = '4.3.5';
|
||||
|
||||
/**
|
||||
* App startup time.
|
||||
|
@ -65,7 +65,6 @@ class CellGenerator extends BaseCommand
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserCell).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
@ -74,27 +73,26 @@ class CellGenerator extends BaseCommand
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
// Generate the Class first
|
||||
$this->component = 'Cell';
|
||||
$this->directory = 'Cells';
|
||||
$this->component = 'Cell';
|
||||
$this->directory = 'Cells';
|
||||
|
||||
$params = array_merge($params, ['suffix' => null]);
|
||||
|
||||
$this->template = 'cell.tpl.php';
|
||||
$this->classNameLang = 'CLI.generator.className.cell';
|
||||
|
||||
$this->generateClass($params);
|
||||
|
||||
// Generate the View
|
||||
$this->name = 'make:cell_view';
|
||||
$this->template = 'cell_view.tpl.php';
|
||||
$this->classNameLang = 'CLI.generator.viewName.cell';
|
||||
|
||||
// Form the view name
|
||||
$segments = explode('\\', $this->qualifyClassName());
|
||||
$className = $this->qualifyClassName();
|
||||
$viewName = decamelize(class_basename($className));
|
||||
$viewName = preg_replace('/([a-z][a-z0-9_\/\\\\]+)(_cell)$/i', '$1', $viewName) ?? $viewName;
|
||||
$namespace = substr($className, 0, strrpos($className, '\\') + 1);
|
||||
|
||||
$view = array_pop($segments);
|
||||
$view = decamelize($view);
|
||||
$segments[] = $view;
|
||||
$view = implode('\\', $segments);
|
||||
$this->generateView($namespace . $viewName, $params);
|
||||
|
||||
$this->template = 'cell_view.tpl.php';
|
||||
|
||||
$this->generateView($view, $params);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -128,11 +128,16 @@ class Connection extends BaseConnection
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
if (! $this->connID || ($pgVersion = pg_version($this->connID)) === false) {
|
||||
if (! $this->connID) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return isset($pgVersion['server']) ? $this->dataCache['version'] = $pgVersion['server'] : false;
|
||||
$pgVersion = pg_version($this->connID);
|
||||
$this->dataCache['version'] = isset($pgVersion['server']) ?
|
||||
(preg_match('/^(\d+\.\d+)/', $pgVersion['server'], $matches) ? $matches[1] : '') :
|
||||
'';
|
||||
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,8 +16,10 @@ use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
use CodeIgniter\View\RendererInterface;
|
||||
use Config\Services;
|
||||
use Config\Validation as ValidationConfig;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use TypeError;
|
||||
|
||||
/**
|
||||
@ -40,10 +42,18 @@ class Validation implements ValidationInterface
|
||||
protected $ruleSetInstances = [];
|
||||
|
||||
/**
|
||||
* Stores the actual rules that should
|
||||
* be ran against $data.
|
||||
* Stores the actual rules that should be run against $data.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* [
|
||||
* field1 => [
|
||||
* 'label' => label,
|
||||
* 'rules' => [
|
||||
* rule1, rule2, ...
|
||||
* ],
|
||||
* ],
|
||||
* ]
|
||||
*/
|
||||
protected $rules = [];
|
||||
|
||||
@ -145,8 +155,7 @@ class Validation implements ValidationInterface
|
||||
// Run through each rule. If we have any field set for
|
||||
// this rule, then we need to run them through!
|
||||
foreach ($this->rules as $field => $setup) {
|
||||
// Blast $rSetup apart, unless it's already an array.
|
||||
$rules = $setup['rules'] ?? $setup;
|
||||
$rules = $setup['rules'];
|
||||
|
||||
if (is_string($rules)) {
|
||||
$rules = $this->splitRules($rules);
|
||||
@ -515,6 +524,14 @@ class Validation implements ValidationInterface
|
||||
$rule = ['rules' => $rule];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($rule['rules']) && is_string($rule['rules'])) {
|
||||
$rule['rules'] = $this->splitRules($rule['rules']);
|
||||
}
|
||||
|
||||
if (is_string($rule)) {
|
||||
$rule = ['rules' => $this->splitRules($rule)];
|
||||
}
|
||||
}
|
||||
|
||||
$this->rules = $rules;
|
||||
@ -677,47 +694,70 @@ class Validation implements ValidationInterface
|
||||
*
|
||||
* and the following rule:
|
||||
*
|
||||
* 'required|is_unique[users,email,id,{id}]'
|
||||
* 'is_unique[users,email,id,{id}]'
|
||||
*
|
||||
* The value of {id} would be replaced with the actual id in the form data:
|
||||
*
|
||||
* 'required|is_unique[users,email,id,13]'
|
||||
* 'is_unique[users,email,id,13]'
|
||||
*/
|
||||
protected function fillPlaceholders(array $rules, array $data): array
|
||||
{
|
||||
$replacements = [];
|
||||
foreach ($rules as &$rule) {
|
||||
$ruleSet = $rule['rules'];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$replacements["{{$key}}"] = $value;
|
||||
}
|
||||
foreach ($ruleSet as &$row) {
|
||||
if (is_string($row)) {
|
||||
$placeholderFields = $this->retrievePlaceholders($row, $data);
|
||||
|
||||
if ($replacements !== []) {
|
||||
foreach ($rules as &$rule) {
|
||||
$ruleSet = $rule['rules'] ?? $rule;
|
||||
foreach ($placeholderFields as $field) {
|
||||
$validator ??= Services::validation(null, false);
|
||||
|
||||
if (is_array($ruleSet)) {
|
||||
foreach ($ruleSet as &$row) {
|
||||
if (is_string($row)) {
|
||||
$row = strtr($row, $replacements);
|
||||
$placeholderRules = $rules[$field]['rules'] ?? null;
|
||||
|
||||
// Check if the validation rule for the placeholder exists
|
||||
if ($placeholderRules === null) {
|
||||
throw new LogicException(
|
||||
'No validation rules for the placeholder: ' . $field
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the rule does not have placeholders
|
||||
foreach ($placeholderRules as $placeholderRule) {
|
||||
if ($this->retrievePlaceholders($placeholderRule, $data)) {
|
||||
throw new LogicException(
|
||||
'The placeholder field cannot use placeholder: ' . $field
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the placeholder field
|
||||
if (! $validator->check($data[$field], implode('|', $placeholderRules))) {
|
||||
// if fails, do nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace the placeholder in the rule
|
||||
$ruleSet = str_replace('{' . $field . '}', $data[$field], $ruleSet);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($ruleSet)) {
|
||||
$ruleSet = strtr($ruleSet, $replacements);
|
||||
}
|
||||
|
||||
if (isset($rule['rules'])) {
|
||||
$rule['rules'] = $ruleSet;
|
||||
} else {
|
||||
$rule = $ruleSet;
|
||||
}
|
||||
}
|
||||
|
||||
$rule['rules'] = $ruleSet;
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves valid placeholder fields.
|
||||
*/
|
||||
private function retrievePlaceholders(string $rule, array $data): array
|
||||
{
|
||||
preg_match_all('/{(.+?)}/', $rule, $matches);
|
||||
|
||||
return array_intersect($matches[1], array_keys($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if an error exists for the given field.
|
||||
*/
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace CodeIgniter\View\Cells;
|
||||
|
||||
use CodeIgniter\Traits\PropertiesTrait;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
@ -64,6 +65,8 @@ class Cell
|
||||
* from within the view, this method extracts $data into the
|
||||
* current scope and captures the output buffer instead of
|
||||
* relying on the view service.
|
||||
*
|
||||
* @throws LogicException
|
||||
*/
|
||||
final protected function view(?string $view, array $data = []): string
|
||||
{
|
||||
@ -71,30 +74,42 @@ class Cell
|
||||
$properties = $this->includeComputedProperties($properties);
|
||||
$properties = array_merge($properties, $data);
|
||||
|
||||
// If no view is specified, we'll try to guess it based on the class name.
|
||||
if (empty($view)) {
|
||||
// According to the docs, the name of the view file should be the
|
||||
// snake_cased version of the cell's class name, but for backward
|
||||
// compatibility, the name also accepts '_cell' being omitted.
|
||||
$ref = new ReflectionClass($this);
|
||||
$view = decamelize($ref->getShortName());
|
||||
$viewPath = dirname($ref->getFileName()) . DIRECTORY_SEPARATOR . $view . '.php';
|
||||
$view = is_file($viewPath) ? $viewPath : str_replace('_cell', '', $view);
|
||||
$view = (string) $view;
|
||||
|
||||
if ($view === '') {
|
||||
$viewName = decamelize(class_basename(static::class));
|
||||
$directory = dirname((new ReflectionClass($this))->getFileName()) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$possibleView1 = $directory . substr($viewName, 0, strrpos($viewName, '_cell')) . '.php';
|
||||
$possibleView2 = $directory . $viewName . '.php';
|
||||
}
|
||||
|
||||
// Locate our view, preferring the directory of the class.
|
||||
if (! is_file($view)) {
|
||||
// Get the local pathname of the Cell
|
||||
$ref = new ReflectionClass($this);
|
||||
$view = dirname($ref->getFileName()) . DIRECTORY_SEPARATOR . $view . '.php';
|
||||
if ($view !== '' && ! is_file($view)) {
|
||||
$directory = dirname((new ReflectionClass($this))->getFileName()) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$view = $directory . $view . '.php';
|
||||
}
|
||||
|
||||
return (function () use ($properties, $view): string {
|
||||
$candidateViews = array_filter(
|
||||
[$view, $possibleView1 ?? '', $possibleView2 ?? ''],
|
||||
static fn (string $path): bool => $path !== '' && is_file($path)
|
||||
);
|
||||
|
||||
if ($candidateViews === []) {
|
||||
throw new LogicException(sprintf(
|
||||
'Cannot locate the view file for the "%s" cell.',
|
||||
static::class
|
||||
));
|
||||
}
|
||||
|
||||
$foundView = current($candidateViews);
|
||||
|
||||
return (function () use ($properties, $foundView): string {
|
||||
extract($properties);
|
||||
ob_start();
|
||||
include $view;
|
||||
include $foundView;
|
||||
|
||||
return ob_get_clean() ?: '';
|
||||
return ob_get_clean();
|
||||
})();
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ class ValidErrorsModel extends Model
|
||||
'description',
|
||||
];
|
||||
protected $validationRules = [
|
||||
'id' => 'permit_empty|is_natural_no_zero',
|
||||
'name' => [
|
||||
'required',
|
||||
'min_length[10]',
|
||||
|
@ -24,6 +24,7 @@ class ValidModel extends Model
|
||||
'description',
|
||||
];
|
||||
protected $validationRules = [
|
||||
'id' => 'permit_empty|is_natural_no_zero',
|
||||
'name' => [
|
||||
'required',
|
||||
'min_length[3]',
|
||||
|
18
tests/_support/View/Cells/BadCell.php
Normal file
18
tests/_support/View/Cells/BadCell.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tests\Support\View\Cells;
|
||||
|
||||
use CodeIgniter\View\Cells\Cell;
|
||||
|
||||
final class BadCell extends Cell
|
||||
{
|
||||
}
|
@ -46,35 +46,54 @@ final class CellGeneratorTest extends CIUnitTestCase
|
||||
return file_get_contents($filepath) ?: '';
|
||||
}
|
||||
|
||||
public function testGenerateCell()
|
||||
public function testGenerateCell(): void
|
||||
{
|
||||
command('make:cell RecentCell');
|
||||
|
||||
// Check the class was generated
|
||||
$file = APPPATH . 'Cells/RecentCell.php';
|
||||
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
|
||||
$this->assertFileExists($file);
|
||||
$contents = $this->getFileContents($file);
|
||||
$this->assertStringContainsString('class RecentCell extends Cell', $contents);
|
||||
$this->assertStringContainsString('class RecentCell extends Cell', $this->getFileContents($file));
|
||||
|
||||
// Check the view was generated
|
||||
$file = APPPATH . 'Cells/recent_cell.php';
|
||||
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
|
||||
$file = APPPATH . 'Cells/recent.php';
|
||||
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
|
||||
$this->assertFileExists($file);
|
||||
$this->assertSame("<div>\n <!-- Your HTML here -->\n</div>\n", $this->getFileContents($file));
|
||||
}
|
||||
|
||||
public function testGenerateCellSimpleName()
|
||||
public function testGenerateCellSimpleName(): void
|
||||
{
|
||||
command('make:cell Another');
|
||||
|
||||
// Check the class was generated
|
||||
$file = APPPATH . 'Cells/Another.php';
|
||||
$file = APPPATH . 'Cells/AnotherCell.php';
|
||||
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
|
||||
$this->assertFileExists($file);
|
||||
$contents = $this->getFileContents($file);
|
||||
$this->assertStringContainsString('class Another extends Cell', $contents);
|
||||
$this->assertStringContainsString('class AnotherCell extends Cell', $this->getFileContents($file));
|
||||
|
||||
// Check the view was generated
|
||||
$file = APPPATH . 'Cells/another.php';
|
||||
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
|
||||
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
|
||||
$this->assertFileExists($file);
|
||||
$this->assertSame("<div>\n <!-- Your HTML here -->\n</div>\n", $this->getFileContents($file));
|
||||
}
|
||||
|
||||
public function testGenerateCellWithCellInBetween(): void
|
||||
{
|
||||
command('make:cell PippoCellular');
|
||||
|
||||
// Check the class was generated
|
||||
$file = APPPATH . 'Cells/PippoCellularCell.php';
|
||||
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
|
||||
$this->assertFileExists($file);
|
||||
$this->assertStringContainsString('class PippoCellularCell extends Cell', $this->getFileContents($file));
|
||||
|
||||
// Check the view was generated
|
||||
$file = APPPATH . 'Cells/pippo_cellular.php';
|
||||
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
|
||||
$this->assertFileExists($file);
|
||||
$this->assertSame("<div>\n <!-- Your HTML here -->\n</div>\n", $this->getFileContents($file));
|
||||
}
|
||||
}
|
||||
|
34
tests/system/Database/Live/GetVersionTest.php
Normal file
34
tests/system/Database/Live/GetVersionTest.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\Live;
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\DatabaseTestTrait;
|
||||
|
||||
/**
|
||||
* @group DatabaseLive
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class GetVersionTest extends CIUnitTestCase
|
||||
{
|
||||
use DatabaseTestTrait;
|
||||
|
||||
protected $migrate = false;
|
||||
|
||||
public function testGetVersion()
|
||||
{
|
||||
$version = $this->db->getVersion();
|
||||
|
||||
$this->assertMatchesRegularExpression('/\A\d+(\.\d+)*\z/', $version);
|
||||
}
|
||||
}
|
@ -242,6 +242,7 @@ final class ValidationModelTest extends LiveModelTestCase
|
||||
{
|
||||
$config = new class () extends Validation {
|
||||
public $grouptest = [
|
||||
'id' => 'is_natural_no_zero',
|
||||
'name' => [
|
||||
'required',
|
||||
'min_length[3]',
|
||||
|
@ -121,7 +121,10 @@ class DatabaseRelatedRulesTest extends CIUnitTestCase
|
||||
'email' => 'derek@world.co.uk',
|
||||
];
|
||||
|
||||
$this->validation->setRules(['email' => 'is_unique[user.email,id,{id}]']);
|
||||
$this->validation->setRules([
|
||||
'id' => 'is_natural_no_zero',
|
||||
'email' => 'is_unique[user.email,id,{id}]',
|
||||
]);
|
||||
$this->assertTrue($this->validation->run($data));
|
||||
}
|
||||
|
||||
@ -221,7 +224,10 @@ class DatabaseRelatedRulesTest extends CIUnitTestCase
|
||||
'id' => $row->id,
|
||||
'email' => 'derek@world.co.uk',
|
||||
];
|
||||
$this->validation->setRules(['email' => 'is_not_unique[user.email,id,{id}]']);
|
||||
$this->validation->setRules([
|
||||
'id' => 'is_natural_no_zero',
|
||||
'email' => 'is_not_unique[user.email,id,{id}]',
|
||||
]);
|
||||
$this->assertTrue($this->validation->run($data));
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,12 @@ class ValidationTest extends CIUnitTestCase
|
||||
'bar' => 'baz|belch',
|
||||
];
|
||||
$this->validation->setRules($rules);
|
||||
$this->assertSame($rules, $this->validation->getRules());
|
||||
|
||||
$expected = [
|
||||
'foo' => ['rules' => ['bar', 'baz']],
|
||||
'bar' => ['rules' => ['baz', 'belch']],
|
||||
];
|
||||
$this->assertSame($expected, $this->validation->getRules());
|
||||
}
|
||||
|
||||
public function testSetRuleStoresRule()
|
||||
@ -97,7 +102,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
$this->assertSame([
|
||||
'foo' => [
|
||||
'label' => null,
|
||||
'rules' => 'bar|baz',
|
||||
'rules' => ['bar', 'baz'],
|
||||
],
|
||||
], $this->validation->getRules());
|
||||
}
|
||||
@ -110,7 +115,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
$this->assertSame([
|
||||
'username' => [
|
||||
'label' => 'Username',
|
||||
'rules' => 'required|min_length[3]',
|
||||
'rules' => ['required', 'min_length[3]'],
|
||||
],
|
||||
'password' => [
|
||||
'label' => 'Password',
|
||||
@ -136,11 +141,11 @@ class ValidationTest extends CIUnitTestCase
|
||||
$this->assertSame([
|
||||
'bar' => [
|
||||
'label' => null,
|
||||
'rules' => 'bar|baz',
|
||||
'rules' => ['bar', 'baz'],
|
||||
],
|
||||
'foo' => [
|
||||
'label' => null,
|
||||
'rules' => 'foo|foz',
|
||||
'rules' => ['foo', 'foz'],
|
||||
],
|
||||
], $this->validation->getRules());
|
||||
}
|
||||
@ -158,7 +163,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
$this->assertSame([
|
||||
'foo' => [
|
||||
'label' => null,
|
||||
'rules' => 'foo|foz',
|
||||
'rules' => ['foo', 'foz'],
|
||||
],
|
||||
], $this->validation->getRules());
|
||||
}
|
||||
@ -176,7 +181,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
$this->assertSame([
|
||||
'foo' => [
|
||||
'label' => null,
|
||||
'rules' => 'bar|baz',
|
||||
'rules' => ['bar', 'baz'],
|
||||
],
|
||||
], $this->validation->getRules());
|
||||
}
|
||||
@ -560,7 +565,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
{
|
||||
$this->validation->setRuleGroup('groupA');
|
||||
$this->assertSame([
|
||||
'foo' => 'required|min_length[5]',
|
||||
'foo' => ['rules' => ['required', 'min_length[5]']],
|
||||
], $this->validation->getRules());
|
||||
}
|
||||
|
||||
@ -1403,7 +1408,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
protected function placeholderReplacementResultDetermination(string $placeholder = 'id', ?array $data = null)
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = [$placeholder => 'placeholder-value'];
|
||||
$data = [$placeholder => '12'];
|
||||
}
|
||||
|
||||
$validationRules = $this->getPrivateMethodInvoker($this->validation, 'fillPlaceholders')($this->validation->getRules(), $data);
|
||||
@ -1438,6 +1443,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
|
||||
public function testPlaceholderReplacementSetSingleRuleString()
|
||||
{
|
||||
$this->validation->setRule('id', null, 'required|is_natural_no_zero');
|
||||
$this->validation->setRule('foo', null, 'required|filter[{id}]');
|
||||
|
||||
$this->placeholderReplacementResultDetermination();
|
||||
@ -1445,6 +1451,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
|
||||
public function testPlaceholderReplacementSetSingleRuleArray()
|
||||
{
|
||||
$this->validation->setRule('id', null, ['required', 'is_natural_no_zero']);
|
||||
$this->validation->setRule('foo', null, ['required', 'filter[{id}]']);
|
||||
|
||||
$this->placeholderReplacementResultDetermination();
|
||||
@ -1453,6 +1460,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
public function testPlaceholderReplacementSetMultipleRulesSimpleString()
|
||||
{
|
||||
$this->validation->setRules([
|
||||
'id' => 'required|is_natural_no_zero',
|
||||
'foo' => 'required|filter[{id}]',
|
||||
]);
|
||||
|
||||
@ -1462,6 +1470,7 @@ class ValidationTest extends CIUnitTestCase
|
||||
public function testPlaceholderReplacementSetMultipleRulesSimpleArray()
|
||||
{
|
||||
$this->validation->setRules([
|
||||
'id' => ['required', 'is_natural_no_zero'],
|
||||
'foo' => ['required', 'filter[{id}]'],
|
||||
]);
|
||||
|
||||
@ -1471,6 +1480,9 @@ class ValidationTest extends CIUnitTestCase
|
||||
public function testPlaceholderReplacementSetMultipleRulesComplexString()
|
||||
{
|
||||
$this->validation->setRules([
|
||||
'id' => [
|
||||
'rules' => 'required|is_natural_no_zero',
|
||||
],
|
||||
'foo' => [
|
||||
'rules' => 'required|filter[{id}]',
|
||||
],
|
||||
@ -1482,6 +1494,9 @@ class ValidationTest extends CIUnitTestCase
|
||||
public function testPlaceholderReplacementSetMultipleRulesComplexArray()
|
||||
{
|
||||
$this->validation->setRules([
|
||||
'id' => [
|
||||
'rules' => ['required', 'is_natural_no_zero'],
|
||||
],
|
||||
'foo' => [
|
||||
'rules' => ['required', 'filter[{id}]'],
|
||||
],
|
||||
|
@ -13,8 +13,10 @@ namespace CodeIgniter\View;
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\View\Exceptions\ViewException;
|
||||
use LogicException;
|
||||
use Tests\Support\View\Cells\AdditionCell;
|
||||
use Tests\Support\View\Cells\AwesomeCell;
|
||||
use Tests\Support\View\Cells\BadCell;
|
||||
use Tests\Support\View\Cells\ColorsCell;
|
||||
use Tests\Support\View\Cells\GreetingCell;
|
||||
use Tests\Support\View\Cells\ListerCell;
|
||||
@ -65,6 +67,14 @@ final class ControlledCellTest extends CIUnitTestCase
|
||||
$this->assertStringContainsString('42, 23, 16, 15, 8, 4', $result);
|
||||
}
|
||||
|
||||
public function testCellThrowsExceptionWhenCannotFindTheViewFile()
|
||||
{
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessage('Cannot locate the view file for the "Tests\\Support\\View\\Cells\\BadCell" cell.');
|
||||
|
||||
view_cell(BadCell::class);
|
||||
}
|
||||
|
||||
public function testCellWithParameters()
|
||||
{
|
||||
$result = view_cell(GreetingCell::class, 'greeting=Hi, name=CodeIgniter');
|
||||
|
@ -1,7 +1,7 @@
|
||||
Version 4.3.5
|
||||
#############
|
||||
|
||||
Release Date: Unreleased
|
||||
Release Date: May 21, 2023
|
||||
|
||||
**4.3.5 release of CodeIgniter4**
|
||||
|
||||
@ -12,18 +12,21 @@ Release Date: Unreleased
|
||||
SECURITY
|
||||
********
|
||||
|
||||
- *Remote Code Execution Vulnerability in Validation Placeholders* was fixed.
|
||||
See the `Security advisory GHSA-m6m8-6gq8-c9fj <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-m6m8-6gq8-c9fj>`_
|
||||
for more information.
|
||||
- Fixed that ``Session::stop()`` did not destroy the session.
|
||||
See :ref:`Session Library <session-stop>` for details.
|
||||
|
||||
BREAKING
|
||||
********
|
||||
|
||||
Message Changes
|
||||
***************
|
||||
|
||||
Changes
|
||||
*******
|
||||
|
||||
- **make:cell** When creating a new cell, the controller would always have the ``Cell`` suffixed to the class name.
|
||||
For the view file, the final ``_cell`` is always removed.
|
||||
- **Cells** For compatibility with previous versions, view filenames ending with ``_cell`` can still be
|
||||
located by the ``Cell`` as long as auto-detection of view file is enabled (via setting the ``$view`` property
|
||||
to an empty string).
|
||||
|
||||
Deprecations
|
||||
************
|
||||
|
||||
@ -34,6 +37,8 @@ Bugs Fixed
|
||||
**********
|
||||
|
||||
- **Validation:** Fixed a bug where a closure used in combination with ``permit_empty`` or ``if_exist`` rules was causing an error.
|
||||
- **make:cell** Fixed generating view files as classes.
|
||||
- **make:cell** Fixed treatment of single word class input for case-insensitive OS.
|
||||
|
||||
See the repo's
|
||||
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
|
||||
|
@ -59,12 +59,11 @@ Usage:
|
||||
|
||||
Argument:
|
||||
=========
|
||||
* ``name``: The name of the cell class. It should be in PascalCase.
|
||||
* ``name``: The name of the cell class. It should be in PascalCase. **[REQUIRED]**
|
||||
|
||||
Options:
|
||||
========
|
||||
* ``--namespace``: Set the root namespace. Defaults to value of ``APP_NAMESPACE``.
|
||||
* ``--suffix``: Append the component suffix to the generated class name.
|
||||
* ``--force``: Set this flag to overwrite existing files on destination.
|
||||
|
||||
make:command
|
||||
|
@ -26,7 +26,7 @@ copyright = '2019-' + str(year_now) + ' CodeIgniter Foundation'
|
||||
version = '4.3'
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '4.3.4'
|
||||
release = '4.3.5'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
|
@ -12,12 +12,14 @@ Please refer to the upgrade instructions corresponding to your installation meth
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
Mandatory File Changes
|
||||
**********************
|
||||
|
||||
Breaking Changes
|
||||
****************
|
||||
|
||||
Validation Placeholders
|
||||
=======================
|
||||
|
||||
- To use :ref:`validation-placeholders` securely, please remember to create a validation rule for the field you will use as a placeholder.
|
||||
|
||||
Session::stop()
|
||||
===============
|
||||
|
||||
@ -30,9 +32,6 @@ If you have code to depend on the bug, replace it with ``session_regenerate_id(t
|
||||
|
||||
See also :ref:`Session Library <session-stop>`.
|
||||
|
||||
Breaking Enhancements
|
||||
*********************
|
||||
|
||||
Project Files
|
||||
*************
|
||||
|
||||
@ -51,7 +50,7 @@ and it is recommended that you merge the updated versions with your application:
|
||||
Config
|
||||
------
|
||||
|
||||
- @TODO
|
||||
- app/Config/Generators.php
|
||||
|
||||
All Changes
|
||||
===========
|
||||
@ -59,4 +58,6 @@ All Changes
|
||||
This is a list of all files in the **project space** that received changes;
|
||||
many will be simple comments or formatting that have no effect on the runtime:
|
||||
|
||||
- @TODO
|
||||
- app/Config/App.php
|
||||
- app/Config/Generators.php
|
||||
- composer.json
|
||||
|
@ -487,6 +487,8 @@ This method sets a rule group from the validation configuration to the validatio
|
||||
.. literalinclude:: validation/018.php
|
||||
:lines: 2-
|
||||
|
||||
.. _validation-placeholders:
|
||||
|
||||
Validation Placeholders
|
||||
=======================
|
||||
|
||||
@ -498,6 +500,9 @@ replaced by the **value** of the matched incoming field. An example should clari
|
||||
.. literalinclude:: validation/020.php
|
||||
:lines: 2-
|
||||
|
||||
.. note:: Since v4.3.5, you must set the validation rules for the placeholder
|
||||
field (``id``).
|
||||
|
||||
In this set of rules, it states that the email address should be unique in the database, except for the row
|
||||
that has an id matching the placeholder's value. Assuming that the form POST data had the following:
|
||||
|
||||
@ -511,6 +516,9 @@ then the ``{id}`` placeholder would be replaced with the number **4**, giving th
|
||||
|
||||
So it will ignore the row in the database that has ``id=4`` when it verifies the email is unique.
|
||||
|
||||
.. note:: Since v4.3.5, if the placeholder (``id``) value does not pass the
|
||||
validation, the placeholder would not be replaced.
|
||||
|
||||
This can also be used to create more dynamic rules at runtime, as long as you take care that any dynamic
|
||||
keys passed in don't conflict with your form data.
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
$validation->setRules([
|
||||
'id' => 'is_natural_no_zero',
|
||||
'email' => 'required|valid_email|is_unique[users.email,id,{id}]',
|
||||
]);
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
$validation->setRules([
|
||||
'id' => 'is_natural_no_zero',
|
||||
'email' => 'required|valid_email|is_unique[users.email,id,4]',
|
||||
]);
|
||||
|
@ -80,7 +80,14 @@ Controlled Cells
|
||||
|
||||
.. versionadded:: 4.3.0
|
||||
|
||||
Controlled Cells have two primary goals: to make it as fast as possible to build the cell, and provide additional logic and flexibility to your views, if they need it. The class must extend ``CodeIgniter\View\Cells\Cell``. They should have a view file in the same folder. By convention the class name should be PascalCase and the view should be the snake_cased version of the class name. So, for example, if you have a ``MyCell`` class, the view file should be ``my_cell.php``.
|
||||
Controlled cells have two primary goals: to make it as fast as possible to build the cell, and provide additional logic and
|
||||
flexibility to your views, if they need it. The class must extend ``CodeIgniter\View\Cells\Cell``. They should have a view file
|
||||
in the same folder. By convention, the class name should be in PascalCase suffixed with ``Cell`` and the view should be
|
||||
the snake_cased version of the class name, without the suffix. For example, if you have a ``MyCell`` class, the view file
|
||||
should be ``my.php``.
|
||||
|
||||
.. note:: Prior to v4.3.5, the generated view file ends with ``_cell.php``. Though v4.3.5 and newer will generate view files
|
||||
without the ``_cell`` suffix, existing view files will still be located and loaded.
|
||||
|
||||
Creating a Controlled Cell
|
||||
==========================
|
||||
@ -99,7 +106,7 @@ At the most basic level, all you need to implement within the class are public p
|
||||
public $message;
|
||||
}
|
||||
|
||||
// app/Cells/alert_message_cell.php
|
||||
// app/Cells/alert_message.php
|
||||
<div class="alert alert-<?= esc($type, 'attr') ?>">
|
||||
<?= esc($message) ?>
|
||||
</div>
|
||||
@ -199,7 +206,7 @@ If you need to perform additional logic for one or more properties you can use c
|
||||
}
|
||||
}
|
||||
|
||||
// app/Cells/alert_message_cell.php
|
||||
// app/Cells/alert_message.php
|
||||
<div>
|
||||
<p>type - <?= esc($type) ?></p>
|
||||
<p>message - <?= esc($message) ?></p>
|
||||
@ -230,7 +237,7 @@ Sometimes you need to perform additional logic for the view, but you don't want
|
||||
}
|
||||
}
|
||||
|
||||
// app/Cells/recent_posts_cell.php
|
||||
// app/Cells/recent_posts.php
|
||||
<ul>
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<li><?= $this->linkPost($post) ?></li>
|
||||
|
Loading…
x
Reference in New Issue
Block a user