first commit splitting onto its own repo
This commit is contained in:
commit
48df5db468
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/composer.lock
|
||||
/composer.phar
|
||||
/phpunit.xml
|
||||
/.phpunit.result.cache
|
||||
/phpunit.phar
|
||||
/config/Migrations/schema-dump-default.lock
|
||||
/vendor/
|
||||
/.idea/
|
||||
tmp
|
||||
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
@ -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/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeCarts\Controller;
|
||||
|
||||
use App\Controller\AppController as BaseController;
|
||||
|
||||
class AppController extends BaseController
|
||||
{
|
||||
}
|
||||
|
|
@ -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'
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
|
@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue