Passed
Push — develop ( 56da09...d1ee6a )
by Andrew
07:12
created

Text::extractTextFromSuperTable()   B

Complexity

Conditions 11
Paths 27

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 19
nc 27
nop 2
dl 0
loc 35
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS 3.x
4
 *
5
 * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
6
 * and flexible
7
 *
8
 * @link      https://nystudio107.com
9
 * @copyright Copyright (c) 2017 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\helpers;
13
14
use nystudio107\seomatic\helpers\Field as FieldHelper;
15
16
use nystudio107\seomatic\Seomatic;
17
18
use craft\elements\db\MatrixBlockQuery;
19
use craft\elements\db\TagQuery;
20
use craft\elements\MatrixBlock;
21
use craft\elements\Tag;
22
23
use yii\base\InvalidConfigException;
24
25
use verbb\supertable\elements\SuperTableBlockElement as SuperTableBlock;
0 ignored issues
show
Bug introduced by
The type verbb\supertable\elements\SuperTableBlockElement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use verbb\supertable\elements\db\SuperTableBlockQuery;
0 ignored issues
show
Bug introduced by
The type verbb\supertable\elements\db\SuperTableBlockQuery was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
28
use benf\neo\elements\db\BlockQuery as NeoBlockQuery;
0 ignored issues
show
Bug introduced by
The type benf\neo\elements\db\BlockQuery was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
29
use benf\neo\elements\Block as NeoBlock;
0 ignored issues
show
Bug introduced by
The type benf\neo\elements\Block was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
31
use Stringy\Stringy;
32
33
use PhpScience\TextRank\TextRankFacade;
34
use PhpScience\TextRank\Tool\StopWords\StopWordsAbstract;
35
36
/**
37
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
38
 * @package   Seomatic
39
 * @since     3.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
40
 */
41
class Text
42
{
43
    // Constants
44
    // =========================================================================
45
46
    const LANGUAGE_MAP = [
47
        'en' => 'English',
48
        'fr' => 'French',
49
        'de' => 'German',
50
        'it' => 'Italian',
51
        'no' => 'Norwegian',
52
        'es' => 'Spanish',
53
    ];
54
55
    // Public Static Methods
56
    // =========================================================================
57
58
    /**
59
     * Truncates the string to a given length. If $substring is provided, and
60
     * truncating occurs, the string is further truncated so that the substring
61
     * may be appended without exceeding the desired length.
62
     *
63
     * @param  string $string    The string to truncate
64
     * @param  int    $length    Desired length of the truncated string
65
     * @param  string $substring The substring to append if it can fit
66
     *
67
     * @return string with the resulting $str after truncating
68
     */
69
    public static function truncate($string, $length, $substring = '…'): string
70
    {
71
        $result = $string;
72
73
        if (!empty($string)) {
74
            $string = strip_tags($string);
75
            $result = (string)Stringy::create($string)->truncate($length, $substring);
76
        }
77
78
        return $result;
79
    }
80
81
    /**
82
     * Truncates the string to a given length, while ensuring that it does not
83
     * split words. If $substring is provided, and truncating occurs, the
84
     * string is further truncated so that the substring may be appended without
85
     * exceeding the desired length.
86
     *
87
     * @param  string $string    The string to truncate
88
     * @param  int    $length    Desired length of the truncated string
89
     * @param  string $substring The substring to append if it can fit
90
     *
91
     * @return string with the resulting $str after truncating
92
     */
93
    public static function truncateOnWord($string, $length, $substring = '…'): string
94
    {
95
        $result = $string;
96
97
        if (!empty($string)) {
98
            $string = strip_tags($string);
99
            $result = (string)Stringy::create($string)->safeTruncate($length, $substring);
100
        }
101
102
        return $result;
103
    }
104
105
    /**
106
     * Extract plain old text from a field
107
     *
108
     * @param $field
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
109
     *
110
     * @return string
111
     */
112
    public static function extractTextFromField($field): string
113
    {
114
        if (empty($field)) {
115
            return '';
116
        }
117
        if ($field instanceof MatrixBlockQuery
118
            || (\is_array($field) && $field[0] instanceof MatrixBlock)) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
119
            $result = self::extractTextFromMatrix($field);
120
        } elseif ($field instanceof NeoBlockQuery
121
            || (\is_array($field) && $field[0] instanceof NeoBlock)) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
122
            $result = self::extractTextFromNeo($field);
123
        } elseif ($field instanceof SuperTableBlockQuery
124
            || (\is_array($field) && $field[0] instanceof SuperTableBlock)) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
125
            $result = self::extractTextFromSuperTable($field);
126
        } elseif ($field instanceof TagQuery
127
            || (\is_array($field) && $field[0] instanceof Tag)) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
128
            $result = self::extractTextFromTags($field);
129
        } else {
130
            if (\is_array($field)) {
131
                $result = strip_tags((string)$field[0]);
132
            } else {
133
                $result = strip_tags((string)$field);
134
            }
135
        }
136
137
        return $result;
138
    }
139
140
    /**
141
     * Extract concatenated text from all of the tags in the $tagElement and
142
     * return as a comma-delimited string
143
     *
144
     * @param TagQuery|Tag[] $tags
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
145
     *
146
     * @return string
147
     */
148
    public static function extractTextFromTags($tags): string
149
    {
150
        if (empty($tags)) {
151
            return '';
152
        }
153
        $result = '';
154
        // Iterate through all of the matrix blocks
155
        if ($tags instanceof TagQuery) {
156
            $tags = $tags->all();
157
        }
158
        foreach ($tags as $tag) {
159
            $result .= $tag->title.', ';
160
        }
161
        $result = rtrim($result, ', ');
162
163
        return $result;
164
    }
165
166
    /**
167
     * Extract text from all of the blocks in a matrix field, concatenating it
168
     * together.
169
     *
170
     * @param MatrixBlockQuery|MatrixBlock[] $blocks
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
171
     * @param string                         $fieldHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
172
     *
173
     * @return string
174
     */
175
    public static function extractTextFromMatrix($blocks, $fieldHandle = ''): string
176
    {
177
        if (empty($blocks)) {
178
            return '';
179
        }
180
        $result = '';
181
        // Iterate through all of the matrix blocks
182
        if ($blocks instanceof MatrixBlockQuery) {
183
            $blocks = $blocks->all();
184
        }
185
        foreach ($blocks as $block) {
186
            try {
187
                $matrixBlockTypeModel = $block->getType();
188
            } catch (InvalidConfigException $e) {
189
                $matrixBlockTypeModel = null;
190
            }
191
            // Find any text fields inside of the matrix block
192
            if ($matrixBlockTypeModel) {
193
                $fieldClasses = FieldHelper::FIELD_CLASSES[FieldHelper::TEXT_FIELD_CLASS_KEY];
194
                $fields = $matrixBlockTypeModel->getFields();
195
196
                foreach ($fields as $field) {
197
                    /** @var array $fieldClasses */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
198
                    foreach ($fieldClasses as $fieldClassKey) {
199
                        if ($field instanceof $fieldClassKey) {
200
                            if ($field->handle === $fieldHandle || empty($fieldHandle)) {
201
                                $result .= self::extractTextFromField($block[$field->handle]).' ';
202
                            }
203
                        }
204
                    }
205
                }
206
            }
207
        }
208
209
        return $result;
210
    }
211
212
    /**
213
     * Extract text from all of the blocks in a Neo field, concatenating it
214
     * together.
215
     *
216
     * @param NeoBlockQuery|NeoBlock[] $blocks
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
217
     * @param string                         $fieldHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 19 spaces after parameter type; 25 found
Loading history...
218
     *
219
     * @return string
220
     */
221
    public static function extractTextFromNeo($blocks, $fieldHandle = ''): string
222
    {
223
        if (empty($blocks)) {
224
            return '';
225
        }
226
        $result = '';
227
        // Iterate through all of the matrix blocks
228
        if ($blocks instanceof NeoBlockQuery) {
229
            $blocks = $blocks->all();
230
        }
231
        foreach ($blocks as $block) {
232
            try {
233
                $neoBlockTypeModel = $block->getType();
234
            } catch (InvalidConfigException $e) {
235
                $neoBlockTypeModel = null;
236
            }
237
            // Find any text fields inside of the matrix block
238
            if ($neoBlockTypeModel) {
239
                $fieldClasses = FieldHelper::FIELD_CLASSES[FieldHelper::TEXT_FIELD_CLASS_KEY];
240
                $fields = $neoBlockTypeModel->getFields();
241
242
                foreach ($fields as $field) {
243
                    /** @var array $fieldClasses */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
244
                    foreach ($fieldClasses as $fieldClassKey) {
245
                        if ($field instanceof $fieldClassKey) {
246
                            if ($field->handle === $fieldHandle || empty($fieldHandle)) {
247
                                $result .= self::extractTextFromField($block[$field->handle]).' ';
248
                            }
249
                        }
250
                    }
251
                }
252
            }
253
        }
254
255
        return $result;
256
    }
257
258
    /**
259
     * Extract text from all of the blocks in a matrix field, concatenating it
260
     * together.
261
     *
262
     * @param SuperTableBlockQuery|SuperTableBlock[] $blocks
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
263
     * @param string                         $fieldHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 33 spaces after parameter type; 25 found
Loading history...
264
     *
265
     * @return string
266
     */
267
    public static function extractTextFromSuperTable($blocks, $fieldHandle = ''): string
268
    {
269
        if (empty($blocks)) {
270
            return '';
271
        }
272
        $result = '';
273
        // Iterate through all of the matrix blocks
274
        if ($blocks instanceof SuperTableBlockQuery) {
275
            $blocks = $blocks->all();
276
        }
277
        foreach ($blocks as $block) {
278
            try {
279
                $superTableBlockTypeModel = $block->getType();
280
            } catch (InvalidConfigException $e) {
281
                $superTableBlockTypeModel = null;
282
            }
283
            // Find any text fields inside of the matrix block
284
            if ($superTableBlockTypeModel) {
285
                $fieldClasses = FieldHelper::FIELD_CLASSES[FieldHelper::TEXT_FIELD_CLASS_KEY];
286
                $fields = $superTableBlockTypeModel->getFields();
287
288
                foreach ($fields as $field) {
289
                    /** @var array $fieldClasses */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
290
                    foreach ($fieldClasses as $fieldClassKey) {
291
                        if ($field instanceof $fieldClassKey) {
292
                            if ($field->handle === $fieldHandle || empty($fieldHandle)) {
293
                                $result .= self::extractTextFromField($block[$field->handle]).' ';
294
                            }
295
                        }
296
                    }
297
                }
298
            }
299
        }
300
301
        return $result;
302
    }
303
304
    /**
305
     * Return the most important keywords extracted from the text as a comma-
306
     * delimited string
307
     *
308
     * @param string $text
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
309
     * @param int    $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
310
     * @param bool   $useStopWords
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
311
     *
312
     * @return string
313
     */
314
    public static function extractKeywords($text, $limit = 15, $useStopWords = true): string
315
    {
316
        if (empty($text)) {
317
            return '';
318
        }
319
        $api = new TextRankFacade();
320
        // Set the stop words that should be ignored
321
        if ($useStopWords) {
322
            $language = strtolower(substr(Seomatic::$language, 0, 2));
323
            $stopWords = self::stopWordsForLanguage($language);
324
            if ($stopWords !== null) {
325
                $api->setStopWords($stopWords);
326
            }
327
        }
328
        // Array of the most important keywords:
329
        $keywords = $api->getOnlyKeyWords(self::cleanupText($text));
330
331
        // If it's empty, just return the text
332
        if (empty($keywords)) {
333
            return $text;
334
        }
335
336
        return \is_array($keywords)
0 ignored issues
show
introduced by
The condition is_array($keywords) is always true.
Loading history...
337
            ? implode(', ', \array_slice(array_keys($keywords), 0, $limit))
338
            : (string)$keywords;
339
    }
340
341
    /**
342
     * Extract a summary consisting of the 3 most important sentences from the
343
     * text
344
     *
345
     * @param string $text
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
346
     * @param bool   $useStopWords
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
347
     *
348
     * @return string
349
     */
350
    public static function extractSummary($text, $useStopWords = true): string
351
    {
352
        if (empty($text)) {
353
            return '';
354
        }
355
        $api = new TextRankFacade();
356
        // Set the stop words that should be ignored
357
        if ($useStopWords) {
358
            $language = strtolower(substr(Seomatic::$language, 0, 2));
359
            $stopWords = self::stopWordsForLanguage($language);
360
            if ($stopWords !== null) {
361
                $api->setStopWords($stopWords);
362
            }
363
        }
364
        // Array of the most important keywords:
365
        $sentences = $api->getHighlights(self::cleanupText($text));
366
367
        // If it's empty, just return the text
368
        if (empty($sentences)) {
369
            return $text;
370
        }
371
372
        return \is_array($sentences)
0 ignored issues
show
introduced by
The condition is_array($sentences) is always true.
Loading history...
373
            ? implode(' ', $sentences)
374
            : (string)$sentences;
375
    }
376
377
    /**
378
     * Clean up the passed in text by converting it to UTF-8, stripping tags,
379
     * removing whitespace, and decoding HTML entities
380
     *
381
     * @param string $text
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
382
     *
383
     * @return string
384
     */
385
    public static function cleanupText($text): string
386
    {
387
        if (empty($text)) {
388
            return '';
389
        }
390
        // Convert to UTF-8
391
        if (\function_exists('iconv')) {
392
            $text = iconv(mb_detect_encoding($text, mb_detect_order(), true), 'UTF-8//IGNORE', $text);
393
        } else {
394
            ini_set('mbstring.substitute_character', 'none');
395
            $text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
396
        }
397
        // Strip HTML tags
398
        $text = strip_tags($text);
399
        // Remove excess whitespace
400
        $text = preg_replace('/\s{2,}/u', ' ', $text);
401
        // Decode any HTML entities
402
        $text = html_entity_decode($text);
403
404
        return $text;
405
    }
406
407
    // Protected Static Methods
408
    // =========================================================================
409
410
    /**
411
     * @param string $language
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
412
     *
413
     * @return null|StopWordsAbstract
414
     */
415
    protected static function stopWordsForLanguage(string $language)
416
    {
417
        $stopWords = null;
418
        if (!empty(self::LANGUAGE_MAP[$language])) {
419
            $language = self::LANGUAGE_MAP[$language];
420
        } else {
421
            $language = 'English';
422
        }
423
424
        $className = 'PhpScience\\TextRank\\Tool\\StopWords\\'.ucfirst($language);
425
        if (class_exists($className)) {
426
            $stopWords = new $className;
427
        }
428
429
        return $stopWords;
430
    }
431
}
432