| @@ -247,8 +247,9 @@ | ||
| 247 | 247 | /** |
| 248 | 248 | * Check if the user is a site admin. |
| 249 | 249 | * |
| 250 | 250 | * @param int|null $userId The user ID to check. If null, checks the current user. |
| 251 | + * @param \FluentCommunity\App\Models\User|null $user Resolved user model, to save a lookup. | |
| 251 | 252 | * @return bool True if the user is a site admin, false otherwise. |
| 252 | 253 | */ |
| 253 | 254 | public static function isSiteAdmin($userId = null, $user = null) |
| 254 | 255 | { |
| @@ -256,9 +257,11 @@ | ||
| 256 | 257 | return true; |
| 257 | 258 | } |
| 258 | 259 | |
| 259 | 260 | if (!$user) { |
| 260 | - $user = self::getCurrentUser(); | |
| 261 | + $user = ($userId && (int)$userId !== get_current_user_id()) | |
| 262 | + ? User::find($userId) | |
| 263 | + : self::getCurrentUser(); | |
| 261 | 264 | } |
| 262 | 265 | |
| 263 | 266 | return $user && Arr::get($user->getPermissions(), 'community_admin'); |
| 264 | 267 | } |
| @@ -795,12 +798,12 @@ | ||
| 795 | 798 | // Blockquotes: remove '>' symbol |
| 796 | 799 | '/^\s*>\s?/m' => '', |
| 797 | 800 | // Horizontal rules: replace with empty line |
| 798 | 801 | '/^\s*([-*_])\1{2,}\s*$/m' => "\n", |
| 802 | + // Images: keep only the alt text (run before links) | |
| 803 | + '/!\[([^\]]*)\]\([^\)]+\)/' => '$1', | |
| 799 | 804 | // Links: keep only the link text |
| 800 | - '/\[([^\]]+)\]\([^\)]+\)/' => '$1', | |
| 801 | - // Images: keep only the alt text | |
| 802 | - '/!\[([^\]]+)\]\([^\)]+\)/' => '$1', | |
| 805 | + '/\[([^\]]*)\]\([^\)]+\)/' => '$1', | |
| 803 | 806 | // Strikethrough: remove '~~' symbols |
| 804 | 807 | '/~~(.*?)~~/' => '$1', |
| 805 | 808 | // Task lists: remove checkbox syntax |
| 806 | 809 | '/^\s*[-*+]\s+\[[ xX]\]\s+/m' => '', |
| @@ -807,8 +810,10 @@ | ||
| 807 | 810 | ]; |
| 808 | 811 | |
| 809 | 812 | $content = preg_replace(array_keys($patterns), array_values($patterns), $content); |
| 810 | 813 | |
| 814 | + $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8'); | |
| 815 | + | |
| 811 | 816 | // remove all tags |
| 812 | 817 | $content = wp_strip_all_tags($content); |
| 813 | 818 | // remove new lines and tabs |
| 814 | 819 | $content = str_replace(["\r", "\n", "\t"], ' ', $content); |
| @@ -1344,8 +1349,30 @@ | ||
| 1344 | 1349 | |
| 1345 | 1350 | return $menuGroups; |
| 1346 | 1351 | } |
| 1347 | 1352 | |
| 1353 | + /** | |
| 1354 | + * Drop the links the given user may not see. | |
| 1355 | + * | |
| 1356 | + * Space links carry their own privacy, so every place that hands a space's settings | |
| 1357 | + * to a client has to filter them. Doing that inline is how the feed endpoints came | |
| 1358 | + * to skip it, so both call sites go through here. | |
| 1359 | + * | |
| 1360 | + * @param array $links | |
| 1361 | + * @param \FluentCommunity\App\Models\User|null $currentUser | |
| 1362 | + * @return array | |
| 1363 | + */ | |
| 1364 | + public static function filterAccessibleLinks($links, $currentUser = null) | |
| 1365 | + { | |
| 1366 | + if (!$links || !is_array($links)) { | |
| 1367 | + return []; | |
| 1368 | + } | |
| 1369 | + | |
| 1370 | + return array_values(array_filter($links, function ($link) use ($currentUser) { | |
| 1371 | + return self::isLinkAccessible($link, $currentUser); | |
| 1372 | + })); | |
| 1373 | + } | |
| 1374 | + | |
| 1348 | 1375 | public static function isLinkAccessible($link, $currentUser = null) |
| 1349 | 1376 | { |
| 1350 | 1377 | $isEnabled = Arr::get($link, 'enabled', 'yes') === 'yes'; |
| 1351 | 1378 | $isUnavailable = Arr::get($link, 'is_unavailable') === 'yes'; |
| @@ -1857,15 +1884,15 @@ | ||
| 1857 | 1884 | 'rel' => Arr::get($link, 'new_tab') === 'yes' ? 'noopener noreferrer' : '', |
| 1858 | 1885 | ]); |
| 1859 | 1886 | |
| 1860 | 1887 | ?> |
| 1861 | - <a aria-label="Go to <?php echo esc_attr(Arr::get($link, 'title')); ?> page" | |
| 1862 | - data-fcom-tip="<?php echo esc_attr(Arr::get($link, 'title')); ?>" | |
| 1888 | + <a data-fcom-hint="<?php echo esc_attr(Arr::get($link, 'title')); ?>" | |
| 1863 | 1889 | href="<?php echo esc_url($link['permalink']); ?>"<?php foreach ($linkAtts as $key => $value) { |
| 1864 | - echo esc_attr($key) . '="' . esc_attr($value) . '"'; | |
| 1890 | + echo ' ' . esc_attr($key) . '="' . esc_attr($value) . '"'; | |
| 1865 | 1891 | } ?>> |
| 1866 | 1892 | <?php $renderIcon && self::printLinkIcon($link, $fallback); ?> |
| 1867 | - <span class="community_name"><?php echo wp_kses_post(Arr::get($link, 'title')); ?></span> | |
| 1893 | + <?php // The native title sits on the label span (not the anchor) so it can not duplicate the link's accessible name for screen readers. ?> | |
| 1894 | + <span class="community_name" title="<?php echo esc_attr(Arr::get($link, 'title')); ?>"><?php echo wp_kses_post((string) Arr::get($link, 'title', '')); ?></span> | |
| 1868 | 1895 | <?php if (Arr::get($link, 'show_lock')) : ?> |
| 1869 | 1896 | <span class="fcom_space_lock"> |
| 1870 | 1897 | <i class="el-icon"> |
| 1871 | 1898 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"> |