Newer
Older
<?php
/**
* This file contains QUI\ERP\Order\OrderProcess
*/
namespace QUI\ERP\Order;
use QUI;
use QUI\ERP\Order\Controls\AbstractOrderingStep;
use QUI\ERP\Order\Controls\OrderProcess\Processing;
use QUI\ERP\Order\OrderProcess\OrderProcessMessageHandlerInterface;
use QUI\ERP\Order\Controls\OrderProcess\Finish as FinishControl;
use QUI\Exception;
use function array_filter;
use function array_keys;
use function array_search;
use function array_values;
use function call_user_func;
use function class_implements;
use function count;
use function current;
use function dirname;
use function end;
use function floor;
use function get_class;
use function in_array;
use function is_a;
use function json_decode;
use function json_encode;
use function key;
use function method_exists;
use function reset;
use function trim;
* Class OrderingProcess
*
* This is the ordering process
* Coordinates the order process, (basket -> address -> delivery -> payment -> invoice)
* @package QUI\ERP\Order\Basket
*
* @event getBody [this]
* @event getBodyBegin [this]
* @event onSend [this]
* @event onSendBegin [this]
class OrderProcess extends QUI\Control
const MESSAGES_SESSION_KEY = 'quiqqer_order_orderprocess_messages';
* @var AbstractOrder|null
protected ?AbstractOrder $Order = null;
* @var Basket\Basket|null
protected ?Basket\Basket $Basket = null;
/**
* @var null|AbstractOrderProcessProvider
*/
protected ?AbstractOrderProcessProvider $ProcessingProvider = null;
/**
* List of order process steps
*
* @var array
*/
protected array $steps = [];
/**
* @var QUI\Events\Event
*/
public QUI\Events\Event $Events;
/**
* Basket constructor.
* @param array $attributes
* @throws QUI\Exception
public function __construct(array $attributes = [])
'Site' => false,
'data-qui' => 'package/quiqqer/order/bin/frontend/controls/OrderProcess',
'orderHash' => false,
'basket' => true, // import basket articles to the order, use the basket
if (isset($attributes['Order']) && $attributes['Order'] instanceof AbstractOrder) {
$this->Order = $attributes['Order'];
}
$this->addCSSFile(dirname(__FILE__) . '/Controls/OrderProcess.css');
$this->Events = new QUI\Events\Event();
$isNobody = QUI::getUsers()->isNobodyUser($User);
if ($isNobody) {

Henning Leutz
committed
// current step
$steps = $this->getSteps();

Henning Leutz
committed
$Order = $this->getOrder();
$customerUUID = $Order->getCustomer()->getUUID();
$userUUID = $User->getUUID();
if ($customerUUID !== $userUUID && !QUI::getUsers()->isSystemUser($User)) {
throw new QUI\Permissions\Exception([
'quiqqer/order',
'exception.no.permission.for.this.order'
]);
}
if ($Order->isSuccessful()) {
$this->setAttribute('orderHash', $Order->getUUID());
$this->setAttribute('step', $LastStep->getName());
$this->setAttribute('orderHash', $Order->getUUID());

Henning Leutz
committed
// basket into the order
$Basket = $this->getBasket();
if (!$this->getAttribute('orderHash') && $this->getAttribute('basket')) {

Henning Leutz
committed
$Basket->toOrder($Order);

Henning Leutz
committed
$this->setAttribute('basketId', $Basket->getId());
$this->setAttribute('orderHash', $Order->getUUID());
// set order currency
$UserCurrency = QUI\ERP\Defaults::getUserCurrency();
if ($UserCurrency && $UserCurrency->getCode() !== $Order->getCurrency()->getCode()) {
$Order->setCurrency($UserCurrency);
// order is successful, so no other step must be shown
if ($Order && $Order->isSuccessful()) {
$this->setAttribute('orderHash', $Order->getUUID());
if (!$step && isset($_REQUEST['step'])) {
$step = $_REQUEST['step'];
$this->setAttribute('step', $step);
}
if (!$step && isset($_REQUEST['current'])) {
$step = $_REQUEST['current'];
$this->setAttribute('step', $step);
}
if (!$step && isset($_REQUEST['current'])) {
$step = $_REQUEST['current'];
$this->setAttribute('step', $step);
}
if (isset($_GET['checkout']) && $_GET['checkout'] == 1) {
$this->setAttribute('step', $keys[1]);
$step = $keys[1];
}
// consider processing step - processing step is ok

Henning Leutz
committed
$Processing = $this->getProcessingStep();
$this->setAttribute('orderHash', $Order->getUUID());
if (!$step || !isset($steps[$step])) {
reset($steps);
$this->setAttribute('step', key($steps));

Henning Leutz
committed
QUI::getEvents()->fireEvent('orderProcess', [$this]);
}
/**
* Checks the submit status
* Must the previous step be saved?
* In this case, it is the step the user took when he clicked next.
* Or the user clicked a submit button in the step
*
* @throws QUI\Exception
{
if (!isset($_REQUEST['current'])) {
return;
}
$preStep = $_REQUEST['current'];
$PreStep = $this->getStepByName($preStep);
if (!$PreStep) {
return;
}
try {
$PreStep->save();
} catch (QUI\Exception $Exception) {

Henning Leutz
committed
* Check if the successful status is ok
protected function checkSuccessfulStatus(): void
{
try {
$Current = $this->getCurrentStep();
&& !($Current instanceof Controls\OrderProcess\Processing)
) {
return;
}
try {
$Payment = $this->getOrder()->getPayment();
if ($Payment && $Payment->isSuccessful($this->getOrder()->getUUID())) {
$this->getOrder()->setSuccessfulStatus();
}
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
}
}
* Send the order
$this->Events->fireEvent('sendBegin', [$this]);
QUI::getEvents()->fireEvent('onQuiqqerOrderProcessSendBegin', [$this]);
$providers = QUI\ERP\Order\Handler::getInstance()->getOrderProcessProvider();
// #locale quiqqer/order#158
if (class_exists('QUI\ERP\Products\Handler\Products')) {
QUI\ERP\Products\Handler\Products::setLocale(QUI::getLocale());
}
// check all previous steps
/* @var $Step AbstractOrderingStep */
continue;
}
$this->setAttribute('current', $Step->getName());
}
QUI::getEvents()->fireEvent('orderStart', [$this]);
// Gehe die verschiedenen Processing Provider durch
$failedPaymentProcedure = Settings::getInstance()->get('order', 'failedPaymentProcedure');
foreach ($providers as $Provider) {
/* @var $Provider AbstractOrderProcessProvider */
if ($status === AbstractOrderProcessProvider::PROCESSING_STATUS_PROCESSING) {
$this->ProcessingProvider = $Provider;
if ($failedPaymentProcedure !== 'execute') {
return;
}
}
if ($status === AbstractOrderProcessProvider::PROCESSING_STATUS_ABORT) {
continue;
QUI::getEvents()->fireEvent('onQuiqqerOrderProcessSendCreateOrder', [$this]);
// all runs fine
if ($OrderInProcess instanceof OrderInProcess) {
$Order = $OrderInProcess->createOrder();
$OrderInProcess->delete();
$this->Order = $Order;
} else {
$this->Order = $OrderInProcess;
}
$this->setAttribute('orderHash', $this->Order->getUUID());
$this->setAttribute('current', 'Finish');
$this->setAttribute('step', 'Finish');
// set all to successful
$this->cleanup();
$this->Events->fireEvent('send', [$this, $this->Order]);
QUI::getEvents()->fireEvent('onQuiqqerOrderProcessSend', [$this]);
/**
* Cleanup stuff, look if smth is not needed anymore
*/
{
// set all to successful
if (!$this->Order->isSuccessful()) {
return;
}

Henning Leutz
committed
// if temp order exist, and a normal order kill it

Henning Leutz
committed
$ProcessOrder = Handler::getInstance()->getOrderInProcessByHash(

Henning Leutz
committed
);
$Order = Handler::getInstance()->getOrderByHash(

Henning Leutz
committed
);
if ($Order instanceof Order) {
$ProcessOrder->delete();
}
$this->Order = $Order;
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
}
if ($this->Basket && method_exists($this->Basket, 'successful')) {
$this->Basket->successful();
$this->Basket->save();
} else {
try {
$Basket = Handler::getInstance()->getBasketByHash($this->Order->getUUID());
$Basket->successful();
$Basket->save();
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
}
}
}
* Execute the payable step
*
* @return bool|string
protected function executePayableStatus(): bool | string
$template = dirname(__FILE__) . '/Controls/OrderProcess.html';
$Engine = QUI::getTemplateManager()->getEngine();
try {
$this->send();
// processing step
// eq: payment gateway
if ($this->ProcessingProvider !== null) {

Henning Leutz
committed
$ProcessingStep = $this->getProcessingStep();
$ProcessingStep->setProcessingProvider($this->ProcessingProvider);
$ProcessingStep->setAttribute('Order', $this->getOrder());

Henning Leutz
committed
$this->setAttribute('step', $ProcessingStep->getName());
// shows payment changing, if allowed
$changePayment = false;

Henning Leutz
committed
if (Settings::getInstance()->get('paymentChangeable', $Payment->getId())) {
$changePayment = true;
}
'listWidth' => floor(100 / count($this->getSteps())),
'this' => $this,
'error' => false,
'next' => false,
'previous' => false,
'payableToOrder' => false,
'changePayment' => $changePayment,
'steps' => $this->getSteps(),
'CurrentStep' => $ProcessingStep,

Henning Leutz
committed
'currentStepContent' => QUI\ControlUtils::parse($ProcessingStep),
'Site' => $this->getSite(),
'Order' => $this->getOrder(),
'hash' => $this->getStepHash(),
'backToShopUrl' => $this->getBackToShopUrl()
return QUI\Output::getInstance()->parse($Engine->fetch($template));
'listWidth' => floor(100 / count($this->getSteps())),
'this' => $this,
'error' => false,
'next' => false,
'previous' => false,
'payableToOrder' => false,
'steps' => $this->getSteps(),
'CurrentStep' => $this->getCurrentStep(),

Henning Leutz
committed
'currentStepContent' => QUI\ControlUtils::parse($this->getCurrentStep()),
'Site' => $this->getSite(),
'Order' => $this->getOrder(),
'hash' => $this->getStepHash(),
'backToShopUrl' => $this->getBackToShopUrl()
return QUI\Output::getInstance()->parse($Engine->fetch($template));
}
return false;
}
/**

Henning Leutz
committed
* Return the html of the order process
*
$this->Events->fireEvent('getBodyBegin', [$this]);
QUI::getEvents()->fireEvent('quiqqerOrderOrderProcessGetBodyBegin', [$this]);
$this->setAttribute(
'data-qui-option-basketeditable',
$this->getAttribute('basketEditable') ? 1 : 0
);
$isNobody = QUI::getUsers()->isNobodyUser($User);
$template = dirname(__FILE__) . '/Controls/OrderProcess.html';
$Engine = QUI::getTemplateManager()->getEngine();
$guestOrderInstalled = QUI::getPackageManager()->isInstalled('quiqqer/order-guestorder');
$GuestOrder = null;
$nobodyIntroTitle = '';
$nobodyIntroDesc = '';
$Site = $this->getSite();
if (
class_exists('QUI\ERP\Order\Guest\GuestOrder')
&& class_exists('QUI\ERP\Order\Guest\Controls\GuestOrderButton')
&& $guestOrderInstalled
&& QUI\ERP\Order\Guest\GuestOrder::isActive()
) {
$GuestOrder = new QUI\ERP\Order\Guest\Controls\GuestOrderButton();
if ($Site->getAttribute('quiqqer.order.nobody.intro.title')) {
$nobodyIntroTitle = $Site->getAttribute('quiqqer.order.nobody.intro.title');
}
if ($Site->getAttribute('quiqqer.order.nobody.intro.desc')) {
$nobodyIntroDesc = $Site->getAttribute('quiqqer.order.nobody.intro.desc');
}
}
$Request = QUI::getRequest();
$url = $Request->getRequestUri();
$activeEntry = match (true) {
str_contains($url, '?open=login') => 'login',
str_contains($url, '?open=signup') => 'signup',
default => 'login'
};
if (
$guestOrderInstalled
&& class_exists('QUI\ERP\Order\Guest\GuestOrder')
&& str_contains($url, '?open=guest')
&& QUI\ERP\Order\Guest\GuestOrder::isActive()
) {
$activeEntry = 'guest';
}
'Registration' => new Controls\Checkout\Registration([
'autofill' => false
]),
'Login' => new Controls\Checkout\Login(),
'guestOrderInstalled' => $guestOrderInstalled,
'GuestOrder' => $GuestOrder,
'activeEntry' => $activeEntry,
'nobodyIntroTitle' => $nobodyIntroTitle,
'nobodyIntroDesc' => $nobodyIntroDesc
dirname(__FILE__) . '/Controls/OrderProcess.Nobody.html'
// check if processing step is needed
$processing = $this->checkProcessing();
QUI::getEvents()->fireEvent('quiqqerOrderProcessingStart', [$this]);
}
// standard procedure
$this->checkSuccessfulStatus();
// check if order is finished

Patrick Müller
committed
$Order = $this->getOrder();
if ($Order && $Order->isSuccessful()) {
if ($Order instanceof OrderInProcess && !$Order->getOrderId()) {
$this->send();
$Order = $this->Order;

Patrick Müller
committed
}
$this->setAttribute('step', $LastStep->getName());
$this->setAttribute('orderHash', $Order->getUUID());
return $this->renderFinish();
}
$Current = $this->getCurrentStep();
// check all previous steps
foreach ($steps as $name => $Step) {
if ($name === $Current->getName()) {
break;
}
/* @var $Step AbstractOrderingStep */
if ($Step->isValid() === false) {
$Current = $Step;
break;
}
}
$error = false;
$next = $this->getNextStepName($Current);
$previous = $this->getPreviousStepName();
$payableToOrder = false;
$Checkout = current(
array_filter($this->getSteps(), function ($Step) {
/* @var $Step AbstractOrderingStep */
return $Step->getType() === Controls\OrderProcess\Checkout::class;
})
);
if ($Current->showNext() === false) {
$next = false;
}
|| $Current->getName() === $this->getFirstStep()->getName()
) {
$previous = false;
}
$payableToOrder = true;
}
try {
$Current->validate();
$this->Events->fireEvent('validate', [$this]);
QUI::getEvents()->fireEvent('quiqqerOrderOrderProcessValidate', [$this]);
} catch (QUI\ERP\Order\Exception $Exception) {
$error = $Exception->getMessage();
if (get_class($Current) === FinishControl::class) {
try {
$Current->validate();
$error = false;
} catch (\Exception $Exception) {
// if step is the same as the current step, then we need an error message
// if step is not the same as the current step, then we need not an error message
if ($this->getAttribute('step') === $Current->getName()) {
$error = false;
}
} catch (\Exception $Exception) {
QUI\System\Log::writeException($Exception);
$Current = $this->getPreviousStep();
$Project = $this->getSite()->getProject();
$this->setAttribute('step', $Current->getName());
$this->setAttribute('data-url', Utils\Utils::getOrderProcess($Project)->getUrlRewritten());
$frontendMessages = [];
$FrontendMessages = $Order->getFrontendMessages();
if (!$FrontendMessages->isEmpty()) {
$frontendMessages = $FrontendMessages->toArray();
$Order->clearFrontendMessages();
}
// #locale quiqqer/order#158
if (class_exists('QUI\ERP\Products\Handler\Products')) {
QUI\ERP\Products\Handler\Products::setLocale(QUI::getLocale());
}
'listWidth' => floor(100 / count($this->getSteps())),
'this' => $this,
'error' => $error,
'next' => $next,
'previous' => $previous,
'payableToOrder' => $payableToOrder,
'steps' => $this->getSteps(),
'CurrentStep' => $Current,

Henning Leutz
committed
'currentStepContent' => QUI\ControlUtils::parse($Current),
'Site' => $this->getSite(),
'Order' => $this->getOrder(),
'hash' => $this->getStepHash(),
'messages' => $this->getStepMessages(get_class($Current)),
'frontendMessages' => $frontendMessages,
'backToShopUrl' => $this->getBackToShopUrl()
$this->Events->fireEvent('getBody', [$this]);
return QUI\Output::getInstance()->parse($Engine->fetch($template));
}
/**
* checks if the order is in the payment process
*if yes, they try to make the payment step
*
* @return bool|string
*
* @throws Exception
* @throws QUI\Exception
*/
protected function checkProcessing(): bool | string
if (!$Order) {
return false;
}

Henning Leutz
committed
if ($Order->isSuccessful() || $Order instanceof Order) {
if (!$checkedTermsAndConditions) {
return false;
}
/* @var $Checkout AbstractOrderingStep */
/* @var $Finish AbstractOrderingStep */
$Checkout = current(
array_filter($this->getSteps(), function ($Step) {
/* @var $Step AbstractOrderingStep */
return $Step->getType() === Controls\OrderProcess\Checkout::class;
})
);
$Finish = current(
array_filter($this->getSteps(), function ($Step) {
/* @var $Step AbstractOrderingStep */
return $Step->getType() === FinishControl::class;

Henning Leutz
committed
$render = function () use ($Order, $Finish, &$Current) {

Henning Leutz
committed
$Processing = $this->getProcessingStep();
$this->setAttribute('step', $Processing->getName());

Henning Leutz
committed
$Payment = $Order->getPayment();
if ($Payment->getPaymentType()->isGateway() === false) {
$this->setAttribute('step', $Finish->getName());
$Current = $Finish;
try {
$this->send();
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeException($Exception);
}
return false;
}
if (Settings::getInstance()->get('order', 'failedPaymentProcedure') === 'execute') {
try {
$this->send();
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeException($Exception);
}
}
// rearrange the steps, insert the processing step
$Steps = $this->parseSteps();
$Steps->append($Processing);
$this->sortSteps($Steps);
$this->steps = $this->parseStepsToArray($Steps);
if ($result === false) {
return false;
}
/* @var $Step AbstractOrderingStep */
foreach ($this->getSteps() as $Step) {
try {
$Step->onExecutePayableStatus();
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
}
}
return $result;
};
if (isset($_REQUEST['payableToOrder'])) {
return $render();
}
// show processing step if order is not paid
$paymentIsSuccessful = false;
// @phpstan-ignore-next-line
if ($Payment && $Payment->isSuccessful($Order->getUUID())) {
$paymentIsSuccessful = true;
}
if ($Order instanceof Order && !$Order->isPaid() && !$paymentIsSuccessful) {
// if a payment transaction exists, maybe the transaction is in pending
// as long as, the order is "successful"
$transactions = $Order->getTransactions();
$isInPending = array_filter($transactions, function ($Transaction) {
/* @var $Transaction QUI\ERP\Accounting\Payments\Transactions\Transaction */
return $Transaction->isPending();
});
if (!$isInPending) {
return $render();
}

Henning Leutz
committed
}
if (
$Finish->getName() === $Current->getName()
&& $this->getOrder()->getDataEntry('orderedWithCosts')
) {
if ($Checkout && $Checkout->getName() !== $Current->getName()) {
return false;
}
if ($this->getOrder()->getDataEntry('orderedWithCosts')) {
return $render();
}
return false;

Henning Leutz
committed
* Render the last step (finish step)
*
* @return mixed
* @throws Exception
* @throws QUI\Exception
* @throws QUI\ExceptionStack
*/
$Order = $this->getOrder();
if ($Order instanceof Order) {
$this->Order = null;
}
$Basket = $this->getBasket();

Henning Leutz
committed
// clear basket
if ($Basket instanceof Basket\Basket) {
$Basket->clear();
}

Henning Leutz
committed
$template = dirname(__FILE__) . '/Controls/OrderProcess.html';
$Engine = QUI::getTemplateManager()->getEngine();
$steps = $this->getSteps();
$LastStep = $this->getLastStep();
$Site = $this->getSite();
$stepHash = $this->getStepHash();
$stepControl = QUI\ControlUtils::parse($LastStep);
$Engine->assign([
'listWidth' => floor(100 / count($steps)),
'this' => $this,
'error' => false,
'next' => false,
'previous' => false,
'payableToOrder' => false,
'steps' => $steps,
'CurrentStep' => $LastStep,
'currentStepContent' => $stepControl,
'Site' => $Site,
'Order' => $Order,
'hash' => $stepHash,
'backToShopUrl' => $this->getBackToShopUrl()
]);
$this->Events->fireEvent('getBody', [$this]);
$this->Events->fireEvent('renderFinish', [$this]);
return QUI\Output::getInstance()->parse($Engine->fetch($template));
}
/**
* Return the current Step
*
public function getCurrentStep(): Controls\OrderProcess\Processing | AbstractOrderingStep
$current = $this->getCurrentStepName();
if (isset($steps[$current])) {
return $steps[$current];
}

Henning Leutz
committed
$Processing = $this->getProcessingStep();
if ($current === $Processing->getName()) {
return $Processing;
}
return $this->getFirstStep();
* Return the first step
* @return AbstractOrderingStep
* @throws QUI\Exception
public function getFirstStep(): AbstractOrderingStep

Henning Leutz
committed
/**
* Returns the last step of the order process
*

Henning Leutz
committed
* @return mixed

Henning Leutz
committed
* @throws Exception
* @throws QUI\Exception
*/

Henning Leutz
committed
{

Henning Leutz
committed

Henning Leutz
committed
}
/**
* Return the next step
*
* @param AbstractOrderingStep|null $StartStep
* @return FinishControl|bool|AbstractOrderingStep
public function getNextStep(
null | AbstractOrderingStep $StartStep = null
): FinishControl | bool | AbstractOrderingStep {
if ($StartStep === null) {
$step = $this->getCurrentStepName();
} else {
$step = $StartStep->getName();
$Order = $this->getOrder();
if (!$Order) {
return false;
}
// special -> processing step
/* @var $Processing AbstractOrderingStep */

Henning Leutz
committed
$Processing = $this->getProcessingStep();
if ($step === $Processing->getName() && !$Order->isSuccessful()) {
$this->setAttribute('orderHash', $Order->getUUID());