diff options
author | ArthurHoaro <arthur@hoa.ro> | 2020-10-13 12:05:08 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2020-10-13 12:05:08 +0200 |
commit | b6f678a5a1d15acf284ebcec16c905e976671ce1 (patch) | |
tree | 33c7da831482ed79c44896ef19c73c72ada84f2e /application/formatter | |
parent | b14687036b9b800681197f51fdc47e62f0c88e2e (diff) | |
parent | 1c1520b6b98ab20201bfe15577782a52320339df (diff) | |
download | Shaarli-b6f678a5a1d15acf284ebcec16c905e976671ce1.tar.gz Shaarli-b6f678a5a1d15acf284ebcec16c905e976671ce1.tar.zst Shaarli-b6f678a5a1d15acf284ebcec16c905e976671ce1.zip |
Merge branch 'v0.12' into latest
Diffstat (limited to 'application/formatter')
-rw-r--r-- | application/formatter/BookmarkDefaultFormatter.php | 87 | ||||
-rw-r--r-- | application/formatter/BookmarkFormatter.php | 313 | ||||
-rw-r--r-- | application/formatter/BookmarkMarkdownFormatter.php | 206 | ||||
-rw-r--r-- | application/formatter/BookmarkRawFormatter.php | 13 | ||||
-rw-r--r-- | application/formatter/FormatterFactory.php | 51 |
5 files changed, 670 insertions, 0 deletions
diff --git a/application/formatter/BookmarkDefaultFormatter.php b/application/formatter/BookmarkDefaultFormatter.php new file mode 100644 index 00000000..9d4a0fa0 --- /dev/null +++ b/application/formatter/BookmarkDefaultFormatter.php | |||
@@ -0,0 +1,87 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | /** | ||
6 | * Class BookmarkDefaultFormatter | ||
7 | * | ||
8 | * Default bookmark formatter. | ||
9 | * Escape values for HTML display and automatically add link to URL and hashtags. | ||
10 | * | ||
11 | * @package Shaarli\Formatter | ||
12 | */ | ||
13 | class BookmarkDefaultFormatter extends BookmarkFormatter | ||
14 | { | ||
15 | /** | ||
16 | * @inheritdoc | ||
17 | */ | ||
18 | public function formatTitle($bookmark) | ||
19 | { | ||
20 | return escape($bookmark->getTitle()); | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * @inheritdoc | ||
25 | */ | ||
26 | public function formatDescription($bookmark) | ||
27 | { | ||
28 | $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : ''; | ||
29 | return format_description(escape($bookmark->getDescription()), $indexUrl); | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * @inheritdoc | ||
34 | */ | ||
35 | protected function formatTagList($bookmark) | ||
36 | { | ||
37 | return escape(parent::formatTagList($bookmark)); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * @inheritdoc | ||
42 | */ | ||
43 | public function formatTagString($bookmark) | ||
44 | { | ||
45 | return implode(' ', $this->formatTagList($bookmark)); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * @inheritdoc | ||
50 | */ | ||
51 | public function formatUrl($bookmark) | ||
52 | { | ||
53 | if ($bookmark->isNote() && isset($this->contextData['index_url'])) { | ||
54 | return rtrim($this->contextData['index_url'], '/') . '/' . escape(ltrim($bookmark->getUrl(), '/')); | ||
55 | } | ||
56 | |||
57 | return escape($bookmark->getUrl()); | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * @inheritdoc | ||
62 | */ | ||
63 | protected function formatRealUrl($bookmark) | ||
64 | { | ||
65 | if ($bookmark->isNote()) { | ||
66 | if (isset($this->contextData['index_url'])) { | ||
67 | $prefix = rtrim($this->contextData['index_url'], '/') . '/'; | ||
68 | } | ||
69 | |||
70 | if (isset($this->contextData['base_path'])) { | ||
71 | $prefix = rtrim($this->contextData['base_path'], '/') . '/'; | ||
72 | } | ||
73 | |||
74 | return escape($prefix ?? '') . escape(ltrim($bookmark->getUrl(), '/')); | ||
75 | } | ||
76 | |||
77 | return escape($bookmark->getUrl()); | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * @inheritdoc | ||
82 | */ | ||
83 | protected function formatThumbnail($bookmark) | ||
84 | { | ||
85 | return escape($bookmark->getThumbnail()); | ||
86 | } | ||
87 | } | ||
diff --git a/application/formatter/BookmarkFormatter.php b/application/formatter/BookmarkFormatter.php new file mode 100644 index 00000000..0042dafe --- /dev/null +++ b/application/formatter/BookmarkFormatter.php | |||
@@ -0,0 +1,313 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use DateTime; | ||
6 | use Shaarli\Bookmark\Bookmark; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | |||
9 | /** | ||
10 | * Class BookmarkFormatter | ||
11 | * | ||
12 | * Abstract class processing all bookmark attributes through methods designed to be overridden. | ||
13 | * | ||
14 | * @package Shaarli\Formatter | ||
15 | */ | ||
16 | abstract class BookmarkFormatter | ||
17 | { | ||
18 | /** | ||
19 | * @var ConfigManager | ||
20 | */ | ||
21 | protected $conf; | ||
22 | |||
23 | /** @var bool */ | ||
24 | protected $isLoggedIn; | ||
25 | |||
26 | /** | ||
27 | * @var array Additional parameters than can be used for specific formatting | ||
28 | * e.g. index_url for Feed formatting | ||
29 | */ | ||
30 | protected $contextData = []; | ||
31 | |||
32 | /** | ||
33 | * LinkDefaultFormatter constructor. | ||
34 | * @param ConfigManager $conf | ||
35 | */ | ||
36 | public function __construct(ConfigManager $conf, bool $isLoggedIn) | ||
37 | { | ||
38 | $this->conf = $conf; | ||
39 | $this->isLoggedIn = $isLoggedIn; | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Convert a Bookmark into an array usable by templates and plugins. | ||
44 | * | ||
45 | * All Bookmark attributes are formatted through a format method | ||
46 | * that can be overridden in a formatter extending this class. | ||
47 | * | ||
48 | * @param Bookmark $bookmark instance | ||
49 | * | ||
50 | * @return array formatted representation of a Bookmark | ||
51 | */ | ||
52 | public function format($bookmark) | ||
53 | { | ||
54 | $out['id'] = $this->formatId($bookmark); | ||
55 | $out['shorturl'] = $this->formatShortUrl($bookmark); | ||
56 | $out['url'] = $this->formatUrl($bookmark); | ||
57 | $out['real_url'] = $this->formatRealUrl($bookmark); | ||
58 | $out['title'] = $this->formatTitle($bookmark); | ||
59 | $out['description'] = $this->formatDescription($bookmark); | ||
60 | $out['thumbnail'] = $this->formatThumbnail($bookmark); | ||
61 | $out['urlencoded_taglist'] = $this->formatUrlEncodedTagList($bookmark); | ||
62 | $out['taglist'] = $this->formatTagList($bookmark); | ||
63 | $out['urlencoded_tags'] = $this->formatUrlEncodedTagString($bookmark); | ||
64 | $out['tags'] = $this->formatTagString($bookmark); | ||
65 | $out['sticky'] = $bookmark->isSticky(); | ||
66 | $out['private'] = $bookmark->isPrivate(); | ||
67 | $out['class'] = $this->formatClass($bookmark); | ||
68 | $out['created'] = $this->formatCreated($bookmark); | ||
69 | $out['updated'] = $this->formatUpdated($bookmark); | ||
70 | $out['timestamp'] = $this->formatCreatedTimestamp($bookmark); | ||
71 | $out['updated_timestamp'] = $this->formatUpdatedTimestamp($bookmark); | ||
72 | return $out; | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Add additional data available to formatters. | ||
77 | * This is used for example to add `index_url` in description's links. | ||
78 | * | ||
79 | * @param string $key Context data key | ||
80 | * @param string $value Context data value | ||
81 | */ | ||
82 | public function addContextData($key, $value) | ||
83 | { | ||
84 | $this->contextData[$key] = $value; | ||
85 | |||
86 | return $this; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Format ID | ||
91 | * | ||
92 | * @param Bookmark $bookmark instance | ||
93 | * | ||
94 | * @return int formatted ID | ||
95 | */ | ||
96 | protected function formatId($bookmark) | ||
97 | { | ||
98 | return $bookmark->getId(); | ||
99 | } | ||
100 | |||
101 | /** | ||
102 | * Format ShortUrl | ||
103 | * | ||
104 | * @param Bookmark $bookmark instance | ||
105 | * | ||
106 | * @return string formatted ShortUrl | ||
107 | */ | ||
108 | protected function formatShortUrl($bookmark) | ||
109 | { | ||
110 | return $bookmark->getShortUrl(); | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Format Url | ||
115 | * | ||
116 | * @param Bookmark $bookmark instance | ||
117 | * | ||
118 | * @return string formatted Url | ||
119 | */ | ||
120 | protected function formatUrl($bookmark) | ||
121 | { | ||
122 | return $bookmark->getUrl(); | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Format RealUrl | ||
127 | * Legacy: identical to Url | ||
128 | * | ||
129 | * @param Bookmark $bookmark instance | ||
130 | * | ||
131 | * @return string formatted RealUrl | ||
132 | */ | ||
133 | protected function formatRealUrl($bookmark) | ||
134 | { | ||
135 | return $this->formatUrl($bookmark); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Format Title | ||
140 | * | ||
141 | * @param Bookmark $bookmark instance | ||
142 | * | ||
143 | * @return string formatted Title | ||
144 | */ | ||
145 | protected function formatTitle($bookmark) | ||
146 | { | ||
147 | return $bookmark->getTitle(); | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Format Description | ||
152 | * | ||
153 | * @param Bookmark $bookmark instance | ||
154 | * | ||
155 | * @return string formatted Description | ||
156 | */ | ||
157 | protected function formatDescription($bookmark) | ||
158 | { | ||
159 | return $bookmark->getDescription(); | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * Format Thumbnail | ||
164 | * | ||
165 | * @param Bookmark $bookmark instance | ||
166 | * | ||
167 | * @return string formatted Thumbnail | ||
168 | */ | ||
169 | protected function formatThumbnail($bookmark) | ||
170 | { | ||
171 | return $bookmark->getThumbnail(); | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * Format Tags | ||
176 | * | ||
177 | * @param Bookmark $bookmark instance | ||
178 | * | ||
179 | * @return array formatted Tags | ||
180 | */ | ||
181 | protected function formatTagList($bookmark) | ||
182 | { | ||
183 | return $this->filterTagList($bookmark->getTags()); | ||
184 | } | ||
185 | |||
186 | /** | ||
187 | * Format Url Encoded Tags | ||
188 | * | ||
189 | * @param Bookmark $bookmark instance | ||
190 | * | ||
191 | * @return array formatted Tags | ||
192 | */ | ||
193 | protected function formatUrlEncodedTagList($bookmark) | ||
194 | { | ||
195 | return array_map('urlencode', $this->filterTagList($bookmark->getTags())); | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * Format TagString | ||
200 | * | ||
201 | * @param Bookmark $bookmark instance | ||
202 | * | ||
203 | * @return string formatted TagString | ||
204 | */ | ||
205 | protected function formatTagString($bookmark) | ||
206 | { | ||
207 | return implode(' ', $this->formatTagList($bookmark)); | ||
208 | } | ||
209 | |||
210 | /** | ||
211 | * Format TagString | ||
212 | * | ||
213 | * @param Bookmark $bookmark instance | ||
214 | * | ||
215 | * @return string formatted TagString | ||
216 | */ | ||
217 | protected function formatUrlEncodedTagString($bookmark) | ||
218 | { | ||
219 | return implode(' ', $this->formatUrlEncodedTagList($bookmark)); | ||
220 | } | ||
221 | |||
222 | /** | ||
223 | * Format Class | ||
224 | * Used to add specific CSS class for a link | ||
225 | * | ||
226 | * @param Bookmark $bookmark instance | ||
227 | * | ||
228 | * @return string formatted Class | ||
229 | */ | ||
230 | protected function formatClass($bookmark) | ||
231 | { | ||
232 | return $bookmark->isPrivate() ? 'private' : ''; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * Format Created | ||
237 | * | ||
238 | * @param Bookmark $bookmark instance | ||
239 | * | ||
240 | * @return DateTime instance | ||
241 | */ | ||
242 | protected function formatCreated(Bookmark $bookmark) | ||
243 | { | ||
244 | return $bookmark->getCreated(); | ||
245 | } | ||
246 | |||
247 | /** | ||
248 | * Format Updated | ||
249 | * | ||
250 | * @param Bookmark $bookmark instance | ||
251 | * | ||
252 | * @return DateTime instance | ||
253 | */ | ||
254 | protected function formatUpdated(Bookmark $bookmark) | ||
255 | { | ||
256 | return $bookmark->getUpdated(); | ||
257 | } | ||
258 | |||
259 | /** | ||
260 | * Format CreatedTimestamp | ||
261 | * | ||
262 | * @param Bookmark $bookmark instance | ||
263 | * | ||
264 | * @return int formatted CreatedTimestamp | ||
265 | */ | ||
266 | protected function formatCreatedTimestamp(Bookmark $bookmark) | ||
267 | { | ||
268 | if (! empty($bookmark->getCreated())) { | ||
269 | return $bookmark->getCreated()->getTimestamp(); | ||
270 | } | ||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | /** | ||
275 | * Format UpdatedTimestamp | ||
276 | * | ||
277 | * @param Bookmark $bookmark instance | ||
278 | * | ||
279 | * @return int formatted UpdatedTimestamp | ||
280 | */ | ||
281 | protected function formatUpdatedTimestamp(Bookmark $bookmark) | ||
282 | { | ||
283 | if (! empty($bookmark->getUpdated())) { | ||
284 | return $bookmark->getUpdated()->getTimestamp(); | ||
285 | } | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Format tag list, e.g. remove private tags if the user is not logged in. | ||
291 | * | ||
292 | * @param array $tags | ||
293 | * | ||
294 | * @return array | ||
295 | */ | ||
296 | protected function filterTagList(array $tags): array | ||
297 | { | ||
298 | if ($this->isLoggedIn === true) { | ||
299 | return $tags; | ||
300 | } | ||
301 | |||
302 | $out = []; | ||
303 | foreach ($tags as $tag) { | ||
304 | if (strpos($tag, '.') === 0) { | ||
305 | continue; | ||
306 | } | ||
307 | |||
308 | $out[] = $tag; | ||
309 | } | ||
310 | |||
311 | return $out; | ||
312 | } | ||
313 | } | ||
diff --git a/application/formatter/BookmarkMarkdownFormatter.php b/application/formatter/BookmarkMarkdownFormatter.php new file mode 100644 index 00000000..5d244d4c --- /dev/null +++ b/application/formatter/BookmarkMarkdownFormatter.php | |||
@@ -0,0 +1,206 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
6 | |||
7 | /** | ||
8 | * Class BookmarkMarkdownFormatter | ||
9 | * | ||
10 | * Format bookmark description into Markdown format. | ||
11 | * | ||
12 | * @package Shaarli\Formatter | ||
13 | */ | ||
14 | class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter | ||
15 | { | ||
16 | /** | ||
17 | * When this tag is present in a bookmark, its description should not be processed with Markdown | ||
18 | */ | ||
19 | const NO_MD_TAG = 'nomarkdown'; | ||
20 | |||
21 | /** @var \Parsedown instance */ | ||
22 | protected $parsedown; | ||
23 | |||
24 | /** @var bool used to escape HTML in Markdown or not. | ||
25 | * It MUST be set to true for shared instance as HTML content can | ||
26 | * introduce XSS vulnerabilities. | ||
27 | */ | ||
28 | protected $escape; | ||
29 | |||
30 | /** | ||
31 | * @var array List of allowed protocols for links inside bookmark's description. | ||
32 | */ | ||
33 | protected $allowedProtocols; | ||
34 | |||
35 | /** | ||
36 | * LinkMarkdownFormatter constructor. | ||
37 | * | ||
38 | * @param ConfigManager $conf instance | ||
39 | * @param bool $isLoggedIn | ||
40 | */ | ||
41 | public function __construct(ConfigManager $conf, bool $isLoggedIn) | ||
42 | { | ||
43 | parent::__construct($conf, $isLoggedIn); | ||
44 | |||
45 | $this->parsedown = new \Parsedown(); | ||
46 | $this->escape = $conf->get('security.markdown_escape', true); | ||
47 | $this->allowedProtocols = $conf->get('security.allowed_protocols', []); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * @inheritdoc | ||
52 | */ | ||
53 | public function formatDescription($bookmark) | ||
54 | { | ||
55 | if (in_array(self::NO_MD_TAG, $bookmark->getTags())) { | ||
56 | return parent::formatDescription($bookmark); | ||
57 | } | ||
58 | |||
59 | $processedDescription = $bookmark->getDescription(); | ||
60 | $processedDescription = $this->filterProtocols($processedDescription); | ||
61 | $processedDescription = $this->formatHashTags($processedDescription); | ||
62 | $processedDescription = $this->reverseEscapedHtml($processedDescription); | ||
63 | $processedDescription = $this->parsedown | ||
64 | ->setMarkupEscaped($this->escape) | ||
65 | ->setBreaksEnabled(true) | ||
66 | ->text($processedDescription); | ||
67 | $processedDescription = $this->sanitizeHtml($processedDescription); | ||
68 | |||
69 | if (!empty($processedDescription)) { | ||
70 | $processedDescription = '<div class="markdown">'. $processedDescription . '</div>'; | ||
71 | } | ||
72 | |||
73 | return $processedDescription; | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Remove the NO markdown tag if it is present | ||
78 | * | ||
79 | * @inheritdoc | ||
80 | */ | ||
81 | protected function formatTagList($bookmark) | ||
82 | { | ||
83 | $out = parent::formatTagList($bookmark); | ||
84 | if ($this->isLoggedIn === false && ($pos = array_search(self::NO_MD_TAG, $out)) !== false) { | ||
85 | unset($out[$pos]); | ||
86 | return array_values($out); | ||
87 | } | ||
88 | return $out; | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Replace not whitelisted protocols with http:// in given description. | ||
93 | * Also adds `index_url` to relative links if it's specified | ||
94 | * | ||
95 | * @param string $description input description text. | ||
96 | * | ||
97 | * @return string $description without malicious link. | ||
98 | */ | ||
99 | protected function filterProtocols($description) | ||
100 | { | ||
101 | $allowedProtocols = $this->allowedProtocols; | ||
102 | $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : ''; | ||
103 | |||
104 | return preg_replace_callback( | ||
105 | '#]\((.*?)\)#is', | ||
106 | function ($match) use ($allowedProtocols, $indexUrl) { | ||
107 | $link = startsWith($match[1], '?') || startsWith($match[1], '/') ? $indexUrl : ''; | ||
108 | $link .= whitelist_protocols($match[1], $allowedProtocols); | ||
109 | return ']('. $link.')'; | ||
110 | }, | ||
111 | $description | ||
112 | ); | ||
113 | } | ||
114 | |||
115 | /** | ||
116 | * Replace hashtag in Markdown links format | ||
117 | * E.g. `#hashtag` becomes `[#hashtag](./add-tag/hashtag)` | ||
118 | * It includes the index URL if specified. | ||
119 | * | ||
120 | * @param string $description | ||
121 | * | ||
122 | * @return string | ||
123 | */ | ||
124 | protected function formatHashTags($description) | ||
125 | { | ||
126 | $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : ''; | ||
127 | |||
128 | /* | ||
129 | * To support unicode: http://stackoverflow.com/a/35498078/1484919 | ||
130 | * \p{Pc} - to match underscore | ||
131 | * \p{N} - numeric character in any script | ||
132 | * \p{L} - letter from any language | ||
133 | * \p{Mn} - any non marking space (accents, umlauts, etc) | ||
134 | */ | ||
135 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; | ||
136 | $replacement = '$1[#$2]('. $indexUrl .'./add-tag/$2)'; | ||
137 | |||
138 | $descriptionLines = explode(PHP_EOL, $description); | ||
139 | $descriptionOut = ''; | ||
140 | $codeBlockOn = false; | ||
141 | $lineCount = 0; | ||
142 | |||
143 | foreach ($descriptionLines as $descriptionLine) { | ||
144 | // Detect line of code: starting with 4 spaces, | ||
145 | // except lists which can start with +/*/- or `2.` after spaces. | ||
146 | $codeLineOn = preg_match('/^ +(?=[^\+\*\-])(?=(?!\d\.).)/', $descriptionLine) > 0; | ||
147 | // Detect and toggle block of code | ||
148 | if (!$codeBlockOn) { | ||
149 | $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0; | ||
150 | } elseif (preg_match('/^```/', $descriptionLine) > 0) { | ||
151 | $codeBlockOn = false; | ||
152 | } | ||
153 | |||
154 | if (!$codeBlockOn && !$codeLineOn) { | ||
155 | $descriptionLine = preg_replace($regex, $replacement, $descriptionLine); | ||
156 | } | ||
157 | |||
158 | $descriptionOut .= $descriptionLine; | ||
159 | if ($lineCount++ < count($descriptionLines) - 1) { | ||
160 | $descriptionOut .= PHP_EOL; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | return $descriptionOut; | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Remove dangerous HTML tags (tags, iframe, etc.). | ||
169 | * Doesn't affect <code> content (already escaped by Parsedown). | ||
170 | * | ||
171 | * @param string $description input description text. | ||
172 | * | ||
173 | * @return string given string escaped. | ||
174 | */ | ||
175 | protected function sanitizeHtml($description) | ||
176 | { | ||
177 | $escapeTags = array( | ||
178 | 'script', | ||
179 | 'style', | ||
180 | 'link', | ||
181 | 'iframe', | ||
182 | 'frameset', | ||
183 | 'frame', | ||
184 | ); | ||
185 | foreach ($escapeTags as $tag) { | ||
186 | $description = preg_replace_callback( | ||
187 | '#<\s*'. $tag .'[^>]*>(.*</\s*'. $tag .'[^>]*>)?#is', | ||
188 | function ($match) { | ||
189 | return escape($match[0]); | ||
190 | }, | ||
191 | $description | ||
192 | ); | ||
193 | } | ||
194 | $description = preg_replace( | ||
195 | '#(<[^>]+\s)on[a-z]*="?[^ "]*"?#is', | ||
196 | '$1', | ||
197 | $description | ||
198 | ); | ||
199 | return $description; | ||
200 | } | ||
201 | |||
202 | protected function reverseEscapedHtml($description) | ||
203 | { | ||
204 | return unescape($description); | ||
205 | } | ||
206 | } | ||
diff --git a/application/formatter/BookmarkRawFormatter.php b/application/formatter/BookmarkRawFormatter.php new file mode 100644 index 00000000..bc372273 --- /dev/null +++ b/application/formatter/BookmarkRawFormatter.php | |||
@@ -0,0 +1,13 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | /** | ||
6 | * Class BookmarkRawFormatter | ||
7 | * | ||
8 | * Used to retrieve bookmarks as array with raw values. | ||
9 | * Warning: Do NOT use this for HTML content as it can introduce XSS vulnerabilities. | ||
10 | * | ||
11 | * @package Shaarli\Formatter | ||
12 | */ | ||
13 | class BookmarkRawFormatter extends BookmarkFormatter {} | ||
diff --git a/application/formatter/FormatterFactory.php b/application/formatter/FormatterFactory.php new file mode 100644 index 00000000..a029579f --- /dev/null +++ b/application/formatter/FormatterFactory.php | |||
@@ -0,0 +1,51 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
6 | |||
7 | /** | ||
8 | * Class FormatterFactory | ||
9 | * | ||
10 | * Helper class used to instantiate the proper BookmarkFormatter. | ||
11 | * | ||
12 | * @package Shaarli\Formatter | ||
13 | */ | ||
14 | class FormatterFactory | ||
15 | { | ||
16 | /** @var ConfigManager instance */ | ||
17 | protected $conf; | ||
18 | |||
19 | /** @var bool */ | ||
20 | protected $isLoggedIn; | ||
21 | |||
22 | /** | ||
23 | * FormatterFactory constructor. | ||
24 | * | ||
25 | * @param ConfigManager $conf | ||
26 | * @param bool $isLoggedIn | ||
27 | */ | ||
28 | public function __construct(ConfigManager $conf, bool $isLoggedIn) | ||
29 | { | ||
30 | $this->conf = $conf; | ||
31 | $this->isLoggedIn = $isLoggedIn; | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Instanciate a BookmarkFormatter depending on the configuration or provided formatter type. | ||
36 | * | ||
37 | * @param string|null $type force a specific type regardless of the configuration | ||
38 | * | ||
39 | * @return BookmarkFormatter instance. | ||
40 | */ | ||
41 | public function getFormatter(string $type = null): BookmarkFormatter | ||
42 | { | ||
43 | $type = $type ? $type : $this->conf->get('formatter', 'default'); | ||
44 | $className = '\\Shaarli\\Formatter\\Bookmark'. ucfirst($type) .'Formatter'; | ||
45 | if (!class_exists($className)) { | ||
46 | $className = '\\Shaarli\\Formatter\\BookmarkDefaultFormatter'; | ||
47 | } | ||
48 | |||
49 | return new $className($this->conf, $this->isLoggedIn); | ||
50 | } | ||
51 | } | ||