├── plugins
└── empty
├── webroot
├── js
│ └── empty
├── favicon.ico
├── img
│ ├── cake-logo.png
│ ├── cake.icon.png
│ ├── cake.power.gif
│ └── cake.logo.svg
├── font
│ ├── cakedingbats-webfont.eot
│ ├── cakedingbats-webfont.ttf
│ ├── cakedingbats-webfont.woff
│ └── cakedingbats-webfont.woff2
├── .htaccess
├── index.php
└── css
│ ├── home.css
│ └── cake.css
├── src
├── View
│ ├── Helper
│ │ └── empty
│ ├── AppView.php
│ └── AjaxView.php
├── Model
│ ├── Behavior
│ │ └── empty
│ ├── Entity
│ │ ├── ArticlesTag.php
│ │ ├── Tag.php
│ │ ├── User.php
│ │ └── Article.php
│ └── Table
│ │ ├── ArticlesTagsTable.php
│ │ ├── TagsTable.php
│ │ ├── UsersTable.php
│ │ └── ArticlesTable.php
├── Controller
│ ├── Component
│ │ └── empty
│ ├── ErrorController.php
│ ├── AppController.php
│ ├── PagesController.php
│ ├── ArticlesController.php
│ ├── TagsController.php
│ └── UsersController.php
├── Policy
│ └── ArticlePolicy.php
├── Application.php
└── Console
│ └── Installer.php
├── tests
├── TestCase
│ ├── View
│ │ └── Helper
│ │ │ └── empty
│ ├── Model
│ │ ├── Behavior
│ │ │ └── empty
│ │ └── Table
│ │ │ ├── ArticlesTagsTableTest.php
│ │ │ ├── TagsTableTest.php
│ │ │ ├── UsersTableTest.php
│ │ │ └── ArticlesTableTest.php
│ ├── Controller
│ │ ├── Component
│ │ │ └── empty
│ │ ├── UsersControllerTest.php
│ │ ├── TagsControllerTest.php
│ │ └── PagesControllerTest.php
│ └── ApplicationTest.php
├── bootstrap.php
└── Fixture
│ ├── TagsFixture.php
│ ├── ArticlesTagsFixture.php
│ ├── UsersFixture.php
│ └── ArticlesFixture.php
├── templates
├── element
│ └── flash
│ │ ├── error.php
│ │ ├── success.php
│ │ └── default.php
├── layout
│ ├── rss
│ │ └── default.php
│ ├── ajax.php
│ ├── email
│ │ ├── text
│ │ │ └── default.php
│ │ └── html
│ │ │ └── default.php
│ ├── error.php
│ └── default.php
├── Articles
│ ├── view.php
│ ├── add.php
│ ├── edit.php
│ ├── tags.php
│ └── index.php
├── Users
│ ├── login.php
│ ├── add.php
│ ├── edit.php
│ ├── index.php
│ └── view.php
├── email
│ ├── text
│ │ └── default.php
│ └── html
│ │ └── default.php
├── Tags
│ ├── add.php
│ ├── edit.php
│ ├── index.php
│ └── view.php
├── Error
│ ├── error400.php
│ └── error500.php
└── Pages
│ └── home.php
├── .htaccess
├── .editorconfig
├── bin
├── cake.php
├── cake.bat
└── cake
├── config
├── schema
│ ├── sessions.sql
│ └── i18n.sql
├── bootstrap_cli.php
├── .env.default
├── requirements.php
├── routes.php
├── app_local.example.php
├── paths.php
├── bootstrap.php
├── Migrations
│ └── 20221126022125_InitialSchema.php
└── app.php
├── phpcs.xml
├── index.php
├── .travis.yml
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE.md
├── .gitignore
├── .gitattributes
├── phpunit.xml.dist
├── README.md
└── composer.json
/plugins/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webroot/js/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/View/Helper/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Model/Behavior/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Controller/Component/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/TestCase/View/Helper/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/TestCase/Model/Behavior/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/TestCase/Controller/Component/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/favicon.ico
--------------------------------------------------------------------------------
/webroot/img/cake-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/img/cake-logo.png
--------------------------------------------------------------------------------
/webroot/img/cake.icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/img/cake.icon.png
--------------------------------------------------------------------------------
/webroot/img/cake.power.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/img/cake.power.gif
--------------------------------------------------------------------------------
/webroot/font/cakedingbats-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/font/cakedingbats-webfont.eot
--------------------------------------------------------------------------------
/webroot/font/cakedingbats-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/font/cakedingbats-webfont.ttf
--------------------------------------------------------------------------------
/webroot/font/cakedingbats-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/font/cakedingbats-webfont.woff
--------------------------------------------------------------------------------
/webroot/font/cakedingbats-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/cms-tutorial/HEAD/webroot/font/cakedingbats-webfont.woff2
--------------------------------------------------------------------------------
/webroot/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | RewriteEngine On
3 | RewriteCond %{REQUEST_FILENAME} !-f
4 | RewriteRule ^ index.php [L]
5 |
6 |
--------------------------------------------------------------------------------
/templates/element/flash/error.php:
--------------------------------------------------------------------------------
1 |
6 |
= $message ?>
7 |
--------------------------------------------------------------------------------
/templates/element/flash/success.php:
--------------------------------------------------------------------------------
1 |
6 | = $message ?>
7 |
--------------------------------------------------------------------------------
/templates/layout/rss/default.php:
--------------------------------------------------------------------------------
1 | fetch('title');
7 | endif;
8 |
9 | echo $this->Rss->document(
10 | $this->Rss->channel([], $channel, $this->fetch('content'))
11 | );
12 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
4 | # RequestHeader unset Proxy
5 | #
6 |
7 |
8 | RewriteEngine on
9 | RewriteRule ^$ webroot/ [L]
10 | RewriteRule (.*) webroot/$1 [L]
11 |
12 |
--------------------------------------------------------------------------------
/templates/element/flash/default.php:
--------------------------------------------------------------------------------
1 |
10 | = $message ?>
11 |
--------------------------------------------------------------------------------
/templates/Articles/view.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | = h($article->title) ?>
4 | = h($article->body) ?>
5 | Tags: = h($article->tag_string) ?>
6 | Created: = $article->created->format(DATE_RFC850) ?>
7 | = $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; This file is for unifying the coding style for different editors and IDEs.
2 | ; More information at https://editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 4
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.bat]
14 | end_of_line = crlf
15 |
16 | [*.yml]
17 | indent_style = space
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/templates/Articles/add.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | Add Article
4 | Form->create($article);
6 | echo $this->Form->control('title');
7 | echo $this->Form->control('body', ['rows' => '3']);
8 | echo $this->Form->control('tag_string', ['type' => 'text']);
9 | echo $this->Form->button(__('Save Article'));
10 | echo $this->Form->end();
11 | ?>
12 |
--------------------------------------------------------------------------------
/templates/Articles/edit.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | Edit Article
4 | Form->create($article);
6 | echo $this->Form->control('title');
7 | echo $this->Form->control('body', ['rows' => '3']);
8 | echo $this->Form->control('tag_string', ['type' => 'text']);
9 | echo $this->Form->button(__('Save Article'));
10 | echo $this->Form->end();
11 | ?>
12 |
--------------------------------------------------------------------------------
/bin/cake.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php -q
2 | run($argv));
13 |
--------------------------------------------------------------------------------
/config/schema/sessions.sql:
--------------------------------------------------------------------------------
1 | # Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
2 | #
3 | # Licensed under The MIT License
4 | # For full copyright and license information, please see the LICENSE.txt
5 | # Redistributions of files must retain the above copyright notice.
6 | # MIT License (https://opensource.org/licenses/mit-license.php)
7 |
8 | CREATE TABLE sessions (
9 | id char(40) NOT NULL,
10 | data text,
11 | expires INT(11) NOT NULL,
12 | PRIMARY KEY (id)
13 | );
14 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 0
10 |
11 |
12 | ./src
13 | ./tests
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/templates/Articles/tags.php:
--------------------------------------------------------------------------------
1 |
2 | Articles tagged with
3 | = $this->Text->toList(h($tags), 'or') ?>
4 |
5 |
6 |
7 |
8 |
9 |
10 | = $this->Html->link(
11 | $article->title,
12 | ['controller' => 'Articles', 'action' => 'view', $article->slug]
13 | ) ?>
14 | = h($article->created) ?>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/templates/Users/login.php:
--------------------------------------------------------------------------------
1 |
2 | = $this->Flash->render() ?>
3 |
Login
4 | = $this->Form->create() ?>
5 |
6 | = __('Please enter your username and password') ?>
7 | = $this->Form->control('email', ['required' => true]) ?>
8 | = $this->Form->control('password', ['required' => true]) ?>
9 |
10 | = $this->Form->submit(__('Login')); ?>
11 | = $this->Form->end() ?>
12 |
13 | = $this->Html->link("Add User", ['action' => 'add']) ?>
14 |
15 |
--------------------------------------------------------------------------------
/templates/email/text/default.php:
--------------------------------------------------------------------------------
1 | fetch('content');
17 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | fetch('content');
17 |
--------------------------------------------------------------------------------
/config/schema/i18n.sql:
--------------------------------------------------------------------------------
1 | # Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
2 | #
3 | # Licensed under The MIT License
4 | # For full copyright and license information, please see the LICENSE.txt
5 | # Redistributions of files must retain the above copyright notice.
6 | # MIT License (https://opensource.org/licenses/mit-license.php)
7 |
8 | CREATE TABLE i18n (
9 | id int NOT NULL auto_increment,
10 | locale varchar(6) NOT NULL,
11 | model varchar(255) NOT NULL,
12 | foreign_key int(10) NOT NULL,
13 | field varchar(255) NOT NULL,
14 | content text,
15 | PRIMARY KEY (id),
16 | UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
17 | INDEX I18N_FIELD(model, foreign_key, field)
18 | );
19 |
--------------------------------------------------------------------------------
/templates/email/html/default.php:
--------------------------------------------------------------------------------
1 | ' . $line . "\n";
20 | endforeach;
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | dist: trusty
4 |
5 | sudo: false
6 |
7 | php:
8 | - 5.6
9 | - 7.0
10 | - 7.1
11 |
12 | matrix:
13 | fast_finish: true
14 |
15 | include:
16 | - php: 7.0
17 | env: PHPCS=1
18 |
19 | before_script:
20 | - if [[ $PHPCS = 1 ]]; then composer require cakephp/cakephp-codesniffer:~2.1; fi
21 | - if [[ $PHPCS != 1 ]]; then composer install; fi
22 | - if [[ $PHPCS != 1 ]]; then composer require phpunit/phpunit:"^5.7|^6.0"; fi
23 | - if [[ $PHPCS != 1 ]]; then composer run-script post-install-cmd --no-interaction; fi
24 |
25 | script:
26 | - if [[ $PHPCS != 1 ]]; then vendor/bin/phpunit; fi
27 | - if [[ $PHPCS = 1 ]]; then vendor/bin/phpcs -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests ./config ./webroot; fi
28 |
29 | notifications:
30 | email: false
31 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **PLEASE NOTE:**
2 |
3 | This is only a issue tracker for issues related to the CakePHP Application Skeleton.
4 | For CakePHP Framework issues please use this [issue tracker](https://github.com/cakephp/cakephp/issues).
5 |
6 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue.
7 |
8 | The best way to propose a feature is to open an issue first and discuss your ideas there before implementing them.
9 |
10 | Always follow the [contribution guidelines](https://github.com/cakephp/cakephp/blob/master/.github/CONTRIBUTING.md) guidelines when submitting a pull request. In particular, make sure existing tests still pass, and add tests for all new behavior. When fixing a bug, you may want to add a test to verify the fix.
--------------------------------------------------------------------------------
/templates/layout/email/html/default.php:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | = $this->fetch('title') ?>
20 |
21 |
22 | = $this->fetch('content') ?>
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # CakePHP specific files #
2 | ##########################
3 | /config/.env
4 | /config/app_local.php
5 | /tmp/*
6 | /logs/*
7 | /tmp/*
8 | /vendor/*
9 |
10 | # OS generated files #
11 | ######################
12 | .DS_Store
13 | .DS_Store?
14 | ._*
15 | .Spotlight-V100
16 | .Trashes
17 | Icon?
18 | ehthumbs.db
19 | Thumbs.db
20 |
21 | # Tool specific files #
22 | #######################
23 | *~ # vim
24 | *.swp # vim
25 | *.swo # vim
26 | *.sublime-* # sublime text
27 | *.tmlanguage.cache # sublime text & textmate
28 | *.tmPreferences.cache # sublime text & textmate
29 | *.stTheme.cache # cache files for sublime text
30 | .settings/* # Eclipse
31 | .idea/* # JetBrains, aka PHPStorm, IntelliJ IDEA
32 | nbproject/* # NetBeans
33 | .vscode # Visual Studio Code
34 | .sass-cache/ # Sass preprocessor
35 | .idea
36 | config/Migrations/schema-dump-default.lock
37 | database
38 | cms-tutorial-database.sqlite
39 |
--------------------------------------------------------------------------------
/src/Model/Entity/ArticlesTag.php:
--------------------------------------------------------------------------------
1 | true,
30 | 'tag' => true,
31 | ];
32 | }
33 |
--------------------------------------------------------------------------------
/src/Policy/ArticlePolicy.php:
--------------------------------------------------------------------------------
1 | isAuthor($user, $article);
19 | }
20 |
21 | public function canDelete(IdentityInterface $user, Article $article)
22 | {
23 | // logged in users can delete their own articles.
24 | return $this->isAuthor($user, $article);
25 | }
26 |
27 | protected function isAuthor(IdentityInterface $user, Article $article)
28 | {
29 | return $article->user_id === $user->getIdentifier();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | This is a (multiple allowed):
2 |
3 | * [x] bug
4 | * [ ] enhancement
5 | * [ ] feature-discussion (RFC)
6 |
7 | * CakePHP Application Skeleton Version: EXACT RELEASE VERSION OR COMMIT HASH, HERE.
8 | * Platform and Target: YOUR WEB-SERVER, DATABASE AND OTHER RELEVANT INFO AND HOW THE REQUEST IS BEING MADE, HERE.
9 |
10 | ### What you did
11 | EXPLAIN WHAT YOU DID, PREFERABLY WITH CODE EXAMPLES, HERE.
12 |
13 | ### What happened
14 | EXPLAIN WHAT IS ACTUALLY HAPPENING, HERE.
15 |
16 | ### What you expected to happen
17 | EXPLAIN WHAT IS TO BE EXPECTED, HERE.
18 |
19 | P.S. Remember, an issue is not the place to ask questions. You can use [Stack Overflow](https://stackoverflow.com/questions/tagged/cakephp)
20 | for that or join the #cakephp channel on irc.freenode.net, where we will be more
21 | than happy to help answer your questions.
22 |
23 | Before you open an issue, please check if a similar issue already exists or has been closed before.
--------------------------------------------------------------------------------
/bin/cake.bat:
--------------------------------------------------------------------------------
1 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
2 | ::
3 | :: Cake is a Windows batch script for invoking CakePHP shell commands
4 | ::
5 | :: CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6 | :: Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7 | ::
8 | :: Licensed under The MIT License
9 | :: Redistributions of files must retain the above copyright notice.
10 | ::
11 | :: @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
12 | :: @link https://cakephp.org CakePHP(tm) Project
13 | :: @since 2.0.0
14 | :: @license https://opensource.org/licenses/mit-license.php MIT License
15 | ::
16 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
17 |
18 | @echo off
19 |
20 | SET app=%0
21 | SET lib=%~dp0
22 |
23 | php "%lib%cake.php" %*
24 |
25 | echo.
26 |
27 | exit /B %ERRORLEVEL%
28 |
--------------------------------------------------------------------------------
/templates/Users/add.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | = __('Actions') ?>
9 | = $this->Html->link(__('List Users'), ['action' => 'index']) ?>
10 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
11 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
12 |
13 |
14 |
15 | = $this->Form->create($user) ?>
16 |
17 | = __('Add User') ?>
18 | Form->control('email');
20 | echo $this->Form->control('password');
21 | ?>
22 |
23 | = $this->Form->button(__('Submit')) ?>
24 | = $this->Form->end() ?>
25 |
26 |
--------------------------------------------------------------------------------
/src/Model/Entity/Tag.php:
--------------------------------------------------------------------------------
1 | true,
31 | 'created' => true,
32 | 'modified' => true,
33 | 'articles' => true,
34 | ];
35 | }
36 |
--------------------------------------------------------------------------------
/templates/Tags/add.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
= __('Actions') ?>
11 | = $this->Html->link(__('List Tags'), ['action' => 'index'], ['class' => 'side-nav-item']) ?>
12 |
13 |
14 |
15 |
16 | = $this->Form->create($tag) ?>
17 |
18 | = __('Add Tag') ?>
19 | Form->control('title');
21 | echo $this->Form->control('articles._ids', ['options' => $articles]);
22 | ?>
23 |
24 | = $this->Form->button(__('Submit')) ?>
25 | = $this->Form->end() ?>
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
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=auto
4 | * text eol=lf
5 |
6 | # Explicitly declare text files we want to always be normalized and converted
7 | # to native line endings on checkout.
8 | *.php text
9 | *.default text
10 | *.ctp text
11 | *.sql text
12 | *.md text
13 | *.po text
14 | *.js text
15 | *.css text
16 | *.ini text
17 | *.properties text
18 | *.txt text
19 | *.xml text
20 | *.svg text
21 | *.yml text
22 | .htaccess text
23 |
24 | # Declare files that will always have CRLF line endings on checkout.
25 | *.bat eol=crlf
26 |
27 | # Declare files that will always have LF line endings on checkout.
28 | *.pem eol=lf
29 |
30 | # Denote all files that are truly binary and should not be modified.
31 | *.png binary
32 | *.jpg binary
33 | *.gif binary
34 | *.ico binary
35 | *.mo binary
36 | *.pdf binary
37 | *.phar binary
38 | *.woff binary
39 | *.woff2 binary
40 | *.ttf binary
41 | *.otf binary
42 | *.eot binary
43 |
--------------------------------------------------------------------------------
/templates/Articles/index.php:
--------------------------------------------------------------------------------
1 |
2 | = $this->Html->link('Add Article', ['action' => 'add']) ?>
3 | Articles
4 |
5 |
6 | Title
7 | Created
8 | Action
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | = $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
17 |
18 |
19 | = $article->created->format(DATE_RFC850) ?>
20 |
21 |
22 | = $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
23 | = $this->Form->postLink(
24 | 'Delete',
25 | ['action' => 'delete', $article->slug],
26 | ['confirm' => 'Are you sure?'])
27 | ?>
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/templates/Error/error400.php:
--------------------------------------------------------------------------------
1 | layout = 'error';
6 |
7 | if (Configure::read('debug')) :
8 | $this->layout = 'dev_error';
9 |
10 | $this->assign('title', $message);
11 | $this->assign('templateName', 'error400.php');
12 |
13 | $this->start('file');
14 | ?>
15 | queryString)) : ?>
16 |
17 | SQL Query:
18 | = h($error->queryString) ?>
19 |
20 |
21 | params)) : ?>
22 | SQL Query Params:
23 | params) ?>
24 |
25 | = $this->element('auto_table_warning') ?>
26 | end();
32 | endif;
33 | ?>
34 | = h($message) ?>
35 |
36 | = __d('cake', 'Error') ?>:
37 | = __d('cake', 'The requested address {0} was not found on this server.', "'{$url}' ") ?>
38 |
39 |
--------------------------------------------------------------------------------
/src/View/AppView.php:
--------------------------------------------------------------------------------
1 | loadHelper('Html');`
35 | *
36 | * @return void
37 | */
38 | public function initialize(): void
39 | {
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/config/bootstrap_cli.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | = __('Actions') ?>
9 | = $this->Form->postLink(
10 | __('Delete'),
11 | ['action' => 'delete', $user->id],
12 | ['confirm' => __('Are you sure you want to delete # {0}?', $user->id)]
13 | )
14 | ?>
15 | = $this->Html->link(__('List Users'), ['action' => 'index']) ?>
16 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
17 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
18 |
19 |
20 |
21 | = $this->Form->create($user) ?>
22 |
23 | = __('Edit User') ?>
24 | Form->control('email');
26 | echo $this->Form->control('password');
27 | ?>
28 |
29 | = $this->Form->button(__('Submit')) ?>
30 | = $this->Form->end() ?>
31 |
32 |
--------------------------------------------------------------------------------
/templates/Tags/edit.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
= __('Actions') ?>
11 | = $this->Form->postLink(
12 | __('Delete'),
13 | ['action' => 'delete', $tag->id],
14 | ['confirm' => __('Are you sure you want to delete # {0}?', $tag->id), 'class' => 'side-nav-item']
15 | ) ?>
16 | = $this->Html->link(__('List Tags'), ['action' => 'index'], ['class' => 'side-nav-item']) ?>
17 |
18 |
19 |
20 |
21 | = $this->Form->create($tag) ?>
22 |
23 | = __('Edit Tag') ?>
24 | Form->control('title');
26 | echo $this->Form->control('articles._ids', ['options' => $articles]);
27 | ?>
28 |
29 | = $this->Form->button(__('Submit')) ?>
30 | = $this->Form->end() ?>
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/templates/Error/error500.php:
--------------------------------------------------------------------------------
1 | layout = 'error';
6 |
7 | if (Configure::read('debug')) :
8 | $this->layout = 'dev_error';
9 |
10 | $this->assign('title', $message);
11 | $this->assign('templateName', 'error500.php');
12 |
13 | $this->start('file');
14 | ?>
15 | queryString)) : ?>
16 |
17 | SQL Query:
18 | = h($error->queryString) ?>
19 |
20 |
21 | params)) : ?>
22 | SQL Query Params:
23 | params) ?>
24 |
25 |
26 | Error in:
27 | = sprintf('%s, line %s', str_replace(ROOT, 'ROOT', $error->getFile()), $error->getLine()) ?>
28 |
29 | element('auto_table_warning');
31 |
32 | if (extension_loaded('xdebug')) :
33 | xdebug_print_function_stack();
34 | endif;
35 |
36 | $this->end();
37 | endif;
38 | ?>
39 | = __d('cake', 'An Internal Error Has Occurred') ?>
40 |
41 | = __d('cake', 'Error') ?>:
42 | = h($message) ?>
43 |
44 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ./tests/TestCase
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ./src/
37 | ./plugins/*/src/
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/View/AjaxView.php:
--------------------------------------------------------------------------------
1 | response = $this->response->withType('ajax');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/templates/layout/error.php:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | = $this->Html->charset() ?>
20 |
21 | = $this->fetch('title') ?>
22 |
23 | = $this->Html->meta('icon') ?>
24 |
25 | = $this->Html->css('base.css') ?>
26 | = $this->Html->css('cake.css') ?>
27 |
28 | = $this->fetch('meta') ?>
29 | = $this->fetch('css') ?>
30 | = $this->fetch('script') ?>
31 |
32 |
33 |
34 |
37 |
38 | = $this->Flash->render() ?>
39 |
40 | = $this->fetch('content') ?>
41 |
42 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/webroot/index.php:
--------------------------------------------------------------------------------
1 | emit($server->run());
41 |
--------------------------------------------------------------------------------
/tests/TestCase/Model/Table/ArticlesTagsTableTest.php:
--------------------------------------------------------------------------------
1 | getTableLocator()->exists('ArticlesTags') ? [] : ['className' => ArticlesTagsTable::class];
41 | $this->ArticlesTags = $this->getTableLocator()->get('ArticlesTags', $config);
42 | }
43 |
44 | /**
45 | * tearDown method
46 | *
47 | * @return void
48 | */
49 | public function tearDown(): void
50 | {
51 | unset($this->ArticlesTags);
52 |
53 | parent::tearDown();
54 | }
55 |
56 | /**
57 | * Test buildRules method
58 | *
59 | * @return void
60 | */
61 | public function testBuildRules(): void
62 | {
63 | $this->markTestIncomplete('Not implemented yet.');
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/TestCase/Controller/UsersControllerTest.php:
--------------------------------------------------------------------------------
1 | markTestIncomplete('Not implemented yet.');
31 | }
32 |
33 | /**
34 | * Test view method
35 | *
36 | * @return void
37 | */
38 | public function testView()
39 | {
40 | $this->markTestIncomplete('Not implemented yet.');
41 | }
42 |
43 | /**
44 | * Test add method
45 | *
46 | * @return void
47 | */
48 | public function testAdd()
49 | {
50 | $this->markTestIncomplete('Not implemented yet.');
51 | }
52 |
53 | /**
54 | * Test edit method
55 | *
56 | * @return void
57 | */
58 | public function testEdit()
59 | {
60 | $this->markTestIncomplete('Not implemented yet.');
61 | }
62 |
63 | /**
64 | * Test delete method
65 | *
66 | * @return void
67 | */
68 | public function testDelete()
69 | {
70 | $this->markTestIncomplete('Not implemented yet.');
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Model/Entity/User.php:
--------------------------------------------------------------------------------
1 | true,
33 | 'password' => true,
34 | 'created' => true,
35 | 'modified' => true,
36 | 'articles' => true,
37 | ];
38 |
39 | /**
40 | * Fields that are excluded from JSON versions of the entity.
41 | *
42 | * @var array
43 | */
44 | protected array $_hidden = [
45 | 'password',
46 | ];
47 |
48 | protected function _setPassword($value)
49 | {
50 | if (strlen($value)) {
51 | $hasher = new DefaultPasswordHasher();
52 |
53 | return $hasher->hash($value);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CakePHP CMS Tutorial
2 |
3 | The completed CMS tutorial application created during
4 | https://book.cakephp.org/5/en/tutorials-and-examples/cms/installation.html
5 |
6 | ## Installation
7 |
8 | 1. Download [Composer](https://getcomposer.org/doc/00-intro.md) or update `composer self-update`.
9 | 2. Download this source code. `git clone https://github.com/cakephp/cms-tutorial.git`
10 | 3. Install dependencies with composer
11 |
12 | ```bash
13 | cd cms-tutorial
14 | composer install
15 | ```
16 |
17 | ## Configuration
18 |
19 | You can use the default configuration, using a sqlite3 file based database. In that case, no configuration
20 | is required.
21 | If you want to use your own database, update the `'Datasources'` configuration in `config/app.php`.
22 | You'll also need to create a database and run the SQL located in the tutorial.
23 |
24 | * Update the `'Datasources'` configuration by:
25 | * editing `config/app.php` or `config/app_local.php` for your local database or
26 | * edit `config/bootstrap.php` to enable use of a `.env` file (uncomment the section that loads the `.env` file) (and provide the `config/.env` file.
27 | * Create a database if needed.
28 | * Run `bin/cake migrations migrate` to create the database tables.
29 |
30 |
31 | ## Running the project
32 |
33 | ```bash
34 | bin/cake server
35 | ```
36 |
37 | Then point your browser to http://localhost:8765
38 |
39 | You will be redirected to the `/users/login` page. This is the behavior we configured to request a valid
40 | username and password. The CMS Tutorial user Authentication and Authorization to identify the users.
41 |
--------------------------------------------------------------------------------
/bin/cake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | ################################################################################
3 | #
4 | # Cake is a shell script for invoking CakePHP shell commands
5 | #
6 | # CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
7 | # Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
8 | #
9 | # Licensed under The MIT License
10 | # For full copyright and license information, please see the LICENSE.txt
11 | # Redistributions of files must retain the above copyright notice.
12 | #
13 | # @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
14 | # @link https://cakephp.org CakePHP(tm) Project
15 | # @since 1.2.0
16 | # @license https://opensource.org/licenses/mit-license.php MIT License
17 | #
18 | ################################################################################
19 |
20 | # Canonicalize by following every symlink of the given name recursively
21 | canonicalize() {
22 | NAME="$1"
23 | if [ -f "$NAME" ]
24 | then
25 | DIR=$(dirname -- "$NAME")
26 | NAME=$(cd -P "$DIR" > /dev/null && pwd -P)/$(basename -- "$NAME")
27 | fi
28 | while [ -h "$NAME" ]; do
29 | DIR=$(dirname -- "$NAME")
30 | SYM=$(readlink "$NAME")
31 | NAME=$(cd "$DIR" > /dev/null && cd $(dirname -- "$SYM") > /dev/null && pwd)/$(basename -- "$SYM")
32 | done
33 | echo "$NAME"
34 | }
35 |
36 | CONSOLE=$(dirname -- "$(canonicalize "$0")")
37 | APP=$(dirname "$CONSOLE")
38 |
39 | if [ $(basename $0) != 'cake' ]
40 | then
41 | exec php "$CONSOLE"/cake.php $(basename $0) "$@"
42 | else
43 | exec php "$CONSOLE"/cake.php "$@"
44 | fi
45 |
46 | exit
47 |
--------------------------------------------------------------------------------
/tests/TestCase/ApplicationTest.php:
--------------------------------------------------------------------------------
1 | middleware($middleware);
41 |
42 | $this->assertInstanceOf(ErrorHandlerMiddleware::class, $middleware->get(0));
43 | $this->assertInstanceOf(AssetMiddleware::class, $middleware->get(1));
44 | $this->assertInstanceOf(RoutingMiddleware::class, $middleware->get(2));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/TestCase/Model/Table/TagsTableTest.php:
--------------------------------------------------------------------------------
1 | getTableLocator()->exists('Tags') ? [] : ['className' => TagsTable::class];
40 | $this->Tags = $this->getTableLocator()->get('Tags', $config);
41 | }
42 |
43 | /**
44 | * tearDown method
45 | *
46 | * @return void
47 | */
48 | public function tearDown(): void
49 | {
50 | unset($this->Tags);
51 |
52 | parent::tearDown();
53 | }
54 |
55 | /**
56 | * Test validationDefault method
57 | *
58 | * @return void
59 | */
60 | public function testValidationDefault(): void
61 | {
62 | $this->markTestIncomplete('Not implemented yet.');
63 | }
64 |
65 | /**
66 | * Test buildRules method
67 | *
68 | * @return void
69 | */
70 | public function testBuildRules(): void
71 | {
72 | $this->markTestIncomplete('Not implemented yet.');
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/config/.env.default:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Used as a default to seed config/.env which
3 | # enables you to use environment variables to configure
4 | # the aspects of your application that vary by
5 | # environment.
6 | #
7 | # To use this file, first copy it into `config/.env`.
8 | #
9 | # In development .env files are parsed by PHP
10 | # and set into the environment. This provides a simpler
11 | # development workflow over standard environment variables.
12 | export APP_NAME="__APP_NAME__"
13 | export DEBUG="true"
14 | export APP_ENCODING="UTF-8"
15 | export APP_DEFAULT_LOCALE="en_US"
16 | export SECURITY_SALT="__SALT__"
17 |
18 | export CACHE_DURATION="+2 minutes"
19 | export CACHE_DEFAULT_URL="file://tmp/cache/?prefix=${APP_NAME}_default&duration=${CACHE_DURATION}"
20 | export CACHE_CAKECORE_URL="file://tmp/cache/persistent?prefix=${APP_NAME}_cake_core&serialize=true&duration=${CACHE_DURATION}"
21 | export CACHE_CAKEMODEL_URL="file://tmp/cache/models?prefix=${APP_NAME}_cake_model&serialize=true&duration=${CACHE_DURATION}"
22 |
23 | export EMAIL_TRANSPORT_DEFAULT_URL=""
24 |
25 | export DATABASE_URL="mysql://my_app:secret@localhost/${APP_NAME}?encoding=utf8&timezone=UTC&cacheMetadata=true"eIdentifiers=false&persistent=false"
26 | export DATABASE_TEST_URL="mysql://my_app:secret@localhost/test_${APP_NAME}?encoding=utf8&timezone=UTC&cacheMetadata=true"eIdentifiers=false&persistent=false"
27 |
28 | # Uncomment these to define logging configuration via environment variables.
29 | #export LOG_DEBUG_URL="file://logs?levels[]=notice&levels[]=info&levels[]=debug&file=debug"
30 | #export LOG_ERROR_URL="file://logs?levels[]=warning&levels[]=error&levels[]=critical&levels[]=alert&levels[]=emergency&file=error"
31 |
--------------------------------------------------------------------------------
/tests/TestCase/Model/Table/UsersTableTest.php:
--------------------------------------------------------------------------------
1 | getTableLocator()->exists('Users') ? [] : ['className' => UsersTable::class];
40 | $this->Users = $this->getTableLocator()->get('Users', $config);
41 | }
42 |
43 | /**
44 | * tearDown method
45 | *
46 | * @return void
47 | */
48 | public function tearDown(): void
49 | {
50 | unset($this->Users);
51 |
52 | parent::tearDown();
53 | }
54 |
55 | /**
56 | * Test validationDefault method
57 | *
58 | * @return void
59 | */
60 | public function testValidationDefault(): void
61 | {
62 | $this->markTestIncomplete('Not implemented yet.');
63 | }
64 |
65 | /**
66 | * Test buildRules method
67 | *
68 | * @return void
69 | */
70 | public function testBuildRules(): void
71 | {
72 | $this->markTestIncomplete('Not implemented yet.');
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/TestCase/Model/Table/ArticlesTableTest.php:
--------------------------------------------------------------------------------
1 | getTableLocator()->exists('Articles') ? [] : ['className' => ArticlesTable::class];
41 | $this->Articles = $this->getTableLocator()->get('Articles', $config);
42 | }
43 |
44 | /**
45 | * tearDown method
46 | *
47 | * @return void
48 | */
49 | public function tearDown(): void
50 | {
51 | unset($this->Articles);
52 |
53 | parent::tearDown();
54 | }
55 |
56 | /**
57 | * Test validationDefault method
58 | *
59 | * @return void
60 | */
61 | public function testValidationDefault(): void
62 | {
63 | $this->markTestIncomplete('Not implemented yet.');
64 | }
65 |
66 | /**
67 | * Test buildRules method
68 | *
69 | * @return void
70 | */
71 | public function testBuildRules(): void
72 | {
73 | $this->markTestIncomplete('Not implemented yet.');
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/TestCase/Controller/TagsControllerTest.php:
--------------------------------------------------------------------------------
1 | markTestIncomplete('Not implemented yet.');
38 | }
39 |
40 | /**
41 | * Test view method
42 | *
43 | * @return void
44 | */
45 | public function testView(): void
46 | {
47 | $this->markTestIncomplete('Not implemented yet.');
48 | }
49 |
50 | /**
51 | * Test add method
52 | *
53 | * @return void
54 | */
55 | public function testAdd(): void
56 | {
57 | $this->markTestIncomplete('Not implemented yet.');
58 | }
59 |
60 | /**
61 | * Test edit method
62 | *
63 | * @return void
64 | */
65 | public function testEdit(): void
66 | {
67 | $this->markTestIncomplete('Not implemented yet.');
68 | }
69 |
70 | /**
71 | * Test delete method
72 | *
73 | * @return void
74 | */
75 | public function testDelete(): void
76 | {
77 | $this->markTestIncomplete('Not implemented yet.');
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/config/requirements.php:
--------------------------------------------------------------------------------
1 | = 50.1 is needed to use CakePHP. Please update the `libicu` package of your system.' . PHP_EOL, E_USER_ERROR);
39 | }
40 |
41 | /*
42 | * You can remove this if you are confident you have mbstring installed.
43 | */
44 | if (!extension_loaded('mbstring')) {
45 | trigger_error('You must enable the mbstring extension to use CakePHP.', E_USER_ERROR);
46 | }
47 |
--------------------------------------------------------------------------------
/src/Controller/ErrorController.php:
--------------------------------------------------------------------------------
1 | viewBuilder()->setTemplatePath('Error');
47 | }
48 |
49 | /**
50 | * afterFilter callback.
51 | *
52 | * @param \Cake\Event\Event $event Event.
53 | * @return \Cake\Http\Response|null|void
54 | */
55 | public function afterFilter(\Cake\Event\EventInterface $event)
56 | {
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Fixture/TagsFixture.php:
--------------------------------------------------------------------------------
1 | ['type' => 'integer', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],
21 | 'title' => ['type' => 'string', 'length' => 191, 'null' => true, 'default' => null, 'collate' => 'utf8mb4_general_ci', 'comment' => '', 'precision' => null],
22 | 'created' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => true, 'default' => null, 'comment' => ''],
23 | 'modified' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => true, 'default' => null, 'comment' => ''],
24 | '_constraints' => [
25 | 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
26 | 'title' => ['type' => 'unique', 'columns' => ['title'], 'length' => []],
27 | ],
28 | '_options' => [
29 | 'engine' => 'InnoDB',
30 | 'collation' => 'utf8mb4_general_ci'
31 | ],
32 | ];
33 | // phpcs:enable
34 | /**
35 | * Init method
36 | *
37 | * @return void
38 | */
39 | public function init(): void
40 | {
41 | $this->records = [
42 | [
43 | 'id' => 1,
44 | 'title' => 'Lorem ipsum dolor sit amet',
45 | 'created' => '2020-08-12 02:48:16',
46 | 'modified' => '2020-08-12 02:48:16',
47 | ],
48 | ];
49 | parent::init();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Fixture/ArticlesTagsFixture.php:
--------------------------------------------------------------------------------
1 | ['type' => 'integer', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null],
21 | 'tag_id' => ['type' => 'integer', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null],
22 | '_indexes' => [
23 | 'tag_key' => ['type' => 'index', 'columns' => ['tag_id'], 'length' => []],
24 | ],
25 | '_constraints' => [
26 | 'primary' => ['type' => 'primary', 'columns' => ['article_id', 'tag_id'], 'length' => []],
27 | 'articles_tags_ibfk_2' => ['type' => 'foreign', 'columns' => ['article_id'], 'references' => ['articles', 'id'], 'update' => 'restrict', 'delete' => 'restrict', 'length' => []],
28 | 'articles_tags_ibfk_1' => ['type' => 'foreign', 'columns' => ['tag_id'], 'references' => ['tags', 'id'], 'update' => 'restrict', 'delete' => 'restrict', 'length' => []],
29 | ],
30 | '_options' => [
31 | 'engine' => 'InnoDB',
32 | 'collation' => 'latin1_swedish_ci'
33 | ],
34 | ];
35 | // phpcs:enable
36 | /**
37 | * Init method
38 | *
39 | * @return void
40 | */
41 | public function init(): void
42 | {
43 | $this->records = [
44 | [
45 | 'article_id' => 1,
46 | 'tag_id' => 1,
47 | ],
48 | ];
49 | parent::init();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Controller/AppController.php:
--------------------------------------------------------------------------------
1 | loadComponent('Security');`
37 | *
38 | * @return void
39 | */
40 | public function initialize(): void
41 | {
42 | parent::initialize();
43 | $this->loadComponent('Flash');
44 | $this->loadComponent('Authentication.Authentication');
45 | $this->loadComponent('Authorization.Authorization');
46 | }
47 |
48 | public function beforeFilter(EventInterface $event)
49 | {
50 | parent::beforeFilter($event);
51 | // for all controllers in our application, make index and view
52 | // actions public, skipping the authentication check
53 | $this->Authentication->addUnauthenticatedActions(['index', 'view']);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Fixture/UsersFixture.php:
--------------------------------------------------------------------------------
1 | ['type' => 'integer', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],
21 | 'email' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'collate' => 'latin1_swedish_ci', 'comment' => '', 'precision' => null],
22 | 'password' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'collate' => 'latin1_swedish_ci', 'comment' => '', 'precision' => null],
23 | 'created' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => true, 'default' => null, 'comment' => ''],
24 | 'modified' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => true, 'default' => null, 'comment' => ''],
25 | '_constraints' => [
26 | 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
27 | ],
28 | '_options' => [
29 | 'engine' => 'InnoDB',
30 | 'collation' => 'latin1_swedish_ci'
31 | ],
32 | ];
33 | // phpcs:enable
34 | /**
35 | * Init method
36 | *
37 | * @return void
38 | */
39 | public function init(): void
40 | {
41 | $this->records = [
42 | [
43 | 'id' => 1,
44 | 'email' => 'Lorem ipsum dolor sit amet',
45 | 'password' => 'Lorem ipsum dolor sit amet',
46 | 'created' => '2020-08-12 02:42:27',
47 | 'modified' => '2020-08-12 02:42:27',
48 | ],
49 | ];
50 | parent::init();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/templates/layout/default.php:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 | = $this->Html->charset() ?>
22 |
23 |
24 | = $cakeDescription ?>:
25 | = $this->fetch('title') ?>
26 |
27 | = $this->Html->meta('icon') ?>
28 |
29 | = $this->Html->css('base.css') ?>
30 | = $this->Html->css('cake.css') ?>
31 |
32 | = $this->fetch('meta') ?>
33 | = $this->fetch('css') ?>
34 | = $this->fetch('script') ?>
35 |
36 |
37 |
38 |
43 |
49 |
50 | = $this->Flash->render() ?>
51 |
52 | = $this->fetch('content') ?>
53 |
54 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/Model/Entity/Article.php:
--------------------------------------------------------------------------------
1 | true,
38 | 'title' => true,
39 | 'slug' => true,
40 | 'body' => true,
41 | 'published' => true,
42 | 'created' => true,
43 | 'modified' => true,
44 | 'user' => true,
45 | 'tags' => true,
46 | 'tag_string' => true,
47 | ];
48 |
49 | protected function _setTitle(string $title): string
50 | {
51 | $this->slug = Text::slug($title);
52 |
53 | return $title;
54 | }
55 |
56 | protected function _getTagString(): string
57 | {
58 | if (isset($this->_fields['tag_string'])) {
59 | return $this->_fields['tag_string'];
60 | }
61 | if (empty($this->tags)) {
62 | return '';
63 | }
64 | $tags = new Collection($this->tags);
65 | $str = $tags->reduce(function ($string, $tag) {
66 | return $string . $tag->title . ', ';
67 | }, '');
68 |
69 | return trim($str, ', ');
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/templates/Tags/index.php:
--------------------------------------------------------------------------------
1 |
7 |
8 | = $this->Html->link(__('New Tag'), ['action' => 'add'], ['class' => 'button float-right']) ?>
9 |
= __('Tags') ?>
10 |
11 |
12 |
13 |
14 | = $this->Paginator->sort('id') ?>
15 | = $this->Paginator->sort('title') ?>
16 | = $this->Paginator->sort('created') ?>
17 | = $this->Paginator->sort('modified') ?>
18 | = __('Actions') ?>
19 |
20 |
21 |
22 |
23 |
24 | = $this->Number->format($tag->id) ?>
25 | = h($tag->title) ?>
26 | = h($tag->created) ?>
27 | = h($tag->modified) ?>
28 |
29 | = $this->Html->link(__('View'), ['action' => 'view', $tag->id]) ?>
30 | = $this->Html->link(__('Edit'), ['action' => 'edit', $tag->id]) ?>
31 | = $this->Form->postLink(__('Delete'), ['action' => 'delete', $tag->id], ['confirm' => __('Are you sure you want to delete # {0}?', $tag->id)]) ?>
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
46 |
= $this->Paginator->counter(__('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')) ?>
47 |
48 |
49 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cakephp/app",
3 | "description": "CakePHP skeleton app",
4 | "homepage": "https://cakephp.org",
5 | "type": "project",
6 | "license": "MIT",
7 | "require": {
8 | "php": ">=8.1",
9 | "cakephp/cakephp": "^5.0",
10 | "cakephp/migrations": "~4.0",
11 | "cakephp/authorization": "~3.0",
12 | "cakephp/authentication": "~3.0",
13 | "cakephp/plugin-installer": "~2.0",
14 | "josegonzalez/dotenv": "2.*"
15 | },
16 | "require-dev": {
17 | "psy/psysh": "@stable",
18 | "cakephp/debug_kit": "^5.0",
19 | "cakephp/bake": "^3.0",
20 | "cakephp/repl": "^2.0",
21 | "cakephp/cakephp-codesniffer": "^5.0"
22 | },
23 | "suggest": {
24 | "markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.",
25 | "dereuromark/cakephp-ide-helper": "After baking your code, this keeps your annotations in sync with the code evolving from there on for maximum IDE and PHPStan compatibility.",
26 | "phpunit/phpunit": "Allows automated tests to be run without system-wide install.",
27 | "cakephp/cakephp-codesniffer": "Allows to check the code against the coding standards used in CakePHP."
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "App\\": "src"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "App\\Test\\": "tests",
37 | "Cake\\Test\\": "./vendor/cakephp/cakephp/tests"
38 | }
39 | },
40 | "scripts": {
41 | "post-install-cmd": "App\\Console\\Installer::postInstall",
42 | "post-create-project-cmd": "App\\Console\\Installer::postInstall",
43 | "check": [
44 | "@test",
45 | "@cs-check"
46 | ],
47 | "cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
48 | "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
49 | "stan": "phpstan analyse src/",
50 | "test": "phpunit --colors=always"
51 | },
52 | "minimum-stability": "dev",
53 | "prefer-stable": true,
54 | "config": {
55 | "allow-plugins": {
56 | "dealerdirect/phpcodesniffer-composer-installer": true,
57 | "cakephp/plugin-installer": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/config/routes.php:
--------------------------------------------------------------------------------
1 | scope('/', function (RouteBuilder $routes) {
48 | // Register scoped middleware for in scopes.
49 | $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware([
50 | 'httponly' => true
51 | ]));
52 | $routes->applyMiddleware('csrf');
53 | $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
54 | $routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
55 |
56 |
57 | // New route we're adding for our tagged action.
58 | // The trailing `*` tells CakePHP that this action has
59 | // passed parameters.
60 | $routes->scope('/articles', function ($routes) {
61 | $routes->connect('/tagged/*', ['controller' => 'Articles', 'action' => 'tags']);
62 | });
63 |
64 | /**
65 | * Connect catchall routes for all controllers.
66 | */
67 | $routes->fallbacks(DashedRoute::class);
68 | });
69 |
--------------------------------------------------------------------------------
/config/app_local.example.php:
--------------------------------------------------------------------------------
1 | filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),
22 |
23 | /*
24 | * Security and encryption configuration
25 | *
26 | * - salt - A random string used in security hashing methods.
27 | * The salt value is also used as the encryption key.
28 | * You should treat it as extremely sensitive data.
29 | */
30 | 'Security' => [
31 | 'salt' => env('SECURITY_SALT', '__SALT__'),
32 | ],
33 |
34 | /*
35 | * Connection information used by the ORM to connect
36 | * to your application's datastores.
37 | *
38 | * See app.php for more configuration options.
39 | */
40 | 'Datasources' => [
41 | 'default' => [
42 | 'driver' => Cake\Database\Driver\Sqlite::class,
43 | 'database' => ROOT . DS . 'cms-tutorial-database.sqlite',
44 | 'encoding' => 'utf8',
45 | 'url' => env('DATABASE_URL'),
46 | ],
47 |
48 | /*
49 | * The test connection is used during the test suite.
50 | */
51 | 'test' => [
52 | 'host' => 'localhost',
53 | //'port' => 'non_standard_port_number',
54 | 'username' => 'my_app',
55 | 'password' => 'secret',
56 | 'database' => 'test_myapp',
57 | //'schema' => 'myapp',
58 | 'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tmp/tests.sqlite'),
59 | ],
60 | ],
61 |
62 | /*
63 | * Email configuration.
64 | *
65 | * Host and credential configuration in case you are using SmtpTransport
66 | *
67 | * See app.php for more configuration options.
68 | */
69 | 'EmailTransport' => [
70 | 'default' => [
71 | 'host' => 'localhost',
72 | 'port' => 25,
73 | 'username' => null,
74 | 'password' => null,
75 | 'client' => null,
76 | 'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
77 | ],
78 | ],
79 | ];
80 |
--------------------------------------------------------------------------------
/src/Controller/PagesController.php:
--------------------------------------------------------------------------------
1 | Authorization->skipAuthorization();
38 | }
39 |
40 | /**
41 | * Displays a view
42 | *
43 | * @param array ...$path Path segments.
44 | * @return \Cake\Http\Response|null
45 | * @throws \Cake\Network\Exception\ForbiddenException When a directory traversal attempt.
46 | * @throws \Cake\Network\Exception\NotFoundException When the view file could not
47 | * be found or \Cake\View\Exception\MissingTemplateException in debug mode.
48 | */
49 | public function display(...$path)
50 | {
51 | $count = count($path);
52 | if (!$count) {
53 | return $this->redirect('/');
54 | }
55 | if (in_array('..', $path, true) || in_array('.', $path, true)) {
56 | throw new ForbiddenException();
57 | }
58 | $page = $subpage = null;
59 |
60 | if (!empty($path[0])) {
61 | $page = $path[0];
62 | }
63 | if (!empty($path[1])) {
64 | $subpage = $path[1];
65 | }
66 | $this->set(compact('page', 'subpage'));
67 |
68 | try {
69 | $this->render(implode('/', $path));
70 | } catch (MissingTemplateException $exception) {
71 | if (Configure::read('debug')) {
72 | throw $exception;
73 | }
74 | throw new NotFoundException();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/config/paths.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | = __('Actions') ?>
10 | = $this->Html->link(__('New User'), ['action' => 'add']) ?>
11 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
12 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
13 |
14 |
15 |
16 |
= __('Users') ?>
17 |
18 |
19 |
20 | = $this->Paginator->sort('id') ?>
21 | = $this->Paginator->sort('email') ?>
22 | = $this->Paginator->sort('password') ?>
23 | = $this->Paginator->sort('created') ?>
24 | = $this->Paginator->sort('modified') ?>
25 | = __('Actions') ?>
26 |
27 |
28 |
29 |
30 |
31 | = $this->Number->format($user->id) ?>
32 | = h($user->email) ?>
33 | = h($user->password) ?>
34 | = h($user->created) ?>
35 | = h($user->modified) ?>
36 |
37 | = $this->Html->link(__('View'), ['action' => 'view', $user->id]) ?>
38 | = $this->Html->link(__('Edit'), ['action' => 'edit', $user->id]) ?>
39 | = $this->Form->postLink(__('Delete'), ['action' => 'delete', $user->id], ['confirm' => __('Are you sure you want to delete # {0}?', $user->id)]) ?>
40 |
41 |
42 |
43 |
44 |
45 |
46 |
53 |
= $this->Paginator->counter(__('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')) ?>
54 |
55 |
56 |
--------------------------------------------------------------------------------
/tests/TestCase/Controller/PagesControllerTest.php:
--------------------------------------------------------------------------------
1 | get('/');
38 | $this->assertResponseOk();
39 | $this->get('/');
40 | $this->assertResponseOk();
41 | }
42 |
43 | /**
44 | * testDisplay method
45 | *
46 | * @return void
47 | */
48 | public function testDisplay()
49 | {
50 | $this->get('/pages/home');
51 | $this->assertResponseOk();
52 | $this->assertResponseContains('CakePHP');
53 | $this->assertResponseContains('');
54 | }
55 |
56 | /**
57 | * Test that missing template renders 404 page in production
58 | *
59 | * @return void
60 | */
61 | public function testMissingTemplate()
62 | {
63 | Configure::write('debug', false);
64 | $this->get('/pages/not_existing');
65 |
66 | $this->assertResponseError();
67 | $this->assertResponseContains('Error');
68 | }
69 |
70 | /**
71 | * Test that missing template in debug mode renders missing_template error page
72 | *
73 | * @return void
74 | */
75 | public function testMissingTemplateInDebug()
76 | {
77 | Configure::write('debug', true);
78 | $this->get('/pages/not_existing');
79 |
80 | $this->assertResponseFailure();
81 | $this->assertResponseContains('Missing Template');
82 | $this->assertResponseContains('Stacktrace');
83 | $this->assertResponseContains('not_existing.php');
84 | }
85 |
86 | /**
87 | * Test directory traversal protection
88 | *
89 | * @return void
90 | */
91 | public function testDirectoryTraversalProtection()
92 | {
93 | $this->get('/pages/../Layout/ajax');
94 | $this->assertResponseCode(403);
95 | $this->assertResponseContains('Forbidden');
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Model/Table/ArticlesTagsTable.php:
--------------------------------------------------------------------------------
1 | setTable('articles_tags');
44 | $this->setDisplayField('article_id');
45 | $this->setPrimaryKey(['article_id', 'tag_id']);
46 |
47 | $this->belongsTo('Articles', [
48 | 'foreignKey' => 'article_id',
49 | 'joinType' => 'INNER',
50 | ]);
51 | $this->belongsTo('Tags', [
52 | 'foreignKey' => 'tag_id',
53 | 'joinType' => 'INNER',
54 | ]);
55 | }
56 |
57 | /**
58 | * Returns a rules checker object that will be used for validating
59 | * application integrity.
60 | *
61 | * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
62 | * @return \Cake\ORM\RulesChecker
63 | */
64 | public function buildRules(RulesChecker $rules): RulesChecker
65 | {
66 | $rules->add($rules->existsIn(['article_id'], 'Articles'), ['errorField' => 'article_id']);
67 | $rules->add($rules->existsIn(['tag_id'], 'Tags'), ['errorField' => 'tag_id']);
68 |
69 | return $rules;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Fixture/ArticlesFixture.php:
--------------------------------------------------------------------------------
1 | ['type' => 'integer', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],
21 | 'user_id' => ['type' => 'integer', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null],
22 | 'title' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'collate' => 'utf8mb4_general_ci', 'comment' => '', 'precision' => null],
23 | 'slug' => ['type' => 'string', 'length' => 191, 'null' => false, 'default' => null, 'collate' => 'utf8mb4_general_ci', 'comment' => '', 'precision' => null],
24 | 'body' => ['type' => 'text', 'length' => null, 'null' => true, 'default' => null, 'collate' => 'utf8mb4_general_ci', 'comment' => '', 'precision' => null],
25 | 'published' => ['type' => 'boolean', 'length' => null, 'null' => true, 'default' => '0', 'comment' => '', 'precision' => null],
26 | 'created' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => true, 'default' => null, 'comment' => ''],
27 | 'modified' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => true, 'default' => null, 'comment' => ''],
28 | '_indexes' => [
29 | 'user_key' => ['type' => 'index', 'columns' => ['user_id'], 'length' => []],
30 | ],
31 | '_constraints' => [
32 | 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
33 | 'slug' => ['type' => 'unique', 'columns' => ['slug'], 'length' => []],
34 | 'articles_ibfk_1' => ['type' => 'foreign', 'columns' => ['user_id'], 'references' => ['users', 'id'], 'update' => 'restrict', 'delete' => 'restrict', 'length' => []],
35 | ],
36 | '_options' => [
37 | 'engine' => 'InnoDB',
38 | 'collation' => 'utf8mb4_general_ci'
39 | ],
40 | ];
41 | // phpcs:enable
42 | /**
43 | * Init method
44 | *
45 | * @return void
46 | */
47 | public function init(): void
48 | {
49 | $this->records = [
50 | [
51 | 'id' => 1,
52 | 'user_id' => 1,
53 | 'title' => 'Lorem ipsum dolor sit amet',
54 | 'slug' => 'Lorem ipsum dolor sit amet',
55 | 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.',
56 | 'published' => 1,
57 | 'created' => '2020-08-12 02:42:19',
58 | 'modified' => '2020-08-12 02:42:19',
59 | ],
60 | ];
61 | parent::init();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Model/Table/TagsTable.php:
--------------------------------------------------------------------------------
1 | setTable('tags');
45 | $this->setDisplayField('title');
46 | $this->setPrimaryKey('id');
47 |
48 | $this->addBehavior('Timestamp');
49 |
50 | $this->belongsToMany('Articles', [
51 | 'foreignKey' => 'tag_id',
52 | 'targetForeignKey' => 'article_id',
53 | 'joinTable' => 'articles_tags',
54 | ]);
55 | }
56 |
57 | /**
58 | * Default validation rules.
59 | *
60 | * @param \Cake\Validation\Validator $validator Validator instance.
61 | * @return \Cake\Validation\Validator
62 | */
63 | public function validationDefault(Validator $validator): Validator
64 | {
65 | $validator
66 | ->integer('id')
67 | ->allowEmptyString('id', null, 'create');
68 |
69 | $validator
70 | ->scalar('title')
71 | ->maxLength('title', 191)
72 | ->allowEmptyString('title')
73 | ->add('title', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
74 |
75 | return $validator;
76 | }
77 |
78 | /**
79 | * Returns a rules checker object that will be used for validating
80 | * application integrity.
81 | *
82 | * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
83 | * @return \Cake\ORM\RulesChecker
84 | */
85 | public function buildRules(RulesChecker $rules): RulesChecker
86 | {
87 | $rules->add($rules->isUnique(['title']), ['errorField' => 'title']);
88 |
89 | return $rules;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Model/Table/UsersTable.php:
--------------------------------------------------------------------------------
1 | setTable('users');
45 | $this->setDisplayField('id');
46 | $this->setPrimaryKey('id');
47 |
48 | $this->addBehavior('Timestamp');
49 |
50 | $this->hasMany('Articles', [
51 | 'foreignKey' => 'user_id',
52 | ]);
53 | }
54 |
55 | /**
56 | * Default validation rules.
57 | *
58 | * @param \Cake\Validation\Validator $validator Validator instance.
59 | * @return \Cake\Validation\Validator
60 | */
61 | public function validationDefault(Validator $validator): Validator
62 | {
63 | $validator
64 | ->integer('id')
65 | ->allowEmptyString('id', null, 'create');
66 |
67 | $validator
68 | ->email('email')
69 | ->requirePresence('email', 'create')
70 | ->notEmptyString('email');
71 |
72 | $validator
73 | ->scalar('password')
74 | ->maxLength('password', 255)
75 | ->requirePresence('password', 'create')
76 | ->notEmptyString('password');
77 |
78 | return $validator;
79 | }
80 |
81 | /**
82 | * Returns a rules checker object that will be used for validating
83 | * application integrity.
84 | *
85 | * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
86 | * @return \Cake\ORM\RulesChecker
87 | */
88 | public function buildRules(RulesChecker $rules): RulesChecker
89 | {
90 | $rules->add($rules->isUnique(['email']), ['errorField' => 'email']);
91 |
92 | return $rules;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Controller/ArticlesController.php:
--------------------------------------------------------------------------------
1 | Authorization->skipAuthorization();
13 | $articles = $this->paginate($this->Articles->find());
14 | $this->set(compact('articles'));
15 | }
16 |
17 | public function view($slug)
18 | {
19 | $this->Authorization->skipAuthorization();
20 | $article = $this->Articles
21 | ->findBySlug($slug)
22 | ->contain('Tags')
23 | ->firstOrFail();
24 | $this->set(compact('article'));
25 | }
26 |
27 | public function add()
28 | {
29 | $article = $this->Articles->newEmptyEntity();
30 | $this->Authorization->authorize($article);
31 |
32 | if ($this->request->is('post')) {
33 | $article = $this->Articles->patchEntity($article, $this->request->getData());
34 |
35 | // Added: Set the user_id from the session.
36 | $article->user_id = $this->request->getAttribute('identity')->getIdentifier();
37 |
38 | if ($this->Articles->save($article)) {
39 | $this->Flash->success(__('Your article has been saved.'));
40 | return $this->redirect(['action' => 'index']);
41 | }
42 | $this->Flash->error(__('Unable to add your article.'));
43 | }
44 | $this->set('article', $article);
45 | }
46 |
47 | public function edit($slug)
48 | {
49 | $article = $this->Articles
50 | ->findBySlug($slug)
51 | ->contain('Tags') // load associated Tags
52 | ->firstOrFail();
53 | $this->Authorization->authorize($article);
54 |
55 | if ($this->request->is(['post', 'put'])) {
56 | $this->Articles->patchEntity($article, $this->request->getData(), [
57 | // Added: Disable modification of user_id.
58 | 'accessibleFields' => ['user_id' => false]
59 | ]);
60 | if ($this->Articles->save($article)) {
61 | $this->Flash->success(__('Your article has been updated.'));
62 | return $this->redirect(['action' => 'index']);
63 | }
64 | $this->Flash->error(__('Unable to update your article.'));
65 | }
66 |
67 | // Get a list of tags.
68 | $tags = $this->Articles->Tags->find('list');
69 |
70 | // Set article & tags to the view context
71 | $this->set('tags', $tags);
72 | $this->set('article', $article);
73 | }
74 |
75 | public function delete($slug)
76 | {
77 | $this->request->allowMethod(['post', 'delete']);
78 |
79 | $article = $this->Articles->findBySlug($slug)->firstOrFail();
80 | $this->Authorization->authorize($article);
81 |
82 | if ($this->Articles->delete($article)) {
83 | $this->Flash->success(__('The {0} article has been deleted.', $article->title));
84 | return $this->redirect(['action' => 'index']);
85 | }
86 | }
87 |
88 | public function tags(array $tags = [])
89 | {
90 | $this->Authorization->skipAuthorization();
91 |
92 | // Use the ArticlesTable to find tagged articles.
93 | $articles = $this->Articles->find('tagged', tags: $tags);
94 |
95 | // Pass variables into the view template context.
96 | $this->set([
97 | 'articles' => $articles,
98 | 'tags' => $tags
99 | ]);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/webroot/img/cake.logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
10 |
12 |
13 |
16 |
21 |
22 |
24 |
25 |
29 |
32 |
33 |
35 |
36 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/Controller/TagsController.php:
--------------------------------------------------------------------------------
1 | paginate($this->Tags);
22 |
23 | $this->set(compact('tags'));
24 | }
25 |
26 | /**
27 | * View method
28 | *
29 | * @param string|null $id Tag id.
30 | * @return \Cake\Http\Response|null|void Renders view
31 | * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
32 | */
33 | public function view($id = null)
34 | {
35 | $tag = $this->Tags->get($id, contain: ['Articles']);
36 |
37 | $this->set(compact('tag'));
38 | }
39 |
40 | /**
41 | * Add method
42 | *
43 | * @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
44 | */
45 | public function add()
46 | {
47 | $tag = $this->Tags->newEmptyEntity();
48 | if ($this->request->is('post')) {
49 | $tag = $this->Tags->patchEntity($tag, $this->request->getData());
50 | if ($this->Tags->save($tag)) {
51 | $this->Flash->success(__('The tag has been saved.'));
52 |
53 | return $this->redirect(['action' => 'index']);
54 | }
55 | $this->Flash->error(__('The tag could not be saved. Please, try again.'));
56 | }
57 | $articles = $this->Tags->Articles->find('list', ['limit' => 200]);
58 | $this->set(compact('tag', 'articles'));
59 | }
60 |
61 | /**
62 | * Edit method
63 | *
64 | * @param string|null $id Tag id.
65 | * @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
66 | * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
67 | */
68 | public function edit($id = null)
69 | {
70 | $tag = $this->Tags->get($id, contain: ['Articles']);
71 | if ($this->request->is(['patch', 'post', 'put'])) {
72 | $tag = $this->Tags->patchEntity($tag, $this->request->getData());
73 | if ($this->Tags->save($tag)) {
74 | $this->Flash->success(__('The tag has been saved.'));
75 |
76 | return $this->redirect(['action' => 'index']);
77 | }
78 | $this->Flash->error(__('The tag could not be saved. Please, try again.'));
79 | }
80 | $articles = $this->Tags->Articles->find('list', ['limit' => 200]);
81 | $this->set(compact('tag', 'articles'));
82 | }
83 |
84 | /**
85 | * Delete method
86 | *
87 | * @param string|null $id Tag id.
88 | * @return \Cake\Http\Response|null|void Redirects to index.
89 | * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
90 | */
91 | public function delete($id = null)
92 | {
93 | $this->request->allowMethod(['post', 'delete']);
94 | $tag = $this->Tags->get($id);
95 | if ($this->Tags->delete($tag)) {
96 | $this->Flash->success(__('The tag has been deleted.'));
97 | } else {
98 | $this->Flash->error(__('The tag could not be deleted. Please, try again.'));
99 | }
100 |
101 | return $this->redirect(['action' => 'index']);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/templates/Users/view.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | = __('Actions') ?>
10 | = $this->Html->link(__('Edit User'), ['action' => 'edit', $user->id]) ?>
11 | = $this->Form->postLink(__('Delete User'), ['action' => 'delete', $user->id], ['confirm' => __('Are you sure you want to delete # {0}?', $user->id)]) ?>
12 | = $this->Html->link(__('List Users'), ['action' => 'index']) ?>
13 | = $this->Html->link(__('New User'), ['action' => 'add']) ?>
14 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
15 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
16 |
17 |
18 |
19 |
= h($user->id) ?>
20 |
21 |
22 | = __('Email') ?>
23 | = h($user->email) ?>
24 |
25 |
26 | = __('Password') ?>
27 | = h($user->password) ?>
28 |
29 |
30 | = __('Id') ?>
31 | = $this->Number->format($user->id) ?>
32 |
33 |
34 | = __('Created') ?>
35 | = h($user->created) ?>
36 |
37 |
38 | = __('Modified') ?>
39 | = h($user->modified) ?>
40 |
41 |
42 |
77 |
78 |
--------------------------------------------------------------------------------
/templates/Tags/view.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
= __('Actions') ?>
11 | = $this->Html->link(__('Edit Tag'), ['action' => 'edit', $tag->id], ['class' => 'side-nav-item']) ?>
12 | = $this->Form->postLink(__('Delete Tag'), ['action' => 'delete', $tag->id], ['confirm' => __('Are you sure you want to delete # {0}?', $tag->id), 'class' => 'side-nav-item']) ?>
13 | = $this->Html->link(__('List Tags'), ['action' => 'index'], ['class' => 'side-nav-item']) ?>
14 | = $this->Html->link(__('New Tag'), ['action' => 'add'], ['class' => 'side-nav-item']) ?>
15 |
16 |
17 |
18 |
19 |
= h($tag->title) ?>
20 |
21 |
22 | = __('Title') ?>
23 | = h($tag->title) ?>
24 |
25 |
26 | = __('Id') ?>
27 | = $this->Number->format($tag->id) ?>
28 |
29 |
30 | = __('Created') ?>
31 | = h($tag->created) ?>
32 |
33 |
34 | = __('Modified') ?>
35 | = h($tag->modified) ?>
36 |
37 |
38 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/Controller/UsersController.php:
--------------------------------------------------------------------------------
1 | Authentication->addUnauthenticatedActions(['login', 'add']);
21 | }
22 |
23 | /**
24 | * Index method
25 | *
26 | * @return \Cake\Http\Response|void
27 | */
28 | public function index()
29 | {
30 | $users = $this->paginate($this->Users);
31 |
32 | $this->set(compact('users'));
33 | $this->set('_serialize', ['users']);
34 | }
35 |
36 | public function login() {
37 | $this->Authorization->skipAuthorization();
38 |
39 | $this->request->allowMethod(['get', 'post']);
40 | $result = $this->Authentication->getResult();
41 | // regardless of POST or GET, redirect if user is logged in
42 | if ($result->isValid()) {
43 | // redirect to /articles after login success
44 | $redirect = $this->request->getQuery('redirect', [
45 | 'controller' => 'Articles',
46 | 'action' => 'index',
47 | ]);
48 |
49 | return $this->redirect($redirect);
50 | }
51 | // display error if user submitted and authentication failed
52 | if ($this->request->is('post') && !$result->isValid()) {
53 | $this->Flash->error(__('Invalid username or password'));
54 | }
55 | }
56 |
57 | public function logout()
58 | {
59 | $this->Authorization->skipAuthorization();
60 |
61 | $result = $this->Authentication->getResult();
62 | // regardless of POST or GET, redirect if user is logged in
63 | if ($result->isValid()) {
64 | $this->Authentication->logout();
65 | return $this->redirect(['controller' => 'Users', 'action' => 'login']);
66 | }
67 | }
68 |
69 | /**
70 | * View method
71 | *
72 | * @param string|null $id User id.
73 | * @return \Cake\Http\Response|void
74 | * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
75 | */
76 | public function view($id = null)
77 | {
78 | $user = $this->Users->get($id, contain: ['Articles']);
79 |
80 | $this->set('user', $user);
81 | $this->set('_serialize', ['user']);
82 | }
83 |
84 | /**
85 | * Add method
86 | *
87 | * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise.
88 | */
89 | public function add()
90 | {
91 | $this->Authorization->skipAuthorization();
92 |
93 | $user = $this->Users->newEmptyEntity();
94 | if ($this->request->is('post')) {
95 | $user = $this->Users->patchEntity($user, $this->request->getData());
96 | if ($this->Users->save($user)) {
97 | $this->Flash->success(__('The user has been saved.'));
98 |
99 | return $this->redirect(['action' => 'login']);
100 | }
101 | $this->Flash->error(__('The user could not be saved. Please, try again.'));
102 | }
103 | $this->set(compact('user'));
104 | $this->set('_serialize', ['user']);
105 | }
106 |
107 | /**
108 | * Edit method
109 | *
110 | * @param string|null $id User id.
111 | * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise.
112 | * @throws \Cake\Network\Exception\NotFoundException When record not found.
113 | */
114 | public function edit($id = null)
115 | {
116 | $user = $this->Users->get($id, contain: []);
117 | if ($this->request->is(['patch', 'post', 'put'])) {
118 | $user = $this->Users->patchEntity($user, $this->request->getData());
119 | if ($this->Users->save($user)) {
120 | $this->Flash->success(__('The user has been saved.'));
121 |
122 | return $this->redirect(['action' => 'index']);
123 | }
124 | $this->Flash->error(__('The user could not be saved. Please, try again.'));
125 | }
126 | $this->set(compact('user'));
127 | $this->set('_serialize', ['user']);
128 | }
129 |
130 | /**
131 | * Delete method
132 | *
133 | * @param string|null $id User id.
134 | * @return \Cake\Http\Response|null Redirects to index.
135 | * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
136 | */
137 | public function delete($id = null)
138 | {
139 | $this->request->allowMethod(['post', 'delete']);
140 | $user = $this->Users->get($id);
141 | if ($this->Users->delete($user)) {
142 | $this->Flash->success(__('The user has been deleted.'));
143 | } else {
144 | $this->Flash->error(__('The user could not be deleted. Please, try again.'));
145 | }
146 |
147 | return $this->redirect(['action' => 'index']);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/webroot/css/home.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'cakefont';
3 | src: url('../font/cakedingbats-webfont.eot');
4 | src: url('../font/cakedingbats-webfont.eot?#iefix') format('embedded-opentype'),
5 | url('../font/cakedingbats-webfont.woff2') format('woff2'),
6 | url('../font/cakedingbats-webfont.woff') format('woff'),
7 | url('../font/cakedingbats-webfont.ttf') format('truetype'),
8 | url('../font/cakedingbats-webfont.svg#cake_dingbatsregular') format('svg');
9 | font-weight: normal;
10 | font-style: normal;
11 | }
12 |
13 | .home {
14 | font-family: 'Roboto', sans-serif;
15 | font-size: 14px;
16 | line-height: 27px;
17 | color: #404041;
18 | }
19 |
20 | a {
21 | color: #0071BC;
22 | -webkit-transition: all 0.2s;
23 | -moz-transition: all 0.2s;
24 | -ms-transition: all 0.2s;
25 | -o-transition: all 0.2s;
26 | transition: all 0.2s;
27 | }
28 |
29 | a:hover, a:active {
30 | color: #d33d44;
31 | -webkit-transition: all 0.2s;
32 | -moz-transition: all 0.2s;
33 | -ms-transition: all 0.2s;
34 | -o-transition: all 0.2s;
35 | transition: all 0.2s;
36 | }
37 |
38 | ul, ol, dl, p {
39 | font-size: 0.85rem;
40 | }
41 |
42 | p {
43 | line-height: 2;
44 | }
45 |
46 | header {
47 | height: auto;
48 | line-height: 1em;
49 | padding: 0;
50 | box-shadow: none;
51 | }
52 |
53 | header.row {
54 | margin-bottom: 30px;
55 | }
56 |
57 | header .header-image {
58 | text-align: center;
59 | padding: 64px 0;
60 | }
61 |
62 | header .header-title {
63 | padding: 0;
64 | display: block;
65 | background: #404041;
66 | text-align: center;
67 | }
68 |
69 | header .header-title h1 {
70 | font-family: 'Raleway', sans-serif;
71 | margin: 0;
72 | font-style: italic;
73 | font-size: 18px;
74 | font-weight: 500;
75 | padding: 18px 30px;
76 | color: #DEDED5;
77 | }
78 |
79 | header h1 {
80 | color: #fff;
81 | }
82 |
83 | h3, h4 {
84 | font-family: 'Roboto', sans-serif;
85 | font-size: 27px;
86 | line-height: 30px;
87 | font-weight: 300;
88 | -webkit-font-smoothing: antialiased;
89 | margin-top: 0;
90 | margin-bottom: 20px;
91 | }
92 |
93 | .more {
94 | color: #ffffff;
95 | background-color: #d33d44;
96 | padding: 15px;
97 | margin-top: 10px;
98 | }
99 |
100 | .row {
101 | max-width: 1000px;
102 | }
103 |
104 | .alert {
105 | background-color: #fff9e1;
106 | font-size: 12px;
107 | text-align: center;
108 | display: block;
109 | padding: 12px;
110 | border-bottom: 2px solid #ffcf06;
111 | }
112 |
113 | .alert {
114 | background-color: #fff9e1;
115 | font-size: 12px;
116 | display: block;
117 | padding: 12px;
118 | border-bottom: 2px solid #ffcf06;
119 | margin-bottom: 30px;
120 | color: #404041;
121 | }
122 |
123 | .alert p {
124 | margin: 0;
125 | font-size: 12px;
126 | line-height: 1.4;
127 | }
128 |
129 | .alert p:before {
130 | color: #ffcf06;
131 | content: "\0055";
132 | font-family: 'cakefont', sans-serif;
133 | font-size: 21px;
134 | margin-left: -0.8em;
135 | width: 2.3em;
136 | -webkit-font-smoothing: antialiased;
137 | -moz-osx-font-smoothing: grayscale;
138 | padding: 0 10px 0 15px;
139 | vertical-align: -2px;
140 | }
141 |
142 | .alert ul {
143 | margin: 0;
144 | font-size: 12px;
145 | }
146 |
147 | .alert.url-rewriting {
148 | background-color: #F0F0F0;
149 | border-color: #cccccc;
150 | display: none;
151 | }
152 |
153 | .text-center {
154 | text-align: center;
155 | }
156 |
157 | ul {
158 | list-style-type: none;
159 | margin: 0 0 30px 0;
160 | }
161 |
162 | li {
163 | padding-left: 1.8em;
164 | }
165 |
166 | ul li ul, ul li ul li {
167 | margin: 0;
168 | padding: 0;
169 | }
170 |
171 | .bullet:before {
172 | font-family: 'cakefont', sans-serif;
173 | font-size: 18px;
174 | display: inline-block;
175 | margin-left: -1.3em;
176 | width: 1.2em;
177 | -webkit-font-smoothing: antialiased;
178 | -moz-osx-font-smoothing: grayscale;
179 | vertical-align: -1px;
180 | }
181 |
182 | .success:before {
183 | color: #88c671;
184 | content: "\0056";
185 | }
186 |
187 | .problem:before {
188 | color: #d33d44;
189 | content: "\0057";
190 | }
191 |
192 | .cutlery:before {
193 | color: #404041;
194 | content: "\0059";
195 | }
196 |
197 | .book:before {
198 | color: #404041;
199 | content: "\0042";
200 | width: 1.7em;
201 | }
202 |
203 | hr {
204 | border-bottom: 1px solid #e7e7e7;
205 | border-top: 0;
206 | margin-bottom: 35px;
207 | margin-left: 30px;
208 | margin-right: 30px;
209 | }
210 |
211 |
212 | .icon {
213 | color: #404041;
214 | font-style: normal;
215 | font-family: 'cakefont', sans-serif;
216 | -webkit-font-smoothing: antialiased;
217 | -moz-osx-font-smoothing: grayscale;
218 | }
219 | .icon.support {
220 | font-size: 60px;
221 | }
222 | .icon.docs {
223 | font-size: 57px;
224 | }
225 | .icon.training {
226 | font-size: 39px;
227 | }
228 |
229 | @media (min-width: 768px) {
230 | .columns {
231 | padding-left: 30px;
232 | padding-right: 30px;
233 | }
234 | }
235 |
236 | @media (min-width: 992px) {
237 | header.row {
238 | max-width: 940px;
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/config/bootstrap.php:
--------------------------------------------------------------------------------
1 | parse()
67 | // ->putenv()
68 | // ->toEnv()
69 | // ->toServer();
70 | // }
71 |
72 | /*
73 | * Read configuration file and inject configuration into various
74 | * CakePHP classes.
75 | *
76 | * By default there is only one configuration file. It is often a good
77 | * idea to create multiple configuration files, and separate the configuration
78 | * that changes from configuration that does not. This makes deployment simpler.
79 | */
80 | try {
81 | Configure::config('default', new PhpConfig());
82 | Configure::load('app', 'default', false);
83 | } catch (\Exception $e) {
84 | exit($e->getMessage() . "\n");
85 | }
86 |
87 | /*
88 | * Load an environment local configuration file to provide overrides to your configuration.
89 | * Notice: For security reasons app_local.php **should not** be included in your git repo.
90 | */
91 | if (file_exists(CONFIG . 'app_local.php')) {
92 | Configure::load('app_local', 'default');
93 | }
94 |
95 | /*
96 | * When debug = true the metadata cache should only last
97 | * for a short time.
98 | */
99 | if (Configure::read('debug')) {
100 | Configure::write('Cache._cake_model_.duration', '+2 minutes');
101 | Configure::write('Cache._cake_core_.duration', '+2 minutes');
102 | // disable router cache during development
103 | Configure::write('Cache._cake_routes_.duration', '+2 seconds');
104 | }
105 |
106 | /*
107 | * Set the default server timezone. Using UTC makes time calculations / conversions easier.
108 | * Check https://php.net/manual/en/timezones.php for list of valid timezone strings.
109 | */
110 | date_default_timezone_set(Configure::read('App.defaultTimezone'));
111 |
112 | /*
113 | * Configure the mbstring extension to use the correct encoding.
114 | */
115 | mb_internal_encoding(Configure::read('App.encoding'));
116 |
117 | /*
118 | * Set the default locale. This controls how dates, number and currency is
119 | * formatted and sets the default language to use for translations.
120 | */
121 | ini_set('intl.default_locale', Configure::read('App.defaultLocale'));
122 |
123 | /*
124 | * Register application error and exception handlers.
125 | */
126 | (new ErrorTrap())->register();
127 | (new ExceptionTrap())->register();
128 |
129 | /*
130 | * Include the CLI bootstrap overrides.
131 | */
132 | if (PHP_SAPI == 'cli') {
133 | require __DIR__ . '/bootstrap_cli.php';
134 | }
135 |
136 | /*
137 | * Set the full base URL.
138 | * This URL is used as the base of all absolute links.
139 | */
140 | $fullBaseUrl = Configure::read('App.fullBaseUrl');
141 | if (!$fullBaseUrl) {
142 | $s = null;
143 | if (env('HTTPS')) {
144 | $s = 's';
145 | }
146 |
147 | $httpHost = env('HTTP_HOST');
148 | if (isset($httpHost)) {
149 | $fullBaseUrl = 'http' . $s . '://' . $httpHost;
150 | }
151 | unset($httpHost, $s);
152 | }
153 | if ($fullBaseUrl) {
154 | Router::fullBaseUrl($fullBaseUrl);
155 | }
156 | unset($fullBaseUrl);
157 |
158 | Cache::setConfig(Configure::consume('Cache'));
159 | ConnectionManager::setConfig(Configure::consume('Datasources'));
160 | TransportFactory::setConfig(Configure::consume('EmailTransport'));
161 | Mailer::setConfig(Configure::consume('Email'));
162 | Log::setConfig(Configure::consume('Log'));
163 | Security::setSalt(Configure::consume('Security.salt'));
164 |
165 | /*
166 | * Setup detectors for mobile and tablet.
167 | */
168 | ServerRequest::addDetector('mobile', function ($request) {
169 | $detector = new \Detection\MobileDetect();
170 |
171 | return $detector->isMobile();
172 | });
173 | ServerRequest::addDetector('tablet', function ($request) {
174 | $detector = new \Detection\MobileDetect();
175 |
176 | return $detector->isTablet();
177 | });
178 |
--------------------------------------------------------------------------------
/src/Application.php:
--------------------------------------------------------------------------------
1 | bootstrapCli();
59 | }
60 |
61 | /*
62 | * Only try to load DebugKit in development mode
63 | * Debug Kit should not be installed on a production system
64 | */
65 | if (Configure::read('debug')) {
66 | $this->addPlugin('DebugKit');
67 | }
68 | $this->addPlugin('Authentication');
69 | $this->addPlugin('Authorization');
70 | }
71 |
72 | /**
73 | * Bootrapping for CLI application.
74 | *
75 | * That is when running commands.
76 | *
77 | * @return void
78 | */
79 | protected function bootstrapCli(): void
80 | {
81 | try {
82 | $this->addPlugin('Bake');
83 | } catch (MissingPluginException $e) {
84 | // Do not halt if the plugin is missing
85 | }
86 |
87 | $this->addPlugin('Migrations');
88 |
89 | // Load more plugins here
90 | }
91 |
92 | /**
93 | * Setup the middleware queue your application will use.
94 | *
95 | * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
96 | * @return \Cake\Http\MiddlewareQueue The updated middleware queue.
97 | */
98 | public function middleware($middlewareQueue): \Cake\Http\MiddlewareQueue
99 | {
100 | $middlewareQueue
101 | // Catch any exceptions in the lower layers,
102 | // and make an error page/response
103 | ->add(ErrorHandlerMiddleware::class)
104 |
105 | // Handle plugin/theme assets like CakePHP normally does.
106 | ->add(AssetMiddleware::class)
107 |
108 | // Add routing middleware.
109 | ->add(new RoutingMiddleware($this))
110 |
111 | // add Authentication after RoutingMiddleware
112 | ->add(new AuthenticationMiddleware($this))
113 |
114 | // Add authorization **after** authentication
115 | ->add(new AuthorizationMiddleware($this));
116 |
117 | if (Configure::read('debug')) {
118 | Configure::write('DebugKit.ignoreAuthorization', true);
119 | }
120 |
121 | return $middlewareQueue;
122 | }
123 |
124 | public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
125 | {
126 | $service = new AuthenticationService();
127 |
128 | // Define where users should be redirected to when they are not authenticated
129 | $service->setConfig([
130 | 'unauthenticatedRedirect' => [
131 | 'prefix' => false,
132 | 'plugin' => false,
133 | 'controller' => 'Users',
134 | 'action' => 'login',
135 | ],
136 | 'queryParam' => 'redirect',
137 | ]);
138 |
139 | // Define identifiers
140 | $fields = [
141 | AbstractIdentifier::CREDENTIAL_USERNAME => 'email',
142 | AbstractIdentifier::CREDENTIAL_PASSWORD => 'password'
143 | ];
144 | $passwordIdentifier = [
145 | 'Authentication.Password' => [
146 | 'fields' => $fields,
147 | ],
148 | ];
149 |
150 | // Load the authenticators. Session should be first.
151 | $service->loadAuthenticator('Authentication.Session', [
152 | 'identifier' => $passwordIdentifier,
153 | ]);
154 | $service->loadAuthenticator('Authentication.Form', [
155 | 'identifier' => $passwordIdentifier,
156 | 'fields' => $fields,
157 | 'loginUrl' => Router::url([
158 | 'prefix' => false,
159 | 'plugin' => null,
160 | 'controller' => 'Users',
161 | 'action' => 'login',
162 | ]),
163 | ]);
164 |
165 | return $service;
166 | }
167 |
168 | public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
169 | {
170 | $resolver = new OrmResolver();
171 |
172 | return new AuthorizationService($resolver);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/config/Migrations/20221126022125_InitialSchema.php:
--------------------------------------------------------------------------------
1 | table('articles')
18 | ->addColumn('user_id', 'integer', [
19 | 'default' => null,
20 | 'limit' => null,
21 | 'null' => false,
22 | 'signed' => false,
23 | ])
24 | ->addColumn('title', 'string', [
25 | 'default' => null,
26 | 'limit' => 255,
27 | 'null' => false,
28 | ])
29 | ->addColumn('slug', 'string', [
30 | 'default' => null,
31 | 'limit' => 191,
32 | 'null' => false,
33 | ])
34 | ->addColumn('body', 'text', [
35 | 'default' => null,
36 | 'limit' => null,
37 | 'null' => true,
38 | ])
39 | ->addColumn('published', 'boolean', [
40 | 'default' => false,
41 | 'limit' => null,
42 | 'null' => true,
43 | ])
44 | ->addColumn('created', 'datetime', [
45 | 'default' => null,
46 | 'limit' => null,
47 | 'null' => true,
48 | ])
49 | ->addColumn('modified', 'datetime', [
50 | 'default' => null,
51 | 'limit' => null,
52 | 'null' => true,
53 | ])
54 | ->addIndex(
55 | [
56 | 'slug',
57 | ],
58 | ['unique' => true]
59 | )
60 | ->addIndex(
61 | [
62 | 'user_id',
63 | ]
64 | )
65 | ->create();
66 |
67 | $this->table('articles_tags', ['id' => false, 'primary_key' => ['article_id', 'tag_id']])
68 | ->addColumn('article_id', 'integer', [
69 | 'default' => null,
70 | 'limit' => null,
71 | 'null' => false,
72 | 'signed' => false,
73 | ])
74 | ->addColumn('tag_id', 'integer', [
75 | 'default' => null,
76 | 'limit' => null,
77 | 'null' => false,
78 | 'signed' => false,
79 | ])
80 | ->addIndex(
81 | [
82 | 'tag_id',
83 | ]
84 | )
85 | ->addIndex(
86 | [
87 | 'article_id',
88 | ]
89 | )
90 | ->create();
91 |
92 | $this->table('tags')
93 | ->addColumn('title', 'string', [
94 | 'default' => null,
95 | 'limit' => 191,
96 | 'null' => true,
97 | ])
98 | ->addColumn('created', 'datetime', [
99 | 'default' => null,
100 | 'limit' => null,
101 | 'null' => true,
102 | ])
103 | ->addColumn('modified', 'datetime', [
104 | 'default' => null,
105 | 'limit' => null,
106 | 'null' => true,
107 | ])
108 | ->addIndex(
109 | [
110 | 'title',
111 | ],
112 | ['unique' => true]
113 | )
114 | ->create();
115 |
116 | $this->table('users')
117 | ->addColumn('email', 'string', [
118 | 'default' => null,
119 | 'limit' => 255,
120 | 'null' => false,
121 | ])
122 | ->addColumn('password', 'string', [
123 | 'default' => null,
124 | 'limit' => 255,
125 | 'null' => false,
126 | ])
127 | ->addColumn('created', 'datetime', [
128 | 'default' => null,
129 | 'limit' => null,
130 | 'null' => true,
131 | ])
132 | ->addColumn('modified', 'datetime', [
133 | 'default' => null,
134 | 'limit' => null,
135 | 'null' => true,
136 | ])
137 | ->create();
138 |
139 | $this->table('articles')
140 | ->addForeignKey(
141 | 'user_id',
142 | 'users',
143 | 'id',
144 | [
145 | 'update' => 'NO_ACTION',
146 | 'delete' => 'NO_ACTION',
147 | ]
148 | )
149 | ->update();
150 |
151 | $this->table('articles_tags')
152 | ->addForeignKey(
153 | 'tag_id',
154 | 'tags',
155 | 'id',
156 | [
157 | 'update' => 'NO_ACTION',
158 | 'delete' => 'NO_ACTION',
159 | ]
160 | )
161 | ->addForeignKey(
162 | 'article_id',
163 | 'articles',
164 | 'id',
165 | [
166 | 'update' => 'NO_ACTION',
167 | 'delete' => 'NO_ACTION',
168 | ]
169 | )
170 | ->update();
171 | }
172 |
173 | /**
174 | * Down Method.
175 | *
176 | * More information on this method is available here:
177 | * https://book.cakephp.org/phinx/0/en/migrations.html#the-down-method
178 | * @return void
179 | */
180 | public function down()
181 | {
182 | $this->table('articles')
183 | ->dropForeignKey(
184 | 'user_id'
185 | )->save();
186 |
187 | $this->table('articles_tags')
188 | ->dropForeignKey(
189 | 'tag_id'
190 | )
191 | ->dropForeignKey(
192 | 'article_id'
193 | )->save();
194 |
195 | $this->table('articles')->drop()->save();
196 | $this->table('articles_tags')->drop()->save();
197 | $this->table('tags')->drop()->save();
198 | $this->table('users')->drop()->save();
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/Model/Table/ArticlesTable.php:
--------------------------------------------------------------------------------
1 | setTable('articles');
46 | $this->setDisplayField('title');
47 | $this->setPrimaryKey('id');
48 |
49 | $this->addBehavior('Timestamp');
50 |
51 | $this->belongsTo('Users', [
52 | 'foreignKey' => 'user_id',
53 | 'joinType' => 'INNER',
54 | ]);
55 | $this->belongsToMany('Tags', [
56 | 'foreignKey' => 'article_id',
57 | 'targetForeignKey' => 'tag_id',
58 | 'joinTable' => 'articles_tags',
59 | ]);
60 | }
61 |
62 | /**
63 | * Default validation rules.
64 | *
65 | * @param \Cake\Validation\Validator $validator Validator instance.
66 | * @return \Cake\Validation\Validator
67 | */
68 | public function validationDefault(Validator $validator): Validator
69 | {
70 | $validator
71 | ->integer('id')
72 | ->allowEmptyString('id', null, 'create');
73 |
74 | $validator
75 | ->scalar('title')
76 | ->maxLength('title', 255)
77 | ->requirePresence('title', 'create')
78 | ->notEmptyString('title');
79 |
80 | $validator
81 | ->scalar('slug')
82 | ->maxLength('slug', 191)
83 | ->requirePresence('slug', 'create')
84 | ->notEmptyString('slug')
85 | ->add('slug', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
86 |
87 | $validator
88 | ->scalar('body')
89 | ->allowEmptyString('body');
90 |
91 | $validator
92 | ->boolean('published')
93 | ->allowEmptyString('published');
94 |
95 | return $validator;
96 | }
97 |
98 | /**
99 | * Returns a rules checker object that will be used for validating
100 | * application integrity.
101 | *
102 | * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
103 | * @return \Cake\ORM\RulesChecker
104 | */
105 | public function buildRules(RulesChecker $rules): RulesChecker
106 | {
107 | $rules->add($rules->isUnique(['slug']), ['errorField' => 'slug']);
108 | $rules->add($rules->existsIn(['user_id'], 'Users'), ['errorField' => 'user_id']);
109 |
110 | return $rules;
111 | }
112 |
113 | public function beforeSave(EventInterface $event, $entity, ArrayObject $options)
114 | {
115 | if ($entity->tag_string) {
116 | $entity->tags = $this->_buildTags($entity->tag_string);
117 | }
118 |
119 | // Other code
120 | }
121 |
122 | protected function _buildTags($tagString)
123 | {
124 | // Trim tags
125 | $newTags = array_map('trim', explode(',', $tagString));
126 | // Remove all empty tags
127 | $newTags = array_filter($newTags);
128 | // Reduce duplicated tags
129 | $newTags = array_unique($newTags);
130 |
131 | $out = [];
132 | $tags = $this->Tags->find()
133 | ->where(['Tags.title IN' => $newTags])
134 | ->all();
135 |
136 | // Remove existing tags from the list of new tags.
137 | foreach ($tags->extract('title') as $existing) {
138 | $index = array_search($existing, $newTags);
139 | if ($index !== false) {
140 | unset($newTags[$index]);
141 | }
142 | }
143 | // Add existing tags.
144 | foreach ($tags as $tag) {
145 | $out[] = $tag;
146 | }
147 | // Add new tags.
148 | foreach ($newTags as $tag) {
149 | $out[] = $this->Tags->newEntity(['title' => $tag]);
150 | }
151 | return $out;
152 | }
153 |
154 | // The $query argument is a query builder instance.
155 | // The $options array will contain the 'tags' option we passed
156 | // to find('tagged') in our controller action.
157 | public function findTagged(SelectQuery $query, array $tags = []): SelectQuery
158 | {
159 | $columns = [
160 | 'Articles.id', 'Articles.user_id', 'Articles.title',
161 | 'Articles.body', 'Articles.published', 'Articles.created',
162 | 'Articles.slug',
163 | ];
164 | $query = $query
165 | ->select($columns)
166 | ->distinct($columns);
167 |
168 | if (empty($tags)) {
169 | // If there are no tags provided, find articles that have no tags.
170 | $query->leftJoinWith('Tags')
171 | ->where(['Tags.title IS' => null]);
172 | } else {
173 | // Find articles that have one or more of the provided tags.
174 | $query->innerJoinWith('Tags')
175 | ->where(['Tags.title IN' => $tags]);
176 | }
177 |
178 | return $query->groupBy(['Articles.id']);
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/Console/Installer.php:
--------------------------------------------------------------------------------
1 | getIO();
57 |
58 | $rootDir = dirname(dirname(__DIR__));
59 |
60 | static::createAppLocalConfig($rootDir, $io);
61 | static::createWritableDirectories($rootDir, $io);
62 |
63 | static::setFolderPermissions($rootDir, $io);
64 | static::setSecuritySalt($rootDir, $io);
65 |
66 | $class = 'Cake\Codeception\Console\Installer';
67 | if (class_exists($class)) {
68 | $class::customizeCodeceptionBinary($event);
69 | }
70 | }
71 |
72 | /**
73 | * Create config/app_local.php file if it does not exist.
74 | *
75 | * @param string $dir The application's root directory.
76 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
77 | * @return void
78 | */
79 | public static function createAppLocalConfig($dir, $io)
80 | {
81 | $appLocalConfig = $dir . '/config/app_local.php';
82 | $appLocalConfigTemplate = $dir . '/config/app_local.example.php';
83 | if (!file_exists($appLocalConfig)) {
84 | copy($appLocalConfigTemplate, $appLocalConfig);
85 | $io->write('Created `config/app_local.php` file');
86 | }
87 | }
88 |
89 | /**
90 | * Create the `logs` and `tmp` directories.
91 | *
92 | * @param string $dir The application's root directory.
93 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
94 | * @return void
95 | */
96 | public static function createWritableDirectories($dir, $io)
97 | {
98 | foreach (static::WRITABLE_DIRS as $path) {
99 | $path = $dir . '/' . $path;
100 | if (!file_exists($path)) {
101 | mkdir($path);
102 | $io->write('Created `' . $path . '` directory');
103 | }
104 | }
105 | }
106 |
107 | /**
108 | * Set globally writable permissions on the "tmp" and "logs" directory.
109 | *
110 | * This is not the most secure default, but it gets people up and running quickly.
111 | *
112 | * @param string $dir The application's root directory.
113 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
114 | * @return void
115 | */
116 | public static function setFolderPermissions($dir, $io)
117 | {
118 | // ask if the permissions should be changed
119 | if ($io->isInteractive()) {
120 | $validator = function ($arg) {
121 | if (in_array($arg, ['Y', 'y', 'N', 'n'])) {
122 | return $arg;
123 | }
124 | throw new Exception('This is not a valid answer. Please choose Y or n.');
125 | };
126 | $setFolderPermissions = $io->askAndValidate(
127 | 'Set Folder Permissions ? (Default to Y) [Y,n ]? ',
128 | $validator,
129 | 10,
130 | 'Y'
131 | );
132 |
133 | if (in_array($setFolderPermissions, ['n', 'N'])) {
134 | return;
135 | }
136 | }
137 |
138 | // Change the permissions on a path and output the results.
139 | $changePerms = function ($path) use ($io) {
140 | $currentPerms = fileperms($path) & 0777;
141 | $worldWritable = $currentPerms | 0007;
142 | if ($worldWritable == $currentPerms) {
143 | return;
144 | }
145 |
146 | $res = chmod($path, $worldWritable);
147 | if ($res) {
148 | $io->write('Permissions set on ' . $path);
149 | } else {
150 | $io->write('Failed to set permissions on ' . $path);
151 | }
152 | };
153 |
154 | $walker = function ($dir) use (&$walker, $changePerms) {
155 | $files = array_diff(scandir($dir), ['.', '..']);
156 | foreach ($files as $file) {
157 | $path = $dir . '/' . $file;
158 |
159 | if (!is_dir($path)) {
160 | continue;
161 | }
162 |
163 | $changePerms($path);
164 | $walker($path);
165 | }
166 | };
167 |
168 | $walker($dir . '/tmp');
169 | $changePerms($dir . '/tmp');
170 | $changePerms($dir . '/logs');
171 | }
172 |
173 | /**
174 | * Set the security.salt value in the application's config file.
175 | *
176 | * @param string $dir The application's root directory.
177 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
178 | * @return void
179 | */
180 | public static function setSecuritySalt($dir, $io)
181 | {
182 | $newKey = hash('sha256', Security::randomBytes(64));
183 | static::setSecuritySaltInFile($dir, $io, $newKey, 'app_local.php');
184 | }
185 |
186 | /**
187 | * Set the security.salt value in a given file
188 | *
189 | * @param string $dir The application's root directory.
190 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
191 | * @param string $newKey key to set in the file
192 | * @param string $file A path to a file relative to the application's root
193 | * @return void
194 | */
195 | public static function setSecuritySaltInFile($dir, $io, $newKey, $file)
196 | {
197 | $config = $dir . '/config/' . $file;
198 | $content = file_get_contents($config);
199 |
200 | $content = str_replace('__SALT__', $newKey, $content, $count);
201 |
202 | if ($count == 0) {
203 | $io->write('No Security.salt placeholder to replace.');
204 |
205 | return;
206 | }
207 |
208 | $result = file_put_contents($config, $content);
209 | if ($result) {
210 | $io->write('Updated Security.salt value in config/' . $file);
211 |
212 | return;
213 | }
214 | $io->write('Unable to update Security.salt value.');
215 | }
216 |
217 | /**
218 | * Set the APP_NAME value in a given file
219 | *
220 | * @param string $dir The application's root directory.
221 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
222 | * @param string $appName app name to set in the file
223 | * @param string $file A path to a file relative to the application's root
224 | * @return void
225 | */
226 | public static function setAppNameInFile($dir, $io, $appName, $file)
227 | {
228 | $config = $dir . '/config/' . $file;
229 | $content = file_get_contents($config);
230 | $content = str_replace('__APP_NAME__', $appName, $content, $count);
231 |
232 | if ($count == 0) {
233 | $io->write('No __APP_NAME__ placeholder to replace.');
234 |
235 | return;
236 | }
237 |
238 | $result = file_put_contents($config, $content);
239 | if ($result) {
240 | $io->write('Updated __APP_NAME__ value in config/' . $file);
241 |
242 | return;
243 | }
244 | $io->write('Unable to update __APP_NAME__ value.');
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/webroot/css/cake.css:
--------------------------------------------------------------------------------
1 | .disabled a,
2 | a.disabled {
3 | pointer-events: none;
4 | }
5 |
6 | a:hover {
7 | color: #15848F;
8 | }
9 |
10 | a {
11 | color: #1798A5;
12 | }
13 |
14 | .side-nav li a:not(.button) {
15 | color: #15848F;
16 | }
17 |
18 | .side-nav li a:not(.button):hover {
19 | color: #15848F;
20 | }
21 |
22 | header {
23 | background-color: #D33C44;
24 | color: #ffffff;
25 | font-size: 30px;
26 | height: 84px;
27 | line-height: 64px;
28 | padding: 16px 0px;
29 | box-shadow: 0px 1px rgba(0, 0, 0, 0.24);
30 | }
31 |
32 | header .header-title {
33 | padding-left:80px
34 | }
35 |
36 | legend {
37 | color:#15848F;
38 | }
39 |
40 | .row {
41 | max-width: 80rem;
42 | }
43 |
44 | .actions.columns {
45 | margin-top:1rem;
46 | border-left: 5px solid #15848F;
47 | padding-left: 15px;
48 | padding: 32px 20px;
49 | }
50 |
51 | .actions.columns h3 {
52 | color:#15848F;
53 | }
54 |
55 | .related table {
56 | border: 0;
57 | width: 100%;
58 | table-layout: fixed;
59 | }
60 |
61 | .index table thead {
62 | height: 3.5rem;
63 | }
64 |
65 | .header-help {
66 | float: right;
67 | margin-right:2rem;
68 | margin-top: -80px;
69 | font-size:16px;
70 | }
71 |
72 | .header-help span {
73 | font-weight: normal;
74 | text-align: center;
75 | text-decoration: none;
76 | line-height: 1;
77 | white-space: nowrap;
78 | display: inline-block;
79 | padding: 0.25rem 0.5rem 0.375rem;
80 | font-size: 0.8rem;
81 | background-color: #0097a7;
82 | color: #FFF;
83 | border-radius: 1000px;
84 | }
85 |
86 | .header-help a {
87 | color: #fff;
88 | }
89 |
90 | ul.pagination li a {
91 | color: rgba(0, 0 ,0 , 0.54);
92 | }
93 |
94 | ul.pagination li.active a {
95 | background-color: #DCE47E;
96 | color: #FFF;
97 | font-weight: bold;
98 | cursor: default;
99 | }
100 | ul.pagination .disabled:hover a {
101 | background: none;
102 | }
103 |
104 | .paginator {
105 | text-align: center;
106 | }
107 |
108 | .paginator ul.pagination li {
109 | float: none;
110 | display: inline-block;
111 | }
112 |
113 | .paginator p {
114 | text-align: right;
115 | color: rgba(0, 0 ,0 , 0.54);
116 | }
117 |
118 | .asc:after {
119 | content: " \2193";
120 | }
121 | .desc:after {
122 | content: " \2191";
123 | }
124 |
125 | .form .error-message {
126 | display: block;
127 | padding: 0.375rem 0.5625rem 0.5625rem;
128 | margin-top: -1px;
129 | margin-bottom: 1rem;
130 | font-size: 0.75rem;
131 | font-weight: normal;
132 | font-style: italic;
133 | color: rgba(0, 0, 0, 0.54);
134 | }
135 |
136 | .required > label {
137 | font-weight: bold;
138 | }
139 | .required > label:after {
140 | content: ' *';
141 | color: #C3232D;
142 | }
143 |
144 | select[multiple] {
145 | min-height:150px;
146 | background: none;
147 | }
148 | input[type=checkbox],
149 | input[type=radio] {
150 | margin-right: 0.5em;
151 | }
152 |
153 | .date select,
154 | .time select,
155 | .datetime select {
156 | display: inline;
157 | width: auto;
158 | margin-right: 10px;
159 | }
160 |
161 | .error label,
162 | .error label.error {
163 | color: #C3232D;
164 | }
165 |
166 | .view h2 {
167 | color: #6F6F6F;
168 | }
169 |
170 | .view .columns.strings {
171 | border-radius: 3px;
172 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
173 | margin-right:0.7rem;
174 | }
175 |
176 | .view .numbers {
177 | background-color: #B7E3EC;
178 | color: #FFF;
179 | border-radius: 3px;
180 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
181 | margin-right: 0.7rem;
182 | }
183 |
184 | .view .columns.dates {
185 | border-radius: 3px;
186 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
187 | margin-right:0.7rem;
188 | background-color:#DCE47E;
189 | color: #fff;
190 | }
191 |
192 | .view .columns.booleans {
193 | border-radius: 3px;
194 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
195 | margin-right:0.7rem;
196 | background-color: #8D6E65;
197 | color: #fff;
198 | }
199 |
200 | .view .strings p {
201 | border-bottom: 1px solid #eee;
202 | }
203 | .view .numbers .subheader, .view .dates .subheader {
204 | color:#747474;
205 | }
206 | .view .booleans .subheader {
207 | color: #E9E9E9
208 | }
209 |
210 | .view .texts .columns {
211 | margin-top:1.2rem;
212 | border-bottom: 1px solid #eee;
213 | }
214 |
215 | /** Notices and Errors **/
216 | .cake-error,
217 | .cake-debug,
218 | .notice,
219 | p.error,
220 | p.notice {
221 | display: block;
222 | clear: both;
223 | background-repeat: repeat-x;
224 | margin-bottom: 18px;
225 | padding: 7px 14px;
226 | border-radius: 3px;
227 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
228 | }
229 |
230 | .cake-debug,
231 | .notice,
232 | p.notice {
233 | color: #000000;
234 | background: #ffcc00;
235 | }
236 |
237 | .cake-error,
238 | p.error {
239 | color: #fff;
240 | background: #C3232D;
241 | }
242 |
243 | pre {
244 | background: none repeat scroll 0% 0% #FFF;
245 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
246 | margin: 15px 0px;
247 | color: rgba(0, 0 ,0 , 0.74);
248 | padding:5px;
249 | }
250 |
251 | .cake-error .cake-stack-trace {
252 | margin-top:10px;
253 | }
254 |
255 | .cake-stack-trace code {
256 | background: inherit;
257 | border:0;
258 | }
259 |
260 | .cake-code-dump .code-highlight {
261 | display: block;
262 | background-color: #FFC600;
263 | }
264 |
265 | .cake-error a,
266 | .cake-error a:hover {
267 | color:#fff;
268 | text-decoration: underline;
269 | }
270 |
271 | .checks {
272 | padding:30px;
273 | color: #626262;
274 | background-color: #B7E3EC;
275 | border-radius: 3px;
276 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
277 | margin-bottom: 2em;
278 | }
279 |
280 | .checks h4 {
281 | margin-bottom: 1.5rem;
282 | }
283 |
284 | .checks hr {
285 | border: 0;
286 | height: 0;
287 | border-top: 1px solid rgba(0, 0, 0, 0.1);
288 | border-bottom: 1px solid rgba(255, 255, 255, 0.3);
289 | }
290 |
291 | .checks .success,
292 | .checks .problem {
293 | margin-left: 10px;
294 | }
295 | .checks .success:before,
296 | .checks .problem:before {
297 | line-height: 0px;
298 | font-size: 28px;
299 | height: 12px;
300 | width: 12px;
301 | border-radius: 15px;
302 | text-align: center;
303 | vertical-align: middle;
304 | display: inline-block;
305 | position: relative;
306 | left: -11px;
307 | }
308 |
309 | .checks .success:before {
310 | content: "✓";
311 | color: green;
312 | margin-right: 9px;
313 | }
314 |
315 | .checks .problem:before {
316 | content: "✘";
317 | color: red;
318 | margin-right: 9px;
319 | }
320 |
321 | .top-bar.expanded .title-area {
322 | background: #01545b;
323 | }
324 |
325 | .top-bar.expanded, .top-bar,.top-bar-section ul li,.top-bar-section li:not(.has-form) a:not(.button) {
326 | background: #116d76;
327 | }
328 |
329 | .top-bar-section li:not(.has-form) a:not(.button):hover {
330 | background-color: #308e97;
331 | background: #308e97;
332 | }
333 |
334 | .side-nav li.heading {
335 | color: #1798A5;
336 | font-size: 0.875rem;
337 | font-weight: bold;
338 | text-transform: uppercase;
339 | padding: 0.4375rem 0.875rem;
340 | }
341 |
342 | #actions-sidebar {
343 | background: #fafafa;
344 | }
345 |
346 | .index table {
347 | margin-top: 0rem;
348 | border: 0;
349 | width: 100%;
350 | table-layout: fixed;
351 | }
352 |
353 | table {
354 | background: #fff;
355 | margin-bottom: 1.25rem;
356 | border: none;
357 | table-layout: fixed;
358 | width: 100%;
359 | }
360 |
361 | table thead {
362 | background: none;
363 | }
364 |
365 | table tr {
366 | border-bottom: 1px solid #ebebec;
367 | }
368 |
369 | table thead tr {
370 | border-bottom: 1px solid #1798A5;
371 | }
372 |
373 | table tr th {
374 | padding: 0.5625rem 0.625rem;
375 | font-size: 0.875rem;
376 | color: #1798A5;
377 | text-align: left;
378 | border-bottom: 2px solid #1798A5;
379 | }
380 |
381 | table tr:nth-of-type(even) {
382 | background: none;
383 | }
384 |
385 | fieldset {
386 | border: none;
387 | padding: 1.25rem;
388 | margin: 1.125rem 0;
389 | }
390 |
391 | fieldset legend {
392 | border-bottom: 2px solid #1798A5;
393 | width: 100%;
394 | line-height: 2rem;
395 | }
396 |
397 | .form button[type="submit"] {
398 | float: right;
399 | text-transform: uppercase;
400 | box-shadow: none;
401 | }
402 |
403 | .form button:hover, .form button:focus {
404 | background: #BE840B;
405 | box-shadow: none;
406 | }
407 |
408 | button {
409 | background: #966600;
410 | }
411 |
412 | div.message {
413 | text-align: center;
414 | cursor: pointer;
415 | display: block;
416 | font-weight: normal;
417 | padding: 0 1.5rem 0 1.5rem;
418 | transition: height 300ms ease-out 0s;
419 | background-color: #a0d3e8;
420 | color: #626262;
421 | top: 15px;
422 | right: 15px;
423 | z-index: 999;
424 | overflow: hidden;
425 | height: 50px;
426 | line-height: 2.5em;
427 | box-radius: 5px;
428 | }
429 |
430 | div.message:before {
431 | line-height: 0px;
432 | font-size: 20px;
433 | height: 12px;
434 | width: 12px;
435 | border-radius: 15px;
436 | text-align: center;
437 | vertical-align: middle;
438 | display: inline-block;
439 | position: relative;
440 | left: -11px;
441 | background-color: #FFF;
442 | padding: 12px 14px 12px 10px;
443 | content: "i";
444 | color: #a0d3e8;
445 | }
446 |
447 | div.message.error {
448 | background-color: #C3232D;
449 | color: #FFF;
450 | }
451 |
452 | div.message.error:before {
453 | padding: 11px 16px 14px 7px;
454 | color: #C3232D;
455 | content: "x";
456 | }
457 | div.message.hidden {
458 | height: 0;
459 | }
460 |
461 |
462 | .vertical-table th {
463 | padding: 0.5625rem 0.625rem;
464 | font-size: 0.875rem;
465 | color: #1798A5;
466 | border: none;
467 | text-align: left;
468 | }
469 |
470 | .vertical-table {
471 | vertical-align: middle;
472 | }
473 |
474 | .vertical-table td {
475 | text-align: right;
476 | }
477 |
478 | .content {
479 | padding: 2rem;
480 | }
481 |
482 | /* Use 'one true layout' methods to get equal height columns */
483 | .container {
484 | overflow: hidden;
485 | min-height: 92%; /* full height almost always */
486 | }
487 |
488 | /* Force equal height by overflowing */
489 | .content,
490 | #actions-sidebar {
491 | margin-bottom: -99999px;
492 | padding-bottom: 99999px;
493 | }
494 | @media(max-width: 640px) {
495 | #actions-sidebar {
496 | padding-bottom: 2rem;
497 | margin-bottom: 0;
498 | }
499 | }
500 |
501 | .content h3 {
502 | color: #be140b;
503 | padding-bottom: 0.5rem;
504 | margin-bottom: 20px;
505 | }
506 |
507 | .content h4 {
508 | color: #be140b;
509 | padding-bottom: 0.5rem;
510 | margin-bottom: 20px;
511 | border-bottom: 2px solid #be140b;
512 | }
513 |
514 | .content .related h4 {
515 | color: #4d8f97;
516 | padding-bottom: 0.5rem;
517 | margin-top: 20px;
518 | margin-bottom: 10px;
519 | border-bottom: 0px;
520 | }
521 |
522 | table td {
523 | vertical-align: top;
524 | word-break: break-all;
525 | }
526 |
--------------------------------------------------------------------------------
/templates/Pages/home.php:
--------------------------------------------------------------------------------
1 | disableAutoLayout();
25 |
26 | $checkConnection = function (string $name) {
27 | $error = null;
28 | $connected = false;
29 | try {
30 | ConnectionManager::get($name)->getDriver()->connect();
31 | // No exception means success
32 | $connected = true;
33 | } catch (Exception $connectionError) {
34 | $error = $connectionError->getMessage();
35 | if (method_exists($connectionError, 'getAttributes')) {
36 | $attributes = $connectionError->getAttributes();
37 | if (isset($attributes['message'])) {
38 | $error .= ' ' . $attributes['message'];
39 | }
40 | }
41 | if ($name === 'debug_kit') {
42 | $error = 'Try adding your current top level domain to the
43 | DebugKit.safeTld
44 | config and reload.';
45 | if (!in_array('sqlite', \PDO::getAvailableDrivers())) {
46 | $error .= ' You need to install the PHP extension pdo_sqlite so DebugKit can work properly.';
47 | }
48 | }
49 | }
50 |
51 | return compact('connected', 'error');
52 | };
53 |
54 | if (!Configure::read('debug')) :
55 | throw new NotFoundException(
56 | 'Please replace templates/Pages/home.php with your own version or re-enable debug mode.'
57 | );
58 | endif;
59 |
60 | ?>
61 |
62 |
63 |
64 | = $this->Html->charset() ?>
65 |
66 |
67 | CakePHP: the rapid development PHP framework:
68 | = $this->fetch('title') ?>
69 |
70 | = $this->Html->meta('icon') ?>
71 |
72 | = $this->Html->css(['normalize.min', 'milligram.min', 'fonts', 'cake', 'home']) ?>
73 |
74 | = $this->fetch('meta') ?>
75 | = $this->fetch('css') ?>
76 | = $this->fetch('script') ?>
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | Welcome to CakePHP = h(Configure::version()) ?> Chiffon (🍰)
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | Please be aware that this page will not be shown if you turn off debug mode unless you replace templates/Pages/home.php with your own version.
96 |
97 |
106 |
107 |
108 |
109 |
110 |
111 |
Environment
112 |
113 | =')) : ?>
114 | Your version of PHP is 8.1.0 or higher (detected = PHP_VERSION ?>).
115 |
116 | Your version of PHP is too low. You need PHP 8.1.0 or higher to use CakePHP (detected = PHP_VERSION ?>).
117 |
118 |
119 |
120 | Your version of PHP has the mbstring extension loaded.
121 |
122 | Your version of PHP does NOT have the mbstring extension loaded.
123 |
124 |
125 |
126 | Your version of PHP has the openssl extension loaded.
127 |
128 | Your version of PHP does NOT have the openssl extension loaded.
129 |
130 |
131 |
132 | Your version of PHP has the intl extension loaded.
133 |
134 | Your version of PHP does NOT have the intl extension loaded.
135 |
136 |
137 |
138 | You should set zend.assertions to 1 in your php.ini for your development environment.
139 |
140 |
141 |
142 |
143 |
Filesystem
144 |
145 |
146 | Your tmp directory is writable.
147 |
148 | Your tmp directory is NOT writable.
149 |
150 |
151 |
152 | Your logs directory is writable.
153 |
154 | Your logs directory is NOT writable.
155 |
156 |
157 |
158 |
159 | The = h($settings['className']) ?> is being used for core caching. To change the config edit config/app.php
160 |
161 | Your cache is NOT working. Please check the settings in config/app.php
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
Database
170 |
173 |
174 |
175 | CakePHP is able to connect to the database.
176 |
177 | CakePHP is NOT able to connect to the database. = h($result['error']) ?>
178 |
179 |
180 |
181 |
182 |
DebugKit
183 |
184 |
185 | DebugKit is loaded.
186 |
189 |
190 | DebugKit can connect to the database.
191 |
192 | There are configuration problems present which need to be fixed: = $result['error'] ?>
193 |
194 |
195 | DebugKit is not loaded.
196 |
197 |
198 |
199 |
200 |
201 |
208 |
209 |
217 |
218 |
230 |
231 |
238 |
239 |
240 |
241 |
242 |
243 |
--------------------------------------------------------------------------------
/config/app.php:
--------------------------------------------------------------------------------
1 | filter_var(env('DEBUG', false), FILTER_VALIDATE_BOOLEAN),
22 |
23 | /*
24 | * Configure basic information about the application.
25 | *
26 | * - namespace - The namespace to find app classes under.
27 | * - defaultLocale - The default locale for translation, formatting currencies and numbers, date and time.
28 | * - encoding - The encoding used for HTML + database connections.
29 | * - base - The base directory the app resides in. If false this
30 | * will be auto-detected.
31 | * - dir - Name of app directory.
32 | * - webroot - The webroot directory.
33 | * - wwwRoot - The file path to webroot.
34 | * - baseUrl - To configure CakePHP to *not* use mod_rewrite and to
35 | * use CakePHP pretty URLs, remove these .htaccess
36 | * files:
37 | * /.htaccess
38 | * /webroot/.htaccess
39 | * And uncomment the baseUrl key below.
40 | * - fullBaseUrl - A base URL to use for absolute links. When set to false (default)
41 | * CakePHP generates required value based on `HTTP_HOST` environment variable.
42 | * However, you can define it manually to optimize performance or if you
43 | * are concerned about people manipulating the `Host` header.
44 | * - imageBaseUrl - Web path to the public images/ directory under webroot.
45 | * - cssBaseUrl - Web path to the public css/ directory under webroot.
46 | * - jsBaseUrl - Web path to the public js/ directory under webroot.
47 | * - paths - Configure paths for non class-based resources. Supports the
48 | * `plugins`, `templates`, `locales` subkeys, which allow the definition of
49 | * paths for plugins, view templates and locale files respectively.
50 | */
51 | 'App' => [
52 | 'namespace' => 'App',
53 | 'encoding' => env('APP_ENCODING', 'UTF-8'),
54 | 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
55 | 'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', 'UTC'),
56 | 'base' => false,
57 | 'dir' => 'src',
58 | 'webroot' => 'webroot',
59 | 'wwwRoot' => WWW_ROOT,
60 | //'baseUrl' => env('SCRIPT_NAME'),
61 | 'fullBaseUrl' => false,
62 | 'imageBaseUrl' => 'img/',
63 | 'cssBaseUrl' => 'css/',
64 | 'jsBaseUrl' => 'js/',
65 | 'paths' => [
66 | 'plugins' => [ROOT . DS . 'plugins' . DS],
67 | 'templates' => [ROOT . DS . 'templates' . DS],
68 | 'locales' => [RESOURCES . 'locales' . DS],
69 | ],
70 | ],
71 |
72 | /*
73 | * Security and encryption configuration
74 | *
75 | * - salt - A random string used in security hashing methods.
76 | * The salt value is also used as the encryption key.
77 | * You should treat it as extremely sensitive data.
78 | */
79 | 'Security' => [
80 | 'salt' => env('SECURITY_SALT'),
81 | ],
82 |
83 | /*
84 | * Apply timestamps with the last modified time to static assets (js, css, images).
85 | * Will append a querystring parameter containing the time the file was modified.
86 | * This is useful for busting browser caches.
87 | *
88 | * Set to true to apply timestamps when debug is true. Set to 'force' to always
89 | * enable timestamping regardless of debug value.
90 | */
91 | 'Asset' => [
92 | //'timestamp' => true,
93 | // 'cacheTime' => '+1 year'
94 | ],
95 |
96 | /*
97 | * Configure the cache adapters.
98 | */
99 | 'Cache' => [
100 | 'default' => [
101 | 'className' => FileEngine::class,
102 | 'path' => CACHE,
103 | 'url' => env('CACHE_DEFAULT_URL', null),
104 | ],
105 |
106 | /*
107 | * Configure the cache used for general framework caching.
108 | * Translation cache files are stored with this configuration.
109 | * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
110 | * If you set 'className' => 'Null' core cache will be disabled.
111 | */
112 | '_cake_translations_' => [
113 | 'className' => FileEngine::class,
114 | 'prefix' => 'myapp_cake_translations_',
115 | 'path' => CACHE . 'persistent' . DS,
116 | 'serialize' => true,
117 | 'duration' => '+1 years',
118 | 'url' => env('CACHE_CAKECORE_URL', null),
119 | ],
120 |
121 | /*
122 | * Configure the cache for model and datasource caches. This cache
123 | * configuration is used to store schema descriptions, and table listings
124 | * in connections.
125 | * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
126 | */
127 | '_cake_model_' => [
128 | 'className' => FileEngine::class,
129 | 'prefix' => 'myapp_cake_model_',
130 | 'path' => CACHE . 'models' . DS,
131 | 'serialize' => true,
132 | 'duration' => '+1 years',
133 | 'url' => env('CACHE_CAKEMODEL_URL', null),
134 | ],
135 | ],
136 |
137 | /*
138 | * Configure the Error and Exception handlers used by your application.
139 | *
140 | * By default errors are displayed using Debugger, when debug is true and logged
141 | * by Cake\Log\Log when debug is false.
142 | *
143 | * In CLI environments exceptions will be printed to stderr with a backtrace.
144 | * In web environments an HTML page will be displayed for the exception.
145 | * With debug true, framework errors like Missing Controller will be displayed.
146 | * When debug is false, framework errors will be coerced into generic HTTP errors.
147 | *
148 | * Options:
149 | *
150 | * - `errorLevel` - int - The level of errors you are interested in capturing.
151 | * - `trace` - boolean - Whether backtraces should be included in
152 | * logged errors/exceptions.
153 | * - `log` - boolean - Whether you want exceptions logged.
154 | * - `exceptionRenderer` - string - The class responsible for rendering uncaught exceptions.
155 | * The chosen class will be used for both CLI and web environments. If you want different
156 | * classes used in CLI and web environments you'll need to write that conditional logic as well.
157 | * The conventional location for custom renderers is in `src/Error`. Your exception renderer needs to
158 | * implement the `render()` method and return either a string or Http\Response.
159 | * `errorRenderer` - string - The class responsible for rendering PHP errors. The selected
160 | * class will be used for both web and CLI contexts. If you want different classes for each environment
161 | * you'll need to write that conditional logic as well. Error renderers need to
162 | * to implement the `Cake\Error\ErrorRendererInterface`.
163 | * - `skipLog` - array - List of exceptions to skip for logging. Exceptions that
164 | * extend one of the listed exceptions will also be skipped for logging.
165 | * E.g.:
166 | * `'skipLog' => ['Cake\Http\Exception\NotFoundException', 'Cake\Http\Exception\UnauthorizedException']`
167 | * - `extraFatalErrorMemory` - int - The number of megabytes to increase the memory limit by
168 | * when a fatal error is encountered. This allows
169 | * breathing room to complete logging or error handling.
170 | * - `ignoredDeprecationPaths` - array - A list of glob-compatible file paths that deprecations
171 | * should be ignored in. Use this to ignore deprecations for plugins or parts of
172 | * your application that still emit deprecations.
173 | */
174 | 'Error' => [
175 | 'errorLevel' => E_ALL,
176 | 'skipLog' => [],
177 | 'log' => true,
178 | 'trace' => true,
179 | 'ignoredDeprecationPaths' => [],
180 | ],
181 |
182 | /*
183 | * Debugger configuration
184 | *
185 | * Define development error values for Cake\Error\Debugger
186 | *
187 | * - `editor` Set the editor URL format you want to use.
188 | * By default atom, emacs, macvim, phpstorm, sublime, textmate, and vscode are
189 | * available. You can add additional editor link formats using
190 | * `Debugger::addEditor()` during your application bootstrap.
191 | * - `outputMask` A mapping of `key` to `replacement` values that
192 | * `Debugger` should replace in dumped data and logs generated by `Debugger`.
193 | */
194 | 'Debugger' => [
195 | 'editor' => 'phpstorm',
196 | ],
197 |
198 | /*
199 | * Email configuration.
200 | *
201 | * By defining transports separately from delivery profiles you can easily
202 | * re-use transport configuration across multiple profiles.
203 | *
204 | * You can specify multiple configurations for production, development and
205 | * testing.
206 | *
207 | * Each transport needs a `className`. Valid options are as follows:
208 | *
209 | * Mail - Send using PHP mail function
210 | * Smtp - Send using SMTP
211 | * Debug - Do not send the email, just return the result
212 | *
213 | * You can add custom transports (or override existing transports) by adding the
214 | * appropriate file to src/Mailer/Transport. Transports should be named
215 | * 'YourTransport.php', where 'Your' is the name of the transport.
216 | */
217 | 'EmailTransport' => [
218 | 'default' => [
219 | 'className' => MailTransport::class,
220 | /*
221 | * The keys host, port, timeout, username, password, client and tls
222 | * are used in SMTP transports
223 | */
224 | 'host' => 'localhost',
225 | 'port' => 25,
226 | 'timeout' => 30,
227 | /*
228 | * It is recommended to set these options through your environment or app_local.php
229 | */
230 | //'username' => null,
231 | //'password' => null,
232 | 'client' => null,
233 | 'tls' => false,
234 | 'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
235 | ],
236 | ],
237 |
238 | /*
239 | * Email delivery profiles
240 | *
241 | * Delivery profiles allow you to predefine various properties about email
242 | * messages from your application and give the settings a name. This saves
243 | * duplication across your application and makes maintenance and development
244 | * easier. Each profile accepts a number of keys. See `Cake\Mailer\Mailer`
245 | * for more information.
246 | */
247 | 'Email' => [
248 | 'default' => [
249 | 'transport' => 'default',
250 | 'from' => 'you@localhost',
251 | /*
252 | * Will by default be set to config value of App.encoding, if that exists otherwise to UTF-8.
253 | */
254 | //'charset' => 'utf-8',
255 | //'headerCharset' => 'utf-8',
256 | ],
257 | ],
258 |
259 | /*
260 | * Connection information used by the ORM to connect
261 | * to your application's datastores.
262 | *
263 | * ### Notes
264 | * - Drivers include Mysql Postgres Sqlite Sqlserver
265 | * See vendor\cakephp\cakephp\src\Database\Driver for the complete list
266 | * - Do not use periods in database name - it may lead to errors.
267 | * See https://github.com/cakephp/cakephp/issues/6471 for details.
268 | * - 'encoding' is recommended to be set to full UTF-8 4-Byte support.
269 | * E.g set it to 'utf8mb4' in MariaDB and MySQL and 'utf8' for any
270 | * other RDBMS.
271 | */
272 | 'Datasources' => [
273 | /*
274 | * These configurations should contain permanent settings used
275 | * by all environments.
276 | *
277 | * The values in app_local.php will override any values set here
278 | * and should be used for local and per-environment configurations.
279 | *
280 | * Environment variable-based configurations can be loaded here or
281 | * in app_local.php depending on the application's needs.
282 | */
283 | 'default' => [
284 | 'className' => Connection::class,
285 | 'driver' => Mysql::class,
286 | 'persistent' => false,
287 | 'timezone' => 'UTC',
288 |
289 | /*
290 | * For MariaDB/MySQL the internal default changed from utf8 to utf8mb4, aka full utf-8 support
291 | */
292 | 'encoding' => 'utf8mb4',
293 |
294 | /*
295 | * If your MySQL server is configured with `skip-character-set-client-handshake`
296 | * then you MUST use the `flags` config to set your charset encoding.
297 | * For e.g. `'flags' => [\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4']`
298 | */
299 | 'flags' => [],
300 | 'cacheMetadata' => true,
301 | 'log' => false,
302 |
303 | /*
304 | * Set identifier quoting to true if you are using reserved words or
305 | * special characters in your table or column names. Enabling this
306 | * setting will result in queries built using the Query Builder having
307 | * identifiers quoted when creating SQL. It should be noted that this
308 | * decreases performance because each query needs to be traversed and
309 | * manipulated before being executed.
310 | */
311 | 'quoteIdentifiers' => false,
312 |
313 | /*
314 | * During development, if using MySQL < 5.6, uncommenting the
315 | * following line could boost the speed at which schema metadata is
316 | * fetched from the database. It can also be set directly with the
317 | * mysql configuration directive 'innodb_stats_on_metadata = 0'
318 | * which is the recommended value in production environments
319 | */
320 | //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
321 | ],
322 |
323 | /*
324 | * The test connection is used during the test suite.
325 | */
326 | 'test' => [
327 | 'className' => Connection::class,
328 | 'driver' => Mysql::class,
329 | 'persistent' => false,
330 | 'timezone' => 'UTC',
331 | 'encoding' => 'utf8mb4',
332 | 'flags' => [],
333 | 'cacheMetadata' => true,
334 | 'quoteIdentifiers' => false,
335 | 'log' => false,
336 | //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
337 | ],
338 | ],
339 |
340 | /*
341 | * Configures logging options
342 | */
343 | 'Log' => [
344 | 'debug' => [
345 | 'className' => FileLog::class,
346 | 'path' => LOGS,
347 | 'file' => 'debug',
348 | 'url' => env('LOG_DEBUG_URL', null),
349 | 'scopes' => null,
350 | 'levels' => ['notice', 'info', 'debug'],
351 | ],
352 | 'error' => [
353 | 'className' => FileLog::class,
354 | 'path' => LOGS,
355 | 'file' => 'error',
356 | 'url' => env('LOG_ERROR_URL', null),
357 | 'scopes' => null,
358 | 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
359 | ],
360 | // To enable this dedicated query log, you need to set your datasource's log flag to true
361 | 'queries' => [
362 | 'className' => FileLog::class,
363 | 'path' => LOGS,
364 | 'file' => 'queries',
365 | 'url' => env('LOG_QUERIES_URL', null),
366 | 'scopes' => ['cake.database.queries'],
367 | ],
368 | ],
369 |
370 | /*
371 | * Session configuration.
372 | *
373 | * Contains an array of settings to use for session configuration. The
374 | * `defaults` key is used to define a default preset to use for sessions, any
375 | * settings declared here will override the settings of the default config.
376 | *
377 | * ## Options
378 | *
379 | * - `cookie` - The name of the cookie to use. Defaults to value set for `session.name` php.ini config.
380 | * Avoid using `.` in cookie names, as PHP will drop sessions from cookies with `.` in the name.
381 | * - `cookiePath` - The url path for which session cookie is set. Maps to the
382 | * `session.cookie_path` php.ini config. Defaults to base path of app.
383 | * - `timeout` - The time in minutes a session can be 'idle'. If no request is received in
384 | * this duration, the session will be expired and rotated. Pass 0 to disable idle timeout checks.
385 | * - `defaults` - The default configuration set to use as a basis for your session.
386 | * There are four built-in options: php, cake, cache, database.
387 | * - `handler` - Can be used to enable a custom session handler. Expects an
388 | * array with at least the `engine` key, being the name of the Session engine
389 | * class to use for managing the session. CakePHP bundles the `CacheSession`
390 | * and `DatabaseSession` engines.
391 | * - `ini` - An associative array of additional 'session.*` ini values to set.
392 | *
393 | * Within the `ini` key, you will likely want to define:
394 | *
395 | * - `session.cookie_lifetime` - The number of seconds that cookies are valid for. This
396 | * should be longer than `Session.timeout`.
397 | * - `session.gc_maxlifetime` - The number of seconds after which a session is considered 'garbage'
398 | * that can be deleted by PHP's session cleanup behavior. This value should be greater than both
399 | * `Sesssion.timeout` and `session.cookie_lifetime`.
400 | *
401 | * The built-in `defaults` options are:
402 | *
403 | * - 'php' - Uses settings defined in your php.ini.
404 | * - 'cake' - Saves session files in CakePHP's /tmp directory.
405 | * - 'database' - Uses CakePHP's database sessions.
406 | * - 'cache' - Use the Cache class to save sessions.
407 | *
408 | * To define a custom session handler, save it at src/Http/Session/.php.
409 | * Make sure the class implements PHP's `SessionHandlerInterface` and set
410 | * Session.handler to
411 | *
412 | * To use database sessions, load the SQL file located at config/schema/sessions.sql
413 | */
414 | 'Session' => [
415 | 'defaults' => 'php',
416 | ],
417 |
418 | /**
419 | * DebugKit configuration.
420 | *
421 | * Contains an array of configurations to apply to the DebugKit plugin, if loaded.
422 | * Documentation: https://book.cakephp.org/debugkit/5/en/index.html#configuration
423 | *
424 | * ## Options
425 | *
426 | * - `panels` - Enable or disable panels. The key is the panel name, and the value is true to enable,
427 | * or false to disable.
428 | * - `includeSchemaReflection` - Set to true to enable logging of schema reflection queries. Disabled by default.
429 | * - `safeTld` - Set an array of whitelisted TLDs for local development.
430 | * - `forceEnable` - Force DebugKit to display. Careful with this, it is usually safer to simply whitelist
431 | * your local TLDs.
432 | * - `ignorePathsPattern` - Regex pattern (including delimiter) to ignore paths.
433 | * DebugKit won’t save data for request URLs that match this regex.
434 | * - `ignoreAuthorization` - Set to true to ignore Cake Authorization plugin for DebugKit requests.
435 | * Disabled by default.
436 | * - `maxDepth` - Defines how many levels of nested data should be shown in general for debug output.
437 | * Default is 5. WARNING: Increasing the max depth level can lead to an out of memory error.
438 | * - `variablesPanelMaxDepth` - Defines how many levels of nested data should be shown in the variables tab.
439 | * Default is 5. WARNING: Increasing the max depth level can lead to an out of memory error.
440 | */
441 | 'DebugKit' => [
442 | 'forceEnable' => filter_var(env('DEBUG_KIT_FORCE_ENABLE', false), FILTER_VALIDATE_BOOLEAN),
443 | 'safeTld' => env('DEBUG_KIT_SAFE_TLD', null),
444 | 'ignoreAuthorization' => env('DEBUG_KIT_IGNORE_AUTHORIZATION', false),
445 | ],
446 |
447 | /**
448 | * TestSuite configuration.
449 | *
450 | * ## Options
451 | *
452 | * - `errorLevel` - Defaults to `E_ALL`. Can be set to `false` to disable overwrite error level.
453 | * - `fixtureStrategy` - Defaults to TruncateStrategy. Can be set to any class implementing FixtureStrategyInterface.
454 | */
455 | 'TestSuite' => [
456 | 'errorLevel' => null,
457 | 'fixtureStrategy' => null,
458 | ],
459 | ];
460 |
--------------------------------------------------------------------------------