Merge remote-tracking branch 'origin/develop' into 4.4

This commit is contained in:
kenjis 2023-05-21 22:46:31 +09:00
commit 3ffee44dab
No known key found for this signature in database
GPG Key ID: BD254878922AF198
26 changed files with 318 additions and 113 deletions

View File

@ -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)

View File

@ -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',

View File

@ -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

View File

@ -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);
}

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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'];
}
/**

View File

@ -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.
*/

View File

@ -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();
})();
}

View File

@ -24,6 +24,7 @@ class ValidErrorsModel extends Model
'description',
];
protected $validationRules = [
'id' => 'permit_empty|is_natural_no_zero',
'name' => [
'required',
'min_length[10]',

View File

@ -24,6 +24,7 @@ class ValidModel extends Model
'description',
];
protected $validationRules = [
'id' => 'permit_empty|is_natural_no_zero',
'name' => [
'required',
'min_length[3]',

View 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
{
}

View File

@ -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));
}
}

View 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);
}
}

View File

@ -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]',

View File

@ -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));
}

View File

@ -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}]'],
],

View File

@ -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');

View File

@ -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>`_

View File

@ -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

View File

@ -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 ---------------------------------------------------

View File

@ -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

View File

@ -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.

View File

@ -1,5 +1,6 @@
<?php
$validation->setRules([
'id' => 'is_natural_no_zero',
'email' => 'required|valid_email|is_unique[users.email,id,{id}]',
]);

View File

@ -1,5 +1,6 @@
<?php
$validation->setRules([
'id' => 'is_natural_no_zero',
'email' => 'required|valid_email|is_unique[users.email,id,4]',
]);

View File

@ -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>