PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.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 +137 -32 2.6.02.10.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',
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 }
@@ -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,8 +116,30 @@
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 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 }
@@ -131,8 +170,12 @@
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 180 // renderPage() exits during admin_init and manually fires wp_enqueue_scripts in
138 181 // both the admin and frontend branches. admin_enqueue_scripts never runs here, so
@@ -248,13 +291,9 @@
248 291
249 292 /**
250 293 * Scripts
251 294 */
252 - wp_enqueue_media(
253 - array(
254 - 'post' => null
255 - )
256 - );
295 + wp_enqueue_media();
257 296
258 297 add_filter('user_can_richedit', '__return_true');
259 298 wp_tinymce_inline_scripts();
260 299 wp_enqueue_editor();
@@ -277,12 +316,18 @@
277 316 wp_enqueue_style('wp-block-cover');
278 317 wp_enqueue_style('global-styles-css-custom-properties');
279 318 wp_enqueue_style('wp-block-spacer');
280 319
281 - 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');
282 321
283 322 add_action('fluent_enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets');
284 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 +
285 330 /**
286 331 * Fires after block assets have been enqueued for the editing interface.
287 332 *
288 333 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
@@ -295,8 +340,12 @@
295 340 do_action('fluent_enqueue_block_editor_assets');
296 341
297 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);
298 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));
299 348 }
300 349
301 350 private function getEditorI18nStrings()
302 351 {
@@ -319,12 +368,25 @@
319 368 'Edit media' => __('Edit media', 'fluent-community'),
320 369 'Oembed' => __('Oembed', 'fluent-community'),
321 370 'Custom HTML' => __('Custom HTML', 'fluent-community'),
322 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'),
323 373 'Paste an iframe code' => __('Paste an iframe code', 'fluent-community'),
324 374 'Embed' => __('Embed', 'fluent-community'),
325 375 'Paste a URL to embed' => __('Paste a URL to embed', 'fluent-community'),
326 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'),
327 389 'Lesson Duration' => __('Lesson Duration', 'fluent-community'),
328 390 'Minutes' => __('Minutes', 'fluent-community'),
329 391 'Seconds' => __('Seconds', 'fluent-community'),
330 392 'View' => __('View', 'fluent-community'),
@@ -333,8 +395,32 @@
333 395 "User's Name" => __("User's Name", 'fluent-community'),
334 396 "User's Email" => __("User's Email", 'fluent-community'),
335 397 "User's photo HTML" => __("User's photo HTML", 'fluent-community'),
336 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'),
337 423 ];
338 424
339 425 return apply_filters('fluent_community/editor_i18n_strings', $strings);
340 426 }
@@ -367,11 +453,14 @@
367 453
368 454 add_action('fluent_community/block_editor_footer', function () {
369 455 wp_underscore_playlist_templates();
370 456 wp_print_footer_scripts();
457 + $this->printScriptModules();
371 458 wp_print_media_templates();
372 459 wp_enqueue_global_styles();
373 - wp_enqueue_stored_styles();
460 + if (function_exists('wp_enqueue_stored_styles')) {
461 + wp_enqueue_stored_styles();
462 + }
374 463 wp_maybe_inline_styles();
375 464 });
376 465
377 466 add_action('fluent_block_editor/head', 'wp_enqueue_scripts', 1);
@@ -405,8 +494,30 @@
405 494 </html>
406 495 <?php
407 496 }
408 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 +
409 520 private function shouldBlockAsset(string $src, string $pluginUrl, string $themesUrl, string $approvedPattern): bool
410 521 {
411 522 $isPlugin = strpos($src, $pluginUrl) !== false;
412 523 $isTheme = strpos($src, $themesUrl) !== false;
@@ -419,24 +530,19 @@
419 530 }
420 531
421 532 private function unloadOtherScripts()
422 533 {
534 + // skips scripts and styles both; fluent_community/skip_no_conflict is styles only
423 535 $isSkip = apply_filters('fluent_com_editor/skip_no_conflict', false);
424 536 if ($isSkip) {
425 537 return;
426 538 }
427 539
428 - /**
429 - * Define the list of approved slugs for FluentCRM assets.
430 - *
431 - * This filter allows modification of the list of slugs that are approved for FluentCRM assets.
432 - *
433 - * @param array $approvedSlugs An array of approved slugs for FluentCRM assets.
434 - */
540 + // scripts only; styles use fluent_community/asset_listed_slugs
435 541 $approvedSlugs = apply_filters('fluent_com_editor/asset_listed_slugs', [
436 542 '\/gutenberg\/'
437 543 ]);
438 - $approvedSlugs[] = 'fluent-community';
544 + $approvedSlugs[] = '\/fluent-community(-pro)?\/';
439 545 $approvedSlugs = array_unique($approvedSlugs);
440 546 $approvedSlugs = implode('|', $approvedSlugs);
441 547
442 548 $pluginUrl = str_replace(['http:', 'https:'], '', plugins_url());
@@ -484,13 +590,14 @@
484 590 if (!$wp_styles) {
485 591 return;
486 592 }
487 593
594 + // styles only; scripts use fluent_com_editor/asset_listed_slugs
488 595 $approvedSlugs = apply_filters('fluent_community/asset_listed_slugs', [
489 596 '\/gutenberg\/',
490 597 ]);
491 598
492 - $approvedSlugs[] = '\/fluent-community\/';
599 + $approvedSlugs[] = '\/fluent-community(-pro)?\/';
493 600
494 601 $approvedSlugs = array_unique($approvedSlugs);
495 602 $approvedSlugs = implode('|', $approvedSlugs);
496 603
@@ -611,8 +718,9 @@
611 718 'core/code',
612 719 'core/columns',
613 720 'core/column',
614 721 'core/cover',
722 + 'core/details',
615 723 'core/embed',
616 724 'core/footnotes',
617 725 'core/freeform',
618 726 'core/gallery',
@@ -619,11 +727,11 @@
619 727 'core/group',
620 728 'core/heading',
621 729 'core/html',
622 730 'core/image',
623 - 'core/latest-posts',
624 731 'core/list',
625 732 'core/list-item',
733 + 'core/math',
626 734 'core/media-text',
627 735 'core/missing',
628 736 'core/paragraph',
629 737 'core/preformatted',
@@ -628,17 +736,14 @@
628 736 'core/paragraph',
629 737 'core/preformatted',
630 738 'core/pullquote',
631 739 'core/quote',
632 - 'core/rss',
633 740 'core/separator',
634 741 'core/social-link',
635 742 'core/social-links',
636 743 'core/spacer',
637 744 'core/table',
638 - 'core/text-columns',
639 - 'core/verse',
640 - 'core/freeform'
745 + 'core/verse'
641 746 ]),
642 747 'gradients' => [],
643 748 'imageDefaultSize' => 'large',
644 749 'imageEditing' => true,