Skip to content

Commit

Permalink
Fix Arabic & Hebrew in invoices
Browse files Browse the repository at this point in the history
Use rtl text handler in invoice PDF generation
  • Loading branch information
ihor-sviziev committed Apr 21, 2020
1 parent 876519b commit 27ec0ae
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 86 deletions.
58 changes: 1 addition & 57 deletions app/code/Magento/Sales/Model/Order/Address/Renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

/**
Expand All @@ -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;
}
}
21 changes: 14 additions & 7 deletions app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,6 +55,11 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject
*/
protected $_pdf;

/**
* @var RtlTextHandler
*/
private $rtlTextHandler;

/**
* Retrieve PDF
*
Expand Down Expand Up @@ -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(
Expand All @@ -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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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);
}

/**
Expand All @@ -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',
];
Expand Down Expand Up @@ -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));
}
}
15 changes: 10 additions & 5 deletions app/code/Magento/Sales/Model/RtlTextHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class RtlTextHandler
*/
private $stringUtils;

/**
* @param StringUtils $stringUtils
*/
public function __construct(StringUtils $stringUtils)
{
$this->stringUtils = $stringUtils;
Expand All @@ -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;
}
Expand Down
19 changes: 12 additions & 7 deletions app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Sales\Test\Unit\Model;

Expand Down Expand Up @@ -26,24 +31,24 @@ protected function setUp(): void

/**
* @param string $str
* @param bool $expected
* @param bool $isRtl
* @dataProvider provideRtlTexts
*/
public function testIsRtlText(string $str, bool $expected): void
public function testIsRtlText(string $str, bool $isRtl): void
{
$this->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
Expand Down

2 comments on commit 27ec0ae

@fisal77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent! You are hero!... you help Arabic and Hebrew (RTL) in Magento 2

@ihor-sviziev
Copy link
Contributor Author

@ihor-sviziev ihor-sviziev commented on 27ec0ae May 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fisal77 Enjoy 😉

Please sign in to comment.