├── LICENSE ├── README.md ├── composer.json ├── config ├── app.default.php └── bootstrap.php ├── docs ├── Component │ └── Setup.md ├── Console │ ├── Bake.md │ └── Commands.md ├── Controller │ └── Setup.md ├── Healthcheck │ └── Healthcheck.md ├── Install.md ├── Maintenance │ ├── Maintenance.md │ └── Uptime.md ├── Panel │ └── L10nPanel.md └── README.md ├── phpcs.xml ├── phpstan.neon ├── src ├── Auth │ ├── AbstractPasswordHasher.php │ ├── DefaultPasswordHasher.php │ └── PasswordHasherFactory.php ├── Command │ ├── CliTestCommand.php │ ├── CurrentConfigConfigureCommand.php │ ├── CurrentConfigDisplayCommand.php │ ├── CurrentConfigPhpinfoCommand.php │ ├── CurrentConfigValidateCommand.php │ ├── DbBackupCreateCommand.php │ ├── DbBackupRestoreCommand.php │ ├── DbInitCommand.php │ ├── DbIntegrityBoolsCommand.php │ ├── DbIntegrityConstraintsCommand.php │ ├── DbIntegrityIntsCommand.php │ ├── DbIntegrityKeysCommand.php │ ├── DbIntegrityNullsCommand.php │ ├── DbResetCommand.php │ ├── DbWipeCommand.php │ ├── HealthcheckCommand.php │ ├── MailCheckCommand.php │ ├── MaintenanceModeActivateCommand.php │ ├── MaintenanceModeDeactivateCommand.php │ ├── MaintenanceModeStatusCommand.php │ ├── MaintenanceModeWhitelistCommand.php │ ├── ResetCommand.php │ ├── Traits │ │ ├── DbBackupTrait.php │ │ └── DbToolsTrait.php │ ├── UserCreateCommand.php │ └── UserUpdateCommand.php ├── Controller │ ├── Admin │ │ ├── BackendController.php │ │ ├── ConfigurationController.php │ │ ├── DatabaseController.php │ │ ├── SetupController.php │ │ └── UptimeController.php │ ├── Component │ │ └── SetupComponent.php │ └── HealthcheckController.php ├── Healthcheck │ ├── Check │ │ ├── Check.php │ │ ├── CheckInterface.php │ │ ├── Core │ │ │ ├── CakeCacheCheck.php │ │ │ ├── CakeSaltCheck.php │ │ │ ├── CakeVersionCheck.php │ │ │ └── FullBaseUrlCheck.php │ │ ├── Database │ │ │ └── ConnectCheck.php │ │ └── Environment │ │ │ ├── PhpUploadLimitCheck.php │ │ │ └── PhpVersionCheck.php │ ├── Healthcheck.php │ └── HealthcheckCollector.php ├── Maintenance │ └── Maintenance.php ├── Middleware │ └── MaintenanceMiddleware.php ├── Panel │ └── L10nPanel.php ├── Queue │ └── Task │ │ └── HealthcheckTask.php ├── SetupPlugin.php ├── TestSuite │ └── DriverSkipTrait.php ├── Utility │ ├── ClassFinder.php │ ├── Config.php │ ├── Debug.php │ ├── OrmTypes.php │ ├── Setup.php │ ├── System.php │ └── Validation.php └── View │ └── Helper │ └── SetupBakeHelper.php ├── templates ├── Admin │ ├── Backend │ │ ├── cache.php │ │ ├── cookies.php │ │ ├── database.php │ │ ├── disk_space.php │ │ ├── env.php │ │ ├── ip.php │ │ ├── locales.php │ │ ├── phpinfo.php │ │ ├── session.php │ │ ├── system.php │ │ ├── timezones.php │ │ └── type_map.php │ ├── Configuration │ │ └── index.php │ ├── Database │ │ └── foreign_keys.php │ └── Setup │ │ ├── index.php │ │ └── maintenance.php ├── Healthcheck │ └── index.php ├── bake │ ├── Command │ │ ├── command.twig │ │ └── helper.twig │ ├── Controller │ │ ├── component.twig │ │ └── controller.twig │ ├── Form │ │ └── form.twig │ ├── Mailer │ │ └── mailer.twig │ ├── Middleware │ │ └── middleware.twig │ ├── Model │ │ ├── behavior.twig │ │ ├── entity.twig │ │ ├── enum.twig │ │ └── table.twig │ ├── Plugin │ │ ├── .editorconfig.twig │ │ ├── .gitattributes.twig │ │ ├── .gitignore.twig │ │ ├── README.md.twig │ │ ├── composer.json.twig │ │ ├── phpunit.xml.dist.twig │ │ ├── src │ │ │ ├── Controller │ │ │ │ └── AppController.php.twig │ │ │ └── Plugin.php.twig │ │ ├── tests │ │ │ └── bootstrap.php.twig │ │ └── webroot │ │ │ └── .gitkeep.twig │ ├── Template │ │ ├── add.twig │ │ ├── edit.twig │ │ ├── index.twig │ │ ├── login.twig │ │ └── view.twig │ ├── View │ │ ├── cell.twig │ │ └── helper.twig │ ├── element │ │ ├── Controller │ │ │ ├── add.twig │ │ │ ├── delete.twig │ │ │ ├── edit.twig │ │ │ ├── index.twig │ │ │ ├── login.twig │ │ │ ├── logout.twig │ │ │ └── view.twig │ │ ├── array_property.twig │ │ ├── file_header.twig │ │ └── form.twig │ ├── layout │ │ └── default.twig │ └── tests │ │ ├── fixture.twig │ │ └── test_case.twig └── element │ ├── l10n_panel.php │ ├── ok.php │ └── yes_no.php └── tests ├── Fixture ├── QueuedJobsFixture.php ├── SessionsFixture.php └── UsersFixture.php ├── TestCase ├── Command │ ├── CliTestCommandTest.php │ ├── CurrentConfigCommandTest.php │ ├── DbInitCommandTest.php │ ├── DbResetCommandTest.php │ ├── DbWipeCommandTest.php │ ├── HealthcheckCommandTest.php │ ├── MaintenanceModeActivateCommandTest.php │ ├── MaintenanceModeDeactivateCommandTest.php │ ├── MaintenanceModeStatusCommandTest.php │ ├── MaintenanceModeWhitelistCommandTest.php │ ├── ResetCommandTest.php │ ├── UserCreateCommandTest.php │ └── UserUpdateCommandTest.php ├── Controller │ ├── Admin │ │ ├── BackendControllerTest.php │ │ ├── ConfigurationControllerTest.php │ │ ├── DatabaseControllerTest.php │ │ └── SetupControllerTest.php │ ├── Component │ │ └── SetupComponentTest.php │ └── HealthcheckControllerTest.php ├── Healthcheck │ └── Check │ │ ├── Core │ │ ├── CakeCacheCheckTest.php │ │ ├── CakeSaltCheckTest.php │ │ ├── CakeVersionCheckTest.php │ │ └── FullBaseUrlCheckTest.php │ │ ├── Database │ │ └── ConnectCheckTest.php │ │ ├── Environment │ │ ├── PhpUploadLimitCheckTest.php │ │ └── PhpVersionCheckTest.php │ │ └── HealthcheckCollectorTest.php ├── Maintenance │ └── MaintenanceTest.php ├── Middleware │ └── MaintenanceMiddlewareTest.php ├── Queue │ └── Task │ │ └── HealthcheckTaskTest.php └── Utility │ └── SetupTest.php ├── config ├── bootstrap.php └── routes.php ├── schema.php └── shim.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mark Scherer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CakePHP Setup Plugin 2 | 3 | [![CI](https://github.com/dereuromark/cakephp-setup/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dereuromark/cakephp-setup/actions/workflows/ci.yml?query=branch%3Amaster) 4 | [![Coverage](https://img.shields.io/codecov/c/github/dereuromark/cakephp-setup/master.svg)](https://codecov.io/gh/dereuromark/cakephp-setup) 5 | [![Latest Stable Version](https://poser.pugx.org/dereuromark/cakephp-setup/v/stable.svg)](https://packagist.org/packages/dereuromark/cakephp-setup) 6 | [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%208.1-8892BF.svg)](https://php.net/) 7 | [![License](https://poser.pugx.org/dereuromark/cakephp-setup/license.svg)](LICENSE) 8 | [![Total Downloads](https://poser.pugx.org/dereuromark/cakephp-setup/d/total.svg)](https://packagist.org/packages/dereuromark/cakephp-setup) 9 | [![Coding Standards](https://img.shields.io/badge/cs-PSR--2--R-yellow.svg)](https://github.com/php-fig-rectified/fig-rectified-standards) 10 | 11 | Provides useful development tools for managing a CakePHP app. 12 | 13 | Note: This branch is for **CakePHP 5.1+**. See [version map](https://github.com/dereuromark/cakephp-setup/wiki#cakephp-version-map) for details. 14 | 15 | ## What is this plugin for? 16 | This plugin aims to be a help for development of CakePHP applications. It extends and leverages 17 | my Tools Plugin. 18 | 19 | Currently, this plugin contains only the parts I managed to migrate yet: 20 | 21 | * Maintenance Mode (dynamic activation and deactivation incl. dynamic IP whitelisting) 22 | * Some very useful development tools and debugging shells 23 | 24 | ## Installation & Docs 25 | 26 | - [Documentation](docs/README.md) 27 | 28 | Possible dependencies: [Tools](https://github.com/dereuromark/cakephp-tools) Plugin 29 | 30 | ## Disclaimer 31 | Use at your own risk. Please provide any fixes or enhancements via issue or better pull request. 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dereuromark/cakephp-setup", 3 | "description": "A CakePHP plugin containing lots of useful management tools", 4 | "license": "MIT", 5 | "type": "cakephp-plugin", 6 | "keywords": [ 7 | "cakephp", 8 | "plugin", 9 | "setup", 10 | "management", 11 | "maintenance", 12 | "backup", 13 | "bake", 14 | "templates" 15 | ], 16 | "authors": [ 17 | { 18 | "name": "Mark Scherer", 19 | "homepage": "https://www.dereuromark.de", 20 | "role": "Author" 21 | } 22 | ], 23 | "homepage": "https://github.com/dereuromark/cakephp-setup", 24 | "support": { 25 | "issues": "https://github.com/dereuromark/cakephp-setup/issues", 26 | "source": "https://github.com/dereuromark/cakephp-setup" 27 | }, 28 | "require": { 29 | "php": ">=8.1", 30 | "cakephp/cakephp": "^5.1.1" 31 | }, 32 | "require-dev": { 33 | "cakephp/bake": "^3.0.1", 34 | "cakephp/debug_kit": "^5.0.1", 35 | "cakephp/migrations": "^4.5.1", 36 | "dereuromark/cakephp-tools": "^3.0.0", 37 | "dereuromark/cakephp-queue": "^8.2.0", 38 | "dereuromark/cakephp-templating": "^0.2.5", 39 | "fig-r/psr2r-sniffer": "dev-master", 40 | "phpunit/phpunit": "^10.5 || ^11.5 || ^12.1", 41 | "composer/semver": "^3.4" 42 | }, 43 | "minimum-stability": "stable", 44 | "prefer-stable": true, 45 | "autoload": { 46 | "psr-4": { 47 | "Setup\\": "src/" 48 | } 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "Cake\\Test\\": "vendor/cakephp/cakephp/tests/", 53 | "Setup\\Test\\": "tests/", 54 | "TestApp\\": "tests/test_app/src/" 55 | } 56 | }, 57 | "config": { 58 | "allow-plugins": { 59 | "dealerdirect/phpcodesniffer-composer-installer": true 60 | } 61 | }, 62 | "scripts": { 63 | "cs-check": "phpcs --extensions=php", 64 | "cs-fix": "phpcbf --extensions=php", 65 | "lowest": " validate-prefer-lowest", 66 | "lowest-setup": "composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction && cp composer.json composer.backup && composer require --dev dereuromark/composer-prefer-lowest && mv composer.backup composer.json", 67 | "stan": "phpstan analyse", 68 | "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^2.0.0 && mv composer.backup composer.json", 69 | "test": "phpunit", 70 | "test-coverage": "phpunit --log-junit tmp/coverage/unitreport.xml --coverage-html tmp/coverage --coverage-clover tmp/coverage/coverage.xml" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /config/app.default.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'blacklistedTables' => [], 6 | 'defaultTable' => null, 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /config/bootstrap.php: -------------------------------------------------------------------------------- 1 | '; 69 | var_dump($var); 70 | echo ''; 71 | 72 | $backtrace = debug_backtrace(false, 1); 73 | pr('vd-location: ' . $backtrace[0]['file'] . ':' . $backtrace[0]['line']); 74 | } 75 | } 76 | 77 | if (!function_exists('vdd')) { 78 | /** 79 | * @param mixed $var 80 | * @return void 81 | */ 82 | function vdd($var) { 83 | if (!Configure::read('debug')) { 84 | return; 85 | } 86 | 87 | echo '
';
 88 | 		var_dump($var);
 89 | 		echo '
'; 90 | 91 | $backtrace = debug_backtrace(false, 1); 92 | pr('vdd-location: ' . $backtrace[0]['file'] . ':' . $backtrace[0]['line']); 93 | exit(); 94 | } 95 | } 96 | 97 | if (PHP_SAPI === 'cli') { 98 | EventManager::instance()->on('Bake.initialize', function (EventInterface $event): void { 99 | $event->getSubject()->loadHelper('Setup.SetupBake'); 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /docs/Component/Setup.md: -------------------------------------------------------------------------------- 1 | # Setup Component 2 | 3 | Attach this to your AppController to power up debugging: 4 | - Quick-Switch: layout, maintenance, debug, clearcache (password protected in production mode) 5 | - Notify Admin via Email about self-inflicted 404s or loops (configurable) 6 | 7 | TODO: 8 | - Catch redirect loops with meaningful exception (will also be logged then) 9 | - Automatically create stats about memory, exec time, queries and alike 10 | 11 | Note that debug, clearcache, maintenance etc for productive mode, since they require a password, 12 | are emergency commands only (in case you cannot power up ssh shell access that quickly). 13 | Change your password immediately afterwards for security reasons as pwds should not be passed 14 | plain via url. 15 | 16 | Tip: Use the CLI and the Setup plugin shells for normal execution. 17 | 18 | ## How to setup 19 | ```php 20 | // In your (App) controller 21 | public function initialize() { 22 | $this->loadComponent('Setup.Setup'); 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/Console/Bake.md: -------------------------------------------------------------------------------- 1 | # Setup plugin Bake templates 2 | 3 | You can use these templates by using the `--theme`/`-t` option: 4 | ``` 5 | bin/cake bake -t Setup ... 6 | ``` 7 | 8 | See [CakePHP docs](https://book.cakephp.org/bake/2/en/development.html) on how to further customize. 9 | 10 | ## Model level 11 | By default uses Tools plugin Entity and Table classes in use statements. 12 | You can easily avoid this by having an `App\Model\Entity\Entity` and/or `App\Model\Table\Table` base class in your application. 13 | It will then detect those and use them instead. In those files you can then extend the Tools, Shim or CakePHP core classes. 14 | 15 | E.g. for your `App\Model\Entity\Entity`: 16 | ```php 17 | namespace App\Model\Entity; 18 | 19 | use Cake\ORM\Entity as CakeEntity; 20 | 21 | class Entity extends CakeEntity { 22 | } 23 | ``` 24 | 25 | ## Controller level 26 | By default, it uses compact() to pass down variables. Makes debugging them easier in the controller scope. 27 | 28 | ## View level 29 | A few additional Table class configs can be added to customize field output. 30 | 31 | By default, the index pages do not print out the primary key(s). This is usually irrelevant info and can also be retrieved from the action links if necessary. 32 | Saves some column space. 33 | 34 | ### Skipping fields 35 | Use `public $skipFields = [...]` in your Table class to skip certain fields from being outputted. 36 | By default, the following fields are being excluded: 37 | ```php 38 | ['password', 'slug', 'lft', 'rght', 'created_by', 'modified_by', 'approved_by', 'deleted_by'] 39 | ``` 40 | 41 | Use one of the following to skip for a specific action type: 42 | - `$scaffoldSkipFieldsIndex` 43 | - `$scaffoldSkipFieldsView` 44 | - `$scaffoldSkipFieldsForm` 45 | 46 | ### Pagination order defaults 47 | `$paginationOrderReversedFields` and `$paginationOrderReversedFieldTypes` help to set the defaults for column sorting in paginations. 48 | The defaults currently are: 49 | ```php 50 | $paginationOrderReversedFields = ['published', 'rating', 'priority']; 51 | $paginationOrderReversedFieldTypes = ['datetime', 'date', 'time', 'bool']; 52 | ``` 53 | 54 | ### Better auto-display of fields 55 | Text fields do not show up on index to avoid overcrowding. 56 | 57 | The following fields are auto-detected on top of the existing ones and formatted accordingly: 58 | 59 | - tinyint(2) of type enum as per Tools plugin enum solution (plural static method of the field): $entity::$method() 60 | - ['date', 'time', 'datetime', 'timestamp']: $this->Time->nice() 61 | - ['boolean']: $this->Format->yesNo() using Tools.Format helper 62 | 63 | ## TODO 64 | - The grouping of all fields has been removed, there is only text (paragraphs at the end) and default (all other field types) now. 65 | - Custom sorting of fields, if the DB cannot provide a good default order, auto-prio displayField as 1st, created/modified as last. 66 | - port $schema[$field]['type'] === 'decimal' || $schema[$field]['type'] === 'float' && strpos($schema[$field]['length'], ',2') as money formatting 67 | -------------------------------------------------------------------------------- /docs/Controller/Setup.md: -------------------------------------------------------------------------------- 1 | # Setup Web Backend 2 | 3 | Useful tooling via `/admin/setup` route. 4 | 5 | **Important**: Make sure this is properly ACL protected (e.g. [TinyAuth](https://github.com/dereuromark/cakephp-tinyauth) and 3 lines of config) if you enable the routing here. 6 | Those actions should never be exposed to the public or non-admins. 7 | 8 | ## Setup 9 | For this you need to make sure the plugin is loaded with `['routes' => true]` or you provide them manually. 10 | Also make sure that the optional Tools dependency is in this case available. 11 | 12 | ## Useful tools 13 | 14 | ### Configuration Dashboard 15 | With useful info about server and application. 16 | 17 | See `/admin/setup/configuration` 18 | 19 | ### PHPInfo 20 | Lists the phpinfo() page as you know it. 21 | 22 | See `/admin/setup/backend/phpinfo`. 23 | 24 | ### Cache 25 | Details about the current cache config. 26 | 27 | See `/admin/setup/backend/cache`. 28 | 29 | ### Session 30 | Details about the current session config. 31 | 32 | See `/admin/setup/backend/session`. 33 | 34 | ### Cookies 35 | Details about the current cookies. 36 | 37 | See `/admin/setup/backend/cookies`. 38 | 39 | ### Database 40 | Overview about the current DB tables and size. 41 | 42 | See `/admin/setup/backend/database`. 43 | 44 | ### Foreign Keys 45 | 46 | Many applications for sure forgot to add proper constraints/handling around foreign keys: 47 | 48 | - NOT NULL foreign keys: Parent removes child if deleted 49 | - NULL: FK (parent_table_id) is set to NULL if parent gets deleted 50 | 51 | The first can be done both on DB level as constraint or Cake app level (using `depending true` config). 52 | The backend here now focuses on the 2nd part. 53 | 54 | #### FK nullable 55 | See `/admin/setup/database/foreign-keys` 56 | for an overview of all possible foreign keys and get a list of possible issues and checks to run. 57 | 58 | Make sure you apply the foreign key "NULL"able part to all existing rows in your DB. 59 | The script contains a check to make sure your DB is clean here before you apply those. 60 | The migration will otherwise fail to apply them. 61 | 62 | This is especially important when you want to find all childs that do not have some belongsTo relation (anymore) using 63 | `fk IS NULL`. You can only trust the results here if you have the sanity constraints here for cleanup of those fields on delete. 64 | 65 | A migration that could be proposed to you could look like this: 66 | ```php 67 | $this->table('repositories') 68 | ->addForeignKey('module_id', 'modules', ['id'], ['delete' => 'SET_NULL']) 69 | ->update(); 70 | ``` 71 | The `module_id` is `DEFAULT NULL` and as such, deleting now the module will auto-set this to false rather than keeping the old id (that cannot be joined in anymore). 72 | 73 | You can test the SQL issue on nullable and deleting live [here](http://sqlfiddle.com/#!9/816f16c/1). 74 | -------------------------------------------------------------------------------- /docs/Healthcheck/Healthcheck.md: -------------------------------------------------------------------------------- 1 | # Healthcheck Documentation 2 | This plugin provides a healthcheck stack that can be used to check the status 3 | of your application and its services. 4 | 5 | ## Config/Setup 6 | The simplest way is to use Configure `Setup.Healthcheck.checks`: 7 | ```php 8 | 'Setup' => [ 9 | 'Healthcheck' => [ 10 | 'checks' => [ 11 | \Setup\Healthcheck\Check\Environment\PhpVersionCheck::class, 12 | \Setup\Healthcheck\Check\Core\CakeVersionCheck::class, 13 | // ... 14 | ], 15 | ], 16 | ], 17 | ``` 18 | Once defined, this will replace the defaults. 19 | You can also use the `Setup.Healthcheck.checks` config to add your own checks. 20 | 21 | To only replace the ones you need, you can also merge with the defaults: 22 | ```php 23 | 'Setup' => [ 24 | 'Healthcheck' => [ 25 | 'checks' => [ 26 | \Setup\Healthcheck\Check\Environment\PhpUploadLimitCheck::class => [ 27 | 'min' => 64, 28 | ], 29 | // ... 30 | ] + \Setup\Healthcheck\HealthcheckCollector::defaultChecks(), 31 | ], 32 | ], 33 | ``` 34 | 35 | 36 | 37 | If you need to pass configs to the checks, you can do so by using the `Setup.Healthcheck.checksConfig` config: 38 | ```php 39 | 'Setup' => [ 40 | 'Healthcheck' => [ 41 | 'checksConfig' => [ 42 | \Setup\Healthcheck\Check\Core\CakeVersionCheck::class => [ 43 | 'overrideComparisonChar' => '^', 44 | ], 45 | // ... 46 | ], 47 | ], 48 | ], 49 | ``` 50 | You can also pass the instance of the check class instead of the class name as a string if needed. 51 | 52 | 53 | ## Usage 54 | 55 | You can use the healthcheck stack by accessing the `/setup/healthcheck` endpoint in your application. 56 | In debug mode you can see the issues in detail. In production mode, only the status is shown. 57 | 58 | For CLI you can run the command: 59 | ```bash 60 | bin/cake healthcheck 61 | ``` 62 | 63 | You can also write a queue task to run the healthcheck periodically and log the results or 64 | on errors directly alert the admin(s). 65 | Using [QueueScheduler plugin](https://github.com/dereuromark/cakephp-queue-scheduler) you can directly 66 | add a scheduled task for it in the backend, e.g. every hour. 67 | 68 | -------------------------------------------------------------------------------- /docs/Install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## How to include 4 | Installing the Plugin is pretty much as with every other CakePHP Plugin. 5 | 6 | Require the plugin using Packagist/Composer by running this in your application's folder: 7 | 8 | composer require dereuromark/cakephp-setup 9 | 10 | Note that you can also use `require-dev` if you don't need it for production environments and only use the dev tools. 11 | 12 | If you want, however, to use certain shells like "User" in the productive environment, as well, please 13 | use `require` then. 14 | Maintenance Mode and additional SetupComponent functionality would also not be available, otherwise. 15 | 16 | Details @ https://packagist.org/packages/dereuromark/cakephp-setup 17 | 18 | This will load the plugin (within your boostrap file): 19 | ```php 20 | Plugin::load('Setup'); 21 | ``` 22 | or 23 | ```php 24 | Plugin::loadAll(); 25 | ``` 26 | 27 | The recommendation is to also include the bootstrap file to leverage the debug output functions: 28 | ```php 29 | Plugin::load('Setup', ['bootstrap' => true]); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/Maintenance/Uptime.md: -------------------------------------------------------------------------------- 1 | # Uptime route 2 | 3 | By default 4 | ``` 5 | /admin/setup/uptime 6 | ``` 7 | 8 | You can customize this on project level. 9 | -------------------------------------------------------------------------------- /docs/Panel/L10nPanel.md: -------------------------------------------------------------------------------- 1 | ## DebugKit L10n Panel 2 | The Setup plugin ships with a useful DebugKit panel to show quickly the current localization status 3 | - Datetime 4 | - Date 5 | - Time 6 | 7 | ### Enable the panel 8 | Activate the panel in your config: 9 | 10 | ```php 11 | 'DebugKit' => [ 12 | 'panels' => [ 13 | ... 14 | 'Setup.L10n' => true, 15 | ], 16 | ], 17 | ``` 18 | 19 | Now it should be visible in your DebugKit panel list. 20 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # CakePHP Setup Plugin Documentation 2 | 3 | ## Version notice 4 | 5 | This branch only works for **CakePHP 4.x** 6 | 7 | ## Installation 8 | * [Installation](Install.md) 9 | 10 | ## Detailed Documentation - Quicklinks 11 | * [Maintenance Mode](Maintenance/Maintenance.md) 12 | * [Setup Component](Component/Setup.md) 13 | * [Setup Web Backend](Controller/Setup.md) 14 | * [Useful Setup Commands](Console/Commands.md) 15 | * [Healthcheck](Healthcheck/Healthcheck.md) 16 | * [Uptime](Maintenance/Uptime.md) 17 | * 18 | ## Bake Templates Deluxe 19 | The highly advanced bake templates have been further enhanced and are part of this plugin. 20 | The defaults go well together with the Tools plugin, Bootstrap3+ and some other useful defaults. 21 | You can also just steal ideas, of course ;) 22 | * [Setup plugin Bake templates](Console/Bake.md) 23 | 24 | ## Useful debugging help 25 | The following are convenience wrappers to debug safely. They will only show output with debug true. 26 | 27 | * dd($data) = debug() + die() // This is now also in CakePHP 3.3+ directly :-) 28 | * prd($data) = pr() + die() 29 | * vd() = var_dump() 30 | * vdd($data) = var_dump() + die() 31 | 32 | They are available when you include the plugin's bootstrap at Plugin::load(). 33 | 34 | ## Testing 35 | You can test using a local installation of phpunit or the phar version of it: 36 | 37 | cd .../cakephp-setup 38 | composer install // or: php composer.phar install 39 | composer test-setup 40 | composer test 41 | 42 | To test a specific file: 43 | 44 | php phpunit.phar /path/to/MyClass.php 45 | 46 | To test MySQL specific tests, run this before (you might have to adjust your connection details): 47 | ``` 48 | export DB_URL="mysql://root:secret@127.0.0.1/cake_test" 49 | ``` 50 | By default the tests use an SQLite DB. 51 | 52 | ## Tips 53 | 54 | Import Huge SQL file: 55 | 56 | ...\bin\mysql -u root dbname < dumpfilename.sql 57 | 58 | Same other direction: 59 | 60 | ...\bin\mysqldump -h host -u root -p dbname > dumpfilename.sql 61 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | config/ 7 | src/ 8 | tests/ 9 | /tests/test_files/ 10 | 11 | 12 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 8 3 | paths: 4 | - src/ 5 | 6 | bootstrapFiles: 7 | - %rootDir%/../../../tests/bootstrap.php 8 | reportUnmatchedIgnoredErrors: false 9 | earlyTerminatingMethodCalls: 10 | Cake\Console\BaseCommand: 11 | - abort 12 | dynamicConstantNames: 13 | - WINDOWS 14 | 15 | ignoreErrors: 16 | - identifier: missingType.iterableValue 17 | - identifier: missingType.generics 18 | - identifier: include.fileNotFound 19 | - identifier: trait.unused 20 | - '#Parameter \#1 \$items of class .+Collection constructor expects iterable, .+StatementInterface given.#' 21 | - '#Call to function method\_exists\(\) with .+Entity.+ and .+statuses.+ will always evaluate to true.#' 22 | - '#Call to function property\_exists\(\) with .+Table and .+ will always evaluate to false.#' 23 | - 24 | message: '#If condition is always false.#' 25 | path: '%currentWorkingDirectory%/src/Shell/IndentShell.php' 26 | -------------------------------------------------------------------------------- /src/Auth/AbstractPasswordHasher.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected array $_defaultConfig = []; 22 | 23 | /** 24 | * Constructor 25 | * 26 | * @param array $config Array of config. 27 | */ 28 | public function __construct(array $config = []) { 29 | $this->setConfig($config); 30 | } 31 | 32 | /** 33 | * Generates password hash. 34 | * 35 | * @param string $password Plain text password to hash. 36 | * @return string The password hash 37 | */ 38 | abstract public function hash(string $password): string; 39 | 40 | /** 41 | * Check hash. Generate hash from user provided password string or data array 42 | * and check against existing hash. 43 | * 44 | * @param string $password Plain text password to hash. 45 | * @param string $hashedPassword Existing hashed password. 46 | * @return bool True if hashes match else false. 47 | */ 48 | abstract public function check(string $password, string $hashedPassword): bool; 49 | 50 | /** 51 | * Returns true if the password need to be rehashed, due to the password being 52 | * created with anything else than the passwords generated by this class. 53 | * 54 | * Returns true by default since the only implementation users should rely 55 | * on is the one provided by default in php 5.5+ or any compatible library 56 | * 57 | * @param string $password The password to verify 58 | * @return bool 59 | */ 60 | public function needsRehash(string $password): bool { 61 | return password_needs_rehash($password, PASSWORD_DEFAULT); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/Auth/DefaultPasswordHasher.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected array $_defaultConfig = [ 24 | 'hashType' => PASSWORD_DEFAULT, 25 | 'hashOptions' => [], 26 | ]; 27 | 28 | /** 29 | * Generates password hash. 30 | * 31 | * @psalm-suppress InvalidNullableReturnType 32 | * @link https://book.cakephp.org/4/en/controllers/components/authentication.html#hashing-passwords 33 | * @param string $password Plain text password to hash. 34 | * @return string Password hash or false on failure 35 | */ 36 | public function hash(string $password): string { 37 | return password_hash( 38 | $password, 39 | $this->_config['hashType'], 40 | $this->_config['hashOptions'], 41 | ); 42 | } 43 | 44 | /** 45 | * Check hash. Generate hash for user provided password and check against existing hash. 46 | * 47 | * @param string $password Plain text password to hash. 48 | * @param string $hashedPassword Existing hashed password. 49 | * @return bool True if hashes match else false. 50 | */ 51 | public function check(string $password, string $hashedPassword): bool { 52 | return password_verify($password, $hashedPassword); 53 | } 54 | 55 | /** 56 | * Returns true if the password need to be rehashed, due to the password being 57 | * created with anything else than the passwords generated by this class. 58 | * 59 | * @param string $password The password to verify 60 | * @return bool 61 | */ 62 | public function needsRehash(string $password): bool { 63 | return password_needs_rehash($password, $this->_config['hashType'], $this->_config['hashOptions']); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Auth/PasswordHasherFactory.php: -------------------------------------------------------------------------------- 1 | |string $passwordHasher Name of the password hasher or an array with 17 | * at least the key `className` set to the name of the class to use 18 | * @throws \RuntimeException If password hasher class not found or 19 | * it does not extend {@link \Setup\Auth\AbstractPasswordHasher} 20 | * @return \Setup\Auth\AbstractPasswordHasher Password hasher instance 21 | */ 22 | public static function build($passwordHasher): AbstractPasswordHasher { 23 | $config = []; 24 | if (is_string($passwordHasher)) { 25 | $class = $passwordHasher; 26 | } else { 27 | $class = $passwordHasher['className']; 28 | $config = $passwordHasher; 29 | unset($config['className']); 30 | } 31 | 32 | $className = App::className('Setup.' . $class, 'Auth', 'PasswordHasher'); 33 | if ($className === null) { 34 | throw new RuntimeException(sprintf('Password hasher class "%s" was not found.', $class)); 35 | } 36 | 37 | $hasher = new $className($config); 38 | if (!($hasher instanceof AbstractPasswordHasher)) { 39 | throw new RuntimeException('Password hasher must extend AbstractPasswordHasher class.'); 40 | } 41 | 42 | return $hasher; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/Command/CliTestCommand.php: -------------------------------------------------------------------------------- 1 | out('Router::url(\'/\'): ' . PHP_EOL . "\t" . $url); 33 | 34 | $arrayUrl = ['controller' => 'Test']; 35 | if ($args->getOption('prefix')) { 36 | /** @var non-falsy-string $prefix */ 37 | $prefix = $args->getOption('prefix'); 38 | $arrayUrl['prefix'] = $prefix; 39 | } 40 | if ($args->getOption('plugin')) { 41 | /** @var non-falsy-string $plugin */ 42 | $plugin = $args->getOption('plugin'); 43 | $arrayUrl['plugin'] = $plugin; 44 | } 45 | 46 | $url = Router::url($arrayUrl); 47 | $text = $this->_urlToText($arrayUrl); 48 | $io->out('Router::url([' . $text . ']): ' . PHP_EOL . "\t" . $url); 49 | 50 | $io->out($io->nl()); 51 | $io->out('Full base URLs:'); 52 | 53 | $url = Router::url('/', true); 54 | $io->out('Router::url(\'/\', true): ' . PHP_EOL . "\t" . $url); 55 | 56 | $url = Router::url($arrayUrl, true); 57 | $io->out('Router::url([' . $text . '], true): ' . PHP_EOL . "\t" . $url); 58 | } 59 | 60 | /** 61 | * Hook action for defining this command's option parser. 62 | * 63 | * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined 64 | * 65 | * @return \Cake\Console\ConsoleOptionParser The built parser. 66 | */ 67 | protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { 68 | $parser = parent::buildOptionParser($parser); 69 | $parser->setDescription(static::getDescription()); 70 | 71 | $parser->addOption('prefix', [ 72 | 'short' => 'p', 73 | 'help' => 'Prefix.', 74 | ]); 75 | $parser->addOption('plugin', [ 76 | 'short' => 'x', 77 | 'help' => 'Plugin.', 78 | ]); 79 | 80 | return $parser; 81 | } 82 | 83 | /** 84 | * @param array $arrayUrl 85 | * @return string 86 | */ 87 | protected function _urlToText(array $arrayUrl): string { 88 | $url = []; 89 | foreach ($arrayUrl as $k => $v) { 90 | $url[] = "'$k' => '$v'"; 91 | } 92 | 93 | return implode(', ', $url); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/Command/CurrentConfigConfigureCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('key'); 38 | } 39 | 40 | /** 41 | * @param \Cake\Console\Arguments $args The command arguments. 42 | * @param \Cake\Console\ConsoleIo $io The console io 43 | * @return int|null|void The exit code or null for success 44 | */ 45 | public function execute(Arguments $args, ConsoleIo $io) { 46 | $key = $args->getArgument('key'); 47 | $config = Configure::read($key); 48 | if (is_array($config)) { 49 | ksort($config); 50 | } 51 | $type = Debugger::getType($config); 52 | if (is_array($config)) { 53 | $type .= ' and size of ' . count($config); 54 | } 55 | $io->out(print_r($config, true)); 56 | $io->info('of type ' . $type); 57 | 58 | return CommandInterface::CODE_SUCCESS; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/Command/CurrentConfigDisplayCommand.php: -------------------------------------------------------------------------------- 1 | info('Security Salt: ' . Security::getSalt()); 40 | $io->info('Full Base URL: ' . Configure::read('App.fullBaseUrl')); 41 | 42 | $io->out(); 43 | 44 | $time = new DateTime(); 45 | $timestamp = $time->getTimestamp(); 46 | $offset = (int)($time->getOffset() / HOUR); 47 | $io->info('Datetime: ' . $time->format(FORMAT_DB_DATETIME) . ' (' . date_default_timezone_get() . ') [GMT' . ($offset > 0 ? '+' . $offset : '-' . abs($offset)) . ']'); 48 | $io->info('Timestamp: ' . $timestamp . ' => ' . (new DateTime(date(FORMAT_DB_DATETIME, $timestamp)))->format(FORMAT_DB_DATETIME)); 49 | 50 | $io->out(); 51 | 52 | $io->info('Email Config:'); 53 | $config = (array)Mailer::getConfig('default'); 54 | foreach ($config as $key => $value) { 55 | $io->out(' - ' . $key . ': ' . $value); 56 | } 57 | 58 | $io->out(); 59 | 60 | $io->info('ENV:'); 61 | foreach ($_ENV as $key => $value) { 62 | $io->out(' - ' . $key . ': ' . $value); 63 | } 64 | 65 | return CommandInterface::CODE_SUCCESS; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/Command/CurrentConfigPhpinfoCommand.php: -------------------------------------------------------------------------------- 1 | out($phpinfo); 44 | 45 | return CommandInterface::CODE_SUCCESS; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/Command/CurrentConfigValidateCommand.php: -------------------------------------------------------------------------------- 1 | out('### DB default ###'); 39 | try { 40 | $db = ConnectionManager::get('default'); 41 | $io->out(print_r($db->config(), true)); 42 | } catch (Exception $e) { 43 | $io->err($e->getMessage()); 44 | } 45 | 46 | $io->out(); 47 | $io->out('### DB test ###'); 48 | try { 49 | $db = ConnectionManager::get('test'); 50 | $io->out(print_r($db->config(), true)); 51 | } catch (Exception $e) { 52 | $io->err($e->getMessage()); 53 | } 54 | 55 | $io->out(); 56 | $io->out('### Cache ###'); 57 | 58 | $configured = Cache::configured(); 59 | foreach ($configured as $key) { 60 | $io->out($key . ':'); 61 | $io->out((string)json_encode(Cache::getConfig($key), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 62 | } 63 | 64 | return CommandInterface::CODE_SUCCESS; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/Command/DbInitCommand.php: -------------------------------------------------------------------------------- 1 | args = $args; 47 | $this->io = $io; 48 | 49 | $connection = $this->_getConnection((string)$args->getOption('connection')); 50 | $config = $connection->config(); 51 | $name = substr($config['driver'], strrpos($config['driver'], '\\') + 1); 52 | $config['scheme'] = strtolower($name); 53 | if ($config['scheme'] === 'sqlite' && $config['database'] === ':memory:') { 54 | $this->io->warning('Using in-memory database, skipping.'); 55 | 56 | return; 57 | } 58 | 59 | $dsn = Text::insert('{scheme}://{username}:{password}@{host}', $config, ['before' => '{', 'after' => '}']); 60 | ConnectionManager::setConfig('setup', ['url' => $dsn]); 61 | 62 | /** @var \Cake\Database\Connection $connection */ 63 | $connection = ConnectionManager::get('setup'); 64 | $connection->execute('CREATE DATABASE IF NOT EXISTS ' . $config['database'] . ' ' . 65 | 'DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;'); 66 | 67 | $this->io->success('Done: ' . $config['database']); 68 | } 69 | 70 | /** 71 | * @return \Cake\Console\ConsoleOptionParser 72 | */ 73 | public function getOptionParser(): ConsoleOptionParser { 74 | $options = [ 75 | 'connection' => [ 76 | 'short' => 'c', 77 | 'help' => 'The datasource connection to use.', 78 | 'default' => 'default', 79 | ], 80 | ]; 81 | 82 | return parent::getOptionParser() 83 | ->setDescription(static::getDescription()) 84 | ->addOptions($options); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/Command/DbResetCommand.php: -------------------------------------------------------------------------------- 1 | args = $args; 47 | $this->io = $io; 48 | 49 | $db = $this->_getConnection((string)$args->getOption('connection')); 50 | 51 | /** @var \Cake\Database\Schema\Collection $schemaCollection */ 52 | $schemaCollection = $db->getSchemaCollection(); 53 | $sources = $schemaCollection->listTables(); 54 | foreach ($sources as $key => $source) { 55 | if ($source === 'phinxlog' || str_contains($source, '_phinxlog')) { 56 | unset($sources[$key]); 57 | } 58 | } 59 | 60 | $tableTruncates = 'TRUNCATE TABLE ' . implode(';' . PHP_EOL . 'TRUNCATE TABLE ', $sources) . ';'; 61 | 62 | $sql = <<io->out('--------', 1, ConsoleIo::VERBOSE); 70 | $this->io->out($sql, 1, ConsoleIo::VERBOSE); 71 | $this->io->out('--------', 1, ConsoleIo::VERBOSE); 72 | 73 | $this->io->out('Truncating ' . count($sources) . ' tables'); 74 | if (!$this->args->getOption('dry-run') && !$this->args->getOption('force')) { 75 | $looksGood = $this->io->askChoice('Sure?', ['y', 'n'], 'y'); 76 | if ($looksGood !== 'y') { 77 | $this->io->abort('Aborted!'); 78 | } 79 | } 80 | 81 | if (!$this->args->getOption('dry-run')) { 82 | $db->execute($sql); 83 | } 84 | 85 | $this->io->out('Done ' . ($this->args->getOption('dry-run') ? 'DRY-RUN' : '') . ' :)'); 86 | } 87 | 88 | /** 89 | * @return \Cake\Console\ConsoleOptionParser 90 | */ 91 | public function getOptionParser(): ConsoleOptionParser { 92 | $options = [ 93 | 'dry-run' => [ 94 | 'short' => 'd', 95 | 'help' => 'Dry run the reset, no data will be removed.', 96 | 'boolean' => true, 97 | ], 98 | 'force' => [ 99 | 'short' => 'f', 100 | 'help' => 'Force the command, do not ask for confirmation.', 101 | 'boolean' => true, 102 | ], 103 | 'connection' => [ 104 | 'short' => 'c', 105 | 'help' => 'The datasource connection to use.', 106 | 'default' => 'default', 107 | ], 108 | ]; 109 | 110 | return parent::getOptionParser() 111 | ->setDescription(static::getDescription() . ' Note: It disables foreign key checks to do this.') 112 | ->addOptions($options); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/Command/DbWipeCommand.php: -------------------------------------------------------------------------------- 1 | args = $args; 47 | $this->io = $io; 48 | 49 | $db = $this->_getConnection((string)$args->getOption('connection')); 50 | 51 | /** @var \Cake\Database\Schema\Collection $schemaCollection */ 52 | $schemaCollection = $db->getSchemaCollection(); 53 | $sources = $schemaCollection->listTables(); 54 | 55 | $tableTruncates = 'DROP TABLE ' . implode(';' . PHP_EOL . 'DROP TABLE ', $sources) . ';'; 56 | 57 | $sql = <<io->out('--------', 1, ConsoleIo::VERBOSE); 65 | $this->io->out($sql, 1, ConsoleIo::VERBOSE); 66 | $this->io->out('--------', 1, ConsoleIo::VERBOSE); 67 | 68 | $this->io->out('Dropping ' . count($sources) . ' tables'); 69 | if (!$this->args->getOption('dry-run') && !$this->args->getOption('force')) { 70 | $looksGood = $this->io->askChoice('Sure?', ['y', 'n'], 'y'); 71 | if ($looksGood !== 'y') { 72 | $this->io->abort('Aborted!'); 73 | } 74 | } 75 | 76 | if (!$this->args->getOption('dry-run')) { 77 | $db->execute($sql); 78 | } 79 | 80 | $this->io->out('Done ' . ($this->args->getOption('dry-run') ? 'DRY-RUN' : '') . ' :)'); 81 | } 82 | 83 | /** 84 | * @return \Cake\Console\ConsoleOptionParser 85 | */ 86 | public function getOptionParser(): ConsoleOptionParser { 87 | $options = [ 88 | 'dry-run' => [ 89 | 'short' => 'd', 90 | 'help' => 'Dry run the reset, no tables will be removed.', 91 | 'boolean' => true, 92 | ], 93 | 'force' => [ 94 | 'short' => 'f', 95 | 'help' => 'Force the command, do not ask for confirmation.', 96 | 'boolean' => true, 97 | ], 98 | 'connection' => [ 99 | 'short' => 'c', 100 | 'help' => 'The datasource connection to use.', 101 | 'default' => 'default', 102 | ], 103 | ]; 104 | 105 | return parent::getOptionParser() 106 | ->setDescription(static::getDescription() . ' Note: It disables foreign key checks to do this.') 107 | ->addOptions($options); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/Command/MailCheckCommand.php: -------------------------------------------------------------------------------- 1 | warning('Configure::read(\'Config.live\') is not enabled. Normal emails wouldn\'t be sent. Overwriting it for this check only.'); 26 | Configure::write('Config.live', true); 27 | } 28 | 29 | $io->verbose('From email as configured: ' . Configure::read('Config.systemEmail', Configure::read('Config.adminEmail'))); 30 | 31 | $to = $io->ask('Email to send to', Configure::read('Config.adminEmail')); 32 | if (!$to) { 33 | return static::CODE_ERROR; 34 | } 35 | 36 | $email = new Mailer(); 37 | $email->setTo($to); 38 | $email->setSubject('Test Mail from CLI'); 39 | 40 | $url = Router::url('/', true); 41 | $message = <<deliver($message); 48 | $io->verbose('Result:'); 49 | $io->verbose((string)json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); 50 | 51 | return static::CODE_SUCCESS; 52 | } 53 | 54 | /** 55 | * Get the option parser. 56 | * 57 | * @return \Cake\Console\ConsoleOptionParser 58 | */ 59 | public function getOptionParser(): ConsoleOptionParser { 60 | $parser = parent::getOptionParser(); 61 | 62 | return $parser; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Command/MaintenanceModeActivateCommand.php: -------------------------------------------------------------------------------- 1 | Maintenance = new Maintenance(); 36 | } 37 | 38 | /** 39 | * Implement this action with your command's logic. 40 | * 41 | * @param \Cake\Console\Arguments $args The command arguments. 42 | * @param \Cake\Console\ConsoleIo $io The console io 43 | * @return int|null|void The exit code or null for success 44 | */ 45 | public function execute(Arguments $args, ConsoleIo $io) { 46 | $this->Maintenance->setMaintenanceMode(true); 47 | 48 | $io->out('Maintenance mode activated ...'); 49 | } 50 | 51 | /** 52 | * Hook action for defining this command's option parser. 53 | * 54 | * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined 55 | * @return \Cake\Console\ConsoleOptionParser The built parser. 56 | */ 57 | protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { 58 | $parser = parent::buildOptionParser($parser); 59 | 60 | return $parser; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/Command/MaintenanceModeDeactivateCommand.php: -------------------------------------------------------------------------------- 1 | Maintenance = new Maintenance(); 33 | } 34 | 35 | /** 36 | * Implement this action with your command's logic. 37 | * 38 | * @param \Cake\Console\Arguments $args The command arguments. 39 | * @param \Cake\Console\ConsoleIo $io The console io 40 | * @return int|null|void The exit code or null for success 41 | */ 42 | public function execute(Arguments $args, ConsoleIo $io) { 43 | $this->Maintenance->setMaintenanceMode(false); 44 | $io->out('Maintenance mode deactivated ...'); 45 | } 46 | 47 | /** 48 | * Hook action for defining this command's option parser. 49 | * 50 | * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined 51 | * @return \Cake\Console\ConsoleOptionParser The built parser. 52 | */ 53 | protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { 54 | $parser = parent::buildOptionParser($parser); 55 | 56 | $parser->addOption('force', [ 57 | 'short' => 'f', 58 | 'help' => 'Force (reset)', 59 | 'boolean' => true, 60 | ]); 61 | 62 | return $parser; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Command/MaintenanceModeStatusCommand.php: -------------------------------------------------------------------------------- 1 | Maintenance = new Maintenance(); 33 | } 34 | 35 | /** 36 | * Implement this action with your command's logic. 37 | * 38 | * @param \Cake\Console\Arguments $args The command arguments. 39 | * @param \Cake\Console\ConsoleIo $io The console io 40 | * @return int|null|void The exit code or null for success 41 | */ 42 | public function execute(Arguments $args, ConsoleIo $io) { 43 | $isMaintenanceMode = $this->Maintenance->isMaintenanceMode(); 44 | if ($isMaintenanceMode) { 45 | $io->out('Maintenance mode active!'); 46 | } else { 47 | $io->out('Maintenance mode not active'); 48 | } 49 | } 50 | 51 | /** 52 | * Hook action for defining this command's option parser. 53 | * 54 | * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined 55 | * @return \Cake\Console\ConsoleOptionParser The built parser. 56 | */ 57 | protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { 58 | $parser = parent::buildOptionParser($parser); 59 | 60 | return $parser; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/Command/MaintenanceModeWhitelistCommand.php: -------------------------------------------------------------------------------- 1 | Maintenance = new Maintenance(); 34 | } 35 | 36 | /** 37 | * Implement this action with your command's logic. 38 | * 39 | * @param \Cake\Console\Arguments $args The command arguments. 40 | * @param \Cake\Console\ConsoleIo $io The console io 41 | * @return int|null|void The exit code or null for success 42 | */ 43 | public function execute(Arguments $args, ConsoleIo $io) { 44 | $ip = $args->getArgument('ip'); 45 | if ($ip) { 46 | if (!Validation::ipOrSubnet($ip)) { 47 | $io->abort($ip . ' is not a valid IP address or subnet.'); 48 | } 49 | if ($args->getOption('remove')) { 50 | $this->Maintenance->clearWhitelist([$ip]); 51 | } else { 52 | $this->Maintenance->addToWhitelist([$ip]); 53 | } 54 | $io->out('Done!', 2); 55 | } else { 56 | if ($args->getOption('remove')) { 57 | $this->Maintenance->clearWhitelist(); 58 | } 59 | } 60 | 61 | $io->out('Current whitelist:'); 62 | /** @var array $ip */ 63 | $ip = $this->Maintenance->whitelist(); 64 | if (!$ip) { 65 | $io->out('n/a'); 66 | } else { 67 | $io->out($ip); 68 | } 69 | 70 | return null; 71 | } 72 | 73 | /** 74 | * Hook action for defining this command's option parser. 75 | * 76 | * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined 77 | * @return \Cake\Console\ConsoleOptionParser The built parser. 78 | */ 79 | protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { 80 | $parser = parent::buildOptionParser($parser); 81 | 82 | $parser->addArgument('ip', [ 83 | 'help' => 'IP address (or subnet) to specify.', 84 | ]); 85 | 86 | $parser->addOption('remove', [ 87 | 'short' => 'r', 88 | 'help' => 'Remove either all or specific IPs.', 89 | 'boolean' => true, 90 | ]); 91 | $parser->addOption('debug', [ 92 | 'short' => 'd', 93 | 'help' => 'Enable debug mode for whitelisted IPs.', 94 | 'boolean' => true, 95 | ]); 96 | 97 | return $parser; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/Command/Traits/DbBackupTrait.php: -------------------------------------------------------------------------------- 1 | params['path'])) { 58 | $customPath = realpath($this->params['path']); 59 | if ($customPath) { 60 | $path = $customPath; 61 | } 62 | } 63 | 64 | return $path; 65 | } 66 | 67 | /** 68 | * Returns available files to restore 69 | * in reverse order (newest ones first!) 70 | * 71 | * @param string $path 72 | * 73 | * @return array Files 74 | */ 75 | protected function _getFiles(string $path): array { 76 | $Directory = new RecursiveDirectoryIterator($path); 77 | $It = new RecursiveIteratorIterator($Directory); 78 | $Regex = new RegexIterator($It, '/\bbackup_.*?[\.sql|\.gz]$/', RecursiveRegexIterator::GET_MATCH); 79 | $files = []; 80 | foreach ($Regex as $v) { 81 | $files[] = $v[0]; 82 | } 83 | $files = array_reverse($files); 84 | 85 | return $files; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Command/Traits/DbToolsTrait.php: -------------------------------------------------------------------------------- 1 | params['connection'])) { 24 | $name = $this->params['connection']; 25 | } elseif ($name === null) { 26 | $name = 'default'; 27 | } 28 | 29 | /** @var \Cake\Database\Connection $connection */ 30 | $connection = ConnectionManager::get($name); 31 | 32 | return $connection; 33 | } 34 | 35 | /** 36 | * @param string $prefix 37 | * @param string|null $connection 38 | * 39 | * @return array 40 | */ 41 | protected function _getTables(string $prefix = '', ?string $connection = null): array { 42 | $db = $this->_getConnection($connection ?? 'default'); 43 | $config = $db->config(); 44 | $database = $config['database']; 45 | 46 | $script = " 47 | SELECT table_name 48 | FROM information_schema.tables AS tb 49 | WHERE table_schema = '$database' 50 | AND table_name LIKE '$prefix%' OR table_name LIKE '\_%';"; 51 | 52 | $res = $db->execute($script)->fetchAll(Pdo::FETCH_ASSOC); 53 | if (!$res) { 54 | throw new RuntimeException('No tables found for DB `' . $database . '`...'); 55 | } 56 | 57 | /** @var array $whitelist */ 58 | $whitelist = []; //Text::tokenize((string)$this->args->getOption('table')); 59 | 60 | $tables = []; 61 | foreach ($res as $key => $table) { 62 | $tableName = $table['table_name'] ?? $table['TABLE_NAME']; 63 | if (str_starts_with($tableName, '_')) { 64 | continue; 65 | } 66 | 67 | if ($whitelist && !in_array($tableName, $whitelist)) { 68 | continue; 69 | } 70 | 71 | $tables[] = $tableName; 72 | } 73 | 74 | sort($tables); 75 | 76 | return $tables; 77 | } 78 | 79 | /** 80 | * @param string|null $model 81 | * @param string|null $plugin 82 | * 83 | * @throws \RuntimeException 84 | * 85 | * @return array<\Cake\ORM\Table> 86 | */ 87 | protected function _getModels(?string $model, ?string $plugin): array { 88 | if ($model) { 89 | $className = App::className($plugin ? $plugin . '.' : $model, 'Model/Table', 'Table'); 90 | if (!$className) { 91 | throw new RuntimeException('Model not found: ' . $model); 92 | } 93 | 94 | return [ 95 | TableRegistry::getTableLocator()->get($plugin ? $plugin . '.' : $model), 96 | ]; 97 | } 98 | 99 | $folders = App::classPath('Model/Table', $plugin); 100 | 101 | $models = []; 102 | foreach ($folders as $folder) { 103 | $folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true); 104 | 105 | foreach ($folderContent[1] as $file) { 106 | $name = pathinfo($file, PATHINFO_FILENAME); 107 | 108 | preg_match('#^(.+)Table$#', $name, $matches); 109 | if (!$matches) { 110 | continue; 111 | } 112 | 113 | $model = $matches[1]; 114 | 115 | $className = App::className($plugin ? $plugin . '.' . $model : $model, 'Model/Table', 'Table'); 116 | if (!$className) { 117 | continue; 118 | } 119 | 120 | $models[] = TableRegistry::getTableLocator()->get($plugin ? $plugin . '.' . $model : $model); 121 | } 122 | } 123 | 124 | return $models; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/Command/UserUpdateCommand.php: -------------------------------------------------------------------------------- 1 | fetchModel(CLASS_USERS); 39 | 40 | $displayField = $Users->getDisplayField(); 41 | if (!is_string($displayField)) { 42 | $io->abort('Only supported for single display fields'); 43 | } 44 | $displayFieldName = Inflector::humanize($displayField); 45 | 46 | $displayFieldValue = $args->getArgument('login'); 47 | while (!$displayFieldValue) { 48 | $displayFieldValue = $io->ask($displayFieldName); 49 | } 50 | 51 | /** @var \App\Model\Entity\User $user */ 52 | $user = $Users->find()->where([$displayField => $displayFieldValue])->firstOrFail(); 53 | 54 | $password = $args->getArgument('password'); 55 | while (!$password) { 56 | $password = $io->ask('Password'); 57 | } 58 | 59 | $Users->addBehavior('Tools.Passwordable', ['confirm' => false]); 60 | $Users->patchEntity($user, ['pwd' => $password]); 61 | 62 | if ($args->getOption('dry-run')) { 63 | $io->out('User dry-run inserted!'); 64 | $io->out('Pwd Hash: ' . $user->password); 65 | 66 | return; 67 | } 68 | 69 | $Users->saveOrFail($user); 70 | 71 | $io->success('Password updated for user ' . $displayFieldValue); 72 | } 73 | 74 | /** 75 | * @return \Cake\Console\ConsoleOptionParser 76 | */ 77 | public function getOptionParser(): ConsoleOptionParser { 78 | return parent::getOptionParser() 79 | ->setDescription('The User shell can create a user on the fly for local development. 80 | Note that you can define the constant CLASS_USERS in your bootstrap to point to another table class, if \'Users\' is not used. 81 | Make sure you configured the Passwordable behavior accordingly as per docs.') 82 | ->addArgument('login', [ 83 | 'help' => 'Display field value', 84 | ]) 85 | ->addArgument('password') 86 | ->addOption('dry-run', [ 87 | 'short' => 'd', 88 | 'help' => 'Dry run the command, no data will actually be modified.', 89 | 'boolean' => true, 90 | ]); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Controller/Admin/ConfigurationController.php: -------------------------------------------------------------------------------- 1 | loadComponent('Flash'); 27 | } 28 | 29 | /** 30 | * @return \Cake\Http\Response|null|void 31 | */ 32 | public function index() { 33 | $Debug = new Debug(); 34 | $uptime = $Debug->getUptime(); 35 | $serverLoad = $Debug->serverLoad(); 36 | $mem = $Debug->getRam(); 37 | $memory = 'n/a'; 38 | if ($mem) { 39 | $memory = '' . $mem['total'] . ' MB total; ' . $mem['free'] . ' MB free'; 40 | } 41 | $this->set(compact('serverLoad', 'memory', 'uptime')); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Controller/Admin/DatabaseController.php: -------------------------------------------------------------------------------- 1 | getConnection(); 25 | 26 | if (!$table) { 27 | $dbTables = $db->execute('SHOW TABLE STATUS')->fetchAll(PDO::FETCH_ASSOC); 28 | $dbTables = (new Collection($dbTables))->toArray(); 29 | } else { 30 | $dbTables = [ 31 | [ 32 | 'Name' => $table, 33 | ], 34 | ]; 35 | } 36 | 37 | $tables = []; 38 | foreach ($dbTables as $dbTable) { 39 | if (preg_match('/phinxlog$/', $dbTable['Name'])) { 40 | continue; 41 | } 42 | $blacklist = Configure::read('Setup.blacklistedTables'); 43 | if ($blacklist && in_array($dbTable['Name'], $blacklist, true)) { 44 | continue; 45 | } 46 | 47 | $name = $dbTable['Name']; 48 | $Model = TableRegistry::getTableLocator()->get($name, ['allowFallbackClass' => true]); 49 | 50 | $schema = $Model->getSchema(); 51 | $tables[$dbTable['Name']] = [ 52 | 'schema' => $schema, 53 | ]; 54 | } 55 | 56 | $this->set(compact('tables')); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Controller/Admin/SetupController.php: -------------------------------------------------------------------------------- 1 | request->is('post')) { 29 | $enable = (bool)$this->request->getQuery('maintenance'); 30 | 31 | $maintenance->addToWhitelist([$ip]); 32 | $maintenance->setMaintenanceMode($enable); 33 | 34 | return $this->redirect([]); 35 | } 36 | 37 | $isMaintenanceModeEnabled = $maintenance->isMaintenanceMode(); 38 | $whitelisted = $maintenance->whitelisted($ip); 39 | $whitelist = $maintenance->whitelist(); 40 | 41 | $this->set(compact('ip', 'isMaintenanceModeEnabled', 'whitelisted', 'whitelist')); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Controller/Admin/UptimeController.php: -------------------------------------------------------------------------------- 1 | components()->has('Auth') && method_exists($this->components()->get('Auth'), 'allow')) { 23 | $this->components()->get('Auth')->allow(); 24 | } 25 | } 26 | 27 | /** 28 | * @return \Cake\Http\Response|null 29 | */ 30 | public function index() { 31 | $response = ''; 32 | if (!isset($_REQUEST['key'])) { 33 | $response = 'OK'; 34 | } else { 35 | if (preg_match('/^[a-f0-9]{32}$/', $_REQUEST['key'])) { 36 | $response = $_REQUEST['key']; 37 | } 38 | } 39 | 40 | return $this->response->withStringBody($response); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Controller/HealthcheckController.php: -------------------------------------------------------------------------------- 1 | components()->has('Auth') && method_exists($this->components()->get('Auth'), 'allow')) { 26 | $this->components()->get('Auth')->allow(); 27 | } elseif ($this->components()->has('Authentication') && method_exists($this->components()->get('Authentication'), 'allowUnauthenticated')) { 28 | $this->components()->get('Authentication')->allowUnauthenticated(['index']); 29 | } 30 | if ($this->components()->has('Authorization') && method_exists($this->components()->get('Authorization'), 'skipAuthorization')) { 31 | $this->components()->get('Authorization')->skipAuthorization(); 32 | } 33 | } 34 | 35 | /** 36 | * @return \Cake\Http\Response|null|void 37 | */ 38 | public function index() { 39 | $healthcheck = new Healthcheck(new HealthcheckCollector()); 40 | $passed = $healthcheck->run($this->request->getQuery('domain')); 41 | 42 | if ($this->request->is('json')) { 43 | $data = [ 44 | 'passed' => $passed, 45 | ]; 46 | if (Configure::read('debug')) { 47 | $data['result'] = $healthcheck->result(); 48 | } 49 | 50 | return $this->response->withType('application/json') 51 | ->withStringBody(json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 52 | } 53 | 54 | if (!Configure::read('debug')) { 55 | return $this->response->withStringBody($passed ? 'OK' : 'FAIL') 56 | ->withStatus($passed ? 200 : 500); 57 | } 58 | 59 | $result = $healthcheck->result(); 60 | $domains = $healthcheck->domains(); 61 | $this->set(compact('passed', 'result', 'domains')); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/Healthcheck/Check/CheckInterface.php: -------------------------------------------------------------------------------- 1 | 40 | */ 41 | public function scope(): array; 42 | 43 | /** 44 | * Returns the message to display when passed. 45 | * 46 | * @return array 47 | */ 48 | public function successMessage(): array; 49 | 50 | /** 51 | * Returns the message to display when warnings occur. 52 | * 53 | * @return array 54 | */ 55 | public function warningMessage(): array; 56 | 57 | /** 58 | * Returns the message to display when failed. 59 | * 60 | * @return array 61 | */ 62 | public function failureMessage(): array; 63 | 64 | /** 65 | * Returns the message to display additional info. 66 | * 67 | * @return array 68 | */ 69 | public function infoMessage(): array; 70 | 71 | /** 72 | * Returns the name of this check. 73 | * 74 | * @return string 75 | */ 76 | public function name(): string; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Healthcheck/Check/Core/CakeCacheCheck.php: -------------------------------------------------------------------------------- 1 | passed = $this->assertCache(); 29 | } 30 | 31 | /** 32 | * @return string[] 33 | */ 34 | public function failureMessage(): array { 35 | return [ 36 | 'The following cache setups are missing: ' . implode(', ', $this->missing) . '.', 37 | ]; 38 | } 39 | 40 | /** 41 | * @return bool 42 | */ 43 | protected function assertCache(): bool { 44 | $cacheKeys = $this->defaultCacheKeys; 45 | $additional = (array)Configure::read('Healthcheck.checkCacheKeys'); 46 | foreach ($additional as $key) { 47 | if (!in_array($key, $cacheKeys, true)) { 48 | $cacheKeys[] = $key; 49 | } 50 | } 51 | 52 | $issues = []; 53 | foreach ($cacheKeys as $cacheKey) { 54 | if (Cache::getConfig($cacheKey)) { 55 | continue; 56 | } 57 | 58 | $issues[] = $cacheKey; 59 | } 60 | 61 | $this->missing = $issues; 62 | 63 | return !$this->missing; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Healthcheck/Check/Core/CakeSaltCheck.php: -------------------------------------------------------------------------------- 1 | passed = $this->assertSalt(); 20 | } 21 | 22 | /** 23 | * @return string[] 24 | */ 25 | public function failureMessage(): array { 26 | return [ 27 | 'Security.salt is not set up yet.', 28 | ]; 29 | } 30 | 31 | /** 32 | * @return bool 33 | */ 34 | protected function assertSalt(): bool { 35 | $salt = Configure::read('Security.salt'); 36 | 37 | return $salt !== '__SALT__'; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/Healthcheck/Check/Core/CakeVersionCheck.php: -------------------------------------------------------------------------------- 1 | cakeVersion = $cakeVersion; 38 | $this->root = $root; 39 | } 40 | 41 | /** 42 | * @return void 43 | */ 44 | public function check(): void { 45 | $this->passed = $this->assertVersion(); 46 | } 47 | 48 | /** 49 | * @return bool 50 | */ 51 | protected function assertVersion(): bool { 52 | $lockFile = $this->root . 'composer.lock'; 53 | if (!is_file($lockFile)) { 54 | $this->warningMessage[] = 'You need to create the lock file first using composer install: ' . $lockFile; 55 | 56 | return false; 57 | } 58 | 59 | $content = json_decode((string)file_get_contents($lockFile), true); 60 | $packages = Hash::combine($content['packages'], '{n}.name', '{n}.version'); 61 | $version = $packages['cakephp/cakephp'] ?? null; 62 | if (!$version) { 63 | $this->warningMessage[] = 'CakePHP does not seem installed as require dependency.'; 64 | 65 | return false; 66 | } 67 | 68 | if (str_starts_with($version, 'dev-') || str_ends_with($version, '-dev')) { 69 | $this->infoMessage[] = 'CakePHP is installed as dev version `' . $version . '`, which is not recommended for production.'; 70 | 71 | return true; 72 | } 73 | 74 | if (class_exists(Semver::class)) { 75 | if (!Semver::satisfies($this->cakeVersion, $version)) { 76 | $this->failureMessage[] = 'Installed CakePHP version `' . $this->cakeVersion . '` does not match lock file version `' . $version . '`'; 77 | 78 | return false; 79 | } 80 | 81 | $this->infoMessage[] = $this->cakeVersion; 82 | 83 | return true; 84 | } 85 | 86 | $result = $this->cakeVersion === $version; 87 | if (!$result) { 88 | $this->failureMessage[] = 'PHP version `' . $this->cakeVersion . '` does not match lock file version `' . $version . '`'; 89 | 90 | return false; 91 | } 92 | 93 | $this->infoMessage[] = $this->cakeVersion; 94 | 95 | return true; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/Healthcheck/Check/Core/FullBaseUrlCheck.php: -------------------------------------------------------------------------------- 1 | checkFullBaseUrl(); 15 | } 16 | 17 | /** 18 | * @return void 19 | */ 20 | protected function checkFullBaseUrl(): void { 21 | $fullBaseUrl = Configure::read('App.fullBaseUrl'); 22 | if (!$fullBaseUrl) { 23 | $this->failureMessage[] = 'App.fullBaseUrl is not set. Please configure it in your app.php or .env file.'; 24 | $this->passed = false; 25 | 26 | return; 27 | } 28 | 29 | $this->infoMessage[] = $fullBaseUrl; 30 | 31 | $this->passed = true; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/Healthcheck/Check/Database/ConnectCheck.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function check(): void { 38 | $this->assertConnection(); 39 | } 40 | 41 | /** 42 | * @return void 43 | */ 44 | protected function assertConnection(): void { 45 | try { 46 | /** @var \Cake\Database\Connection $connection */ 47 | $connection = ConnectionManager::get($this->connection); 48 | $connection->getDriver()->connect(); 49 | $this->passed = true; 50 | } catch (Exception $connectionError) { 51 | $this->passed = false; 52 | } 53 | 54 | if (!$this->passed) { 55 | $this->failureMessage[] = 'Cannot connect to database on connection `' . $this->connection . '`: ' . $connectionError->getMessage(); 56 | 57 | return; 58 | } 59 | 60 | $this->infoMessage[] = 'The PHP upload limit is set to `' . ini_get('upload_max_filesize') . '` and the post limit is set to `' . ini_get('post_max_size') . '`.'; 61 | } 62 | 63 | /** 64 | * @param string $val 65 | * @return int 66 | */ 67 | protected function toBytes(string $val): int { 68 | $val = trim($val); 69 | $unit = strtolower($val[strlen($val) - 1]); 70 | $bytes = (int)$val; 71 | 72 | switch ($unit) { 73 | case 'g': 74 | $bytes *= 1024; 75 | // Continue 76 | case 'm': 77 | $bytes *= 1024; 78 | // Continue 79 | case 'k': 80 | $bytes *= 1024; 81 | // Continue 82 | } 83 | 84 | return $bytes; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/Healthcheck/Check/Environment/PhpUploadLimitCheck.php: -------------------------------------------------------------------------------- 1 | min = $min; 30 | } 31 | 32 | /** 33 | * @return void 34 | */ 35 | public function check(): void { 36 | $this->assertMinimum(); 37 | } 38 | 39 | /** 40 | * @return void 41 | */ 42 | protected function assertMinimum(): void { 43 | $uploadMax = $this->toBytes((string)ini_get('upload_max_filesize')); 44 | $postMax = $this->toBytes((string)ini_get('post_max_size')); 45 | 46 | $this->passed = true; 47 | $min = $this->min * 1024 * 1024; // Convert MB to bytes 48 | if ($min > $uploadMax) { 49 | $this->failureMessage[] = 'The PHP upload limit is too low. It is currently set to `' . ini_get('upload_max_filesize') . '`, but at least ' . $this->min . ' MB is required.'; 50 | 51 | $this->passed = false; 52 | } 53 | if ($min > $postMax) { 54 | $this->failureMessage[] = 'The PHP post limit is too low. It is currently set to `' . ini_get('post_max_size') . '`, but at least ' . $this->min . ' MB is required.'; 55 | 56 | $this->passed = false; 57 | } 58 | 59 | if (!$this->passed) { 60 | return; 61 | } 62 | 63 | $this->infoMessage[] = 'The PHP upload limit is set to `' . ini_get('upload_max_filesize') . '` and the post limit is set to `' . ini_get('post_max_size') . '`.'; 64 | } 65 | 66 | /** 67 | * @param string $val 68 | * @return int 69 | */ 70 | protected function toBytes(string $val): int { 71 | $val = trim($val); 72 | $unit = strtolower($val[strlen($val) - 1]); 73 | $bytes = (int)$val; 74 | 75 | switch ($unit) { 76 | case 'g': 77 | $bytes *= 1024; 78 | // Continue 79 | case 'm': 80 | $bytes *= 1024; 81 | // Continue 82 | case 'k': 83 | $bytes *= 1024; 84 | // Continue 85 | } 86 | 87 | return $bytes; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/Healthcheck/Healthcheck.php: -------------------------------------------------------------------------------- 1 | collector = $collector; 25 | $this->result = new Collection([]); 26 | } 27 | 28 | /** 29 | * @param string|null $domain 30 | * @return bool 31 | */ 32 | public function run(?string $domain = null): bool { 33 | $checks = $this->collector->getChecks(); 34 | foreach ($checks as $check) { 35 | if ($domain && $check->domain() !== $domain) { 36 | continue; 37 | } 38 | 39 | $check->check(); 40 | if (!$check->passed()) { 41 | $this->passed = false; 42 | } 43 | 44 | $this->result = $this->result->appendItem($check); 45 | } 46 | 47 | return $this->passed; 48 | } 49 | 50 | /** 51 | * @return \Cake\Collection\CollectionInterface 52 | */ 53 | public function result(): CollectionInterface { 54 | return $this->result->groupBy(function (CheckInterface $result) { 55 | return $result->domain(); 56 | }); 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | public function domains(): array { 63 | return $this->collector->getDomains(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Healthcheck/HealthcheckCollector.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | protected array $checks; 28 | 29 | /** 30 | * @return array, mixed> 31 | */ 32 | public static function defaultChecks(): array { 33 | $checks = static::$defaultChecks; 34 | 35 | $result = []; 36 | foreach ($checks as $check) { 37 | $result[$check] = []; 38 | } 39 | 40 | return $result; 41 | } 42 | 43 | /** 44 | * @param array $checks 45 | */ 46 | public function __construct(array $checks = []) { 47 | if (!$checks) { 48 | $checks = Configure::read('Setup.Healthcheck.checks', static::defaultChecks()); 49 | } 50 | 51 | $this->checks = $this->buildChecks($checks); 52 | } 53 | 54 | /** 55 | * Returns the list of checks to be run. 56 | * 57 | * @return array<\Setup\Healthcheck\Check\CheckInterface> 58 | */ 59 | public function getChecks(): array { 60 | return $this->checks; 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | public function getDomains(): array { 67 | $domains = []; 68 | foreach ($this->checks as $check) { 69 | $domain = $check->domain(); 70 | if (in_array($domain, $domains, true)) { 71 | continue; 72 | } 73 | 74 | $domains[] = $domain; 75 | } 76 | 77 | return $domains; 78 | } 79 | 80 | /** 81 | * @param array $checks 82 | * @return array<\Setup\Healthcheck\Check\CheckInterface> 83 | */ 84 | protected function buildChecks(mixed $checks): array { 85 | $checkInstances = []; 86 | foreach ($checks as $class => $options) { 87 | if ($options === false) { 88 | continue; 89 | } 90 | if (is_object($options)) { 91 | /** @var \Setup\Healthcheck\Check\CheckInterface $options */ 92 | $checkInstances[] = $options; 93 | 94 | continue; 95 | } 96 | if (is_numeric($class) && is_string($options)) { 97 | /** @var class-string<\Setup\Healthcheck\Check\CheckInterface> $options */ 98 | $checkInstances[] = new $options(); 99 | 100 | continue; 101 | } 102 | 103 | /** @var class-string<\Setup\Healthcheck\Check\CheckInterface> $class */ 104 | $checkInstances[] = new $class(...$options); 105 | } 106 | 107 | return $checkInstances; 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/Middleware/MaintenanceMiddleware.php: -------------------------------------------------------------------------------- 1 | View::class, 26 | 'templatePath' => 'Error', 27 | 'statusCode' => 503, 28 | 'templateLayout' => false, 29 | 'templateFileName' => 'maintenance', 30 | 'templateExtension' => '.php', 31 | 'contentType' => 'text/html', 32 | ]; 33 | 34 | /** 35 | * @param array $config 36 | */ 37 | public function __construct(array $config = []) { 38 | $this->setConfig($config); 39 | } 40 | 41 | /** 42 | * @param \Cake\Http\ServerRequest $request 43 | * @param \Psr\Http\Server\RequestHandlerInterface $handler 44 | * 45 | * @return \Psr\Http\Message\ResponseInterface 46 | */ 47 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { 48 | $ip = $request->clientIp(); 49 | $maintenance = new Maintenance(); 50 | 51 | $response = $handler->handle($request); 52 | if (!$maintenance->isMaintenanceMode($ip)) { 53 | return $response; 54 | } 55 | 56 | $response = $this->build($response); 57 | 58 | return $response; 59 | } 60 | 61 | /** 62 | * @param \Psr\Http\Message\ResponseInterface $response 63 | * @return \Psr\Http\Message\ResponseInterface 64 | */ 65 | protected function build(ResponseInterface $response) { 66 | $cakeRequest = ServerRequestFactory::fromGlobals(); 67 | $builder = new ViewBuilder(); 68 | 69 | $templateName = $this->getConfig('templateFileName'); 70 | $templatePath = $this->getConfig('templatePath'); 71 | 72 | $builder->setClassName($this->getConfig('className')) 73 | ->setTemplatePath(Inflector::camelize($templatePath)); 74 | if (!$this->getConfig('templateLayout')) { 75 | $builder->disableAutoLayout(); 76 | } else { 77 | $builder->setLayout($this->getConfig('templateLayout')); 78 | } 79 | 80 | $view = $builder 81 | ->build($cakeRequest) 82 | ->setConfig('_ext', $this->getConfig('templateExtension')); 83 | 84 | $bodyString = $view->render($templateName); 85 | 86 | $response = $response->withHeader('Retry-After', (string)HOUR) 87 | ->withHeader('Content-Type', $this->getConfig('contentType')) 88 | ->withStatus($this->getConfig('statusCode')); 89 | 90 | $body = new CallbackStream(function () use ($bodyString) { 91 | return $bodyString; 92 | }); 93 | 94 | /** @var \Psr\Http\Message\ResponseInterface $maintenanceResponse */ 95 | $maintenanceResponse = $response->withBody($body); 96 | 97 | return $maintenanceResponse; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/Panel/L10nPanel.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | protected array $_defaultConfig = [ 33 | ]; 34 | 35 | /** 36 | * Data collection callback. 37 | * 38 | * @param \Cake\Event\EventInterface $event The shutdown event. 39 | * 40 | * @return void 41 | */ 42 | public function shutdown(EventInterface $event): void { 43 | } 44 | 45 | /** 46 | * Get the data for this panel 47 | * 48 | * @return array 49 | */ 50 | public function data(): array { 51 | $translator = I18n::getTranslator(); 52 | $messages = $translator->getPackage()->getMessages(); 53 | 54 | $data = [ 55 | 'values' => [ 56 | 'datetime' => new DateTime(), 57 | 'date' => new Date(), 58 | 'time' => new Time(), 59 | 'time-noon' => Time::noon(), 60 | 'time-midnight' => Time::midnight(), 61 | ], 62 | 'timezone' => [ 63 | 'default' => Configure::read('App.defaultTimezone'), 64 | 'output' => Configure::read('App.defaultOutputTimezone'), 65 | 'current' => date_default_timezone_get(), 66 | ], 67 | 'currency' => [ 68 | 'default currency' => Number::getDefaultCurrency(), 69 | 'formatted value' => Number::currency('12.34'), 70 | ], 71 | 'messages' => $messages, 72 | ]; 73 | 74 | return $this->_data + $data; 75 | } 76 | 77 | /** 78 | * Get the summary data for a panel. 79 | * 80 | * This data is displayed in the toolbar even when the panel is collapsed. 81 | * 82 | * @return string 83 | */ 84 | public function summary(): string { 85 | return ''; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Queue/Task/HealthcheckTask.php: -------------------------------------------------------------------------------- 1 | $data The array passed to QueuedJobsTable::createJob() 16 | * @param int $jobId The id of the QueuedJob entity 17 | * @return void 18 | */ 19 | public function run(array $data, int $jobId): void { 20 | $healthcheck = new Healthcheck(new HealthcheckCollector()); 21 | 22 | $passed = $healthcheck->run($data['domain'] ?? null); 23 | 24 | $result = $healthcheck->result(); 25 | $message = 'Healthcheck queue run ' . ($passed ? 'OK' : 'FAIL'); 26 | $message .= PHP_EOL . PHP_EOL; 27 | /** 28 | * @var string $domain 29 | * @var \Setup\Healthcheck\Check\CheckInterface[] $checks 30 | */ 31 | foreach ($result as $domain => $checks) { 32 | $message .= '### ' . $domain . PHP_EOL; 33 | foreach ($checks as $check) { 34 | $message .= ' - ' . $check->name() . ': ' . ($check->passed() ? 'OK' : 'FAIL') . PHP_EOL; 35 | if (!$check->passed()) { 36 | if ($check->failureMessage()) { 37 | $message .= ' Error: ' . implode(', ', $check->failureMessage()) . PHP_EOL; 38 | } 39 | if ($check->warningMessage()) { 40 | $message .= ' Warning: ' . implode(', ', $check->warningMessage()) . PHP_EOL; 41 | } 42 | } else { 43 | if ($check->successMessage()) { 44 | $message .= ' Passed: ' . implode(', ', $check->successMessage()) . PHP_EOL; 45 | } 46 | } 47 | 48 | if ($check->infoMessage()) { 49 | $message .= ' Info: ' . implode(', ', $check->infoMessage()) . PHP_EOL; 50 | } 51 | } 52 | } 53 | 54 | Log::write($passed ? 'info' : 'warning', $message); 55 | } 56 | 57 | /** 58 | * @param string|null $data 59 | * @return void 60 | */ 61 | public function add(?string $data): void { 62 | $data = [ 63 | 'domain' => $data, 64 | ]; 65 | $this->QueuedJobs->createJob('Setup.Healthcheck', $data); 66 | $this->io->success('OK, job created, now run the worker'); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/TestSuite/DriverSkipTrait.php: -------------------------------------------------------------------------------- 1 | skipIf(strpos($config['driver'], $type) === false, $message); 20 | } 21 | 22 | /** 23 | * @param string $type 24 | * @param string $message 25 | * @return void 26 | */ 27 | protected function skipIfDriver(string $type, string $message = '') { 28 | $config = ConnectionManager::getConfig('test'); 29 | $this->skipIf(strpos($config['driver'], $type) !== false, $message); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Utility/ClassFinder.php: -------------------------------------------------------------------------------- 1 | |null $plugins 15 | * 16 | * @return array> 17 | */ 18 | public static function get(string $string, ?array $plugins): array { 19 | $appPaths = App::classPath($string); 20 | $result = []; 21 | $classes = static::getClasses($appPaths); 22 | if ($classes) { 23 | $appNamespace = (string)Configure::readOrFail('App.namespace'); 24 | $result[strtoupper($appNamespace)] = $classes; 25 | } 26 | 27 | if ($plugins === null) { 28 | $plugins = Plugin::loaded(); 29 | } 30 | foreach ($plugins as $plugin) { 31 | $pluginPaths = App::classPath($string, $plugin); 32 | $classes = static::getClasses($pluginPaths); 33 | if ($classes) { 34 | $result[$plugin] = $classes; 35 | } 36 | } 37 | 38 | return $result; 39 | } 40 | 41 | /** 42 | * @param array $folders 43 | * 44 | * @return array 45 | */ 46 | public static function getClasses(array $folders): array { 47 | $names = []; 48 | foreach ($folders as $folder) { 49 | $folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true); 50 | 51 | foreach ($folderContent[1] as $file) { 52 | $name = pathinfo($file, PATHINFO_FILENAME); 53 | $names[] = $name; 54 | } 55 | 56 | foreach ($folderContent[0] as $subFolder) { 57 | $folderContent = (new Folder($folder . $subFolder))->read(Folder::SORT_NAME, true); 58 | 59 | foreach ($folderContent[1] as $file) { 60 | $name = pathinfo($file, PATHINFO_FILENAME); 61 | $names[] = $subFolder . '/' . $name; 62 | } 63 | } 64 | } 65 | 66 | return $names; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Utility/Config.php: -------------------------------------------------------------------------------- 1 | $v) { 71 | if (!is_array($v)) { 72 | $result[$k] = ['name' => $k, 'value' => $v, 'children' => []]; 73 | 74 | continue; 75 | } 76 | 77 | $result[$k] = $v; 78 | $result[$k]['name'] = $k; 79 | 80 | if (is_string($k) && !empty($v)) { 81 | $result[$k]['children'] = static::configTree($v); 82 | $result[$k]['value'] = $v; 83 | } else { 84 | $result[$k]['children'] = []; 85 | $result[$k]['value'] = $v; 86 | } 87 | } 88 | 89 | return $result; 90 | } 91 | 92 | /** 93 | * Replaces sensitive strings with dummy text for security reasons. 94 | * 95 | * @param mixed $value 96 | * 97 | * @return mixed 98 | */ 99 | protected static function value($value) { 100 | if (!is_string($value) || $value === '') { 101 | return $value; 102 | } 103 | 104 | if (in_array(strtoupper($value), ['0', '1', 'TRUE', 'FALSE'], true)) { 105 | return $value; 106 | } 107 | 108 | return $value; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/Utility/OrmTypes.php: -------------------------------------------------------------------------------- 1 | $classes) { 36 | $result = []; 37 | foreach ($classes as $class) { 38 | $namespacePrefix = str_replace('/', '\\', static::NAMESPACE); 39 | $fullClass = $namespacePrefix . '\\' . $class; 40 | if (strtoupper($namespace) !== strtoupper(Configure::readOrFail('App.namespace'))) { 41 | $fullClass = str_replace('/', '\\', $namespace) . '\\' . $fullClass; 42 | } 43 | $name = substr($class, 0, -strlen(static::SUFFIX)); 44 | 45 | if ($exclude && in_array($fullClass, $exclude, true)) { 46 | continue; 47 | } 48 | 49 | $result[$name] = $fullClass; 50 | } 51 | 52 | $allClasses[$namespace] = $result; 53 | if (!$allClasses[$namespace]) { 54 | unset($allClasses[$namespace]); 55 | } 56 | } 57 | 58 | return $allClasses; 59 | } 60 | 61 | /** 62 | * @return array> 63 | */ 64 | public static function getMap(): array { 65 | /** @var array $map */ 66 | $map = TypeFactory::getMap(); 67 | 68 | $result = []; 69 | foreach ($map as $type => $class) { 70 | $name = static::name($class); 71 | 72 | $result[$type] = [ 73 | 'name' => $name, 74 | 'class' => $class, 75 | ]; 76 | } 77 | 78 | return $result; 79 | } 80 | 81 | /** 82 | * @param string $class 83 | * 84 | * @return string 85 | */ 86 | protected static function name(string $class): string { 87 | $namespace = str_replace('/', '\\', static::NAMESPACE); 88 | preg_match('#^(.+)\\\\' . preg_quote($namespace) . '\\\\(.+)' . preg_quote(static::SUFFIX) . '#', $class, $matches); 89 | if (!$matches || empty($matches[1]) || empty($matches[2])) { 90 | throw new RuntimeException('Invalid type class: ' . $class); 91 | } 92 | 93 | if ($matches[1] === 'Cake' || $matches[1] === Configure::readOrFail('App.namespace')) { 94 | return $matches[2]; 95 | } 96 | 97 | return $matches[1] . '.' . $matches[2]; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/Utility/Setup.php: -------------------------------------------------------------------------------- 1 | = 0 && $mask <= 32; 30 | } 31 | if (in_array($type, ['both', 'ipv6', true]) && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { 32 | return is_numeric($mask) && $mask >= 0 && $mask <= 128; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /templates/Admin/Backend/cache.php: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 |

Cache Config

16 | 17 |
18 |
    19 | $config) { ?> 20 | 24 |
  • 25 |

    []

    26 | 27 |
    28 | Data: 29 | 30 | (Time->timeAgoInWords(new DateTime($data[$key])); ?>) 31 | 32 |
    33 | Form->postLink('Store current time for testing', ['?' => ['key' => $key]], ['class' => 'button primary btn btn-primary']); ?> 34 |
    35 |
    36 | 37 |
    38 | Details 39 |
    40 |
    41 |
  • 42 | 43 |
44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /templates/Admin/Backend/cookies.php: -------------------------------------------------------------------------------- 1 | 10 |
11 | 12 |

Cookies

13 | 14 | getIterator() as $cookie) { 17 | $expireDateTime = $cookie->getExpiry(); 18 | 19 | echo '

' . $cookie->getName() . '

'; 20 | 21 | echo '
';
22 | 	echo json_encode($cookie->toArray(), JSON_PRETTY_PRINT);
23 | 	echo '
'; 24 | 25 | echo '

'; 26 | echo 'Expires: ' . ($expireDateTime ? $this->Time->nice($expireDateTime) : 'n/a'); 27 | 28 | echo ' ' . $this->Form->postLink('Delete', ['?' => ['cookie' => $cookie->getName()]], ['class' => 'btn btn-danger', 'confirm' => 'Sure?', 'block' => true]); 29 | echo '

'; 30 | } 31 | 32 | ?> 33 | 34 |
35 | -------------------------------------------------------------------------------- /templates/Admin/Backend/database.php: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 |

12 |

Excluding phinxlog migration tables

13 |

Database size: Number->toReadableSize($dbSize); ?>

14 | 15 | 16 | 17 | '; 26 | echo ''; 27 | echo ''; 28 | echo ''; 29 | echo ''; 30 | echo ''; 31 | echo ''; 32 | echo ''; 33 | } 34 | ?> 35 |
NameRowsSizeCollationUpdatedComment
' . h($table['Name']) . '' . $table['Rows'] . '' . $this->Number->toReadableSize($length) . $this->Progress->htmlProgressBar($size) . '' . $table['Engine'] . ' ' . $table['Collation'] . '' . $updated . '' . h($table['Comment']) . '
36 | 37 |
38 | -------------------------------------------------------------------------------- /templates/Admin/Backend/disk_space.php: -------------------------------------------------------------------------------- 1 | 8 |
9 | 10 |

11 | 12 | 22 | 23 |

Free Space

24 | Total Space: Number->toReadableSize($freeSpace['total']);?>

25 | 26 | Number->toReadableSize($freeSpace['available']);?> frei (%), 27 |
28 | Number->toReadableSize($freeSpace['used']);?> belegt (%) 29 | 30 | 31 |

Currently used space of project:

32 | Number->toReadableSize($appSize);?> 33 | 34 |
35 | -------------------------------------------------------------------------------- /templates/Admin/Backend/env.php: -------------------------------------------------------------------------------- 1 | 8 |
9 | 10 |

ENV config

11 | 12 | 13 | 14 | 15 | 16 | $value) { ?> 17 | 18 | 21 | 24 | 25 | 26 |
ENVValue defined
19 | 20 | 22 | element('Setup.ok', ['value' => $this->element('Setup.yes_no', ['value' => $value !== false]), 'ok' => $value !== false, 'escape' => false]) ?> 23 |
27 | 28 | 29 |
30 |

Dynamic Configs

31 | 32 | app_local.php: element('Setup.yes_no', ['value' => $localConfig !== null]) ?> 33 | 34 |

Defined config keys

35 | loadHelper('Tools.Tree'); 38 | 39 | $callback = function ($node) { 40 | if (!$node['hasChildren'] && empty($node['children']) && $node['data']['value'] === []) { 41 | return ''; 42 | } 43 | 44 | $name = h($node['data']['name']); 45 | if ($node['hasChildren'] || !empty($node['children'])) { 46 | return $name; 47 | } 48 | 49 | $hasValue = $node['data']['value'] !== null; 50 | $value = ''; 51 | if ($hasValue) { 52 | if (!is_string($node['data']['value']) || $node['data']['value'] === '') { 53 | $value = \Cake\Error\Debugger::exportVar($node['data']['value'], 1); 54 | } else { 55 | $value = '(string)...'; 56 | } 57 | } 58 | 59 | return $name . ' ' . ($hasValue ? $this->element('Setup.yes_no', ['value' => $hasValue]) . ' ' . $value .'' : 'null'); 60 | }; 61 | 62 | echo $this->Tree->generate($localConfig, ['callback' => $callback]); 63 | } else { 64 | echo 'n/a'; 65 | } 66 | ?> 67 |
68 | -------------------------------------------------------------------------------- /templates/Admin/Backend/ip.php: -------------------------------------------------------------------------------- 1 | 2 | $proxyHeaders 8 | */ 9 | ?> 10 |
11 | 12 |

IP Address

13 | 14 |

Your Address

15 | 16 | 17 | 20 | 23 | 24 | 25 | 28 | 31 | 32 |
18 | IP 19 | 21 | 22 |
26 | Host 27 | 29 | 30 |
33 | 34 | 35 |

Proxy Headers found

36 | 37 | 38 | 39 | 40 | $value) { ?> 41 | 42 | 45 | 48 | 49 | 50 |
ENVValue defined
43 | 44 | 46 | 47 |
51 | 52 | 53 |
54 | -------------------------------------------------------------------------------- /templates/Admin/Backend/locales.php: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 |

12 | 13 |

Test Locale

14 | Form->create();?> 15 |
16 | 17 | Form->control('Form.format'); 19 | 20 | echo $this->Form->control('Form.locale', ['placeholder' => __('e.g. de_DE.utf8')]); 21 | ?> 22 |
23 | 24 |
25 | 26 | 29 |
30 | 31 | 32 | Form->submit(__('Submit')); echo $this->Form->end();?> 33 | 34 |

System Locales

35 | 36 |
37 | locales 38 | 39 | 40 |
41 |
42 | 43 |

Tryouts

44 |
    45 | $settings) { 47 | echo '
  • '; 48 | echo '' . $key . '' . '
    ' . $settings['res']; 49 | echo pre($settings); 50 | echo '

  • '; 51 | } 52 | ?> 53 |
54 | 55 |
56 | -------------------------------------------------------------------------------- /templates/Admin/Backend/phpinfo.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /templates/Admin/Backend/session.php: -------------------------------------------------------------------------------- 1 | 11 |
12 | 13 |

Session

14 | 15 | Time: Time->niceDate($time); ?> 16 | 17 | 18 |
19 |

Session Config

20 |
23 | 24 |

Cookie Params

25 |
28 | 29 |

Own Session Value

30 | 31 |

ID:

32 | 33 |

Expires: Time->niceDate($sessionData['expires']); ?>

34 | 35 | 36 |

Data:

37 | 38 | 39 | 40 |

Server Timeout

41 | Time->timeAgoInWords((new DateTime())->addSeconds($currentTimeoutInSecs), []); 45 | 46 | ?> 47 | 48 |
49 |

Garbage Collector Settings

50 | 57 | 58 |
59 | -------------------------------------------------------------------------------- /templates/Admin/Backend/system.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 |

Configuration

13 | 14 |

System info

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Upload-LimitNumber->toReadableSize($uploadLimit); ?>
Post-LimitNumber->toReadableSize($postLimit); ?>
Memory LimitNumber->toReadableSize($memoryLimit); ?>
29 | 30 |

31 | 32 |
33 | -------------------------------------------------------------------------------- /templates/Admin/Backend/timezones.php: -------------------------------------------------------------------------------- 1 | 2 | |null $tokenRaw 9 | * @var string|null $dateTimeString 10 | */ 11 | 12 | ?> 13 |
14 | 15 |

Timezones

16 | 17 |

Current

18 |
    19 |
  • 20 | Time: Time->nice($time); ?> () 21 |
  • 22 |
  • 23 | Default string cast: 24 |
  • 25 |
26 | 27 | 28 |

Database

29 | 30 |

String in DB:

31 | 32 |
    33 |
  • 34 | nice(): Time->nice($token->created); ?> (created->timezone->getName()); ?>) 35 |
  • 36 |
  • 37 | format(): Time->format($token->created); ?> (created->timezone->getName()); ?>) 38 |
  • 39 |
  • 40 | Tools.niceDate(): Time->niceDate($token->created); ?> (created->timezone->getName()); ?>) 41 |
  • 42 |
43 | 44 | 45 | Form->create($token);?> 46 |
47 | 48 | Form->control('created', ['label' => 'DateTime']); 50 | ?> 51 |
52 | 53 |
54 | 55 | 58 |
59 | 60 | 61 | Form->submit(__('Submit')); echo $this->Form->end();?> 62 | 63 | Add Tools plugin to see how forms interact with timezone config.'; 65 | } ?> 66 | 67 |
68 | -------------------------------------------------------------------------------- /templates/Admin/Backend/type_map.php: -------------------------------------------------------------------------------- 1 | $plugins 5 | * @var array $classes 6 | * @var array> $map 7 | */ 8 | ?> 9 | 10 |

TypeMap overview

11 | 12 |
13 |
14 |

Mapped types

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | $info) { 25 | ?> 26 | 27 | 30 | 33 | 36 | 37 | 40 |
TypeNameClass
28 | 29 | 31 | 32 | 34 | 35 |
41 |
42 |
43 |

Available classes

44 |

The following request->getQuery('all') ? '' : 'loaded'); ?> plugins have been searched: 45 |
46 | 47 |

48 | 49 |

The following not (yet) used type classes have been found:

50 |
    51 | $classNames) { ?> 52 |
  • 53 | 54 |
      55 | $className) { ?> 56 |
    • 57 | 58 |
      59 | 60 |
      61 |
    • 62 | 63 |
    64 |
  • 65 | 66 |
67 | 68 |

request->getQuery('all') ? $this->Html->link('Check loaded plugins only', ['?' => []]) : $this->Html->link('Check all available plugins', ['?' => ['all' => true]])); ?>

69 |
70 |
71 | -------------------------------------------------------------------------------- /templates/Admin/Setup/index.php: -------------------------------------------------------------------------------- 1 | 6 |
7 | 8 |

Setup Backend Tools

9 | 10 |

Information

11 |
    12 |
  • Html->link('Configuration', ['controller' => 'Configuration', 'action' => 'index']);?>
  • 13 |
  • Html->link('PHP Info (Full)', ['controller' => 'Backend', 'action' => 'phpinfo']);?>
  • 14 |
  • Html->link('Session Info', ['controller' => 'Backend', 'action' => 'session']);?>
  • 15 |
  • Html->link('Cookie Info', ['controller' => 'Backend', 'action' => 'cookies']);?>
  • 16 |
  • Html->link('Cache Info and Testing', ['controller' => 'Backend', 'action' => 'cache']);?>
  • 17 |
  • Html->link('ORM Type Map', ['controller' => 'Backend', 'action' => 'typeMap']); ?>
  • 18 | 19 |
  • Html->link(__('System'), ['controller' => 'Backend', 'action' => 'system']); ?>
  • 20 |
  • Html->link(__('Timezones'), ['controller' => 'Backend', 'action' => 'timezones']);?>
  • 21 |
  • Html->link(__('Locales'), ['controller' => 'Backend', 'action' => 'locales']);?>
  • 22 |
  • Html->link('Database Info', ['controller' => 'Backend', 'action' => 'database']);?>
  • 23 |
  • Html->link(__('Disk Space'), ['controller' => 'Backend', 'action' => 'diskSpace']);?>
  • 24 |
  • Html->link(__('ENV Config'), ['controller' => 'Backend', 'action' => 'env']);?>
  • 25 |
26 | 27 | 28 |

Maintenance

29 |

30 | Html->link('Maintenance Mode', ['action' => 'maintenance']); 32 | ?> 33 |

34 | 35 |

Healthcheck

36 | 37 |

38 | Html->link('Healthcheck', ['controller' => 'Uptime', 'action' => 'index']); 40 | ?> 41 |

42 |

You can customize this route on project level and add this to your healthcheck (ping) services.

43 | 44 |
45 | -------------------------------------------------------------------------------- /templates/Admin/Setup/maintenance.php: -------------------------------------------------------------------------------- 1 | $whitelist 7 | * @var string $ip 8 | */ 9 | ?> 10 |
11 | 12 |

Setup Backend Tools

13 | 14 |

Maintenance Mode

15 |

From here you can put your application into maintenance mode if needed.

16 | 17 |

Current Status

18 |
    19 |
  • 20 |
  • Your IP:
  • 21 |
  • You are currently
  • 22 |
23 | 24 |

25 | Form->postLink('Go to Maintenance mode', ['action' => 'maintenance', '?' => ['maintenance' => 1]], ['class' => 'btn btn-danger']); 28 | } else { 29 | echo $this->Form->postLink('Leave Maintenance mode', ['action' => 'maintenance', '?' => ['maintenance' => 0]], ['class' => 'btn btn-warning']); 30 | } 31 | ?> 32 | 33 |

34 |

Your IP will automatically be whitelisted. So you can still browse.

35 | 36 | 37 |

Whitelist

38 |
    39 | 40 |
  • 41 | 42 |
43 | 44 | 45 |
46 | -------------------------------------------------------------------------------- /templates/Healthcheck/index.php: -------------------------------------------------------------------------------- 1 | $domains 7 | */ 8 | 9 | // Make sure to noindex,nofollow this page 10 | 11 | use Cake\Utility\Inflector; 12 | ?> 13 | 14 |

15 | element('Setup.yes_no', ['value' => $passed])?> 16 | 17 |

18 | 19 |
20 |
    21 |
  • Filter:
  • 22 | 23 | request->getQuery('domain')) { ?> 24 |
  • Html->link('ALL', ['?' => ['domain' => null]]); ?>
  • 25 | 26 | 27 | 28 |
  • Html->link($domain, ['?' => ['domain' => $domain]]); ?>
  • 29 | 30 |
31 |
32 | 33 |

Result

34 | $checks 38 | */ 39 | foreach ($result as $domain => $checks) { 40 | ?> 41 |
42 |

43 |
    44 | 45 |
  • 46 | name()); ?> 47 | passed()) { ?> 48 | failureMessage()) { ?> 49 |
    failureMessage())); ?>
    50 | 51 | warningMessage()) { ?> 52 |
    warningMessage())); ?>
    53 | 54 | 55 | successMessage()) { ?> 56 |
    successMessage())); ?>
    57 | 58 | 59 | infoMessage()) { ?> 60 |
    61 | Info 62 |
      63 | infoMessage() as $key => $value) { ?> 64 |
    • 65 | 66 |
    67 |
    68 | 69 |
  • 70 | 71 |
72 |
73 | 74 | 75 | 76 | 77 |

No checks found.

78 | 79 | 80 | 88 | -------------------------------------------------------------------------------- /templates/bake/Command/command.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 1.7.4 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | _io`. 27 | * 28 | * @param array $args The arguments for the helper. 29 | * @return void 30 | */ 31 | public function output(array $args): void 32 | { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /templates/bake/Controller/component.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | 30 | */ 31 | protected array $_defaultConfig = []; 32 | } 33 | -------------------------------------------------------------------------------- /templates/bake/Controller/controller.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * Controller bake template file 4 | * 5 | * Allows templating of Controllers generated from bake. 6 | * 7 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 8 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 9 | * 10 | * Licensed under The MIT License 11 | * For full copyright and license information, please see the LICENSE.txt 12 | * Redistributions of files must retain the above copyright notice. 13 | * 14 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 15 | * @link https://cakephp.org CakePHP(tm) Project 16 | * @since 2.0.0 17 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 18 | */ 19 | #} 20 | {{ element('Bake.file_header', { 21 | namespace: "#{namespace}\\Controller#{prefix}", 22 | classImports: (plugin or prefix) ? ["#{baseNamespace}\\Controller\\AppController"] : [], 23 | }) }} 24 | 25 | /** 26 | {% if defaultModel %} 27 | * @property \{{ defaultModel }} ${{ name }} 28 | {% endif %} 29 | 30 | {%- for component in components %} 31 | {% set classInfo = Bake.classInfo(component, 'Controller/Component', 'Component') %} 32 | * @property {{ classInfo.fqn }} ${{ classInfo.name }} 33 | {% endfor %} 34 | 35 | {%- if 'index' in actions %} 36 | * @method \{{ namespace }}\Model\Entity\{{ entityClassName }}[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = []) 37 | {% endif %} 38 | */ 39 | class {{ name }}Controller extends AppController 40 | { 41 | {% set pagination = SetupBake.pagination(currentModelName) %} 42 | {% if pagination %} 43 | /** 44 | * @var array 45 | */ 46 | protected array $paginate = {{ Bake.exportArray(pagination, 1)|raw }}; 47 | 48 | {% endif %} 49 | {% if components or helpers %} 50 | /** 51 | * Initialize controller 52 | * 53 | * @return void 54 | */ 55 | public function initialize(): void 56 | { 57 | parent::initialize(); 58 | 59 | {% for component in components %} 60 | $this->loadComponent('{{ component }}'); 61 | {% endfor %} 62 | {% if helpers %} 63 | $this->viewBuilder()->setHelpers({{ Bake.exportArray(helpers)|raw }}); 64 | {% endif %} 65 | } 66 | {% if actions|length %}{{ "\n" }}{% endif %} 67 | {% endif %} 68 | {%- for action in actions %} 69 | {% if loop.index > 1 %}{{ "\n" }}{% endif %} 70 | {{- element('Bake.Controller/' ~ action) -}} 71 | {% endfor %} 72 | } 73 | -------------------------------------------------------------------------------- /templates/bake/Form/form.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | handle($request); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/bake/Model/behavior.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | 30 | */ 31 | protected array $_defaultConfig = []; 32 | } 33 | -------------------------------------------------------------------------------- /templates/bake/Model/entity.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | {% set propertyHintMap = DocBlock.buildEntityPropertyHintTypeMap(propertySchema ?: []) %} 17 | {% set associationHintMap = DocBlock.buildEntityAssociationHintTypeMap(propertySchema ?: []) %} 18 | {% set annotations = DocBlock.propertyHints(propertyHintMap) %} 19 | 20 | {%- if associationHintMap %} 21 | {%- set annotations = annotations|merge(['']) %} 22 | {%- set annotations = annotations|merge(DocBlock.propertyHints(associationHintMap)) %} 23 | {% endif %} 24 | 25 | {%- set accessible = SetupBake.getFieldAccessibility(fields, primaryKey) %} 26 | 27 | {%- set generatedProperties = [] %} 28 | {{ element('Bake.file_header', { 29 | namespace: fileBuilder.namespace, 30 | classImports: fileBuilder.classImports(['Cake\\ORM\\Entity']), 31 | }) }} 32 | 33 | {{ DocBlock.classDescription(name, 'Entity', annotations)|raw }} 34 | class {{ name }} extends Entity{{ fileBuilder.classBuilder.implements ? ' implements ' ~ fileBuilder.classBuilder.implements|join(', ') : '' }} 35 | { 36 | {% set userConstants = fileBuilder.classBuilder.userConstants([]) %} 37 | {% if userConstants %} 38 | {{~ Bake.concat('\n\n', userConstants) }} 39 | 40 | {% endif %} 41 | {% if accessible %} 42 | {%- set generatedProperties = generatedProperties|merge(['_accessible']) %} 43 | /** 44 | * Fields that can be mass assigned using newEntity() or patchEntity(). 45 | * 46 | * Note that when '*' is set to true, this allows all unspecified fields to 47 | * be mass assigned. For security purposes, it is advised to set '*' to false 48 | * (or remove it), and explicitly make individual fields accessible as needed. 49 | * 50 | * @var array 51 | */ 52 | protected array $_accessible = {{ Bake.exportVar(accessible, 1)|raw }}; 53 | {% endif %} 54 | {% if accessible and hidden %} 55 | 56 | {% endif %} 57 | {%- if hidden %} 58 | {%- set generatedProperties = generatedProperties|merge(['_hidden']) %} 59 | /** 60 | * Fields that are excluded from JSON versions of the entity. 61 | * 62 | * @var list 63 | */ 64 | protected array $_hidden = {{ Bake.exportVar(hidden, 1)|raw }}; 65 | {% endif %} 66 | {% set userProperties = fileBuilder.classBuilder.userProperties(generatedProperties) %} 67 | {% if userProperties %} 68 | 69 | {{~ Bake.concat('\n\n', userProperties) }} 70 | {% endif %} 71 | {% set userFunctions = fileBuilder.classBuilder.userFunctions([]) %} 72 | {% if userFunctions %} 73 | 74 | {{~ Bake.concat('\n\n', userFunctions) }} 75 | {% endif %} 76 | } 77 | -------------------------------------------------------------------------------- /templates/bake/Model/enum.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 3.1.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | {{ element('Bake.file_header', { 17 | namespace: "#{namespace}\\Model\\Enum", 18 | classImports: [ 19 | 'Cake\\Database\\Type\\EnumLabelInterface', 20 | 'Cake\\Utility\\Inflector', 21 | ], 22 | }) }} 23 | 24 | {{ DocBlock.classDescription(name, 'Enum', [])|raw }} 25 | enum {{ name }}: {{ backingType }} implements EnumLabelInterface 26 | { 27 | {% if cases %} 28 | {{ Bake.concat('\n ', cases) }} 29 | 30 | {% endif %} 31 | /** 32 | * @return string 33 | */ 34 | public function label(): string 35 | { 36 | return Inflector::humanize(Inflector::underscore($this->name)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /templates/bake/Plugin/.editorconfig.twig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at https://editorconfig.org 3 | root = true 4 | 5 | [*] 6 | indent_style = tab 7 | indent_size = 4 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.bat] 13 | end_of_line = crlf 14 | 15 | [*.yml] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /templates/bake/Plugin/.gitattributes.twig: -------------------------------------------------------------------------------- 1 | # Define the line ending behavior of the different file extensions 2 | # Set default behaviour, in case users don't have core.autocrlf set. 3 | * text ext=auto eol=lf 4 | 5 | *.png binary 6 | *.jpg binary 7 | 8 | # Exclude files for archives generated using `git archive` so that they are not 9 | # included in dist installs through composer. 10 | .editorconfig export-ignore 11 | .gitattributes export-ignore 12 | .gitignore export-ignore 13 | phpunit.xml.dist export-ignore 14 | tests/test_app export-ignore 15 | -------------------------------------------------------------------------------- /templates/bake/Plugin/.gitignore.twig: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /composer.phar 3 | /phpunit.xml 4 | /.phpunit.result.cache 5 | /.phpunit.cache/ 6 | /phpunit.phar 7 | /config/Migrations/schema-dump-default.lock 8 | /vendor/ 9 | /.idea/ 10 | -------------------------------------------------------------------------------- /templates/bake/Plugin/README.md.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | # {{ plugin }} plugin for CakePHP 17 | 18 | ## Installation 19 | 20 | You can install this plugin into your CakePHP application using [composer](https://getcomposer.org). 21 | 22 | The recommended way to install composer packages is: 23 | 24 | ``` 25 | composer require {{ package }} 26 | ``` 27 | -------------------------------------------------------------------------------- /templates/bake/Plugin/composer.json.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | {% set namespace = namespace|replace({'\\': '\\\\'}) %} 17 | { 18 | "name": "{{ package }}", 19 | "description": "{{ plugin }} plugin for CakePHP", 20 | "type": "cakephp-plugin", 21 | "license": "MIT", 22 | "require": { 23 | "php": ">=8.1", 24 | "cakephp/cakephp": "{{ cakeVersion|raw }}" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^10.1" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "{{ namespace }}\\": "src/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "{{ namespace }}\\Test\\": "tests/", 37 | "Cake\\Test\\": "vendor/cakephp/cakephp/tests/" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /templates/bake/Plugin/phpunit.xml.dist.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | tests/TestCase/ 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | src/ 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /templates/bake/Plugin/src/Controller/AppController.php.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | 35 | {{ element('Bake.form') }} -------------------------------------------------------------------------------- /templates/bake/Template/edit.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | 35 | {{ element('Bake.form') }} -------------------------------------------------------------------------------- /templates/bake/Template/login.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | 21 |
22 | Form->create() ?> 23 |
24 | 25 | Form->control('username') ?> 26 | Form->control('password') ?> 27 |
28 | Form->button(__('Login')); ?> 29 | Form->end() ?> 30 |
31 | -------------------------------------------------------------------------------- /templates/bake/View/cell.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | {{ element('Bake.file_header', { 17 | namespace: "#{namespace}\\View\\Cell#{prefix}", 18 | classImports: [ 19 | 'Cake\\View\\Cell', 20 | ], 21 | }) }} 22 | 23 | class {{ name }}Cell extends Cell 24 | { 25 | /** 26 | * List of valid options that can be passed into this 27 | * cell's constructor. 28 | * 29 | * @var array 30 | */ 31 | protected array $_validCellOptions = []; 32 | 33 | /** 34 | * Initialization logic run at the end of object construction. 35 | * 36 | * @return void 37 | */ 38 | public function initialize(): void 39 | { 40 | } 41 | 42 | /** 43 | * Default display method. 44 | * 45 | * @return void 46 | */ 47 | public function display() 48 | { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /templates/bake/View/helper.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | 30 | */ 31 | protected array $_defaultConfig = []; 32 | } 33 | -------------------------------------------------------------------------------- /templates/bake/element/Controller/add.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | {% set compact = ["'#{singularName}'"] %} 17 | /** 18 | * @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise. 19 | */ 20 | public function add() 21 | { 22 | ${{ singularName }} = $this->{{ currentModelName }}->newEmptyEntity(); 23 | if ($this->request->is('post')) { 24 | ${{ singularName }} = $this->{{ currentModelName }}->patchEntity(${{ singularName }}, $this->request->getData()); 25 | if ($this->{{ currentModelName }}->save(${{ singularName }})) { 26 | $this->Flash->success(__('The {{ singularHumanName|lower }} has been saved.')); 27 | 28 | return $this->redirect(['action' => 'view', ${{ singularName }}->id]); 29 | } 30 | $this->Flash->error(__('The {{ singularHumanName|lower }} could not be saved. Please, try again.')); 31 | } 32 | {% set associations = Bake.aliasExtractor(modelObj, 'BelongsTo') %} 33 | {% set associations = associations|merge(Bake.aliasExtractor(modelObj, 'BelongsToMany')) %} 34 | 35 | {%- for assoc in associations %} 36 | {%- set otherName = Bake.getAssociatedTableAlias(modelObj, assoc) %} 37 | {%- set otherPlural = otherName|variable %} 38 | ${{ otherPlural }} = $this->{{ currentModelName }}->{{ otherName }}->find('list', limit: 1000)->all(); 39 | {{- "\n" }} 40 | {%- set compact = compact|merge(["'#{otherPlural}'"]) %} 41 | {% endfor %} 42 | $this->set(compact({{ compact|join(', ')|raw }})); 43 | } 44 | -------------------------------------------------------------------------------- /templates/bake/element/Controller/delete.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | /** 17 | * @param string|null $id {{ singularHumanName }} id. 18 | * @return \Cake\Http\Response|null Redirects to index. 19 | */ 20 | public function delete($id = null) 21 | { 22 | $this->request->allowMethod(['post', 'delete']); 23 | ${{ singularName }} = $this->{{ currentModelName }}->get($id); 24 | if ($this->{{ currentModelName }}->delete(${{ singularName }})) { 25 | $this->Flash->success(__('The {{ singularHumanName|lower }} has been deleted.')); 26 | } else { 27 | $this->Flash->error(__('The {{ singularHumanName|lower }} could not be deleted. Please, try again.')); 28 | } 29 | 30 | return $this->redirect(['action' => 'index']); 31 | } 32 | -------------------------------------------------------------------------------- /templates/bake/element/Controller/edit.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | {% set belongsTo = Bake.aliasExtractor(modelObj, 'BelongsTo') %} 17 | {% set belongsToMany = Bake.aliasExtractor(modelObj, 'belongsToMany') %} 18 | {% set compact = ["'#{singularName}'"] %} 19 | /** 20 | * @param string|null $id {{ singularHumanName }} id. 21 | * @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise. 22 | */ 23 | public function edit($id = null) 24 | { 25 | ${{ singularName }} = $this->{{ currentModelName }}->get($id, contain: {{ Bake.exportArray(belongsToMany)|raw }}); 26 | if ($this->request->is(['patch', 'post', 'put'])) { 27 | ${{ singularName }} = $this->{{ currentModelName }}->patchEntity(${{ singularName }}, $this->request->getData()); 28 | if ($this->{{ currentModelName }}->save(${{ singularName }})) { 29 | $this->Flash->success(__('The {{ singularHumanName|lower }} has been saved.')); 30 | 31 | return $this->redirect(['action' => 'view', ${{ singularName }}->id]); 32 | } 33 | $this->Flash->error(__('The {{ singularHumanName|lower }} could not be saved. Please, try again.')); 34 | } 35 | {% for assoc in belongsTo|merge(belongsToMany) %} 36 | {%- set otherName = Bake.getAssociatedTableAlias(modelObj, assoc) %} 37 | {%- set otherPlural = otherName|variable %} 38 | ${{ otherPlural }} = $this->{{ currentModelName }}->{{ otherName }}->find('list', limit: 1000)->all(); 39 | {{- "\n" }} 40 | {%- set compact = compact|merge(["'#{otherPlural}'"]) %} 41 | {% endfor %} 42 | $this->set(compact({{ compact|join(', ')|raw }})); 43 | } 44 | -------------------------------------------------------------------------------- /templates/bake/element/Controller/index.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | /** 17 | * @return \Cake\Http\Response|null|void Renders view 18 | */ 19 | public function index() 20 | { 21 | {% set belongsTo = Bake.aliasExtractor(modelObj, 'BelongsTo') %} 22 | {% if belongsTo %} 23 | $query = $this->{{ currentModelName }}->find() 24 | ->contain({{ Bake.exportArray(belongsTo)|raw }}); 25 | {% else %} 26 | $query = $this->{{ currentModelName }}->find(); 27 | {% endif %} 28 | ${{ pluralName }} = $this->paginate($query); 29 | 30 | $this->set(compact('{{ pluralName }}')); 31 | } 32 | -------------------------------------------------------------------------------- /templates/bake/element/Controller/login.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | /** 17 | * @return \Cake\Http\Response|null|void Renders view 18 | */ 19 | public function login() 20 | { 21 | {% if Bake.hasPlugin('Authorization') %} 22 | $this->Authorization->skipAuthorization(); 23 | 24 | {% endif %} 25 | $result = $this->Authentication->getResult(); 26 | if ($result->isValid()) { 27 | $this->Flash->success(__('Login successful')); 28 | $redirect = $this->Authentication->getLoginRedirect(); 29 | if ($redirect) { 30 | return $this->redirect($redirect); 31 | } 32 | } 33 | 34 | if ($this->request->is('post')) { 35 | $this->Flash->error(__('Invalid credentials')); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/bake/element/Controller/logout.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | /** 17 | * @return \Cake\Http\Response|null 18 | */ 19 | public function logout() 20 | { 21 | $whereTo = $this->Authentication->logout() ?: ['action' => 'login']; 22 | 23 | return $this->redirect($whereTo); 24 | } 25 | -------------------------------------------------------------------------------- /templates/bake/element/Controller/view.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | * 6 | * Licensed under The MIT License 7 | * For full copyright and license information, please see the LICENSE.txt 8 | * Redistributions of files must retain the above copyright notice. 9 | * 10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 | * @link https://cakephp.org CakePHP(tm) Project 12 | * @since 2.0.0 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | #} 16 | {% set allAssociations = Bake.aliasExtractor(modelObj, 'BelongsTo') %} 17 | {% set allAssociations = allAssociations|merge(Bake.aliasExtractor(modelObj, 'BelongsToMany')) %} 18 | {% set allAssociations = allAssociations|merge(Bake.aliasExtractor(modelObj, 'HasOne')) %} 19 | {% set allAssociations = allAssociations|merge(Bake.aliasExtractor(modelObj, 'HasMany')) %} 20 | /** 21 | * @param string|null $id {{ singularHumanName }} id. 22 | * @return \Cake\Http\Response|null|void Renders view 23 | */ 24 | public function view($id = null) 25 | { 26 | ${{ singularName }} = $this->{{ currentModelName }}->get($id, contain: {{ Bake.exportArray(allAssociations)|raw }}); 27 | $this->set(compact('{{ singularName }}')); 28 | } 29 | -------------------------------------------------------------------------------- /templates/bake/element/array_property.twig: -------------------------------------------------------------------------------- 1 | /** 2 | * {{ name|humanize }} 3 | * 4 | * @var array 5 | */ 6 | public array ${{ name }} = {{ Bake.exportArray(value)|raw }}; -------------------------------------------------------------------------------- /templates/bake/element/file_header.twig: -------------------------------------------------------------------------------- 1 | 46 | */ 47 | public array $import = {{ import|raw }}; 48 | 49 | {% endif %} 50 | 51 | {%- if schema %} 52 | /** 53 | * Fields 54 | * 55 | * @var array 56 | */ 57 | // phpcs:disable 58 | public array $fields = {{ schema|raw }}; 59 | // phpcs:enable 60 | {% endif %} 61 | 62 | {%- if records %} 63 | /** 64 | * Init method 65 | * 66 | * @return void 67 | */ 68 | public function init(): void 69 | { 70 | $this->records = {{ SetupBake.fixtureRecords(records)|raw }}; 71 | parent::init(); 72 | } 73 | {% endif %} 74 | } 75 | -------------------------------------------------------------------------------- /templates/element/l10n_panel.php: -------------------------------------------------------------------------------- 1 | $values 5 | * @var array $timezone 6 | * @var array $currency 7 | * @var array $messages 8 | */ 9 | ?> 10 | 11 |
12 |

Localization

13 | 14 |

Timezone

15 |
    16 |
  • Default:
  • 17 | 18 |
  • Output:
  • 19 | 20 |
  • Current:
  • 21 |
22 | 23 |

Datetime/Date/Time

24 | 25 |
    26 | $value) { ?> 27 |
  • 28 | : () 29 |
  • 30 | 31 |
32 | 33 |

Languages/Locales

34 |
    35 |
  • 36 | App.defaultLocale: 37 |
  • 38 |
  • 39 | ini_get('intl.default_locale'): 40 |
  • 41 |
  • 42 | 43 |
  • 44 |
45 | 46 |

Currency

47 |
    48 | $value) { ?> 49 |
  • 50 | : 51 |
  • 52 | 53 |
54 | 55 |

Translations

56 |

translations

57 |
58 | Details 59 | 60 | $details) { ?> 61 | 62 | 63 | 69 | 70 | 71 |
    $translation) { 66 | echo '
  • ' . ($key ? h($key). ': ' : '') . h($translation). '
  • '; 67 | } 68 | ?>
72 |
73 | 74 |
75 | -------------------------------------------------------------------------------- /templates/element/ok.php: -------------------------------------------------------------------------------- 1 | 14 | helpers()->has('Templating')) { 16 | echo $this->Templating->ok($value, $ok, ['escape' => $escape]); 17 | } elseif ($this->helpers()->has('Format')) { 18 | echo $this->Format->ok($value, $ok); 19 | } else { 20 | echo $ok ? '' . ($escape ? h($value) : $value) . '' : '' . ($escape ? h($value) : $value) . ''; 21 | } 22 | ?> 23 | -------------------------------------------------------------------------------- /templates/element/yes_no.php: -------------------------------------------------------------------------------- 1 | 9 | helpers()->has('IconSnippet')) { 11 | echo $this->IconSnippet->yesNo($value); 12 | } elseif ($this->helpers()->has('Format')) { 13 | echo $this->Format->yesNo($value); 14 | } else { 15 | echo $value ? 'Yes' : 'No'; 16 | } 17 | ?> 18 | -------------------------------------------------------------------------------- /tests/Fixture/QueuedJobsFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], 22 | 'job_task' => ['type' => 'string', 'length' => 90, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], 23 | 'data' => ['type' => 'text', 'length' => 16777215, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], 24 | 'job_group' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], 25 | 'reference' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], 26 | 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], 27 | 'notbefore' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], 28 | 'fetched' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], 29 | 'completed' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], 30 | 'progress' => ['type' => 'float', 'length' => null, 'precision' => null, 'unsigned' => false, 'null' => true, 'default' => null, 'comment' => ''], 31 | 'attempts' => ['type' => 'integer', 'length' => 12, 'unsigned' => false, 'null' => false, 'default' => 0, 'comment' => '', 'precision' => null, 'autoIncrement' => null], 32 | 'failure_message' => ['type' => 'text', 'length' => 16777215, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], 33 | 'workerkey' => ['type' => 'string', 'length' => 45, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], 34 | 'status' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], 35 | 'priority' => ['type' => 'integer', 'length' => 3, 'unsigned' => false, 'null' => false, 'default' => 5, 'comment' => '', 'precision' => null, 'autoIncrement' => null], 36 | '_constraints' => [ 37 | 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], 38 | ], 39 | '_options' => [ 40 | 'engine' => 'InnoDB', 41 | ], 42 | ]; 43 | // @codingStandardsIgnoreEnd 44 | 45 | /** 46 | * Init method 47 | * 48 | * @return void 49 | */ 50 | public function init(): void { 51 | $this->records = [ 52 | ]; 53 | parent::init(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /tests/Fixture/SessionsFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'string', 'length' => 128], 20 | 'data' => ['type' => 'binary', 'length' => TableSchema::LENGTH_MEDIUM, 'null' => true], 21 | 'expires' => ['type' => 'integer', 'length' => 11, 'null' => true], 22 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 23 | ]; 24 | 25 | /** 26 | * records property 27 | * 28 | * @var array 29 | */ 30 | public array $records = []; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /tests/Fixture/UsersFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 19 | 'username' => ['type' => 'string', 'null' => true], 20 | 'email' => ['type' => 'string', 'null' => true], 21 | 'password' => ['type' => 'string', 'null' => true], 22 | 'created' => ['type' => 'timestamp', 'null' => true], 23 | 'updated' => ['type' => 'timestamp', 'null' => true], 24 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 25 | ]; 26 | 27 | /** 28 | * records property 29 | * 30 | * @var array 31 | */ 32 | public array $records = [ 33 | [ 34 | 'username' => 'mariano', 35 | 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO', 36 | 'email' => 'example@example.org', 37 | 'created' => '2007-03-17 01:16:23', 38 | 'updated' => '2007-03-17 01:18:31', 39 | ], 40 | ]; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tests/TestCase/Command/CliTestCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 21 | } 22 | 23 | /** 24 | * @return void 25 | */ 26 | public function testBasic() { 27 | $this->skipIf(version_compare(Configure::version(), '5.1', '<')); 28 | 29 | $this->exec('cli_test'); 30 | $this->assertOutputContains('Router::url([\'controller\' => \'Test\'], true)'); 31 | $this->assertOutputContains('/test'); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testPrefix() { 38 | $builder = Router::createRouteBuilder('/'); 39 | $builder->setRouteClass(DashedRoute::class); 40 | $builder->scope('/', function (RouteBuilder $routes): void { 41 | $routes->fallbacks(); 42 | }); 43 | $builder->prefix('Admin', function (RouteBuilder $routes): void { 44 | $routes->fallbacks(); 45 | }); 46 | 47 | $this->exec('cli_test --prefix Admin'); 48 | 49 | $this->assertOutputContains('/admin/test'); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /tests/TestCase/Command/CurrentConfigCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function testConfigure() { 26 | $this->exec('current_config configure'); 27 | $this->assertOutputContains('[App]'); 28 | $this->assertOutputContains('[namespace] =>'); 29 | } 30 | 31 | /** 32 | * @return void 33 | */ 34 | public function testDisplay() { 35 | $this->exec('current_config display'); 36 | $this->assertOutputContains('Full Base URL:'); 37 | } 38 | 39 | /** 40 | * @return void 41 | */ 42 | public function testPhpinfo() { 43 | $this->exec('current_config phpinfo'); 44 | $this->assertOutputContains('session.auto_start'); 45 | } 46 | 47 | /** 48 | * @return void 49 | */ 50 | public function testValidate() { 51 | $this->exec('current_config validate'); 52 | $this->assertOutputContains('[driver]'); 53 | $this->assertOutputContains('"className"'); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /tests/TestCase/Command/DbInitCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 22 | } 23 | 24 | /** 25 | * @return void 26 | */ 27 | public function testInit() { 28 | $this->skipIfNotDriver('Sqlite'); 29 | 30 | $this->exec('db init'); 31 | 32 | $this->assertErrorContains('Using in-memory database, skipping'); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tests/TestCase/Command/DbResetCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function testReset() { 26 | $this->exec('db reset --dry-run'); 27 | 28 | $this->assertOutputContains('DRY-RUN'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/TestCase/Command/DbWipeCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function testReset() { 26 | $this->exec('db wipe --dry-run'); 27 | 28 | $this->assertOutputContains('DRY-RUN'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/TestCase/Command/HealthcheckCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 25 | } 26 | 27 | /** 28 | * Test defaultName method 29 | * 30 | * @return void 31 | */ 32 | public function testExecute(): void { 33 | Configure::write('Setup.Healthcheck.checks', [ 34 | PhpVersionCheck::class, 35 | ]); 36 | 37 | Configure::write('App.fullBaseUrl', 'https://example.com'); 38 | 39 | $this->exec('healthcheck -v'); 40 | 41 | $this->assertExitSuccess(); 42 | $this->assertOutputContains('=> OK'); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /tests/TestCase/Command/MaintenanceModeActivateCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 17 | } 18 | 19 | /** 20 | * @return void 21 | */ 22 | public function testActivate() { 23 | $this->exec('maintenance_mode activate'); 24 | $this->assertOutputContains('Maintenance mode activated ...'); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tests/TestCase/Command/MaintenanceModeDeactivateCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 17 | } 18 | 19 | /** 20 | * @return void 21 | */ 22 | public function testDeactivate() { 23 | $this->exec('maintenance_mode deactivate'); 24 | $this->assertOutputContains('Maintenance mode deactivated ...'); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tests/TestCase/Command/MaintenanceModeStatusCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 19 | 20 | if (file_exists(TMP . 'maintenance.txt')) { 21 | unlink(TMP . 'maintenance.txt'); 22 | } 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testStatus() { 29 | $this->exec('maintenance_mode status'); 30 | $this->assertOutputContains('Maintenance mode not active'); 31 | } 32 | 33 | /** 34 | * @return void 35 | */ 36 | public function testStatusActive() { 37 | $this->exec('maintenance_mode activate'); 38 | 39 | $this->exec('maintenance_mode status'); 40 | $this->assertOutputContains('Maintenance mode active'); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /tests/TestCase/Command/MaintenanceModeWhitelistCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 19 | 20 | $matches = glob(TMP . 'maintenanceOverride-*\.txt'); 21 | if ($matches) { 22 | foreach ($matches as $match) { 23 | unlink($match); 24 | } 25 | } 26 | } 27 | 28 | /** 29 | * @return void 30 | */ 31 | public function testWhitelist() { 32 | $this->exec('maintenance_mode whitelist'); 33 | $this->assertOutputContains('n/a'); 34 | 35 | $this->exec('maintenance_mode whitelist 192.168.0.1'); 36 | $this->assertOutputContains('192.168.0.1'); 37 | 38 | $this->exec('maintenance_mode whitelist -r'); 39 | $this->assertOutputContains('n/a'); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tests/TestCase/Command/ResetCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function testUpdate() { 26 | $this->exec('reset pwd', ['123']); 27 | 28 | $this->assertOutputContains('0 pwds resetted - DONE'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/TestCase/Command/UserCreateCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function testCreate() { 26 | $this->exec('user create admin 123', ['y', 'some@email.de', 'y']); 27 | $this->assertOutputContains('User inserted! ID: 1'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tests/TestCase/Command/UserUpdateCommandTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function testUpdate() { 26 | $this->exec('user create admin 123', ['y', 'some@email.de', 'y']); 27 | 28 | $this->exec('user update admin 123456', []); 29 | $this->assertOutputContains('Password updated for user admin'); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /tests/TestCase/Controller/Admin/ConfigurationControllerTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 19 | } 20 | 21 | /** 22 | * @return void 23 | */ 24 | public function testIndex() { 25 | $this->disableErrorHandlerMiddleware(); 26 | 27 | $this->session(['Auth' => ['User' => ['id' => 1, 'role_id' => 9]]]); 28 | 29 | $this->get(['prefix' => 'Admin', 'plugin' => 'Setup', 'controller' => 'Configuration', 'action' => 'index']); 30 | 31 | $this->assertResponseCode(200); 32 | $this->assertNoRedirect(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tests/TestCase/Controller/Admin/DatabaseControllerTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 21 | } 22 | 23 | /** 24 | * @return void 25 | */ 26 | public function testForeignKeys() { 27 | $this->skipIfNotDriver('Mysql', 'Only for MYSQL for now'); 28 | 29 | $this->disableErrorHandlerMiddleware(); 30 | 31 | $this->session(['Auth' => ['User' => ['id' => 1]]]); 32 | 33 | $this->get(['prefix' => 'Admin', 'plugin' => 'Setup', 'controller' => 'Database', 'action' => 'foreignKeys']); 34 | 35 | $this->assertResponseCode(200); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /tests/TestCase/Controller/Admin/SetupControllerTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 21 | } 22 | 23 | /** 24 | * @return void 25 | */ 26 | public function testIndex() { 27 | $this->disableErrorHandlerMiddleware(); 28 | 29 | $this->session(['Auth' => ['User' => ['id' => 1]]]); 30 | 31 | $this->get(['prefix' => 'Admin', 'plugin' => 'Setup', 'controller' => 'Setup', 'action' => 'index']); 32 | 33 | $this->assertResponseCode(200); 34 | } 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function testMaintenance() { 40 | $this->disableErrorHandlerMiddleware(); 41 | 42 | $this->session(['Auth' => ['User' => ['id' => 1]]]); 43 | 44 | $this->get(['prefix' => 'Admin', 'plugin' => 'Setup', 'controller' => 'Setup', 'action' => 'maintenance']); 45 | 46 | $this->assertResponseCode(200); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /tests/TestCase/Controller/HealthcheckControllerTest.php: -------------------------------------------------------------------------------- 1 | loadPlugins(['Setup']); 19 | } 20 | 21 | /** 22 | * @return void 23 | */ 24 | public function testIndex() { 25 | $this->disableErrorHandlerMiddleware(); 26 | 27 | $this->get(['plugin' => 'Setup', 'controller' => 'Healthcheck', 'action' => 'index']); 28 | 29 | $this->assertResponseCode(200); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/Core/CakeCacheCheckTest.php: -------------------------------------------------------------------------------- 1 | assertSame('Core', $check->domain()); 17 | } 18 | 19 | /** 20 | * @return void 21 | */ 22 | public function testCheck() { 23 | $check = new CakeCacheCheck(); 24 | 25 | $check->check(); 26 | $this->assertTrue($check->passed(), print_r($check->__debugInfo(), true)); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function testCheckTooLow() { 33 | Configure::write('Healthcheck.checkCacheKeys', ['xyz']); 34 | 35 | $check = new CakeCacheCheck(); 36 | $check->check(); 37 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 38 | 39 | $this->assertSame(['The following cache setups are missing: xyz.'], $check->failureMessage()); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/Core/CakeSaltCheckTest.php: -------------------------------------------------------------------------------- 1 | assertSame('Core', $check->domain()); 17 | } 18 | 19 | /** 20 | * @return void 21 | */ 22 | public function testCheck() { 23 | Configure::write('Security.salt', '123456'); 24 | 25 | $check = new CakeSaltCheck(); 26 | 27 | $check->check(); 28 | $this->assertTrue($check->passed(), print_r($check->__debugInfo(), true)); 29 | } 30 | 31 | /** 32 | * @return void 33 | */ 34 | public function testCheckTooLow() { 35 | Configure::write('Security.salt', '__SALT__'); 36 | 37 | $check = new CakeSaltCheck(); 38 | $check->check(); 39 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 40 | 41 | $this->assertNotEmpty($check->failureMessage()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/Core/CakeVersionCheckTest.php: -------------------------------------------------------------------------------- 1 | testFiles = ROOT . DS . 'tests' . DS . 'test_files' . DS . 'healthcheck' . DS; 19 | } 20 | 21 | /** 22 | * @return void 23 | */ 24 | public function testDomain(): void { 25 | $check = new CakeVersionCheck(); 26 | $this->assertSame('Core', $check->domain()); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function testCheck() { 33 | $check = new CakeVersionCheck('5.2.1', $this->testFiles . 'PhpVersionCheck' . DS . 'basic' . DS); 34 | 35 | $check->check(); 36 | $this->assertTrue($check->passed(), print_r($check->__debugInfo(), true)); 37 | } 38 | 39 | /** 40 | * @return void 41 | */ 42 | public function testCheckTooLow() { 43 | $check = new CakeVersionCheck('5.2.0', $this->testFiles . 'PhpVersionCheck' . DS . 'basic' . DS); 44 | 45 | $check->check(); 46 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 47 | 48 | $this->assertNotEmpty($check->failureMessage()); 49 | } 50 | 51 | /** 52 | * @return void 53 | */ 54 | public function testCheckTooHigh() { 55 | $check = new CakeVersionCheck('5.3.0', $this->testFiles . 'PhpVersionCheck' . DS . 'basic' . DS); 56 | 57 | $check->check(); 58 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 59 | 60 | $this->assertNotEmpty($check->failureMessage()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/Core/FullBaseUrlCheckTest.php: -------------------------------------------------------------------------------- 1 | assertSame('Core', $check->domain()); 17 | } 18 | 19 | /** 20 | * @return void 21 | */ 22 | public function testCheck() { 23 | $check = new FullBaseUrlCheck(); 24 | 25 | Configure::write('App.fullBaseUrl', 'https://example.com'); 26 | 27 | $check->check(); 28 | $this->assertTrue($check->passed(), print_r($check->__debugInfo(), true)); 29 | } 30 | 31 | /** 32 | * @return void 33 | */ 34 | public function testCheckMissing() { 35 | $check = new FullBaseUrlCheck(); 36 | 37 | Configure::write('App.fullBaseUrl', ''); 38 | 39 | $check->check(); 40 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 41 | 42 | $this->assertNotEmpty($check->failureMessage()); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/Database/ConnectCheckTest.php: -------------------------------------------------------------------------------- 1 | assertSame('Database', $check->domain()); 16 | } 17 | 18 | /** 19 | * @return void 20 | */ 21 | public function testCheck() { 22 | $check = new ConnectCheck(); 23 | 24 | $check->check(); 25 | $this->assertTrue($check->passed(), print_r($check->__debugInfo(), true)); 26 | } 27 | 28 | /** 29 | * @return void 30 | */ 31 | public function testCheckInvalid() { 32 | $check = new ConnectCheck('foo'); 33 | 34 | $check->check(); 35 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 36 | 37 | $this->assertSame(['Cannot connect to database on connection `foo`: The datasource configuration `foo` was not found.'], $check->failureMessage()); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/Environment/PhpUploadLimitCheckTest.php: -------------------------------------------------------------------------------- 1 | assertSame('Environment', $check->domain()); 16 | } 17 | 18 | /** 19 | * @return void 20 | */ 21 | public function testCheck() { 22 | $check = new PhpUploadLimitCheck(2); 23 | 24 | $check->check(); 25 | $this->assertTrue($check->passed(), print_r($check->__debugInfo(), true)); 26 | } 27 | 28 | /** 29 | * @return void 30 | */ 31 | public function testCheckTooLow() { 32 | $check = new PhpUploadLimitCheck(2000); 33 | 34 | $check->check(); 35 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 36 | 37 | $this->assertNotEmpty($check->failureMessage()); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/Environment/PhpVersionCheckTest.php: -------------------------------------------------------------------------------- 1 | testFiles = ROOT . DS . 'tests' . DS . 'test_files' . DS . 'healthcheck' . DS; 19 | } 20 | 21 | /** 22 | * @return void 23 | */ 24 | public function testDomain(): void { 25 | $check = new PhpVersionCheck(); 26 | $this->assertSame('Environment', $check->domain()); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function testCheck() { 33 | $check = new PhpVersionCheck('8.3.10', $this->testFiles . 'PhpVersionCheck' . DS . 'basic' . DS); 34 | 35 | $check->check(); 36 | $this->assertTrue($check->passed(), print_r($check->__debugInfo(), true)); 37 | } 38 | 39 | /** 40 | * @return void 41 | */ 42 | public function testCheckTooLow() { 43 | $check = new PhpVersionCheck('8.3.0', $this->testFiles . 'PhpVersionCheck' . DS . 'basic' . DS); 44 | 45 | $check->check(); 46 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 47 | 48 | $this->assertNotEmpty($check->failureMessage()); 49 | } 50 | 51 | /** 52 | * @return void 53 | */ 54 | public function testCheckTooHigh() { 55 | $check = new PhpVersionCheck('8.4.0', $this->testFiles . 'PhpVersionCheck' . DS . 'basic' . DS); 56 | 57 | $check->check(); 58 | $this->assertFalse($check->passed(), print_r($check->__debugInfo(), true)); 59 | 60 | $this->assertNotEmpty($check->failureMessage()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /tests/TestCase/Healthcheck/Check/HealthcheckCollectorTest.php: -------------------------------------------------------------------------------- 1 | assertNotEmpty($checks); 18 | foreach ($checks as $check => $options) { 19 | $this->assertTrue(is_subclass_of($check, CheckInterface::class), $check . ' actually is ' . gettype($check)); 20 | $this->assertSame([], $options, print_r($options, true)); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tests/TestCase/Maintenance/MaintenanceTest.php: -------------------------------------------------------------------------------- 1 | Maintenance = new Maintenance(); 22 | 23 | if (file_exists(TMP . 'maintenance.txt')) { 24 | unlink(TMP . 'maintenance.txt'); 25 | } 26 | } 27 | 28 | /** 29 | * @return void 30 | */ 31 | protected function tearDown(): void { 32 | parent::tearDown(); 33 | 34 | $this->Maintenance->setMaintenanceMode(false); 35 | $this->Maintenance->clearWhitelist(); 36 | } 37 | 38 | /** 39 | * MaintenanceLibTest::testStatus() 40 | * 41 | * @return void 42 | */ 43 | public function testStatus() { 44 | $status = $this->Maintenance->isMaintenanceMode(); 45 | $this->assertFalse($status); 46 | 47 | $this->Maintenance->setMaintenanceMode(true); 48 | $status = $this->Maintenance->isMaintenanceMode(); 49 | $this->assertTrue($status); 50 | 51 | $this->assertFileExists(TMP . 'maintenance.txt'); 52 | } 53 | 54 | /** 55 | * @return void 56 | */ 57 | public function testWhitelist() { 58 | $result = $this->Maintenance->whitelist(); 59 | $this->assertEmpty($result); 60 | 61 | $whitelist = ['192.168.0.1']; 62 | $this->Maintenance->addToWhitelist($whitelist); 63 | 64 | $result = $this->Maintenance->whitelist(); 65 | $this->assertNotSame([], $result); 66 | 67 | $this->Maintenance->clearWhitelist(['192.111.111.111']); 68 | $result = $this->Maintenance->whitelist(); 69 | 70 | $this->assertSame($whitelist, $result); 71 | 72 | $this->Maintenance->clearWhitelist(); 73 | $result = $this->Maintenance->whitelist(); 74 | $this->assertSame([], $result); 75 | } 76 | 77 | /** 78 | * @return void 79 | */ 80 | public function testWhitelistSubnet() { 81 | $result = $this->Maintenance->whitelist(); 82 | $this->assertEmpty($result); 83 | 84 | $whitelist = ['5.146.197.0/24']; 85 | $this->Maintenance->addToWhitelist($whitelist); 86 | 87 | $result = $this->Maintenance->whitelist(); 88 | $this->assertNotEmpty($result); 89 | 90 | $result = $this->Maintenance->whitelisted('5.146.197.255'); 91 | $this->assertTrue($result); 92 | 93 | $this->Maintenance->clearWhitelist(['5.146.197.0/24']); 94 | $result = $this->Maintenance->whitelist(); 95 | $this->assertSame([], $result); 96 | 97 | $this->Maintenance->clearWhitelist(); 98 | $result = $this->Maintenance->whitelist(); 99 | $this->assertEmpty($result); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /tests/TestCase/Middleware/MaintenanceMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | invokeMethod($middleware, 'build', [$response]); 28 | 29 | $this->assertStringContainsString('Please wait... We will be back shortly', $result->getBody()->getContents()); 30 | 31 | $this->assertSame(503, $result->getStatusCode()); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testBuildCustomLayout() { 38 | $config = [ 39 | 'templateLayout' => 'maintenance', 40 | ]; 41 | 42 | $middleware = new MaintenanceMiddleware($config); 43 | 44 | $response = new Response(); 45 | /** @var \Cake\Http\Response $result */ 46 | $result = $this->invokeMethod($middleware, 'build', [$response]); 47 | 48 | $body = $result->getBody()->getContents(); 49 | $this->assertStringContainsString('Please wait... We will be back shortly', $body); 50 | $this->assertStringContainsString('

We are working right now

', $body); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /tests/TestCase/Queue/Task/HealthcheckTaskTest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | protected array $fixtures = [ 14 | ]; 15 | 16 | /** 17 | * @return void 18 | */ 19 | #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] 20 | public function testRun(): void { 21 | $task = new HealthcheckTask(); 22 | 23 | $task->run([], 0); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /tests/TestCase/Utility/SetupTest.php: -------------------------------------------------------------------------------- 1 | 'ControllerName', 'action' => 'action_name', '?' => ['clearcache' => 1, 'foo' => 'bar']]; 15 | $result = Setup::cleanedUrl('clearcache', $url); 16 | $expected = ['controller' => 'ControllerName', 'action' => 'action_name', '?' => ['foo' => 'bar']]; 17 | $this->assertSame($expected, $result); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tests/config/bootstrap.php: -------------------------------------------------------------------------------- 1 | setRouteClass(DashedRoute::class); 9 | 10 | $routes->scope('/', function (RouteBuilder $routes) { 11 | $routes->fallbacks(); 12 | }); 13 | $routes->prefix('Admin', function (RouteBuilder $routes) { 14 | $routes->fallbacks(); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /tests/schema.php: -------------------------------------------------------------------------------- 1 | $iterator 9 | */ 10 | $iterator = new DirectoryIterator(__DIR__ . DS . 'Fixture'); 11 | foreach ($iterator as $file) { 12 | if (!preg_match('/(\w+)Fixture.php$/', (string)$file, $matches)) { 13 | continue; 14 | } 15 | 16 | $name = $matches[1]; 17 | $tableName = null; 18 | $class = 'Setup\\Test\\Fixture\\' . $name . 'Fixture'; 19 | try { 20 | $fieldsObject = (new ReflectionClass($class))->getProperty('fields'); 21 | $tableObject = (new ReflectionClass($class))->getProperty('table'); 22 | $tableName = $tableObject->getDefaultValue(); 23 | 24 | } catch (ReflectionException $e) { 25 | continue; 26 | } 27 | 28 | if (!$tableName) { 29 | $tableName = Inflector::underscore($name); 30 | } 31 | 32 | $array = $fieldsObject->getDefaultValue(); 33 | $constraints = $array['_constraints'] ?? []; 34 | $indexes = $array['_indexes'] ?? []; 35 | unset($array['_constraints'], $array['_indexes'], $array['_options']); 36 | $table = [ 37 | 'table' => $tableName, 38 | 'columns' => $array, 39 | 'constraints' => $constraints, 40 | 'indexes' => $indexes, 41 | ]; 42 | $tables[$tableName] = $table; 43 | } 44 | 45 | return $tables; 46 | -------------------------------------------------------------------------------- /tests/shim.php: -------------------------------------------------------------------------------- 1 |