Content   A
last analyzed

Complexity

Total Complexity 26

Size/Duplication

Total Lines 138
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 26
eloc 58
dl 0
loc 138
ccs 58
cts 58
cp 1
rs 10
c 2
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A fillCutTags() 0 9 2
B contentCut() 0 36 8
A process() 0 14 3
A setContent() 0 5 1
A __construct() 0 3 1
B closeQuotationMarks() 0 17 7
A tagPosition() 0 9 3
A getContent() 0 3 1
1
<?php
2
3
namespace kalanis\kw_forums\Cutting;
4
5
6
/**
7
 * Class CuttingContent
8
 * @package kalanis\kw_forums\Cutting
9
 * Cutting messages
10
 */
11
class Content
12
{
13
    // it's necessary to have both arrays sorted this way - it's for lookup by positions
14
    /** @var string[] */
15
    protected static array $OPENING_TAGS = ['<b ',  '<b>',  '<i ',  '<i>',  '<u ',  '<u>',  '<center>',  '<span>',  '<span ',  '<font color', '<font face', '<font size', '<font ', '<font', '<table ', '<table', '<tr>', '<td>', '<a ', '<a>'];
16
    /** @var string[] */
17
    protected static array $CLOSING_TAGS = ['</b>', '</b>', '</i>', '</i>', '</u>', '</u>', '</center>', '</span>', '</span>', '</font>', '</font>', '</font>', '</font>', '</font>', '</table>', '</table>', '</tr>', '</td>', '</a>', '</a>'];
18
19
    protected int $wordLengthNeedle = 20;
20
    protected int $maxLength = 0;
21
    protected string $content = '';
22
    /**
23
     * Contains positions of tags in
24
     * @var int[]
25
     */
26
    protected array $tagPositionsStack = [];
27
28 6
    public function __construct(int $maxLength)
29
    {
30 6
        $this->maxLength = $maxLength;
31 6
    }
32
33 6
    public function setContent(string $content): self
34
    {
35 6
        $this->content = $content;
36 6
        $this->tagPositionsStack = [];
37 6
        return $this;
38
    }
39
40 6
    public function getContent(): string
41
    {
42 6
        return $this->content;
43
    }
44
45 6
    public function process(): self
46
    {
47 6
        if (mb_strlen($this->content) <= $this->maxLength) {
48 1
            return $this;
49
        }
50
51
        // lookup for available end, the values has been found empirically
52 5
        $content = mb_substr($this->content, 0, $this->maxLength + $this->wordLengthNeedle);
53 5
        $len = mb_strrpos($content, ' ');
54 5
        $len = false !== $len ? intval($len) : null;
55 5
        $content = mb_substr($content, 0, $len) . ' ';
56
57 5
        $this->content = $this->closeQuotationMarks($this->contentCut($content)) . $this->fillCutTags() . ' ...';
58 5
        return $this;
59
    }
60
61
    /**
62
     * @param string $content content to cut
63
     * @return string updated string
64
     */
65 5
    protected function contentCut(string $content): string
66
    {
67 5
        $this->tagPositionsStack = [];
68 5
        for ($i = 0; $i <= mb_strlen($content); $i++) {
69
            // start tag sequence
70 5
            if ('<' == mb_substr($content, $i, 1)) {
71
//print_r(['found open', $i]);
72 4
                if ('/' == mb_substr($content, $i + 1, 1)) {
73
                    // closing tag
74 1
                    $tagPosition = $this->tagPosition(mb_strtolower(mb_substr($content, $i, 7)), static::$CLOSING_TAGS);
75 1
                    if (!is_null($tagPosition)) {
76
                        // is correct one in array of closing tags?
77 1
                        $lastPosition = array_pop($this->tagPositionsStack);
78 1
                        if (!is_null($lastPosition)) {
79 1
                            if (static::$CLOSING_TAGS[$tagPosition] == static::$CLOSING_TAGS[$lastPosition]) { // because there are multiple openings
80 1
                                $i = $i + mb_strlen(static::$CLOSING_TAGS[$tagPosition]) - 2; // move to next available position in string
81
                            } else {
82 1
                                $this->tagPositionsStack[] = $lastPosition;
83
                            }
84
                        }
85
                    }
86
//print_r(['is closing', $tagPosition, is_null($tagPosition) ? 'x' : static::$CLOSING_TAGS[$tagPosition]]);
87
                } else {
88
                    // opening tag
89
                    // cut by longest available tag in array
90 4
                    $tagPosition = $this->tagPosition(mb_substr($content, $i, 11), static::$OPENING_TAGS);
91 4
                    if (!is_null($tagPosition)) { // is as pair in array and I know that
92 4
                        $this->tagPositionsStack[] = $tagPosition;
93 4
                        $i = $i + mb_strlen(static::$OPENING_TAGS[$tagPosition]) - 1; // move to next available position in string
94
                    }
95
//print_r(['is opening', $tagPosition, is_null($tagPosition) ? 'x' : static::$OPENING_TAGS[$tagPosition]]);
96
                }
97
            }
98
        }
99
100 5
        return $content;
101
    }
102
103
    /**
104
     * Returns tag position
105
     * @param string $lookupTag
106
     * @param string[] $definedTags
107
     * @return int|null
108
     * int as position in defined tags, null as not found
109
     */
110 4
    protected function tagPosition(string $lookupTag, array $definedTags): ?int
111
    {
112 4
        foreach ($definedTags as $position => $definedTag) {
113 4
            $cutTag = mb_substr($lookupTag, 0, mb_strlen($definedTag));
114 4
            if ($cutTag == $definedTag) {
115 4
                return $position;
116
            }
117
        }
118 2
        return null;
119
    }
120
121 5
    protected function closeQuotationMarks(string $content): string
122
    {
123
        // check for closing tags with spaces and quotation marks inside
124 5
        $tagStarts = mb_strrpos($content, '<');
125 5
        $tagEnds = mb_strrpos($content, '>');
126 5
        if ((false !== $tagStarts) && ((false === $tagEnds) || (false !== $tagEnds) && ($tagStarts > $tagEnds))) { // unclosed tag
127 3
            $foundOdd = mb_substr_count(mb_substr($content, $tagStarts), "'");
128 3
            if ($foundOdd % 2) {
129 1
                $content .= "'";
130
            }
131 3
            $foundOdd = mb_substr_count(mb_substr($content, $tagStarts), '"');
132 3
            if ($foundOdd % 2) {
133 1
                $content .= '"';
134
            }
135 3
            $content .= '>';
136
        }
137 5
        return $content;
138
    }
139
140 5
    protected function fillCutTags(): string
141
    {
142
//var_dump(['rev', $this->tagPositionsStack]);
143 5
        $content = '';
144 5
        foreach (array_reverse($this->tagPositionsStack) as $positions) {
145 4
            $content .= static::$CLOSING_TAGS[$positions];
146
        }
147
148 5
        return $content;
149
    }
150
}
151