diff --git a/changelog.md b/changelog.md index 76f8e6fc..50c9c156 100755 --- a/changelog.md +++ b/changelog.md @@ -137,3 +137,6 @@ ## [1.1.5] - * - BO : Fixed the redirect issue when payment was aborted. + +## [1.1.6] - * +- BO : Added additional check for confirmation email regarding payment status and customer behavior diff --git a/controllers/admin/AdminSaferPayOfficialPaymentController.php b/controllers/admin/AdminSaferPayOfficialPaymentController.php index ce67fb90..056c9ce6 100755 --- a/controllers/admin/AdminSaferPayOfficialPaymentController.php +++ b/controllers/admin/AdminSaferPayOfficialPaymentController.php @@ -234,6 +234,7 @@ protected function initForm() $fields = []; $fields[] = [ 'type' => 'free', + 'label' => '', 'name' => 'payment_method_label', ]; $fields[] = [ diff --git a/saferpayofficial.php b/saferpayofficial.php index 8cbea8b7..05122c71 100755 --- a/saferpayofficial.php +++ b/saferpayofficial.php @@ -36,7 +36,7 @@ public function __construct($name = null) { $this->name = 'saferpayofficial'; $this->author = 'Invertus'; - $this->version = '1.1.5'; + $this->version = '1.1.6'; $this->module_key = '3d3506c3e184a1fe63b936b82bda1bdf'; $this->displayName = 'SaferpayOfficial'; $this->description = 'Saferpay Payment module'; @@ -508,6 +508,7 @@ public function hookActionEmailSendBefore($params) return true; } $cart = new Cart($params['cart']->id); + /** @var \Order $order */ $order = Order::getByCartId($cart->id); if (!$order) { @@ -518,11 +519,11 @@ public function hookActionEmailSendBefore($params) return true; } + /** @var \Invertus\SaferPay\Core\Order\Verification\CanSendOrderConfirmationEmail $canSendOrderConfirmationEmail */ + $canSendOrderConfirmationEmail = $this->getService(\Invertus\SaferPay\Core\Order\Verification\CanSendOrderConfirmationEmail::class); + if ($params['template'] === 'order_conf') { - if (Configuration::get(\Invertus\SaferPay\Config\SaferPayConfig::SAFERPAY_SEND_ORDER_CONFIRMATION)) { - return true; - } - return false; + return $canSendOrderConfirmationEmail->verify($order, (int) $order->current_state); } if ($params['template'] === 'new_order') { @@ -554,17 +555,19 @@ public function hookActionOrderStatusUpdate($params = []) return; } - if (!Configuration::get(\Invertus\SaferPay\Config\SaferPayConfig::SAFERPAY_SEND_NEW_ORDER_MAIL)) { - return; - } + /** @var \Invertus\SaferPay\Service\SaferPayMailService $mailService */ + $mailService = $this->getService(\Invertus\SaferPay\Service\SaferPayMailService::class); $saferPayAuthorizedStatus = (int) Configuration::get(\Invertus\SaferPay\Config\SaferPayConfig::SAFERPAY_PAYMENT_AUTHORIZED); - if ($orderStatus->id === $saferPayAuthorizedStatus) { - /** @var \Invertus\SaferPay\Service\SaferPayMailService $mailService */ - $mailService = $this->getService( - \Invertus\SaferPay\Service\SaferPayMailService::class - ); - $mailService->sendNewOrderMail($order, $orderStatus->id); + if ($orderStatus->id === $saferPayAuthorizedStatus && Configuration::get(\Invertus\SaferPay\Config\SaferPayConfig::SAFERPAY_SEND_NEW_ORDER_MAIL)) { + $mailService->sendNewOrderMail($order, (int) $orderStatus->id); + } + + /** @var \Invertus\SaferPay\Core\Order\Verification\CanSendOrderConfirmationEmail $canSendOrderConfirmationEmail */ + $canSendOrderConfirmationEmail = $this->getService(\Invertus\SaferPay\Core\Order\Verification\CanSendOrderConfirmationEmail::class); + + if ($canSendOrderConfirmationEmail->verify($order, (int) $orderStatus->id)) { + $mailService->sendOrderConfMail($order, (int)$orderStatus->id); } } diff --git a/src/Api/ApiRequest.php b/src/Api/ApiRequest.php index e5254415..d6651a53 100755 --- a/src/Api/ApiRequest.php +++ b/src/Api/ApiRequest.php @@ -114,7 +114,7 @@ private function getBaseUrl() private function isValidResponse(Response $response) { if ($response->code >= 300){ - throw new SaferPayApiException('Initialize API failed', SaferPayApiException::INITIALIZE); + throw new SaferPayApiException(sprintf('Initialize API failed: %s', $response->raw_body), SaferPayApiException::INITIALIZE); } } } diff --git a/src/Config/SaferPayConfig.php b/src/Config/SaferPayConfig.php index e6f75318..071aac2e 100755 --- a/src/Config/SaferPayConfig.php +++ b/src/Config/SaferPayConfig.php @@ -218,6 +218,8 @@ class SaferPayConfig const SAFERPAY_SEND_ORDER_CONFIRMATION = 'SAFERPAY_SEND_ORDER_CONFIRMATION'; const SAFERPAY_SEND_NEW_ORDER_MAIL = 'SAFERPAY_SEND_NEW_ORDER_MAIL'; + const STATUS_PS_OS_OUTOFSTOCK_PAID = 'PS_OS_OUTOFSTOCK_PAID'; + const SAFERPAY_ORDER_STATE_CHOICE_AWAITING_PAYMENT = 'SAFERPAY_ORDER_STATE_CHOICE_AWAITING_PAYMENT'; const SAFERPAY_TEMPLATE_LOCATION = 'module:saferpayofficial/views/templates/'; diff --git a/src/Core/Order/Verification/CanSendOrderConfirmationEmail.php b/src/Core/Order/Verification/CanSendOrderConfirmationEmail.php new file mode 100644 index 00000000..11166f7b --- /dev/null +++ b/src/Core/Order/Verification/CanSendOrderConfirmationEmail.php @@ -0,0 +1,59 @@ + + *@copyright SIX Payment Services + *@license SIX Payment Services + */ + +namespace Invertus\SaferPay\Core\Order\Verification; + +use Invertus\SaferPay\Config\SaferPayConfig; + +class CanSendOrderConfirmationEmail +{ + public function verify(\Order $order, $orderStatusId) + { + if (!\Configuration::get(\Invertus\SaferPay\Config\SaferPayConfig::SAFERPAY_SEND_ORDER_CONFIRMATION)) { + return false; + } + + if (!$this->isOrderStatusValid($orderStatusId)) { + return false; + } + + return true; + } + + private function isOrderStatusValid($orderStatusId) + { + if ((int) \Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_AUTHORIZED) === (int) $orderStatusId) { + return true; + } + + if ((int) \Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_COMPLETED) === (int) $orderStatusId) { + return true; + } + + if ((int) \Configuration::get(SaferPayConfig::STATUS_PS_OS_OUTOFSTOCK_PAID) === (int) $orderStatusId) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/Service/SaferPayMailService.php b/src/Service/SaferPayMailService.php index 490f3c54..2c892661 100755 --- a/src/Service/SaferPayMailService.php +++ b/src/Service/SaferPayMailService.php @@ -23,22 +23,61 @@ namespace Invertus\SaferPay\Service; +use Address; +use Configuration; use Context; use Invertus\SaferPay\Config\SaferPayConfig; +use Mail; use Module; use Order; use OrderState; +use Product; +use State; +use Tools; class SaferPayMailService { + const FILE_NAME = 'SaferPayMailService'; + /** @var Context|null */ private $context; + /** @var \SaferPayOfficial */ + private $module; - public function __construct() + public function __construct(\SaferPayOfficial $module) { + $this->module = $module; $this->context = Context::getContext(); } + /** + * @param Order $order + * @param int $orderStateId + * + * @throws \PrestaShopDatabaseException + * @throws \PrestaShopException + */ + public function sendOrderConfMail(Order $order, $orderStateId) + { + $data = $this->getOrderConfData($order, $orderStateId); + $fileAttachment = $this->getFileAttachment($orderStateId, $order); + $customer = $order->getCustomer(); + + Mail::Send( + (int) $order->id_lang, + 'order_conf', + Mail::l('Order confirmation', (int) $order->id_lang), + $data, + $customer->email, + + implode(' ', [$customer->firstname, $customer->lastname]), + null, + null, + $fileAttachment, + null, _PS_MAIL_DIR_, false, (int) $order->id_shop + ); + } + /** * @param Order $order * @param int $orderStateId @@ -67,4 +106,340 @@ public function sendNewOrderMail(Order $order, $orderStateId) ); } + private function getOrderConfData(Order $order, $orderStateId) + { + $virtual_product = true; + $carrier = new \Carrier($order->id_carrier); + $customer = $order->getCustomer(); + + $product_var_tpl_list = []; + foreach ($order->getProducts() as $product) { + $specific_price = null; + /* @phpstan-ignore-next-line */ + $price = Product::getPriceStatic((int) $product['id_product'], false, ($product['product_attribute_id'] ? (int) $product['product_attribute_id'] : null), 6, null, false, true, $product['product_quantity'], false, (int) $order->id_customer, (int) $order->id_cart, (int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}, $specific_price, true, true, null, true, $product['id_customization']); + /* @phpstan-ignore-next-line */ + $price_wt = Product::getPriceStatic((int) $product['id_product'], true, ($product['product_attribute_id'] ? (int) $product['product_attribute_id'] : null), 2, null, false, true, $product['product_quantity'], false, (int) $order->id_customer, (int) $order->id_cart, (int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}, $specific_price, true, true, null, true, $product['id_customization']); + + $product_price = PS_TAX_EXC == Product::getTaxCalculationMethod() ? Tools::ps_round($price, 2) : $price_wt; + + $attribute = $this->getProductAttribute((int) $product['product_attribute_id'], $this->context->language->id); + $product_var_tpl = [ + 'id_product' => $product['id_product'], + 'reference' => $product['reference'], + 'name' => $product['product_name'] . (\Validate::isLoadedObject($attribute) ? ' - ' . $attribute->name : ''), + 'price' => Tools::displayPrice($product_price * $product['product_quantity'], $this->context->currency, false), + 'quantity' => $product['product_quantity'], + 'customization' => [], + ]; + + if (isset($product['price']) && $product['price']) { + $product_var_tpl['unit_price'] = Tools::displayPrice($product_price, $this->context->currency, false); + $product_var_tpl['unit_price_full'] = Tools::displayPrice($product_price, $this->context->currency, false) + . ' ' . $product['unity']; + } else { + $product_var_tpl['unit_price'] = $product_var_tpl['unit_price_full'] = ''; + } + + /* @phpstan-ignore-next-line */ + $customized_datas = Product::getAllCustomizedDatas((int) $order->id_cart, null, true, null, (int) $product['id_customization']); + if (isset($customized_datas[$product['id_product']][$product['product_attribute_id']])) { + $product_var_tpl['customization'] = []; + foreach ($customized_datas[$product['id_product']][$product['product_attribute_id']][$order->id_address_delivery] as $customization) { + $customization_text = ''; + if (isset($customization['datas'][Product::CUSTOMIZE_TEXTFIELD])) { + foreach ($customization['datas'][Product::CUSTOMIZE_TEXTFIELD] as $text) { + $customization_text .= '' . $text['name'] . ': ' . $text['value'] . '
'; + } + } + + if (isset($customization['datas'][Product::CUSTOMIZE_FILE])) { + $customization_text .= Context::getContext()->getTranslator()->trans('%d image(s)', [count($customization['datas'][Product::CUSTOMIZE_FILE])], 'Admin.Payment.Notification') . '
'; + } + + $customization_quantity = (int) $customization['quantity']; + + $product_var_tpl['customization'][] = [ + 'customization_text' => $customization_text, + 'customization_quantity' => $customization_quantity, + 'quantity' => Tools::displayPrice($customization_quantity * $product_price, $this->context->currency, false), + ]; + } + } + + $product_var_tpl_list[] = $product_var_tpl; + // Check if is not a virutal product for the displaying of shipping + if (!$product['is_virtual']) { + $virtual_product &= false; + } + } + + $invoice = new Address((int) $order->id_address_invoice); + $delivery = new Address((int) $order->id_address_delivery); + $delivery_state = $delivery->id_state ? new State((int) $delivery->id_state) : false; + $invoice_state = $invoice->id_state ? new State((int) $invoice->id_state) : false; + + $product_list_txt = ''; + $product_list_html = ''; + if (count($product_var_tpl_list) > 0) { + $product_list_txt = $this->getEmailTemplateContent('order_conf_product_list.txt', Mail::TYPE_TEXT, $product_var_tpl_list); + $product_list_html = $this->getEmailTemplateContent('order_conf_product_list.tpl', Mail::TYPE_HTML, $product_var_tpl_list); + } + + $cart_rules_list = $this->getCartRuleList($order, $orderStateId); + $cart_rules_list_txt = ''; + $cart_rules_list_html = ''; + if (count($cart_rules_list) > 0) { + $cart_rules_list_txt = $this->getEmailTemplateContent('order_conf_cart_rules.txt', Mail::TYPE_TEXT, $cart_rules_list); + $cart_rules_list_html = $this->getEmailTemplateContent('order_conf_cart_rules.tpl', Mail::TYPE_HTML, $cart_rules_list); + } + + return [ + '{firstname}' => $customer->firstname, + '{lastname}' => $customer->lastname, + '{email}' => $customer->email, + '{delivery_block_txt}' => $this->_getFormatedAddress($delivery, "\n"), + '{invoice_block_txt}' => $this->_getFormatedAddress($invoice, "\n"), + '{delivery_block_html}' => $this->_getFormatedAddress($delivery, '
', [ + 'firstname' => '%s', + 'lastname' => '%s', + ]), + '{invoice_block_html}' => $this->_getFormatedAddress($invoice, '
', [ + 'firstname' => '%s', + 'lastname' => '%s', + ]), + '{delivery_company}' => $delivery->company, + '{delivery_firstname}' => $delivery->firstname, + '{delivery_lastname}' => $delivery->lastname, + '{delivery_address1}' => $delivery->address1, + '{delivery_address2}' => $delivery->address2, + '{delivery_city}' => $delivery->city, + '{delivery_postal_code}' => $delivery->postcode, + '{delivery_country}' => $delivery->country, + '{delivery_state}' => $delivery->id_state ? $delivery_state->name : '', + '{delivery_phone}' => ($delivery->phone) ? $delivery->phone : $delivery->phone_mobile, + '{delivery_other}' => $delivery->other, + '{invoice_company}' => $invoice->company, + '{invoice_vat_number}' => $invoice->vat_number, + '{invoice_firstname}' => $invoice->firstname, + '{invoice_lastname}' => $invoice->lastname, + '{invoice_address2}' => $invoice->address2, + '{invoice_address1}' => $invoice->address1, + '{invoice_city}' => $invoice->city, + '{invoice_postal_code}' => $invoice->postcode, + '{invoice_country}' => $invoice->country, + '{invoice_state}' => $invoice->id_state ? $invoice_state->name : '', + '{invoice_phone}' => ($invoice->phone) ? $invoice->phone : $invoice->phone_mobile, + '{invoice_other}' => $invoice->other, + '{order_name}' => $order->getUniqReference(), + '{date}' => Tools::displayDate(date('Y-m-d H:i:s'), null, true), + '{carrier}' => ($virtual_product || !isset($carrier->name)) ? $this->module->l('No carrier', self::FILE_NAME) : $carrier->name, + '{payment}' => Tools::substr($order->payment, 0, 255), + '{products}' => $product_list_html, + '{products_txt}' => $product_list_txt, + '{discounts}' => $cart_rules_list_html, + '{discounts_txt}' => $cart_rules_list_txt, + '{total_paid}' => Tools::displayPrice($order->total_paid, $this->context->currency, false), + '{total_products}' => Tools::displayPrice(PS_TAX_EXC == Product::getTaxCalculationMethod() ? $order->total_products : $order->total_products_wt, $this->context->currency, false), + '{total_discounts}' => Tools::displayPrice($order->total_discounts, $this->context->currency, false), + '{total_shipping}' => Tools::displayPrice($order->total_shipping, $this->context->currency, false), + '{total_wrapping}' => Tools::displayPrice($order->total_wrapping, $this->context->currency, false), + '{total_tax_paid}' => Tools::displayPrice(($order->total_products_wt - $order->total_products) + ($order->total_shipping_tax_incl - $order->total_shipping_tax_excl), $this->context->currency, false), + ]; + } + + private function getCartRuleList(Order $order, $orderStateId) + { + $customer = $order->getCustomer(); + $order_list = []; + $cart_rules = $this->context->cart->getCartRules(); + $order_list[] = $order; + $cart_rule_used = []; + + $cart_rules_list = []; + $total_reduction_value_ti = 0; + $total_reduction_value_tex = 0; + foreach ($cart_rules as $cart_rule) { + $package = [ + 'id_carrier' => $order->id_carrier, + 'id_address' => $order->id_address_delivery, + 'products' => $order->getProducts(), + ]; + $values = [ + 'tax_incl' => $cart_rule['obj']->getContextualValue(true, $this->context, \CartRule::FILTER_ACTION_ALL_NOCAP, $package), + 'tax_excl' => $cart_rule['obj']->getContextualValue(false, $this->context, \CartRule::FILTER_ACTION_ALL_NOCAP, $package), + ]; + + // If the reduction is not applicable to this order, then continue with the next one + if (!$values['tax_excl']) { + continue; + } + + // IF + // This is not multi-shipping + // The value of the voucher is greater than the total of the order + // Partial use is allowed + // This is an "amount" reduction, not a reduction in % or a gift + // THEN + // The voucher is cloned with a new value corresponding to the remainder + if (1 == count($order_list) && $values['tax_incl'] > ($order->total_products_wt - $total_reduction_value_ti) && 1 == $cart_rule['obj']->partial_use && $cart_rule['obj']->reduction_amount > 0) { + // Create a new voucher from the original + $voucher = new \CartRule((int) $cart_rule['obj']->id); // We need to instantiate the CartRule without lang parameter to allow saving it + unset($voucher->id); + + // Set a new voucher code + $voucher->code = empty($voucher->code) ? substr(md5($order->id . '-' . $order->id_customer . '-' . $cart_rule['obj']->id), 0, 16) : $voucher->code . '-2'; + if (preg_match('/\-([0-9]{1,2})\-([0-9]{1,2})$/', $voucher->code, $matches) && $matches[1] == $matches[2]) { + $voucher->code = preg_replace('/' . $matches[0] . '$/', '-' . (intval($matches[1]) + 1), $voucher->code); + } + + // Set the new voucher value + if ($voucher->reduction_tax) { + $voucher->reduction_amount = ($total_reduction_value_ti + $values['tax_incl']) - $order->total_products_wt; + + // Add total shipping amout only if reduction amount > total shipping + if (1 == $voucher->free_shipping && $voucher->reduction_amount >= $order->total_shipping_tax_incl) { + $voucher->reduction_amount -= $order->total_shipping_tax_incl; + } + } else { + $voucher->reduction_amount = ($total_reduction_value_tex + $values['tax_excl']) - $order->total_products; + + // Add total shipping amout only if reduction amount > total shipping + if (1 == $voucher->free_shipping && $voucher->reduction_amount >= $order->total_shipping_tax_excl) { + $voucher->reduction_amount -= $order->total_shipping_tax_excl; + } + } + if ($voucher->reduction_amount <= 0) { + continue; + } + + if ($customer->isGuest()) { + $voucher->id_customer = 0; + } else { + $voucher->id_customer = $order->id_customer; + } + + $voucher->quantity = 1; + $voucher->reduction_currency = $order->id_currency; + $voucher->quantity_per_user = 1; + if ($voucher->add()) { + // If the voucher has conditions, they are now copied to the new voucher + \CartRule::copyConditions($cart_rule['obj']->id, $voucher->id); + $orderLanguage = new \Language((int) $order->id_lang); + + $params = [ + '{voucher_amount}' => Tools::displayPrice($voucher->reduction_amount, $this->context->currency, false), + '{voucher_num}' => $voucher->code, + '{firstname}' => $customer->firstname, + '{lastname}' => $customer->lastname, + '{id_order}' => $order->reference, + '{order_name}' => $order->getUniqReference(), + ]; + Mail::Send( + (int) $order->id_lang, + 'voucher', + $this->module->l( + 'New voucher for your order %s', + self::FILE_NAME + ), + $params, + $customer->email, + implode(' ', [$customer->firstname, $customer->lastname]), + null, null, null, null, _PS_MAIL_DIR_, false, (int) $order->id_shop + ); + } + + $values['tax_incl'] = $order->total_products_wt - $total_reduction_value_ti; + $values['tax_excl'] = $order->total_products - $total_reduction_value_tex; + if (1 == $voucher->free_shipping) { + $values['tax_incl'] += $order->total_shipping_tax_incl; + $values['tax_excl'] += $order->total_shipping_tax_excl; + } + } + $total_reduction_value_ti += $values['tax_incl']; + $total_reduction_value_tex += $values['tax_excl']; + + $order->addCartRule($cart_rule['obj']->id, $cart_rule['obj']->name, $values, 0, $cart_rule['obj']->free_shipping); + + if ($orderStateId != Configuration::get('PS_OS_ERROR') && $orderStateId != Configuration::get('PS_OS_CANCELED') + && !in_array($cart_rule['obj']->id, $cart_rule_used)) { + $cart_rule_used[] = $cart_rule['obj']->id; + + // Create a new instance of Cart Rule without id_lang, in order to update its quantity + $cart_rule_to_update = new \CartRule((int) $cart_rule['obj']->id); + $cart_rule_to_update->quantity = max(0, $cart_rule_to_update->quantity - 1); + $cart_rule_to_update->update(); + } + + $cart_rules_list[] = [ + 'voucher_name' => $cart_rule['obj']->name, + 'voucher_reduction' => (0.00 != $values['tax_incl'] ? '-' : '') . Tools::displayPrice($values['tax_incl'], $this->context->currency, false), + ]; + } + + return $cart_rules_list; + } + + private function getEmailTemplateContent($template_name, $mail_type, $var) + { + $email_configuration = Configuration::get('PS_MAIL_TYPE'); + if ($email_configuration != $mail_type && Mail::TYPE_BOTH != $email_configuration) { + return ''; + } + + $pathToFindEmail = [ + _PS_THEME_DIR_ . 'mails' . DIRECTORY_SEPARATOR . $this->context->language->iso_code . DIRECTORY_SEPARATOR . $template_name, + _PS_THEME_DIR_ . 'mails' . DIRECTORY_SEPARATOR . 'en' . DIRECTORY_SEPARATOR . $template_name, + _PS_MAIL_DIR_ . $this->context->language->iso_code . DIRECTORY_SEPARATOR . $template_name, + _PS_MAIL_DIR_ . 'en' . DIRECTORY_SEPARATOR . $template_name, + _PS_MAIL_DIR_ . '_partials' . DIRECTORY_SEPARATOR . $template_name, + ]; + + foreach ($pathToFindEmail as $path) { + if (Tools::file_exists_cache($path)) { + $this->context->smarty->assign('list', $var); + + return $this->context->smarty->fetch($path); + } + } + + return ''; + } + + private function getFileAttachment($orderStatusId, Order $order) + { + $order_status = new OrderState((int) $orderStatusId, (int) $this->context->language->id); + + // Join PDF invoice + if ((int) Configuration::get('PS_INVOICE') && $order_status->invoice && $order->invoice_number) { + $fileAttachment = []; + $order_invoice_list = $order->getInvoicesCollection(); + \Hook::exec('actionPDFInvoiceRender', ['order_invoice_list' => $order_invoice_list]); + $pdf = new \PDF($order_invoice_list, \PDF::TEMPLATE_INVOICE, $this->context->smarty); + $fileAttachment['content'] = $pdf->render(false); + $fileAttachment['name'] = Configuration::get('PS_INVOICE_PREFIX', (int) $order->id_lang, null, $order->id_shop) . sprintf('%06d', $order->invoice_number) . '.pdf'; + $fileAttachment['mime'] = 'application/pdf'; + } else { + $fileAttachment = null; + } + + return $fileAttachment; + } + + private function _getFormatedAddress(Address $the_address, $line_sep, $fields_style = []) + { + return \AddressFormat::generateAddress($the_address, ['avoid' => []], $line_sep, ' ', $fields_style); + } + + public function getProductAttribute($id = null, $idLang = null, $idShop = null) + { + if (class_exists('AttributeCore')) { + return new \AttributeCore($id, $idLang, $idShop); + } + + if (class_exists('ProductAttributeCore')) { + return new \ProductAttributeCore($id, $idLang, $idShop); + } + + throw new \Exception('Attribute class was not found'); + } }