diff options
author | ArthurHoaro <arthur@hoa.ro> | 2016-05-10 23:18:04 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2016-06-06 21:04:43 +0200 |
commit | 9ccca40189652e529732683abcdf54fcf775c9ec (patch) | |
tree | 9deda85d287dcba664bbba2f0bf9228e6118fbad /application | |
parent | bb9ca54838e2f877635197541e8439171c83d5dc (diff) | |
download | Shaarli-9ccca40189652e529732683abcdf54fcf775c9ec.tar.gz Shaarli-9ccca40189652e529732683abcdf54fcf775c9ec.tar.zst Shaarli-9ccca40189652e529732683abcdf54fcf775c9ec.zip |
Hashtag system
* Hashtag are auto-linked with a filter search
* Supports unicode
* Compatible with markdown (excluded in code blocks)
Diffstat (limited to 'application')
-rw-r--r-- | application/LinkDB.php | 2 | ||||
-rw-r--r-- | application/LinkFilter.php | 33 | ||||
-rw-r--r-- | application/LinkUtils.php | 75 | ||||
-rw-r--r-- | application/Utils.php | 55 |
4 files changed, 108 insertions, 57 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php index b1072e07..929a6b0f 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -409,7 +409,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
409 | $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; | 409 | $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; |
410 | 410 | ||
411 | // Search tags + fullsearch. | 411 | // Search tags + fullsearch. |
412 | if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) { | 412 | if (! empty($searchtags) && ! empty($searchterm)) { |
413 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; | 413 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; |
414 | $request = array($searchtags, $searchterm); | 414 | $request = array($searchtags, $searchterm); |
415 | } | 415 | } |
diff --git a/application/LinkFilter.php b/application/LinkFilter.php index e693b284..d4fe28df 100644 --- a/application/LinkFilter.php +++ b/application/LinkFilter.php | |||
@@ -28,6 +28,11 @@ class LinkFilter | |||
28 | public static $FILTER_DAY = 'FILTER_DAY'; | 28 | public static $FILTER_DAY = 'FILTER_DAY'; |
29 | 29 | ||
30 | /** | 30 | /** |
31 | * @var string Allowed characters for hashtags (regex syntax). | ||
32 | */ | ||
33 | public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; | ||
34 | |||
35 | /** | ||
31 | * @var array all available links. | 36 | * @var array all available links. |
32 | */ | 37 | */ |
33 | private $links; | 38 | private $links; |
@@ -263,8 +268,10 @@ class LinkFilter | |||
263 | for ($i = 0 ; $i < count($searchtags) && $found; $i++) { | 268 | for ($i = 0 ; $i < count($searchtags) && $found; $i++) { |
264 | // Exclusive search, quit if tag found. | 269 | // Exclusive search, quit if tag found. |
265 | // Or, tag not found in the link, quit. | 270 | // Or, tag not found in the link, quit. |
266 | if (($searchtags[$i][0] == '-' && in_array(substr($searchtags[$i], 1), $linktags)) | 271 | if (($searchtags[$i][0] == '-' |
267 | || ($searchtags[$i][0] != '-') && ! in_array($searchtags[$i], $linktags) | 272 | && $this->searchTagAndHashTag(substr($searchtags[$i], 1), $linktags, $link['description'])) |
273 | || ($searchtags[$i][0] != '-') | ||
274 | && ! $this->searchTagAndHashTag($searchtags[$i], $linktags, $link['description']) | ||
268 | ) { | 275 | ) { |
269 | $found = false; | 276 | $found = false; |
270 | } | 277 | } |
@@ -307,6 +314,28 @@ class LinkFilter | |||
307 | } | 314 | } |
308 | 315 | ||
309 | /** | 316 | /** |
317 | * Check if a tag is found in the taglist, or as an hashtag in the link description. | ||
318 | * | ||
319 | * @param string $tag Tag to search. | ||
320 | * @param array $taglist List of tags for the current link. | ||
321 | * @param string $description Link description. | ||
322 | * | ||
323 | * @return bool True if found, false otherwise. | ||
324 | */ | ||
325 | protected function searchTagAndHashTag($tag, $taglist, $description) | ||
326 | { | ||
327 | if (in_array($tag, $taglist)) { | ||
328 | return true; | ||
329 | } | ||
330 | |||
331 | if (preg_match('/(^| )#'. $tag .'([^'. self::$HASHTAG_CHARS .']|$)/mui', $description) > 0) { | ||
332 | return true; | ||
333 | } | ||
334 | |||
335 | return false; | ||
336 | } | ||
337 | |||
338 | /** | ||
310 | * Convert a list of tags (str) to an array. Also | 339 | * Convert a list of tags (str) to an array. Also |
311 | * - handle case sensitivity. | 340 | * - handle case sensitivity. |
312 | * - accepts spaces commas as separator. | 341 | * - accepts spaces commas as separator. |
diff --git a/application/LinkUtils.php b/application/LinkUtils.php index da04ca97..eeca631f 100644 --- a/application/LinkUtils.php +++ b/application/LinkUtils.php | |||
@@ -91,5 +91,80 @@ function count_private($links) | |||
91 | foreach ($links as $link) { | 91 | foreach ($links as $link) { |
92 | $cpt = $link['private'] == true ? $cpt + 1 : $cpt; | 92 | $cpt = $link['private'] == true ? $cpt + 1 : $cpt; |
93 | } | 93 | } |
94 | |||
94 | return $cpt; | 95 | return $cpt; |
95 | } | 96 | } |
97 | |||
98 | /** | ||
99 | * In a string, converts URLs to clickable links. | ||
100 | * | ||
101 | * @param string $text input string. | ||
102 | * @param string $redirector if a redirector is set, use it to gerenate links. | ||
103 | * | ||
104 | * @return string returns $text with all links converted to HTML links. | ||
105 | * | ||
106 | * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 | ||
107 | */ | ||
108 | function text2clickable($text, $redirector = '') | ||
109 | { | ||
110 | $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si'; | ||
111 | |||
112 | if (empty($redirector)) { | ||
113 | return preg_replace($regex, '<a href="$1">$1</a>', $text); | ||
114 | } | ||
115 | // Redirector is set, urlencode the final URL. | ||
116 | return preg_replace_callback( | ||
117 | $regex, | ||
118 | function ($matches) use ($redirector) { | ||
119 | return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>'; | ||
120 | }, | ||
121 | $text | ||
122 | ); | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Auto-link hashtags. | ||
127 | * | ||
128 | * @param string $description Given description. | ||
129 | * @param string $indexUrl Root URL. | ||
130 | * | ||
131 | * @return string Description with auto-linked hashtags. | ||
132 | */ | ||
133 | function hashtag_autolink($description, $indexUrl = '') | ||
134 | { | ||
135 | /* | ||
136 | * To support unicode: http://stackoverflow.com/a/35498078/1484919 | ||
137 | * \p{Pc} - to match underscore | ||
138 | * \p{N} - numeric character in any script | ||
139 | * \p{L} - letter from any language | ||
140 | * \p{Mn} - any non marking space (accents, umlauts, etc) | ||
141 | */ | ||
142 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; | ||
143 | $replacement = '$1<a href="'. $indexUrl .'?addtag=$2" title="Hashtag $2">#$2</a>'; | ||
144 | return preg_replace($regex, $replacement, $description); | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * This function inserts where relevant so that multiple spaces are properly displayed in HTML | ||
149 | * even in the absence of <pre> (This is used in description to keep text formatting). | ||
150 | * | ||
151 | * @param string $text input text. | ||
152 | * | ||
153 | * @return string formatted text. | ||
154 | */ | ||
155 | function space2nbsp($text) | ||
156 | { | ||
157 | return preg_replace('/(^| ) /m', '$1 ', $text); | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * Format Shaarli's description | ||
162 | * | ||
163 | * @param string $description shaare's description. | ||
164 | * @param string $redirector if a redirector is set, use it to gerenate links. | ||
165 | * | ||
166 | * @return string formatted description. | ||
167 | */ | ||
168 | function format_description($description, $redirector = '', $indexUrl = '') { | ||
169 | return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl))); | ||
170 | } | ||
diff --git a/application/Utils.php b/application/Utils.php index da521cce..7d7eaffd 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -198,59 +198,6 @@ function is_session_id_valid($sessionId) | |||
198 | } | 198 | } |
199 | 199 | ||
200 | /** | 200 | /** |
201 | * In a string, converts URLs to clickable links. | ||
202 | * | ||
203 | * @param string $text input string. | ||
204 | * @param string $redirector if a redirector is set, use it to gerenate links. | ||
205 | * | ||
206 | * @return string returns $text with all links converted to HTML links. | ||
207 | * | ||
208 | * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 | ||
209 | */ | ||
210 | function text2clickable($text, $redirector) | ||
211 | { | ||
212 | $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si'; | ||
213 | |||
214 | if (empty($redirector)) { | ||
215 | return preg_replace($regex, '<a href="$1">$1</a>', $text); | ||
216 | } | ||
217 | // Redirector is set, urlencode the final URL. | ||
218 | return preg_replace_callback( | ||
219 | $regex, | ||
220 | function ($matches) use ($redirector) { | ||
221 | return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>'; | ||
222 | }, | ||
223 | $text | ||
224 | ); | ||
225 | } | ||
226 | |||
227 | /** | ||
228 | * This function inserts where relevant so that multiple spaces are properly displayed in HTML | ||
229 | * even in the absence of <pre> (This is used in description to keep text formatting). | ||
230 | * | ||
231 | * @param string $text input text. | ||
232 | * | ||
233 | * @return string formatted text. | ||
234 | */ | ||
235 | function space2nbsp($text) | ||
236 | { | ||
237 | return preg_replace('/(^| ) /m', '$1 ', $text); | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * Format Shaarli's description | ||
242 | * TODO: Move me to ApplicationUtils when it's ready. | ||
243 | * | ||
244 | * @param string $description shaare's description. | ||
245 | * @param string $redirector if a redirector is set, use it to gerenate links. | ||
246 | * | ||
247 | * @return string formatted description. | ||
248 | */ | ||
249 | function format_description($description, $redirector = false) { | ||
250 | return nl2br(space2nbsp(text2clickable($description, $redirector))); | ||
251 | } | ||
252 | |||
253 | /** | ||
254 | * Sniff browser language to set the locale automatically. | 201 | * Sniff browser language to set the locale automatically. |
255 | * Note that is may not work on your server if the corresponding locale is not installed. | 202 | * Note that is may not work on your server if the corresponding locale is not installed. |
256 | * | 203 | * |
@@ -273,4 +220,4 @@ function autoLocale($headerLocale) | |||
273 | } | 220 | } |
274 | } | 221 | } |
275 | setlocale(LC_ALL, $attempts); | 222 | setlocale(LC_ALL, $attempts); |
276 | } \ No newline at end of file | 223 | } |