PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.0
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
← All changes | app/Services/Helper.php +94 -8 2.7.72.10.0 View file →
@@ -5,8 +5,9 @@
5 5 use FluentCommunity\App\App;
6 6 use FluentCommunity\App\Functions\Utility;
7 7 use FluentCommunity\App\Hooks\Handlers\ActivationHandler;
8 8 use FluentCommunity\App\Models\BaseSpace;
9 +use FluentCommunity\App\Models\Contact;
9 10 use FluentCommunity\App\Models\Feed;
10 11 use FluentCommunity\App\Models\Space;
11 12 use FluentCommunity\App\Models\Media;
12 13 use FluentCommunity\App\Models\Meta;
@@ -246,8 +247,9 @@
246 247 /**
247 248 * Check if the user is a site admin.
248 249 *
249 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.
250 252 * @return bool True if the user is a site admin, false otherwise.
251 253 */
252 254 public static function isSiteAdmin($userId = null, $user = null)
253 255 {
@@ -255,9 +257,11 @@
255 257 return true;
256 258 }
257 259
258 260 if (!$user) {
259 - $user = self::getCurrentUser();
261 + $user = ($userId && (int)$userId !== get_current_user_id())
262 + ? User::find($userId)
263 + : self::getCurrentUser();
260 264 }
261 265
262 266 return $user && Arr::get($user->getPermissions(), 'community_admin');
263 267 }
@@ -794,12 +798,12 @@
794 798 // Blockquotes: remove '>' symbol
795 799 '/^\s*>\s?/m' => '',
796 800 // Horizontal rules: replace with empty line
797 801 '/^\s*([-*_])\1{2,}\s*$/m' => "\n",
802 + // Images: keep only the alt text (run before links)
803 + '/!\[([^\]]*)\]\([^\)]+\)/' => '$1',
798 804 // Links: keep only the link text
799 - '/\[([^\]]+)\]\([^\)]+\)/' => '$1',
800 - // Images: keep only the alt text
801 - '/!\[([^\]]+)\]\([^\)]+\)/' => '$1',
805 + '/\[([^\]]*)\]\([^\)]+\)/' => '$1',
802 806 // Strikethrough: remove '~~' symbols
803 807 '/~~(.*?)~~/' => '$1',
804 808 // Task lists: remove checkbox syntax
805 809 '/^\s*[-*+]\s+\[[ xX]\]\s+/m' => '',
@@ -806,8 +810,10 @@
806 810 ];
807 811
808 812 $content = preg_replace(array_keys($patterns), array_values($patterns), $content);
809 813
814 + $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
815 +
810 816 // remove all tags
811 817 $content = wp_strip_all_tags($content);
812 818 // remove new lines and tabs
813 819 $content = str_replace(["\r", "\n", "\t"], ' ', $content);
@@ -1343,8 +1349,30 @@
1343 1349
1344 1350 return $menuGroups;
1345 1351 }
1346 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 +
1347 1375 public static function isLinkAccessible($link, $currentUser = null)
1348 1376 {
1349 1377 $isEnabled = Arr::get($link, 'enabled', 'yes') === 'yes';
1350 1378 $isUnavailable = Arr::get($link, 'is_unavailable') === 'yes';
@@ -1856,15 +1884,15 @@
1856 1884 'rel' => Arr::get($link, 'new_tab') === 'yes' ? 'noopener noreferrer' : '',
1857 1885 ]);
1858 1886
1859 1887 ?>
1860 - <a aria-label="Go to <?php echo esc_attr(Arr::get($link, 'title')); ?> page"
1861 - data-fcom-tip="<?php echo esc_attr(Arr::get($link, 'title')); ?>"
1888 + <a data-fcom-hint="<?php echo esc_attr(Arr::get($link, 'title')); ?>"
1862 1889 href="<?php echo esc_url($link['permalink']); ?>"<?php foreach ($linkAtts as $key => $value) {
1863 - echo esc_attr($key) . '="' . esc_attr($value) . '"';
1890 + echo ' ' . esc_attr($key) . '="' . esc_attr($value) . '"';
1864 1891 } ?>>
1865 1892 <?php $renderIcon && self::printLinkIcon($link, $fallback); ?>
1866 - <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>
1867 1895 <?php if (Arr::get($link, 'show_lock')) : ?>
1868 1896 <span class="fcom_space_lock">
1869 1897 <i class="el-icon">
1870 1898 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
@@ -2482,6 +2510,64 @@
2482 2510 // else (6) => expanded, do nothing
2483 2511 }
2484 2512
2485 2513 return $collapsed;
2514 + }
2515 +
2516 + public static function getUndeliverableEmails($emails)
2517 + {
2518 + if (!$emails || !defined('FLUENTCRM')) {
2519 + return [];
2520 + }
2521 +
2522 + if (Utility::getPrivacySetting('skip_crm_undeliverable_emails') != 'yes') {
2523 + return [];
2524 + }
2525 +
2526 + /**
2527 + * FluentCRM contact statuses that FluentCommunity treats as undeliverable.
2528 + * Emails to contacts with these statuses are skipped for notification emails.
2529 + *
2530 + * @param array $statuses Contact statuses to skip. Default: bounced, complained, spammed.
2531 + */
2532 + $skippableStatuses = apply_filters('fluent_community/undeliverable_crm_contact_statuses', ['bounced', 'complained', 'spammed']);
2533 +
2534 + if (!$skippableStatuses) {
2535 + return [];
2536 + }
2537 +
2538 + $undeliverableEmails = Contact::whereIn('email', $emails)
2539 + ->whereIn('status', $skippableStatuses)
2540 + ->pluck('email')
2541 + ->toArray();
2542 +
2543 + return array_map('strtolower', $undeliverableEmails);
2544 + }
2545 +
2546 + public static function getCrmUndeliverableStatus($email)
2547 + {
2548 + if (!$email || !defined('FLUENTCRM')) {
2549 + return '';
2550 + }
2551 +
2552 + if (Utility::getPrivacySetting('skip_crm_undeliverable_emails') != 'yes') {
2553 + return '';
2554 + }
2555 +
2556 + $skippableStatuses = apply_filters('fluent_community/undeliverable_crm_contact_statuses', ['bounced', 'complained', 'spammed']);
2557 +
2558 + if (!$skippableStatuses) {
2559 + return '';
2560 + }
2561 +
2562 + $contact = Contact::where('email', $email)
2563 + ->whereIn('status', $skippableStatuses)
2564 + ->first();
2565 +
2566 + return $contact ? $contact->status : '';
2567 + }
2568 +
2569 + public static function isUndeliverableEmail($email)
2570 + {
2571 + return (bool)self::getCrmUndeliverableStatus($email);
2486 2572 }
2487 2573 }