Passed
Push — master ( f510e9...9822a2 )
by Paul
10:29
created

Translation   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Test Coverage

Coverage 6.09%

Importance

Changes 0
Metric Value
wmc 40
eloc 108
dl 0
loc 267
ccs 7
cts 115
cp 0.0609
rs 9.2
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A render() 0 13 2
A exclude() 0 3 1
A search() 0 16 5
A results() 0 5 1
A getEntryString() 0 5 2
A entries() 0 9 2
A renderResults() 0 22 4
A all() 0 10 2
A filter() 0 13 4
A reset() 0 3 1
A renderAll() 0 9 2
A extractEntriesFromPotFile() 0 14 4
A normalizeEntryString() 0 6 2
A normalizeSettings() 0 10 3
A normalize() 0 11 2
A translations() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like Translation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Translation, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use Exception;
6
use GeminiLabs\Sepia\PoParser\Parser;
7
use GeminiLabs\SiteReviews\Database\OptionManager;
8
use GeminiLabs\SiteReviews\Helpers\Arr;
9
use GeminiLabs\SiteReviews\Helpers\Str;
10
use GeminiLabs\SiteReviews\Modules\Html\Template;
11
12
class Translation
13
{
14
    const CONTEXT_ADMIN_KEY = 'admin-text';
15
    const SEARCH_THRESHOLD = 3;
16
17
    /**
18
     * @var array|null
19
     */
20
    protected $entries;
21
22
    /**
23
     * @var array
24
     */
25
    protected $results;
26
27
    /**
28
     * Returns all saved custom translations with translation context.
29
     * @return array
30
     */
31
    public function all()
32
    {
33
        $translations = $this->translations();
34
        $entries = $this->filter($translations, $this->entries())->results();
35
        array_walk($translations, function (&$entry) use ($entries) {
36
            $entry['desc'] = array_key_exists($entry['id'], $entries)
37
                ? $this->getEntryString($entries[$entry['id']], 'msgctxt')
38
                : '';
39
        });
40
        return $translations;
41
    }
42
43
    /**
44
     * @return array
45
     */
46
    public function entries()
47
    {
48
        if (!isset($this->entries)) {
49
            $potFile = glsr()->path(glsr()->languages.'/'.glsr()->id.'.pot');
50
            $entries = $this->extractEntriesFromPotFile($potFile);
51
            $entries = glsr()->filterArray('translation/entries', $entries);
52
            $this->entries = $entries;
53
        }
54
        return $this->entries;
55
    }
56
57
    /**
58
     * @param array|null $entriesToExclude
59
     * @param array|null $entries
60
     * @return static
61
     */
62
    public function exclude($entriesToExclude = null, $entries = null)
63
    {
64
        return $this->filter($entriesToExclude, $entries, false);
65
    }
66
67
    /**
68
     * @param string $potFile
69
     * @return array
70
     */
71
    public function extractEntriesFromPotFile($potFile, array $entries = [])
72
    {
73
        try {
74
            $potEntries = $this->normalize(Parser::parseFile($potFile)->getEntries());
75
            foreach ($potEntries as $key => $entry) {
76
                if (Str::contains(Arr::get($entry, 'msgctxt'), static::CONTEXT_ADMIN_KEY)) {
77
                    continue;
78
                }
79
                $entries[html_entity_decode($key, ENT_COMPAT, 'UTF-8')] = $entry;
80
            }
81
        } catch (Exception $e) {
82
            glsr_log()->error($e->getMessage());
83
        }
84
        return $entries;
85
    }
86
87
    /**
88
     * @param array|null $filterWith
89
     * @param array|null $entries
90
     * @param bool $intersect
91
     * @return static
92
     */
93
    public function filter($filterWith = null, $entries = null, $intersect = true)
94
    {
95
        if (!is_array($entries)) {
96
            $entries = $this->results;
97
        }
98
        if (!is_array($filterWith)) {
99
            $filterWith = $this->translations();
100
        }
101
        $keys = array_flip(wp_list_pluck($filterWith, 'id'));
102
        $this->results = $intersect
103
            ? array_intersect_key($entries, $keys)
104
            : array_diff_key($entries, $keys);
105
        return $this;
106
    }
107
108
    /**
109
     * @param string $template
110
     * @return string
111
     */
112
    public function render($template, array $entry)
113
    {
114
        $data = array_combine(
115
            array_map(function ($key) { return 'data.'.$key; }, array_keys($entry)),
116
            $entry
117
        );
118
        $data['data.class'] = $data['data.error'] = '';
119
        if (false === Arr::searchByKey($entry['s1'], $this->entries(), 'msgid')) { // @todo handle htmlentities i.e. &rarr;
120
            $data['data.class'] = 'is-invalid';
121
            $data['data.error'] = _x('This custom translation is no longer valid as the original text has been changed or removed.', 'admin-text', 'site-reviews');
122
        }
123
        return glsr(Template::class)->build('partials/translations/'.$template, [
124
            'context' => array_map('esc_html', $data),
125
        ]);
126
    }
127
128
    /**
129
     * Returns a rendered string of all saved custom translations with translation context.
130
     * @return string
131
     */
132
    public function renderAll()
133
    {
134
        $rendered = '';
135
        foreach ($this->all() as $index => $entry) {
136
            $entry['index'] = $index;
137
            $entry['prefix'] = OptionManager::databaseKey();
138
            $rendered .= $this->render($entry['type'], $entry);
139
        }
140
        return $rendered;
141
    }
142
143
    /**
144
     * @param bool $resetAfterRender
145
     * @return string
146
     */
147
    public function renderResults($resetAfterRender = true)
148
    {
149
        $rendered = '';
150
        foreach ($this->results as $id => $entry) {
151
            $data = [
152
                'desc' => $this->getEntryString($entry, 'msgctxt'),
153
                'id' => $id,
154
                'p1' => $this->getEntryString($entry, 'msgid_plural'),
155
                's1' => $this->getEntryString($entry, 'msgid'),
156
            ];
157
            $text = !empty($data['p1'])
158
                ? sprintf('%s | %s', $data['s1'], $data['p1'])
159
                : $data['s1'];
160
            $rendered .= $this->render('result', [
161
                'entry' => json_encode($data, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
162
                'text' => wp_strip_all_tags($text),
163
            ]);
164
        }
165
        if ($resetAfterRender) {
166
            $this->reset();
167
        }
168
        return $rendered;
169
    }
170
171
    /**
172
     * @return void
173
     */
174
    public function reset()
175
    {
176
        $this->results = [];
177
    }
178
179
    /**
180
     * @return array
181
     */
182
    public function results()
183
    {
184
        $results = $this->results;
185
        $this->reset();
186
        return $results;
187
    }
188
189
    /**
190
     * @param string $needle
191
     * @return static
192
     */
193
    public function search($needle = '')
194
    {
195
        $this->reset();
196
        $needle = trim(strtolower($needle));
197
        foreach ($this->entries() as $key => $entry) {
198
            $single = strtolower($this->getEntryString($entry, 'msgid'));
199
            $plural = strtolower($this->getEntryString($entry, 'msgid_plural'));
200
            if (strlen($needle) < static::SEARCH_THRESHOLD) {
201
                if (in_array($needle, [$single, $plural])) {
202
                    $this->results[$key] = $entry;
203
                }
204
            } elseif (Str::contains($needle, sprintf('%s %s', $single, $plural))) {
205
                $this->results[$key] = $entry;
206
            }
207
        }
208
        return $this;
209
    }
210
211
    /**
212
     * Store the translations to avoid unnecessary loops.
213
     * @return array
214
     */
215 8
    public function translations()
216
    {
217 8
        static $translations;
218 8
        if (empty($translations)) {
219 8
            $settings = glsr(OptionManager::class)->get('settings');
220 8
            $translations = isset($settings['strings'])
221
                ? $this->normalizeSettings((array) $settings['strings'])
222 8
                : [];
223
        }
224 8
        return $translations;
225
    }
226
227
    /**
228
     * @param string $key
229
     * @return string
230
     */
231
    protected function getEntryString(array $entry, $key)
232
    {
233
        return isset($entry[$key])
234
            ? implode('', (array) $entry[$key])
235
            : '';
236
    }
237
238
    /**
239
     * @return array
240
     */
241
    protected function normalize(array $entries)
242
    {
243
        $keys = [
244
            'msgctxt', 'msgid', 'msgid_plural', 'msgstr', 'msgstr[0]', 'msgstr[1]',
245
        ];
246
        array_walk($entries, function (&$entry) use ($keys) {
247
            foreach ($keys as $key) {
248
                $entry = $this->normalizeEntryString($entry, $key);
249
            }
250
        });
251
        return $entries;
252
    }
253
254
    /**
255
     * @param string $key
256
     * @return array
257
     */
258
    protected function normalizeEntryString(array $entry, $key)
259
    {
260
        if (isset($entry[$key])) {
261
            $entry[$key] = $this->getEntryString($entry, $key);
262
        }
263
        return $entry;
264
    }
265
266
    /**
267
     * @return array
268
     */
269
    protected function normalizeSettings(array $strings)
270
    {
271
        $defaultString = array_fill_keys(['id', 's1', 's2', 'p1', 'p2'], '');
272
        $strings = array_filter($strings, 'is_array');
273
        foreach ($strings as &$string) {
274
            $string['type'] = isset($string['p1']) ? 'plural' : 'single';
275
            $string = wp_parse_args($string, $defaultString);
276
        }
277
        return array_filter($strings, function ($string) {
278
            return !empty($string['id']);
279
        });
280
    }
281
}
282