diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..9ab59b180fb670006ac910a2bc4524a1376dcea0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ + +# Ignore developer files when exporting +.gitattributes export-ignore +.gitignore export-ignore +.gitlab-ci.yml export-ignore +.phive export-ignore +captainhook.json export-ignore +phpcs.xml.dist export-ignore +phpstan-baseline.neon export-ignore +phpstan.dist.neon export-ignore +phpunit.dist.xml export-ignore +tests export-ignore + +# Explicitly set file type and line endings for PHP files, improves git diff output +*.php text eol=lf diff=php \ No newline at end of file diff --git a/.gitignore b/.gitignore index f4da20891502a3992abf2981d8f6c46e33868ab6..e25391c1f85364060ac958b37e8e35c6c51beafe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ tools/ phpstan.neon .phpunit.result.cache -phpunit.xml \ No newline at end of file +phpunit.xml +tools/ + +phpstan.neon + +.phpunit.result.cache + +phpunit.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8d20acb6c365ad8036a7b36d47dabd876d08efa..b5a64b401e554341447c74d7cf93a89ac95a3fdb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,17 @@ include: - - project: 'quiqqer/stabilization/semantic-release' - file: '/ci-templates/.gitlab-ci.yml' + - component: dev.quiqqer.com/quiqqer/stabilization/ci-cd-components/quiqqer-package-bundle/quiqqer-package-bundle@main + +# Remove the entire phpunit-php8.1 block, to allow PHPUnit to run on PHP 8.1 in your pipeline +phpunit-php8.1: + rules: + - when: never + +# Remove the entire phpunit-php8.2 block, to allow PHPUnit to run on PHP 8.2 in your pipeline +phpunit-php8.2: + rules: + - when: never + +# Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline +phpunit-php8.3: + rules: + - when: never \ No newline at end of file diff --git a/.phive/phars.xml b/.phive/phars.xml index a1315a09b4adad780a9c5e52f74835c708c5c7d5..5bfa092bfad10dad9d23240281a5a2041acb815b 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,4 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <phive xmlns="https://phar.io/phive"> - <phar name="phpstan" version="^1.10.67" installed="1.10.67" location="./tools/phpstan" copy="false"/> + <phar name="phpstan" version="1.11.8" installed="1.11.8" location="./tools/phpstan" copy="false"/> + <phar name="phpunit" version="^10.5.20" installed="10.5.20" location="./tools/phpunit" copy="false"/> + <phar name="phpcs" version="^3.10.1" installed="3.10.1" location="./tools/phpcs" copy="false"/> + <phar name="phpcbf" version="^3.10.1" installed="3.10.1" location="./tools/phpcbf" copy="false"/> + <phar name="captainhook" version="^5.23.3" installed="5.23.3" location="./tools/captainhook" copy="false"/> </phive> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..4a69a59b440e5beec561eca1e341509bd5a18688 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing + +This package follows the [QUIQQER contribution guidelines](https://dev.quiqqer.com/quiqqer/stabilization/documentation/-/wikis/home). \ No newline at end of file diff --git a/ajax/activate.php b/ajax/activate.php index b84ce1082bdcad248149960b65389499ec6b56e0..138f7abb2c7198abd2e142c3cae6475c301a6e37 100644 --- a/ajax/activate.php +++ b/ajax/activate.php @@ -22,7 +22,7 @@ function ($discountId) { $Discount->setAttribute('active', 1); $Discount->update(); - return $Discount->isActive(); + return method_exists($Discount, 'isActive') ? $Discount->isActive() : false; }, ['discountId'], 'Permission::checkAdminUser' diff --git a/ajax/deactivate.php b/ajax/deactivate.php index d82213b85cdb306278a4cba0f91fd7d5760cb585..2ecbc737c2f24c207346eab0e0e78c9251f0401f 100644 --- a/ajax/deactivate.php +++ b/ajax/deactivate.php @@ -22,7 +22,7 @@ function ($discountId) { $Discount->setAttribute('active', 0); $Discount->update(); - return $Discount->isActive(); + return method_exists($Discount, 'isActive') ? $Discount->isActive() : false; }, ['discountId'], 'Permission::checkAdminUser' diff --git a/ajax/get.php b/ajax/get.php index 7a941df712e29a81218ee05b3f6379b480366092..c1aa502c5d77e7b8a457be0dd3af42f6f6e033f6 100644 --- a/ajax/get.php +++ b/ajax/get.php @@ -22,7 +22,7 @@ function ($id) { $attributes = $Discount->getAttributes(); /* @var $Discount Discount */ - $attributes['title'] = $Discount->getTitle(); + $attributes['title'] = method_exists($Discount, 'getTitle') ? $Discount->getTitle() : ''; return $attributes; }, diff --git a/ajax/search.php b/ajax/search.php index 0dfdbc15dbc5e7e7c0dfa2187107fe0f6858f075..f1a17067d9855d46dac9a34e90ba3a370e77c2fe 100644 --- a/ajax/search.php +++ b/ajax/search.php @@ -67,7 +67,7 @@ function ($fields, $params) { } usort($result, function ($a, $b) { - return $a['text'] > $b['text']; + return strcmp($a['text'], $b['text']); }); return $result; diff --git a/ajax/toggle.php b/ajax/toggle.php index 2ff6bc5c79523e9761dabcdca9a65cfdd62fbba0..8e318cba49f59c5eee78e76479b6c0acc2bb06e7 100644 --- a/ajax/toggle.php +++ b/ajax/toggle.php @@ -19,6 +19,10 @@ function ($discountId) { $Discount = $Handler->getChild($discountId); /* @var $Discount Discount */ + if (!method_exists($Discount, 'isActive')) { + return false; + } + if ($Discount->isActive()) { $Discount->setAttribute('active', 0); } else { diff --git a/captainhook.json b/captainhook.json new file mode 100644 index 0000000000000000000000000000000000000000..3702e1a358868bedd5ff4c7eae40bb1abb589267 --- /dev/null +++ b/captainhook.json @@ -0,0 +1,13 @@ +{ + "pre-commit": { + "enabled": true, + "actions": [ + { + "action": "\\CaptainHook\\App\\Hook\\PHP\\Action\\Linting" + }, + { + "action": "composer test" + } + ] + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 98b6c3386d1253df2994068a2b0ee96b00f0b2e1..46d2b55e07a85450f5a2cf611c6e16b2a3d2b443 100644 --- a/composer.json +++ b/composer.json @@ -29,5 +29,47 @@ "QUI\\ERP\\Discount\\": "src/QUI/ERP/Discount", "QUITests\\ERP\\Discount\\": "tests/QUITests/ERP/Discount" } + }, + "scripts": { + "test": [ + "@dev:lint", + "@dev:phpunit" + ], + "dev:phpunit": "./tools/phpunit", + "dev:lint": [ + "@dev:lint:phpstan", + "@dev:lint:style" + ], + "dev:lint:phpstan": "./tools/phpstan", + "dev:lint:style": "./tools/phpcs", + "dev:lint:style:fix": "./tools/phpcbf", + "dev:init": [ + "@dev:init:check-requirements", + "@dev:init:tools", + "@dev:init:git-hooks" + ], + "dev:init:check-requirements": [ + "which composer > /dev/null || (echo 'Error: composer has to be globally installed'; exit 1)", + "which phive > /dev/null || (echo 'Error: PHIVE has to be globally installed'; exit 1)" + ], + "dev:init:tools": "phive install --temporary", + "dev:init:git-hooks": "./tools/captainhook install --only-enabled --force" + }, + "scripts-aliases": { + "test": [ + "dev:test" + ] + }, + "scripts-descriptions": { + "test": "Runs linting, static analysis, and unit tests.", + "dev:phpunit": "Run PHPUnit test suites", + "dev:lint": "Run PHPStan and code style check", + "dev:lint:phpstan": "Run PHPStan", + "dev:lint:style": "Run code style check (PHP_CodeSniffer)", + "dev:lint:style:fix": "Try to fix code style errors automatically", + "dev:init": "Initialize the developer tooling (tools and git hooks)", + "dev:init:check-requirements": "Check if the necessary requirements are met", + "dev:init:tools": "Install all developer tools (requires PHIVE)", + "dev:init:git-hooks": "Install all git hooks (may require tools to be installed)" } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..d48084fbdc86a2dbfb78dd89ab2e0b0ff2322ba7 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<ruleset> + <!-- Use PSR-12 ruleset --> + <rule ref="PSR12"/> + + <!-- Only scan *.php files --> + <arg name="extensions" value="php"/> + + <!-- Ignore warnings --> + <arg name="warning-severity" value="0"/> + + <!-- Process 64 (or number of CPU cores) files in parallel --> + <arg name="parallel" value="64"/> + + <!-- Output relative file paths, by setting the current folder as the basepath --> + <arg name="basepath" value="."/> + + <!-- Show colored output --> + <arg name="colors"/> + + <!-- Scan everything in the current folder --> + <file>.</file> +</ruleset> diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1339890a6e72c75d6ae49a176d1046f0c370282f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -0,0 +1,2 @@ +parameters: + ignoreErrors: \ No newline at end of file diff --git a/phpstan.dist.neon b/phpstan.dist.neon index a545a041a3c5b0c758d99bf8c1c49b2a83252eaa..56d8beab0e0ae895340157b07790b8ad830f8d48 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -2,9 +2,32 @@ includes: - phpstan-baseline.neon parameters: - level: 1 + level: 5 paths: - src - ajax bootstrapFiles: - - tests/phpstan-bootstrap.php \ No newline at end of file + - tests/phpstan-bootstrap.php + treatPhpDocTypesAsCertain: false + customRulesetUsed: true +services: + - + class: \PHPStan\Rules\Properties\TypesAssignedToPropertiesRule + tags: + - phpstan.rules.rule + - + class: \PHPStan\Rules\Functions\ArrowFunctionReturnTypeRule + tags: + - phpstan.rules.rule + - + class: \PHPStan\Rules\Functions\ClosureReturnTypeRule + tags: + - phpstan.rules.rule + - + class: \PHPStan\Rules\Functions\ReturnTypeRule + tags: + - phpstan.rules.rule + - + class: \PHPStan\Rules\Methods\ReturnTypeRule + tags: + - phpstan.rules.rule diff --git a/phpunit.dist.xml b/phpunit.dist.xml new file mode 100644 index 0000000000000000000000000000000000000000..f6c7becf0c12757beb871a9333e2d81e02aa7cae --- /dev/null +++ b/phpunit.dist.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit bootstrap="tests/phpunit-bootstrap.php"> + <testsuites> + <testsuite name="Tests"> + <directory>tests/</directory> + </testsuite> + </testsuites> +</phpunit> diff --git a/src/QUI/ERP/Discount/Discount.php b/src/QUI/ERP/Discount/Discount.php index 399ae32bf3d5f0444e3467c7b50e899085505a57..5ea7ba76f8c4e9fccd36424af88e3656c8032f84 100644 --- a/src/QUI/ERP/Discount/Discount.php +++ b/src/QUI/ERP/Discount/Discount.php @@ -248,7 +248,7 @@ public function setAttribute(string $name, mixed $value): void * @param null|QUI\Locale $Locale - optional, locale object * @return string */ - public function getTitle(QUI\Locale $Locale = null): string + public function getTitle(null | QUI\Locale $Locale = null): string { if (!$Locale) { $Locale = QUI::getLocale(); @@ -284,7 +284,7 @@ public function canCombinedWith(Discount $Discount): bool return false; } - $combine = implode($combine, ','); + $combine = implode(',', $combine); if (in_array($Discount->getId(), (array)$combine)) { return true; @@ -437,15 +437,23 @@ public function canUsedWith(QUI\ERP\Products\Interfaces\ProductInterface $Produc } /** - * @param OrderInterface $Order + * @param QUI\ERP\ErpEntityInterface $Order * @return bool */ - public function canUsedInOrder(OrderInterface $Order): bool + public function canUsedInOrder(QUI\ERP\ErpEntityInterface $Order): bool { if ($this->isActive() === false) { return false; } + if (!interface_exists('QUI\ERP\Order\OrderInterface')) { + return false; + } + + if (!($Order instanceof QUI\ERP\Order\OrderInterface)) { + return false; + } + $Articles = $Order->getArticles(); foreach ($Articles as $Article) { @@ -521,7 +529,7 @@ public function verifyUser(User $User): void public function toPriceFactor( $Locale = null, $Customer = null - ): QUI\ERP\Products\Interfaces\PriceFactorInterface|QUI\ERP\Products\Interfaces\PriceFactorWithVatInterface|QUI\ERP\Products\Utils\PriceFactor { + ): QUI\ERP\Products\Interfaces\PriceFactorInterface | QUI\ERP\Products\Interfaces\PriceFactorWithVatInterface | QUI\ERP\Products\Utils\PriceFactor { switch ($this->getAttribute('discount_type')) { case QUI\ERP\Accounting\Calc::CALCULATION_PERCENTAGE: $calculation = QUI\ERP\Accounting\Calc::CALCULATION_PERCENTAGE; diff --git a/src/QUI/ERP/Discount/PriceFactor.php b/src/QUI/ERP/Discount/PriceFactor.php index c18cdb2bc519e1891f46b97ce27636445e3d91fe..12d57d88c919757ed92051840f75b4320a8a798b 100644 --- a/src/QUI/ERP/Discount/PriceFactor.php +++ b/src/QUI/ERP/Discount/PriceFactor.php @@ -47,7 +47,7 @@ public function getVatType(): QUI\ERP\Tax\TaxType return QUI\ERP\Tax\Utils::getShopTaxType(); } - $standardTax = explode(':', $this->vat); + $standardTax = explode(':', (string)$this->vat); if (!isset($standardTax[1])) { return QUI\ERP\Tax\Utils::getShopTaxType(); diff --git a/tests/phpunit-bootstrap.php b/tests/phpunit-bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..eca92fd67bed8ae4ec424ed82d300119d792f042 --- /dev/null +++ b/tests/phpunit-bootstrap.php @@ -0,0 +1,11 @@ +<?php + +if (!defined('QUIQQER_SYSTEM')) { + define('QUIQQER_SYSTEM', true); +} + +if (!defined('QUIQQER_AJAX')) { + define('QUIQQER_AJAX', true); +} + +require_once __DIR__ . '/../../../../bootstrap.php';