Passed
Push — master ( eeeeb5...9c97a1 )
by Paul
03:55
created

Translation::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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