| @@ -26,9 +26,9 @@ | ||
| 26 | 26 | $item[$key] = sanitize_text_field($item[$key]); |
| 27 | 27 | } |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | - $item['permalink'] = sanitize_url($item['permalink']); | |
| 30 | + $item['permalink'] = sanitize_url(Arr::get($item, 'permalink', '')); | |
| 31 | 31 | |
| 32 | 32 | if (!empty($item['shape_svg'])) { |
| 33 | 33 | $item['shape_svg'] = self::sanitizeSvg($item['shape_svg']); |
| 34 | 34 | } |
| @@ -119,8 +119,9 @@ | ||
| 119 | 119 | |
| 120 | 120 | private static function sanitizeNode(\DOMNode $node, array $allowed_tags) |
| 121 | 121 | { |
| 122 | 122 | if ($node->nodeType === XML_ELEMENT_NODE) { |
| 123 | + /** @var \DOMElement $node */ | |
| 123 | 124 | if (!isset($allowed_tags[$node->nodeName])) { |
| 124 | 125 | $node->parentNode->removeChild($node); |
| 125 | 126 | return; |
| 126 | 127 | } |
| @@ -168,10 +169,12 @@ | ||
| 168 | 169 | } |
| 169 | 170 | return ''; |
| 170 | 171 | } |
| 171 | 172 | |
| 172 | - public static function sanitizeWelcomeBannerSettings($settings) | |
| 173 | + public static function sanitizeWelcomeBannerSettings($settings, $views = ['login', 'logout']) | |
| 173 | 174 | { |
| 175 | + $views = array_intersect($views, ['login', 'logout', 'enrolled', 'not_enrolled']); | |
| 176 | + | |
| 174 | 177 | $rules = [ |
| 175 | 178 | 'title' => 'sanitize_text_field', |
| 176 | 179 | 'description' => 'wp_kses_post', |
| 177 | 180 | 'mediaType' => 'sanitize_text_field', |
| @@ -179,9 +182,9 @@ | ||
| 179 | 182 | 'enabled' => 'sanitize_text_field', |
| 180 | 183 | ]; |
| 181 | 184 | |
| 182 | 185 | $sanitizedSettings = []; |
| 183 | - foreach (['login', 'logout'] as $type) { | |
| 186 | + foreach ($views as $type) { | |
| 184 | 187 | $typeSettings = Arr::get($settings, $type, []); |
| 185 | 188 | if (empty($typeSettings)) { |
| 186 | 189 | continue; |
| 187 | 190 | } |
| @@ -192,12 +195,17 @@ | ||
| 192 | 195 | |
| 193 | 196 | $sanitizedSettings[$type]['bannerVideo'] = self::sanitizeBannerVideo($bannerVideo); |
| 194 | 197 | $sanitizedSettings[$type]['bannerImage'] = self::sanitizeBannerImage($bannerImage); |
| 195 | 198 | $sanitizedSettings[$type]['ctaButtons'] = self::sanitizeCtaButtons($ctaButtons); |
| 196 | - $sanitizedSettings[$type]['description'] = Arr::get($typeSettings, 'description'); | |
| 197 | 199 | |
| 200 | + $description = Arr::get($typeSettings, 'description'); | |
| 201 | + if (!empty($description)) { | |
| 202 | + $description = wp_kses_post(self::unslashMarkdown(wp_unslash($description))); | |
| 203 | + } | |
| 204 | + $sanitizedSettings[$type]['description'] = $description; | |
| 205 | + | |
| 198 | 206 | foreach ($typeSettings as $key => $value) { |
| 199 | - if (isset($rules[$key]) && !in_array($key, ['bannerVideo', 'bannerImage', 'ctaButtons'])) { | |
| 207 | + if (isset($rules[$key]) && !in_array($key, ['bannerVideo', 'bannerImage', 'ctaButtons', 'description'])) { | |
| 200 | 208 | $sanitizedSettings[$type][$key] = call_user_func($rules[$key], $value); |
| 201 | 209 | } |
| 202 | 210 | } |
| 203 | 211 | } |
| @@ -214,12 +222,13 @@ | ||
| 214 | 222 | return array_filter([ |
| 215 | 223 | 'type' => sanitize_text_field(Arr::get($video, 'type', '')), |
| 216 | 224 | 'url' => sanitize_url(Arr::get($video, 'url', '')), |
| 217 | 225 | 'content_type' => sanitize_text_field(Arr::get($video, 'content_type', '')), |
| 218 | - 'provider' => sanitize_url(Arr::get($video, 'provider', '')), | |
| 226 | + 'provider' => sanitize_text_field(Arr::get($video, 'provider', '')), | |
| 219 | 227 | 'title' => sanitize_text_field(Arr::get($video, 'title', '')), |
| 220 | 228 | 'author_name' => sanitize_text_field(Arr::get($video, 'author_name', '')), |
| 221 | 229 | 'html' => self::sanitizeRichText(Arr::get($video, 'html', '')), |
| 230 | + 'image' => sanitize_url(Arr::get($video, 'image', '')), | |
| 222 | 231 | ]); |
| 223 | 232 | } |
| 224 | 233 | |
| 225 | 234 | private static function sanitizeBannerImage($imageUrl) |
| @@ -248,9 +257,11 @@ | ||
| 248 | 257 | } |
| 249 | 258 | |
| 250 | 259 | $sanitizerMap = [ |
| 251 | 260 | 'label' => 'sanitize_text_field', |
| 252 | - 'link' => 'sanitize_url', | |
| 261 | + 'link' => function ($url) { | |
| 262 | + return esc_url_raw($url, ['http', 'https', 'mailto']); | |
| 263 | + }, | |
| 253 | 264 | 'type' => 'sanitize_text_field', |
| 254 | 265 | 'newTab' => 'sanitize_text_field' |
| 255 | 266 | ]; |
| 256 | 267 | |
| @@ -279,9 +290,9 @@ | ||
| 279 | 290 | |
| 280 | 291 | $item['emoji'] = self::sanitizeEmoji(Arr::get($item, 'emoji')); |
| 281 | 292 | |
| 282 | 293 | if (empty($item['slug'])) { |
| 283 | - $item['slug'] = sanitize_title($item['title']); | |
| 294 | + $item['slug'] = sanitize_title(Arr::get($item, 'title', '')); | |
| 284 | 295 | } else { |
| 285 | 296 | $item['slug'] = sanitize_title($item['slug']); |
| 286 | 297 | } |
| 287 | 298 | |
| @@ -290,9 +301,9 @@ | ||
| 290 | 301 | if (isset($item[$key])) { |
| 291 | 302 | $item[$key] = sanitize_text_field($item[$key]); |
| 292 | 303 | } |
| 293 | 304 | } |
| 294 | - $item['permalink'] = sanitize_url($item['permalink']); | |
| 305 | + $item['permalink'] = sanitize_url(Arr::get($item, 'permalink', '')); | |
| 295 | 306 | |
| 296 | 307 | |
| 297 | 308 | if (!empty($item['icon_image'])) { |
| 298 | 309 | $media = Helper::getMediaFromUrl($item['icon_image']); |
| @@ -310,9 +321,13 @@ | ||
| 310 | 321 | |
| 311 | 322 | if (!empty($item['icon_svg'])) { |
| 312 | 323 | $item['icon_svg'] = self::sanitizeSvg($item['icon_svg']); |
| 313 | 324 | } |
| 314 | - | |
| 325 | + | |
| 326 | + if (!empty($item['shape_svg'])) { | |
| 327 | + $item['shape_svg'] = self::sanitizeSvg($item['shape_svg']); | |
| 328 | + } | |
| 329 | + | |
| 315 | 330 | if (Arr::get($item, 'privacy') == 'members_only') { |
| 316 | 331 | $item['membership_ids'] = array_map('sanitize_text_field', (array)Arr::get($item, 'membership_ids', [])); |
| 317 | 332 | } |
| 318 | 333 | |
| @@ -343,18 +358,22 @@ | ||
| 343 | 358 | return $html; |
| 344 | 359 | } |
| 345 | 360 | |
| 346 | 361 | $tags = wp_kses_allowed_html('post'); |
| 347 | - $tags['style'] = [ | |
| 348 | - 'types' => [], | |
| 349 | - ]; | |
| 350 | 362 | |
| 351 | - // iframe | |
| 363 | + // No <style> element: kses filters style="" attributes but never the text content | |
| 364 | + // of a <style> block, so allowing it would let any role that can author this markup | |
| 365 | + // persist CSS (@import, attribute-selector data exfiltration, UI redress) against | |
| 366 | + // every viewer. Embed/media HTML never needs a <style> element. | |
| 367 | + | |
| 368 | + // iframe. Note there is deliberately no 'srcdoc' here: a srcdoc iframe without a | |
| 369 | + // sandbox attribute is same-origin with the portal, so allowing it would let any | |
| 370 | + // role that can author embed markup run script against every viewer. Real embed | |
| 371 | + // providers only ever use src. | |
| 352 | 372 | $tags['iframe'] = [ |
| 353 | 373 | 'width' => [], |
| 354 | 374 | 'height' => [], |
| 355 | 375 | 'src' => [], |
| 356 | - 'srcdoc' => [], | |
| 357 | 376 | 'title' => [], |
| 358 | 377 | 'frameborder' => [], |
| 359 | 378 | 'allow' => [], |
| 360 | 379 | 'class' => [], |
| @@ -360,9 +379,8 @@ | ||
| 360 | 379 | 'class' => [], |
| 361 | 380 | 'id' => [], |
| 362 | 381 | 'allowfullscreen' => [], |
| 363 | 382 | 'referrerpolicy' => [], |
| 364 | - 'style' => [], | |
| 365 | 383 | ]; |
| 366 | 384 | |
| 367 | 385 | $tags = apply_filters('fluent_community/allowed_html_tags', $tags); |
| 368 | 386 | |
| @@ -464,13 +482,15 @@ | ||
| 464 | 482 | public static function santizeSpaceSettings($settings = [], $privacy = 'public') |
| 465 | 483 | { |
| 466 | 484 | $yesNotFields = [ |
| 467 | 485 | 'restricted_post_only', |
| 486 | + 'verified_post_only', | |
| 468 | 487 | 'can_request_join', |
| 469 | 488 | 'show_paywalls', |
| 470 | 489 | 'show_sidebar', |
| 471 | 490 | 'hide_members_count', |
| 472 | 491 | 'document_library', |
| 492 | + 'media_gallery', | |
| 473 | 493 | 'disable_post_sort_by', |
| 474 | 494 | 'disable_layout_style' |
| 475 | 495 | ]; |
| 476 | 496 | |
| @@ -508,8 +528,19 @@ | ||
| 508 | 528 | |
| 509 | 529 | $validCommentOrderOptions = array_keys(Helper::getCommentOrderOptions()); |
| 510 | 530 | $defaultCommentOrder = Arr::get($settings, 'default_comment_sort_by', ''); |
| 511 | 531 | $settings['default_comment_sort_by'] = in_array($defaultCommentOrder, $validCommentOrderOptions) ? $defaultCommentOrder : ''; |
| 532 | + | |
| 533 | + $accessOptions = ['members_only', 'logged_in', 'everybody']; | |
| 534 | + $mediaAccess = Arr::get($settings, 'media_access'); | |
| 535 | + $settings['media_access'] = in_array($mediaAccess, $accessOptions, true) ? $mediaAccess : 'members_only'; | |
| 536 | + | |
| 537 | + $documentAccess = Arr::get($settings, 'document_access'); | |
| 538 | + $settings['document_access'] = in_array($documentAccess, $accessOptions, true) ? $documentAccess : 'members_only'; | |
| 539 | + | |
| 540 | + $documentUploadOptions = ['admin_only', 'members_only']; | |
| 541 | + $documentUpload = Arr::get($settings, 'document_upload'); | |
| 542 | + $settings['document_upload'] = in_array($documentUpload, $documentUploadOptions, true) ? $documentUpload : 'admin_only'; | |
| 512 | 543 | |
| 513 | 544 | return $settings; |
| 514 | 545 | } |
| 515 | 546 | |