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/Hooks/Handlers/FluentBlockEditorHandler.php +151 -44 2.4.012.10.01 View file →
@@ -9,25 +9,40 @@
9 9 use FluentCommunity\Modules\Course\Model\CourseLesson;
10 10
11 11 class FluentBlockEditorHandler
12 12 {
13 + /**
14 + * Extra fcomEditorVars entries contributed by whoever answered the
15 + * fluent_community/block_editor_context filter. Stashed in renderCustomEditor()
16 + * and merged in gutenberg_editor_scripts_and_styles(), which runs later in the
17 + * same request off the wp_enqueue_scripts callback registered below.
18 + *
19 + * @var array
20 + */
21 + private $contextEditorVars = [];
22 +
13 23 public function register()
14 24 {
15 25 add_action('init', function () {
16 26
17 - register_post_type('fcom-dummy', [
18 - 'label' => 'Lesson',
19 - 'public' => false,
20 - 'show_in_rest' => true,
21 - 'supports' => ['title', 'editor', 'thumbnail'],
27 + $editorPostTypes = apply_filters('fluent_community/block_editor_post_types', [
28 + 'fcom-dummy' => [
29 + 'label' => 'Lesson',
30 + 'public' => false,
31 + 'show_in_rest' => true,
32 + 'supports' => ['title', 'editor', 'thumbnail'],
33 + ],
34 + 'fcom-lockscreen' => [
35 + 'label' => 'Lockscreen',
36 + 'public' => false,
37 + 'show_in_rest' => true,
38 + 'supports' => ['editor'],
39 + ],
22 40 ]);
23 41
24 - register_post_type('fcom-lockscreen', [
25 - 'label' => 'Lockscreen',
26 - 'public' => false,
27 - 'show_in_rest' => true,
28 - 'supports' => ['editor'],
29 - ]);
42 + foreach ($editorPostTypes as $editorPostType => $editorPostTypeArgs) {
43 + register_post_type($editorPostType, $editorPostTypeArgs);
44 + }
30 45
31 46 if (!isset($_REQUEST['fluent_community_block_editor'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
32 47 return;
33 48 }
@@ -38,18 +53,23 @@
38 53
39 54 remove_action('enqueue_block_editor_assets', 'wp_enqueue_editor_block_directory_assets');
40 55 add_action('fluent_community/block_editor_head', function () {
41 56 $url = FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/index.css';
57 + $contentStylingUrl = FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/content_styling.css';
58 + // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- emitted into a custom non-wp_head action hook, wp_enqueue_style not applicable
42 59 ?>
43 60 <link rel="stylesheet"
44 61 href="<?php echo esc_url($url); ?>?version=<?php echo esc_attr(FLUENT_COMMUNITY_PLUGIN_VERSION); ?>"
45 - media="screen"/> <?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet ?>
62 + media="screen"/>
46 63 <link rel="stylesheet"
47 - href="<?php echo FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/content_styling.css'; ?>?version=<?php echo esc_attr(FLUENT_COMMUNITY_PLUGIN_VERSION); ?>"
48 - media="screen"/> <?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet ?>
64 + href="<?php echo esc_url($contentStylingUrl); ?>?version=<?php echo esc_attr(FLUENT_COMMUNITY_PLUGIN_VERSION); ?>"
65 + media="screen"/>
66 + <?php // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet ?>
49 67
50 68 <style>
51 - <?php echo $this->getColorSchemaCss(); ?>
69 + /* No admin bar here; WP 7.1 offsets the editor by this var, leaving a gap. */
70 + html { --wp-admin--admin-bar--height: 0; }
71 + <?php echo wp_strip_all_tags($this->getColorSchemaCss()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- generated CSS, no user input ?>
52 72 </style>
53 73
54 74 <?php
55 75 });
@@ -96,10 +116,32 @@
96 116 $hasAccess = $space && $space->isAdmin(get_current_user_id(), true);
97 117 }
98 118 }
99 119
120 + /*
121 + * Extension point for editor contexts registered outside this plugin (the pro
122 + * Space Pages module uses it). Handlers matching $context return their own
123 + * access decision, post type and the content to seed the simulated post with.
124 + */
125 + $contextConfig = apply_filters('fluent_community/block_editor_context', [
126 + 'has_access' => $hasAccess,
127 + 'post_type' => $postType,
128 + 'has_content' => false,
129 + 'title' => '',
130 + 'content' => '',
131 + ], $context, $data);
132 +
133 + $hasAccess = !empty($contextConfig['has_access']);
134 +
135 + $this->contextEditorVars = (array)Arr::get($contextConfig, 'editor_vars', []);
136 +
137 + $contextPostType = Arr::get($contextConfig, 'post_type');
138 + if ($contextPostType) {
139 + $postType = $contextPostType;
140 + }
141 +
100 142 if (!$hasAccess) {
101 - echo '<h3 style="padding: 100px; text-align: center;">Sorry, you do not have access to this page.</h3>';
143 + echo '<h3 style="padding: 100px; text-align: center;">' . esc_html__('Sorry, you do not have access to this page.', 'fluent-community') . '</h3>';
102 144 exit(200);
103 145 }
104 146
105 147 add_filter('should_load_separate_core_block_assets', '__return_false', 20);
@@ -128,17 +170,20 @@
128 170
129 171 if ($lesson) {
130 172 $post->post_title = $lesson->title;
131 173 $post->post_content = $lesson->message ?: '<!-- wp:paragraph --><p> </p><!-- /wp:paragraph -->';
174 + } elseif (!empty($contextConfig['has_content'])) {
175 + $contextContent = Arr::get($contextConfig, 'content');
176 + $post->post_title = Arr::get($contextConfig, 'title', '');
177 + $post->post_content = $contextContent ? $contextContent : '<!-- wp:paragraph --><p> </p><!-- /wp:paragraph -->';
132 178 }
133 179
134 - $enqueueHook = 'wp_enqueue_scripts';
135 -
136 - if(is_admin()) {
137 - $enqueueHook = 'admin_enqueue_scripts';
138 - }
139 -
140 - add_action($enqueueHook, function () use ($post) {
180 + // renderPage() exits during admin_init and manually fires wp_enqueue_scripts in
181 + // both the admin and frontend branches. admin_enqueue_scripts never runs here, so
182 + // the apiFetch preload must ride wp_enqueue_scripts or the editor falls back to a
183 + // live GET /wp/v2/fcom-dummy/{id}?context=edit — which 403s for users without
184 + // edit_others_posts (e.g. contributors) on the shared, admin-authored dummy post.
185 + add_action('wp_enqueue_scripts', function () use ($post) {
141 186 wp_enqueue_script('postbox', admin_url('js/postbox.min.js'), array('jquery-ui-sortable'), FLUENT_COMMUNITY_PLUGIN_VERSION, true);
142 187 wp_enqueue_style('dashicons');
143 188 wp_enqueue_style('media');
144 189 wp_enqueue_style('admin-menu');
@@ -246,13 +291,9 @@
246 291
247 292 /**
248 293 * Scripts
249 294 */
250 - wp_enqueue_media(
251 - array(
252 - 'post' => null
253 - )
254 - );
295 + wp_enqueue_media();
255 296
256 297 add_filter('user_can_richedit', '__return_true');
257 298 wp_tinymce_inline_scripts();
258 299 wp_enqueue_editor();
@@ -275,12 +316,18 @@
275 316 wp_enqueue_style('wp-block-cover');
276 317 wp_enqueue_style('global-styles-css-custom-properties');
277 318 wp_enqueue_style('wp-block-spacer');
278 319
279 - wp_register_style('fluent_com_editor_styles', FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/style.css', false, FLUENT_COMMUNITY_PLUGIN_VERSION, 'all');
320 + wp_register_style('fluent_com_editor_styles', FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/style.css', [], FLUENT_COMMUNITY_PLUGIN_VERSION, 'all');
280 321
281 322 add_action('fluent_enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets');
282 323
324 + // WP 6.9+ loads the LaTeX-to-MathML converter as a script module; core wires this on
325 + // 'enqueue_block_editor_assets', which this standalone editor page never fires.
326 + if (function_exists('wp_enqueue_block_editor_script_modules')) {
327 + add_action('fluent_enqueue_block_editor_assets', 'wp_enqueue_block_editor_script_modules');
328 + }
329 +
283 330 /**
284 331 * Fires after block assets have been enqueued for the editing interface.
285 332 *
286 333 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
@@ -293,8 +340,12 @@
293 340 do_action('fluent_enqueue_block_editor_assets');
294 341
295 342 wp_enqueue_script('fcom_editor_custom', FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/index.js', ['react', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-i18n', 'wp-plugins'], FLUENT_COMMUNITY_PLUGIN_VERSION . time(), true);
296 343 wp_localize_script('fcom_editor_custom', 'fcomEditorI18n', $this->getEditorI18nStrings());
344 + wp_localize_script('fcom_editor_custom', 'fcomEditorVars', array_merge([
345 + 'video_gate_default_threshold' => \FluentCommunity\Modules\Course\Services\LessonVideoGateService::getDefaultThreshold(),
346 + 'can_unfiltered_html' => current_user_can('unfiltered_html') ? 'yes' : 'no',
347 + ], $this->contextEditorVars));
297 348 }
298 349
299 350 private function getEditorI18nStrings()
300 351 {
@@ -317,12 +368,25 @@
317 368 'Edit media' => __('Edit media', 'fluent-community'),
318 369 'Oembed' => __('Oembed', 'fluent-community'),
319 370 'Custom HTML' => __('Custom HTML', 'fluent-community'),
320 371 'Custom HTML Code' => __('Custom HTML Code', 'fluent-community'),
372 + 'Your account cannot save custom HTML. Scripts, iframes and similar tags are removed on save.' => __('Your account cannot save custom HTML. Scripts, iframes and similar tags are removed on save.', 'fluent-community'),
321 373 'Paste an iframe code' => __('Paste an iframe code', 'fluent-community'),
322 374 'Embed' => __('Embed', 'fluent-community'),
323 375 'Paste a URL to embed' => __('Paste a URL to embed', 'fluent-community'),
324 376 'Embed from Vimeo, YouTube, Wistia and more' => __('Embed from Vimeo, YouTube, Wistia and more', 'fluent-community'),
377 + 'FluentPlayer' => __('FluentPlayer', 'fluent-community'),
378 + 'Select Player Block' => __('Select Player Block', 'fluent-community'),
379 + 'Use FluentPlayer Block' => __('Use FluentPlayer Block', 'fluent-community'),
380 + 'The feature video is managed by the FluentPlayer block in the editor.' => __('The feature video is managed by the FluentPlayer block in the editor.', 'fluent-community'),
381 + 'A FluentPlayer block will be added at the top of the lesson content. Add or edit the video directly from that block in the editor.' => __('A FluentPlayer block will be added at the top of the lesson content. Add or edit the video directly from that block in the editor.', 'fluent-community'),
382 + 'Video Completion' => __('Video Completion', 'fluent-community'),
383 + 'Require video watch to complete' => __('Require video watch to complete', 'fluent-community'),
384 + 'Students must watch the video before they can mark this lesson as completed.' => __('Students must watch the video before they can mark this lesson as completed.', 'fluent-community'),
385 + 'Auto-complete lesson when the video ends' => __('Auto-complete lesson when the video ends', 'fluent-community'),
386 + 'Marks the lesson complete a few seconds after the student watches the video to the end.' => __('Marks the lesson complete a few seconds after the student watches the video to the end.', 'fluent-community'),
387 + 'Required watch percentage' => __('Required watch percentage', 'fluent-community'),
388 + '100% means the video must be watched to the end.' => __('100% means the video must be watched to the end.', 'fluent-community'),
325 389 'Lesson Duration' => __('Lesson Duration', 'fluent-community'),
326 390 'Minutes' => __('Minutes', 'fluent-community'),
327 391 'Seconds' => __('Seconds', 'fluent-community'),
328 392 'View' => __('View', 'fluent-community'),
@@ -331,8 +395,32 @@
331 395 "User's Name" => __("User's Name", 'fluent-community'),
332 396 "User's Email" => __("User's Email", 'fluent-community'),
333 397 "User's photo HTML" => __("User's photo HTML", 'fluent-community'),
334 398 'Profile Link' => __('Profile Link', 'fluent-community'),
399 + // Space page settings panel
400 + 'Who can view this page' => __('Who can view this page', 'fluent-community'),
401 + 'Everyone who can view the space' => __('Everyone who can view the space', 'fluent-community'),
402 + 'Space members only' => __('Space members only', 'fluent-community'),
403 + 'URL Slug' => __('URL Slug', 'fluent-community'),
404 + 'Changing the slug will break existing links to this page.' => __('Changing the slug will break existing links to this page.', 'fluent-community'),
405 + 'Show in space menu' => __('Show in space menu', 'fluent-community'),
406 + 'Menu Label' => __('Menu Label', 'fluent-community'),
407 + 'Leave empty to use the page title.' => __('Leave empty to use the page title.', 'fluent-community'),
408 + 'SEO' => __('SEO', 'fluent-community'),
409 + 'SEO Description' => __('SEO Description', 'fluent-community'),
410 + 'Used for search engines and link previews' => __('Used for search engines and link previews', 'fluent-community'),
411 + // Space page layout picker. The template labels and descriptions are
412 + // localized by whoever registers them (SpacePageHelper::getLayoutTemplates
413 + // in pro); only the fallback names used when pro is absent live here.
414 + 'Page Layout' => __('Page Layout', 'fluent-community'),
415 + 'Show the page title' => __('Show the page title', 'fluent-community'),
416 + 'Show the featured image' => __('Show the featured image', 'fluent-community'),
417 + 'The title stays editable here and still names the page in the menu.' => __('The title stays editable here and still names the page in the menu.', 'fluent-community'),
418 + 'The image still appears in search results and link previews.' => __('The image still appears in search results and link previews.', 'fluent-community'),
419 + 'Standard' => __('Standard', 'fluent-community'),
420 + 'Classic' => __('Classic', 'fluent-community'),
421 + 'Full Width' => __('Full Width', 'fluent-community'),
422 + 'Unified' => __('Unified', 'fluent-community'),
335 423 ];
336 424
337 425 return apply_filters('fluent_community/editor_i18n_strings', $strings);
338 426 }
@@ -365,11 +453,14 @@
365 453
366 454 add_action('fluent_community/block_editor_footer', function () {
367 455 wp_underscore_playlist_templates();
368 456 wp_print_footer_scripts();
457 + $this->printScriptModules();
369 458 wp_print_media_templates();
370 459 wp_enqueue_global_styles();
371 - wp_enqueue_stored_styles();
460 + if (function_exists('wp_enqueue_stored_styles')) {
461 + wp_enqueue_stored_styles();
462 + }
372 463 wp_maybe_inline_styles();
373 464 });
374 465
375 466 add_action('fluent_block_editor/head', 'wp_enqueue_scripts', 1);
@@ -403,8 +494,30 @@
403 494 </html>
404 495 <?php
405 496 }
406 497
498 + /**
499 + * Core prints script modules on 'wp_footer' / 'admin_print_footer_scripts'. This editor page
500 + * renders its own document and fires neither, so blocks relying on script modules (core/math
501 + * and its LaTeX-to-MathML converter on WP 6.9+) would never load.
502 + */
503 + protected function printScriptModules()
504 + {
505 + if (!function_exists('wp_script_modules')) {
506 + return;
507 + }
508 +
509 + $scriptModules = wp_script_modules();
510 +
511 + $scriptModules->print_import_map();
512 + $scriptModules->print_enqueued_script_modules();
513 + $scriptModules->print_script_module_preloads();
514 +
515 + if (method_exists($scriptModules, 'print_script_module_translations')) {
516 + $scriptModules->print_script_module_translations();
517 + }
518 + }
519 +
407 520 private function shouldBlockAsset(string $src, string $pluginUrl, string $themesUrl, string $approvedPattern): bool
408 521 {
409 522 $isPlugin = strpos($src, $pluginUrl) !== false;
410 523 $isTheme = strpos($src, $themesUrl) !== false;
@@ -417,24 +530,19 @@
417 530 }
418 531
419 532 private function unloadOtherScripts()
420 533 {
534 + // skips scripts and styles both; fluent_community/skip_no_conflict is styles only
421 535 $isSkip = apply_filters('fluent_com_editor/skip_no_conflict', false);
422 536 if ($isSkip) {
423 537 return;
424 538 }
425 539
426 - /**
427 - * Define the list of approved slugs for FluentCRM assets.
428 - *
429 - * This filter allows modification of the list of slugs that are approved for FluentCRM assets.
430 - *
431 - * @param array $approvedSlugs An array of approved slugs for FluentCRM assets.
432 - */
540 + // scripts only; styles use fluent_community/asset_listed_slugs
433 541 $approvedSlugs = apply_filters('fluent_com_editor/asset_listed_slugs', [
434 542 '\/gutenberg\/'
435 543 ]);
436 - $approvedSlugs[] = 'fluent-community';
544 + $approvedSlugs[] = '\/fluent-community(-pro)?\/';
437 545 $approvedSlugs = array_unique($approvedSlugs);
438 546 $approvedSlugs = implode('|', $approvedSlugs);
439 547
440 548 $pluginUrl = str_replace(['http:', 'https:'], '', plugins_url());
@@ -482,13 +590,14 @@
482 590 if (!$wp_styles) {
483 591 return;
484 592 }
485 593
594 + // styles only; scripts use fluent_com_editor/asset_listed_slugs
486 595 $approvedSlugs = apply_filters('fluent_community/asset_listed_slugs', [
487 596 '\/gutenberg\/',
488 597 ]);
489 598
490 - $approvedSlugs[] = '\/fluent-community\/';
599 + $approvedSlugs[] = '\/fluent-community(-pro)?\/';
491 600
492 601 $approvedSlugs = array_unique($approvedSlugs);
493 602 $approvedSlugs = implode('|', $approvedSlugs);
494 603
@@ -609,8 +718,9 @@
609 718 'core/code',
610 719 'core/columns',
611 720 'core/column',
612 721 'core/cover',
722 + 'core/details',
613 723 'core/embed',
614 724 'core/footnotes',
615 725 'core/freeform',
616 726 'core/gallery',
@@ -617,11 +727,11 @@
617 727 'core/group',
618 728 'core/heading',
619 729 'core/html',
620 730 'core/image',
621 - 'core/latest-posts',
622 731 'core/list',
623 732 'core/list-item',
733 + 'core/math',
624 734 'core/media-text',
625 735 'core/missing',
626 736 'core/paragraph',
627 737 'core/preformatted',
@@ -626,17 +736,14 @@
626 736 'core/paragraph',
627 737 'core/preformatted',
628 738 'core/pullquote',
629 739 'core/quote',
630 - 'core/rss',
631 740 'core/separator',
632 741 'core/social-link',
633 742 'core/social-links',
634 743 'core/spacer',
635 744 'core/table',
636 - 'core/text-columns',
637 - 'core/verse',
638 - 'core/freeform'
745 + 'core/verse'
639 746 ]),
640 747 'gradients' => [],
641 748 'imageDefaultSize' => 'large',
642 749 'imageEditing' => true,