341 lines
13 KiB
PHP
341 lines
13 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* For the full copyright and license information, please view the
|
||
|
|
* docs/licenses/LICENSE.txt file that was distributed with this source code.
|
||
|
|
*/
|
||
|
|
|
||
|
|
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderPresenter;
|
||
|
|
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
|
||
|
|
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface;
|
||
|
|
use PrestaShop\PrestaShop\Core\Security\PasswordPolicyConfiguration;
|
||
|
|
use ZxcvbnPhp\Zxcvbn;
|
||
|
|
|
||
|
|
class OrderConfirmationControllerCore extends FrontController
|
||
|
|
{
|
||
|
|
/** @var bool */
|
||
|
|
public $ssl = true;
|
||
|
|
/** @var string */
|
||
|
|
public $php_self = 'order-confirmation';
|
||
|
|
/** @var int Cart ID */
|
||
|
|
public $id_cart;
|
||
|
|
public $id_module;
|
||
|
|
public $id_order;
|
||
|
|
public $secure_key;
|
||
|
|
|
||
|
|
/** @var Order Order object we found by cart ID */
|
||
|
|
protected $order;
|
||
|
|
|
||
|
|
/** @var Customer Customer object related to the cart */
|
||
|
|
protected $customer;
|
||
|
|
public $reference; // Deprecated
|
||
|
|
public $order_presenter; // Deprecated
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize order confirmation controller.
|
||
|
|
*
|
||
|
|
* @see FrontController::init()
|
||
|
|
*/
|
||
|
|
public function init(): void
|
||
|
|
{
|
||
|
|
// Test below to prevent unnecessary logs from "parent::init()"
|
||
|
|
$this->id_cart = (int) Tools::getValue('id_cart', 0);
|
||
|
|
if (!empty($this->context->cookie->id_cart) && $this->context->cookie->id_cart == $this->id_cart) {
|
||
|
|
$cart = new Cart($this->id_cart);
|
||
|
|
if ($cart->orderExists()) {
|
||
|
|
unset($this->context->cookie->id_cart);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
parent::init();
|
||
|
|
|
||
|
|
/*
|
||
|
|
* There is a special case for free orders, when this page does more than just display
|
||
|
|
* the confirmation. It also creates the order if it is free. This is done if free_order
|
||
|
|
* parameter is passed to this page.
|
||
|
|
*
|
||
|
|
* After the order is created, we redirect to the same page without free_order parameter
|
||
|
|
* and display the confirmation as usual.
|
||
|
|
*/
|
||
|
|
if ((bool) Tools::getValue('free_order') === true) {
|
||
|
|
$this->checkFreeOrder();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Because of order splitting scenarios, we must get the data by id_cart parameter (not id_order),
|
||
|
|
* so we can display all orders made from this cart.
|
||
|
|
*
|
||
|
|
* It's not implemented yet, however, and probably won't be, because we are switching to a new
|
||
|
|
* logic of multiple shipments per order, which doesn't require splitting orders anymore.
|
||
|
|
*/
|
||
|
|
$this->id_order = Order::getIdByCartId((int) $this->id_cart);
|
||
|
|
if (empty($this->id_order)) {
|
||
|
|
Tools::redirect('pagenotfound');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now, load the order object and check validity
|
||
|
|
$this->order = new Order((int) $this->id_order);
|
||
|
|
if (!Validate::isLoadedObject($this->order)) {
|
||
|
|
Tools::redirect('pagenotfound');
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Now, to prevent users from seeing other customers' order confirmations, we are using
|
||
|
|
* a secure key mechanism. The confirmation link contains a unique key which is also saved
|
||
|
|
* in database when the order is created. If the key from the URL doesn't match the one
|
||
|
|
* in database, we redirect to "page not found".
|
||
|
|
*/
|
||
|
|
$this->secure_key = Tools::getValue('key', false);
|
||
|
|
if (empty($this->secure_key) || $this->secure_key != $this->order->secure_key) {
|
||
|
|
Tools::redirect('pagenotfound');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Last step, initialize some other data
|
||
|
|
$this->id_module = $this->order->module == 'free_order' ? -1 : Module::getModuleIdByName($this->order->module);
|
||
|
|
|
||
|
|
// This data is kept only for backward compatibility purposes
|
||
|
|
$this->reference = (string) $this->order->reference;
|
||
|
|
|
||
|
|
// If checks passed, initialize customer, we will need him anyway
|
||
|
|
$this->customer = new Customer((int) $this->order->id_customer);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Logic after submitting forms
|
||
|
|
*
|
||
|
|
* @see FrontController::postProcess()
|
||
|
|
*/
|
||
|
|
public function postProcess(): void
|
||
|
|
{
|
||
|
|
if (Tools::isSubmit('submitTransformGuestToCustomer')) {
|
||
|
|
// Only variable we need is the password
|
||
|
|
// There is no need to check other variables, because hacker would be kicked out in init(), if he tried to convert another customer
|
||
|
|
$password = Tools::getValue('password');
|
||
|
|
|
||
|
|
if (empty($password)) {
|
||
|
|
$this->errors[] = $this->trans(
|
||
|
|
'Enter a password to transform your guest account into a customer account.',
|
||
|
|
[],
|
||
|
|
'Shop.Forms.Help'
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
if (Validate::isAcceptablePasswordLength($password) === false) {
|
||
|
|
$this->errors[] = $this->translator->trans(
|
||
|
|
'Password must be between %d and %d characters long',
|
||
|
|
[
|
||
|
|
Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_LENGTH),
|
||
|
|
Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MAXIMUM_LENGTH),
|
||
|
|
],
|
||
|
|
'Shop.Notifications.Error'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (Validate::isAcceptablePasswordScore($password) === false) {
|
||
|
|
$wordingsForScore = [
|
||
|
|
$this->translator->trans('Very weak', [], 'Shop.Theme.Global'),
|
||
|
|
$this->translator->trans('Weak', [], 'Shop.Theme.Global'),
|
||
|
|
$this->translator->trans('Average', [], 'Shop.Theme.Global'),
|
||
|
|
$this->translator->trans('Strong', [], 'Shop.Theme.Global'),
|
||
|
|
$this->translator->trans('Very strong', [], 'Shop.Theme.Global'),
|
||
|
|
];
|
||
|
|
$globalErrorMessage = $this->translator->trans(
|
||
|
|
'The minimum score must be: %s',
|
||
|
|
[
|
||
|
|
$wordingsForScore[(int) Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_SCORE)],
|
||
|
|
],
|
||
|
|
'Shop.Notifications.Error'
|
||
|
|
);
|
||
|
|
if ($this->context->shop->theme->get('global_settings.new_password_policy_feature') !== true) {
|
||
|
|
$zxcvbn = new Zxcvbn();
|
||
|
|
$result = $zxcvbn->passwordStrength($password);
|
||
|
|
if (!empty($result['feedback']['warning'])) {
|
||
|
|
$this->errors[] = $this->translator->trans(
|
||
|
|
$result['feedback']['warning'], [], 'Shop.Theme.Global'
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
$this->errors[] = $globalErrorMessage;
|
||
|
|
}
|
||
|
|
foreach ($result['feedback']['suggestions'] as $suggestion) {
|
||
|
|
$this->errors[] = $this->translator->trans($suggestion, [], 'Shop.Theme.Global');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$this->errors[] = $globalErrorMessage;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!empty($this->errors)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Prevent error
|
||
|
|
// A) either on page refresh
|
||
|
|
// B) if we already transformed him in other window or through backoffice
|
||
|
|
if ($this->customer->is_guest == 0) {
|
||
|
|
$this->errors[] = $this->trans(
|
||
|
|
'A customer account has already been created from this guest account. Please sign in.',
|
||
|
|
[],
|
||
|
|
'Shop.Notifications.Error'
|
||
|
|
);
|
||
|
|
// Check if a different customer with the same email was not already created in a different window or through backoffice
|
||
|
|
} elseif (Customer::customerExists($this->customer->email)) {
|
||
|
|
$this->errors[] = $this->trans(
|
||
|
|
'You can\'t transform your account into a customer account, because a registered customer with the same email already exists.',
|
||
|
|
[],
|
||
|
|
'Shop.Notifications.Error'
|
||
|
|
);
|
||
|
|
// Attempt to convert the customer
|
||
|
|
} elseif ($this->customer->transformToCustomer($this->context->language->id, $password)) {
|
||
|
|
$this->success[] = $this->trans(
|
||
|
|
'Your guest account has been successfully transformed into a customer account. You can now log in as a registered shopper.',
|
||
|
|
[],
|
||
|
|
'Shop.Notifications.Success'
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
$this->errors[] = $this->trans(
|
||
|
|
'An unexpected error occurred while creating your account.',
|
||
|
|
[],
|
||
|
|
'Shop.Notifications.Error'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Assign template vars related to page content.
|
||
|
|
*
|
||
|
|
* @see FrontController::initContent()
|
||
|
|
*/
|
||
|
|
public function initContent(): void
|
||
|
|
{
|
||
|
|
parent::initContent();
|
||
|
|
|
||
|
|
/** @var FeatureFlagStateCheckerInterface $featureFlagManager */
|
||
|
|
$featureFlagManager = $this->get(FeatureFlagStateCheckerInterface::class);
|
||
|
|
|
||
|
|
$this->context->smarty->assign([
|
||
|
|
'HOOK_ORDER_CONFIRMATION' => $this->displayOrderConfirmation($this->order),
|
||
|
|
'HOOK_PAYMENT_RETURN' => $this->displayPaymentReturn($this->order),
|
||
|
|
'order' => (new OrderPresenter())->present($this->order),
|
||
|
|
'order_customer' => $this->objectPresenter->present($this->customer),
|
||
|
|
'is_multishipment_enabled' => $featureFlagManager->isEnabled(FeatureFlagSettings::FEATURE_FLAG_IMPROVED_SHIPMENT),
|
||
|
|
'registered_customer_exists' => Customer::customerExists($this->customer->email),
|
||
|
|
]);
|
||
|
|
$this->setTemplate('checkout/order-confirmation');
|
||
|
|
|
||
|
|
// If logged in guest we clear the cookie for security reasons
|
||
|
|
if ($this->context->customer->is_guest) {
|
||
|
|
$this->context->customer->mylogout();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute the hook displayPaymentReturn. This hook should be used to display payment
|
||
|
|
* information on the order confirmation page. Payment status, instructions, QR code etc.
|
||
|
|
*/
|
||
|
|
public function displayPaymentReturn(Order $order)
|
||
|
|
{
|
||
|
|
// Check if we have a sensible module ID. Free orders have -1 as module ID
|
||
|
|
if (!Validate::isUnsignedId($this->id_module)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Hook called only for the module concerned
|
||
|
|
return Hook::exec('displayPaymentReturn', ['order' => $order], $this->id_module);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute the hook displayOrderConfirmation.
|
||
|
|
*/
|
||
|
|
public function displayOrderConfirmation(Order $order)
|
||
|
|
{
|
||
|
|
return Hook::exec('displayOrderConfirmation', ['order' => $order]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if an order is free and create it. After creation, we redirect to the same page
|
||
|
|
* which will display the order confirmation as usual.
|
||
|
|
*/
|
||
|
|
protected function checkFreeOrder(): void
|
||
|
|
{
|
||
|
|
/*
|
||
|
|
* Verify if this is not a faulty or duplicate call. If an order
|
||
|
|
* already exists for this cart, we do not create another one.
|
||
|
|
*/
|
||
|
|
if (!empty(Order::getIdByCartId((int) $this->id_cart))) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$cart = $this->context->cart;
|
||
|
|
if ($cart->id_customer == 0 || $cart->id_address_delivery == 0 || $cart->id_address_invoice == 0) {
|
||
|
|
Tools::redirect($this->context->link->getPageLink('order'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$customer = new Customer($cart->id_customer);
|
||
|
|
if (!Validate::isLoadedObject($customer)) {
|
||
|
|
Tools::redirect($this->context->link->getPageLink('order'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$total = (float) $cart->getOrderTotal(true, Cart::BOTH);
|
||
|
|
if ($total > 0) {
|
||
|
|
Tools::redirect($this->context->link->getPageLink('order'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$order = new PaymentFree();
|
||
|
|
$order->validateOrder(
|
||
|
|
$cart->id,
|
||
|
|
(int) Configuration::get('PS_OS_PAYMENT'),
|
||
|
|
0,
|
||
|
|
$this->trans('Free order', [], 'Admin.Orderscustomers.Feature'),
|
||
|
|
null,
|
||
|
|
[],
|
||
|
|
null,
|
||
|
|
false,
|
||
|
|
$cart->secure_key
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Redirect back to this page to display the order confirmation.
|
||
|
|
* Note the id_module parameter with value -1, it's only kept for
|
||
|
|
* backward compatibility, but not used anymore.
|
||
|
|
*/
|
||
|
|
Tools::redirect($this->context->link->getPageLink(
|
||
|
|
'order-confirmation',
|
||
|
|
null,
|
||
|
|
null,
|
||
|
|
[
|
||
|
|
'id_cart' => (int) $cart->id,
|
||
|
|
'id_module' => '-1',
|
||
|
|
'id_order' => (int) $order->currentOrder,
|
||
|
|
'key' => $cart->secure_key,
|
||
|
|
]
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getBreadcrumbLinks(): array
|
||
|
|
{
|
||
|
|
$breadcrumb = parent::getBreadcrumbLinks();
|
||
|
|
|
||
|
|
$breadcrumb['links'][] = [
|
||
|
|
'title' => $this->trans('Order confirmation', [], 'Shop.Theme.Checkout'),
|
||
|
|
'url' => $this->context->link->getPageLink('order-confirmation'),
|
||
|
|
];
|
||
|
|
|
||
|
|
return $breadcrumb;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return Order
|
||
|
|
*/
|
||
|
|
public function getOrder(): Order
|
||
|
|
{
|
||
|
|
return $this->order;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return Customer
|
||
|
|
*/
|
||
|
|
public function getCustomer(): Customer
|
||
|
|
{
|
||
|
|
return $this->customer;
|
||
|
|
}
|
||
|
|
}
|