From 41ab016487843b3e19d339111e719f6916816082 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev Date: Fri, 17 Apr 2020 19:53:14 +0300 Subject: [PATCH 1/3] Fix Arabic & Hebrew in invoices Revert MC-13880 and bring back MAGETWO-95816 This reverts commit 1c0549ea --- .../Sales/Model/Order/Address/Renderer.php | 58 ++++++++++++++++++- .../Sales/Model/Order/Pdf/AbstractPdf.php | 6 +- .../Pdf/Items/Invoice/DefaultInvoice.php | 18 +++++- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Address/Renderer.php b/app/code/Magento/Sales/Model/Order/Address/Renderer.php index 947c92e04942f..ba0f2f852e727 100644 --- a/app/code/Magento/Sales/Model/Order/Address/Renderer.php +++ b/app/code/Magento/Sales/Model/Order/Address/Renderer.php @@ -9,6 +9,8 @@ use Magento\Customer\Model\Address\Config as AddressConfig; use Magento\Framework\Event\ManagerInterface as EventManager; use Magento\Sales\Model\Order\Address; +use Magento\Framework\Stdlib\StringUtils; +use Magento\Framework\App\ObjectManager; /** * Class Renderer used for formatting an order address @@ -27,18 +29,26 @@ class Renderer */ protected $eventManager; + /** + * @var StringUtils + */ + private $stringUtils; + /** * Constructor * * @param AddressConfig $addressConfig * @param EventManager $eventManager + * @param StringUtils $stringUtils */ public function __construct( AddressConfig $addressConfig, - EventManager $eventManager + EventManager $eventManager, + StringUtils $stringUtils = null ) { $this->addressConfig = $addressConfig; $this->eventManager = $eventManager; + $this->stringUtils = $stringUtils ?: ObjectManager::getInstance()->get(StringUtils::class); } /** @@ -58,4 +68,50 @@ public function format(Address $address, $type) $this->eventManager->dispatch('customer_address_format', ['type' => $formatType, 'address' => $address]); return $formatType->getRenderer()->renderArray($address->getData()); } + + /** + * Detect an input string is Arabic + * + * @param string $subject + * @return bool + */ + public function isArabic(string $subject): bool + { + return (preg_match('/\p{Arabic}/u', $subject) > 0); + } + + /** + * Reverse text with Arabic characters + * + * @param string $string + * @return string + */ + public function reverseArabicText($string) + { + $splitText = explode(' ', $string); + for ($i = 0; $i < count($splitText); $i++) { + if ($this->isArabic($splitText[$i])) { + for ($j = $i + 1; $j < count($splitText); $j++) { + $tmp = ($this->isArabic($splitText[$j])) + ? $this->stringUtils->strrev($splitText[$j]) : $splitText[$j]; + $splitText[$j] = ($this->isArabic($splitText[$i])) + ? $this->stringUtils->strrev($splitText[$i]) : $splitText[$i]; + $splitText[$i] = $tmp; + } + } + } + return implode(' ', $splitText); + } + + /** + * Check and revert arabic text + * + * @param string $string + * @return string + */ + public function processArabicText($string) + { + return ($this->isArabic($string)) + ? $this->reverseArabicText($string) : $string; + } } diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index ba82478c9cea1..89e9b569e2bfa 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -501,7 +501,8 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = $_value; + $text[] = ($this->addressRenderer->isArabic($_value)) + ? $this->addressRenderer->reverseArabicText($_value) : $_value; } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8'); @@ -518,7 +519,8 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = $_value; + $text[] = ($this->addressRenderer->isArabic($_value)) + ? $this->addressRenderer->reverseArabicText($_value) : $_value; } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 285, $this->y, 'UTF-8'); diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index 23c2c00daadc3..10a1d2b19a429 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -7,6 +7,9 @@ namespace Magento\Sales\Model\Order\Pdf\Items\Invoice; +use Magento\Framework\App\ObjectManager; +use Magento\Sales\Model\Order\Address\Renderer; + /** * Sales Order Invoice Pdf default items renderer */ @@ -19,6 +22,11 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems */ protected $string; + /** + * @var \Magento\Sales\Model\Order\Address\Renderer + */ + private $renderer; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -29,6 +37,8 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param \Magento\Sales\Model\Order\Address\Renderer $renderer + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\Model\Context $context, @@ -39,7 +49,8 @@ public function __construct( \Magento\Framework\Stdlib\StringUtils $string, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + Renderer $renderer = null ) { $this->string = $string; parent::__construct( @@ -52,6 +63,7 @@ public function __construct( $resourceCollection, $data ); + $this->renderer = $renderer ?: ObjectManager::getInstance()->get(Renderer::class); } /** @@ -71,7 +83,7 @@ public function draw() $lines[0] = [ [ // phpcs:ignore Magento2.Functions.DiscouragedFunction - 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true), + 'text' => $this->string->split($this->renderer->processArabicText(html_entity_decode($item->getName())), 35, true, true), 'feed' => 35 ] ]; @@ -79,7 +91,7 @@ public function draw() // draw SKU $lines[0][] = [ // phpcs:ignore Magento2.Functions.DiscouragedFunction - 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17), + 'text' => $this->string->split($this->renderer->processArabicText(html_entity_decode($this->getSku($item))), 17), 'feed' => 290, 'align' => 'right', ]; From 876519b67486f0d49649fa60105b3dc60bf11c20 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev Date: Fri, 17 Apr 2020 19:53:31 +0300 Subject: [PATCH 2/3] Fix Arabic & Hebrew in invoices Add model for processing RTL texts --- .../Magento/Sales/Model/RtlTextHandler.php | 58 ++++++++++++++++ .../Test/Unit/Model/RtlTextHandlerTest.php | 66 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 app/code/Magento/Sales/Model/RtlTextHandler.php create mode 100644 app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php diff --git a/app/code/Magento/Sales/Model/RtlTextHandler.php b/app/code/Magento/Sales/Model/RtlTextHandler.php new file mode 100644 index 0000000000000..61302cd879907 --- /dev/null +++ b/app/code/Magento/Sales/Model/RtlTextHandler.php @@ -0,0 +1,58 @@ +stringUtils = $stringUtils; + } + + /** + * Detect an input string is Arabic + * + * @param string $subject + * @return bool + */ + public function isRtlText(string $subject): bool + { + return (preg_match('/[\p{Arabic}\p{Hebrew}]/u', $subject) > 0); + } + + /** + * Reverse text with Arabic characters + * + * @param string $string + * @return string + */ + public function reverseArabicText(string $string): string + { + $splitText = explode(' ', $string); + for ($i = 0; $i < count($splitText); $i++) { + if ($this->isRtlText($splitText[$i])) { + for ($j = $i + 1; $j < count($splitText); $j++) { + $tmp = ($this->isRtlText($splitText[$j])) + ? $this->stringUtils->strrev($splitText[$j]) : $splitText[$j]; + $splitText[$j] = ($this->isRtlText($splitText[$i])) + ? $this->stringUtils->strrev($splitText[$i]) : $splitText[$i]; + $splitText[$i] = $tmp; + } + } + } + + return implode(' ', $splitText); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php new file mode 100644 index 0000000000000..70ccca0f87dba --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php @@ -0,0 +1,66 @@ +stringUtils = new StringUtils(); + $this->rtlTextHandler = new RtlTextHandler($this->stringUtils); + } + + /** + * @param string $str + * @param bool $expected + * @dataProvider provideRtlTexts + */ + public function testIsRtlText(string $str, bool $expected): void + { + $this->assertEquals($expected, $this->rtlTextHandler->isRtlText($str)); + } + + /** + * @param string $str + * @param bool $expected + * @dataProvider provideRtlTexts + */ + public function testReverseArabicText(string $str, bool $expected): void + { + $expectedStr = $expected ? $this->stringUtils->strrev($str) : $str; + + $this->assertEquals($expectedStr, $this->rtlTextHandler->reverseArabicText($str)); + } + + public function provideRtlTexts(): array + { + return [ + ['Adeline Jacobson', false],//English + ['Odell Fisher', false],//English + ['Панов Аркадий Львович', false],//Russian + ['Вероника Сергеевна Игнатьева', false],//Russian + ['Mehmet Arnold-Döring', false],//German + ['Herr Prof. Dr. Gerald Schüler B.A.', false],//German + ['نديم مقداد نعمان القحطاني', true],//Arabic + ['شهاب الفرحان', true],//Arabic + ['צבר קרליבך', true],//Hebrew + ['גורי מייזליש', true],//Hebrew + ['اتابک بهشتی', true],//Persian + ['مهداد محمدی', true],//Persian + ]; + } +} From 27ec0ae0adb8d14b2ea90b537c58e221cfa5ef1f Mon Sep 17 00:00:00 2001 From: Ihor Sviziev Date: Fri, 17 Apr 2020 20:13:17 +0300 Subject: [PATCH 3/3] Fix Arabic & Hebrew in invoices Use rtl text handler in invoice PDF generation --- .../Sales/Model/Order/Address/Renderer.php | 58 +------------------ .../Sales/Model/Order/Pdf/AbstractPdf.php | 21 ++++--- .../Pdf/Items/Invoice/DefaultInvoice.php | 30 ++++++---- .../Magento/Sales/Model/RtlTextHandler.php | 15 +++-- .../Test/Unit/Model/RtlTextHandlerTest.php | 19 +++--- 5 files changed, 57 insertions(+), 86 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Address/Renderer.php b/app/code/Magento/Sales/Model/Order/Address/Renderer.php index ba0f2f852e727..947c92e04942f 100644 --- a/app/code/Magento/Sales/Model/Order/Address/Renderer.php +++ b/app/code/Magento/Sales/Model/Order/Address/Renderer.php @@ -9,8 +9,6 @@ use Magento\Customer\Model\Address\Config as AddressConfig; use Magento\Framework\Event\ManagerInterface as EventManager; use Magento\Sales\Model\Order\Address; -use Magento\Framework\Stdlib\StringUtils; -use Magento\Framework\App\ObjectManager; /** * Class Renderer used for formatting an order address @@ -29,26 +27,18 @@ class Renderer */ protected $eventManager; - /** - * @var StringUtils - */ - private $stringUtils; - /** * Constructor * * @param AddressConfig $addressConfig * @param EventManager $eventManager - * @param StringUtils $stringUtils */ public function __construct( AddressConfig $addressConfig, - EventManager $eventManager, - StringUtils $stringUtils = null + EventManager $eventManager ) { $this->addressConfig = $addressConfig; $this->eventManager = $eventManager; - $this->stringUtils = $stringUtils ?: ObjectManager::getInstance()->get(StringUtils::class); } /** @@ -68,50 +58,4 @@ public function format(Address $address, $type) $this->eventManager->dispatch('customer_address_format', ['type' => $formatType, 'address' => $address]); return $formatType->getRenderer()->renderArray($address->getData()); } - - /** - * Detect an input string is Arabic - * - * @param string $subject - * @return bool - */ - public function isArabic(string $subject): bool - { - return (preg_match('/\p{Arabic}/u', $subject) > 0); - } - - /** - * Reverse text with Arabic characters - * - * @param string $string - * @return string - */ - public function reverseArabicText($string) - { - $splitText = explode(' ', $string); - for ($i = 0; $i < count($splitText); $i++) { - if ($this->isArabic($splitText[$i])) { - for ($j = $i + 1; $j < count($splitText); $j++) { - $tmp = ($this->isArabic($splitText[$j])) - ? $this->stringUtils->strrev($splitText[$j]) : $splitText[$j]; - $splitText[$j] = ($this->isArabic($splitText[$i])) - ? $this->stringUtils->strrev($splitText[$i]) : $splitText[$i]; - $splitText[$i] = $tmp; - } - } - } - return implode(' ', $splitText); - } - - /** - * Check and revert arabic text - * - * @param string $string - * @return string - */ - public function processArabicText($string) - { - return ($this->isArabic($string)) - ? $this->reverseArabicText($string) : $string; - } } diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index 89e9b569e2bfa..63dff3660a1b5 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -7,7 +7,9 @@ namespace Magento\Sales\Model\Order\Pdf; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\MediaStorage\Helper\File\Storage\Database; +use Magento\Sales\Model\RtlTextHandler; /** * Sales Order PDF abstract model @@ -53,6 +55,11 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject */ protected $_pdf; + /** + * @var RtlTextHandler + */ + private $rtlTextHandler; + /** * Retrieve PDF * @@ -142,6 +149,7 @@ abstract public function getPdf(); * @param \Magento\Sales\Model\Order\Address\Renderer $addressRenderer * @param array $data * @param Database $fileStorageDatabase + * @param RtlTextHandler|null $rtlTextHandler * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -156,7 +164,8 @@ public function __construct( \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation, \Magento\Sales\Model\Order\Address\Renderer $addressRenderer, array $data = [], - Database $fileStorageDatabase = null + Database $fileStorageDatabase = null, + ?RtlTextHandler $rtlTextHandler = null ) { $this->addressRenderer = $addressRenderer; $this->_paymentData = $paymentData; @@ -169,8 +178,8 @@ public function __construct( $this->_pdfTotalFactory = $pdfTotalFactory; $this->_pdfItemsFactory = $pdfItemsFactory; $this->inlineTranslation = $inlineTranslation; - $this->fileStorageDatabase = $fileStorageDatabase ?: - \Magento\Framework\App\ObjectManager::getInstance()->get(Database::class); + $this->fileStorageDatabase = $fileStorageDatabase ?: ObjectManager::getInstance()->get(Database::class); + $this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class); parent::__construct($data); } @@ -501,8 +510,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = ($this->addressRenderer->isArabic($_value)) - ? $this->addressRenderer->reverseArabicText($_value) : $_value; + $text[] = $this->rtlTextHandler->reverseRtlText($_value); } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8'); @@ -519,8 +527,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = ($this->addressRenderer->isArabic($_value)) - ? $this->addressRenderer->reverseArabicText($_value) : $_value; + $text[] = $this->rtlTextHandler->reverseRtlText($_value); } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 285, $this->y, 'UTF-8'); diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index 10a1d2b19a429..253dbd43fa580 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -8,7 +8,7 @@ namespace Magento\Sales\Model\Order\Pdf\Items\Invoice; use Magento\Framework\App\ObjectManager; -use Magento\Sales\Model\Order\Address\Renderer; +use Magento\Sales\Model\RtlTextHandler; /** * Sales Order Invoice Pdf default items renderer @@ -23,9 +23,9 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems protected $string; /** - * @var \Magento\Sales\Model\Order\Address\Renderer + * @var RtlTextHandler */ - private $renderer; + private $rtlTextHandler; /** * @param \Magento\Framework\Model\Context $context @@ -37,7 +37,7 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data - * @param \Magento\Sales\Model\Order\Address\Renderer $renderer + * @param RtlTextHandler|null $rtlTextHandler * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -50,7 +50,7 @@ public function __construct( \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - Renderer $renderer = null + ?RtlTextHandler $rtlTextHandler = null ) { $this->string = $string; parent::__construct( @@ -63,7 +63,7 @@ public function __construct( $resourceCollection, $data ); - $this->renderer = $renderer ?: ObjectManager::getInstance()->get(Renderer::class); + $this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class); } /** @@ -82,16 +82,14 @@ public function draw() // draw Product name $lines[0] = [ [ - // phpcs:ignore Magento2.Functions.DiscouragedFunction - 'text' => $this->string->split($this->renderer->processArabicText(html_entity_decode($item->getName())), 35, true, true), + 'text' => $this->string->split($this->prepareText((string)$item->getName()), 35, true, true), 'feed' => 35 ] ]; // draw SKU $lines[0][] = [ - // phpcs:ignore Magento2.Functions.DiscouragedFunction - 'text' => $this->string->split($this->renderer->processArabicText(html_entity_decode($this->getSku($item))), 17), + 'text' => $this->string->split($this->prepareText((string)$this->getSku($item)), 17), 'feed' => 290, 'align' => 'right', ]; @@ -168,4 +166,16 @@ public function draw() $page = $pdf->drawLineBlocks($page, [$lineBlock], ['table_header' => true]); $this->setPage($page); } + + /** + * Returns prepared for PDF text, reversed in case of RTL text + * + * @param string $string + * @return string + */ + private function prepareText(string $string): string + { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return $this->rtlTextHandler->reverseRtlText(html_entity_decode($string)); + } } diff --git a/app/code/Magento/Sales/Model/RtlTextHandler.php b/app/code/Magento/Sales/Model/RtlTextHandler.php index 61302cd879907..cfb88dc63f58b 100644 --- a/app/code/Magento/Sales/Model/RtlTextHandler.php +++ b/app/code/Magento/Sales/Model/RtlTextHandler.php @@ -16,6 +16,9 @@ class RtlTextHandler */ private $stringUtils; + /** + * @param StringUtils $stringUtils + */ public function __construct(StringUtils $stringUtils) { $this->stringUtils = $stringUtils; @@ -38,15 +41,17 @@ public function isRtlText(string $subject): bool * @param string $string * @return string */ - public function reverseArabicText(string $string): string + public function reverseRtlText(string $string): string { $splitText = explode(' ', $string); - for ($i = 0; $i < count($splitText); $i++) { + $splitTextAmount = count($splitText); + + for ($i = 0; $i < $splitTextAmount; $i++) { if ($this->isRtlText($splitText[$i])) { - for ($j = $i + 1; $j < count($splitText); $j++) { - $tmp = ($this->isRtlText($splitText[$j])) + for ($j = $i + 1; $j < $splitTextAmount; $j++) { + $tmp = $this->isRtlText($splitText[$j]) ? $this->stringUtils->strrev($splitText[$j]) : $splitText[$j]; - $splitText[$j] = ($this->isRtlText($splitText[$i])) + $splitText[$j] = $this->isRtlText($splitText[$i]) ? $this->stringUtils->strrev($splitText[$i]) : $splitText[$i]; $splitText[$i] = $tmp; } diff --git a/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php index 70ccca0f87dba..2faeb17dc2395 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php @@ -1,4 +1,9 @@ assertEquals($expected, $this->rtlTextHandler->isRtlText($str)); + $this->assertEquals($isRtl, $this->rtlTextHandler->isRtlText($str)); } /** * @param string $str - * @param bool $expected + * @param bool $isRtl * @dataProvider provideRtlTexts */ - public function testReverseArabicText(string $str, bool $expected): void + public function testReverseRtlText(string $str, bool $isRtl): void { - $expectedStr = $expected ? $this->stringUtils->strrev($str) : $str; + $expectedStr = $isRtl ? $this->stringUtils->strrev($str) : $str; - $this->assertEquals($expectedStr, $this->rtlTextHandler->reverseArabicText($str)); + $this->assertEquals($expectedStr, $this->rtlTextHandler->reverseRtlText($str)); } public function provideRtlTexts(): array