diff options
Diffstat (limited to 'plugins/markdown/markdown.php')
-rw-r--r-- | plugins/markdown/markdown.php | 105 |
1 files changed, 86 insertions, 19 deletions
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php index 57fcce32..de7c823d 100644 --- a/plugins/markdown/markdown.php +++ b/plugins/markdown/markdown.php | |||
@@ -6,30 +6,28 @@ | |||
6 | * Shaare's descriptions are parsed with Markdown. | 6 | * Shaare's descriptions are parsed with Markdown. |
7 | */ | 7 | */ |
8 | 8 | ||
9 | require_once 'Parsedown.php'; | ||
10 | |||
11 | /* | 9 | /* |
12 | * If this tag is used on a shaare, the description won't be processed by Parsedown. | 10 | * If this tag is used on a shaare, the description won't be processed by Parsedown. |
13 | * Using a private tag so it won't appear for visitors. | ||
14 | */ | 11 | */ |
15 | define('NO_MD_TAG', '.nomarkdown'); | 12 | define('NO_MD_TAG', 'nomarkdown'); |
16 | 13 | ||
17 | /** | 14 | /** |
18 | * Parse linklist descriptions. | 15 | * Parse linklist descriptions. |
19 | * | 16 | * |
20 | * @param array $data linklist data. | 17 | * @param array $data linklist data. |
18 | * @param ConfigManager $conf instance. | ||
21 | * | 19 | * |
22 | * @return mixed linklist data parsed in markdown (and converted to HTML). | 20 | * @return mixed linklist data parsed in markdown (and converted to HTML). |
23 | */ | 21 | */ |
24 | function hook_markdown_render_linklist($data) | 22 | function hook_markdown_render_linklist($data, $conf) |
25 | { | 23 | { |
26 | foreach ($data['links'] as &$value) { | 24 | foreach ($data['links'] as &$value) { |
27 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { | 25 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { |
26 | $value = stripNoMarkdownTag($value); | ||
28 | continue; | 27 | continue; |
29 | } | 28 | } |
30 | $value['description'] = process_markdown($value['description']); | 29 | $value['description'] = process_markdown($value['description'], $conf->get('security.markdown_escape', true)); |
31 | } | 30 | } |
32 | |||
33 | return $data; | 31 | return $data; |
34 | } | 32 | } |
35 | 33 | ||
@@ -37,16 +35,18 @@ function hook_markdown_render_linklist($data) | |||
37 | * Parse feed linklist descriptions. | 35 | * Parse feed linklist descriptions. |
38 | * | 36 | * |
39 | * @param array $data linklist data. | 37 | * @param array $data linklist data. |
38 | * @param ConfigManager $conf instance. | ||
40 | * | 39 | * |
41 | * @return mixed linklist data parsed in markdown (and converted to HTML). | 40 | * @return mixed linklist data parsed in markdown (and converted to HTML). |
42 | */ | 41 | */ |
43 | function hook_markdown_render_feed($data) | 42 | function hook_markdown_render_feed($data, $conf) |
44 | { | 43 | { |
45 | foreach ($data['links'] as &$value) { | 44 | foreach ($data['links'] as &$value) { |
46 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { | 45 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { |
46 | $value = stripNoMarkdownTag($value); | ||
47 | continue; | 47 | continue; |
48 | } | 48 | } |
49 | $value['description'] = process_markdown($value['description']); | 49 | $value['description'] = process_markdown($value['description'], $conf->get('security.markdown_escape', true)); |
50 | } | 50 | } |
51 | 51 | ||
52 | return $data; | 52 | return $data; |
@@ -55,19 +55,24 @@ function hook_markdown_render_feed($data) | |||
55 | /** | 55 | /** |
56 | * Parse daily descriptions. | 56 | * Parse daily descriptions. |
57 | * | 57 | * |
58 | * @param array $data daily data. | 58 | * @param array $data daily data. |
59 | * @param ConfigManager $conf instance. | ||
59 | * | 60 | * |
60 | * @return mixed daily data parsed in markdown (and converted to HTML). | 61 | * @return mixed daily data parsed in markdown (and converted to HTML). |
61 | */ | 62 | */ |
62 | function hook_markdown_render_daily($data) | 63 | function hook_markdown_render_daily($data, $conf) |
63 | { | 64 | { |
64 | // Manipulate columns data | 65 | // Manipulate columns data |
65 | foreach ($data['cols'] as &$value) { | 66 | foreach ($data['cols'] as &$value) { |
66 | foreach ($value as &$value2) { | 67 | foreach ($value as &$value2) { |
67 | if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) { | 68 | if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) { |
69 | $value2 = stripNoMarkdownTag($value2); | ||
68 | continue; | 70 | continue; |
69 | } | 71 | } |
70 | $value2['formatedDescription'] = process_markdown($value2['formatedDescription']); | 72 | $value2['formatedDescription'] = process_markdown( |
73 | $value2['formatedDescription'], | ||
74 | $conf->get('security.markdown_escape', true) | ||
75 | ); | ||
71 | } | 76 | } |
72 | } | 77 | } |
73 | 78 | ||
@@ -83,7 +88,30 @@ function hook_markdown_render_daily($data) | |||
83 | */ | 88 | */ |
84 | function noMarkdownTag($tags) | 89 | function noMarkdownTag($tags) |
85 | { | 90 | { |
86 | return strpos($tags, NO_MD_TAG) !== false; | 91 | return preg_match('/(^|\s)'. NO_MD_TAG .'(\s|$)/', $tags); |
92 | } | ||
93 | |||
94 | /** | ||
95 | * Remove the no-markdown meta tag so it won't be displayed. | ||
96 | * | ||
97 | * @param array $link Link data. | ||
98 | * | ||
99 | * @return array Updated link without no markdown tag. | ||
100 | */ | ||
101 | function stripNoMarkdownTag($link) | ||
102 | { | ||
103 | if (! empty($link['taglist'])) { | ||
104 | $offset = array_search(NO_MD_TAG, $link['taglist']); | ||
105 | if ($offset !== false) { | ||
106 | unset($link['taglist'][$offset]); | ||
107 | } | ||
108 | } | ||
109 | |||
110 | if (!empty($link['tags'])) { | ||
111 | str_replace(NO_MD_TAG, '', $link['tags']); | ||
112 | } | ||
113 | |||
114 | return $link; | ||
87 | } | 115 | } |
88 | 116 | ||
89 | /** | 117 | /** |
@@ -138,7 +166,45 @@ function hook_markdown_render_editlink($data) | |||
138 | */ | 166 | */ |
139 | function reverse_text2clickable($description) | 167 | function reverse_text2clickable($description) |
140 | { | 168 | { |
141 | return preg_replace('!<a +href="([^ ]*)">[^ ]+</a>!m', '$1', $description); | 169 | $descriptionLines = explode(PHP_EOL, $description); |
170 | $descriptionOut = ''; | ||
171 | $codeBlockOn = false; | ||
172 | $lineCount = 0; | ||
173 | |||
174 | foreach ($descriptionLines as $descriptionLine) { | ||
175 | // Detect line of code: starting with 4 spaces, | ||
176 | // except lists which can start with +/*/- or `2.` after spaces. | ||
177 | $codeLineOn = preg_match('/^ +(?=[^\+\*\-])(?=(?!\d\.).)/', $descriptionLine) > 0; | ||
178 | // Detect and toggle block of code | ||
179 | if (!$codeBlockOn) { | ||
180 | $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0; | ||
181 | } | ||
182 | elseif (preg_match('/^```/', $descriptionLine) > 0) { | ||
183 | $codeBlockOn = false; | ||
184 | } | ||
185 | |||
186 | $hashtagTitle = ' title="Hashtag [^"]+"'; | ||
187 | // Reverse `inline code` hashtags. | ||
188 | $descriptionLine = preg_replace( | ||
189 | '!(`[^`\n]*)<a href="[^ ]*"'. $hashtagTitle .'>([^<]+)</a>([^`\n]*`)!m', | ||
190 | '$1$2$3', | ||
191 | $descriptionLine | ||
192 | ); | ||
193 | |||
194 | // Reverse all links in code blocks, only non hashtag elsewhere. | ||
195 | $hashtagFilter = (!$codeBlockOn && !$codeLineOn) ? '(?!'. $hashtagTitle .')': '(?:'. $hashtagTitle .')?'; | ||
196 | $descriptionLine = preg_replace( | ||
197 | '#<a href="[^ ]*"'. $hashtagFilter .'>([^<]+)</a>#m', | ||
198 | '$1', | ||
199 | $descriptionLine | ||
200 | ); | ||
201 | |||
202 | $descriptionOut .= $descriptionLine; | ||
203 | if ($lineCount++ < count($descriptionLines) - 1) { | ||
204 | $descriptionOut .= PHP_EOL; | ||
205 | } | ||
206 | } | ||
207 | return $descriptionOut; | ||
142 | } | 208 | } |
143 | 209 | ||
144 | /** | 210 | /** |
@@ -190,7 +256,7 @@ function sanitize_html($description) | |||
190 | $description); | 256 | $description); |
191 | } | 257 | } |
192 | $description = preg_replace( | 258 | $description = preg_replace( |
193 | '#(<[^>]+)on[a-z]*="[^"]*"#is', | 259 | '#(<[^>]+)on[a-z]*="?[^ "]*"?#is', |
194 | '$1', | 260 | '$1', |
195 | $description); | 261 | $description); |
196 | return $description; | 262 | return $description; |
@@ -205,20 +271,21 @@ function sanitize_html($description) | |||
205 | * 5. Wrap description in 'markdown' CSS class. | 271 | * 5. Wrap description in 'markdown' CSS class. |
206 | * | 272 | * |
207 | * @param string $description input description text. | 273 | * @param string $description input description text. |
274 | * @param bool $escape escape HTML entities | ||
208 | * | 275 | * |
209 | * @return string HTML processed $description. | 276 | * @return string HTML processed $description. |
210 | */ | 277 | */ |
211 | function process_markdown($description) | 278 | function process_markdown($description, $escape = true) |
212 | { | 279 | { |
213 | $parsedown = new Parsedown(); | 280 | $parsedown = new Parsedown(); |
214 | 281 | ||
215 | $processedDescription = $description; | 282 | $processedDescription = $description; |
216 | $processedDescription = reverse_text2clickable($processedDescription); | ||
217 | $processedDescription = reverse_nl2br($processedDescription); | 283 | $processedDescription = reverse_nl2br($processedDescription); |
218 | $processedDescription = reverse_space2nbsp($processedDescription); | 284 | $processedDescription = reverse_space2nbsp($processedDescription); |
285 | $processedDescription = reverse_text2clickable($processedDescription); | ||
219 | $processedDescription = unescape($processedDescription); | 286 | $processedDescription = unescape($processedDescription); |
220 | $processedDescription = $parsedown | 287 | $processedDescription = $parsedown |
221 | ->setMarkupEscaped(false) | 288 | ->setMarkupEscaped($escape) |
222 | ->setBreaksEnabled(true) | 289 | ->setBreaksEnabled(true) |
223 | ->text($processedDescription); | 290 | ->text($processedDescription); |
224 | $processedDescription = sanitize_html($processedDescription); | 291 | $processedDescription = sanitize_html($processedDescription); |