Passed
Push — develop ( 442c9f...b0c978 )
by Paul
13:58
created

Text::name()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 25
ccs 23
cts 23
cp 1
rs 9.6
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 3
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Helpers;
4
5
use GeminiLabs\SiteReviews\Modules\Html\Builder;
6
use GeminiLabs\SiteReviews\Modules\Sanitizers\SanitizeTextHtml;
7
8
class Text
9
{
10
    public static function excerpt(?string $text, int $limit = 55, bool $splitWords = true): string
11
    {
12
        [$text, $map] = static::replaceTags(static::normalize($text));
13
        $excerptLength = $limit;
14
        if ($splitWords) {
15
            $excerpt = static::words($text, $limit);
16
            $excerptLength = mb_strlen($excerpt);
17
        }
18
        $paragraphs = static::extractParagraphs($text, $excerptLength);
19
        $text = implode(PHP_EOL, $paragraphs);
20
        return static::restoreTags($text, $map);
21
    }
22
23 29
    public static function initials(?string $name, string $initialPunctuation = ''): string
24
    {
25 29
        preg_match_all('/(?<=\s|\b)\p{L}/u', (string) $name, $matches); // match the first letter of each word in the name
26 29
        $result = (string) array_reduce($matches[0], function ($carry, $word) use ($initialPunctuation) {
27 29
            $initial = mb_substr($word, 0, 1, 'UTF-8');
28 29
            $initial = mb_strtoupper($initial, 'UTF-8');
29 29
            return $carry.$initial.$initialPunctuation;
30 29
        });
31 29
        return trim($result);
32
    }
33
34
    /**
35
     * @param string $nameFormat  first|first_initial|last_initial|initials
36
     * @param string $initialType period|period_space|space
37
     */
38 11
    public static function name(?string $name, string $nameFormat = '', string $initialType = 'space'): string
39
    {
40 11
        $names = preg_split('/\W/u', (string) $name, 0, PREG_SPLIT_NO_EMPTY);
41 11
        $firstName = (string) array_shift($names);
42 11
        $lastName = (string) array_pop($names);
43 11
        $nameFormat = Str::restrictTo('first,first_initial,last_initial,initials', $nameFormat, '');
44 11
        $initialType = Str::restrictTo('period,period_space,space', $initialType, 'space');
45 11
        $initialTypes = [
46 11
            'period' => '.',
47 11
            'period_space' => '. ',
48 11
            'space' => ' ',
49 11
        ];
50 11
        $initialPunctuation = $initialTypes[$initialType];
51 11
        if ('initials' === $nameFormat) {
52 3
            return static::initials($name, $initialPunctuation);
53
        }
54 8
        $firstNameInitial = static::initials($firstName).$initialPunctuation;
55 8
        $lastNameInitial = $lastName ? static::initials($lastName).$initialPunctuation : '';
56 8
        $nameFormats = [
57 8
            'first' => $firstName,
58 8
            'first_initial' => $firstNameInitial.$lastName,
59 8
            'last' => $lastName,
60 8
            'last_initial' => "{$firstName} {$lastNameInitial}",
61 8
        ];
62 8
        return trim((string) Arr::get($nameFormats, $nameFormat, $name));
63
    }
64
65
    public static function normalize(?string $text): string
66
    {
67
        $text = (new SanitizeTextHtml($text))->run();
68
        $text = strip_shortcodes($text);
69
        $text = excerpt_remove_blocks($text); // just in case...
70
        $text = str_replace(']]>', ']]&gt;', $text);
71
        $text = normalize_whitespace($text); // normalize EOL characters and strip duplicate whitespace.
72
        $text = preg_replace('/\R{1,}/u', PHP_EOL.PHP_EOL, $text); // replace all line-breaks with a double line break
73
        $text = wptexturize($text); // replace common plain text characters with formatted entities.
74
        $text = ent2ncr($text); // convert named entities into numbered entities.
75
        $text = convert_chars($text); // converts lone & characters into &#038;
76
        $text = convert_invalid_entities($text); // convert invalid Unicode references range to valid range.
77
        $text = convert_smilies($text); // convert text smilies to emojis.
78
        $text = wp_specialchars_decode($text);
79
        return $text;
80
    }
81
82
    public static function text(?string $text): string
83
    {
84
        $text = static::normalize($text);
85
        if ($paragraphs = preg_split('/\R+/um', $text)) { // split text by line-breaks
86
            $paragraphs = array_map('trim', $paragraphs); // trim paragraphs
87
            $text = implode(PHP_EOL.PHP_EOL, $paragraphs);
88
        }
89
        return wpautop($text);
90
    }
91
92
    public static function wordCount(?string $text): int
93
    {
94
        $text = wp_strip_all_tags((string) $text, true);
95
        $iterator = static::iterator($text);
96
        if (empty($iterator)) {
97
            return count(preg_split('/[^\p{L}\p{N}\']+/u', $text));
98
        }
99
        $wordCount = 0;
100
        foreach ($iterator->getPartsIterator() as $part) {
101
            if (\IntlBreakIterator::WORD_NONE !== $iterator->getRuleStatus()) {
102
                ++$wordCount;
103
            }
104
        }
105
        return $wordCount;
106
    }
107
108
    public static function words(?string $text, int $limit = 0): string
109
    {
110
        $stringLength = static::excerptIntlSplit((string) $text, $limit);
111
        return mb_substr((string) $text, 0, $stringLength);
112
    }
113
114
    protected static function excerptIntlSplit(string $text, int $limit): int
115
    {
116
        $iterator = static::iterator($text);
117
        if (empty($iterator)) {
118
            return static::excerptSplit((string) $text, $limit);
119
        }
120
        $stringLength = 0;
121
        $wordCount = 0;
122
        foreach ($iterator->getPartsIterator() as $part) {
123
            $stringLength += mb_strlen($part);
124
            if (\IntlBreakIterator::WORD_NONE === $iterator->getRuleStatus()) {
125
                continue;
126
            }
127
            if (++$wordCount === $limit) {
128
                break;
129
            }
130
        }
131
        return $stringLength;
132
    }
133
134
    protected static function excerptSplit(string $text, int $limit): int
135
    {
136
        $length = mb_strlen($text);
137
        if ($limit < 1) {
138
            return $length;
139
        }
140
        preg_match('/^\s*+(?:\S++\s*+){1,'.$limit.'}/u', $text, $matches);
141
        $match = $matches[0] ?? '';
142
        if ($length === mb_strlen($match)) {
143
            return $length;
144
        }
145
        return mb_strlen(rtrim($match));
146
    }
147
148
    protected static function extractParagraphs(string $text, int $length): array
149
    {
150
        $paragraphs = preg_split('/\R+/um', $text);
151
        $paragraphs = array_map('trim', $paragraphs);
152
        $lastIndex = count($paragraphs) - 1;
153
        foreach ($paragraphs as $index => &$paragraph) {
154
            $paragraphLength = mb_strlen($paragraph);
155
            if ($length > $paragraphLength || ($length === $paragraphLength && $index === $lastIndex)) {
156
                $paragraph = sprintf('<p>%s</p>', $paragraph);
157
                $length -= $paragraphLength;
158
                continue;
159
            }
160
            if ($length > 0) {
161
                $hidden = mb_substr($paragraph, $length);
162
                $visible = mb_substr($paragraph, 0, $length);
163
                $paragraph = glsr(Builder::class)->p([
164
                    'class' => 'glsr-hidden-text',
165
                    'data-show-less' => __('Show less', 'site-reviews'),
166
                    'data-show-more' => __('Show more', 'site-reviews'),
167
                    'data-trigger' => glsr_get_option('reviews.excerpts_action') ?: 'expand',
168
                    'text' => sprintf('%s<span class="glsr-hidden">%s</span>', $visible, $hidden),
169
                ]);
170
                $length = 0;
171
                continue;
172
            }
173
            $paragraph = glsr(Builder::class)->p([
174
                'class' => 'glsr-hidden',
175
                'text' => $paragraph,
176
            ]);
177
        }
178
        return $paragraphs;
179
    }
180
181
    /**
182
     * @return \IntlRuleBasedBreakIterator|null
183
     */
184
    protected static function iterator(string $text)
185
    {
186
        if (!extension_loaded('intl')) {
187
            return null;
188
        }
189
        $iterator = \IntlRuleBasedBreakIterator::createWordInstance('');
190
        $normalizedText = \Normalizer::normalize($text);
191
        if (empty($normalizedText)) {
192
            return null;
193
        }
194
        if (!$iterator->setText($normalizedText)) {
195
            return null;
196
        }
197
        return $iterator;
198
    }
199
200
    protected static function replaceTags(string $text): array
201
    {
202
        $map = [];
203
        $result = preg_replace_callback('|<([a-z]+)[^>]*?>.*?</\\1>|siu', function ($matches) use (&$map) {
204
            $map[] = $matches[0];
205
            return '⍈';
206
        }, $text);
207
        return is_null($result)
208
            ? [$text, []]
209
            : [$result, $map];
210
    }
211
212
    protected static function restoreTags(string $text, array $map = []): string
213
    {
214
        if (empty($map)) {
215
            return $text;
216
        }
217
        $i = 0;
218
        $result = preg_replace_callback('|⍈|u', function ($matches) use (&$i, $map) {
219
            return $map[$i++] ?? '';
220
        }, $text);
221
        return $result ?? $text;
222
    }
223
}
224