TemplateParser   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 0
dl 0
loc 264
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B parseTranslationTags() 0 53 5
A applyTranslationTags() 0 14 1
A getHtmlAttribute() 0 4 1
A getTranslateData() 0 27 3
A getTagLocation() 0 8 2
A _returnViewData() 0 11 2
A extractVariablesFromLocalizationParameters() 0 11 2
A findEndOfTag() 0 22 4
1
<?php
2
3
namespace BeyondCode\InlineTranslation;
4
5
use Exception;
6
7
class TemplateParser
8
{
9
    const DATA_TRANSLATE = 'data-translate';
10
11
    /**
12
     * @var array
13
     */
14
    private $viewData;
15
16
    /**
17
     * List of simple tags
18
     *
19
     * @var array
20
     */
21
    protected $allowedTags = [
22
        'legend' => 'Caption for the fieldset element',
23
        'label' => 'Label for an input element.',
24
        'button' => 'Push button',
25
        'a' => 'Link label',
26
        'b' => 'Bold text',
27
        'strong' => 'Strong emphasized text',
28
        'i' => 'Italic text',
29
        'em' => 'Emphasized text',
30
        'u' => 'Underlined text',
31
        'sup' => 'Superscript text',
32
        'sub' => 'Subscript text',
33
        'span' => 'Span element',
34
        'small' => 'Smaller text',
35
        'big' => 'Bigger text',
36
        'address' => 'Contact information',
37
        'blockquote' => 'Long quotation',
38
        'q' => 'Short quotation',
39
        'cite' => 'Citation',
40
        'caption' => 'Table caption',
41
        'abbr' => 'Abbreviated phrase',
42
        'acronym' => 'An acronym',
43
        'var' => 'Variable part of a text',
44
        'dfn' => 'Term',
45
        'strike' => 'Strikethrough text',
46
        'del' => 'Deleted text',
47
        'ins' => 'Inserted text',
48
        'h1' => 'Heading level 1',
49
        'h2' => 'Heading level 2',
50
        'h3' => 'Heading level 3',
51
        'h4' => 'Heading level 4',
52
        'h5' => 'Heading level 5',
53
        'h6' => 'Heading level 6',
54
        'center' => 'Centered text',
55
        'select' => 'List options',
56
        'img' => 'Image',
57
        'input' => 'Form element',
58
        'p' => 'Generic Paragraph',
59
    ];
60
61
    /**
62
     * TemplateParser constructor.
63
     * @param array $viewData
64
     */
65
    public function __construct(array $viewData = [])
66
    {
67
        $this->viewData = $viewData;
68
    }
69
70
    public function parseTranslationTags(string &$viewContent)
71
    {
72
        $nextTag = 0;
73
74
        $tags = implode('|', array_keys($this->allowedTags));
75
76
        $tagRegExp = '#<(' . $tags . ')(/?>| \s*[^>]*+/?>)#iSU';
77
78
        $tagMatch = [];
79
80
        while (preg_match($tagRegExp, $viewContent, $tagMatch, PREG_OFFSET_CAPTURE, $nextTag)) {
81
            $tagName = strtolower($tagMatch[1][0]);
82
83
            if (substr($tagMatch[0][0], -2) == '/>') {
84
                $tagClosurePos = $tagMatch[0][1] + strlen($tagMatch[0][0]);
85
            } else {
86
                $tagClosurePos = $this->findEndOfTag($viewContent, $tagName, $tagMatch[0][1]);
87
            }
88
89
            if ($tagClosurePos === false) {
90
                $nextTag += strlen($tagMatch[0][0]);
91
                continue;
92
            }
93
94
            $tagLength = $tagClosurePos - $tagMatch[0][1];
95
            $tagStartLength = strlen($tagMatch[0][0]);
96
97
            $tagHtml = $tagMatch[0][0] . substr(
98
                    $viewContent,
99
                    $tagMatch[0][1] + $tagStartLength,
100
                    $tagLength - $tagStartLength
101
                );
102
103
            $tagClosurePos = $tagMatch[0][1] + strlen($tagHtml);
104
105
            $trArr = $this->getTranslateData(
106
                '/({{|{!!)\s*(__|\@lang|\@trans|\@choice|trans|trans_choice)\((.*?)\)\s*(}}|!!})/m',
107
                $tagHtml,
108
                ['tagName' => $tagName, 'tagList' => $this->allowedTags]
109
            );
110
111
            if (!empty($trArr)) {
112
                $trArr = array_unique($trArr);
113
114
                $tagHtml = $this->applyTranslationTags($tagHtml, $tagName, $trArr);
115
116
                $tagClosurePos = $tagMatch[0][1] + strlen($tagHtml);
117
                $viewContent = substr_replace($viewContent, $tagHtml, $tagMatch[0][1], $tagLength);
118
            }
119
120
            $nextTag = $tagClosurePos;
121
        }
122
    }
123
124
    /**
125
     * Format translation for simple tags.  Added translate mode attribute for vde requests.
126
     *
127
     * @param string $tagHtml
128
     * @param string $tagName
129
     * @param array $trArr
130
     * @return string
131
     */
132
    protected function applyTranslationTags($tagHtml, $tagName, $trArr)
133
    {
134
        $simpleTags = substr(
135
                $tagHtml,
136
                0,
137
                strlen($tagName) + 1
138
            ) . ' ' . $this->getHtmlAttribute(
139
                self::DATA_TRANSLATE,
140
                htmlspecialchars('[' . join(',', $trArr) . ']')
141
            );
142
143
        $simpleTags .= substr($tagHtml, strlen($tagName) + 1);
144
        return $simpleTags;
145
    }
146
147
    /**
148
     * Get html element attribute
149
     *
150
     * @param string $name
151
     * @param string $value
152
     * @return string
153
     */
154
    private function getHtmlAttribute($name, $value)
155
    {
156
        return $name . '="' . $value . '"';
157
    }
158
159
    /**
160
     * Get translate data by regexp
161
     *
162
     * @param string $regexp
163
     * @param string $text
164
     * @param array $options
165
     * @return array
166
     */
167
    private function getTranslateData($regexp, $text, $options = [])
168
    {
169
        $trArr = [];
170
        $nextRegexOffset = 0;
171
        while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE, $nextRegexOffset)) {
172
173
            extract($this->viewData, EXTR_OVERWRITE);
174
            $translationParameters = $this->extractVariablesFromLocalizationParameters($matches[3][0]);
175
176
            try {
177
                $translated = eval('return ' . $matches[2][0] . '(' . $matches[3][0] . ');');
178
            } catch (Exception $e) {
179
                $translated = '';
180
            }
181
182
            $trArr[] = json_encode(
183
                [
184
                    'translated' => $translated,
185
                    'original' => $translationParameters[0],
186
                    'parameters' => $translationParameters[1] ?? [],
187
                    'location' => htmlspecialchars_decode($this->getTagLocation($matches, $options)),
188
                ]
189
            );
190
            $nextRegexOffset = $matches[4][1];
191
        }
192
        return $trArr;
193
    }
194
195
    /**
196
     * Get tag location
197
     *
198
     * @param array $matches
199
     * @param array $options
200
     * @return string
201
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
202
     */
203
    protected function getTagLocation($matches, $options)
0 ignored issues
show
Unused Code introduced by
The parameter $matches is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
204
    {
205
        $tagName = strtolower($options['tagName']);
206
        if (isset($options['tagList'][$tagName])) {
207
            return $options['tagList'][$tagName];
208
        }
209
        return ucfirst($tagName) . ' Text';
210
    }
211
212
    private function _returnViewData()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
213
    {
214
        $args = func_get_args();
215
        $return = [];
216
217
        foreach ($args as $argument) {
218
            $return[] = $argument;
219
        }
220
221
        return $return;
222
    }
223
224
    /**
225
     * @param string $localizationParameters
226
     * @return array|mixed
227
     */
228
    private function extractVariablesFromLocalizationParameters($localizationParameters)
229
    {
230
        try {
231
            extract($this->viewData, EXTR_OVERWRITE);
232
            return eval('return $this->_returnViewData(' . $localizationParameters . ');');
233
        } catch (Exception $e) {
234
            return [
235
                $localizationParameters
236
            ];
237
        }
238
    }
239
240
    /**
241
     * Find end of tag
242
     *
243
     * @param string $body
244
     * @param string $tagName
245
     * @param int $from
246
     * @return bool|int return false if end of tag is not found
247
     */
248
    private function findEndOfTag($body, $tagName, $from)
249
    {
250
        $openTag = '<' . $tagName;
251
        $closeTag = '</' . $tagName;
252
        $tagLength = strlen($tagName);
253
        $length = $tagLength + 1;
254
        $end = $from + 1;
255
256
        while (substr_count($body, $openTag, $from, $length) !== substr_count($body, $closeTag, $from, $length)) {
257
            $end = strpos($body, $closeTag, $end + $tagLength + 1);
258
            if ($end === false) {
259
                return false;
260
            }
261
            $length = $end - $from + $tagLength + 3;
262
        }
263
264
        if (preg_match('#<\\\\?\/' . $tagName . '\s*?>#i', $body, $tagMatch, null, $end)) {
265
            return $end + strlen($tagMatch[0]);
266
        } else {
267
            return false;
268
        }
269
    }
270
}
271