diff --git a/.github/workflows/test-file-permissions.yml b/.github/workflows/test-file-permissions.yml new file mode 100644 index 000000000000..e6ad5949d8b4 --- /dev/null +++ b/.github/workflows/test-file-permissions.yml @@ -0,0 +1,24 @@ +name: Check File Permissions + +on: + pull_request: + push: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + permission-check: + name: Check File Permission + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect unnecessary execution permissions + run: php utils/check_permission_x.php diff --git a/admin/starter/tests/.htaccess b/admin/starter/tests/.htaccess old mode 100755 new mode 100644 diff --git a/admin/starter/tests/index.html b/admin/starter/tests/index.html old mode 100755 new mode 100644 diff --git a/app/Config/DocTypes.php b/app/Config/DocTypes.php old mode 100755 new mode 100644 diff --git a/rector.php b/rector.php index b9d51f872fdc..596eb7a3da65 100644 --- a/rector.php +++ b/rector.php @@ -44,11 +44,9 @@ use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; use Rector\Php70\Rector\FuncCall\RandomFunctionRector; -use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Php80\Rector\Class_\AnnotationToAttributeRector; use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector; use Rector\Php80\Rector\FunctionLike\MixedTypeRector; -use Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\AnnotationWithValueToAttributeRector; use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\CoversAnnotationWithValueToAttributeRector; @@ -107,7 +105,6 @@ __DIR__ . '/tests/_support/Commands/Foobar.php', __DIR__ . '/tests/_support/View', - JsonThrowOnErrorRector::class, YieldDataProviderRector::class, RemoveUnusedPromotedPropertyRector::class => [ @@ -174,16 +171,6 @@ ], MixedTypeRector::class, - // PHP 8.1 features but cause breaking changes - FinalizePublicClassConstantRector::class => [ - __DIR__ . '/system/Cache/Handlers/BaseHandler.php', - __DIR__ . '/system/Cache/Handlers/FileHandler.php', - __DIR__ . '/system/CodeIgniter.php', - __DIR__ . '/system/Events/Events.php', - __DIR__ . '/system/Log/Handlers/ChromeLoggerHandler.php', - __DIR__ . '/system/Log/Handlers/ErrorlogHandler.php', - __DIR__ . '/system/Security/Security.php', - ], ReturnNeverTypeRector::class => [ __DIR__ . '/system/Cache/Handlers/BaseHandler.php', __DIR__ . '/system/Cache/Handlers/MemcachedHandler.php', diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 77c9bd39ef3e..57c37d8a0a6c 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -420,7 +420,12 @@ public function initialize() // Connect to the database and set the connection ID $this->connID = $this->connect($this->pConnect); } catch (Throwable $e) { - $connectionErrors[] = sprintf('Main connection [%s]: %s', $this->DBDriver, $e->getMessage()); + $this->connID = false; + $connectionErrors[] = sprintf( + 'Main connection [%s]: %s', + $this->DBDriver, + $e->getMessage() + ); log_message('error', 'Error connecting to the database: ' . $e); } @@ -441,7 +446,12 @@ public function initialize() // Try to connect $this->connID = $this->connect($this->pConnect); } catch (Throwable $e) { - $connectionErrors[] = sprintf('Failover #%d [%s]: %s', ++$index, $this->DBDriver, $e->getMessage()); + $connectionErrors[] = sprintf( + 'Failover #%d [%s]: %s', + ++$index, + $this->DBDriver, + $e->getMessage() + ); log_message('error', 'Error connecting to the database: ' . $e); } diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 126aa98bb1c2..c22dc8605f56 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -78,9 +78,14 @@ public function connect(bool $persistent = false) $this->connID = $persistent === true ? pg_pconnect($this->DSN) : pg_connect($this->DSN); if ($this->connID !== false) { - if ($persistent === true && pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD && pg_ping($this->connID) === false + if ( + $persistent === true + && pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD + && pg_ping($this->connID) === false ) { - return false; + $error = pg_last_error($this->connID); + + throw new DatabaseException($error); } if (! empty($this->schema)) { @@ -88,7 +93,9 @@ public function connect(bool $persistent = false) } if ($this->setClientEncoding($this->charset) === false) { - return false; + $error = pg_last_error($this->connID); + + throw new DatabaseException($error); } } diff --git a/system/Database/SQLSRV/Builder.php b/system/Database/SQLSRV/Builder.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/Connection.php b/system/Database/SQLSRV/Connection.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/Forge.php b/system/Database/SQLSRV/Forge.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/PreparedQuery.php b/system/Database/SQLSRV/PreparedQuery.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/Result.php b/system/Database/SQLSRV/Result.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/Utils.php b/system/Database/SQLSRV/Utils.php old mode 100755 new mode 100644 diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php old mode 100755 new mode 100644 diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php old mode 100755 new mode 100644 diff --git a/system/Helpers/html_helper.php b/system/Helpers/html_helper.php old mode 100755 new mode 100644 diff --git a/system/Helpers/inflector_helper.php b/system/Helpers/inflector_helper.php old mode 100755 new mode 100644 diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php old mode 100755 new mode 100644 diff --git a/system/Test/Mock/MockResponse.php b/system/Test/Mock/MockResponse.php old mode 100755 new mode 100644 diff --git a/system/Test/PhpStreamWrapper.php b/system/Test/PhpStreamWrapper.php index 980d5424ab62..a6be8dd1329c 100644 --- a/system/Test/PhpStreamWrapper.php +++ b/system/Test/PhpStreamWrapper.php @@ -46,7 +46,7 @@ public static function restore() stream_wrapper_restore('php'); } - public function stream_open(string $path): bool + public function stream_open(): bool { return true; } diff --git a/tests/system/Database/Live/Postgre/ConnectTest.php b/tests/system/Database/Live/Postgre/ConnectTest.php new file mode 100644 index 000000000000..29de0976eb21 --- /dev/null +++ b/tests/system/Database/Live/Postgre/ConnectTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database\Live\Postgre; + +use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Test\CIUnitTestCase; +use Config\Database; +use PHPUnit\Framework\Attributes\Group; + +/** + * @internal + */ +#[Group('DatabaseLive')] +final class ConnectTest extends CIUnitTestCase +{ + protected function setUp(): void + { + parent::setUp(); + + $this->db = Database::connect($this->DBGroup); + + if ($this->db->DBDriver !== 'Postgre') { + $this->markTestSkipped('This test is only for Postgre.'); + } + } + + public function testShowErrorMessageWhenSettingInvalidCharset(): void + { + $this->expectException(DatabaseException::class); + $this->expectExceptionMessage( + 'Unable to connect to the database. +Main connection [Postgre]: ERROR: invalid value for parameter "client_encoding": "utf8mb4"' + ); + + $config = config('Database'); + $group = $config->tests; + // Sets invalid charset. + $group['charset'] = 'utf8mb4'; + $db = Database::connect($group); + + // Actually connect to DB. + $db->initialize(); + } +} diff --git a/tests/system/Helpers/CookieHelperTest.php b/tests/system/Helpers/CookieHelperTest.php old mode 100755 new mode 100644 diff --git a/tests/system/Helpers/HTMLHelperTest.php b/tests/system/Helpers/HTMLHelperTest.php old mode 100755 new mode 100644 diff --git a/tests/system/Helpers/InflectorHelperTest.php b/tests/system/Helpers/InflectorHelperTest.php old mode 100755 new mode 100644 diff --git a/tests/system/Helpers/NumberHelperTest.php b/tests/system/Helpers/NumberHelperTest.php old mode 100755 new mode 100644 diff --git a/tests/system/Helpers/TextHelperTest.php b/tests/system/Helpers/TextHelperTest.php old mode 100755 new mode 100644 diff --git a/user_guide_src/source/_static/css/citheme.css b/user_guide_src/source/_static/css/citheme.css index b3264e096336..fae4909dd62c 100644 --- a/user_guide_src/source/_static/css/citheme.css +++ b/user_guide_src/source/_static/css/citheme.css @@ -245,7 +245,8 @@ html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not( background-color: #fffff0; } -span.std { +span.std, +span.pre { text-wrap: nowrap; } diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst old mode 100755 new mode 100644 diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst old mode 100755 new mode 100644 diff --git a/user_guide_src/source/helpers/cookie_helper.rst b/user_guide_src/source/helpers/cookie_helper.rst old mode 100755 new mode 100644 diff --git a/user_guide_src/source/helpers/html_helper.rst b/user_guide_src/source/helpers/html_helper.rst old mode 100755 new mode 100644 diff --git a/user_guide_src/source/helpers/inflector_helper.rst b/user_guide_src/source/helpers/inflector_helper.rst old mode 100755 new mode 100644 diff --git a/user_guide_src/source/helpers/text_helper.rst b/user_guide_src/source/helpers/text_helper.rst old mode 100755 new mode 100644 diff --git a/user_guide_src/source/libraries/caching.rst b/user_guide_src/source/libraries/caching.rst index dd735a8a12c0..169fc681f6e6 100644 --- a/user_guide_src/source/libraries/caching.rst +++ b/user_guide_src/source/libraries/caching.rst @@ -62,17 +62,17 @@ to projects and modules. This will replace the hard-coded value in a future rele $file ===== -This is an array of settings specific to the ``File`` handler to determine how it should save the cache files. +This is an array of settings specific to the **File** handler to determine how it should save the cache files. $memcached ========== -This is an array of servers that will be used when using the ``Memcache(d)`` handler. +This is an array of servers that will be used when using the **Memcached** handler. $redis ====== -The settings for the Redis server that you wish to use when using the ``Redis`` and ``Predis`` handler. +The settings for the Redis server that you wish to use when using the **Redis** and **Predis** handler. ****************** Command-Line Tools @@ -139,12 +139,11 @@ Class Reference Gets an item from the cache. If ``null`` was returned, this will invoke the callback and save the result. Either way, this will return the value. - .. php:method:: save(string $key, $data[, int $ttl = 60[, $raw = false]]) + .. php:method:: save(string $key, $data[, int $ttl = 60]) :param string $key: Cache item name :param mixed $data: the data to save :param int $ttl: Time To Live, in seconds (default 60) - :param bool $raw: Whether to store the raw value :returns: ``true`` on success, ``false`` on failure :rtype: bool @@ -155,9 +154,6 @@ Class Reference .. literalinclude:: caching/004.php - .. note:: The ``$raw`` parameter is only utilized by Memcache, - in order to allow usage of ``increment()`` and ``decrement()``. - .. php:method:: delete($key): bool :param string $key: name of cached item @@ -280,11 +276,10 @@ Drivers File-based Caching ================== -Unlike caching from the Output Class, the driver file-based caching -allows for pieces of view files to be cached. Use this with care, and -make sure to benchmark your application, as a point can come where disk -I/O will negate positive gains by caching. This requires a cache -directory to be really writable by the application. +This requires a cache directory to be really writable by the application. + +Use this with care, and make sure to benchmark your application, as a point can +come where disk I/O will negate positive gains by caching. Memcached Caching ================= diff --git a/user_guide_src/source/models/model.rst b/user_guide_src/source/models/model.rst index edc4139a4d42..c77e68cb81b8 100644 --- a/user_guide_src/source/models/model.rst +++ b/user_guide_src/source/models/model.rst @@ -38,7 +38,7 @@ CodeIgniter does provide a model class that has a few nice features, including: - automatic database connection - basic CRUD methods -- in-model validation +- :ref:`in-model validation ` - :ref:`automatic pagination ` - and more @@ -659,9 +659,14 @@ prior to saving to the database with the ``insert()``, ``update()``, or ``save() .. important:: When you update data, by default, the validation in the model class only validates provided fields. This is to avoid validation errors when updating only some fields. - But this means ``required*`` rules do not work as expected when updating. - If you want to check required fields, you can change the behavior by configuration. - See :ref:`clean-validation-rules` for details. + However, this means that not all validation rules you set will be checked + during updates. Thus, incomplete data may pass the validation. + + For example, ``required*`` rules or ``is_unique`` rule that require the + values of other fields may not work as expected. + + To avoid such glitches, this behavior can be changed by configuration. See + :ref:`clean-validation-rules` for details. Setting Validation Rules ------------------------ diff --git a/user_guide_src/source/outgoing/view_cells.rst b/user_guide_src/source/outgoing/view_cells.rst index f940bc99ea17..e01bdcd53343 100644 --- a/user_guide_src/source/outgoing/view_cells.rst +++ b/user_guide_src/source/outgoing/view_cells.rst @@ -101,6 +101,10 @@ Implementing the AlertMessage from above as a Controlled Cell would look like th .. literalinclude:: view_cells/010.php +.. note:: If you use typed properties, you must set the initial values: + + .. literalinclude:: view_cells/023.php + .. _generating-cell-via-command: Generating Cell via Command diff --git a/user_guide_src/source/outgoing/view_cells/023.php b/user_guide_src/source/outgoing/view_cells/023.php new file mode 100644 index 000000000000..c892fcd683a7 --- /dev/null +++ b/user_guide_src/source/outgoing/view_cells/023.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Utils; + +require __DIR__ . '/../system/Test/bootstrap.php'; + +use CodeIgniter\CLI\CLI; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RuntimeException; + +function findExecutableFiles($dir) +{ + $execFileList = [ + 'admin/release-userguide', + 'admin/release-deploy', + 'admin/apibot', + 'admin/alldocs', + 'admin/release', + 'admin/docbot', + 'admin/release-notes.bb', + 'admin/release-revert', + 'admin/starter/builds', + 'user_guide_src/add-edit-this-page', + ]; + + $executableFiles = []; + + // Check if the directory exists + if (! is_dir($dir)) { + throw new RuntimeException('No such directory: ' . $dir); + } + + // Create a Recursive Directory Iterator + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir) + ); + + // Iterate over each item in the directory + foreach ($iterator as $fileinfo) { + // Check if the item is a file and is executable + if ($fileinfo->isFile() && is_executable($fileinfo->getPathname())) { + $filePath = $fileinfo->getPathname(); + + // Check allow list + if (in_array($filePath, $execFileList, true)) { + continue; + } + + if (str_ends_with($filePath, '.sh')) { + continue; + } + + $executableFiles[] = $filePath; + } + } + + return $executableFiles; +} + +// Main +chdir(__DIR__ . '/../'); + +$dirs = ['admin', 'app', 'system', 'tests', 'user_guide_src', 'utils', 'writable']; + +$executableFiles = []; + +foreach ($dirs as $dir) { + $executableFiles = array_merge($executableFiles, findExecutableFiles($dir)); +} + +if ($executableFiles !== []) { + CLI::write('Files with unnecessary execution permissions were detected:', 'light_gray', 'red'); + + foreach ($executableFiles as $file) { + CLI::write('- ' . $file); + } + + exit(1); +} + +CLI::write('No files with unnecessary execution permissions were detected.', 'black', 'green'); + +exit(0); diff --git a/writable/.htaccess b/writable/.htaccess old mode 100755 new mode 100644 diff --git a/writable/cache/index.html b/writable/cache/index.html old mode 100755 new mode 100644 diff --git a/writable/debugbar/index.html b/writable/debugbar/index.html old mode 100755 new mode 100644 diff --git a/writable/index.html b/writable/index.html old mode 100755 new mode 100644 diff --git a/writable/logs/index.html b/writable/logs/index.html old mode 100755 new mode 100644 diff --git a/writable/session/index.html b/writable/session/index.html old mode 100755 new mode 100644 diff --git a/writable/uploads/index.html b/writable/uploads/index.html old mode 100755 new mode 100644