PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.11.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 All 78 releases
← All changes | app/Services/CustomSanitizer.php +4 -162 2.11.0 → 2.10.01 View file →
@@ -102,33 +102,19 @@
102 102 'title' => [],
103 103 'desc' => [],
104 104 ];
105 105
106 - // Browsers accept bare "&" and HTML named entities in inline SVG, but strict XML parsing rejects them
107 - $svg_content = preg_replace('/&(?!#?[a-zA-Z0-9]+;)/', '&', $svg_content);
108 - $svg_content = preg_replace_callback('/&([a-zA-Z][a-zA-Z0-9]*);/', function ($matches) {
109 - $decoded = html_entity_decode($matches[0], ENT_QUOTES | ENT_HTML5, 'UTF-8');
110 -
111 - if ($decoded === $matches[0]) {
112 - return '';
113 - }
114 -
115 - return htmlspecialchars($decoded, ENT_QUOTES | ENT_XML1, 'UTF-8');
116 - }, $svg_content);
117 -
118 106 // Load the SVG string into a DOMDocument and discard errors for malformed XML
119 107 $dom = new \DOMDocument();
120 108 libxml_use_internal_errors(true);
121 - $loaded = $dom->loadXML($svg_content);
109 + $dom->loadXML($svg_content);
122 110 libxml_clear_errors();
123 111
124 - if (!$loaded || !$dom->documentElement) {
125 - return '';
112 + if ($dom->documentElement) {
113 + // Sanitize by removing unwanted tags and attributes
114 + self::sanitizeNode($dom->documentElement, $allowed_tags);
126 115 }
127 116
128 - // Sanitize by removing unwanted tags and attributes
129 - self::sanitizeNode($dom->documentElement, $allowed_tags);
130 -
131 117 return $dom->saveXML($dom->documentElement);
132 118 }
133 119
134 120 private static function sanitizeNode(\DOMNode $node, array $allowed_tags)
@@ -345,152 +331,8 @@
345 331 $item['membership_ids'] = array_map('sanitize_text_field', (array)Arr::get($item, 'membership_ids', []));
346 332 }
347 333
348 334 return array_filter($item);
349 - }
350 -
351 - /**
352 - * @param array $items
353 - * @return array
354 - */
355 - public static function sanitizeSpaceMenuItems($items)
356 - {
357 - $sanitized = [];
358 - $seen = [];
359 -
360 - foreach ((array)$items as $item) {
361 - $menuItem = self::sanitizeSpaceMenuItem($item);
362 -
363 - if (!$menuItem || isset($seen[$menuItem['slug']])) {
364 - continue;
365 - }
366 -
367 - $seen[$menuItem['slug']] = true;
368 - $sanitized[] = $menuItem;
369 - }
370 -
371 - return $sanitized;
372 - }
373 -
374 - /**
375 - * One row of a space's primary menu. Returns null for a row that cannot be rendered — no
376 - * slug, a custom row with no label, or a destination that survived neither the protocol
377 - * allowlist nor the page lookup.
378 - *
379 - * @param array $item
380 - * @return array|null
381 - */
382 - public static function sanitizeSpaceMenuItem($item)
383 - {
384 - // `parent` is accepted and stored but nothing renders it yet. It holds a sibling row's
385 - // slug for the one-level sub-menu, and keeping it on the write path now means that
386 - // feature is additive rather than a migration of everyone's stored menu.
387 - $validKeys = [
388 - 'slug', 'title', 'enabled', 'new_tab', 'emoji', 'icon_image', 'shape_svg',
389 - 'permalink', 'page_slug', 'link_type', 'privacy', 'membership_ids', 'is_custom', 'parent',
390 - ];
391 -
392 - $item = Arr::only((array)$item, $validKeys);
393 -
394 - $isCustom = Arr::get($item, 'is_custom') === 'yes';
395 -
396 - $slug = Utility::slugify(Arr::get($item, 'slug', ''));
397 -
398 - if ($isCustom) {
399 - // Force the prefix so a custom row's slug can never hijack a real tab's slug.
400 - if (strpos($slug, 'fcom_custom_') !== 0) {
401 - $slug = 'fcom_custom_' . ($slug ?: substr(md5(wp_generate_password(12, false)), 0, 10));
402 - }
403 - } elseif (!$slug) {
404 - return null;
405 - }
406 -
407 - $sanitized = [
408 - 'slug' => $slug,
409 - 'title' => sanitize_text_field(Arr::get($item, 'title', '')),
410 - 'enabled' => Arr::get($item, 'enabled') === 'no' ? 'no' : 'yes',
411 - 'is_custom' => $isCustom ? 'yes' : 'no',
412 - 'parent' => sanitize_title(Arr::get($item, 'parent', '')),
413 - ];
414 -
415 - $emoji = self::sanitizeEmoji(Arr::get($item, 'emoji', ''));
416 -
417 - if ($emoji) {
418 - $sanitized['emoji'] = $emoji;
419 - }
420 -
421 - $shapeSvg = self::sanitizeSvg(Arr::get($item, 'shape_svg', ''));
422 -
423 - if ($shapeSvg) {
424 - $sanitized['shape_svg'] = $shapeSvg;
425 - }
426 -
427 - $iconImage = Arr::get($item, 'icon_image');
428 -
429 - if ($iconImage) {
430 - $media = Helper::getMediaFromUrl($iconImage);
431 -
432 - if ($media) {
433 - $media->update([
434 - 'is_active' => true,
435 - 'user_id' => get_current_user_id(),
436 - 'object_source' => 'general',
437 - ]);
438 - $sanitized['icon_image'] = $media->public_url;
439 - } else {
440 - $sanitized['icon_image'] = sanitize_url($iconImage);
441 - }
442 - }
443 -
444 - $privacy = Arr::get($item, 'privacy');
445 -
446 - if (!in_array($privacy, ['public', 'logged_in', 'logged_out_only', 'members_only'], true)) {
447 - $privacy = 'public';
448 - }
449 -
450 - $sanitized['privacy'] = $privacy;
451 -
452 - if ($privacy === 'members_only') {
453 - $membershipIds = array_map('intval', (array)Arr::get($item, 'membership_ids', []));
454 - $sanitized['membership_ids'] = array_values(array_filter($membershipIds));
455 - }
456 -
457 - if (!$isCustom) {
458 - return $sanitized;
459 - }
460 -
461 - if (!$sanitized['title']) {
462 - return null;
463 - }
464 -
465 - $linkType = Arr::get($item, 'link_type') === 'space_page' ? 'space_page' : 'url';
466 - $sanitized['link_type'] = $linkType;
467 -
468 - if ($linkType === 'space_page') {
469 - $pageSlug = sanitize_title(Arr::get($item, 'page_slug', ''));
470 -
471 - if (!$pageSlug) {
472 - return null;
473 - }
474 -
475 - $sanitized['page_slug'] = $pageSlug;
476 - $sanitized['new_tab'] = 'no';
477 -
478 - return $sanitized;
479 - }
480 -
481 - // sanitize_url drops everything outside WordPress's protocol allowlist, so a
482 - // javascript: destination comes back empty and the row is discarded.
483 - $permalink = sanitize_url(Arr::get($item, 'permalink', ''));
484 -
485 - if (!$permalink) {
486 - return null;
487 - }
488 -
489 - $sanitized['permalink'] = $permalink;
490 - $sanitized['new_tab'] = Arr::get($item, 'new_tab') === 'yes' ? 'yes' : 'no';
491 -
492 - return $sanitized;
493 335 }
494 336
495 337 public static function sanitizeRichText($content, $print = false)
496 338 {