This commit is contained in:
2026-10-27 20:40:32 +01:00
parent cac3b0c27f
commit 502da31a46
110 changed files with 0 additions and 11859 deletions
-47
View File
@@ -1,47 +0,0 @@
#
# Bash completion file for CakePHP console.
# Copy this file to a file named `cake` under `/etc/bash_completion.d/`.
# For more info check https://book.cakephp.org/5/en/console-commands/completion.html#how-to-enable-bash-autocompletion-for-the-cakephp-console
#
_cake()
{
local cur prev opts cake
COMPREPLY=()
cake="${COMP_WORDS[0]}"
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ "$cur" == -* ]] ; then
if [[ ${COMP_CWORD} = 1 ]] ; then
opts=$(${cake} completion options)
elif [[ ${COMP_CWORD} = 2 ]] ; then
opts=$(${cake} completion options "${COMP_WORDS[1]}")
else
opts=$(${cake} completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}")
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
if [[ ${COMP_CWORD} = 1 ]] ; then
opts=$(${cake} completion commands)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
if [[ ${COMP_CWORD} = 2 ]] ; then
opts=$(${cake} completion subcommands $prev)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
if [[ $COMPREPLY = "" ]] ; then
_filedir
return 0
fi
return 0
fi
return 0
}
complete -F _cake cake bin/cake
-75
View File
@@ -1,75 +0,0 @@
#!/usr/bin/env sh
################################################################################
#
# Cake is a shell script for invoking CakePHP shell commands
#
# CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
# Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
#
# Licensed under The MIT License
# For full copyright and license information, please see the LICENSE.txt
# Redistributions of files must retain the above copyright notice.
#
# @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
# @link https://cakephp.org CakePHP(tm) Project
# @since 1.2.0
# @license https://opensource.org/licenses/mit-license.php MIT License
#
################################################################################
# Canonicalize by following every symlink of the given name recursively
canonicalize() {
NAME="$1"
if [ -f "$NAME" ]
then
DIR=$(dirname -- "$NAME")
NAME=$(cd -P "$DIR" > /dev/null && pwd -P)/$(basename -- "$NAME")
fi
while [ -h "$NAME" ]; do
DIR=$(dirname -- "$NAME")
SYM=$(readlink "$NAME")
NAME=$(cd "$DIR" > /dev/null && cd "$(dirname -- "$SYM")" > /dev/null && pwd)/$(basename -- "$SYM")
done
echo "$NAME"
}
# Find a CLI version of PHP
findCliPhp() {
for TESTEXEC in php php-cli /usr/local/bin/php
do
SAPI=$(echo "<?= PHP_SAPI ?>" | $TESTEXEC 2>/dev/null)
if [ "$SAPI" = "cli" ]
then
echo $TESTEXEC
return
fi
done
echo "Failed to find a CLI version of PHP; falling back to system standard php executable" >&2
echo "php";
}
# If current path is a symlink, resolve to real path
realname="$0"
if [ -L "$realname" ]
then
realname=$(readlink -f "$0")
fi
CONSOLE=$(dirname -- "$(canonicalize "$realname")")
APP=$(dirname "$CONSOLE")
# If your CLI PHP is somewhere that this doesn't find, you can define a PHP environment
# variable with the correct path in it.
if [ -z "$PHP" ]
then
PHP=$(findCliPhp)
fi
if [ "$(basename "$realname")" != 'cake' ]
then
exec "$PHP" "$CONSOLE"/cake.php "$(basename "$realname")" "$@"
else
exec "$PHP" "$CONSOLE"/cake.php "$@"
fi
exit
-27
View File
@@ -1,27 +0,0 @@
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
:: Cake is a Windows batch script for invoking CakePHP shell commands
::
:: CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
:: Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
::
:: Licensed under The MIT License
:: Redistributions of files must retain the above copyright notice.
::
:: @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
:: @link https://cakephp.org CakePHP(tm) Project
:: @since 2.0.0
:: @license https://opensource.org/licenses/mit-license.php MIT License
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@echo off
SET app=%0
SET lib=%~dp0
php "%lib%cake.php" %*
echo.
exit /B %ERRORLEVEL%
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/php -q
<?php
require dirname(__DIR__) . '/vendor/autoload.php';
use App\Application;
use Cake\Console\CommandRunner;
// Build the runner with an application and root executable name.
$runner = new CommandRunner(new Application(dirname(__DIR__) . '/config'), 'cake');
exit($runner->run($argv));
-60
View File
@@ -1,60 +0,0 @@
{
"name": "cakephp/app",
"description": "CakePHP skeleton app",
"license": "MIT",
"type": "project",
"homepage": "https://cakephp.org",
"require": {
"php": ">=8.2",
"cakephp/authentication": "^4.0",
"cakephp/cakephp": "5.4.*",
"cakephp/migrations": "^5.0",
"cakephp/plugin-installer": "^2.0",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"mobiledetect/mobiledetectlib": "^4.8.03"
},
"require-dev": {
"cakephp/bake": "^3.6",
"cakephp/cakephp-codesniffer": "^5.3",
"cakephp/debug_kit": "^5.2",
"josegonzalez/dotenv": "^4.0",
"phpunit/phpunit": "^11.5.3 || ^12.1.3 || ^13.0"
},
"suggest": {
"cakephp/repl": "Console tools for a REPL interface for CakePHP applications.",
"dereuromark/cakephp-ide-helper": "After baking your code, this keeps your annotations in sync with the code evolving from there on for maximum IDE and PHPStan/Psalm compatibility.",
"markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.",
"phpstan/phpstan": "PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs even before you write tests for the code."
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Test\\": "tests/",
"Cake\\Test\\": "vendor/cakephp/cakephp/tests/"
}
},
"config": {
"allow-plugins": {
"cakephp/plugin-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"platform-check": true,
"sort-packages": true
},
"scripts": {
"post-install-cmd": "App\\Console\\Installer::postInstall",
"post-create-project-cmd": "App\\Console\\Installer::postInstall",
"check": [
"@test",
"@cs-check"
],
"cs-check": "phpcs --colors -p",
"cs-fix": "phpcbf --colors -p",
"test": "phpunit --colors=always"
}
}
Generated
-6358
View File
File diff suppressed because it is too large Load Diff
-41
View File
@@ -1,41 +0,0 @@
#!/usr/bin/env bash
# Used as a default to seed config/.env which
# enables you to use environment variables to configure
# the aspects of your application that vary by
# environment.
#
# Having this file in production is considered a **SECURITY RISK** and also decreases
# the bootstrap performance of your application.
#
# To use this file, first copy it into `config/.env`. Also ensure the related
# code block for loading this file is uncommented in `config/bootstrap.php`
#
# In development .env files are parsed by PHP
# and set into the environment. This provides a simpler
# development workflow over standard environment variables.
export APP_NAME="__APP_NAME__"
export DEBUG="true"
export APP_ENCODING="UTF-8"
export APP_DEFAULT_LOCALE="en_US"
export APP_DEFAULT_TIMEZONE="UTC"
# SECURITY: Set this to your domain to prevent Host Header Injection attacks
# This is REQUIRED in production for password resets and other security features
export APP_FULL_BASE_URL="https://example.com"
export SECURITY_SALT="__SALT__"
# Uncomment these to define cache configuration via environment variables.
#export CACHE_DURATION="+2 minutes"
#export CACHE_DEFAULT_URL="file:///path/to/tmp/cache/?prefix=${APP_NAME}_default_&duration=${CACHE_DURATION}"
#export CACHE_CAKECORE_URL="file:///path/to/tmp/cache/persistent?prefix=${APP_NAME}_cake_translations_&serialize=true&duration=${CACHE_DURATION}"
#export CACHE_CAKEMODEL_URL="file:///path/to/tmp/cache/models?prefix=${APP_NAME}_cake_model_&serialize=true&duration=${CACHE_DURATION}"
# Uncomment these to define email transport configuration via environment variables.
#export EMAIL_TRANSPORT_DEFAULT_URL=""
# Uncomment these to define database configuration via environment variables.
#export DATABASE_URL="mysql://my_app:secret@localhost/${APP_NAME}?encoding=utf8&timezone=UTC&cacheMetadata=true&quoteIdentifiers=false&persistent=false"
#export DATABASE_TEST_URL="mysql://my_app:secret@localhost/test_${APP_NAME}?encoding=utf8&timezone=UTC&cacheMetadata=true&quoteIdentifiers=false&persistent=false"
# Uncomment these to define logging configuration via environment variables.
#export LOG_DEBUG_URL="file:///path/to/logs/?levels[]=notice&levels[]=info&levels[]=debug&file=debug"
#export LOG_ERROR_URL="file:///path/to/logs/?levels[]=warning&levels[]=error&levels[]=critical&levels[]=alert&levels[]=emergency&file=error"
@@ -1,72 +0,0 @@
<?php
declare(strict_types=1);
use Migrations\BaseMigration;
class CreateInitialSchema extends BaseMigration
{
/**
* Creates users, customers and cars tables.
*
* @return void
*/
public function change(): void
{
$this->table('users')
->addColumn('email', 'string', ['limit' => 255, 'null' => false])
->addColumn('password', 'string', ['limit' => 255, 'null' => false])
->addColumn('first_name', 'string', ['limit' => 100, 'null' => false])
->addColumn('last_name', 'string', ['limit' => 100, 'null' => false])
->addColumn('role', 'string', ['limit' => 20, 'null' => false, 'default' => 'user'])
->addColumn('active', 'boolean', ['null' => false, 'default' => true])
->addColumn('last_login', 'datetime', ['null' => true, 'default' => null])
->addColumn('created', 'datetime', ['null' => false])
->addColumn('modified', 'datetime', ['null' => false])
->addIndex(['email'], ['unique' => true])
->create();
$this->table('customers')
->addColumn('type', 'string', ['limit' => 20, 'null' => false, 'default' => 'individual'])
->addColumn('first_name', 'string', ['limit' => 100, 'null' => false])
->addColumn('last_name', 'string', ['limit' => 100, 'null' => false])
->addColumn('email', 'string', ['limit' => 255, 'null' => false])
->addColumn('phone', 'string', ['limit' => 30, 'null' => false])
->addColumn('company_name', 'string', ['limit' => 255, 'null' => true, 'default' => null])
->addColumn('tax_id', 'string', ['limit' => 20, 'null' => true, 'default' => null])
->addColumn('street', 'string', ['limit' => 255, 'null' => true, 'default' => null])
->addColumn('city', 'string', ['limit' => 100, 'null' => true, 'default' => null])
->addColumn('postal_code', 'string', ['limit' => 10, 'null' => true, 'default' => null])
->addColumn('country', 'string', ['limit' => 2, 'null' => false, 'default' => 'PL'])
->addColumn('birth_date', 'date', ['null' => true, 'default' => null])
->addColumn('notes', 'text', ['null' => true, 'default' => null])
->addColumn('created_by', 'integer', ['null' => true, 'default' => null])
->addColumn('created', 'datetime', ['null' => false])
->addColumn('modified', 'datetime', ['null' => false])
->addIndex(['email'], ['unique' => true])
->addIndex(['last_name'])
->addForeignKey('created_by', 'users', 'id', ['delete' => 'SET_NULL', 'update' => 'CASCADE'])
->create();
$this->table('cars')
->addColumn('customer_id', 'integer', ['null' => false])
->addColumn('make', 'string', ['limit' => 50, 'null' => false])
->addColumn('model', 'string', ['limit' => 50, 'null' => false])
->addColumn('production_year', 'integer', ['null' => false])
->addColumn('vin', 'string', ['limit' => 17, 'null' => false])
->addColumn('registration_number', 'string', ['limit' => 10, 'null' => false])
->addColumn('fuel_type', 'string', ['limit' => 20, 'null' => false])
->addColumn('color', 'string', ['limit' => 30, 'null' => true, 'default' => null])
->addColumn('engine_capacity', 'integer', ['null' => true, 'default' => null])
->addColumn('power_hp', 'integer', ['null' => true, 'default' => null])
->addColumn('transmission', 'string', ['limit' => 20, 'null' => true, 'default' => null])
->addColumn('mileage', 'integer', ['null' => true, 'default' => null])
->addColumn('notes', 'text', ['null' => true, 'default' => null])
->addColumn('created', 'datetime', ['null' => false])
->addColumn('modified', 'datetime', ['null' => false])
->addIndex(['vin'], ['unique' => true])
->addIndex(['registration_number'], ['unique' => true])
->addIndex(['customer_id'])
->addForeignKey('customer_id', 'customers', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->create();
}
}
Binary file not shown.
-216
View File
@@ -1,216 +0,0 @@
<?php
declare(strict_types=1);
use Authentication\PasswordHasher\DefaultPasswordHasher;
use Faker\Factory;
use Faker\Generator;
use Migrations\BaseSeed;
/**
* Seeds test data: 2 accounts, 100 customers and 1-5 cars per customer.
*
* Run: bin/cake seeds run DatabaseSeed
* Existing data in users, customers and cars is removed first.
*/
class DatabaseSeed extends BaseSeed
{
private const CARS = [
'Toyota' => ['Corolla', 'Yaris', 'RAV4', 'Auris', 'C-HR', 'Camry'],
'Volkswagen' => ['Golf', 'Passat', 'Polo', 'Tiguan', 'T-Roc', 'Arteon'],
'Skoda' => ['Octavia', 'Fabia', 'Superb', 'Kodiaq', 'Karoq', 'Scala'],
'Ford' => ['Focus', 'Fiesta', 'Mondeo', 'Kuga', 'Puma'],
'Opel' => ['Astra', 'Corsa', 'Insignia', 'Mokka', 'Grandland'],
'BMW' => ['Seria 3', 'Seria 5', 'X1', 'X3', 'X5'],
'Audi' => ['A3', 'A4', 'A6', 'Q3', 'Q5'],
'Mercedes-Benz' => ['Klasa A', 'Klasa C', 'Klasa E', 'GLC', 'GLA'],
'Renault' => ['Clio', 'Megane', 'Captur', 'Kadjar'],
'Kia' => ['Ceed', 'Sportage', 'Rio', 'Niro'],
'Hyundai' => ['i20', 'i30', 'Tucson', 'Kona'],
'Tesla' => ['Model 3', 'Model Y', 'Model S'],
'Fiat' => ['Panda', '500', 'Tipo'],
'Dacia' => ['Duster', 'Sandero', 'Logan'],
];
private const COLORS = ['czarny', 'biały', 'srebrny', 'szary', 'niebieski', 'czerwony', 'zielony', 'grafitowy', 'beżowy'];
private const PLATE_PREFIXES = ['WA', 'WE', 'WI', 'KR', 'KK', 'PO', 'PZ', 'GD', 'GA', 'WR', 'DW', 'LU', 'SK', 'SC', 'EL', 'BI', 'OP', 'RZ', 'TK', 'NO', 'ZS', 'CB', 'FZ'];
private const CUSTOMER_NOTES = [
'Preferuje kontakt telefoniczny po godzinie 16.',
'Stały klient, przysługuje rabat 10% na usługi serwisowe.',
'Prosi o przypomnienia SMS przed terminem przeglądu.',
'Faktury wysyłać wyłącznie na adres e-mail.',
'Klient flotowy - rozliczenie zbiorcze na koniec miesiąca.',
'Zainteresowany wymianą auta na nowsze w przyszłym roku.',
'Kontakt tylko w dni robocze.',
];
private const CAR_NOTES = [
'Wymiana oleju przy 15 000 km.',
'Do sprawdzenia klocki hamulcowe przy następnej wizycie.',
'Opony zimowe przechowywane w serwisie.',
'Auto po szkodzie parkingowej - naprawiony tylny zderzak.',
'Gwarancja producenta do końca roku.',
'Klient zgłaszał stuki w zawieszeniu.',
'Zalecana wymiana rozrządu.',
];
private const VIN_CHARS = 'ABCDEFGHJKLMNPRSTUVWXYZ0123456789';
/**
* The seed truncates its tables first, so it can safely be re-run.
*
* @return bool
*/
public function isIdempotent(): bool
{
return true;
}
/**
* @return void
*/
public function run(): void
{
$faker = Factory::create('pl_PL');
$faker->seed(20261019);
$now = date('Y-m-d H:i:s');
$hasher = new DefaultPasswordHasher();
$this->execute('SET FOREIGN_KEY_CHECKS=0');
foreach (['cars', 'customers', 'users'] as $table) {
$this->execute("TRUNCATE TABLE `{$table}`");
}
$this->execute('SET FOREIGN_KEY_CHECKS=1');
$this->table('users')->insert([
[
'email' => 'admin@example.com',
'password' => $hasher->hash('Admin123!'),
'first_name' => 'Adam',
'last_name' => 'Administrator',
'role' => 'admin',
'active' => true,
'created' => $now,
'modified' => $now,
],
[
'email' => 'user@example.com',
'password' => $hasher->hash('User1234!'),
'first_name' => 'Jan',
'last_name' => 'Użytkownik',
'role' => 'user',
'active' => true,
'created' => $now,
'modified' => $now,
],
])->saveData();
$customers = [];
$emails = [];
for ($i = 1; $i <= 100; $i++) {
$isCompany = $faker->boolean(25);
$firstName = $faker->firstName();
$lastName = $faker->lastName();
do {
$email = $this->slug($firstName) . '.' . $this->slug($lastName) . $faker->numberBetween(1, 999)
. '@' . $faker->freeEmailDomain();
} while (isset($emails[$email]));
$emails[$email] = true;
$created = $faker->dateTimeBetween('-3 years', '-1 day')->format('Y-m-d H:i:s');
$customers[] = [
'type' => $isCompany ? 'company' : 'individual',
'first_name' => $firstName,
'last_name' => $lastName,
'email' => $email,
'phone' => '+48 ' . $faker->numerify('### ### ###'),
'company_name' => $isCompany ? $faker->company() : null,
'tax_id' => $isCompany ? $faker->numerify('##########') : null,
'street' => $faker->boolean(90) ? $faker->streetAddress() : null,
'city' => $faker->city(),
'postal_code' => $faker->numerify('##-###'),
'country' => 'PL',
'birth_date' => $isCompany ? null : $faker->dateTimeBetween('-80 years', '-18 years')->format('Y-m-d'),
'notes' => $faker->boolean(20) ? $faker->randomElement(self::CUSTOMER_NOTES) : null,
'created_by' => $faker->randomElement([1, 2]),
'created' => $created,
'modified' => $created,
];
}
$this->table('customers')->insert($customers)->saveData();
$cars = [];
$vins = [];
$plates = [];
for ($customerId = 1; $customerId <= 100; $customerId++) {
$count = $faker->numberBetween(1, 5);
for ($j = 0; $j < $count; $j++) {
$make = $faker->randomElement(array_keys(self::CARS));
$isTesla = $make === 'Tesla';
$fuel = $isTesla ? 'electric' : $faker->randomElement(['petrol', 'petrol', 'diesel', 'diesel', 'lpg', 'hybrid']);
do {
$vin = $this->randomVin($faker);
} while (isset($vins[$vin]));
$vins[$vin] = true;
do {
$plate = $faker->randomElement(self::PLATE_PREFIXES)
. $faker->numberBetween(10000, 99999);
$plate = substr($plate, 0, 2) . $faker->bothify('?') . substr($plate, 3);
$plate = strtoupper($plate);
} while (isset($plates[$plate]));
$plates[$plate] = true;
$year = $faker->numberBetween(2005, 2026);
$created = $faker->dateTimeBetween('-3 years', '-1 day')->format('Y-m-d H:i:s');
$cars[] = [
'customer_id' => $customerId,
'make' => $make,
'model' => $faker->randomElement(self::CARS[$make]),
'production_year' => $year,
'vin' => $vin,
'registration_number' => $plate,
'fuel_type' => $fuel,
'color' => $faker->randomElement(self::COLORS),
'engine_capacity' => $fuel === 'electric' ? null : $faker->randomElement([999, 1197, 1395, 1498, 1598, 1968, 1995, 2494, 2993]),
'power_hp' => $faker->numberBetween(70, $isTesla ? 500 : 300),
'transmission' => $isTesla ? 'automatic' : $faker->randomElement(['manual', 'manual', 'automatic']),
'mileage' => max(0, (2026 - $year) * $faker->numberBetween(5000, 25000) + $faker->numberBetween(0, 5000)),
'notes' => $faker->boolean(15) ? $faker->randomElement(self::CAR_NOTES) : null,
'created' => $created,
'modified' => $created,
];
}
}
$this->table('cars')->insert($cars)->saveData();
$this->getIo()?->out(sprintf('Seeded 2 users, %d customers and %d cars.', count($customers), count($cars)));
}
/**
* @param \Faker\Generator $faker Faker
* @return string
*/
private function randomVin(Generator $faker): string
{
$vin = '';
for ($i = 0; $i < 17; $i++) {
$vin .= self::VIN_CHARS[$faker->numberBetween(0, strlen(self::VIN_CHARS) - 1)];
}
return $vin;
}
/**
* ASCII slug for e-mail addresses (strips Polish diacritics).
*
* @param string $text Text
* @return string
*/
private function slug(string $text): string
{
$map = ['ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', 'ż' => 'z'];
return preg_replace('/[^a-z]/', '', strtr(mb_strtolower($text), $map)) ?? '';
}
}
-475
View File
@@ -1,475 +0,0 @@
<?php
use Cake\Cache\Engine\FileEngine;
use Cake\Database\Connection;
use Cake\Database\Driver\Mysql;
use Cake\Log\Engine\FileLog;
use Cake\Mailer\Transport\MailTransport;
use function Cake\Core\env;
return [
/*
* Debug Level:
*
* Production Mode:
* false: No error messages, errors, or warnings shown.
*
* Development Mode:
* true: Errors and warnings shown.
*/
'debug' => filter_var(env('DEBUG', false), FILTER_VALIDATE_BOOLEAN),
/*
* Configure basic information about the application.
*
* - namespace - The namespace to find app classes under.
* - defaultLocale - The default locale for translation, formatting currencies and numbers, date and time.
* - encoding - The encoding used for HTML + database connections.
* - base - The base directory the app resides in. If false this
* will be auto-detected.
* - dir - Name of app directory.
* - webroot - The webroot directory.
* - wwwRoot - The file path to webroot.
* - baseUrl - To configure CakePHP to *not* use mod_rewrite and to
* use CakePHP pretty URLs, remove these .htaccess
* files:
* /.htaccess
* /webroot/.htaccess
* And uncomment the baseUrl key below.
* - fullBaseUrl - SECURITY: A base URL to use for absolute links.
* IMPORTANT: This MUST be set in production to prevent Host Header Injection attacks
* that can compromise password reset and other security-critical features.
* Set this via APP_FULL_BASE_URL environment variable or directly in config.
* Example: 'https://example.com'
* When not set, the application will throw an exception in production mode.
* - imageBaseUrl - Web path to the public images/ directory under webroot.
* - cssBaseUrl - Web path to the public css/ directory under webroot.
* - jsBaseUrl - Web path to the public js/ directory under webroot.
* - paths - Configure paths for non class-based resources. Supports the
* `plugins`, `templates`, `locales` subkeys, which allow the definition of
* paths for plugins, view templates and locale files respectively.
*/
'App' => [
'namespace' => 'App',
'encoding' => env('APP_ENCODING', 'UTF-8'),
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', 'UTC'),
'base' => false,
'dir' => 'src',
'webroot' => 'webroot',
'wwwRoot' => WWW_ROOT,
//'baseUrl' => env('SCRIPT_NAME'),
'fullBaseUrl' => env('APP_FULL_BASE_URL', false),
'imageBaseUrl' => 'img/',
'cssBaseUrl' => 'css/',
'jsBaseUrl' => 'js/',
'paths' => [
'plugins' => [ROOT . DS . 'plugins' . DS],
'templates' => [ROOT . DS . 'templates' . DS],
'locales' => [RESOURCES . 'locales' . DS],
],
],
/*
* Security and encryption configuration
*
* - salt - A random string used in security hashing methods.
* The salt value is also used as the encryption key.
* You should treat it as extremely sensitive data.
*/
'Security' => [
'salt' => env('SECURITY_SALT'),
],
/*
* Apply timestamps with the last modified time to static assets (js, css, images).
* Will append a querystring parameter containing the time the file was modified.
* This is useful for busting browser caches.
*
* Set to true to apply timestamps when debug is true. Set to 'force' to always
* enable timestamping regardless of debug value.
*/
'Asset' => [
//'timestamp' => true,
// 'cacheTime' => '+1 year'
],
/*
* Configure the cache adapters.
*/
'Cache' => [
'default' => [
'className' => FileEngine::class,
'path' => CACHE,
'url' => env('CACHE_DEFAULT_URL', null),
],
/*
* Configure the cache used for general framework caching.
* Translation cache files are stored with this configuration.
* Duration will be set to '+2 minutes' in bootstrap.php when debug = true
* If you set 'className' => 'Null' core cache will be disabled.
*/
'_cake_translations_' => [
'className' => FileEngine::class,
'prefix' => 'myapp_cake_translations_',
'path' => CACHE . 'persistent' . DS,
'serialize' => true,
'duration' => '+1 years',
'url' => env('CACHE_CAKECORE_URL', null),
],
/*
* Configure the cache for model and datasource caches. This cache
* configuration is used to store schema descriptions, and table listings
* in connections.
* Duration will be set to '+2 minutes' in bootstrap.php when debug = true
*/
'_cake_model_' => [
'className' => FileEngine::class,
'prefix' => 'myapp_cake_model_',
'path' => CACHE . 'models' . DS,
'serialize' => true,
'duration' => '+1 years',
'url' => env('CACHE_CAKEMODEL_URL', null),
],
],
/*
* Configure the Error and Exception handlers used by your application.
*
* By default errors are displayed using Debugger, when debug is true and logged
* by Cake\Log\Log when debug is false.
*
* In CLI environments exceptions will be printed to stderr with a backtrace.
* In web environments an HTML page will be displayed for the exception.
* With debug true, framework errors like Missing Controller will be displayed.
* When debug is false, framework errors will be coerced into generic HTTP errors.
*
* Options:
*
* - `errorLevel` - int - The level of errors you are interested in capturing.
* - `trace` - boolean - Whether backtraces should be included in
* logged errors/exceptions.
* - `log` - boolean - Whether you want exceptions logged.
* - `exceptionRenderer` - string - The class responsible for rendering uncaught exceptions.
* The chosen class will be used for both CLI and web environments. If you want different
* classes used in CLI and web environments you'll need to write that conditional logic as well.
* The conventional location for custom renderers is in `src/Error`. Your exception renderer needs to
* implement the `render()` method and return either a string or Http\Response.
* `errorRenderer` - string - The class responsible for rendering PHP errors. The selected
* class will be used for both web and CLI contexts. If you want different classes for each environment
* you'll need to write that conditional logic as well. Error renderers need to
* to implement the `Cake\Error\ErrorRendererInterface`.
* - `skipLog` - array - List of exceptions to skip for logging. Exceptions that
* extend one of the listed exceptions will also be skipped for logging.
* E.g.:
* `'skipLog' => ['Cake\Http\Exception\NotFoundException', 'Cake\Http\Exception\UnauthorizedException']`
* - `extraFatalErrorMemory` - int - The number of megabytes to increase the memory limit by
* when a fatal error is encountered. This allows
* breathing room to complete logging or error handling.
* - `ignoredDeprecationPaths` - array - A list of glob-compatible file paths that deprecations
* should be ignored in. Use this to ignore deprecations for plugins or parts of
* your application that still emits deprecations.
* - `traceFormat` - when logging errors, List of `'array'`, `'points'`, `'shortPoints'`, defaults to `shortPoints`.
*/
'Error' => [
'exceptionRenderer' => \App\Error\ApiExceptionRenderer::class,
'errorLevel' => E_ALL,
// Expected API client errors (404/401/403/400) are not worth logging
'skipLog' => [
\Cake\Datasource\Exception\RecordNotFoundException::class,
\Cake\Http\Exception\NotFoundException::class,
\Cake\Http\Exception\UnauthorizedException::class,
\Cake\Http\Exception\ForbiddenException::class,
\Cake\Http\Exception\BadRequestException::class,
\Cake\Http\Exception\MethodNotAllowedException::class,
\Authentication\Authenticator\UnauthenticatedException::class,
\Cake\Routing\Exception\MissingRouteException::class,
],
'log' => true,
'trace' => true,
'ignoredDeprecationPaths' => [],
'traceFormat' => null,
],
/*
* Debugger configuration
*
* Define development error values for Cake\Error\Debugger
*
* - `editor` Set the editor URL format you want to use.
* By default atom, emacs, macvim, phpstorm, sublime, textmate, and vscode are
* available. You can add additional editor link formats using
* `Debugger::addEditor()` during your application bootstrap.
* - `editorBasePath` - The base path to your project for editor integration.
* Used to generate file links in stack traces.
* - `outputMask` A mapping of `key` to `replacement` values that
* `Debugger` should replace in dumped data and logs generated by `Debugger`.
*/
'Debugger' => [
'editor' => 'phpstorm',
],
/*
* Email configuration.
*
* By defining transports separately from delivery profiles you can easily
* re-use transport configuration across multiple profiles.
*
* You can specify multiple configurations for production, development and
* testing.
*
* Each transport needs a `className`. Valid options are as follows:
*
* Mail - Send using PHP mail function
* Smtp - Send using SMTP
* Debug - Do not send the email, just return the result
*
* You can add custom transports (or override existing transports) by adding the
* appropriate file to src/Mailer/Transport. Transports should be named
* 'YourTransport.php', where 'Your' is the name of the transport.
*/
'EmailTransport' => [
'default' => [
'className' => MailTransport::class,
/*
* The keys host, port, timeout, username, password, client and tls
* are used in SMTP transports
*/
'host' => 'localhost',
'port' => 25,
'timeout' => 30,
/*
* It is recommended to set these options through your environment or app_local.php
*/
//'username' => null,
//'password' => null,
'client' => null,
'tls' => false,
'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
],
],
/*
* Email delivery profiles
*
* Delivery profiles allow you to predefine various properties about email
* messages from your application and give the settings a name. This saves
* duplication across your application and makes maintenance and development
* easier. Each profile accepts a number of keys. See `Cake\Mailer\Mailer`
* for more information.
*/
'Email' => [
'default' => [
'transport' => 'default',
'from' => 'you@localhost',
/*
* Will by default be set to config value of App.encoding, if that exists otherwise to UTF-8.
*/
//'charset' => 'utf-8',
//'headerCharset' => 'utf-8',
],
],
/*
* Connection information used by the ORM to connect
* to your application's datastores.
*
* ### Notes
* - Drivers include Mysql Postgres Sqlite Sqlserver
* See vendor\cakephp\cakephp\src\Database\Driver for the complete list
* - Do not use periods in database name - it may lead to errors.
* See https://github.com/cakephp/cakephp/issues/6471 for details.
* - 'encoding' is recommended to be set to full UTF-8 4-Byte support.
* E.g set it to 'utf8mb4' in MariaDB and MySQL and 'utf8' for any
* other RDBMS.
*/
'Datasources' => [
/*
* These configurations should contain permanent settings used
* by all environments.
*
* The values in app_local.php will override any values set here
* and should be used for local and per-environment configurations.
*
* Environment variable-based configurations can be loaded here or
* in app_local.php depending on the application's needs.
*/
'default' => [
'className' => Connection::class,
'driver' => Mysql::class,
'persistent' => false,
'timezone' => 'UTC',
/*
* For MariaDB/MySQL the internal default changed from utf8 to utf8mb4, aka full utf-8 support
*/
'encoding' => 'utf8mb4',
/*
* If your MySQL server is configured with `skip-character-set-client-handshake`
* then you MUST use the `flags` config to set your charset encoding.
* For e.g. `'flags' => [\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4']`
*/
'flags' => [],
'cacheMetadata' => true,
'log' => false,
/*
* Set identifier quoting to true if you are using reserved words or
* special characters in your table or column names. Enabling this
* setting will result in queries built using the Query Builder having
* identifiers quoted when creating SQL. It should be noted that this
* decreases performance because each query needs to be traversed and
* manipulated before being executed.
*/
'quoteIdentifiers' => false,
/*
* During development, if using MySQL < 5.6, uncommenting the
* following line could boost the speed at which schema metadata is
* fetched from the database. It can also be set directly with the
* mysql configuration directive 'innodb_stats_on_metadata = 0'
* which is the recommended value in production environments
*/
//'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
],
/*
* The test connection is used during the test suite.
*/
'test' => [
'className' => Connection::class,
'driver' => Mysql::class,
'persistent' => false,
'timezone' => 'UTC',
'encoding' => 'utf8mb4',
'flags' => [],
'cacheMetadata' => true,
'quoteIdentifiers' => false,
'log' => false,
//'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
],
],
/*
* Configures logging options
*/
'Log' => [
'debug' => [
'className' => FileLog::class,
'path' => LOGS,
'file' => 'debug',
'url' => env('LOG_DEBUG_URL', null),
'scopes' => null,
'levels' => ['notice', 'info', 'debug'],
],
'error' => [
'className' => FileLog::class,
'path' => LOGS,
'file' => 'error',
'url' => env('LOG_ERROR_URL', null),
'scopes' => null,
'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
],
// To enable this dedicated query log, you need to set your datasource's log flag to true
'queries' => [
'className' => FileLog::class,
'path' => LOGS,
'file' => 'queries',
'url' => env('LOG_QUERIES_URL', null),
'scopes' => ['cake.database.queries'],
],
],
/*
* Session configuration.
*
* Contains an array of settings to use for session configuration. The
* `defaults` key is used to define a default preset to use for sessions, any
* settings declared here will override the settings of the default config.
*
* ## Options
*
* - `cookie` - The name of the cookie to use. Defaults to value set for `session.name` php.ini config.
* Avoid using `.` in cookie names, as PHP will drop sessions from cookies with `.` in the name.
* - `cookiePath` - The url path for which session cookie is set. Maps to the
* `session.cookie_path` php.ini config. Defaults to base path of app.
* - `timeout` - The time in minutes a session can be 'idle'. If no request is received in
* this duration, the session will be expired and rotated. Pass 0 to disable idle timeout checks.
* - `defaults` - The default configuration set to use as a basis for your session.
* There are four built-in options: php, cake, cache, database.
* - `handler` - Can be used to enable a custom session handler. Expects an
* array with at least the `engine` key, being the name of the Session engine
* class to use for managing the session. CakePHP bundles the `CacheSession`
* and `DatabaseSession` engines.
* - `ini` - An associative array of additional 'session.*` ini values to set.
*
* Within the `ini` key, you will likely want to define:
*
* - `session.cookie_lifetime` - The number of seconds that cookies are valid for. This
* should be longer than `Session.timeout`.
* - `session.gc_maxlifetime` - The number of seconds after which a session is considered 'garbage'
* that can be deleted by PHP's session cleanup behavior. This value should be greater than both
* `Sesssion.timeout` and `session.cookie_lifetime`.
*
* The built-in `defaults` options are:
*
* - 'php' - Uses settings defined in your php.ini.
* - 'cake' - Saves session files in CakePHP's /tmp directory.
* - 'database' - Uses CakePHP's database sessions.
* - 'cache' - Use the Cache class to save sessions.
*
* To define a custom session handler, save it at src/Http/Session/<name>.php.
* Make sure the class implements PHP's `SessionHandlerInterface` and set
* Session.handler to <name>
*
* To use database sessions, load the SQL file located at config/schema/sessions.sql
*/
'Session' => [
'defaults' => 'php',
],
/**
* DebugKit configuration.
*
* Contains an array of configurations to apply to the DebugKit plugin, if loaded.
* Documentation: https://book.cakephp.org/debugkit/5/en/index.html#configuration
*
* ## Options
*
* - `panels` - Enable or disable panels. The key is the panel name, and the value is true to enable,
* or false to disable.
* - `includeSchemaReflection` - Set to true to enable logging of schema reflection queries. Disabled by default.
* - `safeTld` - Set an array of whitelisted TLDs for local development.
* - `forceEnable` - Force DebugKit to display. Careful with this, it is usually safer to simply whitelist
* your local TLDs.
* - `ignorePathsPattern` - Regex pattern (including delimiter) to ignore paths.
* DebugKit won’t save data for request URLs that match this regex.
* - `ignoreAuthorization` - Set to true to ignore Cake Authorization plugin for DebugKit requests.
* Disabled by default.
* - `maxDepth` - Defines how many levels of nested data should be shown in general for debug output.
* Default is 5. WARNING: Increasing the max depth level can lead to an out of memory error.
* - `variablesPanelMaxDepth` - Defines how many levels of nested data should be shown in the variables tab.
* Default is 5. WARNING: Increasing the max depth level can lead to an out of memory error.
*/
'DebugKit' => [
'forceEnable' => filter_var(env('DEBUG_KIT_FORCE_ENABLE', false), FILTER_VALIDATE_BOOLEAN),
'safeTld' => env('DEBUG_KIT_SAFE_TLD', null),
'ignoreAuthorization' => env('DEBUG_KIT_IGNORE_AUTHORIZATION', false),
],
/**
* TestSuite configuration.
*
* ## Options
*
* - `errorLevel` - Defaults to `E_ALL`. Can be set to `false` to disable overwrite error level.
* - `fixtureStrategy` - Defaults to TruncateStrategy. Can be set to any class implementing FixtureStrategyInterface.
*/
'TestSuite' => [
'errorLevel' => null,
'fixtureStrategy' => null,
],
];
-97
View File
@@ -1,97 +0,0 @@
<?php
use function Cake\Core\env;
/*
* Local configuration file to provide any overrides to your app.php configuration.
* Copy and save this file as app_local.php and make changes as required.
* Note: It is not recommended to commit files with credentials such as app_local.php
* into source code version control.
*/
return [
/*
* Debug Level:
*
* Production Mode:
* false: No error messages, errors, or warnings shown.
*
* Development Mode:
* true: Errors and warnings shown.
*/
'debug' => filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),
/*
* Security and encryption configuration
*
* - salt - A random string used in security hashing methods.
* The salt value is also used as the encryption key.
* You should treat it as extremely sensitive data.
*/
'Security' => [
'salt' => env('SECURITY_SALT', '__SALT__'),
],
/*
* Connection information used by the ORM to connect
* to your application's datastores.
*
* See app.php for more configuration options.
*/
'Datasources' => [
'default' => [
'host' => 'localhost',
/*
* CakePHP will use the default DB port based on the driver selected
* MySQL on MAMP uses port 8889, MAMP users will want to uncomment
* the following line and set the port accordingly
*/
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'my_app',
/*
* If not using the default 'public' schema with the PostgreSQL driver
* set it here.
*/
//'schema' => 'myapp',
/*
* You can use a DSN string to set the entire configuration
*/
'url' => env('DATABASE_URL', null),
],
/*
* The test connection is used during the test suite.
*/
'test' => [
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'test_myapp',
//'schema' => 'myapp',
'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tmp/tests.sqlite'),
],
],
/*
* Email configuration.
*
* Host and credential configuration in case you are using SmtpTransport
*
* See app.php for more configuration options.
*/
'EmailTransport' => [
'default' => [
'host' => 'localhost',
'port' => 25,
'username' => null,
'password' => null,
'client' => null,
'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
],
],
];
-107
View File
@@ -1,107 +0,0 @@
<?php
use function Cake\Core\env;
/*
* Local configuration file to provide any overrides to your app.php configuration.
* Copy and save this file as app_local.php and make changes as required.
* Note: It is not recommended to commit files with credentials such as app_local.php
* into source code version control.
*/
return [
/*
* Debug Level:
*
* Production Mode:
* false: No error messages, errors, or warnings shown.
*
* Development Mode:
* true: Errors and warnings shown.
*/
'debug' => filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),
/*
* Security and encryption configuration
*
* - salt - A random string used in security hashing methods.
* The salt value is also used as the encryption key.
* You should treat it as extremely sensitive data.
*/
'Security' => [
'salt' => env('SECURITY_SALT', '49fe3800e65a39586782ea33bdec25c951ed15fe4ddbd4fdf67a8fb7ae8cffa3'),
],
/*
* Connection information used by the ORM to connect
* to your application's datastores.
*
* See app.php for more configuration options.
*/
/*
* JWT configuration for API authentication.
* - secret: HS256 signing key (keep it private, min. 32 bytes)
* - ttl: token lifetime in seconds
*/
'Jwt' => [
'secret' => env('JWT_SECRET', '4b86c9e074a44d46c6e43103f7cba15972b058cfcc69a1d24c7c69e77d5884a1'),
'ttl' => 3600 * 8,
],
'Datasources' => [
'default' => [
'host' => '192.168.1.10',
/*
* CakePHP will use the default DB port based on the driver selected
* MySQL on MAMP uses port 8889, MAMP users will want to uncomment
* the following line and set the port accordingly
*/
//'port' => 'non_standard_port_number',
'username' => 'testapi',
'password' => 'sw]vV-VCvDjiahu3',
'database' => 'testapi',
/*
* If not using the default 'public' schema with the PostgreSQL driver
* set it here.
*/
//'schema' => 'myapp',
/*
* You can use a DSN string to set the entire configuration
*/
'url' => env('DATABASE_URL', null),
],
/*
* The test connection is used during the test suite.
*/
'test' => [
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'test_myapp',
//'schema' => 'myapp',
'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tmp/tests.sqlite'),
],
],
/*
* Email configuration.
*
* Host and credential configuration in case you are using SmtpTransport
*
* See app.php for more configuration options.
*/
'EmailTransport' => [
'default' => [
'host' => 'localhost',
'port' => 25,
'username' => null,
'password' => null,
'client' => null,
'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
],
],
];
-237
View File
@@ -1,237 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.8
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
/*
* This file is loaded by your src/Application.php bootstrap method.
* Feel free to extend/extract parts of the bootstrap process into your own files
* to suit your needs/preferences.
*/
/*
* Configure paths required to find CakePHP + general filepath constants
*/
require __DIR__ . DIRECTORY_SEPARATOR . 'paths.php';
/*
* Bootstrap CakePHP
* Currently all this does is initialize the router (without loading your routes)
*/
require CORE_PATH . 'config' . DS . 'bootstrap.php';
use Cake\Cache\Cache;
use Cake\Core\Configure;
use Cake\Core\Configure\Engine\PhpConfig;
use Cake\Datasource\ConnectionManager;
use Cake\Error\ErrorTrap;
use Cake\Error\ExceptionTrap;
use Cake\Http\ServerRequest;
use Cake\Log\Log;
use Cake\Mailer\Mailer;
use Cake\Mailer\TransportFactory;
use Cake\Routing\Router;
use Cake\Utility\Security;
use Detection\MobileDetect;
use function Cake\Core\env;
/*
* Load global functions for collections, translations, debugging etc.
*/
require CAKE . 'functions.php';
/*
* See https://github.com/josegonzalez/php-dotenv for API details.
*
* Uncomment block of code below if you want to use `.env` file during development.
* You should copy `config/.env.example` to `config/.env` and set/modify the
* variables as required.
*
* The purpose of the .env file is to emulate the presence of the environment
* variables like they would be present in production.
*
* If you use .env files, be careful to not commit them to source control to avoid
* security risks. See https://github.com/josegonzalez/php-dotenv#general-security-information
* for more information for recommended practices.
*/
// if (!env('APP_NAME') && file_exists(CONFIG . '.env')) {
// $dotenv = new \josegonzalez\Dotenv\Loader([CONFIG . '.env']);
// $dotenv->parse()
// ->putenv()
// ->toEnv()
// ->toServer();
// }
/*
* Initializes default Config store and loads the main configuration file (app.php)
*
* CakePHP contains 2 configuration files after project creation:
* - `config/app.php` for the default application configuration.
* - `config/app_local.php` for environment specific configuration.
*/
try {
Configure::config('default', new PhpConfig());
Configure::load('app', 'default', false);
} catch (Exception $e) {
exit($e->getMessage() . "\n");
}
/*
* Load an environment local configuration file to provide overrides to your configuration.
* Notice: For security reasons app_local.php **should not** be included in your git repo.
*/
if (file_exists(CONFIG . 'app_local.php')) {
Configure::load('app_local', 'default');
}
/*
* When debug = true the metadata cache should only last for a short time.
*/
if (Configure::read('debug')) {
Configure::write('Cache._cake_model_.duration', '+1 minute');
Configure::write('Cache._cake_translations_.duration', '+1 minute');
}
/*
* Set the default server timezone. Using UTC makes time calculations / conversions easier.
* Check https://php.net/manual/en/timezones.php for list of valid timezone strings.
*/
date_default_timezone_set(Configure::read('App.defaultTimezone'));
/*
* Configure the mbstring extension to use the correct encoding.
*/
mb_internal_encoding(Configure::read('App.encoding'));
/*
* Set the default locale. This controls how dates, number and currency is
* formatted and sets the default language to use for translations.
*/
ini_set('intl.default_locale', Configure::read('App.defaultLocale'));
/*
* Register application error and exception handlers.
*/
(new ErrorTrap(Configure::read('Error')))->register();
(new ExceptionTrap(Configure::read('Error')))->register();
/*
* CLI/Command specific configuration.
*/
if (PHP_SAPI === 'cli') {
// Set the fullBaseUrl to allow URLs to be generated in commands.
// This is useful when sending email from commands.
// Configure::write('App.fullBaseUrl', php_uname('n'));
// Set logs to different files so they don't have permission conflicts.
if (Configure::check('Log.debug')) {
Configure::write('Log.debug.file', 'cli-debug');
}
if (Configure::check('Log.error')) {
Configure::write('Log.error.file', 'cli-error');
}
}
/*
* Set the full base URL for the application.
*
* SECURITY: In production, App.fullBaseUrl MUST be explicitly configured to prevent
* Host Header Injection attacks. The HostHeaderMiddleware enforces this requirement
* and validates incoming Host headers against the configured value.
*
* Set APP_FULL_BASE_URL in your environment variables or configure App.fullBaseUrl
* in config/app.php or config/app_local.php
*
* Example: APP_FULL_BASE_URL=https://example.com
*/
$fullBaseUrl = Configure::read('App.fullBaseUrl');
if (!$fullBaseUrl) {
$httpHost = env('HTTP_HOST');
/*
* Development mode fallback: Use HTTP_HOST for convenience.
* WARNING: This is ONLY safe in development. In production, the
* HostHeaderMiddleware will reject requests when fullBaseUrl is not configured.
*/
if ($httpHost) {
$s = null;
if (env('HTTPS') || env('HTTP_X_FORWARDED_PROTO') === 'https') {
$s = 's';
}
$fullBaseUrl = 'http' . $s . '://' . $httpHost;
}
unset($httpHost, $s);
}
if ($fullBaseUrl) {
Router::fullBaseUrl($fullBaseUrl);
}
unset($fullBaseUrl);
/*
* Apply the loaded configuration settings to their respective systems.
* This will also remove the loaded config data from memory.
*/
Cache::setConfig(Configure::consume('Cache'));
ConnectionManager::setConfig(Configure::consume('Datasources'));
TransportFactory::setConfig(Configure::consume('EmailTransport'));
Mailer::setConfig(Configure::consume('Email'));
Log::setConfig(Configure::consume('Log'));
Security::setSalt(Configure::consume('Security.salt'));
/*
* Setup detectors for mobile and tablet.
* If you don't use these checks you can safely remove this code
* and the mobiledetect package from composer.json.
*/
ServerRequest::addDetector('mobile', function ($request) {
$detector = new MobileDetect();
return $detector->isMobile();
});
ServerRequest::addDetector('tablet', function ($request) {
$detector = new MobileDetect();
return $detector->isTablet();
});
/*
* You can enable default locale format parsing by adding calls
* to `useLocaleParser()`. This enables the automatic conversion of
* locale specific date formats when processing request data. For details see
* @link https://book.cakephp.org/5/en/core-libraries/internationalization-and-localization.html#parsing-localized-datetime-data
*/
// \Cake\Database\TypeFactory::build('time')->useLocaleParser();
// \Cake\Database\TypeFactory::build('date')->useLocaleParser();
// \Cake\Database\TypeFactory::build('datetime')->useLocaleParser();
// \Cake\Database\TypeFactory::build('timestamp')->useLocaleParser();
// \Cake\Database\TypeFactory::build('datetimefractional')->useLocaleParser();
// \Cake\Database\TypeFactory::build('timestampfractional')->useLocaleParser();
// \Cake\Database\TypeFactory::build('datetimetimezone')->useLocaleParser();
// \Cake\Database\TypeFactory::build('timestamptimezone')->useLocaleParser();
/*
* Custom Inflector rules, can be set to correctly pluralize or singularize
* table, model, controller names or whatever other string is passed to the
* inflection functions.
*/
// \Cake\Utility\Inflector::rules('plural', ['/^(inflect)or$/i' => '\1ables']);
// \Cake\Utility\Inflector::rules('irregular', ['red' => 'redlings']);
// \Cake\Utility\Inflector::rules('uninflected', ['dontinflectme']);
// set a custom date and time format
// see https://book.cakephp.org/5/en/core-libraries/time.html#setting-the-default-locale-and-format-string
// and https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
// \Cake\I18n\Date::setToStringFormat('dd.MM.yyyy');
// \Cake\I18n\Time::setToStringFormat('dd.MM.yyyy HH:mm');
-94
View File
@@ -1,94 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license MIT License (https://opensource.org/licenses/mit-license.php)
*/
/*
* Use the DS to separate the directories in other defines
*/
if (!defined('DS')) {
define('DS', DIRECTORY_SEPARATOR);
}
/*
* These definitions should only be edited if you have cake installed in
* a directory layout other than the way it is distributed.
* When using custom settings be sure to use the DS and do not add a trailing DS.
*/
/*
* The full path to the directory which holds "src", WITHOUT a trailing DS.
*/
define('ROOT', dirname(__DIR__));
/*
* The actual directory name for the application directory. Normally
* named 'src'.
*/
define('APP_DIR', 'src');
/*
* Path to the application's directory.
*/
define('APP', ROOT . DS . APP_DIR . DS);
/*
* Path to the config directory.
*/
define('CONFIG', ROOT . DS . 'config' . DS);
/*
* File path to the webroot directory.
*
* To derive your webroot from your webserver change this to:
*
* `define('WWW_ROOT', rtrim($_SERVER['DOCUMENT_ROOT'], DS) . DS);`
*/
define('WWW_ROOT', ROOT . DS . 'webroot' . DS);
/*
* Path to the tests directory.
*/
define('TESTS', ROOT . DS . 'tests' . DS);
/*
* Path to the temporary files directory.
*/
define('TMP', ROOT . DS . 'tmp' . DS);
/*
* Path to the logs directory.
*/
define('LOGS', ROOT . DS . 'logs' . DS);
/*
* Path to the cache files directory. It can be shared between hosts in a multi-server setup.
*/
define('CACHE', TMP . 'cache' . DS);
/*
* Path to the resources directory.
*/
define('RESOURCES', ROOT . DS . 'resources' . DS);
/*
* The absolute path to the "cake" directory, WITHOUT a trailing DS.
*
* CakePHP should always be installed with composer, so look there.
*/
define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'vendor' . DS . 'cakephp' . DS . 'cakephp');
/*
* Path to the cake directory.
*/
define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS);
define('CAKE', CORE_PATH . 'src' . DS);
-37
View File
@@ -1,37 +0,0 @@
<?php
/**
* Plugin configuration.
*
* In this file, you configure which plugins are loaded in the different states your app can be.
* It's loaded via the `parent::bootstrap();` call inside your `Application::bootstrap()` method.
* For more information see https://book.cakephp.org/5/en/plugins.html#loading-plugins-via-configuration-array
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 5.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
/*
* List of plugins to load in the form `PluginName` => `[configuration options]`.
*
* Available options:
* - onlyDebug: Load the plugin only in debug mode. Default false.
* - onlyCli: Load the plugin only in CLI mode. Default false.
* - optional: Do not throw an exception if the plugin is not found. Default false.
*/
return [
'DebugKit' => ['onlyDebug' => true],
'Bake' => ['onlyCli' => true, 'optional' => true],
'Migrations' => ['onlyCli' => true],
'Authentication' => [],
// Additional plugins here
];
-94
View File
@@ -1,94 +0,0 @@
<?php
/**
* Routes configuration.
*
* In this file, you set up routes to your controllers and their actions.
* Routes are very important mechanism that allows you to freely connect
* different URLs to chosen controllers and their actions (functions).
*
* It's loaded within the context of `Application::routes()` method which
* receives a `RouteBuilder` instance `$routes` as method argument.
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
/*
* This file is loaded in the context of the `Application` class.
* So you can use `$this` to reference the application class instance
* if required.
*/
return function (RouteBuilder $routes): void {
/*
* The default class to use for all routes
*
* The following route classes are supplied with CakePHP and are appropriate
* to set as the default:
*
* - Route
* - InflectedRoute
* - DashedRoute
*
* If no call is made to `Router::defaultRouteClass()`, the class used is
* `Route` (`Cake\Routing\Route\Route`)
*
* Note that `Route` does not do any inflections on URLs which will result in
* inconsistently cased URLs when used with `{plugin}`, `{controller}` and
* `{action}` markers.
*/
$routes->setRouteClass(DashedRoute::class);
$routes->scope('/', function (RouteBuilder $builder): void {
// The base path goes straight to the API documentation
$builder->redirect('/', '/docs');
// Swagger UI (API documentation), spec served from webroot/openapi.yaml
$builder->connect('/docs', ['controller' => 'Docs', 'action' => 'index']);
/*
* ...and connect the rest of 'Pages' controller's URLs.
*/
$builder->connect('/pages/*', 'Pages::display');
});
/*
* REST API - all endpoints under /api, JSON in and out.
*/
$routes->prefix('Api', function (RouteBuilder $builder): void {
$builder->post('/auth/login', ['controller' => 'Auth', 'action' => 'login']);
$builder->connect('/auth/me', ['controller' => 'Auth', 'action' => 'me']);
$builder->resources('Users');
$builder->resources('Customers', function (RouteBuilder $builder): void {
$builder->resources('Cars', ['only' => ['index', 'create']]);
});
$builder->resources('Cars');
});
/*
* If you need a different set of middleware or none at all,
* open new scope and define routes there.
*
* ```
* $routes->scope('/api', function (RouteBuilder $builder): void {
* // No $builder->applyMiddleware() here.
*
* // Parse specified extensions from URLs
* // $builder->setExtensions(['json', 'xml']);
*
* // Connect API actions here.
* });
* ```
*/
};
-18
View File
@@ -1,18 +0,0 @@
# Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
#
# Licensed under The MIT License
# For full copyright and license information, please see the LICENSE.txt
# Redistributions of files must retain the above copyright notice.
# MIT License (https://opensource.org/licenses/mit-license.php)
CREATE TABLE i18n (
id int NOT NULL auto_increment,
locale varchar(6) NOT NULL,
model varchar(255) NOT NULL,
foreign_key int(10) NOT NULL,
field varchar(255) NOT NULL,
content text,
PRIMARY KEY (id),
UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
INDEX I18N_FIELD(model, foreign_key, field)
);
-15
View File
@@ -1,15 +0,0 @@
# Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
#
# Licensed under The MIT License
# For full copyright and license information, please see the LICENSE.txt
# Redistributions of files must retain the above copyright notice.
# MIT License (https://opensource.org/licenses/mit-license.php)
CREATE TABLE `sessions` (
`id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP, -- optional, requires MySQL 5.6.5+
`modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- optional, requires MySQL 5.6.5+
`data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob
`expires` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-16
View File
@@ -1,16 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
require 'webroot' . DIRECTORY_SEPARATOR . 'index.php';
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0"?>
<ruleset name="App">
<rule ref="CakePHP"/>
<rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint">
<exclude-pattern>*/src/Controller/*</exclude-pattern>
</rule>
<file>src/</file>
<file>tests/</file>
</ruleset>
-7
View File
@@ -1,7 +0,0 @@
parameters:
level: 8
treatPhpDocTypesAsCertain: false
bootstrapFiles:
- config/bootstrap.php
paths:
- src/
-36
View File
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
colors="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
<php>
<ini name="memory_limit" value="-1"/>
</php>
<!-- Add any additional test suites you want to run here -->
<testsuites>
<testsuite name="app">
<directory>tests/TestCase/</directory>
</testsuite>
<!-- Add plugin test suites here. -->
</testsuites>
<!-- Load extension for fixtures -->
<extensions>
<bootstrap class="Cake\TestSuite\Fixture\Extension\PHPUnitExtension"/>
</extensions>
<!-- Ignore vendor tests in code coverage reports -->
<source>
<include>
<directory suffix=".php">src/</directory>
<directory suffix=".php">plugins/*/src/</directory>
</include>
<exclude>
<file>src/Console/Installer.php</file>
</exclude>
</source>
</phpunit>
View File
-15
View File
@@ -1,15 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src/"/>
<ignoreFiles>
<directory name="vendor/"/>
</ignoreFiles>
</projectFiles>
</psalm>
View File
-182
View File
@@ -1,182 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.3.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App;
use App\Middleware\CorsMiddleware;
use App\Middleware\HostHeaderMiddleware;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Core\Configure;
use Cake\Core\ContainerInterface;
use Cake\Datasource\FactoryLocator;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Event\EventManagerInterface;
use Cake\Http\BaseApplication;
use Cake\Http\Middleware\BodyParserMiddleware;
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\ORM\Locator\TableLocator;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Psr\Http\Message\ServerRequestInterface;
/**
* Application setup class.
*
* This defines the bootstrapping logic and middleware layers you
* want to use in your application.
*
* @extends \Cake\Http\BaseApplication<\App\Application>
*/
class Application extends BaseApplication implements AuthenticationServiceProviderInterface
{
/**
* Load all the application configuration and bootstrap logic.
*
* @return void
*/
public function bootstrap(): void
{
// Call parent to load bootstrap from files.
parent::bootstrap();
// By default, does not allow fallback classes.
FactoryLocator::add(
'Table',
(new TableLocator())->allowFallbackClass(false),
);
}
/**
* Setup the middleware queue your application will use.
*
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
* @return \Cake\Http\MiddlewareQueue The updated middleware queue.
*/
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
// CORS headers for the REST API (outermost, so error responses get them too)
->add(new CorsMiddleware())
// Catch any exceptions in the lower layers,
// and make an error page/response
->add(new ErrorHandlerMiddleware(Configure::read('Error'), $this))
// Validate Host header to prevent Host Header Injection attacks.
// In production, ensures App.fullBaseUrl is configured and validates
// the incoming Host header against it.
->add(new HostHeaderMiddleware())
// Handle plugin/theme assets like CakePHP normally does.
->add(new AssetMiddleware([
'cacheTime' => Configure::read('Asset.cacheTime'),
]))
// Add routing middleware.
// If you have a large number of routes connected, turning on routes
// caching in production could improve performance.
// See https://github.com/CakeDC/cakephp-cached-routing
->add(new RoutingMiddleware($this))
// Parse various types of encoded request bodies so that they are
// available as array through $request->getData()
// https://book.cakephp.org/5/en/controllers/middleware.html#body-parser-middleware
->add(new BodyParserMiddleware())
// JWT / login authentication for the REST API
->add(new AuthenticationMiddleware($this))
// Cross Site Request Forgery (CSRF) Protection Middleware
// https://book.cakephp.org/5/en/security/csrf.html#cross-site-request-forgery-csrf-middleware
// The stateless /api endpoints (JWT) are excluded from CSRF checks.
->add((new CsrfProtectionMiddleware([
'httponly' => true,
]))->skipCheckCallback(function (ServerRequestInterface $request): bool {
return str_starts_with($request->getUri()->getPath(), '/api');
}));
return $middlewareQueue;
}
/**
* Register application container services.
*
* @param \Cake\Core\ContainerInterface $container The Container to update.
* @return void
* @link https://book.cakephp.org/5/en/development/dependency-injection.html#dependency-injection
*/
public function services(ContainerInterface $container): void
{
// Allow your Tables to be dependency injected
//$container->delegate(new \Cake\ORM\Locator\TableContainer());
}
/**
* Register custom event listeners here
*
* @param \Cake\Event\EventManagerInterface $eventManager
* @return \Cake\Event\EventManagerInterface
* @link https://book.cakephp.org/5/en/core-libraries/events.html#registering-listeners
*/
public function events(EventManagerInterface $eventManager): EventManagerInterface
{
// $eventManager->on(new SomeCustomListenerClass());
return $eventManager;
}
/**
* Returns the authentication service: JWT bearer tokens for API calls
* and email/password credentials for the login endpoint.
*
* @param \Psr\Http\Message\ServerRequestInterface $request Request
* @return \Authentication\AuthenticationServiceInterface
*/
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
$resolver = [
'className' => 'Authentication.Orm',
'userModel' => 'Users',
'finder' => 'active',
];
$service->loadAuthenticator('Authentication.Jwt', [
'identifier' => [
'className' => 'Authentication.JwtSubject',
'resolver' => $resolver,
],
'secretKey' => Configure::read('Jwt.secret'),
'algorithm' => 'HS256',
'returnPayload' => false,
]);
$service->loadAuthenticator('Authentication.Form', [
'identifier' => [
'className' => 'Authentication.Password',
'fields' => ['username' => 'email', 'password' => 'password'],
'resolver' => $resolver,
],
'fields' => ['username' => 'email', 'password' => 'password'],
'loginUrl' => '/api/auth/login',
]);
return $service;
}
}
-260
View File
@@ -1,260 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Console;
if (!defined('STDIN')) {
define('STDIN', fopen('php://stdin', 'r'));
}
use Cake\Codeception\Console\Installer as CodeceptionInstaller;
use Cake\Utility\Security;
use Composer\IO\IOInterface;
use Composer\Script\Event;
use Exception;
/**
* Provides installation hooks for when this application is installed through
* composer. Customize this class to suit your needs.
*/
class Installer
{
/**
* An array of directories to be made writable
*
* @var list<string>
*/
public const WRITABLE_DIRS = [
'logs',
'tmp',
'tmp/cache',
'tmp/cache/models',
'tmp/cache/persistent',
'tmp/cache/views',
'tmp/sessions',
'tmp/tests',
];
/**
* Does some routine installation tasks so people don't have to.
*
* @param \Composer\Script\Event $event The composer event object.
* @throws \Exception Exception raised by validator.
* @return void
*/
public static function postInstall(Event $event): void
{
$io = $event->getIO();
$rootDir = dirname(__DIR__, 2);
static::createAppLocalConfig($rootDir, $io);
static::createWritableDirectories($rootDir, $io);
static::setFolderPermissions($rootDir, $io);
static::setSecuritySalt($rootDir, $io);
if (class_exists(CodeceptionInstaller::class)) {
CodeceptionInstaller::customizeCodeceptionBinary($event);
}
}
/**
* Create config/app_local.php file if it does not exist.
*
* @param string $dir The application's root directory.
* @param \Composer\IO\IOInterface $io IO interface to write to console.
* @return void
*/
public static function createAppLocalConfig(string $dir, IOInterface $io): void
{
$appLocalConfig = $dir . '/config/app_local.php';
$appLocalConfigTemplate = $dir . '/config/app_local.example.php';
if (!file_exists($appLocalConfig)) {
copy($appLocalConfigTemplate, $appLocalConfig);
$io->write('Created `config/app_local.php` file');
}
}
/**
* Create the `logs` and `tmp` directories.
*
* @param string $dir The application's root directory.
* @param \Composer\IO\IOInterface $io IO interface to write to console.
* @return void
*/
public static function createWritableDirectories(string $dir, IOInterface $io): void
{
foreach (static::WRITABLE_DIRS as $path) {
$path = $dir . '/' . $path;
if (!file_exists($path)) {
mkdir($path);
$io->write('Created `' . $path . '` directory');
}
}
}
/**
* Set globally writable permissions on the "tmp" and "logs" directory.
*
* This is not the most secure default, but it gets people up and running quickly.
*
* @param string $dir The application's root directory.
* @param \Composer\IO\IOInterface $io IO interface to write to console.
* @return void
*/
public static function setFolderPermissions(string $dir, IOInterface $io): void
{
// ask if the permissions should be changed
if ($io->isInteractive()) {
$validator = function (string $arg): string {
if (in_array($arg, ['Y', 'y', 'N', 'n'])) {
return $arg;
}
throw new Exception('This is not a valid answer. Please choose Y or n.');
};
$setFolderPermissions = $io->askAndValidate(
'<info>Set Folder Permissions ? (Default to Y)</info> [<comment>Y,n</comment>]? ',
$validator,
10,
'Y',
);
if (in_array($setFolderPermissions, ['n', 'N'])) {
return;
}
}
// Change the permissions on a path and output the results.
$changePerms = function (string $path) use ($io): void {
$currentPerms = fileperms($path) & 0777;
$worldWritable = $currentPerms | 0007;
if ($worldWritable == $currentPerms) {
return;
}
$res = chmod($path, $worldWritable);
if ($res) {
$io->write('Permissions set on ' . $path);
} else {
$io->write('Failed to set permissions on ' . $path);
}
};
$walker = function (string $dir) use (&$walker, $changePerms): void {
$files = array_diff(scandir($dir) ?: [], ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
if (!is_dir($path)) {
continue;
}
$changePerms($path);
$walker($path);
}
};
$walker($dir . '/tmp');
$changePerms($dir . '/tmp');
$changePerms($dir . '/logs');
}
/**
* Set the security.salt value in the application's config file.
*
* @param string $dir The application's root directory.
* @param \Composer\IO\IOInterface $io IO interface to write to console.
* @return void
*/
public static function setSecuritySalt(string $dir, IOInterface $io): void
{
$newKey = hash('sha256', Security::randomBytes(64));
static::setSecuritySaltInFile($dir, $io, $newKey, 'app_local.php');
}
/**
* Set the security.salt value in a given file
*
* @param string $dir The application's root directory.
* @param \Composer\IO\IOInterface $io IO interface to write to console.
* @param string $newKey key to set in the file
* @param string $file A path to a file relative to the application's root
* @return void
*/
public static function setSecuritySaltInFile(string $dir, IOInterface $io, string $newKey, string $file): void
{
$config = $dir . '/config/' . $file;
$content = file_get_contents($config);
if ($content === false) {
$io->write('Config file not readable or not found: config/' . $file);
return;
}
$content = str_replace('__SALT__', $newKey, $content, $count);
if ($count == 0) {
$io->write('No Security.salt placeholder to replace.');
return;
}
$result = file_put_contents($config, $content);
if ($result) {
$io->write('Updated Security.salt value in config/' . $file);
return;
}
$io->write('Unable to update Security.salt value.');
}
/**
* Set the APP_NAME value in a given file
*
* @param string $dir The application's root directory.
* @param \Composer\IO\IOInterface $io IO interface to write to console.
* @param string $appName app name to set in the file
* @param string $file A path to a file relative to the application's root
* @return void
*/
public static function setAppNameInFile(string $dir, IOInterface $io, string $appName, string $file): void
{
$config = $dir . '/config/' . $file;
$content = file_get_contents($config);
if ($content === false) {
$io->write('Config file not readable or not found: config/' . $file);
return;
}
$content = str_replace('__APP_NAME__', $appName, $content, $count);
if ($count == 0) {
$io->write('No __APP_NAME__ placeholder to replace.');
return;
}
$result = file_put_contents($config, $content);
if ($result) {
$io->write('Updated __APP_NAME__ value in config/' . $file);
return;
}
$io->write('Unable to update __APP_NAME__ value.');
}
}
-161
View File
@@ -1,161 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use App\Model\Entity\User;
use Cake\Controller\Controller;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\Paging\PaginatedInterface;
use Cake\Http\Exception\ForbiddenException;
use Cake\Http\Response;
/**
* Base controller for all REST API endpoints.
*
* Every endpoint requires a valid JWT unless the action is listed
* via `$this->Authentication->allowUnauthenticated()`.
*
* @property \Authentication\Controller\Component\AuthenticationComponent $Authentication
*/
abstract class ApiController extends Controller
{
/**
* @return void
*/
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Authentication.Authentication');
}
/**
* Currently authenticated user.
*
* @return \App\Model\Entity\User
*/
protected function currentUser(): User
{
/** @var \App\Model\Entity\User $user */
$user = $this->Authentication->getIdentity()->getOriginalData();
return $user;
}
/**
* Throws 403 unless the current user is an admin.
*
* @return void
* @throws \Cake\Http\Exception\ForbiddenException
*/
protected function requireAdmin(): void
{
if (!$this->currentUser()->isAdmin()) {
throw new ForbiddenException('This action requires the admin role.');
}
}
/**
* Builds a JSON response: {"success": true, "data": ..., ...$extra}.
*
* @param mixed $data Response payload
* @param int $status HTTP status code
* @param array<string, mixed> $extra Additional top-level keys
* @return \Cake\Http\Response
*/
protected function respond(mixed $data = null, int $status = 200, array $extra = []): Response
{
if ($status === 204) {
return $this->response->withStatus(204);
}
$body = ['success' => $status < 400, 'data' => $data] + $extra;
return $this->response
->withStatus($status)
->withType('application/json')
->withStringBody((string)json_encode(
$body,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
));
}
/**
* Responds with a paginated list and pagination metadata.
*
* @param \Cake\Datasource\Paging\PaginatedInterface $result Paginated result
* @return \Cake\Http\Response
*/
protected function respondPaginated(PaginatedInterface $result): Response
{
return $this->respond(iterator_to_array($result->items(), false), 200, [
'meta' => [
'page' => $result->currentPage(),
'limit' => $result->perPage(),
'total' => $result->totalCount(),
'pages' => $result->pageCount(),
'has_next_page' => $result->hasNextPage(),
'has_prev_page' => $result->hasPrevPage(),
'sort' => $this->stripAlias($result->pagingParam('sort')),
'direction' => $result->pagingParam('direction'),
],
])->withHeader('X-Total-Count', (string)$result->totalCount());
}
/**
* 422 response with entity validation errors.
*
* @param \Cake\Datasource\EntityInterface $entity Entity that failed validation
* @return \Cake\Http\Response
*/
protected function respondValidationError(EntityInterface $entity): Response
{
$body = [
'success' => false,
'error' => [
'code' => 422,
'message' => 'Validation failed.',
'fields' => $entity->getErrors(),
],
];
return $this->response
->withStatus(422)
->withType('application/json')
->withStringBody((string)json_encode($body, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
/**
* Pagination settings shared by list endpoints.
*
* @param list<string> $sortableFields Fields allowed in ?sort=
* @param array<string, string> $order Default order
* @return array<string, mixed>
*/
protected function paginationSettings(array $sortableFields, array $order): array
{
return [
'limit' => 20,
'maxLimit' => 100,
'sortableFields' => $sortableFields,
'order' => $order,
];
}
/**
* Turns "Customers.last_name" into "last_name".
*
* @param mixed $field Sort field
* @return string|null
*/
private function stripAlias(mixed $field): ?string
{
if (!is_string($field)) {
return null;
}
$pos = strrpos($field, '.');
return $pos === false ? $field : substr($field, $pos + 1);
}
}
-93
View File
@@ -1,93 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use Authentication\Authenticator\FormAuthenticator;
use Cake\Core\Configure;
use Cake\Event\EventInterface;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Http\Response;
use Cake\I18n\DateTime;
use Firebase\JWT\JWT;
/**
* Authentication endpoints: login (JWT issue) and own profile.
*/
class AuthController extends ApiController
{
/**
* @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event
* @return void
*/
public function beforeFilter(EventInterface $event): void
{
parent::beforeFilter($event);
$this->Authentication->allowUnauthenticated(['login']);
}
/**
* POST /api/auth/login - exchange email + password for a JWT.
*
* @return \Cake\Http\Response
*/
public function login(): Response
{
$this->request->allowMethod(['post']);
// A token is issued only for email/password credentials, never in exchange for another token.
$result = $this->Authentication->getResult();
$provider = $this->Authentication->getAuthenticationService()->getAuthenticationProvider();
if ($result === null || !$result->isValid() || !$provider instanceof FormAuthenticator) {
throw new UnauthorizedException('Invalid email or password.');
}
/** @var \App\Model\Entity\User $user */
$user = $result->getData();
$user->last_login = DateTime::now();
$this->fetchTable('Users')->saveOrFail($user);
$ttl = (int)Configure::read('Jwt.ttl', 3600);
$now = time();
$token = JWT::encode([
'iss' => 'testRestApi',
'sub' => $user->id,
'role' => $user->role,
'iat' => $now,
'exp' => $now + $ttl,
], (string)Configure::read('Jwt.secret'), 'HS256');
return $this->respond([
'token' => $token,
'token_type' => 'Bearer',
'expires_in' => $ttl,
'user' => $user,
]);
}
/**
* GET /api/auth/me - current user's profile.
* PUT|PATCH /api/auth/me - update own profile (name, email, password).
*
* @return \Cake\Http\Response
*/
public function me(): Response
{
$this->request->allowMethod(['get', 'put', 'patch']);
$usersTable = $this->fetchTable('Users');
$user = $usersTable->get($this->currentUser()->id);
if ($this->request->is('get')) {
return $this->respond($user);
}
$user = $usersTable->patchEntity($user, (array)$this->request->getData(), [
'fields' => ['email', 'password', 'first_name', 'last_name'],
]);
if (!$usersTable->save($user)) {
return $this->respondValidationError($user);
}
return $this->respond($user);
}
}
-144
View File
@@ -1,144 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use Cake\Http\Response;
/**
* Cars CRUD - available to every authenticated user.
*
* Also serves the nested routes GET/POST /api/customers/{customer_id}/cars.
*
* @property \App\Model\Table\CarsTable $Cars
*/
class CarsController extends ApiController
{
/**
* GET /api/cars, GET /api/customers/{customer_id}/cars
*
* Query: page, limit, sort, direction, q (make/model/VIN/registration),
* customer_id, make, fuel_type, transmission, year_from, year_to, with_customer (1 = embed customer)
*
* @return \Cake\Http\Response
*/
public function index(): Response
{
$query = $this->Cars->find();
$customerId = $this->request->getParam('customer_id') ?? $this->request->getQuery('customer_id');
if ($customerId !== null && $customerId !== '') {
// 404 for a non-existing customer in the nested route
if ($this->request->getParam('customer_id') !== null) {
$this->Cars->Customers->get($customerId);
}
$query->where(['Cars.customer_id' => (int)$customerId]);
}
$q = trim((string)$this->request->getQuery('q', ''));
if ($q !== '') {
$like = '%' . $q . '%';
$query->where(['OR' => [
'Cars.make LIKE' => $like,
'Cars.model LIKE' => $like,
'Cars.vin LIKE' => $like,
'Cars.registration_number LIKE' => $like,
]]);
}
foreach (['make', 'fuel_type', 'transmission'] as $field) {
$value = $this->request->getQuery($field);
if ($value !== null && $value !== '') {
$query->where(["Cars.{$field}" => $value]);
}
}
$yearFrom = $this->request->getQuery('year_from');
if (is_numeric($yearFrom)) {
$query->where(['Cars.production_year >=' => (int)$yearFrom]);
}
$yearTo = $this->request->getQuery('year_to');
if (is_numeric($yearTo)) {
$query->where(['Cars.production_year <=' => (int)$yearTo]);
}
if (filter_var($this->request->getQuery('with_customer'), FILTER_VALIDATE_BOOLEAN)) {
$query->contain(['Customers']);
}
$result = $this->paginate($query, $this->paginationSettings(
[
'id', 'customer_id', 'make', 'model', 'production_year', 'registration_number',
'fuel_type', 'mileage', 'power_hp', 'created', 'modified',
],
['Cars.id' => 'asc'],
));
return $this->respondPaginated($result);
}
/**
* GET /api/cars/{id} - car with its owner.
*
* @param string $id Car id
* @return \Cake\Http\Response
*/
public function view(string $id): Response
{
return $this->respond($this->Cars->get($id, contain: ['Customers']));
}
/**
* POST /api/cars, POST /api/customers/{customer_id}/cars
*
* @return \Cake\Http\Response
*/
public function add(): Response
{
$this->request->allowMethod(['post']);
$data = (array)$this->request->getData();
$customerId = $this->request->getParam('customer_id');
if ($customerId !== null) {
$this->Cars->Customers->get($customerId);
$data['customer_id'] = (int)$customerId;
}
$car = $this->Cars->newEntity($data);
if (!$this->Cars->save($car)) {
return $this->respondValidationError($car);
}
return $this->respond($car, 201)
->withLocation('/api/cars/' . $car->id);
}
/**
* PUT|PATCH /api/cars/{id} - customer_id may be changed to transfer the car.
*
* @param string $id Car id
* @return \Cake\Http\Response
*/
public function edit(string $id): Response
{
$this->request->allowMethod(['put', 'patch']);
$car = $this->Cars->get($id);
$car = $this->Cars->patchEntity($car, (array)$this->request->getData());
if (!$this->Cars->save($car)) {
return $this->respondValidationError($car);
}
return $this->respond($car);
}
/**
* DELETE /api/cars/{id}
*
* @param string $id Car id
* @return \Cake\Http\Response
*/
public function delete(string $id): Response
{
$this->request->allowMethod(['delete']);
$this->Cars->deleteOrFail($this->Cars->get($id));
return $this->respond(null, 204);
}
}
-118
View File
@@ -1,118 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use Cake\Http\Response;
/**
* Customers CRUD - available to every authenticated user.
*
* @property \App\Model\Table\CustomersTable $Customers
*/
class CustomersController extends ApiController
{
/**
* GET /api/customers
*
* Query: page, limit, sort, direction, q (name/email/phone/company/city/tax id),
* type, city, country, with_cars (1 = embed cars)
*
* @return \Cake\Http\Response
*/
public function index(): Response
{
$query = $this->Customers->find();
$q = trim((string)$this->request->getQuery('q', ''));
if ($q !== '') {
$like = '%' . $q . '%';
$query->where(['OR' => [
'Customers.first_name LIKE' => $like,
'Customers.last_name LIKE' => $like,
'Customers.email LIKE' => $like,
'Customers.phone LIKE' => $like,
'Customers.company_name LIKE' => $like,
'Customers.city LIKE' => $like,
'Customers.tax_id LIKE' => $like,
]]);
}
foreach (['type', 'city', 'country'] as $field) {
$value = $this->request->getQuery($field);
if ($value !== null && $value !== '') {
$query->where(["Customers.{$field}" => $value]);
}
}
if (filter_var($this->request->getQuery('with_cars'), FILTER_VALIDATE_BOOLEAN)) {
$query->contain(['Cars']);
}
$result = $this->paginate($query, $this->paginationSettings(
['id', 'type', 'first_name', 'last_name', 'email', 'company_name', 'city', 'country', 'created', 'modified'],
['Customers.id' => 'asc'],
));
return $this->respondPaginated($result);
}
/**
* GET /api/customers/{id} - customer with cars.
*
* @param string $id Customer id
* @return \Cake\Http\Response
*/
public function view(string $id): Response
{
return $this->respond($this->Customers->get($id, contain: ['Cars']));
}
/**
* POST /api/customers
*
* @return \Cake\Http\Response
*/
public function add(): Response
{
$this->request->allowMethod(['post']);
$customer = $this->Customers->newEntity((array)$this->request->getData());
$customer->created_by = $this->currentUser()->id;
if (!$this->Customers->save($customer)) {
return $this->respondValidationError($customer);
}
return $this->respond($customer, 201)
->withLocation('/api/customers/' . $customer->id);
}
/**
* PUT|PATCH /api/customers/{id}
*
* @param string $id Customer id
* @return \Cake\Http\Response
*/
public function edit(string $id): Response
{
$this->request->allowMethod(['put', 'patch']);
$customer = $this->Customers->get($id);
$customer = $this->Customers->patchEntity($customer, (array)$this->request->getData());
if (!$this->Customers->save($customer)) {
return $this->respondValidationError($customer);
}
return $this->respond($customer);
}
/**
* DELETE /api/customers/{id} - also deletes the customer's cars.
*
* @param string $id Customer id
* @return \Cake\Http\Response
*/
public function delete(string $id): Response
{
$this->request->allowMethod(['delete']);
$this->Customers->deleteOrFail($this->Customers->get($id));
return $this->respond(null, 204);
}
}
-138
View File
@@ -1,138 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use Cake\Event\EventInterface;
use Cake\Http\Exception\BadRequestException;
use Cake\Http\Response;
/**
* Users CRUD - available to admins only.
*
* @property \App\Model\Table\UsersTable $Users
*/
class UsersController extends ApiController
{
/**
* @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event
* @return void
*/
public function beforeFilter(EventInterface $event): void
{
parent::beforeFilter($event);
$this->requireAdmin();
}
/**
* GET /api/users
*
* Query: page, limit, sort, direction, q (email/name), role, active
*
* @return \Cake\Http\Response
*/
public function index(): Response
{
$query = $this->Users->find();
$q = trim((string)$this->request->getQuery('q', ''));
if ($q !== '') {
$like = '%' . $q . '%';
$query->where(['OR' => [
'Users.email LIKE' => $like,
'Users.first_name LIKE' => $like,
'Users.last_name LIKE' => $like,
]]);
}
$role = $this->request->getQuery('role');
if ($role) {
$query->where(['Users.role' => $role]);
}
$active = $this->request->getQuery('active');
if ($active !== null && $active !== '') {
$query->where(['Users.active' => filter_var($active, FILTER_VALIDATE_BOOLEAN)]);
}
$result = $this->paginate($query, $this->paginationSettings(
['id', 'email', 'first_name', 'last_name', 'role', 'active', 'last_login', 'created', 'modified'],
['Users.id' => 'asc'],
));
return $this->respondPaginated($result);
}
/**
* GET /api/users/{id}
*
* @param string $id User id
* @return \Cake\Http\Response
*/
public function view(string $id): Response
{
return $this->respond($this->Users->get($id));
}
/**
* POST /api/users
*
* @return \Cake\Http\Response
*/
public function add(): Response
{
$this->request->allowMethod(['post']);
$user = $this->Users->newEntity((array)$this->request->getData());
if (!$this->Users->save($user)) {
return $this->respondValidationError($user);
}
return $this->respond($user, 201)
->withLocation('/api/users/' . $user->id);
}
/**
* PUT|PATCH /api/users/{id}
*
* @param string $id User id
* @return \Cake\Http\Response
*/
public function edit(string $id): Response
{
$this->request->allowMethod(['put', 'patch']);
$user = $this->Users->get($id);
$data = (array)$this->request->getData();
if ($user->id === $this->currentUser()->id) {
if (isset($data['role']) && $data['role'] !== $user->role) {
throw new BadRequestException('You cannot change your own role.');
}
if (isset($data['active']) && !filter_var($data['active'], FILTER_VALIDATE_BOOLEAN)) {
throw new BadRequestException('You cannot deactivate your own account.');
}
}
$user = $this->Users->patchEntity($user, $data);
if (!$this->Users->save($user)) {
return $this->respondValidationError($user);
}
return $this->respond($user);
}
/**
* DELETE /api/users/{id}
*
* @param string $id User id
* @return \Cake\Http\Response
*/
public function delete(string $id): Response
{
$this->request->allowMethod(['delete']);
$user = $this->Users->get($id);
if ($user->id === $this->currentUser()->id) {
throw new BadRequestException('You cannot delete your own account.');
}
$this->Users->deleteOrFail($user);
return $this->respond(null, 204);
}
}
-52
View File
@@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.2.9
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Controller;
use Cake\Controller\Controller;
/**
* Application Controller
*
* Add your application-wide methods in the class below, your controllers
* will inherit them.
*
* @link https://book.cakephp.org/5/en/controllers.html#the-app-controller
*/
class AppController extends Controller
{
/**
* Initialization hook method.
*
* Use this method to add common initialization code like loading components.
*
* e.g. `$this->loadComponent('FormProtection');`
*
* @return void
*/
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Flash');
/*
* Enable the following component for recommended CakePHP form protection settings.
* see https://book.cakephp.org/5/en/controllers/components/form-protection.html
*/
//$this->loadComponent('FormProtection');
}
}
View File
-18
View File
@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller;
/**
* Serves the Swagger UI page for the REST API.
*/
class DocsController extends AppController
{
/**
* @return void
*/
public function index(): void
{
$this->viewBuilder()->disableAutoLayout();
}
}
-70
View File
@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.3.4
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Controller;
use Cake\Event\EventInterface;
/**
* Error Handling Controller
*
* Controller used by ExceptionRenderer to render error responses.
*/
class ErrorController extends AppController
{
/**
* Initialization hook method.
*
* @return void
*/
public function initialize(): void
{
// Only add parent::initialize() if you are confident your `AppController` is safe.
}
/**
* beforeFilter callback.
*
* @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event.
* @return void
*/
public function beforeFilter(EventInterface $event): void
{
}
/**
* beforeRender callback.
*
* @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event.
* @return void
*/
public function beforeRender(EventInterface $event): void
{
parent::beforeRender($event);
$this->viewBuilder()->setTemplatePath('Error');
}
/**
* afterFilter callback.
*
* @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event.
* @return void
*/
public function afterFilter(EventInterface $event): void
{
}
}
-73
View File
@@ -1,73 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.2.9
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Controller;
use Cake\Core\Configure;
use Cake\Http\Exception\ForbiddenException;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
use Cake\View\Exception\MissingTemplateException;
/**
* Static content controller
*
* This controller will render views from templates/Pages/
*
* @link https://book.cakephp.org/5/en/controllers/pages-controller.html
*/
class PagesController extends AppController
{
/**
* Displays a view
*
* @param string ...$path Path segments.
* @return \Cake\Http\Response|null
* @throws \Cake\Http\Exception\ForbiddenException When a directory traversal attempt.
* @throws \Cake\View\Exception\MissingTemplateException When the view file could not
* be found and in debug mode.
* @throws \Cake\Http\Exception\NotFoundException When the view file could not
* be found and not in debug mode.
* @throws \Cake\View\Exception\MissingTemplateException In debug mode.
*/
public function display(string ...$path): ?Response
{
if (!$path) {
return $this->redirect('/');
}
if (in_array('..', $path, true) || in_array('.', $path, true)) {
throw new ForbiddenException();
}
$page = $subpage = null;
if (!empty($path[0])) {
$page = $path[0];
}
if (!empty($path[1])) {
$subpage = $path[1];
}
$this->set(compact('page', 'subpage'));
try {
return $this->render(implode('/', $path));
} catch (MissingTemplateException $exception) {
if (Configure::read('debug')) {
throw $exception;
}
throw new NotFoundException();
}
}
}
-58
View File
@@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Error;
use Cake\Core\Configure;
use Cake\Error\Renderer\WebExceptionRenderer;
use Cake\Http\Exception\HttpException;
use Cake\Http\Response;
use Psr\Http\Message\ResponseInterface;
/**
* Renders exceptions thrown under /api as JSON, everything else as usual.
*/
class ApiExceptionRenderer extends WebExceptionRenderer
{
/**
* @return \Psr\Http\Message\ResponseInterface
*/
public function render(): ResponseInterface
{
$request = $this->request;
if ($request === null || !str_starts_with($request->getUri()->getPath(), '/api')) {
return parent::render();
}
$exception = $this->error;
$code = $this->getHttpCode($exception);
$body = [
'success' => false,
'error' => [
'code' => $code,
'message' => $this->_message($exception, $code),
],
];
if (Configure::read('debug') && $code >= 500) {
$body['error']['exception'] = $exception::class;
$body['error']['file'] = $exception->getFile();
$body['error']['line'] = $exception->getLine();
}
$response = (new Response())
->withStatus($code)
->withType('application/json')
->withStringBody((string)json_encode(
$body,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
));
if ($exception instanceof HttpException) {
foreach ($exception->getHeaders() as $name => $value) {
$response = $response->withHeader($name, $value);
}
}
return $response;
}
}
-48
View File
@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use Cake\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
/**
* Adds permissive CORS headers to /api responses and answers preflight requests.
*/
class CorsMiddleware implements MiddlewareInterface
{
/**
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
* @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler.
* @return \Psr\Http\Message\ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!str_starts_with($request->getUri()->getPath(), '/api')) {
return $handler->handle($request);
}
if ($request->getMethod() === 'OPTIONS') {
return $this->addHeaders(new Response(['status' => 204]));
}
return $this->addHeaders($handler->handle($request));
}
/**
* @param \Psr\Http\Message\ResponseInterface $response Response
* @return \Psr\Http\Message\ResponseInterface
*/
protected function addHeaders(ResponseInterface $response): ResponseInterface
{
return $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
->withHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, Accept')
->withHeader('Access-Control-Expose-Headers', 'X-Total-Count')
->withHeader('Access-Control-Max-Age', '86400');
}
}
-58
View File
@@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use Cake\Core\Configure;
use Cake\Http\Exception\BadRequestException;
use Cake\Http\Exception\InternalErrorException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
/**
* Middleware to validate Host header and prevent Host Header Injection attacks.
*
* In production, this middleware ensures that App.fullBaseUrl is configured
* and validates incoming Host headers against it. This prevents attackers
* from manipulating password reset links and other security-critical URLs.
*
* @see https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection
*/
class HostHeaderMiddleware implements MiddlewareInterface
{
/**
* Process the request and validate the Host header.
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
* @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler.
* @return \Psr\Http\Message\ResponseInterface A response.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (Configure::read('debug')) {
return $handler->handle($request);
}
$fullBaseUrl = Configure::read('App.fullBaseUrl');
if (!$fullBaseUrl) {
throw new InternalErrorException(
'SECURITY: App.fullBaseUrl is not configured. ' .
'This is required in production to prevent Host Header Injection attacks. ' .
'Set APP_FULL_BASE_URL environment variable or configure App.fullBaseUrl in config/app.php',
);
}
$configuredHost = parse_url($fullBaseUrl, PHP_URL_HOST);
$requestHost = $request->getUri()->getHost();
if ($configuredHost && $requestHost && strtolower($configuredHost) !== strtolower($requestHost)) {
throw new BadRequestException(
'Invalid Host header. Request host does not match configured application host.',
);
}
return $handler->handle($request);
}
}
View File
View File
-74
View File
@@ -1,74 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
/**
* Car Entity
*
* @property int $id
* @property int $customer_id
* @property string $make
* @property string $model
* @property int $production_year
* @property string $vin
* @property string $registration_number
* @property string $fuel_type
* @property string|null $color
* @property int|null $engine_capacity
* @property int|null $power_hp
* @property string|null $transmission
* @property int|null $mileage
* @property string|null $notes
* @property \Cake\I18n\DateTime $created
* @property \Cake\I18n\DateTime $modified
* @property \App\Model\Entity\Customer $customer
*/
class Car extends Entity
{
public const FUEL_TYPES = ['petrol', 'diesel', 'lpg', 'hybrid', 'electric'];
public const TRANSMISSIONS = ['manual', 'automatic'];
/**
* @var array<string, bool>
*/
protected array $_accessible = [
'customer_id' => true,
'make' => true,
'model' => true,
'production_year' => true,
'vin' => true,
'registration_number' => true,
'fuel_type' => true,
'color' => true,
'engine_capacity' => true,
'power_hp' => true,
'transmission' => true,
'mileage' => true,
'notes' => true,
];
/**
* Normalize VIN to upper case.
*
* @param string $vin VIN
* @return string
*/
protected function _setVin(string $vin): string
{
return strtoupper(trim($vin));
}
/**
* Normalize registration number (upper case, no spaces).
*
* @param string $number Registration number
* @return string
*/
protected function _setRegistrationNumber(string $number): string
{
return strtoupper(str_replace(' ', '', $number));
}
}
-54
View File
@@ -1,54 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
/**
* Customer Entity
*
* @property int $id
* @property string $type
* @property string $first_name
* @property string $last_name
* @property string $email
* @property string $phone
* @property string|null $company_name
* @property string|null $tax_id
* @property string|null $street
* @property string|null $city
* @property string|null $postal_code
* @property string $country
* @property \Cake\I18n\Date|null $birth_date
* @property string|null $notes
* @property int|null $created_by
* @property \Cake\I18n\DateTime $created
* @property \Cake\I18n\DateTime $modified
* @property array<\App\Model\Entity\Car> $cars
*/
class Customer extends Entity
{
public const TYPE_INDIVIDUAL = 'individual';
public const TYPE_COMPANY = 'company';
public const TYPES = [self::TYPE_INDIVIDUAL, self::TYPE_COMPANY];
/**
* @var array<string, bool>
*/
protected array $_accessible = [
'type' => true,
'first_name' => true,
'last_name' => true,
'email' => true,
'phone' => true,
'company_name' => true,
'tax_id' => true,
'street' => true,
'city' => true,
'postal_code' => true,
'country' => true,
'birth_date' => true,
'notes' => true,
];
}
-70
View File
@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Authentication\PasswordHasher\DefaultPasswordHasher;
use Cake\ORM\Entity;
/**
* User Entity
*
* @property int $id
* @property string $email
* @property string $password
* @property string $first_name
* @property string $last_name
* @property string $role
* @property bool $active
* @property \Cake\I18n\DateTime|null $last_login
* @property \Cake\I18n\DateTime $created
* @property \Cake\I18n\DateTime $modified
*/
class User extends Entity
{
public const ROLE_ADMIN = 'admin';
public const ROLE_USER = 'user';
public const ROLES = [self::ROLE_ADMIN, self::ROLE_USER];
/**
* @var array<string, bool>
*/
protected array $_accessible = [
'email' => true,
'password' => true,
'first_name' => true,
'last_name' => true,
'role' => true,
'active' => true,
];
/**
* @var list<string>
*/
protected array $_hidden = [
'password',
];
/**
* Hash the password on assignment.
*
* @param string $password Plain password
* @return string|null
*/
protected function _setPassword(string $password): ?string
{
if ($password === '') {
return null;
}
return (new DefaultPasswordHasher())->hash($password);
}
/**
* @return bool
*/
public function isAdmin(): bool
{
return $this->role === self::ROLE_ADMIN;
}
}
View File
-139
View File
@@ -1,139 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Model\Table;
use App\Model\Entity\Car;
use Cake\I18n\Date;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Cars Model
*
* @property \App\Model\Table\CustomersTable&\Cake\ORM\Association\BelongsTo $Customers
* @method \App\Model\Entity\Car newEmptyEntity()
* @method \App\Model\Entity\Car get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args)
* @method \App\Model\Entity\Car patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
*/
class CarsTable extends Table
{
/**
* @param array<string, mixed> $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('cars');
$this->setDisplayField('registration_number');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Customers', [
'foreignKey' => 'customer_id',
'joinType' => 'INNER',
]);
}
/**
* @param \Cake\Validation\Validator $validator Validator instance.
* @return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$maxYear = (int)Date::now()->format('Y') + 1;
$validator
->integer('customer_id')
->requirePresence('customer_id', 'create')
->notEmptyString('customer_id');
$validator
->scalar('make')
->maxLength('make', 50)
->requirePresence('make', 'create')
->notEmptyString('make');
$validator
->scalar('model')
->maxLength('model', 50)
->requirePresence('model', 'create')
->notEmptyString('model');
$validator
->integer('production_year')
->range('production_year', [1900, $maxYear], "Production year must be between 1900 and {$maxYear}.")
->requirePresence('production_year', 'create')
->notEmptyString('production_year');
$validator
->scalar('vin')
->regex('vin', '/^[A-HJ-NPR-Z0-9]{17}$/i', 'VIN must have 17 characters (letters I, O, Q are not allowed).')
->requirePresence('vin', 'create')
->notEmptyString('vin');
$validator
->scalar('registration_number')
->regex('registration_number', '/^[A-Z0-9 ]{4,10}$/i', 'Registration number must have 4-10 letters/digits.')
->requirePresence('registration_number', 'create')
->notEmptyString('registration_number');
$validator
->inList('fuel_type', Car::FUEL_TYPES, 'Fuel type must be one of: ' . implode(', ', Car::FUEL_TYPES))
->requirePresence('fuel_type', 'create')
->notEmptyString('fuel_type');
$validator
->scalar('color')
->maxLength('color', 30)
->allowEmptyString('color');
$validator
->integer('engine_capacity')
->range('engine_capacity', [0, 10000])
->allowEmptyString('engine_capacity');
$validator
->integer('power_hp')
->range('power_hp', [1, 2000])
->allowEmptyString('power_hp');
$validator
->inList('transmission', Car::TRANSMISSIONS, 'Transmission must be one of: ' . implode(', ', Car::TRANSMISSIONS))
->allowEmptyString('transmission');
$validator
->integer('mileage')
->nonNegativeInteger('mileage')
->allowEmptyString('mileage');
$validator
->scalar('notes')
->maxLength('notes', 5000)
->allowEmptyString('notes');
return $validator;
}
/**
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* @return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['vin'], 'This VIN is already registered.'), ['errorField' => 'vin']);
$rules->add(
$rules->isUnique(['registration_number'], 'This registration number is already registered.'),
['errorField' => 'registration_number'],
);
$rules->add($rules->existsIn(['customer_id'], 'Customers', 'Customer does not exist.'), [
'errorField' => 'customer_id',
]);
return $rules;
}
}
-139
View File
@@ -1,139 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Model\Table;
use App\Model\Entity\Customer;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Customers Model
*
* @property \App\Model\Table\CarsTable&\Cake\ORM\Association\HasMany $Cars
* @method \App\Model\Entity\Customer newEmptyEntity()
* @method \App\Model\Entity\Customer get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args)
* @method \App\Model\Entity\Customer patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
*/
class CustomersTable extends Table
{
/**
* @param array<string, mixed> $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('customers');
$this->setDisplayField('email');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Cars', [
'foreignKey' => 'customer_id',
'dependent' => true,
'cascadeCallbacks' => true,
]);
$this->belongsTo('Creators', [
'className' => 'Users',
'foreignKey' => 'created_by',
]);
}
/**
* @param \Cake\Validation\Validator $validator Validator instance.
* @return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->inList('type', Customer::TYPES, 'Type must be one of: ' . implode(', ', Customer::TYPES))
->notEmptyString('type');
$validator
->scalar('first_name')
->maxLength('first_name', 100)
->requirePresence('first_name', 'create')
->notEmptyString('first_name');
$validator
->scalar('last_name')
->maxLength('last_name', 100)
->requirePresence('last_name', 'create')
->notEmptyString('last_name');
$validator
->email('email')
->maxLength('email', 255)
->requirePresence('email', 'create')
->notEmptyString('email');
$validator
->scalar('phone')
->maxLength('phone', 30)
->regex('phone', '/^\+?[0-9 ()-]{6,30}$/', 'Invalid phone number.')
->requirePresence('phone', 'create')
->notEmptyString('phone');
$validator
->scalar('company_name')
->maxLength('company_name', 255)
->requirePresence('company_name', function ($context) {
return $context['newRecord'] && ($context['data']['type'] ?? null) === Customer::TYPE_COMPANY;
}, 'Company name is required for company customers.')
->allowEmptyString('company_name')
->notEmptyString('company_name', 'Company name is required for company customers.', function ($context) {
return ($context['data']['type'] ?? null) === Customer::TYPE_COMPANY;
});
$validator
->scalar('tax_id')
->regex('tax_id', '/^[0-9]{10}$/', 'Tax ID (NIP) must consist of 10 digits.')
->allowEmptyString('tax_id');
$validator
->scalar('street')
->maxLength('street', 255)
->allowEmptyString('street');
$validator
->scalar('city')
->maxLength('city', 100)
->allowEmptyString('city');
$validator
->scalar('postal_code')
->regex('postal_code', '/^[0-9]{2}-[0-9]{3}$/', 'Postal code must be in format 00-000.')
->allowEmptyString('postal_code');
$validator
->scalar('country')
->regex('country', '/^[A-Z]{2}$/', 'Country must be an ISO 3166-1 alpha-2 code, e.g. PL.')
->notEmptyString('country');
$validator
->date('birth_date', ['ymd'])
->allowEmptyDate('birth_date');
$validator
->scalar('notes')
->maxLength('notes', 5000)
->allowEmptyString('notes');
return $validator;
}
/**
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* @return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['email'], 'This email is already in use.'), ['errorField' => 'email']);
return $rules;
}
}
-98
View File
@@ -1,98 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Model\Table;
use App\Model\Entity\User;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Users Model
*
* @method \App\Model\Entity\User newEmptyEntity()
* @method \App\Model\Entity\User get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args)
* @method \App\Model\Entity\User patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
*/
class UsersTable extends Table
{
/**
* @param array<string, mixed> $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('email');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
}
/**
* @param \Cake\Validation\Validator $validator Validator instance.
* @return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->email('email')
->maxLength('email', 255)
->requirePresence('email', 'create')
->notEmptyString('email');
$validator
->scalar('password')
->minLength('password', 8, 'Password must be at least 8 characters long.')
->maxLength('password', 72)
->requirePresence('password', 'create')
->notEmptyString('password');
$validator
->scalar('first_name')
->maxLength('first_name', 100)
->requirePresence('first_name', 'create')
->notEmptyString('first_name');
$validator
->scalar('last_name')
->maxLength('last_name', 100)
->requirePresence('last_name', 'create')
->notEmptyString('last_name');
$validator
->inList('role', User::ROLES, 'Role must be one of: ' . implode(', ', User::ROLES))
->notEmptyString('role');
$validator
->boolean('active');
return $validator;
}
/**
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* @return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['email'], 'This email is already in use.'), ['errorField' => 'email']);
return $rules;
}
/**
* Finder used by authentication - only active accounts can log in.
*
* @param \Cake\ORM\Query\SelectQuery $query Query
* @return \Cake\ORM\Query\SelectQuery
*/
public function findActive(SelectQuery $query): SelectQuery
{
return $query->where([$this->aliasField('active') => true]);
}
}
-46
View File
@@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.4
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\View;
/**
* A view class that is used for AJAX responses.
* Currently only switches the default layout and sets the response type -
* which just maps to text/html by default.
*/
class AjaxView extends AppView
{
/**
* The name of the layout file to render the view inside of. The name
* specified is the filename of the layout in /templates/Layout without
* the .php extension.
*
* @var string
*/
protected string $layout = 'ajax';
/**
* Initialization hook method.
*
* @return void
*/
public function initialize(): void
{
parent::initialize();
$this->response = $this->response->withType('ajax');
}
}
-41
View File
@@ -1,41 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\View;
use Cake\View\View;
/**
* Application View
*
* Your application's default view class
*
* @link https://book.cakephp.org/5/en/views.html#the-app-view
*/
class AppView extends View
{
/**
* Initialization hook method.
*
* Use this method to add common initialization code like adding helpers.
*
* e.g. `$this->addHelper('Html');`
*
* @return void
*/
public function initialize(): void
{
}
}
View File
View File
-30
View File
@@ -1,30 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
*/
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Test REST API - docs</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.17.14/swagger-ui.min.css">
<style>body { margin: 0; }</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.17.14/swagger-ui-bundle.min.js"></script>
<script>
window.ui = SwaggerUIBundle({
url: '<?= $this->Url->build('/openapi.yaml') ?>',
dom_id: '#swagger-ui',
deepLinking: true,
persistAuthorization: true,
displayRequestDuration: true,
tryItOutEnabled: true,
filter: true,
});
</script>
</body>
</html>
-26
View File
@@ -1,26 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
* @var string $message
* @var string $url
*/
use Cake\Core\Configure;
$this->setLayout('error');
if (Configure::read('debug')) :
$this->setLayout('dev_error');
$this->assign('title', $message);
$this->assign('templateName', 'error400.php');
$this->start('file');
echo $this->element('auto_table_warning');
$this->end();
endif;
?>
<h2><?= h($message) ?></h2>
<p class="error">
<strong><?= __d('cake', 'Error') ?>: </strong>
<?= __d('cake', 'The requested address {0} was not found on this server.', "<strong>'{$url}'</strong>") ?>
</p>
-36
View File
@@ -1,36 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
* @var string $message
* @var string $url
*/
use Cake\Core\Configure;
use Cake\Error\Debugger;
$this->setLayout('error');
if (Configure::read('debug')) :
$this->setLayout('dev_error');
$this->assign('title', $message);
$this->assign('templateName', 'error500.php');
$this->start('file');
?>
<?php if ($error instanceof Error) : ?>
<?php $file = $error->getFile() ?>
<?php $line = $error->getLine() ?>
<strong>Error in: </strong>
<?= $this->Html->link(sprintf('%s, line %s', Debugger::trimPath($file), $line), Debugger::editorUrl($file, $line)); ?>
<?php endif; ?>
<?php
echo $this->element('auto_table_warning');
$this->end();
endif;
?>
<h2><?= __d('cake', 'An Internal Error Has Occurred.') ?></h2>
<p class="error">
<strong><?= __d('cake', 'Error') ?>: </strong>
<?= h($message) ?>
</p>
-248
View File
@@ -1,248 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \App\View\AppView $this
*/
use Cake\Cache\Cache;
use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Datasource\ConnectionManager;
use Cake\Error\Debugger;
use Cake\Http\Exception\NotFoundException;
$this->disableAutoLayout();
$checkConnection = function (string $name) {
$error = null;
$connected = false;
try {
ConnectionManager::get($name)->getDriver()->connect();
// No exception means success
$connected = true;
} catch (Exception $connectionError) {
$error = $connectionError->getMessage();
if (method_exists($connectionError, 'getAttributes')) {
$attributes = $connectionError->getAttributes();
if (isset($attributes['message'])) {
$error .= '<br />' . $attributes['message'];
}
}
if ($name === 'debug_kit') {
$error = 'Try adding your current <b>top level domain</b> to the
<a href="https://book.cakephp.org/debugkit/5/configuration.html" target="_blank">DebugKit.safeTld</a>
config and reload.';
if (!in_array('sqlite', \PDO::getAvailableDrivers())) {
$error .= '<br />You need to install the PHP extension <code>pdo_sqlite</code> so DebugKit can work properly.';
}
}
}
return compact('connected', 'error');
};
if (!Configure::read('debug')) :
throw new NotFoundException(
'Please replace templates/Pages/home.php with your own version or re-enable debug mode.'
);
endif;
?>
<!DOCTYPE html>
<html>
<head>
<?= $this->Html->charset() ?>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
CakePHP: the rapid development PHP framework:
<?= $this->fetch('title') ?>
</title>
<?= $this->Html->meta('icon') ?>
<?= $this->Html->css(['normalize.min', 'milligram.min', 'fonts', 'cake', 'home']) ?>
<?= $this->fetch('meta') ?>
<?= $this->fetch('css') ?>
<?= $this->fetch('script') ?>
</head>
<body>
<header>
<div class="container text-center">
<a href="https://cakephp.org/" target="_blank" rel="noopener">
<img alt="CakePHP" src="https://cakephp.org/v2/img/logos/CakePHP_Logo.svg" width="350" />
</a>
<h1>
Welcome to CakePHP <?= h(Configure::version()) ?> Chiffon (🍰)
</h1>
</div>
</header>
<main class="main">
<div class="container">
<div class="content">
<div class="row">
<div class="column">
<div class="message default text-center">
<small>Please be aware that this page will not be shown if you turn off debug mode unless you replace templates/Pages/home.php with your own version.</small>
</div>
<div id="url-rewriting-warning" style="padding: 1rem; background: #fcebea; color: #cc1f1a; border-color: #ef5753;">
<ul>
<li class="bullet problem">
URL rewriting is not properly configured on your server.<br />
1) <a target="_blank" rel="noopener" href="https://book.cakephp.org/5/en/installation.html#url-rewriting">Help me configure it</a><br />
2) <a target="_blank" rel="noopener" href="https://book.cakephp.org/5/en/development/configuration.html#general-configuration">I don't / can't use URL rewriting</a>
</li>
</ul>
</div>
<?php Debugger::checkSecurityKeys(); ?>
</div>
</div>
<div class="row">
<div class="column">
<h4>Environment</h4>
<ul>
<?php if (version_compare(PHP_VERSION, '8.1.0', '>=')) : ?>
<li class="bullet success">Your version of PHP is 8.1.0 or higher (detected <?= PHP_VERSION ?>).</li>
<?php else : ?>
<li class="bullet problem">Your version of PHP is too low. You need PHP 8.1.0 or higher to use CakePHP (detected <?= PHP_VERSION ?>).</li>
<?php endif; ?>
<?php if (extension_loaded('mbstring')) : ?>
<li class="bullet success">Your version of PHP has the mbstring extension loaded.</li>
<?php else : ?>
<li class="bullet problem">Your version of PHP does NOT have the mbstring extension loaded.</li>
<?php endif; ?>
<?php if (extension_loaded('openssl')) : ?>
<li class="bullet success">Your version of PHP has the openssl extension loaded.</li>
<?php else : ?>
<li class="bullet problem">Your version of PHP does NOT have the openssl extension loaded.</li>
<?php endif; ?>
<?php if (extension_loaded('intl')) : ?>
<li class="bullet success">Your version of PHP has the intl extension loaded.</li>
<?php else : ?>
<li class="bullet problem">Your version of PHP does NOT have the intl extension loaded.</li>
<?php endif; ?>
<?php if (ini_get('zend.assertions') !== '1') : ?>
<li class="bullet problem">You should set <code>zend.assertions</code> to <code>1</code> in your <code>php.ini</code> for your development environment.</li>
<?php endif; ?>
</ul>
</div>
<div class="column">
<h4>Filesystem</h4>
<ul>
<?php if (is_writable(TMP)) : ?>
<li class="bullet success">Your tmp directory is writable.</li>
<?php else : ?>
<li class="bullet problem">Your tmp directory is NOT writable.</li>
<?php endif; ?>
<?php if (is_writable(LOGS)) : ?>
<li class="bullet success">Your logs directory is writable.</li>
<?php else : ?>
<li class="bullet problem">Your logs directory is NOT writable.</li>
<?php endif; ?>
<?php $settings = Cache::getConfig('_cake_translations_'); ?>
<?php if (!empty($settings)) : ?>
<li class="bullet success">The <em><?= h($settings['className']) ?></em> engine is being used for translations caching. To change the config edit <code>config/app.php</code></li>
<?php else : ?>
<li class="bullet problem">Your translations cache is NOT working. Please check the settings in config/app.php</li>
<?php endif; ?>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="column">
<h4>Database</h4>
<?php
$result = $checkConnection('default');
?>
<ul>
<?php if ($result['connected']) : ?>
<li class="bullet success">CakePHP is able to connect to the database.</li>
<?php else : ?>
<li class="bullet problem">CakePHP is NOT able to connect to the database.<br /><?= h($result['error']) ?></li>
<?php endif; ?>
<?php $settings = Cache::getConfig('_cake_model_'); ?>
<?php if (!empty($settings)) : ?>
<li class="bullet success">The <em><?= h($settings['className']) ?></em> engine is being used for database schema caching. To change the config edit config/app.php</li>
<?php else : ?>
<li class="bullet problem">Your database schema cache is NOT working. Please check the settings in <code>config/app.php</code></li>
<?php endif; ?>
</ul>
</div>
<div class="column">
<h4>DebugKit</h4>
<ul>
<?php if (Plugin::isLoaded('DebugKit')) : ?>
<li class="bullet success">DebugKit is loaded.</li>
<?php
$result = $checkConnection('debug_kit');
?>
<?php if ($result['connected']) : ?>
<li class="bullet success">DebugKit can connect to the database.</li>
<?php else : ?>
<li class="bullet problem">There are configuration problems present which need to be fixed:<br /><?= $result['error'] ?></li>
<?php endif; ?>
<?php else : ?>
<li class="bullet problem">DebugKit is <strong>not</strong> loaded.</li>
<?php endif; ?>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="column links">
<h3>Getting Started</h3>
<a target="_blank" rel="noopener" href="https://book.cakephp.org/5/en/">CakePHP Documentation</a>
<a target="_blank" rel="noopener" href="https://book.cakephp.org/5/en/tutorials-and-examples/cms/installation.html">The 20 min CMS Tutorial</a>
</div>
</div>
<hr>
<div class="row">
<div class="column links">
<h3>Help and Bug Reports</h3>
<a target="_blank" rel="noopener" href="https://slack-invite.cakephp.org/">Slack</a>
<a target="_blank" rel="noopener" href="https://github.com/cakephp/cakephp/issues">CakePHP Issues</a>
<a target="_blank" rel="noopener" href="https://discourse.cakephp.org/">CakePHP Forum</a>
</div>
</div>
<hr>
<div class="row">
<div class="column links">
<h3>Docs and Downloads</h3>
<a target="_blank" rel="noopener" href="https://api.cakephp.org/">CakePHP API</a>
<a target="_blank" rel="noopener" href="https://bakery.cakephp.org">The Bakery</a>
<a target="_blank" rel="noopener" href="https://book.cakephp.org/5/en/">CakePHP Documentation</a>
<a target="_blank" rel="noopener" href="https://plugins.cakephp.org">CakePHP plugins repo</a>
<a target="_blank" rel="noopener" href="https://github.com/cakephp/">CakePHP Code</a>
<a target="_blank" rel="noopener" href="https://github.com/FriendsOfCake/awesome-cakephp">CakePHP Awesome List</a>
<a target="_blank" rel="noopener" href="https://www.cakephp.org">CakePHP</a>
</div>
</div>
<hr>
<div class="row">
<div class="column links">
<h3>Training and Certification</h3>
<a target="_blank" rel="noopener" href="https://cakefoundation.org/">Cake Software Foundation</a>
<a target="_blank" rel="noopener" href="https://training.cakephp.org/">CakePHP Training</a>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
-1
View File
@@ -1 +0,0 @@
-15
View File
@@ -1,15 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
* @var array $params
* @var string $message
*/
$class = 'message';
if (!empty($params['class'])) {
$class .= ' ' . $params['class'];
}
if (!isset($params['escape']) || $params['escape'] !== false) {
$message = h($message);
}
?>
<div class="<?= h($class) ?>" onclick="this.classList.add('hidden');"><?= $message ?></div>
-11
View File
@@ -1,11 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
* @var array $params
* @var string $message
*/
if (!isset($params['escape']) || $params['escape'] !== false) {
$message = h($message);
}
?>
<div class="message error" onclick="this.classList.add('hidden');"><?= $message ?></div>
-11
View File
@@ -1,11 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
* @var array $params
* @var string $message
*/
if (!isset($params['escape']) || $params['escape'] !== false) {
$message = h($message);
}
?>
<div class="message" onclick="this.classList.add('hidden');"><?= $message ?></div>
-11
View File
@@ -1,11 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
* @var array $params
* @var string $message
*/
if (!isset($params['escape']) || $params['escape'] !== false) {
$message = h($message);
}
?>
<div class="message success" onclick="this.classList.add('hidden')"><?= $message ?></div>
-11
View File
@@ -1,11 +0,0 @@
<?php
/**
* @var \App\View\AppView $this
* @var array $params
* @var string $message
*/
if (!isset($params['escape']) || $params['escape'] !== false) {
$message = h($message);
}
?>
<div class="message warning" onclick="this.classList.add('hidden');"><?= $message ?></div>
-22
View File
@@ -1,22 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \Cake\View\View $this
* @var string $content
*/
$lines = explode("\n", $content);
foreach ($lines as $line) :
echo '<p> ' . $line . "</p>\n";
endforeach;
-18
View File
@@ -1,18 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \Cake\View\View $this
* @var string $content
*/
echo $content;
-17
View File
@@ -1,17 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \App\View\AppView $this
*/
echo $this->fetch('content');
-55
View File
@@ -1,55 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \App\View\AppView $this
*/
$cakeDescription = 'CakePHP: the rapid development php framework';
?>
<!DOCTYPE html>
<html>
<head>
<?= $this->Html->charset() ?>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
<?= $cakeDescription ?>:
<?= $this->fetch('title') ?>
</title>
<?= $this->Html->meta('icon') ?>
<?= $this->Html->css(['normalize.min', 'milligram.min', 'fonts', 'cake']) ?>
<?= $this->fetch('meta') ?>
<?= $this->fetch('css') ?>
<?= $this->fetch('script') ?>
</head>
<body>
<nav class="top-nav">
<div class="top-nav-title">
<a href="<?= $this->Url->build('/') ?>"><span>Cake</span>PHP</a>
</div>
<div class="top-nav-links">
<a target="_blank" rel="noopener" href="https://book.cakephp.org/5/">Documentation</a>
<a target="_blank" rel="noopener" href="https://api.cakephp.org/">API</a>
</div>
</nav>
<main class="main">
<div class="container">
<?= $this->Flash->render() ?>
<?= $this->fetch('content') ?>
</div>
</main>
<footer>
</footer>
</body>
</html>
-25
View File
@@ -1,25 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \App\View\AppView $this
*/
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title><?= $this->fetch('title') ?></title>
</head>
<body>
<?= $this->fetch('content') ?>
</body>
</html>
-17
View File
@@ -1,17 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \App\View\AppView $this
*/
echo $this->fetch('content');
-39
View File
@@ -1,39 +0,0 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @var \App\View\AppView $this
*/
?>
<!DOCTYPE html>
<html>
<head>
<?= $this->Html->charset() ?>
<title>
<?= $this->fetch('title') ?>
</title>
<?= $this->Html->meta('icon') ?>
<?= $this->Html->css(['normalize.min', 'milligram.min', 'fonts', 'cake']) ?>
<?= $this->fetch('meta') ?>
<?= $this->fetch('css') ?>
<?= $this->fetch('script') ?>
</head>
<body>
<div class="error-container">
<?= $this->Flash->render() ?>
<?= $this->fetch('content') ?>
<?= $this->Html->link(__('Back'), 'javascript:history.back()') ?>
</div>
</body>
</html>
View File
-88
View File
@@ -1,88 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.3.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Test\TestCase;
use App\Application;
use App\Middleware\HostHeaderMiddleware;
use Cake\Core\Configure;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* ApplicationTest class
*/
class ApplicationTest extends TestCase
{
use IntegrationTestTrait;
/**
* Test bootstrap in production.
*
* @return void
*/
public function testBootstrap()
{
Configure::write('debug', false);
$app = new Application(dirname(__DIR__, 2) . '/config');
$app->bootstrap();
$plugins = $app->getPlugins();
$this->assertTrue($plugins->has('Bake'), 'plugins has Bake?');
$this->assertFalse($plugins->has('DebugKit'), 'plugins has DebugKit?');
$this->assertTrue($plugins->has('Migrations'), 'plugins has Migrations?');
}
/**
* Test bootstrap add DebugKit plugin in debug mode.
*
* @return void
*/
public function testBootstrapInDebug()
{
Configure::write('debug', true);
$app = new Application(dirname(__DIR__, 2) . '/config');
$app->bootstrap();
$plugins = $app->getPlugins();
$this->assertTrue($plugins->has('DebugKit'), 'plugins has DebugKit?');
}
/**
* testMiddleware
*
* @return void
*/
public function testMiddleware()
{
$app = new Application(dirname(__DIR__, 2) . '/config');
$middleware = new MiddlewareQueue();
$middleware = $app->middleware($middleware);
$this->assertInstanceOf(ErrorHandlerMiddleware::class, $middleware->current());
$middleware->seek(1);
$this->assertInstanceOf(HostHeaderMiddleware::class, $middleware->current());
$middleware->seek(2);
$this->assertInstanceOf(AssetMiddleware::class, $middleware->current());
$middleware->seek(3);
$this->assertInstanceOf(RoutingMiddleware::class, $middleware->current());
}
}
@@ -1,113 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Test\TestCase\Controller;
use Cake\Core\Configure;
use Cake\TestSuite\Constraint\Response\StatusCode;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* PagesControllerTest class
*/
class PagesControllerTest extends TestCase
{
use IntegrationTestTrait;
/**
* testDisplay method
*
* @return void
*/
public function testDisplay()
{
Configure::write('debug', true);
$this->get('/pages/home');
$this->assertResponseOk();
$this->assertResponseContains('CakePHP');
$this->assertResponseContains('<html>');
}
/**
* Test that missing template renders 404 page in production
*
* @return void
*/
public function testMissingTemplate()
{
Configure::write('debug', false);
$this->get('/pages/not_existing');
$this->assertResponseError();
$this->assertResponseContains('Error');
}
/**
* Test that missing template in debug mode renders missing_template error page
*
* @return void
*/
public function testMissingTemplateInDebug()
{
Configure::write('debug', true);
$this->get('/pages/not_existing');
$this->assertResponseFailure();
$this->assertResponseContains('Missing Template');
$this->assertResponseContains('stack-frames');
$this->assertResponseContains('not_existing.php');
}
/**
* Test directory traversal protection
*
* @return void
*/
public function testDirectoryTraversalProtection()
{
$this->get('/pages/../Layout/ajax');
$this->assertResponseCode(403);
$this->assertResponseContains('Forbidden');
}
/**
* Test that CSRF protection is applied to page rendering.
*
* @return void
*/
public function testCsrfAppliedError()
{
$this->post('/pages/home', ['hello' => 'world']);
$this->assertResponseCode(403);
$this->assertResponseContains('CSRF');
}
/**
* Test that CSRF protection is applied to page rendering.
*
* @return void
*/
public function testCsrfAppliedOk()
{
$this->enableCsrfToken();
$this->post('/pages/home', ['hello' => 'world']);
$this->assertThat(403, $this->logicalNot(new StatusCode($this->_response)));
$this->assertResponseNotContains('CSRF');
}
}
View File
-60
View File
@@ -1,60 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
use Cake\Chronos\Chronos;
use Cake\Core\Configure;
use Cake\TestSuite\ConnectionHelper;
use Migrations\TestSuite\Migrator;
/**
* Test runner bootstrap.
*
* Add additional configuration/setup your application needs when running
* unit tests in this file.
*/
require dirname(__DIR__) . '/vendor/autoload.php';
require dirname(__DIR__) . '/config/bootstrap.php';
if (empty($_SERVER['HTTP_HOST']) && !Configure::read('App.fullBaseUrl')) {
Configure::write('App.fullBaseUrl', 'http://localhost');
}
// Fixate now to avoid one-second-leap-issues
Chronos::setTestNow(Chronos::now());
// Fixate sessionid early on, as php7.2+
// does not allow the sessionid to be set after stdout
// has been written to.
session_id('cli');
// Connection aliasing needs to happen before migrations are run.
// Otherwise, table objects inside migrations would use the default datasource
ConnectionHelper::addTestAliases();
// Use migrations to build test database schema.
//
// Will rebuild the database if the migration state differs
// from the migration history in files.
//
// If you are not using CakePHP's migrations you can
// hook into your migration tool of choice here or
// load schema from a SQL dump file with
// use Cake\TestSuite\Fixture\SchemaLoader;
// (new SchemaLoader())->loadSqlFiles('./tests/schema.sql', 'test');
(new Migrator())->run();
-4
View File
@@ -1,4 +0,0 @@
-- Test database schema.
--
-- If you are not using CakePHP migrations you can put
-- your application's schema in this file and use it in tests.
-8
View File
@@ -1,8 +0,0 @@
<IfModule mod_rewrite.c>
RewriteEngine On
# Pass the Authorization header (JWT Bearer) to PHP - Apache/PHP-FPM strips it otherwise
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
-15
View File
@@ -1,15 +0,0 @@
<?php
// TEMPORARY diagnostic - delete after checking. Shows which variables carry the Authorization header (values hidden).
header('Content-Type: application/json');
$found = [];
foreach ($_SERVER as $k => $v) {
if (stripos($k, 'AUTH') !== false) {
$found[$k] = substr((string)$v, 0, 12) . '...';
}
}
echo json_encode([
'sapi' => PHP_SAPI,
'server_vars' => $found,
'getallheaders_has_auth' => function_exists('getallheaders')
&& isset(array_change_key_case(getallheaders())['authorization']),
], JSON_PRETTY_PRINT);
-301
View File
@@ -1,301 +0,0 @@
/* Milligram overrides */
:root {
/* The following are official CakePHP colors */
--color-cakephp-red: #d33c43;
--color-cakephp-gray: #404041;
--color-cakephp-blue: #2f85ae;
--color-cakephp-lightblue: #34bdd7;
/* These are additional colors */
--color-lightgray: #606c76;
--color-white: #fff;
--color-main-bg: #f5f7fa;
--color-links: var(--color-cakephp-blue);
--color-links-active: #2a6496;
--color-headings: #363637;
--color-message-success-bg: #e3fcec;
--color-message-success-text: #1f9d55;
--color-message-success-border: #51d88a;
--color-message-warning-bg: #fffabc;
--color-message-warning-text: #8d7b00;
--color-message-warning-border: #d3b800;
--color-message-error-bg: #fcebea;
--color-message-error-text: #cc1f1a;
--color-message-error-border: #ef5753;
--color-message-info-bg: #eff8ff;
--color-message-info-text: #2779bd;
--color-message-info-border: #6cb2eb;
}
.button, button, input[type='button'], input[type='reset'], input[type='submit'] {
background-color: var(--color-cakephp-red);
border-color: var(--color-cakephp-red);
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 400;
background: var(--color-main-bg);
}
.top-nav-links,
.side-nav,
h1, h2, h3, h4, h5, h6 {
font-family: "Raleway", sans-serif;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 400;
color: var(--color-headings);
}
a {
color: var(--color-links);
transition: color 0.2s linear;
}
a:hover,
a:focus,
a:active {
color: var(--color-links-active);
transition: color 0.2s ease-out;
}
.side-nav a,
.top-nav-links a,
th a,
.actions a {
color: var(--color-lightgray);
}
.side-nav a:hover,
.side-nav a:focus,
.actions a:hover,
.actions a:focus {
color: var(--color-links-active);
}
/* Utility */
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Main */
.content {
padding: 2rem;
background: var(--color-white);
border-radius: 0.4rem;
/* Thanks Stripe */
box-shadow: 0 7px 14px 0 rgba(60, 66, 87, 0.1),
0 3px 6px 0 rgba(0, 0, 0, 0.07);
}
.content form {
margin: 0;
}
.actions a {
font-weight: bold;
padding: 0 0.4rem;
}
.actions a:first-child {
padding-left: 0;
}
th {
white-space: nowrap;
}
/* Nav bar */
.top-nav {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 112rem;
padding: 2rem;
margin: 0 auto;
}
.top-nav-title a {
font-size: 2.4rem;
color: var(--color-cakephp-red);
}
.top-nav-title span {
color: var(--color-cakephp-gray);
}
.top-nav-links a {
margin: 0 0.5rem;
}
.top-nav-title a,
.top-nav-links a {
font-weight: bold;
}
.side-nav-item {
display: block;
padding: 0.5rem 0;
}
/* View action */
.view.content .text {
margin-top: 1.2rem;
}
.related {
margin-top: 2rem;
}
/* Flash messages */
.message {
padding: .5rem 1rem;
background: var(--color-message-info-bg);
color: var(--color-message-info-text);
border-color: var(--color-message-info-border);
border-width: 1px;
border-style: solid;
border-radius: 4px;
margin-bottom: 1rem;
cursor: pointer;
}
.message.hidden {
display: none;
}
.message.success {
background: var(--color-message-success-bg);
color: var(--color-message-success-text);
border-color: var(--color-message-success-border);
}
.message.warning {
background: var(--color-message-warning-bg);
color: var(--color-message-warning-text);
border-color: var(--color-message-warning-border);
}
.message.error {
background: var(--color-message-error-bg);
color: var(--color-message-error-text);
border-color: var(--color-message-error-border);
}
/* Forms */
.input {
margin-bottom: 1.5rem;
}
.input input,
.input select,
.input textarea {
margin-bottom: 0;
}
.input label:has(input[type='checkbox']),
.input label:has(input[type='radio']) {
display: flex;
align-items: center;
}
.input label:has(~ label),
.input label:has(input[type='radio']) {
margin-bottom: 0;
}
.input label > input[type='checkbox'],
.input label > input[type='radio'] {
margin-right: 1.0rem;
}
input[type='color'] {
max-width: 4rem;
padding: 0.3rem .5rem 0.3rem;
}
.error-message {
color: var(--color-message-error-text);
}
/* Paginator */
.paginator {
text-align: right;
}
.paginator p {
margin-bottom: 0;
}
.pagination {
display: flex;
justify-content: center;
list-style: none;
margin: 0 0 1rem 0;
padding: 0;
}
.pagination li {
display: inline-block;
margin: 0.25em;
text-align: center;
}
.pagination a {
color: var(--color-cakephp-blue);
display: inline-block;
font-size: 1.25rem;
line-height: 3rem;
min-width: 3rem;
padding: 0;
position: relative;
text-decoration: none;
transition: background .3s,color .3s;
}
.pagination li.active a,
.pagination a:hover {
text-decoration: underline;
}
.pagination .disabled a {
cursor: not-allowed;
color: var(--color-lightgray);
text-decoration: none;
}
.first a,
.prev a,
.next a,
.last a {
padding: 0 .75rem;
}
.disabled a:hover {
background: initial;
color: initial;
}
.asc:after {
content: " \2193";
}
.desc:after {
content: " \2191";
}
/* Error in non debug mode */
.error-container {
align-items: center;
display: flex;
flex-direction: column;
height: 100vh;
justify-content: center;
}
@media screen and (max-width: 640px) {
/* Fix milligram not having a responsive column system */
.row .column[class*='column-'] {
flex: 0 0 100%;
max-width: 100%
}
.top-nav {
margin: 0 auto;
}
.side-nav {
margin-bottom: 1rem;
}
.heading {
margin-bottom: 1rem;
}
.side-nav-item {
display: inline;
margin: 0 1.5rem 0 0;
}
.asc:after {
content: " \2192";
}
.desc:after {
content: " \2190";
}
}
-80
View File
@@ -1,80 +0,0 @@
/* cyrillic-ext */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 400;
src: url('../font/raleway-400-cyrillic-ext.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 400;
src: url('../font/raleway-400-cyrillic.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 400;
src: url('../font/raleway-400-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 400;
src: url('../font/raleway-400-latin-ext.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 400;
src: url('../font/raleway-400-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 700;
src: url('../font/raleway-700-cyrillic-ext.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 700;
src: url('../font/raleway-700-cyrillic.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 700;
src: url('../font/raleway-700-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 700;
src: url('../font/raleway-700-latin-ext.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 700;
src: url('../font/raleway-700-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
-75
View File
@@ -1,75 +0,0 @@
/* Home page styles */
@font-face {
font-family: 'cakefont';
src: url('../font/cakedingbats-webfont.eot');
src: url('../font/cakedingbats-webfont.eot?#iefix') format('embedded-opentype'),
url('../font/cakedingbats-webfont.woff2') format('woff2'),
url('../font/cakedingbats-webfont.woff') format('woff'),
url('../font/cakedingbats-webfont.ttf') format('truetype'),
url('../font/cakedingbats-webfont.svg#cake_dingbatsregular') format('svg');
font-weight: normal;
font-style: normal;
}
body {
padding: 60px 0;
}
header {
margin-bottom: 60px;
}
img {
margin-bottom: 30px;
}
h1 {
font-weight: bold;
}
ul {
list-style-type: none;
margin: 0 0 30px 0;
padding-left: 25px;
}
a {
color: #0071BC;
text-decoration: underline;
}
hr {
border-bottom: 1px solid #e7e7e7;
border-top: 0;
margin-bottom: 35px;
}
.text-center {
text-align: center;
}
.links a {
margin-right: 10px;
}
.release-name {
color: #D33C43;
font-weight: 400;
font-style: italic;
}
.bullet:before {
font-family: 'cakefont', sans-serif;
font-size: 18px;
display: inline-block;
margin-left: -1.3em;
width: 1.2em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
vertical-align: -1px;
}
.success:before {
color: #88c671;
content: "\0056";
}
.problem:before {
color: #d33d44;
content: "\0057";
}
.cake-error {
padding: 10px;
margin: 10px 0;
}
#url-rewriting-warning {
display: none;
}
-9
View File
File diff suppressed because one or more lines are too long
-8
View File
@@ -1,8 +0,0 @@
/**
* Minified by jsDelivr using clean-css v4.2.1.
* Original file: /npm/normalize.css@8.0.1/normalize.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

-51
View File
@@ -1,51 +0,0 @@
License for 'Raleway'
SIL Open Font License
Copyright (c) 2010, Matt McInerney (matt@pixelspread.com),
Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com),
Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Raleway
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
—————————————————————————————-
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
—————————————————————————————-
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More