PluginProbe
OPcache Manager / 3.3.0
OPcache Manager v3.3.0
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.2.0 1.3.0 1.3.1 1.3.2 2.0.0 2.1.0 2.10.0 2.11.0 2.12.0 2.13.0 2.13.1 2.14.0 2.2.0 2.3.0 2.3.1 2.3.2 2.4.0 2.5.0 2.6.0 All 36 releases
opcache-manager / includes / libraries / markdown / inline / LinkTrait.php

LinkTrait.php in OPcache Manager 3.3.0, at includes/libraries/markdown/inline/LinkTrait.php

288 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\markdownparser\inline;
9
10 // work around https://github.com/facebook/hhvm/issues/1120
11 defined('ENT_HTML401') || define('ENT_HTML401', 0);
12
13 /**
14 * Addes links and images as well as url markers.
15 *
16 * This trait conflicts with the HtmlTrait. If both are used together,
17 * you have to define a resolution, by defining the HtmlTrait::parseInlineHtml
18 * as private so it is not used directly:
19 *
20 * ```php
21 * use block\HtmlTrait {
22 * parseInlineHtml as private parseInlineHtml;
23 * }
24 * ```
25 *
26 * If the method exists it is called internally by this trait.
27 *
28 * Also make sure to reset references on prepare():
29 *
30 * ```php
31 * protected function prepare()
32 * {
33 * // reset references
34 * $this->references = [];
35 * }
36 * ```
37 */
38 trait LinkTrait
39 {
40 /**
41 * @var array a list of defined references in this document.
42 */
43 protected $references = [];
44
45 /**
46 * Remove backslash from escaped characters
47 * @param $text
48 * @return string
49 */
50 protected function replaceEscape($text)
51 {
52 $strtr = [];
53 foreach($this->escapeCharacters as $char) {
54 $strtr["\\$char"] = $char;
55 }
56 return strtr($text, $strtr);
57 }
58
59 /**
60 * Parses a link indicated by `[`.
61 * @marker [
62 */
63 protected function parseLink($markdown)
64 {
65 if (!in_array('parseLink', array_slice($this->context, 1)) && ($parts = $this->parseLinkOrImage($markdown)) !== false) {
66 list($text, $url, $title, $offset, $key) = $parts;
67 return [
68 [
69 'link',
70 'text' => $this->parseInline($text),
71 'url' => $url,
72 'title' => $title,
73 'refkey' => $key,
74 'orig' => substr($markdown, 0, $offset),
75 ],
76 $offset
77 ];
78 } else {
79 // remove all starting [ markers to avoid next one to be parsed as link
80 $result = '[';
81 $i = 1;
82 while (isset($markdown[$i]) && $markdown[$i] === '[') {
83 $result .= '[';
84 $i++;
85 }
86 return [['text', $result], $i];
87 }
88 }
89
90 /**
91 * Parses an image indicated by `![`.
92 * @marker ![
93 */
94 protected function parseImage($markdown)
95 {
96 if (($parts = $this->parseLinkOrImage(substr($markdown, 1))) !== false) {
97 list($text, $url, $title, $offset, $key) = $parts;
98
99 return [
100 [
101 'image',
102 'text' => $text,
103 'url' => $url,
104 'title' => $title,
105 'refkey' => $key,
106 'orig' => substr($markdown, 0, $offset + 1),
107 ],
108 $offset + 1
109 ];
110 } else {
111 // remove all starting [ markers to avoid next one to be parsed as link
112 $result = '!';
113 $i = 1;
114 while (isset($markdown[$i]) && $markdown[$i] === '[') {
115 $result .= '[';
116 $i++;
117 }
118 return [['text', $result], $i];
119 }
120 }
121
122 protected function parseLinkOrImage($markdown)
123 {
124 if (strpos($markdown, ']') !== false && preg_match('/\[((?>[^\]\[]+|(?R))*)\]/', $markdown, $textMatches)) { // TODO improve bracket regex
125 $text = $textMatches[1];
126 $offset = strlen($textMatches[0]);
127 $markdown = substr($markdown, $offset);
128
129 $pattern = <<<REGEXP
130 /(?(R) # in case of recursion match parentheses
131 \(((?>[^\s()]+)|(?R))*\)
132 | # else match a link with title
133 ^\(\s*(((?>[^\s()]+)|(?R))*)(\s+"(.*?)")?\s*\)
134 )/x
135 REGEXP;
136 if (preg_match($pattern, $markdown, $refMatches)) {
137 // inline link
138 return [
139 $text,
140 isset($refMatches[2]) ? $this->replaceEscape($refMatches[2]) : '', // url
141 empty($refMatches[5]) ? null: $refMatches[5], // title
142 $offset + strlen($refMatches[0]), // offset
143 null, // reference key
144 ];
145 } elseif (preg_match('/^([ \n]?\[(.*?)\])?/s', $markdown, $refMatches)) {
146 // reference style link
147 if (empty($refMatches[2])) {
148 $key = strtolower($text);
149 } else {
150 $key = strtolower($refMatches[2]);
151 }
152 return [
153 $text,
154 null, // url
155 null, // title
156 $offset + strlen($refMatches[0]), // offset
157 $key,
158 ];
159 }
160 }
161 return false;
162 }
163
164 /**
165 * Parses inline HTML.
166 * @marker <
167 */
168 protected function parseLt($text)
169 {
170 if (strpos($text, '>') !== false) {
171 if (!in_array('parseLink', $this->context)) { // do not allow links in links
172 if (preg_match('/^<([^\s>]*?@[^\s]*?\.\w+?)>/', $text, $matches)) {
173 // email address
174 return [
175 ['email', $this->replaceEscape($matches[1])],
176 strlen($matches[0])
177 ];
178 } elseif (preg_match('/^<([a-z]{3,}:\/\/[^\s]+?)>/', $text, $matches)) {
179 // URL
180 return [
181 ['url', $this->replaceEscape($matches[1])],
182 strlen($matches[0])
183 ];
184 }
185 }
186 // try inline HTML if it was neither a URL nor email if HtmlTrait is included.
187 if (method_exists($this, 'parseInlineHtml')) {
188 return $this->parseInlineHtml($text);
189 }
190 }
191 return [['text', '&lt;'], 1];
192 }
193
194 protected function renderEmail($block)
195 {
196 $email = htmlspecialchars($block[1], ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8');
197 return "<a href=\"mailto:$email\">$email</a>";
198 }
199
200 protected function renderUrl($block)
201 {
202 $url = htmlspecialchars($block[1], ENT_COMPAT | ENT_HTML401, 'UTF-8');
203 $decodedUrl = urldecode($block[1]);
204 $secureUrlText = preg_match('//u', $decodedUrl) ? $decodedUrl : $block[1];
205 $text = htmlspecialchars($secureUrlText, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8');
206 return "<a href=\"$url\">$text</a>";
207 }
208
209 protected function lookupReference($key)
210 {
211 $normalizedKey = preg_replace('/\s+/', ' ', $key);
212 if (isset($this->references[$key]) || isset($this->references[$key = $normalizedKey])) {
213 return $this->references[$key];
214 }
215 return false;
216 }
217
218 protected function renderLink($block)
219 {
220 if (isset($block['refkey'])) {
221 if (($ref = $this->lookupReference($block['refkey'])) !== false) {
222 $block = array_merge($block, $ref);
223 } else {
224 if (strncmp($block['orig'], '[', 1) === 0) {
225 return '[' . $this->renderAbsy($this->parseInline(substr($block['orig'], 1)));
226 }
227 return $block['orig'];
228 }
229 }
230 return '<a href="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"'
231 . (empty($block['title']) ? '' : ' title="' . htmlspecialchars($block['title'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"')
232 . '>' . $this->renderAbsy($block['text']) . '</a>';
233 }
234
235 protected function renderImage($block)
236 {
237 if (isset($block['refkey'])) {
238 if (($ref = $this->lookupReference($block['refkey'])) !== false) {
239 $block = array_merge($block, $ref);
240 } else {
241 if (strncmp($block['orig'], '![', 2) === 0) {
242 return '![' . $this->renderAbsy($this->parseInline(substr($block['orig'], 2)));
243 }
244 return $block['orig'];
245 }
246 }
247 return '<img src="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"'
248 . ' alt="' . htmlspecialchars($block['text'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"'
249 . (empty($block['title']) ? '' : ' title="' . htmlspecialchars($block['title'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"')
250 . ($this->html5 ? '>' : ' />');
251 }
252
253 // references
254
255 protected function identifyReference($line)
256 {
257 return isset($line[0]) && ($line[0] === ' ' || $line[0] === '[') && preg_match('/^ {0,3}\[[^\[](.*?)\]:\s*([^\s]+?)(?:\s+[\'"](.+?)[\'"])?\s*$/', $line);
258 }
259
260 /**
261 * Consume link references
262 */
263 protected function consumeReference($lines, $current)
264 {
265 while (isset($lines[$current]) && preg_match('/^ {0,3}\[(.+?)\]:\s*(.+?)(?:\s+[\(\'"](.+?)[\)\'"])?\s*$/', $lines[$current], $matches)) {
266 $label = strtolower($matches[1]);
267
268 $this->references[$label] = [
269 'url' => $this->replaceEscape($matches[2]),
270 ];
271 if (isset($matches[3])) {
272 $this->references[$label]['title'] = $matches[3];
273 } else {
274 // title may be on the next line
275 if (isset($lines[$current + 1]) && preg_match('/^\s+[\(\'"](.+?)[\)\'"]\s*$/', $lines[$current + 1], $matches)) {
276 $this->references[$label]['title'] = $matches[1];
277 $current++;
278 }
279 }
280 $current++;
281 }
282 return [false, --$current];
283 }
284
285 abstract protected function parseInline($text);
286 abstract protected function renderAbsy($blocks);
287 }
288