diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index ba82478c9cea1..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,7 +510,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = $_value; + $text[] = $this->rtlTextHandler->reverseRtlText($_value); } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8'); @@ -518,7 +527,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = $_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 23c2c00daadc3..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 @@ -7,6 +7,9 @@ namespace Magento\Sales\Model\Order\Pdf\Items\Invoice; +use Magento\Framework\App\ObjectManager; +use Magento\Sales\Model\RtlTextHandler; + /** * Sales Order Invoice Pdf default items renderer */ @@ -19,6 +22,11 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems */ protected $string; + /** + * @var RtlTextHandler + */ + private $rtlTextHandler; + /** * @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 RtlTextHandler|null $rtlTextHandler + * @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 = [], + ?RtlTextHandler $rtlTextHandler = null ) { $this->string = $string; parent::__construct( @@ -52,6 +63,7 @@ public function __construct( $resourceCollection, $data ); + $this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class); } /** @@ -70,16 +82,14 @@ public function draw() // draw Product name $lines[0] = [ [ - // phpcs:ignore Magento2.Functions.DiscouragedFunction - 'text' => $this->string->split(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(html_entity_decode($this->getSku($item)), 17), + 'text' => $this->string->split($this->prepareText((string)$this->getSku($item)), 17), 'feed' => 290, 'align' => 'right', ]; @@ -156,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 new file mode 100644 index 0000000000000..cfb88dc63f58b --- /dev/null +++ b/app/code/Magento/Sales/Model/RtlTextHandler.php @@ -0,0 +1,63 @@ +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 reverseRtlText(string $string): string + { + $splitText = explode(' ', $string); + $splitTextAmount = count($splitText); + + for ($i = 0; $i < $splitTextAmount; $i++) { + if ($this->isRtlText($splitText[$i])) { + 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]) + ? $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..2faeb17dc2395 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php @@ -0,0 +1,71 @@ +stringUtils = new StringUtils(); + $this->rtlTextHandler = new RtlTextHandler($this->stringUtils); + } + + /** + * @param string $str + * @param bool $isRtl + * @dataProvider provideRtlTexts + */ + public function testIsRtlText(string $str, bool $isRtl): void + { + $this->assertEquals($isRtl, $this->rtlTextHandler->isRtlText($str)); + } + + /** + * @param string $str + * @param bool $isRtl + * @dataProvider provideRtlTexts + */ + public function testReverseRtlText(string $str, bool $isRtl): void + { + $expectedStr = $isRtl ? $this->stringUtils->strrev($str) : $str; + + $this->assertEquals($expectedStr, $this->rtlTextHandler->reverseRtlText($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 + ]; + } +}