Skip to content

Commit d35ce43

Browse files
ENGCOM-7465: Fix Arabic and Hebrew in invoices #27887
- Merge Pull Request #27887 from ihor-sviziev/magento2:fix-arabic-hebrew-in-invoices - Merged commits: 1. 41ab016 2. 876519b 3. 27ec0ae
2 parents 4a8f99d + 27ec0ae commit d35ce43

File tree

4 files changed

+175
-10
lines changed

4 files changed

+175
-10
lines changed

app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
namespace Magento\Sales\Model\Order\Pdf;
88

99
use Magento\Framework\App\Filesystem\DirectoryList;
10+
use Magento\Framework\App\ObjectManager;
1011
use Magento\MediaStorage\Helper\File\Storage\Database;
12+
use Magento\Sales\Model\RtlTextHandler;
1113

1214
/**
1315
* Sales Order PDF abstract model
@@ -53,6 +55,11 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject
5355
*/
5456
protected $_pdf;
5557

58+
/**
59+
* @var RtlTextHandler
60+
*/
61+
private $rtlTextHandler;
62+
5663
/**
5764
* Retrieve PDF
5865
*
@@ -142,6 +149,7 @@ abstract public function getPdf();
142149
* @param \Magento\Sales\Model\Order\Address\Renderer $addressRenderer
143150
* @param array $data
144151
* @param Database $fileStorageDatabase
152+
* @param RtlTextHandler|null $rtlTextHandler
145153
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
146154
*/
147155
public function __construct(
@@ -156,7 +164,8 @@ public function __construct(
156164
\Magento\Framework\Translate\Inline\StateInterface $inlineTranslation,
157165
\Magento\Sales\Model\Order\Address\Renderer $addressRenderer,
158166
array $data = [],
159-
Database $fileStorageDatabase = null
167+
Database $fileStorageDatabase = null,
168+
?RtlTextHandler $rtlTextHandler = null
160169
) {
161170
$this->addressRenderer = $addressRenderer;
162171
$this->_paymentData = $paymentData;
@@ -169,8 +178,8 @@ public function __construct(
169178
$this->_pdfTotalFactory = $pdfTotalFactory;
170179
$this->_pdfItemsFactory = $pdfItemsFactory;
171180
$this->inlineTranslation = $inlineTranslation;
172-
$this->fileStorageDatabase = $fileStorageDatabase ?:
173-
\Magento\Framework\App\ObjectManager::getInstance()->get(Database::class);
181+
$this->fileStorageDatabase = $fileStorageDatabase ?: ObjectManager::getInstance()->get(Database::class);
182+
$this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class);
174183
parent::__construct($data);
175184
}
176185

@@ -501,7 +510,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true)
501510
if ($value !== '') {
502511
$text = [];
503512
foreach ($this->string->split($value, 45, true, true) as $_value) {
504-
$text[] = $_value;
513+
$text[] = $this->rtlTextHandler->reverseRtlText($_value);
505514
}
506515
foreach ($text as $part) {
507516
$page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8');
@@ -518,7 +527,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true)
518527
if ($value !== '') {
519528
$text = [];
520529
foreach ($this->string->split($value, 45, true, true) as $_value) {
521-
$text[] = $_value;
530+
$text[] = $this->rtlTextHandler->reverseRtlText($_value);
522531
}
523532
foreach ($text as $part) {
524533
$page->drawText(strip_tags(ltrim($part)), 285, $this->y, 'UTF-8');

app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
namespace Magento\Sales\Model\Order\Pdf\Items\Invoice;
99

10+
use Magento\Framework\App\ObjectManager;
11+
use Magento\Sales\Model\RtlTextHandler;
12+
1013
/**
1114
* Sales Order Invoice Pdf default items renderer
1215
*/
@@ -19,6 +22,11 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems
1922
*/
2023
protected $string;
2124

25+
/**
26+
* @var RtlTextHandler
27+
*/
28+
private $rtlTextHandler;
29+
2230
/**
2331
* @param \Magento\Framework\Model\Context $context
2432
* @param \Magento\Framework\Registry $registry
@@ -29,6 +37,8 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems
2937
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
3038
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
3139
* @param array $data
40+
* @param RtlTextHandler|null $rtlTextHandler
41+
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
3242
*/
3343
public function __construct(
3444
\Magento\Framework\Model\Context $context,
@@ -39,7 +49,8 @@ public function __construct(
3949
\Magento\Framework\Stdlib\StringUtils $string,
4050
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
4151
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
42-
array $data = []
52+
array $data = [],
53+
?RtlTextHandler $rtlTextHandler = null
4354
) {
4455
$this->string = $string;
4556
parent::__construct(
@@ -52,6 +63,7 @@ public function __construct(
5263
$resourceCollection,
5364
$data
5465
);
66+
$this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class);
5567
}
5668

5769
/**
@@ -70,16 +82,14 @@ public function draw()
7082
// draw Product name
7183
$lines[0] = [
7284
[
73-
// phpcs:ignore Magento2.Functions.DiscouragedFunction
74-
'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true),
85+
'text' => $this->string->split($this->prepareText((string)$item->getName()), 35, true, true),
7586
'feed' => 35
7687
]
7788
];
7889

7990
// draw SKU
8091
$lines[0][] = [
81-
// phpcs:ignore Magento2.Functions.DiscouragedFunction
82-
'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17),
92+
'text' => $this->string->split($this->prepareText((string)$this->getSku($item)), 17),
8393
'feed' => 290,
8494
'align' => 'right',
8595
];
@@ -156,4 +166,16 @@ public function draw()
156166
$page = $pdf->drawLineBlocks($page, [$lineBlock], ['table_header' => true]);
157167
$this->setPage($page);
158168
}
169+
170+
/**
171+
* Returns prepared for PDF text, reversed in case of RTL text
172+
*
173+
* @param string $string
174+
* @return string
175+
*/
176+
private function prepareText(string $string): string
177+
{
178+
// phpcs:ignore Magento2.Functions.DiscouragedFunction
179+
return $this->rtlTextHandler->reverseRtlText(html_entity_decode($string));
180+
}
159181
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Model;
9+
10+
use Magento\Framework\Stdlib\StringUtils;
11+
12+
class RtlTextHandler
13+
{
14+
/**
15+
* @var StringUtils
16+
*/
17+
private $stringUtils;
18+
19+
/**
20+
* @param StringUtils $stringUtils
21+
*/
22+
public function __construct(StringUtils $stringUtils)
23+
{
24+
$this->stringUtils = $stringUtils;
25+
}
26+
27+
/**
28+
* Detect an input string is Arabic
29+
*
30+
* @param string $subject
31+
* @return bool
32+
*/
33+
public function isRtlText(string $subject): bool
34+
{
35+
return (preg_match('/[\p{Arabic}\p{Hebrew}]/u', $subject) > 0);
36+
}
37+
38+
/**
39+
* Reverse text with Arabic characters
40+
*
41+
* @param string $string
42+
* @return string
43+
*/
44+
public function reverseRtlText(string $string): string
45+
{
46+
$splitText = explode(' ', $string);
47+
$splitTextAmount = count($splitText);
48+
49+
for ($i = 0; $i < $splitTextAmount; $i++) {
50+
if ($this->isRtlText($splitText[$i])) {
51+
for ($j = $i + 1; $j < $splitTextAmount; $j++) {
52+
$tmp = $this->isRtlText($splitText[$j])
53+
? $this->stringUtils->strrev($splitText[$j]) : $splitText[$j];
54+
$splitText[$j] = $this->isRtlText($splitText[$i])
55+
? $this->stringUtils->strrev($splitText[$i]) : $splitText[$i];
56+
$splitText[$i] = $tmp;
57+
}
58+
}
59+
}
60+
61+
return implode(' ', $splitText);
62+
}
63+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Test\Unit\Model;
9+
10+
use Magento\Framework\Stdlib\StringUtils;
11+
use Magento\Sales\Model\RtlTextHandler;
12+
use PHPUnit\Framework\TestCase;
13+
14+
class RtlTextHandlerTest extends TestCase
15+
{
16+
/**
17+
* @var RtlTextHandler
18+
*/
19+
private $rtlTextHandler;
20+
21+
/**
22+
* @var StringUtils
23+
*/
24+
private $stringUtils;
25+
26+
protected function setUp(): void
27+
{
28+
$this->stringUtils = new StringUtils();
29+
$this->rtlTextHandler = new RtlTextHandler($this->stringUtils);
30+
}
31+
32+
/**
33+
* @param string $str
34+
* @param bool $isRtl
35+
* @dataProvider provideRtlTexts
36+
*/
37+
public function testIsRtlText(string $str, bool $isRtl): void
38+
{
39+
$this->assertEquals($isRtl, $this->rtlTextHandler->isRtlText($str));
40+
}
41+
42+
/**
43+
* @param string $str
44+
* @param bool $isRtl
45+
* @dataProvider provideRtlTexts
46+
*/
47+
public function testReverseRtlText(string $str, bool $isRtl): void
48+
{
49+
$expectedStr = $isRtl ? $this->stringUtils->strrev($str) : $str;
50+
51+
$this->assertEquals($expectedStr, $this->rtlTextHandler->reverseRtlText($str));
52+
}
53+
54+
public function provideRtlTexts(): array
55+
{
56+
return [
57+
['Adeline Jacobson', false],//English
58+
['Odell Fisher', false],//English
59+
['Панов Аркадий Львович', false],//Russian
60+
['Вероника Сергеевна Игнатьева', false],//Russian
61+
['Mehmet Arnold-Döring', false],//German
62+
['Herr Prof. Dr. Gerald Schüler B.A.', false],//German
63+
['نديم مقداد نعمان القحطاني', true],//Arabic
64+
['شهاب الفرحان', true],//Arabic
65+
['צבר קרליבך', true],//Hebrew
66+
['גורי מייזליש', true],//Hebrew
67+
['اتابک بهشتی', true],//Persian
68+
['مهداد محمدی', true],//Persian
69+
];
70+
}
71+
}

0 commit comments

Comments
 (0)