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/libraries/time.rst b/user_guide_src/source/libraries/time.rst index d94a8cd58e62..b18e56350403 100644 --- a/user_guide_src/source/libraries/time.rst +++ b/user_guide_src/source/libraries/time.rst @@ -2,7 +2,7 @@ Times and Dates ############### -CodeIgniter provides a fully-localized, immutable, date/time class that is built on PHP's DateTimeImmutable object, but uses the Intl +CodeIgniter provides a fully-localized, immutable, date/time class that is built on PHP's DateTimeImmutable class, but uses the Intl extension's features to convert times across timezones and display the output correctly for different locales. This class is the ``Time`` class and lives in the ``CodeIgniter\I18n`` namespace. @@ -34,9 +34,9 @@ This can be any string that PHP's `DateTimeImmutable`_ constructor can parse. Se .. literalinclude:: time/001.php -You can pass in strings representing the timezone and the locale in the second and parameters, respectively. Timezones -can be any supported by PHP's `DateTimeZone `__ class. The locale can be -any supported by PHP's `Locale `__ class. If no locale or timezone is +You can pass in strings representing the timezone and the locale in the second and the third parameters, respectively. The timezone +can be any one supported by PHP's `DateTimeZone `__ class. The locale can be +any one supported by PHP's `Locale `__ class. If no locale or timezone is provided, the application defaults will be used. .. literalinclude:: time/002.php @@ -45,8 +45,8 @@ now() ===== The Time class has several helper methods to instantiate the class. The first of these is the ``now()`` method -that returns a new instance set to the current time. You can pass in strings representing the timezone and the locale -in the second and parameters, respectively. If no locale or timezone is provided, the application defaults will be used. +that returns a new instance set to the current time. You can pass in strings representing the timezone and locale +in the second and third parameters, respectively. If no locale or timezone is provided, the application defaults will be used. .. literalinclude:: time/003.php @@ -86,7 +86,7 @@ createFromDate() ================ Given separate inputs for **year**, **month**, and **day**, will return a new instance. If any of these parameters -are not provided, it will use the current value to fill it in. Accepts strings for the timezone and locale in the +are not provided, it will use the current year, month and day. Accepts strings for the timezone and locale in the fourth and fifth parameters: .. literalinclude:: time/008.php @@ -94,7 +94,7 @@ fourth and fifth parameters: createFromTime() ================ -Like ``createFromDate()`` except it is only concerned with the **hours**, **minutes**, and **seconds**. Uses the +Like ``createFromDate()``, except it is only concerned with the **hours**, **minutes**, and **seconds**. Uses the current day for the date portion of the Time instance. Accepts strings for the timezone and locale in the fourth and fifth parameters: @@ -104,7 +104,7 @@ create() ======== A combination of the previous two methods, takes **year**, **month**, **day**, **hour**, **minutes**, and **seconds** -as separate parameters. Any value not provided will use the current date and time to determine. Accepts strings for the +as separate parameters. Any value not provided will use the current date and time. Accepts strings for the timezone and locale in the fourth and fifth parameters: .. literalinclude:: time/010.php @@ -178,21 +178,21 @@ This will return a localized version of string formatted as (``Y-m-d H:i:s``): toDateString() ============== -Displays just the localized version of date portion of the Time: +Displays just the localized date portion of the Time: .. literalinclude:: time/017.php toTimeString() ============== -Displays just the localized version of time portion of the value: +Displays just the localized time portion of the value: .. literalinclude:: time/018.php humanize() ========== -This methods returns a string that displays the difference between the current date/time and the instance in a +This method returns a string that displays the difference between the current date/time and the instance in a human readable format that is geared towards being easily understood. It can create strings like '3 hours ago', 'in 1 month', etc: @@ -203,17 +203,18 @@ The exact time displayed is determined in the following manner: =============================== ================================= Time difference Result =============================== ================================= -$time > 1 year && < 2 years in 1 year / 1 year ago -$time > 1 month && < 1 year in 6 months / 6 months ago -$time > 7 days && < 1 month in 3 weeks / 3 weeks ago -$time > today && < 7 days in 4 days / 4 days ago -$time == tomorrow / yesterday Tomorrow / Yesterday -$time > 59 minutes && < 1 day in 2 hours / 2 hours ago -$time > now && < 1 hour in 35 minutes / 35 minutes ago +1 year < $time < 2 years in 1 year / 1 year ago +1 month < $time < 1 year in 6 months / 6 months ago +7 days < $time < 1 month in 3 weeks / 3 weeks ago +today < $time < 7 days in 4 days / 4 days ago +$time == yesterday / tomorrow Yesterday / Tomorrow +59 minutes < $time < 1 day in 2 hours / 2 hours ago +now < $time < 1 hour in 35 minutes / 35 minutes ago $time == now Now =============================== ================================= -The exact language used is controlled through the language file, **Time.php**. +The result strings are coming from the language file, **system/Language/en/Time.php**. +If you want to overwrite them, create **app/Language/{locale}/Time.php**. ****************************** Working with Individual Values @@ -240,7 +241,7 @@ In addition to these, a number of methods exist to provide additional informatio getAge() -------- -Returns the age, in years, of between the Time's instance and the current time. Perfect for checking +Returns the age, in years, between the Time instance and the current time. Perfect for checking the age of someone based on their birthday: .. literalinclude:: time/022.php @@ -402,7 +403,7 @@ humanize() Much like Time's ``humanize()`` method, this returns a string that displays the difference between the times in a human readable format that is geared towards being easily understood. It can create strings like '3 hours ago', -'in 1 month', etc. The biggest differences are in how very recent dates are handled: +'in 1 month', etc. The biggest difference is in how very recent dates are handled: .. literalinclude:: time/041.php @@ -411,13 +412,14 @@ The exact time displayed is determined in the following manner: =============================== ================================= Time difference Result =============================== ================================= -$time > 1 year && < 2 years in 1 year / 1 year ago -$time > 1 month && < 1 year in 6 months / 6 months ago -$time > 7 days && < 1 month in 3 weeks / 3 weeks ago -$time > today && < 7 days in 4 days / 4 days ago -$time > 1 hour && < 1 day in 8 hours / 8 hours ago -$time > 1 minute && < 1 hour in 35 minutes / 35 minutes ago +1 year < $time < 2 years in 1 year / 1 year ago +1 month < $time < 1 year in 6 months / 6 months ago +7 days < $time < 1 month in 3 weeks / 3 weeks ago +today < $time < 7 days in 4 days / 4 days ago +1 hour < $time < 1 day in 8 hours / 8 hours ago +1 minute < $time < 1 hour in 35 minutes / 35 minutes ago $time < 1 minute Now =============================== ================================= -The exact language used is controlled through the language file, **Time.php**. +The result strings are coming from the language file, **system/Language/en/Time.php**. +If you want to overwrite them, create **app/Language/{locale}/Time.php**. diff --git a/user_guide_src/source/libraries/uploaded_files.rst b/user_guide_src/source/libraries/uploaded_files.rst index c6cd88d46016..7cc3084d306f 100644 --- a/user_guide_src/source/libraries/uploaded_files.rst +++ b/user_guide_src/source/libraries/uploaded_files.rst @@ -83,12 +83,10 @@ this code and save it to your **app/Controllers** directory: .. literalinclude:: uploaded_files/002.php -Since the value of a file upload HTML field doesn't exist, and is stored in the -``$_FILES`` global, only :ref:`rules-for-file-uploads` can be used to validate -the upload file with :doc:`validation`. +Only the :ref:`rules-for-file-uploads` can be used to validate uploaded files. -The rule ``required`` cannot be used either, so if the file is required, use -the rule ``uploaded`` instead. +Therefore, the rule ``required`` cannot be used either, so if the file is required, +use the rule ``uploaded`` instead. Note that an empty array (``[]``) is passed as the first argument to ``$this->validateData()``. It is because the file validation rules get the data 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