Skip to content

Commit

Permalink
Merge pull request PrestaShop#38111 from jolelievre/create-discount-ui
Browse files Browse the repository at this point in the history
Refactor Discount creation domain services and allow creation without names
  • Loading branch information
jolelievre authored Feb 24, 2025
2 parents 15c612c + 8d3da9f commit 1332054
Show file tree
Hide file tree
Showing 18 changed files with 421 additions and 90 deletions.
3 changes: 2 additions & 1 deletion classes/CartRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ class CartRuleCore extends ObjectModel
'name' => [
'type' => self::TYPE_HTML,
'lang' => true,
'required' => true,
'required' => false,
'size' => CartRuleSettings::NAME_MAX_LENGTH,
'validate' => 'defaultLanguageRequiredWhenActive',
],
],
];
Expand Down
98 changes: 62 additions & 36 deletions classes/ObjectModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,16 @@ public function add($auto_date = true, $null_values = false)
$this->id_shop_default = (in_array(Configuration::get('PS_SHOP_DEFAULT'), $id_shop_list) == true) ? Configuration::get('PS_SHOP_DEFAULT') : min($id_shop_list);
}

if (!$result = Db::getInstance()->insert($this->def['table'], $this->getFields(), $null_values)) {
// We get the fields before any insertion, because the validation is called inside those methods and in case of invalid value nothing
// should be inserted at all
$entityFields = $this->getFields();
if (!empty($this->def['multilang'])) {
$entityMultiLangFields = $this->getFieldsLang();
} else {
$entityMultiLangFields = [];
}

if (!$result = Db::getInstance()->insert($this->def['table'], $entityFields, $null_values)) {
return false;
}

Expand All @@ -565,27 +574,24 @@ public function add($auto_date = true, $null_values = false)
}

// Database insertion for multilingual fields related to the object
if (!empty($this->def['multilang'])) {
$fields = $this->getFieldsLang();
if ($fields && is_array($fields)) {
$shops = Shop::getCompleteListOfShopsID();
$asso = Shop::getAssoTable($this->def['table'] . '_lang');
foreach ($fields as $field) {
foreach (array_keys($field) as $key) {
if (!Validate::isTableOrIdentifier($key)) {
throw new PrestaShopException('key ' . $key . ' is not table or identifier');
}
if (!empty($entityMultiLangFields)) {
$shops = Shop::getCompleteListOfShopsID();
$asso = Shop::getAssoTable($this->def['table'] . '_lang');
foreach ($entityMultiLangFields as $field) {
foreach (array_keys($field) as $key) {
if (!Validate::isTableOrIdentifier($key)) {
throw new PrestaShopException('key ' . $key . ' is not table or identifier');
}
$field[$this->def['primary']] = (int) $this->id;
}
$field[$this->def['primary']] = (int) $this->id;

if ($asso !== false && $asso['type'] == 'fk_shop') {
foreach ($shops as $id_shop) {
$field['id_shop'] = (int) $id_shop;
$result &= Db::getInstance()->insert($this->def['table'] . '_lang', $field);
}
} else {
if ($asso !== false && $asso['type'] == 'fk_shop') {
foreach ($shops as $id_shop) {
$field['id_shop'] = (int) $id_shop;
$result &= Db::getInstance()->insert($this->def['table'] . '_lang', $field);
}
} else {
$result &= Db::getInstance()->insert($this->def['table'] . '_lang', $field);
}
}
}
Expand Down Expand Up @@ -721,8 +727,17 @@ public function update($null_values = false)
/* @phpstan-ignore-next-line */
$this->id_shop_default = (in_array(Configuration::get('PS_SHOP_DEFAULT'), $id_shop_list) == true) ? Configuration::get('PS_SHOP_DEFAULT') : min($id_shop_list);
}

// Database update
$fieldsToUpdate = $this->getFields();
// Multi lang fields must be fetched before any updates is done, because if they are invalid the whole update should be blocked
// and validation process is performed in getFieldsLang
if (isset($this->def['multilang']) && $this->def['multilang']) {
$multiLangFieldsToUpdate = $this->getFieldsLang();
} else {
$multiLangFieldsToUpdate = [];
}

if (!$result = Db::getInstance()->update($this->def['table'], $fieldsToUpdate, '`' . pSQL($this->def['primary']) . '` = ' . (int) $this->id, 0, $null_values)) {
return false;
}
Expand Down Expand Up @@ -760,7 +775,7 @@ public function update($null_values = false)
}

// Database update for multilingual fields related to the object
if (isset($this->def['multilang']) && $this->def['multilang']) {
if (!empty($multiLangFieldsToUpdate)) {
$multiLangFieldsToUpdate = $this->getFieldsLang();
if (is_array($multiLangFieldsToUpdate)) {
foreach ($multiLangFieldsToUpdate as $field) {
Expand Down Expand Up @@ -1034,16 +1049,11 @@ public function validateFieldsLang($die = true, $errorReturn = false)
public function validateField($field, $value, $id_lang = null, $skip = [], $human_errors = false)
{
static $ps_lang_default = null;
static $ps_allow_html_iframe = null;

if ($ps_lang_default === null) {
$ps_lang_default = Configuration::get('PS_LANG_DEFAULT');
}

if ($ps_allow_html_iframe === null) {
$ps_allow_html_iframe = (int) Configuration::get('PS_ALLOW_HTML_IFRAME');
}

$this->cacheFieldsRequiredDatabase();
$data = $this->def['fields'][$field];

Expand Down Expand Up @@ -1128,17 +1138,10 @@ public function validateField($field, $value, $id_lang = null, $skip = [], $huma
throw new PrestaShopException($this->trans('Validation function not found: %s.', [$data['validate']], 'Admin.Notifications.Error'));
}

if (!empty($value)) {
$res = true;
if (Tools::strtolower($data['validate']) === 'iscleanhtml') {
if (!call_user_func(['Validate', $data['validate']], $value, $ps_allow_html_iframe)) {
$res = false;
}
} else {
if (!call_user_func(['Validate', $data['validate']], $value)) {
$res = false;
}
}
// isRequiredWhenActive and defaultLanguageRequiredWhenActive validators must be called especially when the value is empty
$isEmptyValidationMethod = Tools::strtolower($data['validate']) === 'isrequiredwhenactive' || Tools::strtolower($data['validate']) === 'defaultlanguagerequiredwhenactive';
if (!empty($value) || $isEmptyValidationMethod) {
$res = $this->callValidateMethod($data['validate'], $value, isset($id_lang) ? (int) $id_lang : null);
if (!$res) {
if ($human_errors) {
return $this->trans('The %s field is invalid.', [$this->displayFieldName($field, get_class($this))], 'Admin.Notifications.Error');
Expand All @@ -1152,6 +1155,29 @@ public function validateField($field, $value, $id_lang = null, $skip = [], $huma
return true;
}

protected function callValidateMethod(string $validateMethod, mixed $value, ?int $langId = null): bool
{
static $ps_allow_html_iframe = null;

if (!method_exists('Validate', $validateMethod)) {
throw new PrestaShopException($this->trans('Validation function not found: %s.', [$validateMethod], 'Admin.Notifications.Error'));
}

if (Tools::strtolower($validateMethod) === 'iscleanhtml') {
if ($ps_allow_html_iframe === null) {
$ps_allow_html_iframe = (int) Configuration::get('PS_ALLOW_HTML_IFRAME');
}

return Validate::isCleanHtml($value, $ps_allow_html_iframe);
} elseif (Tools::strtolower($validateMethod) === 'isrequiredwhenactive') {
return Validate::isRequiredWhenActive($value, $this);
} elseif (Tools::strtolower($validateMethod) === 'defaultlanguagerequiredwhenactive') {
return Validate::defaultLanguageRequiredWhenActive($value, $langId, $this);
}

return call_user_func(['Validate', $validateMethod], $value);
}

/**
* Returns the human readable, translated field name.
*
Expand Down Expand Up @@ -1221,7 +1247,7 @@ public function validateController($htmlentities = true)
// Checking for fields validity
// Hack for postcode required for country which does not have postcodes
if (!empty($value) || $value === '0' || ($field == 'postcode' && $value == '0')) {
if (isset($data['validate']) && (!call_user_func('Validate::' . $data['validate'], $value) && (!empty($value) || $data['required']))) {
if (isset($data['validate']) && (!$this->callValidateMethod($data['validate'], $value) && (!empty($value) || $data['required']))) {
$errors[$field] = $this->trans(
'%s is invalid.',
[
Expand Down
19 changes: 19 additions & 0 deletions classes/Validate.php
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,25 @@ public static function isThemeName($theme_name)
return (bool) preg_match('/^[\w-]{3,255}$/u', $theme_name);
}

public static function isRequiredWhenActive($value, ObjectModelCore $object): bool
{
$isActive = property_exists($object, 'active') ? $object->active : true;

return !$isActive || !empty($value);
}

public static function defaultLanguageRequiredWhenActive($value, ?int $langId, ObjectModelCore $object): bool
{
static $defaultLangId = null;
if (null === $defaultLangId) {
$defaultLangId = (int) Configuration::get('PS_LANG_DEFAULT');
}

$isActive = property_exists($object, 'active') ? $object->active : true;

return !$isActive || !empty($value) || $langId !== $defaultLangId;
}

/**
* Check if enable_insecure_rsh exists in
* this PHP version otherwise disable the
Expand Down
9 changes: 8 additions & 1 deletion src/Adapter/AbstractObjectModelValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

namespace PrestaShop\PrestaShop\Adapter;

use Configuration;
use ObjectModel;
use PrestaShop\PrestaShop\Core\Exception\CoreException;
use PrestaShopException;
Expand Down Expand Up @@ -78,9 +79,15 @@ protected function validateObjectModelProperty(ObjectModel $objectModel, string
*/
protected function validateObjectModelLocalizedProperty(ObjectModel $objectModel, string $propertyName, string $exceptionClass, int $errorCode = 0)
{
$localizedValues = $objectModel->{$propertyName};
$localizedValues = $objectModel->{$propertyName} ?? [];

try {
$defaultLang = (int) Configuration::get('PS_LANG_DEFAULT');
if (!isset($localizedValues[$defaultLang])) {
// The value for the default must always be set, so we put an empty string if it does not exist
$localizedValues[$defaultLang] = '';
}

foreach ($localizedValues as $langId => $value) {
if (true !== $objectModel->validateField($propertyName, $value, $langId)) {
throw new $exceptionClass(
Expand Down
4 changes: 4 additions & 0 deletions src/Adapter/CartRule/Repository/CartRuleRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
use PrestaShop\PrestaShop\Core\Exception\InvalidArgumentException;
use PrestaShop\PrestaShop\Core\Repository\AbstractObjectModelRepository;

/**
* @deprecated in favor of DiscountRepository this one is kept until we probably migrate all to the new Discount domain
* and clean this namespace
*/
class CartRuleRepository extends AbstractObjectModelRepository
{
public function __construct(
Expand Down
4 changes: 4 additions & 0 deletions src/Adapter/CartRule/Validate/CartRuleValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
use PrestaShop\PrestaShop\Core\Exception\CoreException;
use PrestaShopException;

/**
* @deprecated in favor of DiscountValidator this one is kept until we probably migrate all to the new Discount domain
* and clean this namespace
*/
class CartRuleValidator extends AbstractObjectModelValidator
{
public function validate(CartRule $cartRule): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
namespace PrestaShop\PrestaShop\Adapter\Discount\CommandHandler;

use PrestaShop\PrestaShop\Adapter\CartRule\CartRuleBuilder;
use PrestaShop\PrestaShop\Adapter\CartRule\Repository\CartRuleRepository;
use PrestaShop\PrestaShop\Adapter\Discount\Repository\DiscountRepository;
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler;
use PrestaShop\PrestaShop\Core\Domain\Discount\Command\AddFreeShippingDiscountCommand;
use PrestaShop\PrestaShop\Core\Domain\Discount\CommandHandler\AddFreeShippingDiscountHandlerInterface;
Expand All @@ -37,15 +37,15 @@
class AddFreeShippingDiscountHandler implements AddFreeShippingDiscountHandlerInterface
{
public function __construct(
private readonly CartRuleRepository $cartRuleRepository,
private readonly DiscountRepository $discountRepository,
private readonly CartRuleBuilder $cartRuleBuilder
) {
}

public function handle(AddFreeShippingDiscountCommand $command): DiscountId
{
$BuiltCartRule = $this->cartRuleBuilder->build($command);
$discount = $this->cartRuleRepository->add($BuiltCartRule);
$discount = $this->discountRepository->add($BuiltCartRule);

return new DiscountId((int) $discount->id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@

use DateTimeImmutable;
use Exception;
use PrestaShop\PrestaShop\Adapter\CartRule\Repository\CartRuleRepository;
use PrestaShop\PrestaShop\Adapter\Discount\Repository\DiscountRepository;
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsQueryHandler;
use PrestaShop\PrestaShop\Core\Domain\CartRule\ValueObject\CartRuleId;
use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountForEditing;
use PrestaShop\PrestaShop\Core\Domain\Discount\QueryHandler\GetDiscountForEditingHandlerInterface;
use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountForEditing;
Expand All @@ -39,7 +38,7 @@
class GetDiscountForEditingHandler implements GetDiscountForEditingHandlerInterface
{
public function __construct(
protected readonly CartRuleRepository $cartRuleRepository
protected readonly DiscountRepository $discountRepository
) {
}

Expand All @@ -48,8 +47,7 @@ public function __construct(
*/
public function handle(GetDiscountForEditing $query): DiscountForEditing
{
$cartRuleId = new CartRuleId($query->discountId->getValue());
$cartRule = $this->cartRuleRepository->get($cartRuleId);
$cartRule = $this->discountRepository->get($query->discountId);

return new DiscountForEditing(
$query->discountId->getValue(),
Expand Down
68 changes: 68 additions & 0 deletions src/Adapter/Discount/Repository/DiscountRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/

namespace PrestaShop\PrestaShop\Adapter\Discount\Repository;

use CartRule;
use Doctrine\DBAL\Connection;
use PrestaShop\PrestaShop\Adapter\Discount\Validate\DiscountValidator;
use PrestaShop\PrestaShop\Core\Domain\Discount\Exception\CannotAddDiscountException;
use PrestaShop\PrestaShop\Core\Domain\Discount\Exception\DiscountNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountId;
use PrestaShop\PrestaShop\Core\Repository\AbstractObjectModelRepository;

/**
* This repository is used for the new Discount domain, but it still relies on the legacy CartRule ObjectModel.
*/
class DiscountRepository extends AbstractObjectModelRepository
{
public function __construct(
protected readonly DiscountValidator $cartRuleValidator,
protected readonly Connection $connection,
protected readonly string $dbPrefix
) {
}

public function add(CartRule $cartRule): CartRule
{
$this->cartRuleValidator->validate($cartRule);
$this->addObjectModel($cartRule, CannotAddDiscountException::class);

return $cartRule;
}

public function get(DiscountId $discountId): CartRule
{
/** @var CartRule $cartRule */
$cartRule = $this->getObjectModel(
$discountId->getValue(),
CartRule::class,
DiscountNotFoundException::class
);

return $cartRule;
}
}
Loading

0 comments on commit 1332054

Please sign in to comment.