These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @copyright Copyright (c) 2014 Carsten Brandt |
||
4 | * @license https://github.com/cebe/markdown/blob/master/LICENSE |
||
5 | * @link https://github.com/cebe/markdown#readme |
||
6 | */ |
||
7 | |||
8 | namespace cebe\markdown\block; |
||
9 | |||
10 | /** |
||
11 | * Adds the list blocks |
||
12 | */ |
||
13 | trait ListTrait |
||
14 | { |
||
15 | /** |
||
16 | * @var bool enable support `start` attribute of ordered lists. This means that lists |
||
17 | * will start with the number you actually type in markdown and not the HTML generated one. |
||
18 | * Defaults to `false` which means that numeration of all ordered lists(<ol>) starts with 1. |
||
19 | */ |
||
20 | public $keepListStartNumber = false; |
||
21 | |||
22 | /** |
||
23 | * identify a line as the beginning of an ordered list. |
||
24 | */ |
||
25 | 201 | protected function identifyOl($line) |
|
26 | { |
||
27 | 201 | return (($l = $line[0]) > '0' && $l <= '9' || $l === ' ') && preg_match('/^ {0,3}\d+\.[ \t]/', $line); |
|
28 | } |
||
29 | |||
30 | /** |
||
31 | * identify a line as the beginning of an unordered list. |
||
32 | */ |
||
33 | 202 | protected function identifyUl($line) |
|
34 | { |
||
35 | 202 | $l = $line[0]; |
|
36 | 202 | return ($l === '-' || $l === '+' || $l === '*') && (isset($line[1]) && (($l1 = $line[1]) === ' ' || $l1 === "\t")) || |
|
37 | 202 | ($l === ' ' && preg_match('/^ {0,3}[\-\+\*][ \t]/', $line)); |
|
38 | } |
||
39 | |||
40 | /** |
||
41 | * Consume lines for an ordered list |
||
42 | */ |
||
43 | 22 | protected function consumeOl($lines, $current) |
|
44 | { |
||
45 | // consume until newline |
||
46 | |||
47 | $block = [ |
||
48 | 22 | 'list', |
|
49 | 'list' => 'ol', |
||
50 | 'attr' => [], |
||
51 | 'items' => [], |
||
52 | ]; |
||
53 | 22 | return $this->consumeList($lines, $current, $block, 'ol'); |
|
54 | } |
||
55 | |||
56 | /** |
||
57 | * Consume lines for an unordered list |
||
58 | */ |
||
59 | 39 | protected function consumeUl($lines, $current) |
|
60 | { |
||
61 | // consume until newline |
||
62 | |||
63 | $block = [ |
||
64 | 39 | 'list', |
|
65 | 'list' => 'ul', |
||
66 | 'items' => [], |
||
67 | ]; |
||
68 | 39 | return $this->consumeList($lines, $current, $block, 'ul'); |
|
69 | } |
||
70 | |||
71 | 44 | private function consumeList($lines, $current, $block, $type) |
|
72 | { |
||
73 | 44 | $item = 0; |
|
74 | 44 | $indent = ''; |
|
75 | 44 | $len = 0; |
|
76 | 44 | $lastLineEmpty = false; |
|
77 | // track the indentation of list markers, if indented more than previous element |
||
78 | // a list marker is considered to be long to a lower level |
||
79 | 44 | $leadSpace = 3; |
|
80 | 44 | $marker = $type === 'ul' ? ltrim($lines[$current])[0] : ''; |
|
81 | 44 | for ($i = $current, $count = count($lines); $i < $count; $i++) { |
|
82 | 44 | $line = $lines[$i]; |
|
83 | // match list marker on the beginning of the line |
||
84 | 44 | $pattern = ($type === 'ol') ? '/^( {0,'.$leadSpace.'})(\d+)\.[ \t]+/' : '/^( {0,'.$leadSpace.'})\\'.$marker.'[ \t]+/'; |
|
85 | 44 | if (preg_match($pattern, $line, $matches)) { |
|
86 | 44 | if (($len = substr_count($matches[0], "\t")) > 0) { |
|
87 | 10 | $indent = str_repeat("\t", $len); |
|
88 | 10 | $line = substr($line, strlen($matches[0])); |
|
89 | } else { |
||
90 | 41 | $len = strlen($matches[0]); |
|
91 | 41 | $indent = str_repeat(' ', $len); |
|
92 | 41 | $line = substr($line, $len); |
|
93 | } |
||
94 | 44 | if ($i === $current) { |
|
95 | 44 | $leadSpace = strlen($matches[1]) + 1; |
|
96 | } |
||
97 | |||
98 | 44 | if ($type === 'ol' && $this->keepListStartNumber) { |
|
99 | // attr `start` for ol |
||
100 | 2 | if (!isset($block['attr']['start']) && isset($matches[2])) { |
|
101 | 2 | $block['attr']['start'] = $matches[2]; |
|
102 | } |
||
103 | } |
||
104 | |||
105 | 44 | $block['items'][++$item][] = $line; |
|
106 | 44 | $block['lazyItems'][$item] = $lastLineEmpty; |
|
107 | 44 | $lastLineEmpty = false; |
|
108 | 38 | } elseif (ltrim($line) === '') { |
|
109 | // line is empty, may be a lazy list |
||
110 | 38 | $lastLineEmpty = true; |
|
111 | |||
112 | // two empty lines will end the list |
||
113 | 38 | if (!isset($lines[$i + 1][0])) { |
|
114 | 23 | break; |
|
115 | |||
116 | // next item is the continuation of this list -> lazy list |
||
117 | 38 | } elseif (preg_match($pattern, $lines[$i + 1])) { |
|
118 | 18 | $block['items'][$item][] = $line; |
|
119 | 18 | $block['lazyItems'][$item] = true; |
|
120 | |||
121 | // next item is indented as much as this list -> lazy list if it is not a reference |
||
122 | 35 | } elseif (strncmp($lines[$i + 1], $indent, $len) === 0 || !empty($lines[$i + 1]) && $lines[$i + 1][0] == "\t") { |
|
123 | 18 | $block['items'][$item][] = $line; |
|
124 | 18 | $nextLine = $lines[$i + 1][0] === "\t" ? substr($lines[$i + 1], 1) : substr($lines[$i + 1], $len); |
|
125 | 18 | $block['lazyItems'][$item] = empty($nextLine) || !method_exists($this, 'identifyReference') || !$this->identifyReference($nextLine); |
|
126 | |||
127 | // everything else ends the list |
||
128 | } else { |
||
129 | 38 | break; |
|
130 | } |
||
131 | } else { |
||
132 | 28 | if ($line[0] === "\t") { |
|
133 | 7 | $line = substr($line, 1); |
|
134 | 24 | } elseif (strncmp($line, $indent, $len) === 0) { |
|
135 | 21 | $line = substr($line, $len); |
|
136 | } |
||
137 | 28 | $block['items'][$item][] = $line; |
|
138 | 28 | $lastLineEmpty = false; |
|
139 | } |
||
140 | |||
141 | // if next line is <hr>, end the list |
||
142 | 44 | if (!empty($lines[$i + 1]) && method_exists($this, 'identifyHr') && $this->identifyHr($lines[$i + 1])) { |
|
0 ignored issues
–
show
|
|||
143 | 3 | break; |
|
144 | } |
||
145 | } |
||
146 | |||
147 | 44 | foreach($block['items'] as $itemId => $itemLines) { |
|
148 | 44 | $content = []; |
|
149 | 44 | if (!$block['lazyItems'][$itemId]) { |
|
150 | 38 | $firstPar = []; |
|
151 | 38 | while (!empty($itemLines) && rtrim($itemLines[0]) !== '' && $this->detectLineType($itemLines, 0) === 'paragraph') { |
|
152 | 38 | $firstPar[] = array_shift($itemLines); |
|
153 | } |
||
154 | 38 | $content = $this->parseInline(implode("\n", $firstPar)); |
|
155 | } |
||
156 | 44 | if (!empty($itemLines)) { |
|
157 | 28 | $content = array_merge($content, $this->parseBlocks($itemLines)); |
|
158 | } |
||
159 | 44 | $block['items'][$itemId] = $content; |
|
160 | } |
||
161 | |||
162 | 44 | return [$block, $i]; |
|
163 | } |
||
164 | |||
165 | /** |
||
166 | * Renders a list |
||
167 | */ |
||
168 | 44 | protected function renderList($block) |
|
169 | { |
||
170 | 44 | $type = $block['list']; |
|
171 | |||
172 | 44 | if (!empty($block['attr'])) { |
|
173 | 2 | $output = "<$type " . $this->generateHtmlAttributes($block['attr']) . ">\n"; |
|
174 | } else { |
||
175 | 43 | $output = "<$type>\n"; |
|
176 | } |
||
177 | |||
178 | 44 | foreach ($block['items'] as $item => $itemLines) { |
|
179 | 44 | $output .= '<li>' . $this->renderAbsy($itemLines). "</li>\n"; |
|
180 | } |
||
181 | 44 | return $output . "</$type>\n"; |
|
182 | } |
||
183 | |||
184 | |||
185 | /** |
||
186 | * Return html attributes string from [attrName => attrValue] list |
||
187 | * @param array $attributes the attribute name-value pairs. |
||
188 | * @return string |
||
189 | */ |
||
190 | 2 | private function generateHtmlAttributes($attributes) |
|
191 | { |
||
192 | 2 | foreach ($attributes as $name => $value) { |
|
193 | 2 | $attributes[$name] = "$name=\"$value\""; |
|
194 | } |
||
195 | 2 | return implode(' ', $attributes); |
|
196 | } |
||
197 | |||
198 | abstract protected function parseBlocks($lines); |
||
199 | abstract protected function parseInline($text); |
||
200 | abstract protected function renderAbsy($absy); |
||
201 | abstract protected function detectLineType($lines, $current); |
||
202 | } |
||
203 |
This check looks for methods that are used by a trait but not required by it.
To illustrate, let’s look at the following code example
The trait
Idable
provides a methodequalsId
that in turn relies on the methodgetId()
. If this method does not exist on a class mixing in this trait, the method will fail.Adding the
getId()
as an abstract method to the trait will make sure it is available.