├── plugins
└── empty
├── webroot
├── js
│ └── empty
├── favicon.ico
├── img
│ ├── cake.icon.png
│ └── cake.power.gif
├── .htaccess
├── index.php
└── css
│ └── cake.css
├── src
├── View
│ ├── Helper
│ │ └── empty
│ └── AppView.php
├── Model
│ ├── Behavior
│ │ └── empty
│ ├── Entity
│ │ ├── User.php
│ │ └── Category.php
│ └── Table
│ │ ├── ArticlesTable.php
│ │ ├── UsersTable.php
│ │ └── CategoriesTable.php
├── Controller
│ ├── Component
│ │ └── empty
│ ├── PagesController.php
│ ├── UsersController.php
│ ├── AppController.php
│ ├── ArticlesController.php
│ └── CategoriesController.php
├── Template
│ ├── Element
│ │ └── Flash
│ │ │ ├── error.ctp
│ │ │ ├── success.ctp
│ │ │ └── default.ctp
│ ├── Articles
│ │ ├── view.ctp
│ │ ├── edit.ctp
│ │ ├── add.ctp
│ │ └── index.ctp
│ ├── Layout
│ │ ├── rss
│ │ │ └── default.ctp
│ │ ├── ajax.ctp
│ │ ├── Email
│ │ │ ├── text
│ │ │ │ └── default.ctp
│ │ │ └── html
│ │ │ │ └── default.ctp
│ │ ├── error.ctp
│ │ └── default.ctp
│ ├── Users
│ │ ├── login.ctp
│ │ ├── add.ctp
│ │ ├── view.ctp
│ │ └── index.ctp
│ ├── Email
│ │ ├── text
│ │ │ └── default.ctp
│ │ └── html
│ │ │ └── default.ctp
│ ├── Error
│ │ ├── error500.ctp
│ │ └── error400.ctp
│ ├── Categories
│ │ ├── add.ctp
│ │ ├── edit.ctp
│ │ ├── index.ctp
│ │ └── view.ctp
│ └── Pages
│ │ └── home.ctp
├── Shell
│ └── ConsoleShell.php
└── Console
│ └── Installer.php
├── tests
├── TestCase
│ ├── View
│ │ └── Helper
│ │ │ └── empty
│ ├── Model
│ │ ├── Behavior
│ │ │ └── empty
│ │ └── Table
│ │ │ ├── UsersTableTest.php
│ │ │ └── CategoriesTableTest.php
│ └── Controller
│ │ ├── Component
│ │ └── empty
│ │ └── CategoriesControllerTest.php
├── bootstrap.php
└── Fixture
│ ├── UsersFixture.php
│ ├── ArticlesFixture.php
│ └── CategoriesFixture.php
├── .gitignore
├── .htaccess
├── behat.yml
├── .editorconfig
├── .travis.yml
├── config
├── schema
│ ├── sessions.sql
│ └── i18n.sql
├── Migrations
│ ├── 20150428111734_add_user_id_to_articles.php
│ ├── 20150419074519_create_articles.php
│ ├── 20150428105014_create_users.php
│ └── 20150428102422_create_categories.php
├── bootstrap_cli.php
├── paths.php
├── routes.php
├── bootstrap.php
└── app.default.php
├── features
├── bootstrap
│ ├── WebContext.php
│ ├── CategoriesContext.php
│ ├── UsersContext.php
│ ├── ArticlesContext.php
│ └── FeatureContext.php
└── articles.feature
├── index.php
├── bin
├── cake.php
├── cake.bat
└── cake
├── blog-tutorial.app.test
├── .gitattributes
├── phpunit.xml.dist
├── LICENSE
├── composer.json
└── README.md
/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/*
2 | /config/app.php
3 | /tmp/*
4 | /logs/*
5 |
--------------------------------------------------------------------------------
/src/Template/Element/Flash/error.ctp:
--------------------------------------------------------------------------------
1 |
= h($message) ?>
2 |
--------------------------------------------------------------------------------
/src/Template/Element/Flash/success.ctp:
--------------------------------------------------------------------------------
1 | = h($message) ?>
2 |
--------------------------------------------------------------------------------
/webroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sizuhiko/cakephp3-bdd-example/HEAD/webroot/favicon.ico
--------------------------------------------------------------------------------
/webroot/img/cake.icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sizuhiko/cakephp3-bdd-example/HEAD/webroot/img/cake.icon.png
--------------------------------------------------------------------------------
/webroot/img/cake.power.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sizuhiko/cakephp3-bdd-example/HEAD/webroot/img/cake.power.gif
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | RewriteEngine on
3 | RewriteRule ^$ webroot/ [L]
4 | RewriteRule (.*) webroot/$1 [L]
5 |
--------------------------------------------------------------------------------
/webroot/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | RewriteEngine On
3 | RewriteCond %{REQUEST_FILENAME} !-f
4 | RewriteRule ^ index.php [L]
5 |
6 |
--------------------------------------------------------------------------------
/src/Template/Element/Flash/default.ctp:
--------------------------------------------------------------------------------
1 |
7 | = h($message) ?>
8 |
--------------------------------------------------------------------------------
/src/Template/Articles/view.ctp:
--------------------------------------------------------------------------------
1 |
2 |
3 | = h($article->title) ?>
4 | = h($article->body) ?>
5 | Created: = $article->created->format(DATE_RFC850) ?>
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | Edit Article
4 | Form->create($article);
6 | echo $this->Form->input('title');
7 | echo $this->Form->input('body', ['rows' => '3']);
8 | echo $this->Form->button(__('Save Article'));
9 | echo $this->Form->end();
10 | ?>
--------------------------------------------------------------------------------
/src/Template/Layout/rss/default.ctp:
--------------------------------------------------------------------------------
1 | fetch('title');
7 | endif;
8 |
9 | echo $this->Rss->document(
10 | $this->Rss->channel(
11 | array(), $channel, $this->fetch('content')
12 | )
13 | );
14 | ?>
15 |
--------------------------------------------------------------------------------
/tests/Fixture/UsersFixture.php:
--------------------------------------------------------------------------------
1 | 'users'];
14 |
15 | /**
16 | * Records
17 | *
18 | * @var array
19 | */
20 | public $records = [];
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Fixture/ArticlesFixture.php:
--------------------------------------------------------------------------------
1 | 'articles'];
14 |
15 | /**
16 | * Records
17 | *
18 | * @var array
19 | */
20 | public $records = [];
21 | }
22 |
--------------------------------------------------------------------------------
/behat.yml:
--------------------------------------------------------------------------------
1 | # behat.yml
2 | default:
3 | # ...
4 | extensions:
5 | Behat\MinkExtension:
6 | base_url: 'http://blog-tutorial.app.test/'
7 | sessions:
8 | default:
9 | goutte: ~
10 | suites:
11 | default:
12 | contexts:
13 | - FeatureContext
14 | - WebContext
15 | - ArticlesContext
16 | - UsersContext
17 | - CategoriesContext
18 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; This file is for unifying the coding style for different editors and IDEs.
2 | ; More information at http://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 |
--------------------------------------------------------------------------------
/tests/Fixture/CategoriesFixture.php:
--------------------------------------------------------------------------------
1 | 'categories'];
14 |
15 | /**
16 | * Records
17 | *
18 | * @var array
19 | */
20 | public $records = [];
21 | }
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: false
4 |
5 | php:
6 | - 5.4
7 | - 5.5
8 | - 5.6
9 |
10 | before_script:
11 | - sh -c "composer require 'cakephp/cakephp-codesniffer:dev-master'"
12 | - phpenv rehash
13 |
14 | script:
15 | - sh -c "vendor/bin/phpcs -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests ./config ./webroot"
16 |
17 | notifications:
18 | email: false
19 |
--------------------------------------------------------------------------------
/src/Template/Articles/add.ctp:
--------------------------------------------------------------------------------
1 |
2 |
3 | Add Article
4 | Form->create($article);
6 | // just added the categories input
7 | echo $this->Form->input('categories');
8 | echo $this->Form->input('title');
9 | echo $this->Form->input('body', ['rows' => '3']);
10 | echo $this->Form->button(__('Save Article'));
11 | echo $this->Form->end();
12 | ?>
--------------------------------------------------------------------------------
/src/Template/Users/login.ctp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | = $this->Flash->render('auth') ?>
5 | = $this->Form->create() ?>
6 |
7 | = __('Please enter your username and password') ?>
8 | = $this->Form->input('username') ?>
9 | = $this->Form->input('password') ?>
10 |
11 | = $this->Form->button(__('Login')); ?>
12 | = $this->Form->end() ?>
13 |
--------------------------------------------------------------------------------
/src/Model/Entity/User.php:
--------------------------------------------------------------------------------
1 | true];
20 |
21 | protected function _setPassword($password)
22 | {
23 | return (new DefaultPasswordHasher)->hash($password);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/config/schema/sessions.sql:
--------------------------------------------------------------------------------
1 | # $Id$
2 | #
3 | # Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
4 | # 1785 E. Sahara Avenue, Suite 490-204
5 | # Las Vegas, Nevada 89104
6 | #
7 | # Licensed under The MIT License
8 | # For full copyright and license information, please see the LICENSE.txt
9 | # Redistributions of files must retain the above copyright notice.
10 | # MIT License (http://www.opensource.org/licenses/mit-license.php)
11 |
12 | CREATE TABLE sessions (
13 | id varchar(40) NOT NULL default '',
14 | data text,
15 | expires INT(11) NOT NULL,
16 | PRIMARY KEY (id)
17 | );
18 |
--------------------------------------------------------------------------------
/src/Model/Entity/Category.php:
--------------------------------------------------------------------------------
1 | true,
19 | 'lft' => true,
20 | 'rght' => true,
21 | 'name' => true,
22 | 'description' => true,
23 | 'parent_category' => true,
24 | 'articles' => true,
25 | 'child_categories' => true,
26 | ];
27 | }
28 |
--------------------------------------------------------------------------------
/features/bootstrap/WebContext.php:
--------------------------------------------------------------------------------
1 | getPathTo($path));
11 | }
12 |
13 | private function getPathTo($path)
14 | {
15 | switch ($path) {
16 | case 'TopPage': return Router::url(['controller' => 'articles', 'action' => 'index']);
17 | case 'トップページ': return Router::url(['controller' => 'articles', 'action' => 'index']);
18 | default: return $path;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Template/Email/text/default.ctp:
--------------------------------------------------------------------------------
1 |
16 | = $content ?>
17 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
16 | = $this->fetch('content') ?>
17 |
--------------------------------------------------------------------------------
/src/Template/Layout/Email/text/default.ctp:
--------------------------------------------------------------------------------
1 |
16 | = $this->fetch('content') ?>
17 |
--------------------------------------------------------------------------------
/src/Template/Users/add.ctp:
--------------------------------------------------------------------------------
1 |
2 |
= __('Actions') ?>
3 |
4 | = $this->Html->link(__('List Users'), ['action' => 'index']) ?>
5 |
6 |
7 |
8 | = $this->Form->create($user); ?>
9 |
10 | = __('Add User') ?>
11 | Form->input('username');
13 | echo $this->Form->input('password');
14 | echo $this->Form->input('role', [
15 | 'options' => ['admin' => 'Admin', 'author' => 'Author']
16 | ]);
17 | ?>
18 |
19 | = $this->Form->button(__('Submit')) ?>
20 | = $this->Form->end() ?>
21 |
22 |
--------------------------------------------------------------------------------
/src/Template/Email/html/default.ctp:
--------------------------------------------------------------------------------
1 |
16 | ' . $line . "\n";
21 | endforeach;
22 | ?>
23 |
--------------------------------------------------------------------------------
/features/bootstrap/CategoriesContext.php:
--------------------------------------------------------------------------------
1 | getHash();
16 | Fabricate::create('Categories', count($categories), function($data, $world) use($categories) {
17 | $index = $world->sequence('index', 0);
18 | return [
19 | 'parent_id' => null,
20 | 'lft' => null,
21 | 'rght' => null,
22 | 'name' => $categories[$index]['Name'],
23 | ];
24 | });
25 |
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Model/Table/ArticlesTable.php:
--------------------------------------------------------------------------------
1 | addBehavior('Timestamp');
14 | $this->belongsTo('Categories', [
15 | 'foreignKey' => 'category_id',
16 | ]);
17 | }
18 |
19 | public function validationDefault(Validator $validator)
20 | {
21 | $validator
22 | ->notEmpty('title')
23 | ->notEmpty('body');
24 |
25 | return $validator;
26 | }
27 |
28 | public function isOwnedBy($articleId, $userId)
29 | {
30 | return $this->exists(['id' => $articleId, 'user_id' => $userId]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/bin/cake.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php -q
2 |
16 |
17 |
18 |
19 | = $this->fetch('title') ?>
20 |
21 |
22 | = $this->fetch('content') ?>
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.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 | *.yml text
21 | .htaccess text
22 |
23 | # Declare files that will always have CRLF line endings on checkout.
24 | *.bat eol=crlf
25 |
26 | # Declare files that will always have LF line endings on checkout.
27 | *.pem eol=lf
28 |
29 | # Denote all files that are truly binary and should not be modified.
30 | *.png binary
31 | *.jpg binary
32 | *.gif binary
33 | *.ico binary
34 | *.mo binary
35 | *.pdf binary
36 | *.phar binary
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/View/AppView.php:
--------------------------------------------------------------------------------
1 | loadHelper('Html');`
29 | *
30 | * @return void
31 | */
32 | public function initialize()
33 | {
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/config/schema/i18n.sql:
--------------------------------------------------------------------------------
1 | # $Id$
2 | #
3 | # Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
4 | #
5 | # Licensed under The MIT License
6 | # For full copyright and license information, please see the LICENSE.txt
7 | # Redistributions of files must retain the above copyright notice.
8 | # MIT License (http://www.opensource.org/licenses/mit-license.php)
9 |
10 | CREATE TABLE i18n (
11 | id int(10) NOT NULL auto_increment,
12 | locale varchar(6) NOT NULL,
13 | model varchar(255) NOT NULL,
14 | foreign_key int(10) NOT NULL,
15 | field varchar(255) NOT NULL,
16 | content mediumtext,
17 | PRIMARY KEY (id),
18 | # UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
19 | # INDEX I18N_LOCALE_ROW(locale, model, foreign_key),
20 | # INDEX I18N_LOCALE_MODEL(locale, model),
21 | # INDEX I18N_FIELD(model, foreign_key, field),
22 | # INDEX I18N_ROW(model, foreign_key),
23 | INDEX locale (locale),
24 | INDEX model (model),
25 | INDEX row_id (foreign_key),
26 | INDEX field (field)
27 | );
--------------------------------------------------------------------------------
/config/Migrations/20150428111734_add_user_id_to_articles.php:
--------------------------------------------------------------------------------
1 | table('articles');
26 | $articles->addColumn('user_id', 'integer', ['after' => 'category_id'])
27 | ->save();
28 | }
29 |
30 | /**
31 | * Migrate Down.
32 | */
33 | public function down()
34 | {
35 | $articles = $this->table('articles');
36 | $articles->removeColumn('user_id')
37 | ->save();
38 | }
39 | }
--------------------------------------------------------------------------------
/config/Migrations/20150419074519_create_articles.php:
--------------------------------------------------------------------------------
1 | table('articles');
26 | $table->addColumn('title', 'string', ['limit' => 50])
27 | ->addColumn('body', 'text')
28 | ->addColumn('created', 'datetime')
29 | ->addColumn('modified', 'datetime')
30 | ->create();
31 | }
32 |
33 | /**
34 | * Migrate Down.
35 | */
36 | public function down()
37 | {
38 | $this->dropTable('articles');
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Template/Error/error500.ctp:
--------------------------------------------------------------------------------
1 | layout = 'dev_error';
7 |
8 | $this->assign('title', $message);
9 | $this->assign('templateName', 'error500.ctp');
10 |
11 | $this->start('file');
12 | ?>
13 | queryString)) : ?>
14 |
15 | SQL Query:
16 | = h($error->queryString) ?>
17 |
18 |
19 | params)) : ?>
20 | SQL Query Params:
21 | = Debugger::dump($error->params) ?>
22 |
23 | element('auto_table_warning');
25 |
26 | if (extension_loaded('xdebug')):
27 | xdebug_print_function_stack();
28 | endif;
29 |
30 | $this->end();
31 | endif;
32 | ?>
33 | = __d('cake', 'An Internal Error Has Occurred') ?>
34 |
35 | = __d('cake', 'Error') ?>:
36 | = h($message) ?>
37 |
38 |
--------------------------------------------------------------------------------
/src/Template/Categories/add.ctp:
--------------------------------------------------------------------------------
1 |
2 |
= __('Actions') ?>
3 |
4 | = $this->Html->link(__('List Categories'), ['action' => 'index']) ?>
5 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
6 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
7 |
8 |
9 |
10 | = $this->Form->create($category); ?>
11 |
12 | = __('Add Category') ?>
13 | Form->input('parent_id');
15 | echo $this->Form->input('lft');
16 | echo $this->Form->input('rght');
17 | echo $this->Form->input('name');
18 | echo $this->Form->input('description');
19 | ?>
20 |
21 | = $this->Form->button(__('Submit')) ?>
22 | = $this->Form->end() ?>
23 |
24 |
--------------------------------------------------------------------------------
/bin/cake.bat:
--------------------------------------------------------------------------------
1 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
2 | ::
3 | :: Bake is a shell script for running CakePHP bake script
4 | ::
5 | :: CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6 | :: Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
12 | :: @link http://cakephp.org CakePHP(tm) Project
13 | :: @since 2.0.0
14 | :: @license http://www.opensource.org/licenses/mit-license.php MIT License
15 | ::
16 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
17 |
18 | :: In order for this script to work as intended, the cake\console\ folder must be in your PATH
19 |
20 | @echo.
21 | @echo off
22 |
23 | SET app=%0
24 | SET lib=%~dp0
25 |
26 | php "%lib%cake.php" %*
27 |
28 | echo.
29 |
30 | exit /B %ERRORLEVEL%
31 |
--------------------------------------------------------------------------------
/src/Template/Error/error400.ctp:
--------------------------------------------------------------------------------
1 | layout = 'dev_error';
6 |
7 | $this->assign('title', $message);
8 | $this->assign('templateName', 'error400.ctp');
9 |
10 | $this->start('file');
11 | ?>
12 | queryString)) : ?>
13 |
14 | SQL Query:
15 | = h($error->queryString) ?>
16 |
17 |
18 | params)) : ?>
19 | SQL Query Params:
20 | = Debugger::dump($error->params) ?>
21 |
22 | = $this->element('auto_table_warning') ?>
23 | end();
29 | endif;
30 | ?>
31 | = h($message) ?>
32 |
33 | = __d('cake', 'Error') ?>:
34 | = sprintf(
35 | __d('cake', 'The requested address %s was not found on this server.'),
36 | "'{$url}' "
37 | ) ?>
38 |
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Kenichiro Kishida
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/Template/Articles/index.ctp:
--------------------------------------------------------------------------------
1 |
2 |
3 | Blog articles
4 | = $this->Html->link('Add Article', ['action' => 'add']) ?>
5 |
6 |
7 | Id
8 | Title
9 | Created
10 | Actions
11 |
12 |
13 |
14 |
15 |
16 |
17 | = $article->id ?>
18 |
19 | = $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
20 |
21 |
22 | = $article->created->format(DATE_RFC850) ?>
23 |
24 |
25 | = $this->Form->postLink(
26 | 'Delete',
27 | ['action' => 'delete', $article->id],
28 | ['confirm' => 'Are you sure?'])
29 | ?>
30 | = $this->Html->link('Edit', ['action' => 'edit', $article->id]) ?>
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/config/Migrations/20150428105014_create_users.php:
--------------------------------------------------------------------------------
1 | table('users');
26 | $users->addColumn('username', 'string', ['limit' => 50])
27 | ->addColumn('password', 'string', ['limit' => 255])
28 | ->addColumn('role', 'string', ['limit' => 20])
29 | ->addColumn('created', 'datetime', ['null' => true, 'default' => null])
30 | ->addColumn('modified', 'datetime', ['null' => true, 'default' => null])
31 | ->create();
32 | }
33 |
34 | /**
35 | * Migrate Down.
36 | */
37 | public function down()
38 | {
39 | $this->dropTable('users');
40 | }
41 | }
--------------------------------------------------------------------------------
/config/bootstrap_cli.php:
--------------------------------------------------------------------------------
1 | getHash();
16 | Fabricate::create('Users', count($users), function($data, $world) use($users) {
17 | $index = $world->sequence('index', 0);
18 | return [
19 | 'username' => $users[$index]['Username'],
20 | 'password' => $users[$index]['Password'],
21 | 'firstname' => $users[$index]['FirstName'],
22 | 'lastname' => $users[$index]['LastName']];
23 | });
24 | }
25 |
26 | /**
27 | * @Given I login :username :password
28 | */
29 | public function iLogin($username, $password)
30 | {
31 | $page = $this->getSession()->getPage();
32 |
33 | $page->findField('Username')->setValue($username);
34 | $page->findField('Password')->setValue($password);
35 | $page->findButton("Login")->press();
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/tests/TestCase/Model/Table/UsersTableTest.php:
--------------------------------------------------------------------------------
1 | 'app.users'
21 | ];
22 |
23 | /**
24 | * setUp method
25 | *
26 | * @return void
27 | */
28 | public function setUp()
29 | {
30 | parent::setUp();
31 | $config = TableRegistry::exists('Users') ? [] : ['className' => 'App\Model\Table\UsersTable'];
32 | $this->Users = TableRegistry::get('Users', $config);
33 | }
34 |
35 | /**
36 | * tearDown method
37 | *
38 | * @return void
39 | */
40 | public function tearDown()
41 | {
42 | unset($this->Users);
43 |
44 | parent::tearDown();
45 | }
46 |
47 | /**
48 | * Test validationDefault method
49 | *
50 | * @return void
51 | */
52 | public function testValidationDefault()
53 | {
54 | $this->markTestIncomplete('Not implemented yet.');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Template/Categories/edit.ctp:
--------------------------------------------------------------------------------
1 |
2 |
= __('Actions') ?>
3 |
4 | = $this->Form->postLink(
5 | __('Delete'),
6 | ['action' => 'delete', $category->id],
7 | ['confirm' => __('Are you sure you want to delete # {0}?', $category->id)]
8 | )
9 | ?>
10 | = $this->Html->link(__('List Categories'), ['action' => 'index']) ?>
11 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
12 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
13 |
14 |
15 |
16 | = $this->Form->create($category); ?>
17 |
18 | = __('Edit Category') ?>
19 | Form->input('parent_id');
21 | echo $this->Form->input('lft');
22 | echo $this->Form->input('rght');
23 | echo $this->Form->input('name');
24 | echo $this->Form->input('description');
25 | ?>
26 |
27 | = $this->Form->button(__('Submit')) ?>
28 | = $this->Form->end() ?>
29 |
30 |
--------------------------------------------------------------------------------
/webroot/index.php:
--------------------------------------------------------------------------------
1 | dispatch(
35 | Request::createFromGlobals(),
36 | new Response()
37 | );
38 |
--------------------------------------------------------------------------------
/bin/cake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ################################################################################
3 | #
4 | # Bake is a shell script for running CakePHP bake script
5 | #
6 | # CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
7 | # Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
14 | # @link http://cakephp.org CakePHP(tm) Project
15 | # @since 1.2.0
16 | # @license http://www.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 | exec php "$CONSOLE"/cake.php "$@"
40 | exit
41 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cakephp/app",
3 | "description": "CakePHP skeleton app",
4 | "homepage": "http://cakephp.org",
5 | "type": "project",
6 | "license": "MIT",
7 | "require": {
8 | "php": ">=5.4.16",
9 | "cakephp/cakephp": "~3.0",
10 | "mobiledetect/mobiledetectlib": "2.*",
11 | "cakephp/migrations": "~1.0",
12 | "cakephp/plugin-installer": "*",
13 | "vlucas/phpdotenv": "~1.1@dev"
14 | },
15 | "require-dev": {
16 | "psy/psysh": "@stable",
17 | "cakephp/debug_kit": "~3.0",
18 | "cakephp/bake": "~1.0",
19 | "behat/mink-extension": "~2.0@dev",
20 | "behat/mink-goutte-driver": "~1.1@dev",
21 | "sizuhiko/cake_fabricate": "dev-master",
22 | "phpunit/phpunit": "~4.8@dev"
23 | },
24 | "suggest": {
25 | "cakephp/cakephp-codesniffer": "Allows to check the code against the coding standards used in CakePHP."
26 | },
27 | "autoload": {
28 | "psr-4": {
29 | "App\\": "src"
30 | }
31 | },
32 | "autoload-dev": {
33 | "psr-4": {
34 | "App\\Test\\": "tests",
35 | "Cake\\Test\\": "./vendor/cakephp/cakephp/tests"
36 | }
37 | },
38 | "scripts": {
39 | "post-install-cmd": "App\\Console\\Installer::postInstall",
40 | "post-autoload-dump": "Cake\\Composer\\Installer\\PluginInstaller::postAutoloadDump"
41 | },
42 | "minimum-stability": "dev",
43 | "prefer-stable": true
44 | }
45 |
--------------------------------------------------------------------------------
/tests/TestCase/Controller/CategoriesControllerTest.php:
--------------------------------------------------------------------------------
1 | 'app.categories',
20 | 'Articles' => 'app.articles'
21 | ];
22 |
23 | /**
24 | * Test index method
25 | *
26 | * @return void
27 | */
28 | public function testIndex()
29 | {
30 | $this->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/Template/Users/view.ctp:
--------------------------------------------------------------------------------
1 |
2 |
= __('Actions') ?>
3 |
4 | = $this->Html->link(__('Edit User'), ['action' => 'edit', $user->id]) ?>
5 | = $this->Form->postLink(__('Delete User'), ['action' => 'delete', $user->id], ['confirm' => __('Are you sure you want to delete # {0}?', $user->id)]) ?>
6 | = $this->Html->link(__('List Users'), ['action' => 'index']) ?>
7 | = $this->Html->link(__('New User'), ['action' => 'add']) ?>
8 |
9 |
10 |
11 |
= h($user->id) ?>
12 |
13 |
14 |
15 |
= h($user->username) ?>
16 |
17 |
= h($user->password) ?>
18 |
19 |
= h($user->role) ?>
20 |
21 |
22 |
23 |
= $this->Number->format($user->id) ?>
24 |
25 |
26 |
27 |
= h($user->created) ?>
28 |
29 |
= h($user->modified) ?>
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/features/articles.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | Feature:
3 | In order to tell the masses what's on my mind
4 | As a user
5 | I want to read articles on the site
6 |
7 | Background:
8 | Given there is a post:
9 | | Title | Body |
10 | | The title | This is the post body. |
11 | | A title once again | And the post body follows. |
12 | | Title strikes back | This is really exciting! Not. |
13 | And there is a user:
14 | | Username | Password | FirstName | LastName |
15 | | alice | ecila | Alice | Smith |
16 | | bob | obo | Bob | Johnson |
17 | And there is a category:
18 | | Name |
19 | | Events |
20 | | Computers |
21 | | Foods |
22 |
23 | Scenario: Show articles
24 | When I am on "TopPage"
25 | Then I should see "The title"
26 | And I should see "A title once again"
27 | And I should see "Title strikes back"
28 |
29 | Scenario: Show the article
30 | Given I am on "TopPage"
31 | When I follow "A title once again"
32 | Then I should see "And the post body follows."
33 |
34 | Scenario: Add new article
35 | Given I am on "TopPage"
36 | And I follow "Add"
37 | And I login "bob" "obo"
38 | When I post article form :
39 | | Label | Value |
40 | | Categories | Events |
41 | | Title | Today is Party |
42 | | Body | From 19:30 with Alice |
43 | And I should see "Your article has been saved."
44 | And I should see "Today is party"
45 |
46 | Scenario: Remove article
47 | Given I am on "TopPage"
48 | When I delete article "Title strikes back"
49 | Then I should not see "Title strikes back"
50 |
--------------------------------------------------------------------------------
/src/Model/Table/UsersTable.php:
--------------------------------------------------------------------------------
1 | table('users');
25 | $this->displayField('id');
26 | $this->primaryKey('id');
27 | $this->addBehavior('Timestamp');
28 | }
29 |
30 | /**
31 | * Default validation rules.
32 | *
33 | * @param \Cake\Validation\Validator $validator Validator instance.
34 | * @return \Cake\Validation\Validator
35 | */
36 | public function validationDefault(Validator $validator)
37 | {
38 | return $validator
39 | ->notEmpty('username', 'A username is required')
40 | ->notEmpty('password', 'A password is required')
41 | ->notEmpty('role', 'A role is required')
42 | ->add('role', 'inList', [
43 | 'rule' => ['inList', ['admin', 'author']],
44 | 'message' => 'Please enter a valid role'
45 | ]);
46 | }
47 |
48 | /**
49 | * Returns a rules checker object that will be used for validating
50 | * application integrity.
51 | *
52 | * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
53 | * @return \Cake\ORM\RulesChecker
54 | */
55 | public function buildRules(RulesChecker $rules)
56 | {
57 | $rules->add($rules->isUnique(['username']));
58 | return $rules;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/TestCase/Model/Table/CategoriesTableTest.php:
--------------------------------------------------------------------------------
1 | 'app.categories',
21 | 'Articles' => 'app.articles'
22 | ];
23 |
24 | /**
25 | * setUp method
26 | *
27 | * @return void
28 | */
29 | public function setUp()
30 | {
31 | parent::setUp();
32 | $config = TableRegistry::exists('Categories') ? [] : ['className' => 'App\Model\Table\CategoriesTable'];
33 | $this->Categories = TableRegistry::get('Categories', $config);
34 | }
35 |
36 | /**
37 | * tearDown method
38 | *
39 | * @return void
40 | */
41 | public function tearDown()
42 | {
43 | unset($this->Categories);
44 |
45 | parent::tearDown();
46 | }
47 |
48 | /**
49 | * Test initialize method
50 | *
51 | * @return void
52 | */
53 | public function testInitialize()
54 | {
55 | $this->markTestIncomplete('Not implemented yet.');
56 | }
57 |
58 | /**
59 | * Test validationDefault method
60 | *
61 | * @return void
62 | */
63 | public function testValidationDefault()
64 | {
65 | $this->markTestIncomplete('Not implemented yet.');
66 | }
67 |
68 | /**
69 | * Test buildRules method
70 | *
71 | * @return void
72 | */
73 | public function testBuildRules()
74 | {
75 | $this->markTestIncomplete('Not implemented yet.');
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/config/Migrations/20150428102422_create_categories.php:
--------------------------------------------------------------------------------
1 | table('categories');
26 | $categories->addColumn('parent_id', 'integer', ['null' => true, 'default' => null])
27 | ->addColumn('lft', 'integer', ['null' => true, 'default' => null])
28 | ->addColumn('rght', 'integer', ['null' => true, 'default' => null])
29 | ->addColumn('name', 'string', ['limit' => 255])
30 | ->addColumn('description', 'string', ['limit' => 255, 'null' => true, 'default' => null])
31 | ->addColumn('created', 'datetime')
32 | ->addColumn('modified', 'datetime', ['null' => true, 'default' => null])
33 | ->create();
34 |
35 | $articles = $this->table('articles');
36 | $articles->changeColumn('body', 'text', ['null' => true, 'default' => null])
37 | ->addColumn('category_id', 'integer', ['null' => true, 'default' => null, 'after' => 'body'])
38 | ->save();
39 | }
40 |
41 | /**
42 | * Migrate Down.
43 | */
44 | public function down()
45 | {
46 | $articles = $this->table('articles');
47 | $articles->changeColumn('body', 'text')
48 | ->removeColumn('category_id')
49 | ->save();
50 |
51 | $this->dropTable('categories');
52 | }
53 | }
--------------------------------------------------------------------------------
/src/Template/Layout/error.ctp:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 | = $this->Html->charset() ?>
22 |
23 | = $cakeDescription ?>:
24 | = $this->fetch('title') ?>
25 |
26 | = $this->Html->meta('icon') ?>
27 |
28 | = $this->Html->css('base.css') ?>
29 | = $this->Html->css('cake.css') ?>
30 |
31 | = $this->fetch('meta') ?>
32 | = $this->fetch('css') ?>
33 | = $this->fetch('script') ?>
34 |
35 |
36 |
37 |
40 |
41 | = $this->Flash->render() ?>
42 |
43 | = $this->fetch('content') ?>
44 |
45 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/Template/Layout/default.ctp:
--------------------------------------------------------------------------------
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 |
46 |
47 |
48 |
49 | = $this->Flash->render() ?>
50 |
51 |
52 | = $this->fetch('content') ?>
53 |
54 |
55 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/features/bootstrap/ArticlesContext.php:
--------------------------------------------------------------------------------
1 | getHash();
17 | Fabricate::create('Articles', count($posts), function($data, $world) use($posts) {
18 | $index = $world->sequence('index', 0);
19 | return ['title'=>$posts[$index]['Title'], 'body'=>$posts[$index]['Body']];
20 | });
21 | }
22 |
23 | /**
24 | * @When I post article form :
25 | */
26 | public function iPostArticleForm(TableNode $table)
27 | {
28 | $hash = $table->getHash();
29 | $page = $this->getSession()->getPage();
30 |
31 | foreach ($hash as $field) {
32 | $element = $page->findField($field['Label']);
33 | if ('select' == $element->getTagName()) {
34 | $element->selectOption($field['Value']);
35 | } else {
36 | $element->setValue($field['Value']);
37 | }
38 | }
39 | $page->findButton("Save Article")->press();
40 | }
41 |
42 | /**
43 | * @When I delete article :title
44 | */
45 | public function iDeleteArticle($title)
46 | {
47 | $page = $this->getSession()->getPage();
48 | $table = $page->find('css', 'table#articles');
49 | foreach ($table->findAll('css', 'tr') as $tr) {
50 | if (!$tr->has('css', 'td')) {
51 | continue; // skip title row
52 | }
53 | if ($tr->findAll('css', 'td')[1]->getText() == $title) {
54 | $tr->find('css', 'form')->submit(); // submit delete form
55 | return;
56 | }
57 | }
58 | throw new ElementNotFoundException($this->getSession(), 'article title', $title, null);
59 | }
60 | }
--------------------------------------------------------------------------------
/src/Template/Users/index.ctp:
--------------------------------------------------------------------------------
1 |
2 |
= __('Actions') ?>
3 |
4 | = $this->Html->link(__('New User'), ['action' => 'add']) ?>
5 |
6 |
7 |
8 |
9 |
10 |
11 | = $this->Paginator->sort('id') ?>
12 | = $this->Paginator->sort('username') ?>
13 | = $this->Paginator->sort('password') ?>
14 | = $this->Paginator->sort('role') ?>
15 | = $this->Paginator->sort('created') ?>
16 | = $this->Paginator->sort('modified') ?>
17 | = __('Actions') ?>
18 |
19 |
20 |
21 |
22 |
23 | = $this->Number->format($user->id) ?>
24 | = h($user->username) ?>
25 | = h($user->password) ?>
26 | = h($user->role) ?>
27 | = h($user->created) ?>
28 | = h($user->modified) ?>
29 |
30 | = $this->Html->link(__('View'), ['action' => 'view', $user->id]) ?>
31 | = $this->Html->link(__('Edit'), ['action' => 'edit', $user->id]) ?>
32 | = $this->Form->postLink(__('Delete'), ['action' => 'delete', $user->id], ['confirm' => __('Are you sure you want to delete # {0}?', $user->id)]) ?>
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
= $this->Paginator->counter() ?>
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/Controller/PagesController.php:
--------------------------------------------------------------------------------
1 | redirect('/');
45 | }
46 | $page = $subpage = null;
47 |
48 | if (!empty($path[0])) {
49 | $page = $path[0];
50 | }
51 | if (!empty($path[1])) {
52 | $subpage = $path[1];
53 | }
54 | $this->set(compact('page', 'subpage'));
55 |
56 | try {
57 | $this->render(implode('/', $path));
58 | } catch (MissingTemplateException $e) {
59 | if (Configure::read('debug')) {
60 | throw $e;
61 | }
62 | throw new NotFoundException();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Controller/UsersController.php:
--------------------------------------------------------------------------------
1 | Auth->allow(['add', 'logout']);
20 | }
21 |
22 | public function login()
23 | {
24 | if ($this->request->is('post')) {
25 | $user = $this->Auth->identify();
26 | if ($user) {
27 | $this->Auth->setUser($user);
28 | return $this->redirect($this->Auth->redirectUrl());
29 | }
30 | $this->Flash->error(__('Invalid username or password, try again'));
31 | }
32 | }
33 |
34 | public function logout()
35 | {
36 | return $this->redirect($this->Auth->logout());
37 | }
38 |
39 | public function index()
40 | {
41 | $this->set('users', $this->paginate($this->Users));
42 | $this->set('_serialize', ['users']);
43 | }
44 |
45 | public function view($id)
46 | {
47 | if (!$id) {
48 | throw new NotFoundException(__('Invalid user'));
49 | }
50 |
51 | $user = $this->Users->get($id);
52 | $this->set(compact('user'));
53 | }
54 |
55 | public function add()
56 | {
57 | $user = $this->Users->newEntity();
58 | if ($this->request->is('post')) {
59 | $user = $this->Users->patchEntity($user, $this->request->data);
60 | if ($this->Users->save($user)) {
61 | $this->Flash->success(__('The user has been saved.'));
62 | return $this->redirect(['action' => 'add']);
63 | }
64 | $this->Flash->error(__('Unable to add the user.'));
65 | }
66 | $this->set('user', $user);
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/src/Controller/AppController.php:
--------------------------------------------------------------------------------
1 | loadComponent('Flash');
41 | $this->loadComponent('Auth', [
42 | 'authorize' => ['Controller'], // Added this line
43 | 'loginRedirect' => [
44 | 'controller' => 'Articles',
45 | 'action' => 'index'
46 | ],
47 | 'logoutRedirect' => [
48 | 'controller' => 'Pages',
49 | 'action' => 'display',
50 | 'home'
51 | ]
52 | ]);
53 | }
54 |
55 | public function beforeFilter(Event $event)
56 | {
57 | $this->Auth->allow(['index', 'view', 'display']);
58 | }
59 |
60 | public function isAuthorized($user)
61 | {
62 | // Admin can access every action
63 | if (isset($user['role']) && $user['role'] === 'admin') {
64 | return true;
65 | }
66 |
67 | // Default deny
68 | return false;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Model/Table/CategoriesTable.php:
--------------------------------------------------------------------------------
1 | table('categories');
25 | $this->displayField('name');
26 | $this->primaryKey('id');
27 | $this->addBehavior('Timestamp');
28 | $this->addBehavior('Tree');
29 | $this->belongsTo('ParentCategories', [
30 | 'className' => 'Categories',
31 | 'foreignKey' => 'parent_id'
32 | ]);
33 | $this->hasMany('Articles', [
34 | 'foreignKey' => 'category_id'
35 | ]);
36 | $this->hasMany('ChildCategories', [
37 | 'className' => 'Categories',
38 | 'foreignKey' => 'parent_id'
39 | ]);
40 | }
41 |
42 | /**
43 | * Default validation rules.
44 | *
45 | * @param \Cake\Validation\Validator $validator Validator instance.
46 | * @return \Cake\Validation\Validator
47 | */
48 | public function validationDefault(Validator $validator)
49 | {
50 | $validator
51 | ->add('id', 'valid', ['rule' => 'numeric'])
52 | ->allowEmpty('id', 'create')
53 | ->add('lft', 'valid', ['rule' => 'numeric'])
54 | ->allowEmpty('lft')
55 | ->add('rght', 'valid', ['rule' => 'numeric'])
56 | ->allowEmpty('rght')
57 | ->requirePresence('name', 'create')
58 | ->notEmpty('name')
59 | ->allowEmpty('description');
60 |
61 | return $validator;
62 | }
63 |
64 | /**
65 | * Returns a rules checker object that will be used for validating
66 | * application integrity.
67 | *
68 | * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
69 | * @return \Cake\ORM\RulesChecker
70 | */
71 | public function buildRules(RulesChecker $rules)
72 | {
73 | $rules->add($rules->existsIn(['parent_id'], 'ParentCategories'));
74 | return $rules;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/config/paths.php:
--------------------------------------------------------------------------------
1 | adaptor = new CakeFabricateAdaptor([
47 | CakeFabricateAdaptor::OPTION_FILTER_KEY => true,
48 | CakeFabricateAdaptor::OPTION_VALIDATE => false
49 | ]);
50 | });
51 |
52 | $this->fixtureInjector = new FixtureInjector(new FixtureManager());
53 | $this->fixture = new BddAllFixture();
54 | }
55 |
56 | /** @BeforeScenario */
57 | public function beforeScenario(BeforeScenarioScope $scope)
58 | {
59 | $this->fixtureInjector->startTest($this->fixture);
60 | }
61 |
62 | /** @AfterScenario */
63 | public function afterScenario(AfterScenarioScope $scope)
64 | {
65 | $this->fixtureInjector->endTest($this->fixture, time());
66 | }
67 |
68 |
69 | }
70 |
71 | class BddAllFixture extends TestCase {
72 | public $fixtures = [
73 | 'Categories' => 'app.categories',
74 | 'Articles' => 'app.articles',
75 | 'Users' => 'app.users',
76 | 'Categories' => 'app.categories'
77 | ];
78 | }
79 |
--------------------------------------------------------------------------------
/config/routes.php:
--------------------------------------------------------------------------------
1 | connect('/', ['controller' => 'Articles', 'action' => 'index']);
47 |
48 | /**
49 | * Connect catchall routes for all controllers.
50 | *
51 | * Using the argument `InflectedRoute`, the `fallbacks` method is a shortcut for
52 | * `$routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']);`
53 | * `$routes->connect('/:controller/:action/*', [], ['routeClass' => 'InflectedRoute']);`
54 | *
55 | * Any route class can be used with this method, such as:
56 | * - DashedRoute
57 | * - InflectedRoute
58 | * - Route
59 | * - Or your own route class
60 | *
61 | * You can remove these routes once you've connected the
62 | * routes you want in your application.
63 | */
64 | $routes->fallbacks('InflectedRoute');
65 | });
66 |
67 | /**
68 | * Load all plugin routes. See the Plugin documentation on
69 | * how to customize the loading of plugin routes.
70 | */
71 | Plugin::routes();
72 |
--------------------------------------------------------------------------------
/src/Shell/ConsoleShell.php:
--------------------------------------------------------------------------------
1 | err('Unable to load Psy\Shell. ');
37 | $this->err('');
38 | $this->err('Make sure you have installed psysh as a dependency,');
39 | $this->err('and that Psy\Shell is registered in your autoloader.');
40 | $this->err('');
41 | $this->err('If you are using composer run');
42 | $this->err('');
43 | $this->err('$ php composer.phar require --dev psy/psysh ');
44 | $this->err('');
45 | return 1;
46 | }
47 |
48 | $this->out("You can exit with `CTRL-C` or `exit` ");
49 | $this->out('');
50 |
51 | Log::drop('debug');
52 | Log::drop('error');
53 | $this->_io->setLoggers(false);
54 | restore_error_handler();
55 | restore_exception_handler();
56 |
57 | $psy = new PsyShell();
58 | $psy->run();
59 | }
60 |
61 | /**
62 | * Display help for this console.
63 | *
64 | * @return ConsoleOptionParser
65 | */
66 | public function getOptionParser()
67 | {
68 | $parser = new ConsoleOptionParser('console', false);
69 | $parser->description(
70 | 'This shell provides a REPL that you can use to interact ' .
71 | 'with your application in an interactive fashion. You can use ' .
72 | 'it to run adhoc queries with your models, or experiment ' .
73 | 'and explore the features of CakePHP and your application.' .
74 | "\n\n" .
75 | 'You will need to have psysh installed for this Shell to work.'
76 | );
77 | return $parser;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Template/Categories/index.ctp:
--------------------------------------------------------------------------------
1 |
2 |
= __('Actions') ?>
3 |
4 | = $this->Html->link(__('New Category'), ['action' => 'add']) ?>
5 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
6 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
7 |
8 |
9 |
10 |
11 |
12 |
13 | = $this->Paginator->sort('id') ?>
14 | = $this->Paginator->sort('parent_id') ?>
15 | = $this->Paginator->sort('lft') ?>
16 | = $this->Paginator->sort('rght') ?>
17 | = $this->Paginator->sort('name') ?>
18 | = $this->Paginator->sort('description') ?>
19 | = $this->Paginator->sort('created') ?>
20 | = __('Actions') ?>
21 |
22 |
23 |
24 |
25 |
26 | = $this->Number->format($category->id) ?>
27 | = $this->Number->format($category->parent_id) ?>
28 | = $this->Number->format($category->lft) ?>
29 | = $this->Number->format($category->rght) ?>
30 | = h($category->name) ?>
31 | = h($category->description) ?>
32 | = h($category->created) ?>
33 |
34 | = $this->Html->link(__('View'), ['action' => 'view', $category->id]) ?>
35 | = $this->Html->link(__('Edit'), ['action' => 'edit', $category->id]) ?>
36 | = $this->Form->postLink(__('Delete'), ['action' => 'delete', $category->id], ['confirm' => __('Are you sure you want to delete # {0}?', $category->id)]) ?>
37 | = $this->Form->postLink(__('Move down'), ['action' => 'move_down', $category->id], ['confirm' => __('Are you sure you want to move down # {0}?', $category->id)]) ?>
38 | = $this->Form->postLink(__('Move up'), ['action' => 'move_up', $category->id], ['confirm' => __('Are you sure you want to move up # {0}?', $category->id)]) ?>
39 |
40 |
41 |
42 |
43 |
44 |
45 |
50 |
= $this->Paginator->counter() ?>
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/Controller/ArticlesController.php:
--------------------------------------------------------------------------------
1 | loadComponent('Flash'); // Include the FlashComponent
13 | }
14 |
15 | public function index()
16 | {
17 | $this->set('articles', $this->Articles->find('all'));
18 | }
19 |
20 | public function view($id)
21 | {
22 | $article = $this->Articles->get($id);
23 | $this->set(compact('article'));
24 | }
25 |
26 | public function add()
27 | {
28 | $article = $this->Articles->newEntity();
29 | if ($this->request->is('post')) {
30 | $article = $this->Articles->patchEntity($article, $this->request->data);
31 | // Added this line
32 | $article->user_id = $this->Auth->user('id');
33 | if ($this->Articles->save($article)) {
34 | $this->Flash->success(__('Your article has been saved.'));
35 | return $this->redirect(['action' => 'index']);
36 | }
37 | $this->Flash->error(__('Unable to add your article.'));
38 | }
39 | $this->set('article', $article);
40 |
41 | // Just added the categories list to be able to choose
42 | // one category for an article
43 | $categories = $this->Articles->Categories->find('treeList');
44 | $this->set(compact('categories'));
45 | }
46 |
47 | public function edit($id = null)
48 | {
49 | $article = $this->Articles->get($id);
50 | if ($this->request->is(['post', 'put'])) {
51 | $this->Articles->patchEntity($article, $this->request->data);
52 | if ($this->Articles->save($article)) {
53 | $this->Flash->success(__('Your article has been updated.'));
54 | return $this->redirect(['action' => 'index']);
55 | }
56 | $this->Flash->error(__('Unable to update your article.'));
57 | }
58 |
59 | $this->set('article', $article);
60 | }
61 |
62 | public function delete($id)
63 | {
64 | $this->request->allowMethod(['post', 'delete']);
65 |
66 | $article = $this->Articles->get($id);
67 | if ($this->Articles->delete($article)) {
68 | $this->Flash->success(__('The article with id: {0} has been deleted.', h($id)));
69 | return $this->redirect(['action' => 'index']);
70 | }
71 | }
72 |
73 | public function isAuthorized($user)
74 | {
75 | // All registered users can add articles
76 | if ($this->request->action === 'add') {
77 | return true;
78 | }
79 |
80 | // The owner of an article can edit and delete it
81 | if (in_array($this->request->action, ['edit', 'delete'])) {
82 | $articleId = (int)$this->request->params['pass'][0];
83 | if ($this->Articles->isOwnedBy($articleId, $user['id'])) {
84 | return true;
85 | }
86 | }
87 |
88 | return parent::isAuthorized($user);
89 | }
90 | }
--------------------------------------------------------------------------------
/src/Template/Categories/view.ctp:
--------------------------------------------------------------------------------
1 |
2 |
= __('Actions') ?>
3 |
4 | = $this->Html->link(__('Edit Category'), ['action' => 'edit', $category->id]) ?>
5 | = $this->Form->postLink(__('Delete Category'), ['action' => 'delete', $category->id], ['confirm' => __('Are you sure you want to delete # {0}?', $category->id)]) ?>
6 | = $this->Html->link(__('List Categories'), ['action' => 'index']) ?>
7 | = $this->Html->link(__('New Category'), ['action' => 'add']) ?>
8 | = $this->Html->link(__('List Articles'), ['controller' => 'Articles', 'action' => 'index']) ?>
9 | = $this->Html->link(__('New Article'), ['controller' => 'Articles', 'action' => 'add']) ?>
10 |
11 |
12 |
13 |
= h($category->name) ?>
14 |
15 |
16 |
17 |
= h($category->name) ?>
18 |
19 |
= h($category->description) ?>
20 |
21 |
22 |
23 |
= $this->Number->format($category->id) ?>
24 |
25 |
= $this->Number->format($category->parent_id) ?>
26 |
27 |
= $this->Number->format($category->lft) ?>
28 |
29 |
= $this->Number->format($category->rght) ?>
30 |
31 |
32 |
33 |
= h($category->created) ?>
34 |
35 |
= h($category->modified) ?>
36 |
37 |
38 |
39 |
77 |
--------------------------------------------------------------------------------
/src/Controller/CategoriesController.php:
--------------------------------------------------------------------------------
1 | paginate = [
22 | 'contain' => ['ParentCategories']
23 | ];
24 | $this->set('categories', $this->paginate($this->Categories));
25 | $this->set('_serialize', ['categories']);
26 | }
27 |
28 | public function move_up($id = null)
29 | {
30 | $this->request->allowMethod(['post', 'put']);
31 | $category = $this->Categories->get($id);
32 | if ($this->Categories->moveUp($category)) {
33 | $this->Flash->success('The category has been moved Up.');
34 | } else {
35 | $this->Flash->error('The category could not be moved up. Please, try again.');
36 | }
37 | return $this->redirect($this->referer(['action' => 'index']));
38 | }
39 |
40 | public function move_down($id = null)
41 | {
42 | $this->request->allowMethod(['post', 'put']);
43 | $category = $this->Categories->get($id);
44 | if ($this->Categories->moveDown($category)) {
45 | $this->Flash->success('The category has been moved down.');
46 | } else {
47 | $this->Flash->error('The category could not be moved down. Please, try again.');
48 | }
49 | return $this->redirect($this->referer(['action' => 'index']));
50 | }
51 |
52 | /**
53 | * View method
54 | *
55 | * @param string|null $id Category id.
56 | * @return void
57 | * @throws \Cake\Network\Exception\NotFoundException When record not found.
58 | */
59 | public function view($id = null)
60 | {
61 | $category = $this->Categories->get($id, [
62 | 'contain' => ['ParentCategories', 'Articles', 'ChildCategories']
63 | ]);
64 | $this->set('category', $category);
65 | $this->set('_serialize', ['category']);
66 | }
67 |
68 | /**
69 | * Add method
70 | *
71 | * @return void Redirects on successful add, renders view otherwise.
72 | */
73 | public function add()
74 | {
75 | $category = $this->Categories->newEntity();
76 | if ($this->request->is('post')) {
77 | $category = $this->Categories->patchEntity($category, $this->request->data);
78 | if ($this->Categories->save($category)) {
79 | $this->Flash->success('The category has been saved.');
80 | return $this->redirect(['action' => 'index']);
81 | } else {
82 | $this->Flash->error('The category could not be saved. Please, try again.');
83 | }
84 | }
85 | $parentCategories = $this->Categories->ParentCategories->find('list', ['limit' => 200]);
86 | $this->set(compact('category', 'parentCategories'));
87 | $this->set('_serialize', ['category']);
88 | }
89 |
90 | /**
91 | * Edit method
92 | *
93 | * @param string|null $id Category id.
94 | * @return void Redirects on successful edit, renders view otherwise.
95 | * @throws \Cake\Network\Exception\NotFoundException When record not found.
96 | */
97 | public function edit($id = null)
98 | {
99 | $category = $this->Categories->get($id, [
100 | 'contain' => []
101 | ]);
102 | if ($this->request->is(['patch', 'post', 'put'])) {
103 | $category = $this->Categories->patchEntity($category, $this->request->data);
104 | if ($this->Categories->save($category)) {
105 | $this->Flash->success('The category has been saved.');
106 | return $this->redirect(['action' => 'index']);
107 | } else {
108 | $this->Flash->error('The category could not be saved. Please, try again.');
109 | }
110 | }
111 | $parentCategories = $this->Categories->ParentCategories->find('list', ['limit' => 200]);
112 | $this->set(compact('category', 'parentCategories'));
113 | $this->set('_serialize', ['category']);
114 | }
115 |
116 | /**
117 | * Delete method
118 | *
119 | * @param string|null $id Category id.
120 | * @return void Redirects to index.
121 | * @throws \Cake\Network\Exception\NotFoundException When record not found.
122 | */
123 | public function delete($id = null)
124 | {
125 | $this->request->allowMethod(['post', 'delete']);
126 | $category = $this->Categories->get($id);
127 | if ($this->Categories->delete($category)) {
128 | $this->Flash->success('The category has been deleted.');
129 | } else {
130 | $this->Flash->error('The category could not be deleted. Please, try again.');
131 | }
132 | return $this->redirect(['action' => 'index']);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/config/bootstrap.php:
--------------------------------------------------------------------------------
1 | getMessage() . "\n");
69 | }
70 |
71 | // Load an environment local configuration file.
72 | // You can use a file like app_local.php to provide local overrides to your
73 | // shared configuration.
74 | //Configure::load('app_local', 'default');
75 |
76 | // When debug = false the metadata cache should last
77 | // for a very very long time, as we don't want
78 | // to refresh the cache while users are doing requests.
79 | if (!Configure::read('debug')) {
80 | Configure::write('Cache._cake_model_.duration', '+1 years');
81 | Configure::write('Cache._cake_core_.duration', '+1 years');
82 | }
83 |
84 | /**
85 | * Set server timezone to UTC. You can change it to another timezone of your
86 | * choice but using UTC makes time calculations / conversions easier.
87 | */
88 | date_default_timezone_set('UTC');
89 |
90 | /**
91 | * Configure the mbstring extension to use the correct encoding.
92 | */
93 | mb_internal_encoding(Configure::read('App.encoding'));
94 |
95 | /**
96 | * Set the default locale. This controls how dates, number and currency is
97 | * formatted and sets the default language to use for translations.
98 | */
99 | ini_set('intl.default_locale', 'en_US');
100 |
101 | /**
102 | * Register application error and exception handlers.
103 | */
104 | $isCli = php_sapi_name() === 'cli';
105 | if ($isCli) {
106 | (new ConsoleErrorHandler(Configure::read('Error')))->register();
107 | } else {
108 | (new ErrorHandler(Configure::read('Error')))->register();
109 | }
110 |
111 | // Include the CLI bootstrap overrides.
112 | if ($isCli) {
113 | require __DIR__ . '/bootstrap_cli.php';
114 | }
115 |
116 | /**
117 | * Set the full base URL.
118 | * This URL is used as the base of all absolute links.
119 | *
120 | * If you define fullBaseUrl in your config file you can remove this.
121 | */
122 | if (!Configure::read('App.fullBaseUrl')) {
123 | $s = null;
124 | if (env('HTTPS')) {
125 | $s = 's';
126 | }
127 |
128 | $httpHost = env('HTTP_HOST');
129 | if (isset($httpHost)) {
130 | Configure::write('App.fullBaseUrl', 'http' . $s . '://' . $httpHost);
131 | }
132 | unset($httpHost, $s);
133 | }
134 |
135 | Cache::config(Configure::consume('Cache'));
136 | ConnectionManager::config(Configure::consume('Datasources'));
137 | Email::configTransport(Configure::consume('EmailTransport'));
138 | Email::config(Configure::consume('Email'));
139 | Log::config(Configure::consume('Log'));
140 | Security::salt(Configure::consume('Security.salt'));
141 |
142 | /**
143 | * The default crypto extension in 3.0 is OpenSSL.
144 | * If you are migrating from 2.x uncomment this code to
145 | * use a more compatible Mcrypt based implementation
146 | */
147 | // Security::engine(new \Cake\Utility\Crypto\Mcrypt());
148 |
149 | /**
150 | * Setup detectors for mobile and tablet.
151 | */
152 | Request::addDetector('mobile', function ($request) {
153 | $detector = new \Detection\MobileDetect();
154 | return $detector->isMobile();
155 | });
156 | Request::addDetector('tablet', function ($request) {
157 | $detector = new \Detection\MobileDetect();
158 | return $detector->isTablet();
159 | });
160 |
161 | /**
162 | * Custom Inflector rules, can be set to correctly pluralize or singularize
163 | * table, model, controller names or whatever other string is passed to the
164 | * inflection functions.
165 | *
166 | * Inflector::rules('plural', ['/^(inflect)or$/i' => '\1ables']);
167 | * Inflector::rules('irregular' => ['red' => 'redlings']);
168 | * Inflector::rules('uninflected', ['dontinflectme']);
169 | * Inflector::rules('transliteration', ['/å/' => 'aa']);
170 | */
171 |
172 | /**
173 | * Plugins need to be loaded manually, you can either load them one by one or all of them in a single call
174 | * Uncomment one of the lines below, as you need. make sure you read the documentation on Plugin to use more
175 | * advanced ways of loading plugins
176 | *
177 | * Plugin::loadAll(); // Loads all plugins at once
178 | * Plugin::load('Migrations'); //Loads a single plugin named Migrations
179 | *
180 | */
181 |
182 | Plugin::load('Migrations');
183 |
184 | // Only try to load DebugKit in development mode
185 | // Debug Kit should not be installed on a production system
186 | if (Configure::read('debug')) {
187 | Plugin::load('DebugKit', ['bootstrap' => true]);
188 | }
189 |
190 | /**
191 | * Connect middleware/dispatcher filters.
192 | */
193 | DispatcherFactory::add('Asset');
194 | DispatcherFactory::add('Routing');
195 | DispatcherFactory::add('ControllerFactory');
196 |
197 | if (getenv('CAKE_ENV') === 'test') {
198 | ConnectionManager::alias('test', 'default');
199 | }
200 |
--------------------------------------------------------------------------------
/src/Console/Installer.php:
--------------------------------------------------------------------------------
1 | getIO();
37 |
38 | $rootDir = dirname(dirname(__DIR__));
39 |
40 | static::createAppConfig($rootDir, $io);
41 | static::createWritableDirectories($rootDir, $io);
42 |
43 | // ask if the permissions should be changed
44 | if ($io->isInteractive()) {
45 | $validator = function ($arg) {
46 | if (in_array($arg, ['Y', 'y', 'N', 'n'])) {
47 | return $arg;
48 | }
49 | throw new Exception('This is not a valid answer. Please choose Y or n.');
50 | };
51 | $setFolderPermissions = $io->askAndValidate(
52 | 'Set Folder Permissions ? (Default to Y) [Y,n ]? ',
53 | $validator,
54 | 10,
55 | 'Y'
56 | );
57 |
58 | if (in_array($setFolderPermissions, ['Y', 'y'])) {
59 | static::setFolderPermissions($rootDir, $io);
60 | }
61 | } else {
62 | static::setFolderPermissions($rootDir, $io);
63 | }
64 |
65 | static::setSecuritySalt($rootDir, $io);
66 |
67 | if (class_exists('\Cake\Codeception\Console\Installer')) {
68 | \Cake\Codeception\Console\Installer::customizeCodeceptionBinary($event);
69 | }
70 | }
71 |
72 | /**
73 | * Create the config/app.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 createAppConfig($dir, $io)
80 | {
81 | $appConfig = $dir . '/config/app.php';
82 | $defaultConfig = $dir . '/config/app.default.php';
83 | if (!file_exists($appConfig)) {
84 | copy($defaultConfig, $appConfig);
85 | $io->write('Created `config/app.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 | $paths = [
99 | 'logs',
100 | 'tmp',
101 | 'tmp/cache',
102 | 'tmp/cache/models',
103 | 'tmp/cache/persistent',
104 | 'tmp/cache/views',
105 | 'tmp/sessions',
106 | 'tmp/tests'
107 | ];
108 |
109 | foreach ($paths as $path) {
110 | $path = $dir . '/' . $path;
111 | if (!file_exists($path)) {
112 | mkdir($path);
113 | $io->write('Created `' . $path . '` directory');
114 | }
115 | }
116 | }
117 |
118 | /**
119 | * Set globally writable permissions on the "tmp" and "logs" directory.
120 | *
121 | * This is not the most secure default, but it gets people up and running quickly.
122 | *
123 | * @param string $dir The application's root directory.
124 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
125 | * @return void
126 | */
127 | public static function setFolderPermissions($dir, $io)
128 | {
129 | // Change the permissions on a path and output the results.
130 | $changePerms = function ($path, $perms, $io) {
131 | // Get current permissions in decimal format so we can bitmask it.
132 | $currentPerms = octdec(substr(sprintf('%o', fileperms($path)), -4));
133 | if (($currentPerms & $perms) == $perms) {
134 | return;
135 | }
136 |
137 | $res = chmod($path, $currentPerms | $perms);
138 | if ($res) {
139 | $io->write('Permissions set on ' . $path);
140 | } else {
141 | $io->write('Failed to set permissions on ' . $path);
142 | }
143 | };
144 |
145 | $walker = function ($dir, $perms, $io) use (&$walker, $changePerms) {
146 | $files = array_diff(scandir($dir), ['.', '..']);
147 | foreach ($files as $file) {
148 | $path = $dir . '/' . $file;
149 |
150 | if (!is_dir($path)) {
151 | continue;
152 | }
153 |
154 | $changePerms($path, $perms, $io);
155 | $walker($path, $perms, $io);
156 | }
157 | };
158 |
159 | $worldWritable = bindec('0000000111');
160 | $walker($dir . '/tmp', $worldWritable, $io);
161 | $changePerms($dir . '/tmp', $worldWritable, $io);
162 | $changePerms($dir . '/logs', $worldWritable, $io);
163 | }
164 |
165 | /**
166 | * Set the security.salt value in the application's config file.
167 | *
168 | * @param string $dir The application's root directory.
169 | * @param \Composer\IO\IOInterface $io IO interface to write to console.
170 | * @return void
171 | */
172 | public static function setSecuritySalt($dir, $io)
173 | {
174 | $config = $dir . '/config/app.php';
175 | $content = file_get_contents($config);
176 |
177 | $newKey = hash('sha256', $dir . php_uname() . microtime(true));
178 | $content = str_replace('__SALT__', $newKey, $content, $count);
179 |
180 | if ($count == 0) {
181 | $io->write('No Security.salt placeholder to replace.');
182 | return;
183 | }
184 |
185 | $result = file_put_contents($config, $content);
186 | if ($result) {
187 | $io->write('Updated Security.salt value in config/app.php');
188 | return;
189 | }
190 | $io->write('Unable to update Security.salt value.');
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/webroot/css/cake.css:
--------------------------------------------------------------------------------
1 | a.disabled {
2 | pointer-events: none;
3 | }
4 |
5 | a:hover {
6 | color: #15848F;
7 | }
8 |
9 | a {
10 | color: #1798A5;
11 | }
12 |
13 | .side-nav li a:not(.button) {
14 | color: #15848F;
15 | }
16 |
17 | .side-nav li a:not(.button):hover {
18 | color: #15848F;
19 | }
20 |
21 | header {
22 | background-color: #15848F;
23 | color: #ffffff;
24 | font-size: 30px;
25 | height: 84px;
26 | line-height: 64px;
27 | padding: 16px 0px;
28 | box-shadow: 0px 1px rgba(0, 0, 0, 0.24);
29 | }
30 |
31 | header .header-title {
32 | padding-left:80px
33 | }
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 | .index table {
56 | margin-top: 2rem;
57 | border: 0;
58 | }
59 |
60 | .index table thead {
61 | height: 3.5rem;
62 | }
63 |
64 | .header-help {
65 | float: right;
66 | margin-right:2rem;
67 | margin-top: -80px;
68 | font-size:16px;
69 | }
70 |
71 | .header-help span {
72 | font-weight: normal;
73 | text-align: center;
74 | text-decoration: none;
75 | line-height: 1;
76 | white-space: nowrap;
77 | display: inline-block;
78 | padding: 0.25rem 0.5rem 0.375rem;
79 | font-size: 0.8rem;
80 | background-color: #0097a7;
81 | color: #FFF;
82 | border-radius: 1000px;
83 | }
84 |
85 | .header-help a {
86 | color: #fff;
87 | }
88 |
89 | ul.pagination li a {
90 | color: rgba(0, 0 ,0 , 0.54);
91 | }
92 |
93 | ul.pagination li.active a {
94 | background: none repeat scroll 0% 0% #DCE47E;
95 | color: #FFF;
96 | font-weight: bold;
97 | cursor: default;
98 | }
99 |
100 | .paginator {
101 | text-align: center;
102 | }
103 |
104 | .paginator ul.pagination li {
105 | float: none;
106 | display: inline-block;
107 | }
108 |
109 | .paginator p {
110 | text-align: right;
111 | color: rgba(0, 0 ,0 , 0.54);
112 | }
113 |
114 | button {
115 | background: #8D6E65;
116 | }
117 |
118 | .form button:hover, .form button:focus {
119 | background: #7A6058;
120 | box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.26) !important;
121 | }
122 |
123 | .form button[type="submit"] {
124 | float: right;
125 | text-transform: uppercase;
126 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.26);
127 | }
128 |
129 | .form .error-message {
130 | display: block;
131 | padding: 0.375rem 0.5625rem 0.5625rem;
132 | margin-top: -1px;
133 | margin-bottom: 1rem;
134 | font-size: 0.75rem;
135 | font-weight: normal;
136 | font-style: italic;
137 | color: rgba(0, 0, 0, 0.54);
138 | }
139 |
140 | .required > label {
141 | font-weight: bold;
142 | }
143 | .required > label:after {
144 | content: ' *';
145 | color: #C3232D;
146 | }
147 |
148 | select[multiple] {
149 | min-height:150px;
150 | background: none;
151 | }
152 | input[type=checkbox],
153 | input[type=radio] {
154 | margin-right: 0.5em;
155 | }
156 |
157 | .date select,
158 | .time select,
159 | .datetime select {
160 | display: inline;
161 | width: auto;
162 | margin-right: 10px;
163 | }
164 |
165 | .error label,
166 | .error label.error {
167 | color: #C3232D;
168 | }
169 |
170 | div.message {
171 | border-style: solid;
172 | border-width: 1px;
173 | display: block;
174 | font-weight: normal;
175 | position: relative;
176 | padding: 0.875rem 1.5rem 0.875rem 20%;
177 | transition: opacity 300ms ease-out 0s;
178 | background-color: #DCE47E;
179 | border-color: #DCE47E;
180 | color: #626262;
181 | }
182 |
183 | div.message.error {
184 | background-color: #C3232D;
185 | border-color: #C3232D;
186 | color: #FFF;
187 | }
188 |
189 | div.message:before {
190 | line-height: 0px;
191 | font-size: 20px;
192 | height: 12px;
193 | width: 12px;
194 | border-radius: 15px;
195 | text-align: center;
196 | vertical-align: middle;
197 | display: inline-block;
198 | position: relative;
199 | left: -11px;
200 | background-color: #FFF;
201 | padding: 12px 14px 12px 10px;
202 | content: "i";
203 | color: #DCE47E;
204 | }
205 |
206 | div.message.error:before {
207 | padding: 11px 16px 14px 7px;
208 | color: #C3232D;
209 | content: "x";
210 | }
211 |
212 | .view h2 {
213 | color: #6F6F6F;
214 | }
215 |
216 | .view .columns.strings {
217 | border-radius: 3px;
218 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
219 | margin-right:0.7rem;
220 | }
221 |
222 | .view .numbers {
223 | background-color: #B7E3EC;
224 | color: #FFF;
225 | border-radius: 3px;
226 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
227 | margin-right: 0.7rem;
228 | }
229 |
230 | .view .columns.dates {
231 | border-radius: 3px;
232 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
233 | margin-right:0.7rem;
234 | background-color:#DCE47E;
235 | color: #fff;
236 | }
237 |
238 | .view .columns.booleans {
239 | border-radius: 3px;
240 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
241 | margin-right:0.7rem;
242 | background-color: #8D6E65;
243 | color: #fff;
244 | }
245 |
246 | .view .strings p {
247 | border-bottom: 1px solid #eee;
248 | }
249 | .view .numbers .subheader, .view .dates .subheader {
250 | color:#747474;
251 | }
252 | .view .booleans .subheader {
253 | color: #E9E9E9
254 | }
255 |
256 | .view .texts .columns {
257 | margin-top:1.2rem;
258 | border-bottom: 1px solid #eee;
259 | }
260 |
261 |
262 | /** Notices and Errors **/
263 | .cake-error,
264 | .cake-debug,
265 | .notice,
266 | p.error,
267 | p.notice {
268 | display: block;
269 | clear: both;
270 | background-repeat: repeat-x;
271 | margin-bottom: 18px;
272 | padding: 7px 14px;
273 | border-radius: 3px;
274 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
275 | }
276 |
277 | .cake-debug,
278 | .notice,
279 | p.notice {
280 | color: #000000;
281 | background: #ffcc00;
282 | }
283 |
284 | .cake-error,
285 | p.error {
286 | color: #fff;
287 | background: #C3232D;
288 | }
289 |
290 | pre {
291 | background: none repeat scroll 0% 0% #FFF;
292 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
293 | margin: 15px 0px;
294 | color: rgba(0, 0 ,0 , 0.74);
295 | padding:5px;
296 | }
297 |
298 | .cake-error .cake-stack-trace {
299 | margin-top:10px;
300 | }
301 |
302 | .cake-stack-trace code {
303 | background: inherit;
304 | border:0;
305 | }
306 |
307 | .cake-code-dump .code-highlight {
308 | display: block;
309 | background-color: #FFC600;
310 | }
311 |
312 | .cake-error a,
313 | .cake-error a:hover {
314 | color:#fff;
315 | text-decoration: underline;
316 | }
317 |
318 | .home header {
319 | width: 100%;
320 | height: 85%;
321 | position: relative;
322 | display: table;
323 | }
324 |
325 | .home h1 {
326 | font-family: "Gill Sans MT", Calibri, sans-serif;
327 | }
328 |
329 | .home header .header-image {
330 | display: table-cell;
331 | vertical-align: middle;
332 | text-align: center;
333 | }
334 |
335 | .home header h1 {
336 | color: #fff;
337 | }
338 |
339 | .home .checks {
340 | padding:30px;
341 | color: #626262;
342 | border-radius: 3px;
343 | box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.24);
344 | margin-top:50px;
345 | }
346 |
347 | .checks.platform {
348 | background-color: #B7E3EC;
349 | }
350 |
351 | .checks.filesystem {
352 | background: #DCE47E;
353 | }
354 |
355 | .checks.database {
356 | background-color: #DFF0D8;
357 | padding-bottom: 10px;
358 | margin-bottom: 30px;
359 | }
360 |
361 | .home .checks .success:before, .home .checks .problem:before {
362 | line-height: 0px;
363 | font-size: 28px;
364 | height: 12px;
365 | width: 12px;
366 | border-radius: 15px;
367 | text-align: center;
368 | vertical-align: middle;
369 | display: inline-block;
370 | position: relative;
371 | left: -11px;
372 | }
373 |
374 | .home .checks .success:before {
375 | content: "✓";
376 | color: green;
377 | margin-right: 9px;
378 | }
379 |
380 | .home .checks .problem:before {
381 | content: "✘";
382 | color: red;
383 | margin-right: 9px;
384 | }
385 |
--------------------------------------------------------------------------------
/src/Template/Pages/home.ctp:
--------------------------------------------------------------------------------
1 | layout = false;
22 |
23 | if (!Configure::read('debug')):
24 | throw new NotFoundException();
25 | endif;
26 |
27 | $cakeDescription = 'CakePHP: the rapid development php framework';
28 | ?>
29 |
30 |
31 |
32 | = $this->Html->charset() ?>
33 |
34 |
35 | = $cakeDescription ?>
36 |
37 | = $this->Html->meta('icon') ?>
38 | = $this->Html->css('base.css') ?>
39 | = $this->Html->css('cake.css') ?>
40 |
41 |
42 |
48 |
49 |
54 |
55 | URL rewriting is not properly configured on your server.
56 | 1) Help me configure it
57 | 2) I don't / can't use URL rewriting
58 |
59 |
60 |
61 |
88 |
89 |
90 |
Your tmp directory is writable.
91 |
92 |
Your tmp directory is NOT writable.
93 |
94 |
95 |
96 |
Your logs directory is writable.
97 |
98 |
Your logs directory is NOT writable.
99 |
100 |
101 |
102 |
103 |
The = $settings['className'] ?>Engine is being used for core caching. To change the config edit config/app.php
104 |
105 |
Your cache is NOT working. Please check the settings in config/app.php
106 |
107 |
108 |
109 |
110 |
111 | connect();
115 | } catch (Exception $connectionError) {
116 | $connected = false;
117 | $errorMsg = $connectionError->getMessage();
118 | if (method_exists($connectionError, 'getAttributes')):
119 | $attributes = $connectionError->getAttributes();
120 | if (isset($errorMsg['message'])):
121 | $errorMsg .= '
' . $attributes['message'];
122 | endif;
123 | endif;
124 | }
125 | ?>
126 |
127 |
CakePHP is able to connect to the database.
128 |
129 |
CakePHP is NOT able to connect to the database. = $errorMsg ?>
130 |
131 |
132 |
133 |
134 |
135 |
Editing this Page
136 |
137 | To change the content of this page, edit: src/Template/Pages/home.ctp.
138 | You can also add some CSS styles for your pages at: webroot/css/.
139 |
140 |
141 |
142 |
Getting Started
143 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
More about Cake
156 |
157 | CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Front Controller and MVC.
158 |
159 |
160 | Our primary goal is to provide a structured framework that enables PHP users at all levels to rapidly develop robust web applications, without any loss to flexibility.
161 |
162 |
163 |
185 |
186 |
187 |
188 |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/config/app.default.php:
--------------------------------------------------------------------------------
1 | true,
13 |
14 | /**
15 | * Configure basic information about the application.
16 | *
17 | * - namespace - The namespace to find app classes under.
18 | * - encoding - The encoding used for HTML + database connections.
19 | * - base - The base directory the app resides in. If false this
20 | * will be auto detected.
21 | * - dir - Name of app directory.
22 | * - webroot - The webroot directory.
23 | * - wwwRoot - The file path to webroot.
24 | * - baseUrl - To configure CakePHP to *not* use mod_rewrite and to
25 | * use CakePHP pretty URLs, remove these .htaccess
26 | * files:
27 | * /.htaccess
28 | * /webroot/.htaccess
29 | * And uncomment the baseUrl key below.
30 | * - fullBaseUrl - A base URL to use for absolute links.
31 | * - imageBaseUrl - Web path to the public images directory under webroot.
32 | * - cssBaseUrl - Web path to the public css directory under webroot.
33 | * - jsBaseUrl - Web path to the public js directory under webroot.
34 | * - paths - Configure paths for non class based resources. Supports the
35 | * `plugins`, `templates`, `locales` subkeys, which allow the definition of
36 | * paths for plugins, view templates and locale files respectively.
37 | */
38 | 'App' => [
39 | 'namespace' => 'App',
40 | 'encoding' => 'UTF-8',
41 | 'base' => false,
42 | 'dir' => 'src',
43 | 'webroot' => 'webroot',
44 | 'wwwRoot' => WWW_ROOT,
45 | // 'baseUrl' => env('SCRIPT_NAME'),
46 | 'fullBaseUrl' => false,
47 | 'imageBaseUrl' => 'img/',
48 | 'cssBaseUrl' => 'css/',
49 | 'jsBaseUrl' => 'js/',
50 | 'paths' => [
51 | 'plugins' => [ROOT . DS . 'plugins' . DS],
52 | 'templates' => [APP . 'Template' . DS],
53 | 'locales' => [APP . 'Locale' . DS],
54 | ],
55 | ],
56 |
57 | /**
58 | * Security and encryption configuration
59 | *
60 | * - salt - A random string used in security hashing methods.
61 | * The salt value is also used as the encryption key.
62 | * You should treat it as extremely sensitive data.
63 | */
64 | 'Security' => [
65 | 'salt' => '__SALT__',
66 | ],
67 |
68 | /**
69 | * Apply timestamps with the last modified time to static assets (js, css, images).
70 | * Will append a querystring parameter containing the time the file was modified.
71 | * This is useful for busting browser caches.
72 | *
73 | * Set to true to apply timestamps when debug is true. Set to 'force' to always
74 | * enable timestamping regardless of debug value.
75 | */
76 | 'Asset' => [
77 | // 'timestamp' => true,
78 | ],
79 |
80 | /**
81 | * Configure the cache adapters.
82 | */
83 | 'Cache' => [
84 | 'default' => [
85 | 'className' => 'File',
86 | 'path' => CACHE,
87 | ],
88 |
89 | /**
90 | * Configure the cache used for general framework caching. Path information,
91 | * object listings, and translation cache files are stored with this
92 | * configuration.
93 | */
94 | '_cake_core_' => [
95 | 'className' => 'File',
96 | 'prefix' => 'myapp_cake_core_',
97 | 'path' => CACHE . 'persistent/',
98 | 'serialize' => true,
99 | 'duration' => '+2 minutes',
100 | ],
101 |
102 | /**
103 | * Configure the cache for model and datasource caches. This cache
104 | * configuration is used to store schema descriptions, and table listings
105 | * in connections.
106 | */
107 | '_cake_model_' => [
108 | 'className' => 'File',
109 | 'prefix' => 'myapp_cake_model_',
110 | 'path' => CACHE . 'models/',
111 | 'serialize' => true,
112 | 'duration' => '+2 minutes',
113 | ],
114 | ],
115 |
116 | /**
117 | * Configure the Error and Exception handlers used by your application.
118 | *
119 | * By default errors are displayed using Debugger, when debug is true and logged
120 | * by Cake\Log\Log when debug is false.
121 | *
122 | * In CLI environments exceptions will be printed to stderr with a backtrace.
123 | * In web environments an HTML page will be displayed for the exception.
124 | * With debug true, framework errors like Missing Controller will be displayed.
125 | * When debug is false, framework errors will be coerced into generic HTTP errors.
126 | *
127 | * Options:
128 | *
129 | * - `errorLevel` - int - The level of errors you are interested in capturing.
130 | * - `trace` - boolean - Whether or not backtraces should be included in
131 | * logged errors/exceptions.
132 | * - `log` - boolean - Whether or not you want exceptions logged.
133 | * - `exceptionRenderer` - string - The class responsible for rendering
134 | * uncaught exceptions. If you choose a custom class you should place
135 | * the file for that class in src/Error. This class needs to implement a
136 | * render method.
137 | * - `skipLog` - array - List of exceptions to skip for logging. Exceptions that
138 | * extend one of the listed exceptions will also be skipped for logging.
139 | * E.g.:
140 | * `'skipLog' => ['Cake\Network\Exception\NotFoundException', 'Cake\Network\Exception\UnauthorizedException']`
141 | */
142 | 'Error' => [
143 | 'errorLevel' => E_ALL & ~E_DEPRECATED,
144 | 'exceptionRenderer' => 'Cake\Error\ExceptionRenderer',
145 | 'skipLog' => [],
146 | 'log' => true,
147 | 'trace' => true,
148 | ],
149 |
150 | /**
151 | * Email configuration.
152 | *
153 | * You can configure email transports and email delivery profiles here.
154 | *
155 | * By defining transports separately from delivery profiles you can easily
156 | * re-use transport configuration across multiple profiles.
157 | *
158 | * You can specify multiple configurations for production, development and
159 | * testing.
160 | *
161 | * ### Configuring transports
162 | *
163 | * Each transport needs a `className`. Valid options are as follows:
164 | *
165 | * Mail - Send using PHP mail function
166 | * Smtp - Send using SMTP
167 | * Debug - Do not send the email, just return the result
168 | *
169 | * You can add custom transports (or override existing transports) by adding the
170 | * appropriate file to src/Network/Email. Transports should be named
171 | * 'YourTransport.php', where 'Your' is the name of the transport.
172 | *
173 | * ### Configuring delivery profiles
174 | *
175 | * Delivery profiles allow you to predefine various properties about email
176 | * messages from your application and give the settings a name. This saves
177 | * duplication across your application and makes maintenance and development
178 | * easier. Each profile accepts a number of keys. See `Cake\Network\Email\Email`
179 | * for more information.
180 | */
181 | 'EmailTransport' => [
182 | 'default' => [
183 | 'className' => 'Mail',
184 | // The following keys are used in SMTP transports
185 | 'host' => 'localhost',
186 | 'port' => 25,
187 | 'timeout' => 30,
188 | 'username' => 'user',
189 | 'password' => 'secret',
190 | 'client' => null,
191 | 'tls' => null,
192 | ],
193 | ],
194 |
195 | 'Email' => [
196 | 'default' => [
197 | 'transport' => 'default',
198 | 'from' => 'you@localhost',
199 | //'charset' => 'utf-8',
200 | //'headerCharset' => 'utf-8',
201 | ],
202 | ],
203 |
204 | /**
205 | * Connection information used by the ORM to connect
206 | * to your application's datastores.
207 | * Drivers include Mysql Postgres Sqlite Sqlserver
208 | * See vendor\cakephp\cakephp\src\Database\Driver for complete list
209 | */
210 | 'Datasources' => [
211 | 'default' => [
212 | 'className' => 'Cake\Database\Connection',
213 | 'driver' => 'Cake\Database\Driver\Mysql',
214 | 'persistent' => false,
215 | 'host' => 'localhost',
216 | /**
217 | * CakePHP will use the default DB port based on the driver selected
218 | * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
219 | * the following line and set the port accordingly
220 | */
221 | //'port' => 'nonstandard_port_number',
222 | 'username' => 'my_app',
223 | 'password' => 'secret',
224 | 'database' => 'my_app',
225 | 'encoding' => 'utf8',
226 | 'timezone' => 'UTC',
227 | 'cacheMetadata' => true,
228 |
229 | /**
230 | * Set identifier quoting to true if you are using reserved words or
231 | * special characters in your table or column names. Enabling this
232 | * setting will result in queries built using the Query Builder having
233 | * identifiers quoted when creating SQL. It should be noted that this
234 | * decreases performance because each query needs to be traversed and
235 | * manipulated before being executed.
236 | */
237 | 'quoteIdentifiers' => false,
238 |
239 | /**
240 | * During development, if using MySQL < 5.6, uncommenting the
241 | * following line could boost the speed at which schema metadata is
242 | * fetched from the database. It can also be set directly with the
243 | * mysql configuration directive 'innodb_stats_on_metadata = 0'
244 | * which is the recommended value in production environments
245 | */
246 | //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
247 | ],
248 |
249 | /**
250 | * The test connection is used during the test suite.
251 | */
252 | 'test' => [
253 | 'className' => 'Cake\Database\Connection',
254 | 'driver' => 'Cake\Database\Driver\Mysql',
255 | 'persistent' => false,
256 | 'host' => 'localhost',
257 | //'port' => 'nonstandard_port_number',
258 | 'username' => 'my_app',
259 | 'password' => 'secret',
260 | 'database' => 'test_myapp',
261 | 'encoding' => 'utf8',
262 | 'timezone' => 'UTC',
263 | 'cacheMetadata' => true,
264 | 'quoteIdentifiers' => false,
265 | //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
266 | ],
267 | ],
268 |
269 | /**
270 | * Configures logging options
271 | */
272 | 'Log' => [
273 | 'debug' => [
274 | 'className' => 'Cake\Log\Engine\FileLog',
275 | 'path' => LOGS,
276 | 'file' => 'debug',
277 | 'levels' => ['notice', 'info', 'debug'],
278 | ],
279 | 'error' => [
280 | 'className' => 'Cake\Log\Engine\FileLog',
281 | 'path' => LOGS,
282 | 'file' => 'error',
283 | 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
284 | ],
285 | ],
286 |
287 | /**
288 | *
289 | * Session configuration.
290 | *
291 | * Contains an array of settings to use for session configuration. The
292 | * `defaults` key is used to define a default preset to use for sessions, any
293 | * settings declared here will override the settings of the default config.
294 | *
295 | * ## Options
296 | *
297 | * - `cookie` - The name of the cookie to use. Defaults to 'CAKEPHP'.
298 | * - `cookiePath` - The url path for which session cookie is set. Maps to the
299 | * `session.cookie_path` php.ini config. Defaults to base path of app.
300 | * - `timeout` - The time in minutes the session should be valid for.
301 | * Pass 0 to disable checking timeout.
302 | * - `defaults` - The default configuration set to use as a basis for your session.
303 | * There are four built-in options: php, cake, cache, database.
304 | * - `handler` - Can be used to enable a custom session handler. Expects an
305 | * array with at least the `engine` key, being the name of the Session engine
306 | * class to use for managing the session. CakePHP bundles the `CacheSession`
307 | * and `DatabaseSession` engines.
308 | * - `ini` - An associative array of additional ini values to set.
309 | *
310 | * The built-in `defaults` options are:
311 | *
312 | * - 'php' - Uses settings defined in your php.ini.
313 | * - 'cake' - Saves session files in CakePHP's /tmp directory.
314 | * - 'database' - Uses CakePHP's database sessions.
315 | * - 'cache' - Use the Cache class to save sessions.
316 | *
317 | * To define a custom session handler, save it at src/Network/Session/.php.
318 | * Make sure the class implements PHP's `SessionHandlerInterface` and set
319 | * Session.handler to
320 | *
321 | * To use database sessions, load the SQL file located at config/Schema/sessions.sql
322 | */
323 | 'Session' => [
324 | 'defaults' => 'php',
325 | ],
326 | ];
327 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CakePHP3 + Behat and PHPSpec
2 |
3 | This is CakePHP3 Blog Tutorial Application Example For Testing by Behat and PHPSpec
4 |
5 | ## Installing and Runnning the example
6 |
7 | ### Install
8 |
9 | I use Cakebox for creating application.
10 | Using it, easy to run the example application.
11 |
12 | 1: Install Vagrant and Virtualbox
13 |
14 | - [Vagrant](https://www.vagrantup.com/)
15 | - [Virtualbox](https://www.virtualbox.org/)
16 |
17 | 2: Install Cakebox
18 |
19 | ```
20 | localhost:any $ git clone https://github.com/alt3/cakebox.git
21 | localhost:any $ cd cakebox
22 | localhost:cakebox $ cp Cakebox.yaml.default Cakebox.yaml
23 | localhost:cakebox $ vagrant up
24 | ```
25 |
26 | upgrade your box to Ubuntu 16.04 with PHP7.1 by running:
27 |
28 | ```
29 | localhost:cakebox $ vagrant ssh
30 | vagrant@cakebox $ sudo apt-get update
31 | vagrant@cakebox $ sudo apt-get install software-properties-common python-software-properties
32 | vagrant@cakebox $ /cakebox/bash/ubuntu-16.sh
33 | vagrant@cakebox $ exit
34 | localhost:cakebox $ vagrant reload
35 | ```
36 |
37 | Check PHP version:
38 |
39 | ```
40 | localhost:cakebox $ vagrant ssh
41 | vagrant@cakebox:~$ php -v
42 | PHP 7.1.12-2+ubuntu16.04.1+deb.sury.org+2 (cli) (built: Dec 7 2017 20:12:04) ( NTS )
43 | ```
44 |
45 | 3: Install example application
46 |
47 | ```
48 | localhost:cakebox $ vagrant ssh
49 | Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-103-generic x86_64)
50 |
51 | vagrant@cakebox $ cakebox application add blog-tutorial.app --source https://github.com/sizuhiko/cakephp3-bdd-example.git --webroot /home/vagrant/Apps/blog-tutorial.app/webroot
52 | ```
53 |
54 | It will print out logs of installation followings:
55 |
56 | ```
57 | Creating application http://blog-tutorial.app
58 |
59 | Configuring installer
60 | Creating installation directory
61 | Git installing user specified application sources
62 | Creating virtual host
63 | * Successfully created PHP-FPM virtual host
64 | Creating databases
65 | * Successfully created main database
66 | * Successfully created test database
67 | Configuring permissions
68 | Updating configuration files
69 | Application created using:
70 | database => blog-tutorial_app
71 | framework_human => user specified
72 | framework_short => custom
73 | installation_method => git
74 | path => /home/vagrant/Apps/blog-tutorial.app
75 | source => https://github.com/sizuhiko/cakephp3-bdd-example.git
76 | url => blog-tutorial.app
77 | webroot => /home/vagrant/Apps/blog-tutorial.app/webroot
78 | Please note:
79 | => Configuration files are not automatically updated for user specified applications.
80 | => Make sure to manually update your database credentials, plugins, etc.
81 |
82 | Remember to update your hosts file with: 10.33.10.10 http://blog-tutorial.app
83 |
84 | Installation completed successfully
85 | ```
86 |
87 | After installation completed successfully, create directories and install dependencies.
88 |
89 | ```
90 | vagrant@cakebox $ cd Apps/blog-tutorial.app
91 | vagrant@cakebox:~/Apps/blog-tutorial.app$ mkdir tmp
92 | vagrant@cakebox:~/Apps/blog-tutorial.app$ mkdir logs
93 | vagrant@cakebox:~/Apps/blog-tutorial.app$ cp config/app.default.php config/app.php
94 | vagrant@cakebox:~/Apps/blog-tutorial.app$ composer install
95 |
96 | Generating autoload files
97 | Set Folder Permissions ? (Default to Y) [Y,n]?
98 | Updated Security.salt value in config/app.php
99 | ```
100 |
101 | ### Configuration
102 |
103 | #### Example Application Config
104 |
105 | Edit section of database connection in `config/app.php`.
106 |
107 | ```php
108 | 'Datasources' => [
109 | 'default' => [
110 | 'className' => 'Cake\Database\Connection',
111 | 'driver' => 'Cake\Database\Driver\Mysql',
112 | 'persistent' => false,
113 | 'host' => 'localhost',
114 | 'username' => 'cakebox', // CHANGE
115 | 'password' => 'secret',
116 | 'database' => 'blog-tutorial_app', // CHANGE
117 | 'encoding' => 'utf8',
118 | 'timezone' => 'UTC',
119 | 'cacheMetadata' => true,
120 | 'quoteIdentifiers' => false,
121 | ],
122 | 'test' => [
123 | 'className' => 'Cake\Database\Connection',
124 | 'driver' => 'Cake\Database\Driver\Mysql',
125 | 'persistent' => false,
126 | 'host' => 'localhost',
127 | 'username' => 'cakebox', // CHANGE
128 | 'password' => 'secret',
129 | 'database' => 'test_blog-tutorial_app', // CHANGE
130 | 'encoding' => 'utf8',
131 | 'timezone' => 'UTC',
132 | 'cacheMetadata' => true,
133 | 'quoteIdentifiers' => false,
134 | ],
135 | ```
136 |
137 | #### Host Computer Config
138 |
139 | Add host name to your host computer
140 | For example followings:
141 |
142 | ```
143 | localhost:cakebox $ sudo vi /etc/hosts
144 | ```
145 |
146 | Add `10.33.10.10 blog-tutorial.app`.
147 |
148 | #### Cakebox Config
149 |
150 | For running behat, recommend to change some configurations.
151 |
152 | 1: Up box memory to 2048M.
153 |
154 | ```yaml
155 | # Cakebox.yml
156 | vm:
157 | hostname: cakebox
158 | ip: 10.33.10.10
159 | memory: 2048
160 | cpus: 1
161 | ```
162 |
163 | 2: Set xdebug.max_nesting_level
164 |
165 | ```
166 | localhost:cakebox $ sudo vi /etc/php/7.1/fpm/conf.d/20-xdebug.ini
167 | ```
168 |
169 | ```ini
170 | # /etc/php/7.1/fpm/conf.d/20-xdebug.ini
171 | xdebug.max_nesting_level=500
172 | ```
173 |
174 | #### Webserver Config On Cakebox
175 |
176 | I provided configuration of nginx for testing.
177 | If you use Cakebox, then copy the config file like followings:
178 |
179 | ```
180 | vagrant@cakebox $ cd ~/Apps/blog-tutorial.app
181 | vagrant@cakebox:~/Apps/blog-tutorial.app$ sudo cp blog-tutorial.app.test /etc/nginx/sites-available/
182 | vagrant@cakebox:~/Apps/blog-tutorial.app$ sudo ln -s /etc/nginx/sites-available/blog-tutorial.app.test /etc/nginx/sites-enabled/
183 | vagrant@cakebox:~/Apps/blog-tutorial.app$ sudo service nginx restart
184 | ```
185 |
186 | And add `/etc/hosts` on Cakebox vm to hostname for testing.
187 |
188 | ```
189 | localhost:cakebox $ sudo vi /etc/hosts
190 |
191 | # Add host
192 | 10.33.10.10 blog-tutorial.app.test
193 | ```
194 |
195 | ### Migrate Database
196 |
197 | Create all tables for application by migration command.
198 |
199 | ```
200 | bin/cake migrations migrate
201 | ```
202 |
203 | ### Run Test
204 |
205 | After installations and configurations completed successfully, run test using Behat.
206 |
207 | ```
208 | vagrant@cakebox:~/Apps/blog-tutorial.app$ vendor/bin/behat
209 | ```
210 |
211 | It will print out test results followings:
212 |
213 | ```
214 | Feature:
215 | In order to tell the masses what's on my mind
216 | As a user
217 | I want to read articles on the site
218 |
219 | Background: # features/articles.feature:7
220 | Given there is a post: # ArticlesContext::thereIsAPost()
221 | | Title | Body |
222 | | The title | This is the post body. |
223 | | A title once again | And the post body follows. |
224 | | Title strikes back | This is really exciting! Not. |
225 | And there is a user: # UsersContext::thereIsAUser()
226 | | Username | Password | FirstName | LastName |
227 | | alice | ecila | Alice | Smith |
228 | | bob | obo | Bob | Johnson |
229 | And there is a category: # CategoriesContext::thereIsACategory()
230 | | Name |
231 | | Events |
232 | | Computers |
233 | | Foods |
234 |
235 | Scenario: Show articles # features/articles.feature:23
236 | When I am on "TopPage" # WebContext::visit()
237 | Then I should see "The title" # WebContext::assertPageContainsText()
238 | And I should see "A title once again" # WebContext::assertPageContainsText()
239 | And I should see "Title strikes back" # WebContext::assertPageContainsText()
240 |
241 | Scenario: Show the article # features/articles.feature:29
242 | Given I am on "TopPage" # WebContext::visit()
243 | When I follow "A title once again" # WebContext::clickLink()
244 | Then I should see "And the post body follows." # WebContext::assertPageContainsText()
245 |
246 | Scenario: Add new article # features/articles.feature:34
247 | Given I am on "TopPage" # WebContext::visit()
248 | And I follow "Add" # WebContext::clickLink()
249 | And I login "bob" "obo" # UsersContext::iLogin()
250 | When I post article form : # ArticlesContext::iPostArticleForm()
251 | | Label | Value |
252 | | Categories | Events |
253 | | Title | Today is Party |
254 | | Body | From 19:30 with Alice |
255 | And I should see "Your article has been saved." # WebContext::assertPageContainsText()
256 | And I should see "Today is party" # WebContext::assertPageContainsText()
257 |
258 | Scenario: Remove article # features/articles.feature:46
259 | Given I am on "TopPage" # WebContext::visit()
260 | When I delete article "Title strikes back" # ArticlesContext::iDeleteArticle()
261 | Then I should not see "Title strikes back" # WebContext::assertPageNotContainsText()
262 |
263 | 4 scenarios (4 passed)
264 | 28 steps (28 passed)
265 | 0m10.65s (41.29Mb)
266 | ```
267 |
268 |
269 | ## How to made this example
270 |
271 | This section explains about steps of creation for the example application.
272 |
273 | ### Create CakePHP3 Blog Tutorial Application
274 |
275 | I use Cakebox for creating application.
276 |
277 | 1. Install Vagrant and Virtualbox.
278 | 2. Install Cakebox
279 | 3. Generate CakePHP3 application skelton by cakebox
280 |
281 | ```
282 | localhost:cakebox $ vagrant ssh
283 | Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-24-generic x86_64)
284 |
285 | vagrant@cakebox:~$ cakebox application add blog-tutorial.app
286 | ```
287 |
288 | After generate application completed successfully, add host name to your host computer
289 |
290 | ```
291 | localhost:cakebox $ sudo vi /etc/hosts
292 | ```
293 |
294 | Add `10.33.10.10 blog-tutorial.app`.
295 |
296 | Continue to read tutorial docs, bake and write codes.
297 |
298 | [Blog tutorial](http://book.cakephp.org/3.0/en/tutorials-and-examples/blog/blog.html)
299 |
300 | Any configuration was finished by Cakebox.
301 |
302 | ### Install Behat/Mink and any components by composer
303 |
304 | For testing, use following components:
305 |
306 | - Mink Extension (dependent Mink and Behat)
307 | - Mink goutte driver (Headless web driver)
308 | - PHPUnit (for using CakePHP fixture manager)
309 | - PHP dotenv (for switching environment)
310 | - Cake Fabricate (Testdata generator)
311 |
312 | ```
313 | vagrant@cakebox:~$ cd Apps/blog-tutorial.app/
314 | vagrant@cakebox:~/Apps/blog-tutorial.app$ composer require --dev behat/mink-extension
315 | vagrant@cakebox:~/Apps/blog-tutorial.app$ composer require --dev behat/mink-goutte-driver
316 | vagrant@cakebox:~/Apps/blog-tutorial.app$ composer require --dev phpunit/phpunit
317 | vagrant@cakebox:~/Apps/blog-tutorial.app$ composer require --dev sizuhiko/cake_fabricate
318 | vagrant@cakebox:~/Apps/blog-tutorial.app$ composer require vlucas/phpdotenv
319 | ```
320 |
321 | #### Generate behat skelton
322 |
323 | Behat has feature for generation skelton.
324 | Run `behat --init` on application root directory.
325 |
326 | ```
327 | vagrant@cakebox:~$ cd Apps/blog-tutorial.app/
328 | vagrant@cakebox:~/Apps/blog-tutorial.app$ vendor/bin/behat --init
329 | ```
330 |
331 | #### Add behat.yml
332 |
333 | Create `behat.yml` on `Apps/blog-tutorial.app` directory.
334 | And activate `Mink Extension`.
335 | See [Mink Extension Official documentation](https://github.com/Behat/MinkExtension/blob/master/doc/index.rst).
336 |
337 | ```yaml
338 | # behat.yml
339 | default:
340 | # ...
341 | extensions:
342 | Behat\MinkExtension:
343 | base_url: 'http://blog-tutorial.app.test/'
344 | sessions:
345 | default:
346 | goutte: ~
347 | suites:
348 | my_suite:
349 | contexts:
350 | - FeatureContext
351 | - Behat\MinkExtension\Context\MinkContext
352 | ```
353 |
354 | #### Add posts.feature
355 |
356 | Add `posts.feature` onto `features` directory.
357 | See example code.
358 |
359 | ### For switching environment
360 |
361 | For testing by behat, you should switch application environment.
362 | Because use test database when accessed by behat.
363 | On this example application, uses CAKE_ENV environment.
364 | Add `config/bootstrap.php` for switching database env.
365 |
366 | ```php
367 | if (getenv('CAKE_ENV') === 'test') {
368 | ConnectionManager::alias('test', 'default');
369 | }
370 | ```
371 |
372 | I provided configuration of nginx for testing.
373 | If you use Cakebox, then copy the config file like followings:
374 |
375 | ```bash
376 | sudo cp blog-tutorial.app.test /etc/nginx/sites-available/
377 | sudo ln -s /etc/nginx/sites-available/blog-tutorial.app.test /etc/nginx/sites-enabled/
378 | sudo service nginx restart
379 | ```
380 |
381 | And add `/etc/hosts` on Cakebox vm to hostname for testing.
382 |
383 | ```bash
384 | localhost:cakebox $ sudo vi /etc/hosts
385 |
386 | # Add host
387 | 10.33.10.10 blog-tutorial.app.test
388 | ```
389 |
390 | ### Integrate CakePHP3 and Behat
391 |
392 | `features/bootstrap/FeatureContext.php` is bootstrap of Behat test.
393 |
394 | ```php
395 | class FeatureContext implements Context, SnippetAcceptingContext
396 | {
397 | public function __construct()
398 | {
399 | require_once dirname(dirname(__DIR__)) . '/tests/bootstrap.php'; // (1)
400 |
401 | // Always connect test database
402 | ConnectionManager::alias('test', 'default'); // (2)
403 |
404 | Fabricate::config(function($config) { // (3)
405 | $config->adaptor = new CakeFabricateAdaptor([
406 | CakeFabricateAdaptor::OPTION_FILTER_KEY => true,
407 | CakeFabricateAdaptor::OPTION_VALIDATE => false
408 | ]);
409 | });
410 |
411 | $this->fixtureInjector = new FixtureInjector(new FixtureManager()); //(4)
412 | $this->fixture = new BddAllFixture();
413 | }
414 | }
415 | ```
416 |
417 | This bootstrap flow (especially 1 and 4) inspired from `phpunit.xml.dist` of CakePHP3.
418 | (1) is from `phpunit` tag. Load `bootstrap.php` for testing CakePHP application.
419 | (4) is from `listeners` tag. For using fixture system into behat step.
420 |
421 | ```xml
422 |
423 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 | ./tests/TestCase
439 |
440 |
441 |
442 |
443 |
444 |
445 |
448 |
449 |
450 |
451 |
452 |
453 |
454 | ```
455 |
456 | (2) is connection of (default) database for testing.
457 | (3) is configuration for integration fabricate to CakePHP.
458 |
459 |
460 | ### Using fixture of CakePHP on context of Behat
461 |
462 | In the example case, Fixtures define on FeatureContext as previously described.
463 |
464 | ```php
465 | $this->fixtureInjector = new FixtureInjector(new FixtureManager()); //(4)
466 | $this->fixture = new BddAllFixture();
467 | ```
468 |
469 | Behat provides some hook points.
470 | Fixtures are loaded and unloaded with this.
471 |
472 | ```php
473 | /** @BeforeScenario */
474 | public function beforeScenario(BeforeScenarioScope $scope)
475 | {
476 | $this->fixtureInjector->startTest($this->fixture);
477 | }
478 |
479 | /** @AfterScenario */
480 | public function afterScenario(AfterScenarioScope $scope)
481 | {
482 | $this->fixtureInjector->endTest($this->fixture, time());
483 | }
484 | ```
485 |
486 | `@BeforeScenario` hook is run before a specific scenario will run.
487 | `@AfterScenario` hook is run after Behat finishes executing a scenario.
488 |
489 | Inside CakePHP, `FixtureInjector` takes a role as PHPUnit_Framework_TestListener.
490 |
491 | ```php
492 | class FixtureInjector implements PHPUnit_Framework_TestListener
493 | {
494 |
495 | /**
496 | * Adds fixtures to a test case when it starts.
497 | *
498 | * @param \PHPUnit_Framework_Test $test The test case
499 | * @return void
500 | */
501 | public function startTest(PHPUnit_Framework_Test $test)
502 | {
503 | $test->fixtureManager = $this->_fixtureManager;
504 | if ($test instanceof TestCase) {
505 | $this->_fixtureManager->fixturize($test);
506 | $this->_fixtureManager->load($test);
507 | }
508 | }
509 |
510 | /**
511 | * Unloads fixtures from the test case.
512 | *
513 | * @param \PHPUnit_Framework_Test $test The test case
514 | * @param float $time current time
515 | * @return void
516 | */
517 | public function endTest(PHPUnit_Framework_Test $test, $time)
518 | {
519 | if ($test instanceof TestCase) {
520 | $this->_fixtureManager->unload($test);
521 | }
522 | }
523 |
524 | }
525 | ```
526 |
527 | Without PHPUnit, it should call these hook functions.
528 | FeatureContext hooks simulate the listener functions.
529 |
530 | At last, FixtureInjector startTest and endTest functions are required of arguments as PHPUnit_Framework_Test.
531 | So, it should create class extends TestCase.
532 | In the example, I create BddAllFixture class into `FeatureContext.php`.
533 | It only has `$fixtures` array for FixtureInjector.
534 |
535 | ```php
536 | class BddAllFixture extends TestCase {
537 | public $fixtures = [
538 | 'Categories' => 'app.categories',
539 | 'Articles' => 'app.articles',
540 | 'Users' => 'app.users',
541 | 'Categories' => 'app.categories'
542 | ];
543 | }
544 | ```
545 |
546 | ### Using any CakePHP3 feature in contexts of Behat
547 |
548 | You can use any CakePHP3 feature in contexts of Behat.
549 | In the example, it calls CakePHP Router::url() at `WebContext.php`.
550 |
551 | ```php
552 | use Behat\MinkExtension\Context\MinkContext;
553 | use Cake\Routing\Router;
554 |
555 | class WebContext extends MinkContext
556 | {
557 | public function locatePath($path)
558 | {
559 | return parent::locatePath($this->getPathTo($path));
560 | }
561 |
562 | private function getPathTo($path)
563 | {
564 | switch ($path) {
565 | case 'TopPage': return Router::url(['controller' => 'articles', 'action' => 'index']);
566 | case 'トップページ': return Router::url(['controller' => 'articles', 'action' => 'index']);
567 | default: return $path;
568 | }
569 | }
570 | }
571 | ```
572 |
573 | CakePHP3 feature can use in any context of Behat.
574 | Because [CakePHP3 is fully adopt PSR-2](http://bakery.cakephp.org/2014/12/16/CakePHP-3-to-fully-adopt-PSR-2.html), it is awesome and take it easy.
575 |
576 | ### Write Step to Contexts
577 |
578 | The example app has 5 context classes.
579 |
580 | - FeatureContext is bootstrap. It constructor integrates to CakePHP.
581 | - WebContext extends MinkContext to testing for web application. Overwrite locatePath for using alias of url.
582 | - ArticlesContext has steps about Articles model and the pages.
583 | - UsersContext has steps about Users model, the pages and authentication.
584 | - CategoriesContext has steps about Category model.
585 |
586 | The steps list to `behat.yml`.
587 |
588 | ```yaml
589 | suites:
590 | default:
591 | contexts:
592 | - FeatureContext
593 | - WebContext
594 | - ArticlesContext
595 | - UsersContext
596 | - CategoriesContext
597 | ```
598 |
599 |
600 | ## TODO
601 |
602 | - PHPSpec integration documentation and example test code.
603 |
604 |
--------------------------------------------------------------------------------