Test Failed
Branch master (5aadec)
by Agel_Nash
04:00
created

SummaryText::textTrunc()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
c 0
b 0
f 0
rs 9.2
cc 4
eloc 9
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.3
14
 */
15
include_once(MODX_BASE_PATH . 'assets/lib/APIHelpers.class.php');
1 ignored issue
show
Bug introduced by
The constant MODX_BASE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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 $text
45
     * @param $action
46
     * @param null $break
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $break is correct as it would always require null to be passed?
Loading history...
47
     */
48
    public function __construct($text, $action, $break = null)
49
    {
50
        $this->_cfg['content'] = is_scalar($text) ? $text : '';
51
        $this->_cfg['original'] = $this->_cfg['content'];
52
        $this->_cfg['summary'] = is_scalar($action) ? $action : '';
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: {currentAssign}, Probably Intended Meaning: {alternativeAssign}
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)
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
109
                $process = explode(":", $doing);
110
                switch ($process[0]) {
111
                    case 'notags':
112
                        $this->_cfg['content'] = strip_tags($this->_cfg['content']);
113
                        break;
114
                    case 'noparser':
115
                        $this->_cfg['content'] = APIhelpers::sanitarTag($this->_cfg['content']);
116
                        break;
117
                    case 'chars':
118
                        if (!(isset($process[1]) && $process[1] > 0)) {
119
                            $process[1] = 200;
120
                        }
121
                        $this->_cfg['content'] = APIhelpers::mb_trim_word($this->_cfg['content'], $process[1]);
122
                        break;
123
                    case 'len':
124
                        if (!(isset($process[1]) && $process[1] > 0)) {
125
                            $process[1] = 200;
126
                        }
127
                        $this->_cfg['content'] = $this->summary($this->_cfg['content'], $process[1], 50, true,
128
                            $this->getCut());
129
                        break;
130
                }
131
            }
132
        }
133
134
        return $this->dotted($dotted);
135
    }
136
137
    /**
138
     * @param $resource
139
     * @param string $splitter
140
     * @return array|mixed
141
     */
142
    protected function beforeCut($resource, $splitter = '')
143
    {
144
        if ($splitter !== '') {
145
            $summary = str_replace('<p>' . $splitter . '</p>', $splitter,
146
                $resource); // For TinyMCE or if it isn't wrapped inside paragraph tags
147
            $summary = explode($splitter, $summary, 2);
148
            $this->_useCut = isset($summary[1]);
149
            $summary = $summary['0'];
150
        } else {
151
            $summary = $resource;
152
        }
153
154
        return $summary;
155
    }
156
157
    /**
158
     * @param $resource
159
     * @param $truncLen
160
     * @param $truncOffset
161
     * @param $truncChars
162
     * @param string $splitter
163
     * @return array|mixed|string
164
     */
165
    protected function summary($resource, $truncLen, $truncOffset, $truncChars, $splitter = '')
166
    {
167
        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 $part 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

167
        if (isset($this->_useCut) && $splitter != '' && mb_strstr($resource, $splitter, /** @scrutinizer ignore-type */ 'UTF-8')) {
Loading history...
168
            $summary = $this->beforeCut($resource, $splitter);
169
        } else {
170
            if ($this->_useCut !== true && (mb_strlen($resource, 'UTF-8') > $truncLen)) {
171
172
                $summary = $this->html_substr($resource, $truncLen, $truncOffset, $truncChars);
173
                if ($resource != $summary) {
174
                    $this->_useSubstr = true;
175
                }
176
            } else {
177
                $summary = $resource;
178
            }
179
        }
180
181
        $summary = $this->closeTags($summary);
182
        $summary = $this->rTriming($summary);
183
184
        return $summary;
185
    }
186
187
    /**
188
     * @see summary extender for Ditto (truncate::html_substr)
189
     * @link https://github.com/modxcms/evolution/blob/develop/assets/snippets/ditto/extenders/summary.extender.inc.php#L142
190
     *
191
     * @param $posttext
192
     * @param int $minimum_length
193
     * @param int $length_offset
194
     * @param bool $truncChars
195
     * @return string
196
     */
197
    protected function html_substr($posttext, $minimum_length = 200, $length_offset = 100, $truncChars = false)
198
    {
199
        $tag_counter = 0;
200
        $quotes_on = false;
201
        if (mb_strlen($posttext) > $minimum_length && $truncChars !== true) {
202
            $c = 0;
203
            $len = mb_strlen($posttext, 'UTF-8');
204
            for ($i = 0; $i < $len; $i++) {
205
                $current_char = mb_substr($posttext, $i, 1, 'UTF-8');
206
                if ($i < mb_strlen($posttext, 'UTF-8') - 1) {
207
                    $next_char = mb_substr($posttext, $i + 1, 1, 'UTF-8');
208
                } else {
209
                    $next_char = "";
210
                }
211
                if (!$quotes_on) {
212
                    // Check if it's a tag
213
                    // On a "<" add 3 if it's an opening tag (like <a href...)
214
                    // or add only 1 if it's an ending tag (like </a>)
215
                    if ($current_char == '<') {
216
                        if ($next_char == '/') {
217
                            $tag_counter += 1;
218
                        } else {
219
                            $tag_counter += 3;
220
                        }
221
                    }
222
                    // Slash signifies an ending (like </a> or ... />)
223
                    // substract 2
224
                    if ($current_char == '/' && $tag_counter <> 0) {
225
                        $tag_counter -= 2;
226
                    }
227
                    // On a ">" substract 1
228
                    if ($current_char == '>') {
229
                        $tag_counter -= 1;
230
                    }
231
                    // If quotes are encountered, start ignoring the tags
232
                    // (for directory slashes)
233
                    if ($current_char == '"') {
234
                        $quotes_on = true;
235
                    }
236
                } else {
237
                    // IF quotes are encountered again, turn it back off
238
                    if ($current_char == '"') {
239
                        $quotes_on = false;
240
                    }
241
                }
242
243
                // Count only the chars outside html tags
244
                if ($tag_counter == 2 || $tag_counter == 0) {
245
                    $c++;
246
                }
247
248
                // Check if the counter has reached the minimum length yet,
249
                // then wait for the tag_counter to become 0, and chop the string there
250
                if ($c > $minimum_length - $length_offset && $tag_counter == 0) {
251
                    $posttext = mb_substr($posttext, 0, $i + 1, 'UTF-8');
252
253
                    return $posttext;
254
                }
255
            }
256
        }
257
258
        return $this->textTrunc($posttext, $minimum_length + $length_offset, $this->_cfg['break']);
259
    }
260
261
    /**
262
     * @see summary extender for Ditto (truncate::textTrunc)
263
     * @link https://github.com/modxcms/evolution/blob/develop/assets/snippets/ditto/extenders/summary.extender.inc.php#L213
264
     *
265
     * @param $string
266
     * @param $limit
267
     * @param string $break
268
     * @return string
269
     */
270
    protected function textTrunc($string, $limit, $break = ". ")
271
    {
272
        // Original PHP code from The Art of Web: www.the-art-of-web.com
273
274
        // return with no change if string is shorter than $limit
275
        if (mb_strlen($string, 'UTF-8') < $limit) {
276
            return $string;
277
        }
278
279
        $string = mb_substr($string, 0, $limit, 'UTF-8');
280
        if (false !== ($breakpoint = mb_strrpos($string, $break, 'UTF-8'))) {
0 ignored issues
show
Bug introduced by
'UTF-8' of type string is incompatible with the type integer expected by parameter $offset of mb_strrpos(). ( Ignorable by Annotation )

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

280
        if (false !== ($breakpoint = mb_strrpos($string, $break, /** @scrutinizer ignore-type */ 'UTF-8'))) {
Loading history...
introduced by
The condition false !== $breakpoint = ...tring, $break, 'UTF-8') can never be false.
Loading history...
281
            $string = mb_substr($string, 0, $breakpoint + 1, 'UTF-8');
282
        } else {
283
            if ($break != ' ') {
284
                $string = $this->textTrunc($string, $limit, " ");
285
            }
286
        }
287
288
        return $string;
289
    }
290
291
    /**
292
     * @param $str
293
     * @return mixed
294
     */
295
    protected function rTriming($str)
296
    {
297
        $str = preg_replace('/[\r\n]++/', ' ', $str);
298
        if ($this->_useCut !== true || $this->_dotted != 2) {
299
            $str = preg_replace("/(([\.,\-:!?;\s])|(&\w+;))+$/ui", "", $str);
300
        }
301
302
        return $str;
303
    }
304
305
    /**
306
     * @see summary extender for Ditto (truncate::closeTags)
307
     * @link https://github.com/modxcms/evolution/blob/develop/assets/snippets/ditto/extenders/summary.extender.inc.php#L227
308
     * @param $text
309
     * @return string
310
     */
311
    private function closeTags($text)
312
    {
313
        $openPattern = "/<([^\/].*?)>/";
314
        $closePattern = "/<\/(.*?)>/";
315
        $endTags = '';
316
317
        preg_match_all($openPattern, $text, $openTags);
318
        preg_match_all($closePattern, $text, $closeTags);
319
320
        $c = 0;
321
        $loopCounter = count($closeTags[1]); //used to prevent an infinite loop if the html is malformed
322
        while ($c < count($closeTags[1]) && $loopCounter) {
323
            $i = 0;
324
            while ($i < count($openTags[1])) {
325
                $tag = trim($openTags[1][$i]);
326
327
                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 $part 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

327
                if (mb_strstr($tag, ' ', /** @scrutinizer ignore-type */ 'UTF-8')) {
Loading history...
328
                    $tag = mb_substr($tag, 0, strpos($tag, ' '), 'UTF-8');
329
                }
330
                if ($tag == $closeTags[1][$c]) {
331
                    $openTags[1][$i] = '';
332
                    $c++;
333
                    break;
334
                }
335
                $i++;
336
            }
337
            $loopCounter--;
338
        }
339
340
        $results = $openTags[1];
341
342
        if (is_array($results)) {
343
            $results = array_reverse($results);
344
345
            foreach ($results as $tag) {
346
                $tag = trim($tag);
347
348
                if (mb_strstr($tag, ' ', 'UTF-8')) {
349
                    $tag = mb_substr($tag, 0, strpos($tag, ' '), 'UTF-8');
350
                }
351
                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 $part 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

351
                if (!mb_stristr($tag, 'br', /** @scrutinizer ignore-type */ 'UTF-8') && !mb_stristr($tag, 'img', 'UTF-8') && !empty ($tag)) {
Loading history...
352
                    $endTags .= '</' . $tag . '>';
353
                }
354
            }
355
        }
356
357
        return $text . $endTags;
358
    }
359
}
360