PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.0
2.2.0 2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 All 43 releases
← All changes | app/Functions/helpers.php +202 -2 1.23 → 2.2.0 View file →
@@ -1,7 +1,8 @@
1 1 <?php
2 2
3 3 use FluentBoards\App\App;
4 +use FluentBoards\App\Services\Constant;
4 5
5 6 // $app is available
6 7
7 8 if (!function_exists('fluentBoards')) {
@@ -18,11 +19,65 @@
18 19 return is_null($key) ? $api : $api->{$key};
19 20 }
20 21 }
21 22
23 +if (!function_exists('fluent_boards_sanitize_description')) {
24 + function fluent_boards_sanitize_description($description): string
25 + {
26 + $description = preg_replace_callback('/<((?:https?:\/\/)[^<>\s]+)>/i', function ($matches) {
27 + $url = esc_url_raw(html_entity_decode($matches[1], ENT_QUOTES | ENT_HTML5, 'UTF-8'));
28 +
29 + return $url ? '[' . $url . '](' . str_replace(')', '%29', $url) . ')' : $matches[0];
30 + }, (string) $description);
31 +
32 + // Markdown email autolinks otherwise look like disallowed HTML to KSES.
33 + $description = preg_replace_callback('/<(?:mailto:)?([^<>\s]+@[^<>\s]+)>/i', function ($matches) {
34 + $email = html_entity_decode($matches[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
35 + if (!is_email($email)) {
36 + return $matches[0];
37 + }
38 +
39 + $label = str_replace(['*', '_', '`'], ['\\*', '\\_', '\\`'], $email);
40 + return '[' . $label . '](mailto:' . $email . ')';
41 + }, $description);
42 +
43 + // KSES encodes standalone > characters, which breaks Markdown quotes.
44 + // Protect only quote prefixes; all HTML still passes through KSES.
45 + $quotePrefix = 'fbs-quote-' . wp_generate_uuid4() . '-';
46 + $quotes = [];
47 + $description = preg_replace_callback('/^[ \t]*(?:>[ \t]*)+/m', function ($matches) use (&$quotes, $quotePrefix) {
48 + $key = $quotePrefix . count($quotes);
49 + $quotes[$key] = $matches[0];
50 + return $key;
51 + }, $description);
52 +
53 + return strtr(wp_kses_post($description), $quotes);
54 + }
55 +}
56 +
22 57 if (!function_exists('fluent_boards_user_avatar')) {
23 58 function fluent_boards_user_avatar($email, $name = '')
24 59 {
60 + $user = get_user_by('email', $email);
61 +
62 + if ($user) {
63 + $photo_url = get_user_meta($user->ID, Constant::PROFILE_PHOTO_META_KEY, true);
64 + if ($photo_url) {
65 + return apply_filters('fluent_boards/get_avatar', $photo_url, $email);
66 + }
67 +
68 + $has_custom_avatar = get_user_meta($user->ID, 'wp_user_avatar', true);
69 + if ($has_custom_avatar) {
70 + $custom_avatar_attachment = get_post($has_custom_avatar);
71 + if ($custom_avatar_attachment) {
72 + $avatar_url = wp_get_attachment_url($has_custom_avatar);
73 + return apply_filters('fluent_boards/get_avatar', $avatar_url, $email);
74 + }
75 + }
76 + $avatar_url = get_avatar_url($user->ID, array('size' => 128));
77 + return apply_filters('fluent_boards/get_avatar', $avatar_url, $email);
78 + }
79 +
25 80 $hash = md5(strtolower(trim($email)));
26 81 /**
27 82 * Gravatar URL by Email
28 83 *
@@ -62,8 +117,20 @@
62 117 return apply_filters('fluent_boards/app_url', admin_url('admin.php?page=fluent-boards#/'));
63 118 }
64 119 }
65 120
121 +if (!function_exists('fluent_boards_is_rtl')) {
122 + /**
123 + * Determine if Fluent Boards should render right-to-left UI.
124 + *
125 + * @return bool
126 + */
127 + function fluent_boards_is_rtl(): bool
128 + {
129 + return is_rtl();
130 + }
131 +}
132 +
66 133 function fluent_boards_get_pref_settings($cached = true)
67 134 {
68 135 static $pref = null;
69 136
@@ -85,9 +152,14 @@
85 152 ],
86 153 'menu_settings' => [
87 154 'in_fluent_crm' => 'no',
88 155 'menu_position' => 3
89 - ]
156 + ],
157 + 'recurring_task' => [
158 + 'enabled' => 'no',
159 + 'all_boards' => 'yes',
160 + 'selected_boards' => []
161 + ],
90 162 ];
91 163
92 164 $storedSettings = get_option('fluent_boards_modules', []);
93 165 if ($storedSettings && is_array($storedSettings)) {
@@ -150,5 +222,133 @@
150 222
151 223 function fluentBoardsDb()
152 224 {
153 225 return fluentBoards('db');
154 -}
226 +}
227 +
228 +/**
229 + * Retrieves features configuration.
230 + *
231 + * @deprecated 1.90 Use fluent_boards_get_features_config() instead.
232 + */
233 +function fbsGetFeaturesConfig()
234 +{
235 + _deprecated_function(__FUNCTION__, '1.90', 'fluent_boards_get_features_config');
236 + return fluent_boards_get_features_config();
237 +}
238 +
239 +function fluent_boards_get_features_config()
240 +{
241 + $features = fluent_boards_get_option('_fbs_features', []);
242 +
243 + $defaults = [
244 + 'cloud_storage' => 'no',
245 + // more advanced features/config can be added here
246 + ];
247 +
248 + if (defined('FLUENT_BOARDS_CLOUD_STORAGE') && FLUENT_BOARDS_CLOUD_STORAGE) {
249 + $features['cloud_storage'] = 'yes';
250 + }
251 +
252 + return wp_parse_args($features, $defaults);
253 +}
254 +
255 +/**
256 + * Updates an option value.
257 + *
258 + * @deprecated 1.90 Use fluent_boards_get_option() instead.
259 + */
260 +function fbsGetOption($key, $default = null)
261 +{
262 + _deprecated_function(__FUNCTION__, '1.90', 'fluent_boards_get_option');
263 + return fluent_boards_get_option($key, $default);
264 +}
265 +
266 +/**
267 + * Updates an option value.
268 + *
269 + * @deprecated 1.90 Use fluent_boards_update_option() instead.
270 + */
271 +function fbsUpdateOption($key, $value)
272 +{
273 + _deprecated_function(__FUNCTION__, '1.90', 'fluent_boards_update_option');
274 + return fluent_boards_update_option($key, $value);
275 +}
276 +
277 +
278 +function fluentboardsCsvMimes()
279 +{
280 + /**
281 + * Board Import CSV Mimes
282 + *
283 + * @return array array of CSV mimes
284 + */
285 + return apply_filters('fluent_boards_csv_mimes', [
286 + 'text/csv',
287 + 'text/plain',
288 + 'application/csv',
289 + 'text/comma-separated-values',
290 + 'application/excel',
291 + 'application/vnd.ms-excel',
292 + 'application/vnd.msexcel',
293 + 'text/anytext',
294 + 'application/octet-stream',
295 + 'application/txt'
296 + ]);
297 +}
298 +
299 +function fluent_boards_string_to_bool($value)
300 +{
301 + // Normalize known string values to booleans
302 + $trueFalseMap = [
303 + 'yes' => true,
304 + 'no' => false,
305 + 'true' => true,
306 + 'false' => false,
307 + '1' => true,
308 + '0' => false,
309 + true => true,
310 + false => false,
311 + ];
312 +
313 + // Allow modification of the map
314 + $trueFalseMap = apply_filters('fluent_boards/true_false_convert', $trueFalseMap);
315 +
316 + // Handle array input recursively
317 + if (is_array($value)) {
318 + return array_map('fluent_boards_string_to_bool', $value);
319 + }
320 +
321 + // Normalize string input
322 + $key = is_string($value) ? strtolower(trim($value)) : $value;
323 +
324 + // Match normalized value against map
325 + if (array_key_exists($key, $trueFalseMap)) {
326 + return $trueFalseMap[$key];
327 + }
328 +
329 + // Fallback: return original value if not recognized
330 + return $value;
331 +}
332 +
333 +/**
334 + * Builds the canonical Fluent Boards Upgrade-to-Pro URL.
335 + *
336 + * @param string $utmContent Exact in-plugin CTA placement.
337 + * @return string
338 + */
339 +function fluent_boards_get_upgrade_url($utmContent = '')
340 +{
341 + $queryArgs = [
342 + 'utm_source' => 'fluent-boards',
343 + 'utm_medium' => 'free_plugin',
344 + 'utm_campaign' => 'upgrade_pro',
345 + 'utm_term' => FLUENT_BOARDS_PLUGIN_VERSION,
346 + ];
347 +
348 + $utmContent = sanitize_key($utmContent);
349 + if ($utmContent) {
350 + $queryArgs['utm_content'] = $utmContent;
351 + }
352 +
353 + return add_query_arg($queryArgs, 'https://fluentboards.com/pricing/');
354 +}