SummaryText::textTrunc()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 19
c 0
b 0
f 0
rs 9.9666
cc 4
nc 4
nop 3
1
<?php
2
/**
3
 * summary
4
 * Truncates the HTML string to the specified length
5
 *
6
 * Copyright 2013 by Agel_Nash <[email protected]>
7
 *
8
 * @category extender
9
 * @license GNU General Public License (GPL), http://www.gnu.org/copyleft/gpl.html
10
 * @author Agel_Nash <[email protected]>
11
 * @see http://blog.agel-nash.ru/addon/summary.html
12
 * @date 31.07.2013
13
 * @version 1.0.4
14
 */
15
include_once(MODX_BASE_PATH . 'assets/lib/APIHelpers.class.php');
16
17
/**
18
 * Class SummaryText
19
 */
20
class SummaryText
21
{
22
    /**
23
     * @var array
24
     */
25
    private $_cfg = array('content' => '', 'summary' => '', 'original' => '', 'break' => '');
26
27
    /**
28
     * @var bool|null
29
     */
30
    private $_useCut = null;
31
32
    /**
33
     * @var bool
34
     */
35
    private $_useSubstr = false;
36
37
    /**
38
     * @var int
39
     */
40
    private $_dotted = 0;
41
42
    /**
43
     * SummaryText constructor.
44
     * @param string $text
45
     * @param string $action
46
     * @param null|string $break
47
     */
48
    public function __construct($text, $action, $break = null)
49
    {
50
        $this->_cfg['content'] = is_scalar($text) ? $text : '';
0 ignored issues
show
introduced by
The condition is_scalar($text) is always true.
Loading history...
51
        $this->_cfg['original'] = $this->_cfg['content'];
52
        $this->_cfg['summary'] = is_scalar($action) ? $action : '';
0 ignored issues
show
introduced by
The condition is_scalar($action) is always true.
Loading history...
53
        $this->_cfg['break'] = is_scalar($break) ? $break : '. ';
54
    }
55
56
    /**
57
     * @param $cut
58
     * @return bool
59
     */
60
    public function setCut($cut)
61
    {
62
        if (is_scalar($cut) && $cut != '') {
63
            $this->_cfg['cut'] = $cut;
64
            $flag = true;
65
        } else {
66
            $flag = false;
67
        }
68
69
        return $flag;
70
    }
71
72
    /**
73
     * @return mixed
74
     */
75
    public function getCut()
76
    {
77
        return \APIHelpers::getkey($this->_cfg, 'cut', '<cut/>');
78
    }
79
80
    /**
81
     * @param int $scheme
82
     * @return mixed
83
     */
84
    protected function dotted($scheme = 0)
85
    {
86
        if (($scheme == 1 && ($this->_useCut === true || $this->_useSubstr)) || ($scheme == 2 && $this->_useSubstr && $this->_useCut !== true)) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($scheme == 1 && $this->...$this->_useCut !== true, Probably Intended Meaning: $scheme == 1 && ($this->...this->_useCut !== true)
Loading history...
87
            $this->_cfg['content'] .= '&hellip;'; //...
88
        } else {
89
            if ($scheme && ($this->_useCut !== true|| $scheme != 2)) {
90
                $this->_cfg['content'] .= '.';
91
            }
92
        }
93
94
        return $this->_cfg['content'];
95
    }
96
97
    /**
98
     * @param int $dotted
99
     * @return mixed
100
     */
101
    public function run($dotted = 0)
0 ignored issues
show
Coding Style introduced by
Function's nesting level (5) exceeds 3; consider refactoring the function
Loading history...
102
    {
103
        $this->_dotted = $dotted;
104
        if (isset($this->_cfg['content'], $this->_cfg['summary']) && $this->_cfg['summary'] != '' && $this->_cfg['content'] != '') {
105
            $param = explode(",", $this->_cfg['summary']);
106
            $this->_cfg['content'] = $this->beforeCut($this->_cfg['content'], $this->getCut());
107
            foreach ($param as $doing) {
108
                $process = explode(":", $doing);
109
                switch ($process[0]) {
110
                    case 'notags':
111
                        $this->_cfg['content'] = strip_tags($this->_cfg['content']);
0 ignored issues
show
Bug introduced by
It seems like $this->_cfg['content'] can also be of type array; however, parameter $string of strip_tags() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
                        $this->_cfg['content'] = strip_tags(/** @scrutinizer ignore-type */ $this->_cfg['content']);
Loading history...
112
                        break;
113
                    case 'noparser':
114
                        $this->_cfg['content'] = APIhelpers::sanitarTag($this->_cfg['content']);
115
                        break;
116
                    case 'chars':
117
                        if (!(isset($process[1]) && $process[1] > 0)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
118
                            $process[1] = 200;
119
                        }
120
                        $this->_cfg['content'] = APIhelpers::mb_trim_word($this->_cfg['content'], $process[1]);
0 ignored issues
show
Bug introduced by
It seems like $this->_cfg['content'] can also be of type array; however, parameter $html of APIhelpers::mb_trim_word() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
                        $this->_cfg['content'] = APIhelpers::mb_trim_word(/** @scrutinizer ignore-type */ $this->_cfg['content'], $process[1]);
Loading history...
121
                        break;
122
                    case 'len':
123
                        if (!(isset($process[1]) && $process[1] > 0)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
124
                            $process[1] = 200;
125
                        }
126
                        $this->_cfg['content'] = $this->summary(
127
                            $this->_cfg['content'],
128
                            $process[1],
129
                            50,
130
                            true,
131
                            $this->getCut()
132
                        );
133
                        break;
134
                }
135
            }
136
        }
137
138
        return $this->dotted($dotted);
139
    }
140
141
    /**
142
     * @param $resource
143
     * @param string $splitter
144
     * @return array|mixed
145
     */
146
    protected function beforeCut($resource, $splitter = '')
147
    {
148
        if ($splitter !== '') {
149
            $summary = str_replace(
150
                '<p>' . $splitter . '</p>',
151
                $splitter,
152
                $resource
153
            ); // For TinyMCE or if it isn't wrapped inside paragraph tags
154
            $summary = explode($splitter, $summary, 2);
155
            $this->_useCut = isset($summary[1]);
156
            $summary = $summary['0'];
157
        } else {
158
            $summary = $resource;
159
        }
160
161
        return $summary;
162
    }
163
164
    /**
165
     * @param $resource
166
     * @param $truncLen
167
     * @param $truncOffset
168
     * @param $truncChars
169
     * @param string $splitter
170
     * @return array|mixed|string
171
     */
172
    protected function summary($resource, $truncLen, $truncOffset, $truncChars, $splitter = '')
173
    {
174
        if (isset($this->_useCut) && $splitter != '' && mb_strstr($resource, $splitter, 'UTF-8')) {
0 ignored issues
show
Bug introduced by
'UTF-8' of type string is incompatible with the type boolean expected by parameter $before_needle of mb_strstr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
        if (isset($this->_useCut) && $splitter != '' && mb_strstr($resource, $splitter, /** @scrutinizer ignore-type */ 'UTF-8')) {
Loading history...
175
            $summary = $this->beforeCut($resource, $splitter);
176
        } else {
177
            if ($this->_useCut !== true && (mb_strlen($resource, 'UTF-8') > $truncLen)) {
178
                $summary = $this->html_substr($resource, $truncLen, $truncOffset, $truncChars);
179
                if ($resource != $summary) {
180
                    $this->_useSubstr = true;
181
                }
182
            } else {
183
                $summary = $resource;
184
            }
185
        }
186
187
        $summary = $this->closeTags($summary);
188
        $summary = $this->rTriming($summary);
189
190
        return $summary;
191
    }
192
193
    /**
194
     * @see summary extender for Ditto (truncate::html_substr)
195
     * @link https://github.com/modxcms/evolution/blob/develop/assets/snippets/ditto/extenders/summary.extender.inc.php#L142
196
     *
197
     * @param $posttext
198
     * @param int $minimum_length
199
     * @param int $length_offset
200
     * @param bool $truncChars
201
     * @return string
202
     */
203
    protected function html_substr($posttext, $minimum_length = 200, $length_offset = 100, $truncChars = false)
0 ignored issues
show
Coding Style introduced by
Function's nesting level (5) exceeds 3; consider refactoring the function
Loading history...
Coding Style introduced by
Method name "SummaryText::html_substr" is not in camel caps format
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
204
    {
205
        $tag_counter = 0;
206
        $quotes_on = false;
207
        if (mb_strlen($posttext) > $minimum_length && $truncChars !== true) {
208
            $c = 0;
209
            $len = mb_strlen($posttext, 'UTF-8');
210
            for ($i = 0; $i < $len; $i++) {
211
                $current_char = mb_substr($posttext, $i, 1, 'UTF-8');
212
                if ($i < mb_strlen($posttext, 'UTF-8') - 1) {
213
                    $next_char = mb_substr($posttext, $i + 1, 1, 'UTF-8');
214
                } else {
215
                    $next_char = "";
216
                }
217
                if (! $quotes_on) {
218
                    // Check if it's a tag
219
                    // On a "<" add 3 if it's an opening tag (like <a href...)
220
                    // or add only 1 if it's an ending tag (like </a>)
221
                    if ($current_char == '<') {
222
                        if ($next_char == '/') {
223
                            $tag_counter += 1;
224
                        } else {
225
                            $tag_counter += 3;
226
                        }
227
                    }
228
                    // Slash signifies an ending (like </a> or ... />)
229
                    // substract 2
230
                    if ($current_char == '/' && $tag_counter <> 0) {
231
                        $tag_counter -= 2;
232
                    }
233
                    // On a ">" substract 1
234
                    if ($current_char == '>') {
235
                        $tag_counter -= 1;
236
                    }
237
                    // If quotes are encountered, start ignoring the tags
238
                    // (for directory slashes)
239
                    if ($current_char == '"') {
240
                        $quotes_on = true;
241
                    }
242
                } else {
243
                    // IF quotes are encountered again, turn it back off
244
                    if ($current_char == '"') {
245
                        $quotes_on = false;
246
                    }
247
                }
248
249
                // Count only the chars outside html tags
250
                if ($tag_counter == 2 || $tag_counter == 0) {
251
                    $c++;
252
                }
253
254
                // Check if the counter has reached the minimum length yet,
255
                // then wait for the tag_counter to become 0, and chop the string there
256
                if ($c > $minimum_length - $length_offset && $tag_counter == 0) {
257
                    $posttext = mb_substr($posttext, 0, $i + 1, 'UTF-8');
258
259
                    return $posttext;
260
                }
261
            }
262
        }
263
264
        return $this->textTrunc($posttext, $minimum_length + $length_offset, $this->_cfg['break']);
265
    }
266
267
    /**
268
     * @see summary extender for Ditto (truncate::textTrunc)
269
     * @link https://github.com/modxcms/evolution/blob/develop/assets/snippets/ditto/extenders/summary.extender.inc.php#L213
270
     *
271
     * @param $string
272
     * @param $limit
273
     * @param string $break
274
     * @return string
275
     */
276
    protected function textTrunc($string, $limit, $break = ". ")
277
    {
278
        // Original PHP code from The Art of Web: www.the-art-of-web.com
279
280
        // return with no change if string is shorter than $limit
281
        if (mb_strlen($string, 'UTF-8') < $limit) {
282
            return $string;
283
        }
284
285
        $string = mb_substr($string, 0, $limit, 'UTF-8');
286
        if (false !== ($breakpoint = mb_strrpos($string, $break, 0, 'UTF-8'))) {
287
            $string = mb_substr($string, 0, $breakpoint + 1, 'UTF-8');
288
        } else {
289
            if ($break != ' ') {
290
                $string = $this->textTrunc($string, $limit, " ");
291
            }
292
        }
293
294
        return $string;
295
    }
296
297
    /**
298
     * @param $str
299
     * @return mixed
300
     */
301
    protected function rTriming($str)
302
    {
303
        $str = preg_replace('/[\r\n]++/', ' ', $str);
304
        if ($this->_useCut !== true || $this->_dotted != 2) {
305
            $str = preg_replace("/(([\.,\-:!?;\s])|(&\w+;))+$/ui", "", $str);
306
        }
307
308
        return $str;
309
    }
310
311
    /**
312
     * @see summary extender for Ditto (truncate::closeTags)
313
     * @link https://github.com/modxcms/evolution/blob/develop/assets/snippets/ditto/extenders/summary.extender.inc.php#L227
314
     * @param $text
315
     * @return string
316
     */
317
    private function closeTags($text)
318
    {
319
        $openPattern = "/<([^\/].*?)>/";
320
        $closePattern = "/<\/(.*?)>/";
321
        $endTags = '';
322
323
        preg_match_all($openPattern, $text, $openTags);
324
        preg_match_all($closePattern, $text, $closeTags);
325
326
        $c = 0;
327
        $loopCounter = count($closeTags[1]); //used to prevent an infinite loop if the html is malformed
328
        while ($c < count($closeTags[1]) && $loopCounter) {
329
            $i = 0;
330
            while ($i < count($openTags[1])) {
331
                $tag = trim($openTags[1][$i]);
332
333
                if (mb_strstr($tag, ' ', 'UTF-8')) {
0 ignored issues
show
Bug introduced by
'UTF-8' of type string is incompatible with the type boolean expected by parameter $before_needle of mb_strstr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

333
                if (mb_strstr($tag, ' ', /** @scrutinizer ignore-type */ 'UTF-8')) {
Loading history...
334
                    $tag = mb_substr($tag, 0, strpos($tag, ' '), 'UTF-8');
335
                }
336
                if ($tag == $closeTags[1][$c]) {
337
                    $openTags[1][$i] = '';
338
                    $c++;
339
                    break;
340
                }
341
                $i++;
342
            }
343
            $loopCounter--;
344
        }
345
346
        $results = $openTags[1];
347
348
        if (is_array($results)) {
349
            $results = array_reverse($results);
350
351
            foreach ($results as $tag) {
352
                $tag = trim($tag);
353
354
                if (mb_strstr($tag, ' ', 'UTF-8')) {
355
                    $tag = mb_substr($tag, 0, strpos($tag, ' '), 'UTF-8');
356
                }
357
                if (! mb_stristr($tag, 'br', 'UTF-8') && ! mb_stristr($tag, 'img', 'UTF-8') && ! empty($tag)) {
0 ignored issues
show
Bug introduced by
'UTF-8' of type string is incompatible with the type boolean expected by parameter $before_needle of mb_stristr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

357
                if (! mb_stristr($tag, 'br', /** @scrutinizer ignore-type */ 'UTF-8') && ! mb_stristr($tag, 'img', 'UTF-8') && ! empty($tag)) {
Loading history...
358
                    $endTags .= '</' . $tag . '>';
359
                }
360
            }
361
        }
362
363
        return $text . $endTags;
364
    }
365
}
366