first commit splitting onto its own repo
This commit is contained in:
143
.gitea/workflows/ci.yaml
Normal file
143
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,143 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
testsuite:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version: ['8.2', '8.4']
|
||||
db-type: ['mysql']
|
||||
# db-type: ['sqlite', 'mysql', 'pgsql']
|
||||
prefer-lowest: ['']
|
||||
include:
|
||||
- php-version: '8.2'
|
||||
db-type: 'sqlite'
|
||||
prefer-lowest: 'prefer-lowest'
|
||||
|
||||
services:
|
||||
mysql8:
|
||||
image: mysql:8.0
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: cakephp
|
||||
# services:
|
||||
# postgres:
|
||||
# image: postgres
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
# env:
|
||||
# POSTGRES_PASSWORD: postgres
|
||||
# mysql8:
|
||||
# image: mysql:8.0
|
||||
# env:
|
||||
# MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
# MYSQL_DATABASE: test_db
|
||||
# ports:
|
||||
# - 3306:3306
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: mbstring, intl, sqlite, pdo_${{ matrix.db-type }}
|
||||
coverage: pcov
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composercache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composercache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }}
|
||||
|
||||
- name: Composer install
|
||||
run: |
|
||||
composer --version
|
||||
if ${{ matrix.prefer-lowest == 'prefer-lowest' }}
|
||||
then
|
||||
composer update --prefer-lowest --prefer-stable
|
||||
composer require --dev dereuromark/composer-prefer-lowest:dev-master
|
||||
else
|
||||
composer install --no-progress --prefer-dist --optimize-autoloader
|
||||
fi
|
||||
|
||||
- name: Setup problem matchers for PHPUnit
|
||||
if: matrix.db-type == 'mysql'
|
||||
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||
|
||||
|
||||
- name: Run PHPUnit tests
|
||||
env:
|
||||
TEST_MYSQL_HOST: mysql8
|
||||
TEST_MYSQL_CHARSET: utf8mb4
|
||||
TEST_MYSQL_DBNAME: cakephp
|
||||
TEST_MYSQL_USERNAME: root
|
||||
TEST_MYSQL_PASSWORD:
|
||||
run: |
|
||||
if [[ ${{ matrix.php-version }} == '8.2' ]]; then
|
||||
vendor/bin/phpunit --coverage-clover=coverage.xml
|
||||
else
|
||||
vendor/bin/phpunit
|
||||
fi
|
||||
|
||||
- name: Validate prefer-lowest
|
||||
if: matrix.prefer-lowest == 'prefer-lowest'
|
||||
run: vendor/bin/validate-prefer-lowest -m
|
||||
|
||||
# - name: Upload coverage reports to Codecov
|
||||
# if: success() && matrix.php-version == '8.2'
|
||||
# uses: codecov/codecov-action@v4
|
||||
# with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
validation:
|
||||
name: Coding Standard & Static Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: mbstring, intl, sqlite
|
||||
coverage: none
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composercache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composercache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }}
|
||||
|
||||
- name: Composer install
|
||||
run: |
|
||||
composer --version
|
||||
if ${{ matrix.prefer-lowest == 'prefer-lowest' }}
|
||||
then
|
||||
composer update --prefer-lowest --prefer-stable
|
||||
composer require --dev dereuromark/composer-prefer-lowest:dev-master
|
||||
else
|
||||
composer install --no-progress --prefer-dist --optimize-autoloader
|
||||
fi
|
||||
|
||||
- name: Composer phpstan setup
|
||||
run: composer stan-setup
|
||||
|
||||
- name: Run phpstan
|
||||
run: vendor/bin/phpstan analyse --error-format=github
|
||||
|
||||
- name: Run phpcs
|
||||
run: composer cs-check
|
||||
127
.github/workflows/ci.yml
vendored
Normal file
127
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
testsuite:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version: ['8.1', '8.4']
|
||||
db-type: ['sqlite', 'mysql', 'pgsql']
|
||||
prefer-lowest: ['']
|
||||
include:
|
||||
- php-version: '8.1'
|
||||
db-type: 'sqlite'
|
||||
prefer-lowest: 'prefer-lowest'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Service
|
||||
if: matrix.db-type == 'mysql'
|
||||
run: |
|
||||
sudo service mysql start
|
||||
mysql -h 127.0.0.1 -u root -proot -e 'CREATE DATABASE cakephp;'
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: mbstring, intl, pdo_${{ matrix.db-type }}
|
||||
coverage: pcov
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composercache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composercache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }}
|
||||
|
||||
- name: Composer install
|
||||
run: |
|
||||
composer --version
|
||||
if ${{ matrix.prefer-lowest == 'prefer-lowest' }}
|
||||
then
|
||||
composer update --prefer-lowest --prefer-stable
|
||||
composer require --dev dereuromark/composer-prefer-lowest:dev-master
|
||||
else
|
||||
composer install --no-progress --prefer-dist --optimize-autoloader
|
||||
fi
|
||||
|
||||
- name: Setup problem matchers for PHPUnit
|
||||
if: matrix.db-type == 'mysql'
|
||||
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||
|
||||
- name: Wait for MySQL
|
||||
if: matrix.db-type == 'mysql'
|
||||
run: while ! `mysqladmin ping -h 127.0.0.1 --silent`; do printf 'Waiting for MySQL...\n'; sleep 2; done;
|
||||
|
||||
- name: Run PHPUnit
|
||||
run: |
|
||||
if [[ ${{ matrix.db-type }} == 'sqlite' ]]; then export DB_URL='sqlite:///:memory:'; fi
|
||||
if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp?encoding=utf8'; fi
|
||||
if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi
|
||||
if [[ ${{ matrix.php-version }} == '8.1' ]]; then
|
||||
vendor/bin/phpunit --coverage-clover=coverage.xml
|
||||
else
|
||||
vendor/bin/phpunit
|
||||
fi
|
||||
|
||||
- name: Validate prefer-lowest
|
||||
if: matrix.prefer-lowest == 'prefer-lowest'
|
||||
run: vendor/bin/validate-prefer-lowest -m
|
||||
|
||||
# - name: Upload coverage reports to Codecov
|
||||
# if: success() && matrix.php-version == '8.1'
|
||||
# uses: codecov/codecov-action@v4
|
||||
# with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
validation:
|
||||
name: Coding Standard & Static Analysis
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: mbstring, intl
|
||||
coverage: none
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composercache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composercache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }}
|
||||
|
||||
- name: Composer phpstan setup
|
||||
run: composer stan-setup
|
||||
|
||||
- name: Run phpstan
|
||||
run: vendor/bin/phpstan analyse --error-format=github
|
||||
|
||||
- name: Run phpcs
|
||||
run: composer cs-check
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/composer.lock
|
||||
/composer.phar
|
||||
/phpunit.xml
|
||||
/.phpunit.result.cache
|
||||
/phpunit.phar
|
||||
/config/Migrations/schema-dump-default.lock
|
||||
/vendor/
|
||||
/.idea/
|
||||
tmp
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/CakeCarts.iml" filepath="$PROJECT_DIR$/.idea/CakeCarts.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
9
LICENSE
Normal file
9
LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 HI POWERED DEV, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
29
composer.json
Normal file
29
composer.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "hi-powered-dev/cake-carts",
|
||||
"description": "A CakePHP plugin for storing entities in carts",
|
||||
"type": "cakephp-plugin",
|
||||
"license": "MIT",
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"dereuromark/cakephp-tools": "^3.9",
|
||||
"muffin/trash": "^4.2",
|
||||
"hi-powered-dev/cheese-cake": "dev-prod",
|
||||
"cakephp/cakephp": "^5.0.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.1",
|
||||
"cakephp/migrations": "^4.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"CakeCarts\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"CakeCarts\\Test\\": "tests/",
|
||||
"Cake\\Test\\": "vendor/cakephp/cakephp/tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
114
config/Migrations/20251008093507_CreateCarts.php
Normal file
114
config/Migrations/20251008093507_CreateCarts.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Migrations\BaseMigration;
|
||||
|
||||
class CreateCarts extends BaseMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* More information on this method is available here:
|
||||
* https://book.cakephp.org/migrations/4/en/migrations.html#the-change-method
|
||||
* @return void
|
||||
*/
|
||||
public function change(): void
|
||||
{
|
||||
$table = $this->table('carts', ['id' => false, 'primary_key' => ['id']]);
|
||||
$table->addColumn('id', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('cart_type_id', 'integer', [
|
||||
'default' => null,
|
||||
'limit' => 11,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('session_id', 'string', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('user_id', 'integer', [
|
||||
'default' => null,
|
||||
'limit' => 11,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('user_id_uuid', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('created', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('modified', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('deleted', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('removed', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('removed_reason_id', 'integer', [
|
||||
'default' => null,
|
||||
'limit' => 11,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('num_items', 'integer', [
|
||||
'default' => 0,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->create();
|
||||
|
||||
$table = $this->table('cart_items', ['id' => false, 'primary_key' => ['id']]);
|
||||
$table->addColumn('id', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('foreign_key', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('foreign_key_uuid', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('model', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('cart_id', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('position', 'integer', [
|
||||
'default' => null,
|
||||
'limit' => 11,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('qty', 'integer', [
|
||||
'default' => null,
|
||||
'limit' => 11,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('price', 'decimal', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'precision' => 13,
|
||||
'scale' => 5,
|
||||
]);
|
||||
$table->addColumn('subtotal', 'decimal', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'precision' => 13,
|
||||
'scale' => 5,
|
||||
]);
|
||||
|
||||
$table->create();
|
||||
}
|
||||
}
|
||||
BIN
config/Migrations/schema-dump-default.lock
Normal file
BIN
config/Migrations/schema-dump-default.lock
Normal file
Binary file not shown.
31
phpunit.xml.dist
Normal file
31
phpunit.xml.dist
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
colors="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="memory_limit" value="-1"/>
|
||||
<ini name="apc.enable_cli" value="1"/>
|
||||
<env name="FIXTURE_SCHEMA_METADATA" value="tests/schema.php"/>
|
||||
</php>
|
||||
|
||||
<!-- Add any additional test suites you want to run here -->
|
||||
<testsuites>
|
||||
<testsuite name="CakeProducts">
|
||||
<directory>tests/TestCase/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<!-- Setup fixture extension -->
|
||||
<extensions>
|
||||
<bootstrap class="Cake\TestSuite\Fixture\Extension\PHPUnitExtension"/>
|
||||
</extensions>
|
||||
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">src/</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
99
src/CakeCartsPlugin.php
Normal file
99
src/CakeCartsPlugin.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts;
|
||||
|
||||
use Cake\Console\CommandCollection;
|
||||
use Cake\Core\BasePlugin;
|
||||
use Cake\Core\ContainerInterface;
|
||||
use Cake\Core\PluginApplicationInterface;
|
||||
use Cake\Http\MiddlewareQueue;
|
||||
use Cake\Routing\RouteBuilder;
|
||||
|
||||
/**
|
||||
* Plugin for CakeCarts
|
||||
*/
|
||||
class CakeCartsPlugin extends BasePlugin
|
||||
{
|
||||
/**
|
||||
* Load all the plugin configuration and bootstrap logic.
|
||||
*
|
||||
* The host application is provided as an argument. This allows you to load
|
||||
* additional plugin dependencies, or attach events.
|
||||
*
|
||||
* @param \Cake\Core\PluginApplicationInterface $app The host application
|
||||
* @return void
|
||||
*/
|
||||
public function bootstrap(PluginApplicationInterface $app): void
|
||||
{
|
||||
// remove this method hook if you don't need it
|
||||
}
|
||||
|
||||
/**
|
||||
* Add routes for the plugin.
|
||||
*
|
||||
* If your plugin has many routes and you would like to isolate them into a separate file,
|
||||
* you can create `$plugin/config/routes.php` and delete this method.
|
||||
*
|
||||
* @param \Cake\Routing\RouteBuilder $routes The route builder to update.
|
||||
* @return void
|
||||
*/
|
||||
public function routes(RouteBuilder $routes): void
|
||||
{
|
||||
// remove this method hook if you don't need it
|
||||
$routes->plugin(
|
||||
'CakeCarts',
|
||||
['path' => '/cake-carts'],
|
||||
function (RouteBuilder $builder) {
|
||||
// Add custom routes here
|
||||
$builder->connect('/wishlist', ['controller' => 'CakeCarts', 'action' => 'wishlist']);
|
||||
|
||||
$builder->fallbacks();
|
||||
}
|
||||
);
|
||||
parent::routes($routes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add middleware for the plugin.
|
||||
*
|
||||
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to update.
|
||||
* @return \Cake\Http\MiddlewareQueue
|
||||
*/
|
||||
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
|
||||
{
|
||||
// Add your middlewares here
|
||||
// remove this method hook if you don't need it
|
||||
|
||||
return $middlewareQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add commands for the plugin.
|
||||
*
|
||||
* @param \Cake\Console\CommandCollection $commands The command collection to update.
|
||||
* @return \Cake\Console\CommandCollection
|
||||
*/
|
||||
public function console(CommandCollection $commands): CommandCollection
|
||||
{
|
||||
// Add your commands here
|
||||
// remove this method hook if you don't need it
|
||||
|
||||
$commands = parent::console($commands);
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
// Add your services here
|
||||
// remove this method hook if you don't need it
|
||||
}
|
||||
}
|
||||
10
src/Controller/AppController.php
Normal file
10
src/Controller/AppController.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Controller;
|
||||
|
||||
use App\Controller\AppController as BaseController;
|
||||
|
||||
class AppController extends BaseController
|
||||
{
|
||||
}
|
||||
160
src/Controller/CartItemsController.php
Normal file
160
src/Controller/CartItemsController.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Controller;
|
||||
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Datasource\Exception\RecordNotFoundException;
|
||||
use Cake\Http\Client;
|
||||
use Cake\Log\Log;
|
||||
use CakeCarts\Controller\AppController;
|
||||
|
||||
/**
|
||||
* CartItems Controller
|
||||
*
|
||||
* @property \CakeCarts\Model\Table\CartItemsTable $CartItems
|
||||
* @property \Authorization\Controller\Component\AuthorizationComponent $Authorization
|
||||
*/
|
||||
class CartItemsController extends AppController
|
||||
{
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize(); // TODO: Change the autogenerated stub
|
||||
|
||||
$this->loadComponent('CakeCarts.ShoppingCart', [
|
||||
// This is default config. You can modify "actions" as needed to make
|
||||
// component work only for specified methods.
|
||||
'actions' => ['add'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add method
|
||||
*
|
||||
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->request->allowMethod(['post', 'put', 'patch']);
|
||||
$this->Authorization->skipAuthorization();
|
||||
|
||||
Log::debug(print_r('$this->request->getData()', true));
|
||||
Log::debug(print_r($this->request->getData(), true));
|
||||
$cart = $this->viewBuilder()->getVar('cart');
|
||||
$postData = $this->request->getData();
|
||||
$postData['cart_id'] = $cart->id;
|
||||
// get product skus with variants
|
||||
$price = $this->request->getData('price');
|
||||
$qty = $this->request->getData('qty', 1);
|
||||
$postData['price'] = $price;
|
||||
$postData['subtotal'] = isset($price) ? bcmul("$price", "$qty", 5) : null;
|
||||
|
||||
$newCartItem = $this->Carts->CartItems->newEntity($postData, [
|
||||
'validate' => Configure::readOrFail('CakeCarts.CartItems.requirePricing') ? 'requirePricing' : 'default',
|
||||
]);
|
||||
|
||||
if ($this->Carts->CartItems->save($newCartItem)) {
|
||||
$this->Flash->success('Added to cart');
|
||||
|
||||
return $this->redirect($this->referer([
|
||||
'plugin' => 'CakeCarts',
|
||||
'controller' => 'CartItems',
|
||||
'action' => 'index'
|
||||
]));
|
||||
}
|
||||
Log::debug(print_r('$newCartItem->getErrors()', true));
|
||||
Log::debug(print_r($newCartItem->getErrors(), true));
|
||||
|
||||
$this->Flash->error('Failed to add to cart.');
|
||||
|
||||
return $this->redirect($this->referer([
|
||||
'plugin' => 'CakeCarts',
|
||||
'controller' => 'CartItems',
|
||||
'action' => 'index'
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit method
|
||||
*
|
||||
* @param string|null $id Cart Item id.
|
||||
* @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
|
||||
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
|
||||
*/
|
||||
public function edit($id = null)
|
||||
{
|
||||
$this->request->allowMethod(['post', 'put', 'patch']);
|
||||
|
||||
$cartItem = $this->CartItems->find()
|
||||
->where(['CartItems.id' => $id])
|
||||
->contain(['Carts'])
|
||||
->firstOrFail();
|
||||
|
||||
$this->ShoppingCart->checkIfIsOwnCart($cartItem->cart);
|
||||
$this->Authorization->skipAuthorization();
|
||||
|
||||
if ($this->request->is(['patch', 'post', 'put'])) {
|
||||
$postData = $this->request->getData();
|
||||
$qty = $this->request->getData('qty', 1);
|
||||
$price = $this->request->getData('price', null);
|
||||
$subtotal = isset($price) ? bcmul("$qty", "$price", 5) : null;
|
||||
$postData['subtotal'] = $subtotal;
|
||||
$cartItem = $this->CartItems->patchEntity($cartItem, $postData, [
|
||||
'validate' => Configure::readOrFail('CakeCarts.CartItems.requirePricing') ? 'requiresPricing' : 'default',
|
||||
]);
|
||||
if ($this->CartItems->save($cartItem)) {
|
||||
$this->Flash->success(__('The cart item has been saved.'));
|
||||
|
||||
return $this->redirect($this->referer([
|
||||
'plugin' => 'CakeCarts',
|
||||
'controller' => 'CartItems',
|
||||
'action' => 'index'
|
||||
]));
|
||||
}
|
||||
Log::debug(print_r('$cartItem->getErrors()', true));
|
||||
Log::debug(print_r($cartItem->getErrors(), true));
|
||||
$this->Flash->error(__('The cart item could not be saved. Please, try again.'));
|
||||
}
|
||||
|
||||
return $this->redirect($this->referer([
|
||||
'plugin' => 'CakeCarts',
|
||||
'controller' => 'CartItems',
|
||||
'action' => 'index'
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete method
|
||||
*
|
||||
* @param string|null $id Cart Item id.
|
||||
* @return \Cake\Http\Response|null Redirects to index.
|
||||
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
$this->request->allowMethod(['post', 'delete']);
|
||||
$identity = $this->getRequest()->getAttribute('identity');
|
||||
|
||||
// $cart = $this->viewBuilder()->getVar('cart');
|
||||
$cartItem = $this->CartItems->find()
|
||||
->where(['CartItems.id' => $id])
|
||||
->contain(['Carts'])
|
||||
->firstOrFail();
|
||||
|
||||
$this->ShoppingCart->checkIfIsOwnCart($cartItem->cart);
|
||||
$this->Authorization->skipAuthorization();
|
||||
|
||||
unset($cartItem->cart);
|
||||
if ($this->CartItems->delete($cartItem)) {
|
||||
$this->Flash->success(__('The cart item has been deleted.'));
|
||||
} else {
|
||||
$this->Flash->error(__('The cart item could not be deleted. Please, try again.'));
|
||||
}
|
||||
|
||||
return $this->redirect($this->referer([
|
||||
'plugin' => 'CakeCarts',
|
||||
'controller' => 'CartItems',
|
||||
'action' => 'index'
|
||||
]));
|
||||
}
|
||||
}
|
||||
38
src/Controller/CartsController.php
Normal file
38
src/Controller/CartsController.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Event\EventInterface;
|
||||
use CakeCarts\Model\Enum\CartTypeId;
|
||||
|
||||
/**
|
||||
* Carts Controller
|
||||
*
|
||||
* @property \Authorization\Controller\Component\AuthorizationComponent $Authorization
|
||||
*/
|
||||
class CartsController extends AppController
|
||||
{
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize(); // TODO: Change the autogenerated stub
|
||||
|
||||
$this->loadComponent('CakeCarts.ShoppingCart', [
|
||||
// This is default config. You can modify "actions" as needed to make
|
||||
// component work only for specified methods.
|
||||
'actions' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index method
|
||||
*
|
||||
* @return \Cake\Http\Response|null|void Renders view
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// use cart from beforeFilter
|
||||
}
|
||||
}
|
||||
138
src/Controller/Component/ShoppingCartComponent.php
Normal file
138
src/Controller/Component/ShoppingCartComponent.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Controller\Component;
|
||||
|
||||
use Cake\Controller\Component;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Datasource\EntityInterface;
|
||||
use Cake\Datasource\Exception\RecordNotFoundException;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use CakeCarts\Model\Entity\CartItem;
|
||||
use CakeCarts\Model\Enum\CartTypeId;
|
||||
|
||||
/**
|
||||
* ShoppingCart component
|
||||
*/
|
||||
class ShoppingCartComponent extends Component
|
||||
{
|
||||
/**
|
||||
* @var string $userIdField
|
||||
*/
|
||||
protected string $userIdField;
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config); // TODO: Change the autogenerated stub
|
||||
|
||||
$this->userIdField = Configure::readOrFail('CakeCarts.Users.user_id') === 'uuid' ? 'user_id_uuid' : 'user_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $_defaultConfig = [];
|
||||
|
||||
public function beforeFilter(EventInterface $event): void
|
||||
{
|
||||
if (!$this->_isActionEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionId = $this->getSessionId();
|
||||
$cart = $this->findExistingCartOrCreate($sessionId);
|
||||
|
||||
$this->getController()->set(compact('cart'));
|
||||
}
|
||||
|
||||
public function findExistingCartOrCreate(string $sessionId, int $cartTypeId = null)
|
||||
{
|
||||
$cartsTable = TableRegistry::getTableLocator()->get(Configure::readOrFail('CakeCarts.Carts.table'));
|
||||
$userIdField = Configure::readOrFail('CakeCarts.Users.user_id') === 'integer' ? 'user_id' : 'user_id_uuid';
|
||||
$identity = $this->getController()->getRequest()->getAttribute('identity');
|
||||
|
||||
$cartTypeId = $cartTypeId ?? CartTypeId::Cart->value;
|
||||
|
||||
$cart = $cartsTable
|
||||
->findBySessionId($sessionId)
|
||||
->contain(['CartItems'])
|
||||
->where(['cart_type_id' => $cartTypeId])
|
||||
->first();
|
||||
|
||||
if (isset($cart) && isset($identity) && !isset($cart[$this->userIdField])) {
|
||||
$cart = $cartsTable->patchEntity([
|
||||
$this->userIdField => $identity->getIdentifier(),
|
||||
]);
|
||||
|
||||
$cart = $cartsTable->saveOrFail($cart);
|
||||
}
|
||||
if (!isset($cart)) {
|
||||
$cart = $cartsTable->newEntity([
|
||||
'cart_type_id' => $cartTypeId,
|
||||
'session_id' => $sessionId,
|
||||
$this->userIdField => isset($identity) ? $identity->getIdentifier() : null,
|
||||
'num_items' => 0,
|
||||
'cart_items' => [],
|
||||
]);
|
||||
|
||||
$cart = $cartsTable->saveOrFail($cart);
|
||||
}
|
||||
|
||||
return $cart;
|
||||
}
|
||||
|
||||
public function getUserIdField()
|
||||
{
|
||||
return $this->userIdField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionId(): string
|
||||
{
|
||||
if (!$this->getController()->getRequest()->getSession()->started()) {
|
||||
$this->getController()->getRequest()->getSession()->start();
|
||||
}
|
||||
|
||||
if (!$this->getController()->getRequest()->getSession()->check('CakeCarts.session_id')) {
|
||||
$this->getController()->getRequest()->getSession()->write('CakeCarts.session_id', $this->getController()->getRequest()->getSession()->id());
|
||||
}
|
||||
|
||||
return $this->getController()->getRequest()->getSession()->read('CakeCarts.session_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EntityInterface $cart
|
||||
* @throws RecordNotFoundException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function checkIfIsOwnCart(EntityInterface $cart): void
|
||||
{
|
||||
$identity = $this->getController()->getRequest()->getAttribute('identity');
|
||||
|
||||
if (!isset($identity) && isset($cart->session_id) && ($cart->session_id != $this->getSessionId())) {
|
||||
throw new RecordNotFoundException();
|
||||
}
|
||||
if (isset($identity) && $identity->getIdentifier() != $cart->get($this->getUserIdField())) {
|
||||
throw new RecordNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function _isActionEnabled(): bool
|
||||
{
|
||||
$actions = $this->getConfig('actions');
|
||||
if (is_bool($actions)) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
return in_array($this->getController()->getRequest()->getParam('action'), (array)$actions, true);
|
||||
}
|
||||
}
|
||||
50
src/Model/Entity/Cart.php
Normal file
50
src/Model/Entity/Cart.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Model\Entity;
|
||||
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
/**
|
||||
* Cart Entity
|
||||
*
|
||||
* @property string $id
|
||||
* @property int $cart_type_id
|
||||
* @property string|null $session_id
|
||||
* @property integer|null $user_id
|
||||
* @property string|null $user_id_uuid
|
||||
* @property \Cake\I18n\DateTime $created
|
||||
* @property \Cake\I18n\DateTime|null $modified
|
||||
* @property \Cake\I18n\DateTime|null $deleted
|
||||
* @property \Cake\I18n\DateTime $removed
|
||||
* @property int $removed_reason_id
|
||||
* @property int $num_items
|
||||
*
|
||||
* @property \CakeCarts\Model\Entity\CartItem[] $cart_items
|
||||
*/
|
||||
class Cart extends Entity
|
||||
{
|
||||
/**
|
||||
* Fields that can be mass assigned using newEntity() or patchEntity().
|
||||
*
|
||||
* Note that when '*' is set to true, this allows all unspecified fields to
|
||||
* be mass assigned. For security purposes, it is advised to set '*' to false
|
||||
* (or remove it), and explicitly make individual fields accessible as needed.
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
protected array $_accessible = [
|
||||
'cart_type_id' => true,
|
||||
'session_id' => true,
|
||||
'user_id' => true,
|
||||
'user_id_uuid' => true,
|
||||
'created' => true,
|
||||
'modified' => true,
|
||||
'deleted' => true,
|
||||
'removed' => true,
|
||||
'removed_reason_id' => true,
|
||||
'num_items' => true,
|
||||
'user' => true,
|
||||
'cart_items' => true,
|
||||
];
|
||||
}
|
||||
45
src/Model/Entity/CartItem.php
Normal file
45
src/Model/Entity/CartItem.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Model\Entity;
|
||||
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
/**
|
||||
* CartItem Entity
|
||||
*
|
||||
* @property string $id
|
||||
* @property int $foreign_key
|
||||
* @property string $foreign_key_uuid
|
||||
* @property string $model
|
||||
* @property string $cart_id
|
||||
* @property int|null $position
|
||||
* @property int $qty
|
||||
* @property string $price
|
||||
* @property string $subtotal
|
||||
*
|
||||
* @property \CakeCarts\Model\Entity\Cart $cart
|
||||
*/
|
||||
class CartItem extends Entity
|
||||
{
|
||||
/**
|
||||
* Fields that can be mass assigned using newEntity() or patchEntity().
|
||||
*
|
||||
* Note that when '*' is set to true, this allows all unspecified fields to
|
||||
* be mass assigned. For security purposes, it is advised to set '*' to false
|
||||
* (or remove it), and explicitly make individual fields accessible as needed.
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
protected array $_accessible = [
|
||||
'foreign_key' => true,
|
||||
'foreign_key_uuid' => true,
|
||||
'model' => true,
|
||||
'cart_id' => true,
|
||||
'position' => true,
|
||||
'qty' => true,
|
||||
'price' => true,
|
||||
'subtotal' => true,
|
||||
'cart' => true,
|
||||
];
|
||||
}
|
||||
23
src/Model/Enum/CartTypeId.php
Normal file
23
src/Model/Enum/CartTypeId.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace CakeCarts\Model\Enum;
|
||||
|
||||
use Cake\Database\Type\EnumLabelInterface;
|
||||
use Tools\Model\Enum\EnumOptionsTrait;
|
||||
|
||||
enum CartTypeId: int implements EnumLabelInterface
|
||||
{
|
||||
use EnumOptionsTrait;
|
||||
|
||||
case Cart = 1;
|
||||
case Wishlist = 2;
|
||||
case CustomList = 3;
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match($this) {
|
||||
self::Cart => 'Cart',
|
||||
self::Wishlist => 'Wishlist',
|
||||
self::CustomList => 'CustomList'
|
||||
};
|
||||
}
|
||||
}
|
||||
112
src/Model/Table/CartItemsTable.php
Normal file
112
src/Model/Table/CartItemsTable.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Model\Table;
|
||||
|
||||
use Cake\ORM\Query\SelectQuery;
|
||||
use Cake\ORM\RulesChecker;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
|
||||
/**
|
||||
* CartItems Model
|
||||
*
|
||||
* @property \CakeCarts\Model\Table\CartsTable&\Cake\ORM\Association\BelongsTo $Carts
|
||||
*
|
||||
* @method \CakeCarts\Model\Entity\CartItem newEmptyEntity()
|
||||
* @method \CakeCarts\Model\Entity\CartItem newEntity(array $data, array $options = [])
|
||||
* @method array<\CakeCarts\Model\Entity\CartItem> newEntities(array $data, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\CartItem get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args)
|
||||
* @method \CakeCarts\Model\Entity\CartItem findOrCreate($search, ?callable $callback = null, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\CartItem patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
|
||||
* @method array<\CakeCarts\Model\Entity\CartItem> patchEntities(iterable $entities, array $data, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\CartItem|false save(\Cake\Datasource\EntityInterface $entity, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\CartItem saveOrFail(\Cake\Datasource\EntityInterface $entity, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\CartItem>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\CartItem>|false saveMany(iterable $entities, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\CartItem>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\CartItem> saveManyOrFail(iterable $entities, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\CartItem>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\CartItem>|false deleteMany(iterable $entities, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\CartItem>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\CartItem> deleteManyOrFail(iterable $entities, array $options = [])
|
||||
*/
|
||||
class CartItemsTable extends Table
|
||||
{
|
||||
/**
|
||||
* Initialize method
|
||||
*
|
||||
* @param array<string, mixed> $config The configuration for the Table.
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
|
||||
$this->setTable('cart_items');
|
||||
$this->setDisplayField('id');
|
||||
$this->setPrimaryKey('id');
|
||||
|
||||
$this->belongsTo('Carts', [
|
||||
'foreignKey' => 'cart_id',
|
||||
'joinType' => 'INNER',
|
||||
'className' => 'CakeCarts.Carts',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default validation rules.
|
||||
*
|
||||
* @param \Cake\Validation\Validator $validator Validator instance.
|
||||
* @return \Cake\Validation\Validator
|
||||
*/
|
||||
public function validationDefault(Validator $validator): Validator
|
||||
{
|
||||
$validator
|
||||
->uuid('product_sku_id')
|
||||
->requirePresence('product_sku_id', 'create')
|
||||
->notEmptyString('product_sku_id');
|
||||
|
||||
$validator
|
||||
->uuid('cart_id')
|
||||
->notEmptyString('cart_id');
|
||||
|
||||
$validator
|
||||
->integer('position')
|
||||
->allowEmptyString('position');
|
||||
|
||||
$validator
|
||||
->integer('qty')
|
||||
->requirePresence('qty', 'create')
|
||||
->notEmptyString('qty');
|
||||
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function validationRequiresPricing(Validator $validator): Validator
|
||||
{
|
||||
$validator = $this->validationDefault($validator);
|
||||
|
||||
$validator
|
||||
->decimal('price')
|
||||
->requirePresence('price', 'create')
|
||||
->allowEmptyString('price');
|
||||
|
||||
$validator
|
||||
->decimal('subtotal')
|
||||
->requirePresence('subtotal', 'create')
|
||||
->notEmptyString('subtotal');
|
||||
|
||||
return $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a rules checker object that will be used for validating
|
||||
* application integrity.
|
||||
*
|
||||
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
|
||||
* @return \Cake\ORM\RulesChecker
|
||||
*/
|
||||
public function buildRules(RulesChecker $rules): RulesChecker
|
||||
{
|
||||
$rules->add($rules->existsIn(['cart_id'], 'Carts'), ['errorField' => 'cart_id']);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
105
src/Model/Table/CartsTable.php
Normal file
105
src/Model/Table/CartsTable.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Model\Table;
|
||||
|
||||
use Cake\Database\Type\EnumType;
|
||||
use Cake\ORM\Query\SelectQuery;
|
||||
use Cake\ORM\RulesChecker;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
use CakeCarts\Model\Enum\CartTypeId;
|
||||
|
||||
/**
|
||||
* Carts Model
|
||||
*
|
||||
* @property \CakeCarts\Model\Table\UsersTable&\Cake\ORM\Association\BelongsTo $Users
|
||||
* @property \CakeCarts\Model\Table\CartItemsTable&\Cake\ORM\Association\HasMany $CartItems
|
||||
*
|
||||
* @method \CakeCarts\Model\Entity\Cart newEmptyEntity()
|
||||
* @method \CakeCarts\Model\Entity\Cart newEntity(array $data, array $options = [])
|
||||
* @method array<\CakeCarts\Model\Entity\Cart> newEntities(array $data, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\Cart get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args)
|
||||
* @method \CakeCarts\Model\Entity\Cart findOrCreate($search, ?callable $callback = null, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\Cart patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
|
||||
* @method array<\CakeCarts\Model\Entity\Cart> patchEntities(iterable $entities, array $data, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\Cart|false save(\Cake\Datasource\EntityInterface $entity, array $options = [])
|
||||
* @method \CakeCarts\Model\Entity\Cart saveOrFail(\Cake\Datasource\EntityInterface $entity, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\Cart>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\Cart>|false saveMany(iterable $entities, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\Cart>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\Cart> saveManyOrFail(iterable $entities, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\Cart>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\Cart>|false deleteMany(iterable $entities, array $options = [])
|
||||
* @method iterable<\CakeCarts\Model\Entity\Cart>|\Cake\Datasource\ResultSetInterface<\CakeCarts\Model\Entity\Cart> deleteManyOrFail(iterable $entities, array $options = [])
|
||||
*
|
||||
* @mixin \Cake\ORM\Behavior\TimestampBehavior
|
||||
*/
|
||||
class CartsTable extends Table
|
||||
{
|
||||
/**
|
||||
* Initialize method
|
||||
*
|
||||
* @param array<string, mixed> $config The configuration for the Table.
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
|
||||
$this->setTable('carts');
|
||||
$this->setDisplayField('id');
|
||||
$this->setPrimaryKey('id');
|
||||
|
||||
$this->addBehavior('Timestamp');
|
||||
|
||||
$this->hasMany('CartItems', [
|
||||
'foreignKey' => 'cart_id',
|
||||
'className' => 'CakeCarts.CartItems',
|
||||
]);
|
||||
$this->getSchema()->setColumnType('cart_type_id', EnumType::from(CartTypeId::class));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default validation rules.
|
||||
*
|
||||
* @param \Cake\Validation\Validator $validator Validator instance.
|
||||
* @return \Cake\Validation\Validator
|
||||
*/
|
||||
public function validationDefault(Validator $validator): Validator
|
||||
{
|
||||
$validator
|
||||
->integer('cart_type_id')
|
||||
->requirePresence('cart_type_id', 'create')
|
||||
->notEmptyString('cart_type_id');
|
||||
|
||||
$validator
|
||||
->scalar('session_id')
|
||||
->maxLength('session_id', 255)
|
||||
->allowEmptyString('session_id');
|
||||
|
||||
$validator
|
||||
->uuid('user_id_uuid')
|
||||
->allowEmptyString('user_id_uuid');
|
||||
|
||||
$validator
|
||||
->integer('user_id')
|
||||
->allowEmptyString('user_id');
|
||||
|
||||
$validator
|
||||
->dateTime('deleted')
|
||||
->allowEmptyDateTime('deleted');
|
||||
|
||||
$validator
|
||||
->dateTime('removed')
|
||||
->allowEmptyDateTime('removed');
|
||||
|
||||
$validator
|
||||
->integer('removed_reason_id')
|
||||
->allowEmptyString('removed_reason_id');
|
||||
|
||||
$validator
|
||||
->integer('num_items')
|
||||
->notEmptyString('num_items');
|
||||
|
||||
return $validator;
|
||||
}
|
||||
}
|
||||
35
tests/Fixture/CartItemsFixture.php
Normal file
35
tests/Fixture/CartItemsFixture.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Test\Fixture;
|
||||
|
||||
use Cake\TestSuite\Fixture\TestFixture;
|
||||
|
||||
/**
|
||||
* CartItemsFixture
|
||||
*/
|
||||
class CartItemsFixture extends TestFixture
|
||||
{
|
||||
/**
|
||||
* Init method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
$this->records = [
|
||||
[
|
||||
'id' => '79f66e8d-8d8d-4095-adc4-fd15234a4397',
|
||||
'foreign_key' => null,
|
||||
'foreign_key_uuid' => 'e5efe749-d6b6-4f72-83c9-32b19936c70c',
|
||||
'model' => 'ProductSkus',
|
||||
'cart_id' => '21794607-f68e-424f-91ba-3230e2f92e2b',
|
||||
'position' => 1,
|
||||
'qty' => 1,
|
||||
'price' => 1.5,
|
||||
'subtotal' => 1.5,
|
||||
],
|
||||
];
|
||||
parent::init();
|
||||
}
|
||||
}
|
||||
77
tests/Fixture/CartsFixture.php
Normal file
77
tests/Fixture/CartsFixture.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Test\Fixture;
|
||||
|
||||
use Cake\TestSuite\Fixture\TestFixture;
|
||||
use CakeCarts\Model\Enum\CartTypeId;
|
||||
|
||||
/**
|
||||
* CartsFixture
|
||||
*/
|
||||
class CartsFixture extends TestFixture
|
||||
{
|
||||
/**
|
||||
* Init method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
$this->records = [
|
||||
// normal cart - open
|
||||
[
|
||||
'id' => '74d1aa54-92a2-4039-bc10-61e1190c51ea',
|
||||
'cart_type_id' => CartTypeId::Cart->value,
|
||||
'session_id' => 'session_1',
|
||||
'user_id' => '5a34a6ae-7d3f-4dcf-bac7-57335b51e697',
|
||||
'created' => '2025-10-08 09:55:15',
|
||||
'modified' => '2025-10-08 09:55:15',
|
||||
'deleted' => null,
|
||||
'removed' => null,
|
||||
'removed_reason_id' => null,
|
||||
'num_items' => 1,
|
||||
],
|
||||
// normal cart - deleted
|
||||
[
|
||||
'id' => '74d1aa54-92a2-4039-bc10-61e1190c51eb',
|
||||
'cart_type_id' => CartTypeId::Cart->value,
|
||||
'session_id' => 'session_1',
|
||||
'user_id' => '5a34a6ae-7d3f-4dcf-bac7-57335b51e697',
|
||||
'created' => '2025-10-08 09:55:15',
|
||||
'modified' => '2025-10-08 09:55:15',
|
||||
'deleted' => '2025-10-08 09:55:15',
|
||||
'removed' => null,
|
||||
'removed_reason_id' => null,
|
||||
'num_items' => 1,
|
||||
],
|
||||
// wishlist cart - open
|
||||
[
|
||||
'id' => '74d1aa54-92a2-4039-bc10-61e1190c51ec',
|
||||
'cart_type_id' => CartTypeId::Wishlist->value,
|
||||
'session_id' => 'session_2',
|
||||
'user_id' => '5a34a6ae-7d3f-4dcf-bac7-57335b51e697',
|
||||
'created' => '2025-10-08 09:55:15',
|
||||
'modified' => '2025-10-08 09:55:15',
|
||||
'deleted' => null,
|
||||
'removed' => null,
|
||||
'removed_reason_id' => null,
|
||||
'num_items' => 1,
|
||||
],
|
||||
// normal cart - deleted
|
||||
[
|
||||
'id' => '74d1aa54-92a2-4039-bc10-61e1190c51eb',
|
||||
'cart_type_id' => CartTypeId::Cart->value,
|
||||
'session_id' => 'session_2',
|
||||
'user_id' => '5a34a6ae-7d3f-4dcf-bac7-57335b51e697',
|
||||
'created' => '2025-10-08 09:55:15',
|
||||
'modified' => '2025-10-08 09:55:15',
|
||||
'deleted' => '2025-10-08 09:55:15',
|
||||
'removed' => null,
|
||||
'removed_reason_id' => null,
|
||||
'num_items' => 1,
|
||||
],
|
||||
];
|
||||
parent::init();
|
||||
}
|
||||
}
|
||||
83
tests/TestCase/Controller/CartItemsControllerTest.php
Normal file
83
tests/TestCase/Controller/CartItemsControllerTest.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Test\TestCase\Controller;
|
||||
|
||||
use Cake\TestSuite\IntegrationTestTrait;
|
||||
use Cake\TestSuite\TestCase;
|
||||
use CakeCarts\Controller\CartItemsController;
|
||||
|
||||
/**
|
||||
* CakeCarts\Controller\CartItemsController Test Case
|
||||
*
|
||||
* @link \CakeCarts\Controller\CartItemsController
|
||||
*/
|
||||
class CartItemsControllerTest extends TestCase
|
||||
{
|
||||
use IntegrationTestTrait;
|
||||
|
||||
/**
|
||||
* Fixtures
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $fixtures = [
|
||||
'plugin.CakeCarts.CartItems',
|
||||
'plugin.CakeCarts.Carts',
|
||||
];
|
||||
|
||||
/**
|
||||
* Test index method
|
||||
*
|
||||
* @return void
|
||||
* @link \CakeCarts\Controller\CartItemsController::index()
|
||||
*/
|
||||
public function testIndex(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test view method
|
||||
*
|
||||
* @return void
|
||||
* @link \CakeCarts\Controller\CartItemsController::view()
|
||||
*/
|
||||
public function testView(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test add method
|
||||
*
|
||||
* @return void
|
||||
* @link \CakeCarts\Controller\CartItemsController::add()
|
||||
*/
|
||||
public function testAdd(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test edit method
|
||||
*
|
||||
* @return void
|
||||
* @link \CakeCarts\Controller\CartItemsController::edit()
|
||||
*/
|
||||
public function testEdit(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete method
|
||||
*
|
||||
* @return void
|
||||
* @link \CakeCarts\Controller\CartItemsController::delete()
|
||||
*/
|
||||
public function testDelete(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
}
|
||||
18
tests/TestCase/Controller/CartsControllerTest.php
Normal file
18
tests/TestCase/Controller/CartsControllerTest.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Test\TestCase\Controller;
|
||||
|
||||
use Cake\TestSuite\IntegrationTestTrait;
|
||||
use Cake\TestSuite\TestCase;
|
||||
use CakeCarts\Controller\CartsController;
|
||||
|
||||
/**
|
||||
* CakeCarts\Controller\CartsController Test Case
|
||||
*
|
||||
* @uses \CakeCarts\Controller\CartsController
|
||||
*/
|
||||
class CartsControllerTest extends TestCase
|
||||
{
|
||||
use IntegrationTestTrait;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Test\TestCase\Controller\Component;
|
||||
|
||||
use Cake\Controller\ComponentRegistry;
|
||||
use Cake\TestSuite\TestCase;
|
||||
use CakeCarts\Controller\Component\ShoppingCartComponent;
|
||||
|
||||
/**
|
||||
* CakeCarts\Controller\Component\ShoppingCartComponent Test Case
|
||||
*/
|
||||
class ShoppingCartComponentTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test subject
|
||||
*
|
||||
* @var \CakeCarts\Controller\Component\ShoppingCartComponent
|
||||
*/
|
||||
protected $ShoppingCart;
|
||||
|
||||
/**
|
||||
* setUp method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$registry = new ComponentRegistry();
|
||||
$this->ShoppingCart = new ShoppingCartComponent($registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* tearDown method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
unset($this->ShoppingCart);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
76
tests/TestCase/Model/Table/CartItemsTableTest.php
Normal file
76
tests/TestCase/Model/Table/CartItemsTableTest.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Test\TestCase\Model\Table;
|
||||
|
||||
use Cake\TestSuite\TestCase;
|
||||
use CakeCarts\Model\Table\CartItemsTable;
|
||||
|
||||
/**
|
||||
* CakeCarts\Model\Table\CartItemsTable Test Case
|
||||
*/
|
||||
class CartItemsTableTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test subject
|
||||
*
|
||||
* @var \CakeCarts\Model\Table\CartItemsTable
|
||||
*/
|
||||
protected $CartItems;
|
||||
|
||||
/**
|
||||
* Fixtures
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected array $fixtures = [
|
||||
'plugin.CakeCarts.CartItems',
|
||||
'plugin.CakeCarts.Carts',
|
||||
];
|
||||
|
||||
/**
|
||||
* setUp method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$config = $this->getTableLocator()->exists('CartItems') ? [] : ['className' => CartItemsTable::class];
|
||||
$this->CartItems = $this->getTableLocator()->get('CartItems', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* tearDown method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
unset($this->CartItems);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validationDefault method
|
||||
*
|
||||
* @return void
|
||||
* @uses \CakeCarts\Model\Table\CartItemsTable::validationDefault()
|
||||
*/
|
||||
public function testValidationDefault(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildRules method
|
||||
*
|
||||
* @return void
|
||||
* @uses \CakeCarts\Model\Table\CartItemsTable::buildRules()
|
||||
*/
|
||||
public function testBuildRules(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
}
|
||||
77
tests/TestCase/Model/Table/CartsTableTest.php
Normal file
77
tests/TestCase/Model/Table/CartsTableTest.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Test\TestCase\Model\Table;
|
||||
|
||||
use Cake\TestSuite\TestCase;
|
||||
use CakeCarts\Model\Table\CartsTable;
|
||||
|
||||
/**
|
||||
* CakeCarts\Model\Table\CartsTable Test Case
|
||||
*/
|
||||
class CartsTableTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test subject
|
||||
*
|
||||
* @var \CakeCarts\Model\Table\CartsTable
|
||||
*/
|
||||
protected $Carts;
|
||||
|
||||
/**
|
||||
* Fixtures
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected array $fixtures = [
|
||||
'plugin.CakeCarts.Carts',
|
||||
'plugin.CakeCarts.Users',
|
||||
'plugin.CakeCarts.CartItems',
|
||||
];
|
||||
|
||||
/**
|
||||
* setUp method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$config = $this->getTableLocator()->exists('Carts') ? [] : ['className' => CartsTable::class];
|
||||
$this->Carts = $this->getTableLocator()->get('Carts', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* tearDown method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
unset($this->Carts);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validationDefault method
|
||||
*
|
||||
* @return void
|
||||
* @uses \CakeCarts\Model\Table\CartsTable::validationDefault()
|
||||
*/
|
||||
public function testValidationDefault(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildRules method
|
||||
*
|
||||
* @return void
|
||||
* @uses \CakeCarts\Model\Table\CartsTable::buildRules()
|
||||
*/
|
||||
public function testBuildRules(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
}
|
||||
0
webroot/.gitkeep
Normal file
0
webroot/.gitkeep
Normal file
Reference in New Issue
Block a user