PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
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 +153 -41 2.5.02.11.0 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', 'fluent-community'),
30 + 'public' => false,
31 + 'show_in_rest' => true,
32 + 'supports' => ['title', 'editor', 'thumbnail'],
33 + ],
34 + 'fcom-lockscreen' => [
35 + 'label' => __('Lock Screen', 'fluent-community'),
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 }
@@ -50,8 +65,10 @@
50 65 media="screen"/>
51 66 <?php // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet ?>
52 67
53 68 <style>
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; }
54 71 <?php echo wp_strip_all_tags($this->getColorSchemaCss()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- generated CSS, no user input ?>
55 72 </style>
56 73
57 74 <?php
@@ -99,10 +116,32 @@
99 116 $hasAccess = $space && $space->isAdmin(get_current_user_id(), true);
100 117 }
101 118 }
102 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 +
103 142 if (!$hasAccess) {
104 - 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>';
105 144 exit(200);
106 145 }
107 146
108 147 add_filter('should_load_separate_core_block_assets', '__return_false', 20);
@@ -131,17 +170,20 @@
131 170
132 171 if ($lesson) {
133 172 $post->post_title = $lesson->title;
134 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 -->';
135 178 }
136 179
137 - $enqueueHook = 'wp_enqueue_scripts';
138 -
139 - if(is_admin()) {
140 - $enqueueHook = 'admin_enqueue_scripts';
141 - }
142 -
143 - 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) {
144 186 wp_enqueue_script('postbox', admin_url('js/postbox.min.js'), array('jquery-ui-sortable'), FLUENT_COMMUNITY_PLUGIN_VERSION, true);
145 187 wp_enqueue_style('dashicons');
146 188 wp_enqueue_style('media');
147 189 wp_enqueue_style('admin-menu');
@@ -249,13 +291,9 @@
249 291
250 292 /**
251 293 * Scripts
252 294 */
253 - wp_enqueue_media(
254 - array(
255 - 'post' => null
256 - )
257 - );
295 + wp_enqueue_media();
258 296
259 297 add_filter('user_can_richedit', '__return_true');
260 298 wp_tinymce_inline_scripts();
261 299 wp_enqueue_editor();
@@ -278,12 +316,18 @@
278 316 wp_enqueue_style('wp-block-cover');
279 317 wp_enqueue_style('global-styles-css-custom-properties');
280 318 wp_enqueue_style('wp-block-spacer');
281 319
282 - 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');
283 321
284 322 add_action('fluent_enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets');
285 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 +
286 330 /**
287 331 * Fires after block assets have been enqueued for the editing interface.
288 332 *
289 333 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
@@ -296,8 +340,12 @@
296 340 do_action('fluent_enqueue_block_editor_assets');
297 341
298 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);
299 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));
300 348 }
301 349
302 350 private function getEditorI18nStrings()
303 351 {
@@ -316,16 +364,37 @@
316 364 "User's Profile Link:" => __("User's Profile Link:", 'fluent-community'),
317 365 'Failed to fetch embed. Please check the URL.' => __('Failed to fetch embed. Please check the URL.', 'fluent-community'),
318 366 'Video thumbnail' => __('Video thumbnail', 'fluent-community'),
319 367 'Media embedded successfully' => __('Media embedded successfully', 'fluent-community'),
320 - 'Edit media' => __('Edit media', 'fluent-community'),
368 + '+ Add Media' => __('+ Add Media', 'fluent-community'),
369 + 'Add Media' => __('Add Media', 'fluent-community'),
370 + 'Edit Media' => __('Edit Media', 'fluent-community'),
371 + 'Oembed URL' => __('Oembed URL', 'fluent-community'),
372 + 'Video URL' => __('Video URL', 'fluent-community'),
373 + 'Supports Vimeo, YouTube, Wistia and more' => __('Supports Vimeo, YouTube, Wistia and more', 'fluent-community'),
374 + 'Embedding…' => __('Embedding…', 'fluent-community'),
375 + 'Cancel' => __('Cancel', 'fluent-community'),
376 + 'Remove' => __('Remove', 'fluent-community'),
321 377 'Oembed' => __('Oembed', 'fluent-community'),
322 378 'Custom HTML' => __('Custom HTML', 'fluent-community'),
323 379 'Custom HTML Code' => __('Custom HTML Code', 'fluent-community'),
380 + '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'),
324 381 'Paste an iframe code' => __('Paste an iframe code', 'fluent-community'),
325 382 'Embed' => __('Embed', 'fluent-community'),
326 383 'Paste a URL to embed' => __('Paste a URL to embed', 'fluent-community'),
327 384 'Embed from Vimeo, YouTube, Wistia and more' => __('Embed from Vimeo, YouTube, Wistia and more', 'fluent-community'),
385 + 'FluentPlayer' => __('FluentPlayer', 'fluent-community'),
386 + 'Select Player Block' => __('Select Player Block', 'fluent-community'),
387 + 'Use FluentPlayer Block' => __('Use FluentPlayer Block', 'fluent-community'),
388 + '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'),
389 + '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'),
390 + 'Video Completion' => __('Video Completion', 'fluent-community'),
391 + 'Require video watch to complete' => __('Require video watch to complete', 'fluent-community'),
392 + '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'),
393 + 'Auto-complete lesson when the video ends' => __('Auto-complete lesson when the video ends', 'fluent-community'),
394 + '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'),
395 + 'Required watch percentage' => __('Required watch percentage', 'fluent-community'),
396 + '100% means the video must be watched to the end.' => __('100% means the video must be watched to the end.', 'fluent-community'),
328 397 'Lesson Duration' => __('Lesson Duration', 'fluent-community'),
329 398 'Minutes' => __('Minutes', 'fluent-community'),
330 399 'Seconds' => __('Seconds', 'fluent-community'),
331 400 'View' => __('View', 'fluent-community'),
@@ -334,8 +403,32 @@
334 403 "User's Name" => __("User's Name", 'fluent-community'),
335 404 "User's Email" => __("User's Email", 'fluent-community'),
336 405 "User's photo HTML" => __("User's photo HTML", 'fluent-community'),
337 406 'Profile Link' => __('Profile Link', 'fluent-community'),
407 + // Space page settings panel
408 + 'Who can view this page' => __('Who can view this page', 'fluent-community'),
409 + 'Everyone who can view the space' => __('Everyone who can view the space', 'fluent-community'),
410 + 'Space members only' => __('Space members only', 'fluent-community'),
411 + 'URL Slug' => __('URL Slug', 'fluent-community'),
412 + 'Changing the slug will break existing links to this page.' => __('Changing the slug will break existing links to this page.', 'fluent-community'),
413 + 'Show in space menu' => __('Show in space menu', 'fluent-community'),
414 + 'Menu Label' => __('Menu Label', 'fluent-community'),
415 + 'Leave empty to use the page title.' => __('Leave empty to use the page title.', 'fluent-community'),
416 + 'SEO' => __('SEO', 'fluent-community'),
417 + 'SEO Description' => __('SEO Description', 'fluent-community'),
418 + 'Used for search engines and link previews' => __('Used for search engines and link previews', 'fluent-community'),
419 + // Space page layout picker. The template labels and descriptions are
420 + // localized by whoever registers them (SpacePageHelper::getLayoutTemplates
421 + // in pro); only the fallback names used when pro is absent live here.
422 + 'Page Layout' => __('Page Layout', 'fluent-community'),
423 + 'Show the page title' => __('Show the page title', 'fluent-community'),
424 + 'Show the featured image' => __('Show the featured image', 'fluent-community'),
425 + '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'),
426 + 'The image still appears in search results and link previews.' => __('The image still appears in search results and link previews.', 'fluent-community'),
427 + 'Standard' => __('Standard', 'fluent-community'),
428 + 'Classic' => __('Classic', 'fluent-community'),
429 + 'Full Width' => __('Full Width', 'fluent-community'),
430 + 'Unified' => __('Unified', 'fluent-community'),
338 431 ];
339 432
340 433 return apply_filters('fluent_community/editor_i18n_strings', $strings);
341 434 }
@@ -368,11 +461,14 @@
368 461
369 462 add_action('fluent_community/block_editor_footer', function () {
370 463 wp_underscore_playlist_templates();
371 464 wp_print_footer_scripts();
465 + $this->printScriptModules();
372 466 wp_print_media_templates();
373 467 wp_enqueue_global_styles();
374 - wp_enqueue_stored_styles();
468 + if (function_exists('wp_enqueue_stored_styles')) {
469 + wp_enqueue_stored_styles();
470 + }
375 471 wp_maybe_inline_styles();
376 472 });
377 473
378 474 add_action('fluent_block_editor/head', 'wp_enqueue_scripts', 1);
@@ -406,8 +502,30 @@
406 502 </html>
407 503 <?php
408 504 }
409 505
506 + /**
507 + * Core prints script modules on 'wp_footer' / 'admin_print_footer_scripts'. This editor page
508 + * renders its own document and fires neither, so blocks relying on script modules (core/math
509 + * and its LaTeX-to-MathML converter on WP 6.9+) would never load.
510 + */
511 + protected function printScriptModules()
512 + {
513 + if (!function_exists('wp_script_modules')) {
514 + return;
515 + }
516 +
517 + $scriptModules = wp_script_modules();
518 +
519 + $scriptModules->print_import_map();
520 + $scriptModules->print_enqueued_script_modules();
521 + $scriptModules->print_script_module_preloads();
522 +
523 + if (method_exists($scriptModules, 'print_script_module_translations')) {
524 + $scriptModules->print_script_module_translations();
525 + }
526 + }
527 +
410 528 private function shouldBlockAsset(string $src, string $pluginUrl, string $themesUrl, string $approvedPattern): bool
411 529 {
412 530 $isPlugin = strpos($src, $pluginUrl) !== false;
413 531 $isTheme = strpos($src, $themesUrl) !== false;
@@ -420,24 +538,19 @@
420 538 }
421 539
422 540 private function unloadOtherScripts()
423 541 {
542 + // skips scripts and styles both; fluent_community/skip_no_conflict is styles only
424 543 $isSkip = apply_filters('fluent_com_editor/skip_no_conflict', false);
425 544 if ($isSkip) {
426 545 return;
427 546 }
428 547
429 - /**
430 - * Define the list of approved slugs for FluentCRM assets.
431 - *
432 - * This filter allows modification of the list of slugs that are approved for FluentCRM assets.
433 - *
434 - * @param array $approvedSlugs An array of approved slugs for FluentCRM assets.
435 - */
548 + // scripts only; styles use fluent_community/asset_listed_slugs
436 549 $approvedSlugs = apply_filters('fluent_com_editor/asset_listed_slugs', [
437 550 '\/gutenberg\/'
438 551 ]);
439 - $approvedSlugs[] = 'fluent-community';
552 + $approvedSlugs[] = '\/fluent-community(-pro)?\/';
440 553 $approvedSlugs = array_unique($approvedSlugs);
441 554 $approvedSlugs = implode('|', $approvedSlugs);
442 555
443 556 $pluginUrl = str_replace(['http:', 'https:'], '', plugins_url());
@@ -485,13 +598,14 @@
485 598 if (!$wp_styles) {
486 599 return;
487 600 }
488 601
602 + // styles only; scripts use fluent_com_editor/asset_listed_slugs
489 603 $approvedSlugs = apply_filters('fluent_community/asset_listed_slugs', [
490 604 '\/gutenberg\/',
491 605 ]);
492 606
493 - $approvedSlugs[] = '\/fluent-community\/';
607 + $approvedSlugs[] = '\/fluent-community(-pro)?\/';
494 608
495 609 $approvedSlugs = array_unique($approvedSlugs);
496 610 $approvedSlugs = implode('|', $approvedSlugs);
497 611
@@ -612,8 +726,9 @@
612 726 'core/code',
613 727 'core/columns',
614 728 'core/column',
615 729 'core/cover',
730 + 'core/details',
616 731 'core/embed',
617 732 'core/footnotes',
618 733 'core/freeform',
619 734 'core/gallery',
@@ -620,11 +735,11 @@
620 735 'core/group',
621 736 'core/heading',
622 737 'core/html',
623 738 'core/image',
624 - 'core/latest-posts',
625 739 'core/list',
626 740 'core/list-item',
741 + 'core/math',
627 742 'core/media-text',
628 743 'core/missing',
629 744 'core/paragraph',
630 745 'core/preformatted',
@@ -629,17 +744,14 @@
629 744 'core/paragraph',
630 745 'core/preformatted',
631 746 'core/pullquote',
632 747 'core/quote',
633 - 'core/rss',
634 748 'core/separator',
635 749 'core/social-link',
636 750 'core/social-links',
637 751 'core/spacer',
638 752 'core/table',
639 - 'core/text-columns',
640 - 'core/verse',
641 - 'core/freeform'
753 + 'core/verse'
642 754 ]),
643 755 'gradients' => [],
644 756 'imageDefaultSize' => 'large',
645 757 'imageEditing' => true,