Files
gustavoo-me/wp-content/plugins/fluent-crm/app/Hooks/Handlers/FluentBlockEditorHandler.php
T
2026-07-23 22:56:30 -03:00

2265 lines
119 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace FluentCrm\App\Hooks\Handlers;
use FluentCrm\App\Models\Campaign;
use FluentCrm\App\Models\FunnelCampaign;
use FluentCrm\App\Models\Meta;
use FluentCrm\App\Models\Tag;
use FluentCrm\App\Models\Template;
use FluentCrm\App\Services\BlockRender\BlockEditorHelper;
use FluentCrm\App\Services\BlockRender\CartProductData;
use FluentCrm\App\Services\Helper;
use FluentCrm\App\Services\PermissionManager;
use FluentCrm\App\Services\TransStringsGuten;
use FluentCrm\Framework\Support\Arr;
class FluentBlockEditorHandler
{
/**
* Data passed to the iframe editor via window.fcrmEditorBoot
* @var array
*/
protected $editorBootData = [];
/*
|--------------------------------------------------------------------------
| Public: register(), handleEditorAutosave(), handleCartProductsListing()
|--------------------------------------------------------------------------
*/
public function register()
{
add_filter('register_block_type_args', [$this, 'registerConditionalVisibilityAttributes'], 10, 2);
add_action('init', function () {
register_post_type('fcrm-dummy', [
'label' => 'Email-Body',
'public' => false,
'show_in_rest' => true,
'supports' => ['editor', 'thumbnail']
]);
if (isset($_REQUEST['fluent_crm_block_editor'])) {
// Require authentication
if (!is_user_logged_in()) {
$scheme = is_ssl() ? 'https://' : 'http://';
$current_url = $scheme . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '') . (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '');
wp_safe_redirect(wp_login_url(esc_url_raw($current_url)));
exit;
}
// Determine required capability based on context
$block_type = isset($_REQUEST['block_type']) ? sanitize_text_field(wp_unslash($_REQUEST['block_type'])) : '';
$required_cap = self::getRequiredCapability($block_type);
/**
* Filter the required capability for opening the FluentCRM block editor.
*
* @param string $required_cap Default capability derived from block type.
* @param string $block_type The requested block type.
* @param array $request Raw request array for advanced checks.
*/
$required_cap = apply_filters('fluent_crm/block_editor_required_cap', $required_cap, $block_type, $_REQUEST);
if (!PermissionManager::currentUserCan($required_cap)) {
status_header(403);
wp_die(__('Sorry, you are not allowed to access this page.', 'fluent-crm'), 403);
}
// Optional nonce enforcement (off by default to preserve existing links)
$require_nonce = (bool)apply_filters('fluent_crm/block_editor_require_nonce', false, $block_type, $_REQUEST);
if ($require_nonce) {
$nonce = isset($_REQUEST['_fcrm_nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['_fcrm_nonce'])) : '';
if (!$nonce || !wp_verify_nonce($nonce, 'fcrm_block_editor')) {
status_header(403);
wp_die(__('Invalid or missing security token.', 'fluent-crm'), 403);
}
}
if (!defined('IFRAME_REQUEST')) {
define('IFRAME_REQUEST', true);
}
remove_action('enqueue_block_editor_assets', 'wp_enqueue_editor_block_directory_assets');
add_action('fluent_crm/block_editor_head', function () {
$asset_file = FLUENTCRM_PLUGIN_PATH . 'assets/guten-editor/index.asset.php';
$asset = file_exists($asset_file) ? require($asset_file) : [];
$version = !empty($asset['version']) ? $asset['version'] : FLUENTCRM_PLUGIN_VERSION;
$url = FLUENTCRM_PLUGIN_URL . 'assets/guten-editor/index.css';
?>
<link rel="stylesheet"
href="<?php echo esc_url($url); ?>?version=<?php echo esc_attr($version); ?>"
media="screen"/>
<?php
});
add_filter('should_load_separate_core_block_assets', '__return_false', 20);
$this->initializeEditor($_REQUEST); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$actionHook = 'template_redirect';
if(is_admin()) {
$actionHook = 'admin_init';
}
add_action($actionHook, function () {
$this->renderPage();
exit(200);
}, -1000);
}
}, 2);
// REST route for autosave
add_action('rest_api_init', function () {
register_rest_route('fluent-crm/v1', '/editor-autosave', [
'methods' => 'POST',
'callback' => [$this, 'handleEditorAutosave'],
'permission_callback' => function () {
// Basic capability check refined per payload inside handler
return is_user_logged_in();
}
]);
register_rest_route('fluent-crm/v2', '/editor/cart-products', [
'methods' => 'GET',
'callback' => [$this, 'handleCartProductsListing'],
'permission_callback' => function () {
return is_user_logged_in() && PermissionManager::currentUserCan('fcrm_read_emails');
}
]);
// Native REST routes for editor pattern CRUD (wp_block-compatible format)
$patternPermission = function () {
return is_user_logged_in() && PermissionManager::currentUserCan('fcrm_manage_email_templates');
};
register_rest_route('fluent-crm/v2', '/editor-patterns', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'handleEditorPatternsList'],
'permission_callback' => $patternPermission
]);
register_rest_route('fluent-crm/v2', '/editor-patterns', [
'methods' => \WP_REST_Server::CREATABLE,
'callback' => [$this, 'handleEditorPatternCreate'],
'permission_callback' => $patternPermission
]);
register_rest_route('fluent-crm/v2', '/editor-patterns/(?P<id>\d+)', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'handleEditorPatternGet'],
'permission_callback' => $patternPermission
]);
register_rest_route('fluent-crm/v2', '/editor-patterns/(?P<id>\d+)', [
'methods' => \WP_REST_Server::EDITABLE,
'callback' => [$this, 'handleEditorPatternUpdate'],
'permission_callback' => $patternPermission
]);
register_rest_route('fluent-crm/v2', '/editor-patterns/(?P<id>\d+)', [
'methods' => \WP_REST_Server::DELETABLE,
'callback' => [$this, 'handleEditorPatternDelete'],
'permission_callback' => function () {
return is_user_logged_in() && PermissionManager::currentUserCan('fcrm_manage_email_delete');
}
]);
register_rest_route('fluent-crm/v2', '/editor-pattern-categories', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'handleEditorPatternCategories'],
'permission_callback' => $patternPermission
]);
register_rest_route('fluent-crm/v2', '/editor-pattern-categories', [
'methods' => \WP_REST_Server::CREATABLE,
'callback' => [$this, 'handleEditorPatternCategoryCreate'],
'permission_callback' => $patternPermission
]);
});
}
/**
* Register FluentCRM conditional visibility attributes in PHP.
*
* WordPress validates dynamic block attributes against the server-side
* block schema during render REST requests, so this also runs when the
* render request contains FluentCRM's conditional visibility attributes.
*
* @param array $args Block registration arguments.
* @param string $name Block name.
* @return array
*/
public function registerConditionalVisibilityAttributes($args, $name)
{
$shouldRegister = $this->shouldRegisterConditionalVisibilityAttributes();
if (!apply_filters('fluent_crm/block_editor_register_conditional_visibility_attributes', $shouldRegister, $name, $args)) {
return $args;
}
$skipBlocks = [
'fluentcrm/conditional-group',
'fluent-crm/conditional-content'
];
if (in_array($name, $skipBlocks, true)) {
return $args;
}
if (!isset($args['attributes']) || !is_array($args['attributes'])) {
$args['attributes'] = [];
}
$attributes = apply_filters('fluent_crm/block_editor_conditional_visibility_attributes', [
'fcrmConditionType' => [
'type' => 'string',
'default' => ''
],
'fcrmTagIds' => [
'type' => 'array',
'default' => []
]
], $name, $args);
foreach ((array)$attributes as $attributeName => $attributeConfig) {
if (!isset($args['attributes'][$attributeName])) {
$args['attributes'][$attributeName] = $attributeConfig;
}
}
return $args;
}
/**
* Check if the current request needs FluentCRM conditional visibility
* attributes in server-side block schemas.
*
* @return bool
*/
private function shouldRegisterConditionalVisibilityAttributes()
{
if (isset($_REQUEST['fluent_crm_block_editor'])) {
return true;
}
if (!isset($_REQUEST['attributes'])) {
return false;
}
$attributes = wp_unslash($_REQUEST['attributes']);
if (is_array($attributes)) {
return array_key_exists('fcrmConditionType', $attributes) || array_key_exists('fcrmTagIds', $attributes);
}
if (is_string($attributes)) {
return strpos($attributes, 'fcrmConditionType') !== false || strpos($attributes, 'fcrmTagIds') !== false;
}
return false;
}
/**
* Handle autosave requests for the block editor iframe.
* Creates or updates campaign / recurring campaign / template records.
* If block_type is empty and id/bid is 0, nothing will be created.
*
* @param \WP_REST_Request $request
* @return \WP_REST_Response|\WP_Error
*/
public function handleEditorAutosave(\WP_REST_Request $request)
{
$params = $request->get_json_params();
if (!$params) {
$params = $request->get_body_params();
}
$blockType = isset($params['block_type']) ? sanitize_text_field($params['block_type']) : '';
$entityId = isset($params['id']) ? (int)$params['id'] : 0;
$title = isset($params['title']) ? wp_strip_all_tags($params['title']) : '';
$content = isset($params['content']) ? $params['content'] : '';
$prevUpdatedAt = isset($params['prev_updated_at']) ? sanitize_text_field($params['prev_updated_at']) : '';
$hash = isset($params['hash']) ? sanitize_text_field($params['hash']) : '';
// Basic guard: if block type empty and no existing entity, do not create
if (empty($blockType) && !$entityId) {
return new \WP_REST_Response([
'status' => 'skipped',
'message' => __('No block_type provided; nothing created.', 'fluent-crm')
], 200);
}
// Capability check
$required_cap = self::getRequiredCapability($blockType);
$required_cap = apply_filters('fluent_crm/block_editor_required_cap', $required_cap, $blockType, $params);
if (!PermissionManager::currentUserCan($required_cap)) {
return new \WP_Error('forbidden', __('You do not have permission to autosave this item', 'fluent-crm'), ['status' => 403]);
}
// Resolve model + field mapping
$model = null;
$contentField = null;
$titleField = null;
if ($blockType === 'campaign') {
$model = Campaign::class;
$contentField = 'email_body';
$titleField = 'title';
} elseif ($blockType === 'email_body_in_funnel') {
$model = FunnelCampaign::class;
$contentField = 'email_body';
$titleField = 'title';
} elseif ($blockType === 'recurring_campaign') {
if (defined('FLUENTCAMPAIGN')) {
$model = \FluentCampaign\App\Models\RecurringCampaign::class;
}
// Free-tier: $model stays null; the find block below uses withoutGlobalScopes() fallback.
$contentField = 'email_body';
$titleField = 'title';
} elseif ($blockType === 'sequence_mail') {
if (defined('FLUENTCAMPAIGN')) {
$model = \FluentCampaign\App\Models\SequenceMail::class;
}
// Free-tier: $model stays null; the find block below uses withoutGlobalScopes() fallback.
$contentField = 'email_body';
$titleField = 'title';
} elseif ($blockType === 'recurring_mail') {
if (defined('FLUENTCAMPAIGN')) {
$model = \FluentCampaign\App\Models\RecurringMail::class;
}
$contentField = 'email_body';
$titleField = 'title';
} elseif ($blockType === 'template') {
$model = Template::class;
$contentField = 'post_content';
$titleField = 'post_title';
} else {
return new \WP_Error('invalid_block_type', __('Unsupported block_type', 'fluent-crm'), ['status' => 400]);
}
// Guard: recurring_campaign requires Pro; without it $model stays null — skip gracefully
if ($model === null) {
return new \WP_REST_Response([
'status' => 'skipped',
'message' => __('Feature not available.', 'fluent-crm')
], 200);
}
$now = current_time('mysql');
$created = false;
$record = null;
if (!$entityId) {
return new \WP_REST_Response([
'status' => 'skipped',
'message' => __('No entity ID provided.', 'fluent-crm')
], 200);
}
if ($entityId) {
if ($model) {
$record = $model::find($entityId);
} else {
// recurring_campaign on free tier: fc_campaigns with type=recurring_campaign.
// sequence_mail on free tier: fc_campaigns with type=sequence_mail.
// Bypass the Campaign global scope so the type filter does not exclude it.
$record = Campaign::withoutGlobalScopes()->find($entityId);
}
if (!$record) {
return new \WP_Error('not_found', __('Entity not found', 'fluent-crm'), ['status' => 404]);
}
// Conflict detection (always compare with model's updated_at mapping)
if (!empty($prevUpdatedAt) && isset($record->updated_at) && $prevUpdatedAt && $prevUpdatedAt !== $record->updated_at) {
return new \WP_REST_Response([
'status' => 'conflict',
'id' => $entityId,
'server_updated_at' => $record->updated_at,
'message' => __('The item was modified elsewhere.', 'fluent-crm')
], 200);
}
}
// Update existing record if needed
$dirty = false;
if ($blockType === 'template') {
// For templates we ONLY save post_content using core wp_update_post for proper cache + hooks
if ($record->{$contentField} !== $content) {
$postArr = [
'ID' => $entityId,
'post_content' => $content,
'post_modified' => current_time('mysql'),
'post_modified_gmt' => gmdate('Y-m-d H:i:s')
];
$result = wp_update_post($postArr, true);
if (is_wp_error($result)) {
return new \WP_Error('update_failed', $result->get_error_message(), ['status' => 500]);
}
$dirty = true;
// Reload record to get fresh timestamps and updated content
$record = Template::find($entityId);
}
} else {
if ($record->{$contentField} !== $content) {
$record->{$contentField} = $content;
$dirty = true;
}
// Even though frontend no longer sends title for autosave, keep defensive logic (but campaign/recurring only)
if ($title && $record->{$titleField} !== $title) {
$record->{$titleField} = $title;
$dirty = true;
}
if ($dirty) {
try {
$record->save();
} catch (\Exception $e) {
return new \WP_Error('update_failed', $e->getMessage(), ['status' => 500]);
}
}
}
$updatedAt = isset($record->updated_at) ? $record->updated_at : $now;
return new \WP_REST_Response([
'status' => $created ? 'created' : ($dirty ? 'ok' : 'noop'),
'id' => $entityId,
'hash' => $hash,
'updated_at' => $updatedAt,
'saved_at' => $now,
'created' => $created
], 200);
}
public function handleCartProductsListing(\WP_REST_Request $request)
{
$fallback = [
'products' => [],
'product' => null,
'taxonomies' => []
];
$providerResponse = apply_filters('fluent_crm/cart_products_preview_data', null, $request);
if (is_array($providerResponse)) {
return new \WP_REST_Response(wp_parse_args($providerResponse, $fallback), 200);
}
if (!defined('FLUENTCART_VERSION')) {
return new \WP_REST_Response($fallback, 200);
}
$perPage = max(1, min(20, absint($request->get_param('per_page') ?: 3)));
$order = strtolower((string)$request->get_param('order')) === 'asc' ? 'ASC' : 'DESC';
$productId = absint($request->get_param('product_id'));
$search = sanitize_text_field((string)$request->get_param('search'));
if (!$productId) {
$searchProductId = 0;
if (preg_match('/^\s*(?:id|product_id)\s*:\s*(\d+)\s*$/i', $search, $matches)) {
$searchProductId = absint($matches[1]);
} elseif (preg_match('/^\s*#\s*(\d+)\s*$/', $search, $matches)) {
$searchProductId = absint($matches[1]);
}
if ($searchProductId) {
$productId = $searchProductId;
$search = '';
}
}
$taxType = sanitize_text_field((string)$request->get_param('taxType'));
$products = CartProductData::getProducts([
'perPage' => $perPage,
'order' => $order,
'orderBy' => sanitize_key((string)$request->get_param('orderby')),
'taxType' => $taxType,
'search' => $search,
'productId' => $productId,
], 'medium');
$selectedProduct = ($productId && !empty($products[0])) ? $products[0] : null;
$terms = get_terms([
'taxonomy' => 'product-categories',
'hide_empty' => false
]);
$termOptions = [];
if (!is_wp_error($terms) && is_array($terms)) {
foreach ($terms as $term) {
$termOptions[] = [
'value' => (string)$term->term_id,
'label' => $term->name
];
}
}
$response = [
'products' => $products,
'product' => $selectedProduct,
'taxonomies' => [
'product' => [
'terms' => [
'product_cat' => $termOptions
]
]
]
];
return new \WP_REST_Response(
apply_filters('fluent_crm/cart_products_preview_response', $response, $request),
200
);
}
/*
|--------------------------------------------------------------------------
| Editor init: initializeEditor(), resolveEntityContent(),
| getOrCreateDummyPost(), prepareEditorBootData(), setupEditorHooks()
|--------------------------------------------------------------------------
*/
public function initializeEditor($data = [])
{
do_action('litespeed_control_set_nocache', 'fluentcrm api request');
// set no cache headers
nocache_headers();
$context = Arr::get($data, 'block_type'); // campaign or template etc ..
$this->unregisterDefaultBlockPatterns($context, $data);
// Double-check permissions inside renderer based on context
$required_cap = self::getRequiredCapability($context);
$required_cap = apply_filters('fluent_crm/block_editor_required_cap', $required_cap, $context, $data);
$hasAccess = PermissionManager::currentUserCan($required_cap);
$entity = $this->resolveEntityContent($context, $data);
if (!$hasAccess) {
echo '<h3 style="padding: 100px; text-align: center;">' . esc_html__('Sorry, you do not have access to this page.', 'fluent-crm') . '</h3>';
exit(200);
}
add_filter('should_load_separate_core_block_assets', '__return_false', 20);
show_admin_bar(false);
global $post;
$post = $this->getOrCreateDummyPost($entity['title'], $entity['content']);
$this->prepareEditorBootData($context, $data, $entity);
$this->setupEditorHooks($post);
}
/**
* Resolve entity content from the database based on block_type and bid.
*
* @param string $context The block_type (campaign, recurring_campaign, template).
* @param array $data The request data.
* @return array With keys: content, title, id, updatedAt, availableTags.
*/
private function resolveEntityContent($context, $data)
{
$post_content = '<!-- wp:paragraph --><p></p><!-- /wp:paragraph -->';
$post_title = 'Demo Title';
$recordId = null;
$recordUpdatedAt = current_time('mysql');
$availableTags = [];
if ($context == 'campaign') {
$campaignId = (int)Arr::get($data, 'bid');
if ($campaignId && $campaignId != 'undefined') {
$campaign = Campaign::find($campaignId);
if ($campaign) {
$post_content = $campaign->email_body;
$post_title = $campaign->title;
$recordId = $campaign->id;
$recordUpdatedAt = isset($campaign->updated_at) ? $campaign->updated_at : $recordUpdatedAt;
}
}
}
if ($context == 'email_body_in_funnel') {
$campaignId = (int)Arr::get($data, 'bid');
if ($campaignId && $campaignId != 'undefined') {
$campaign = FunnelCampaign::find($campaignId);
if ($campaign) {
$post_content = $campaign->email_body;
$post_title = $campaign->title;
$recordId = $campaign->id;
$recordUpdatedAt = isset($campaign->updated_at) ? $campaign->updated_at : $recordUpdatedAt;
}
}
}
// Pro Required for Recurring Campaigns
if (defined('FLUENTCAMPAIGN')) {
if ($context == 'recurring_campaign') {
$campaign = null;
$campaignId = (int)Arr::get($data, 'bid');
if ($campaignId && $campaignId != 'undefined') {
$campaign = \FluentCampaign\App\Models\RecurringCampaign::find($campaignId);
}
// Fallback for free-tier Email Sequences stored in fc_campaigns with type=recurring_campaign.
if (!$campaign && $campaignId) {
$campaign = Campaign::withoutGlobalScopes()->find($campaignId);
}
if ($campaign) {
$post_content = $campaign->email_body;
$post_title = $campaign->title;
$recordId = $campaign->id;
$recordUpdatedAt = isset($campaign->updated_at) ? $campaign->updated_at : $recordUpdatedAt;
}
}
if ($context == 'recurring_mail') {
$mailId = (int)Arr::get($data, 'bid');
if ($mailId && $mailId != 'undefined') {
$mail = \FluentCampaign\App\Models\RecurringMail::find($mailId);
if ($mail) {
$post_content = $mail->email_body;
$post_title = $mail->title;
$recordId = $mail->id;
$recordUpdatedAt = isset($mail->updated_at) ? $mail->updated_at : $recordUpdatedAt;
}
}
}
if ($context == 'sequence_mail') {
$mail = null;
$mailId = (int)Arr::get($data, 'bid');
if ($mailId && $mailId != 'undefined') {
$mail = \FluentCampaign\App\Models\SequenceMail::find($mailId);
}
// Fallback for free-tier Email Sequences stored in fc_campaigns with type=sequence_mail.
if (!$mail && $mailId) {
$mail = Campaign::withoutGlobalScopes()->find($mailId);
}
if ($mail) {
$post_content = $mail->email_body;
$post_title = $mail->title;
$recordId = $mail->id;
$recordUpdatedAt = isset($mail->updated_at) ? $mail->updated_at : $recordUpdatedAt;
}
}
}
if ($context == 'template') {
$templateId = (int)Arr::get($data, 'bid');
if ($templateId && $templateId != 'undefined') {
$template = Template::find($templateId);
if ($template) {
$post_content = $template->post_content;
$post_title = $template->post_title;
$recordId = $templateId;
$recordUpdatedAt = isset($template->updated_at) ? $template->updated_at : $recordUpdatedAt;
}
}
}
if ($context == 'email_pattern') {
$patternId = (int)Arr::get($data, 'bid');
if ($patternId && $patternId != 'undefined') {
$pattern = Meta::where('object_type', 'email_pattern')->where('id', $patternId)->first();
if ($pattern) {
$post_content = Arr::get($pattern->value, 'content', '');
$post_title = Arr::get($pattern->value, 'title', '');
$recordId = $pattern->id;
$recordUpdatedAt = $pattern->updated_at ?: $recordUpdatedAt;
}
}
}
try {
$availableTags = Tag::select(['id', 'title'])->orderBy('title', 'ASC')->get();
} catch (\Throwable $e) {
$availableTags = [];
}
if (!$post_content) {
$post_content = '<!-- wp:paragraph --><p></p><!-- /wp:paragraph -->';
}
return [
'content' => $post_content,
'title' => $post_title,
'id' => $recordId,
'updatedAt' => $recordUpdatedAt,
'availableTags' => $availableTags,
];
}
/**
* Retrieve or create the fcrm-dummy post used as a simulated post for the editor.
*
* @param string $title
* @param string $content
* @return \WP_Post
*/
private function getOrCreateDummyPost($title, $content)
{
$firstPost = fluentCrmDb()->table('posts')
->where('post_type', 'fcrm-dummy')
->first();
if ($firstPost) {
$simulatedPost = get_post($firstPost->ID);
$simulatedPost->post_content = $content;
$simulatedPost->post_title = $title;
} else {
$newPostId = wp_insert_post(array(
'post_title' => $title,
'post_content' => $content,
'post_type' => 'fcrm-dummy',
'post_status' => 'draft',
));
$simulatedPost = get_post($newPostId);
}
return $simulatedPost;
}
/*
* -----------------------------------------------------------------------
* Native REST handlers for editor patterns (wp_block-compatible format).
* These bypass WPFluent's response pipeline so core-data gets raw responses.
* -----------------------------------------------------------------------
*/
public function handleEditorPatternsList()
{
$patterns = Meta::where('object_type', 'email_pattern')
->orderBy('id', 'desc')
->get();
$categoryMap = $this->getPatternCategoryMap();
$items = [];
foreach ($patterns as $pattern) {
$items[] = $this->formatMetaAsWpBlock($pattern, $categoryMap);
}
return new \WP_REST_Response($items, 200);
}
public function handleEditorPatternGet(\WP_REST_Request $request)
{
$id = (int) $request->get_param('id');
$pattern = Meta::where('object_type', 'email_pattern')->where('id', $id)->first();
if (!$pattern) {
return new \WP_Error('not_found', __('Pattern not found', 'fluent-crm'), ['status' => 404]);
}
$categoryMap = $this->getPatternCategoryMap();
return new \WP_REST_Response($this->formatMetaAsWpBlock($pattern, $categoryMap), 200);
}
public function handleEditorPatternCreate(\WP_REST_Request $request)
{
$params = $request->get_json_params();
if (empty($params)) {
$params = $request->get_body_params();
}
$title = Arr::get($params, 'title', '');
if (is_array($title)) {
$title = Arr::get($title, 'raw', '');
}
$title = sanitize_text_field($title);
$content = Arr::get($params, 'content', '');
if (is_array($content)) {
$content = Arr::get($content, 'raw', '');
}
$content = wp_kses_post($content);
if (!$title) {
$title = __('Untitled Pattern', 'fluent-crm');
}
$meta = Arr::get($params, 'meta', []);
$syncStatus = is_array($meta) ? sanitize_text_field(Arr::get($meta, 'wp_pattern_sync_status', '')) : '';
$categoryIds = (array) Arr::get($params, 'wp_pattern_category', []);
$categoryName = $this->resolvePatternCategoryName($categoryIds);
$slug = 'fluentcrm/' . sanitize_title($title . '-' . uniqid());
$pattern = Meta::create([
'object_type' => 'email_pattern',
'object_id' => get_current_user_id(),
'key' => $slug,
'value' => [
'title' => $title,
'content' => $content,
'category' => $categoryName,
'description' => '',
'sync_status' => $syncStatus,
],
]);
$categoryMap = $this->getPatternCategoryMap();
return new \WP_REST_Response($this->formatMetaAsWpBlock($pattern, $categoryMap), 201);
}
public function handleEditorPatternUpdate(\WP_REST_Request $request)
{
$id = (int) $request->get_param('id');
$pattern = Meta::where('object_type', 'email_pattern')->where('id', $id)->first();
if (!$pattern) {
return new \WP_Error('not_found', __('Pattern not found', 'fluent-crm'), ['status' => 404]);
}
$params = $request->get_json_params();
if (empty($params)) {
$params = $request->get_body_params();
}
$value = $pattern->value;
$title = Arr::get($params, 'title');
if ($title !== null) {
if (is_array($title)) {
$title = Arr::get($title, 'raw', '');
}
$value['title'] = sanitize_text_field($title);
}
$content = Arr::get($params, 'content');
if ($content !== null) {
if (is_array($content)) {
$content = Arr::get($content, 'raw', '');
}
$value['content'] = wp_kses_post($content);
}
$meta = Arr::get($params, 'meta', []);
if (is_array($meta) && isset($meta['wp_pattern_sync_status'])) {
$value['sync_status'] = sanitize_text_field($meta['wp_pattern_sync_status']);
}
$categoryIds = Arr::get($params, 'wp_pattern_category');
if ($categoryIds !== null) {
$value['category'] = $this->resolvePatternCategoryName((array) $categoryIds);
}
$pattern->value = $value;
$pattern->save();
$categoryMap = $this->getPatternCategoryMap();
return new \WP_REST_Response($this->formatMetaAsWpBlock($pattern, $categoryMap), 200);
}
public function handleEditorPatternDelete(\WP_REST_Request $request)
{
$id = (int) $request->get_param('id');
$pattern = Meta::where('object_type', 'email_pattern')->where('id', $id)->first();
if (!$pattern) {
return new \WP_Error('not_found', __('Pattern not found', 'fluent-crm'), ['status' => 404]);
}
$response = $this->formatMetaAsWpBlock($pattern, $this->getPatternCategoryMap());
$pattern->delete();
return new \WP_REST_Response($response, 200);
}
public function handleEditorPatternCategories()
{
$categories = Meta::where('object_type', 'email_pattern_category')
->orderBy('id', 'asc')
->get();
$items = [];
foreach ($categories as $cat) {
$items[] = [
'id' => (int) $cat->id,
'count' => 0,
'name' => Arr::get($cat->value, 'name', $cat->key),
'slug' => $cat->key,
'parent' => 0,
];
}
return new \WP_REST_Response($items, 200);
}
public function handleEditorPatternCategoryCreate(\WP_REST_Request $request)
{
$params = $request->get_json_params();
if (empty($params)) {
$params = $request->get_body_params();
}
$name = sanitize_text_field(Arr::get($params, 'name', ''));
if (!$name) {
return new \WP_Error('missing_name', __('Category name is required', 'fluent-crm'), ['status' => 400]);
}
$slug = sanitize_title($name);
$existing = Meta::where('object_type', 'email_pattern_category')->where('key', $slug)->first();
if ($existing) {
return new \WP_REST_Response([
'id' => (int) $existing->id,
'count' => 0,
'name' => Arr::get($existing->value, 'name', $existing->key),
'slug' => $existing->key,
'parent' => 0,
], 200);
}
$category = Meta::create([
'object_type' => 'email_pattern_category',
'object_id' => 0,
'key' => $slug,
'value' => ['name' => $name],
]);
return new \WP_REST_Response([
'id' => (int) $category->id,
'count' => 0,
'name' => $name,
'slug' => $slug,
'parent' => 0,
], 201);
}
private function formatMetaAsWpBlock($meta, $categoryMap = [])
{
$value = $meta->value;
$title = Arr::get($value, 'title', '');
$content = Arr::get($value, 'content', '');
$syncStatus = Arr::get($value, 'sync_status', 'unsynced');
$category = Arr::get($value, 'category', '');
$categoryIds = [];
if ($category) {
$catSlug = sanitize_title($category);
if (isset($categoryMap[$catSlug])) {
$categoryIds[] = (int) $categoryMap[$catSlug];
}
}
return [
'id' => (int) $meta->id,
'date' => $meta->created_at ?: gmdate('Y-m-d\TH:i:s'),
'date_gmt' => $meta->created_at ?: gmdate('Y-m-d\TH:i:s'),
'modified' => $meta->updated_at ?: gmdate('Y-m-d\TH:i:s'),
'modified_gmt' => $meta->updated_at ?: gmdate('Y-m-d\TH:i:s'),
'slug' => $meta->key,
'status' => 'publish',
'type' => 'wp_block',
'link' => '',
'title' => ['raw' => $title],
'content' => ['raw' => $content, 'protected' => false],
'meta' => new \stdClass(),
'wp_pattern_sync_status' => $syncStatus ?: '',
'wp_pattern_category' => $categoryIds,
];
}
private function getPatternCategoryMap()
{
$categories = Meta::where('object_type', 'email_pattern_category')->get();
$map = [];
foreach ($categories as $cat) {
$map[$cat->key] = $cat->id;
}
return $map;
}
private function resolvePatternCategoryName($categoryIds)
{
if (empty($categoryIds)) {
return '';
}
$categoryIds = array_map('intval', $categoryIds);
$category = Meta::where('object_type', 'email_pattern_category')
->whereIn('id', $categoryIds)
->first();
return $category ? Arr::get($category->value, 'name', $category->key) : '';
}
/**
* Build the editorBootData array that is injected into the iframe as window.fcrmEditorBoot.
*
* @param string $context
* @param array $data
* @param array $entity Return value from resolveEntityContent().
*/
private function prepareEditorBootData($context, $data, $entity)
{
$canSave = (!empty($context) && $context !== '0');
// If block_type null/empty and bid == 0 we won't create anything (flag can_save false)
if (empty($context) && (int)Arr::get($data, 'bid') === 0) {
$canSave = false;
}
// New templates (id=0) should not autosave until they are explicitly created.
if ($context === 'template' && (int)Arr::get($data, 'bid') === 0) {
$canSave = false;
}
$hideBackBtn = self::parseBoolParam(Arr::get($data, 'hideBackBtn', Arr::get($data, 'hide_back_btn', false)));
$hideNextBtn = self::parseBoolParam(Arr::get($data, 'hideNextBtn', Arr::get($data, 'hide_next_btn', false)));
$hideSaveBtn = self::parseBoolParam(Arr::get($data, 'hideSaveBtn', Arr::get($data, 'hide_save_btn', false)));
$disableAutosave = self::parseBoolParam(Arr::get($data, 'disableAutosave', Arr::get($data, 'disable_autosave', false)));
if ($disableAutosave) {
$canSave = false;
}
// Load user-saved email patterns from the database
$savedPatternData = \FluentCrm\App\Http\Controllers\EmailPatternController::getEditorPatterns();
$savedPatterns = $savedPatternData['patterns'];
$savedPatternCategories = $savedPatternData['categories'];
$customPatterns = apply_filters('fluent_crm/block_editor_custom_patterns', $savedPatterns, $context, $data);
if (!is_array($customPatterns)) {
$customPatterns = [];
}
$customPatternCategories = apply_filters('fluent_crm/block_editor_custom_pattern_categories', $savedPatternCategories, $context, $data);
if (!is_array($customPatternCategories)) {
$customPatternCategories = [];
}
$designTemplate = isset($data['design_template']) ? sanitize_text_field($data['design_template']) : '';
$features = $this->getEditorFeatures($context);
$this->editorBootData = [
'entity' => [
'id' => $entity['id'],
'block_type' => $context,
'title' => $entity['title'],
'content' => $entity['content'],
'updated_at' => $entity['updatedAt'],
],
'can_save' => $canSave,
'autosave' => [
'endpoint' => rest_url('fluent-crm/v1/editor-autosave'),
'nonce' => wp_create_nonce('wp_rest')
],
'fcrm_ui' => isset($data['fcrm_ui']) ? sanitize_text_field($data['fcrm_ui']) : '',
'compose_nav' => [
'hideBackBtn' => $hideBackBtn,
'hideNextBtn' => $hideNextBtn,
'hideSaveBtn' => $hideSaveBtn
],
'email_template_designs' => \FluentCrm\App\Services\Helper::getEmailDesignTemplates(),
'current_design_template' => $designTemplate,
'available_tags' => $entity['availableTags'],
'global_email_footer' => \FluentCrm\App\Services\Helper::getEmailFooterContent(),
'more_menu' => [
'help_url' => apply_filters('fluent_crm/block_editor_help_url', 'https://fluentcrm.com/docs/'),
'patterns_url' => apply_filters('fluent_crm/block_editor_patterns_url', admin_url('edit.php?post_type=wp_block')),
'hide_welcome_guide' => (bool)apply_filters('fluent_crm/block_editor_hide_welcome_guide', true),
'hide_manage_patterns' => (bool)apply_filters('fluent_crm/block_editor_hide_manage_patterns', true),
'replace_native_help' => (bool)apply_filters('fluent_crm/block_editor_replace_native_help', true),
],
'patterns' => [
'unregister_all' => (bool)apply_filters('fluent_crm/block_editor_unregister_all_patterns', true, $context, $data),
'custom' => array_values($customPatterns),
'categories' => array_values($customPatternCategories),
],
'ai_writing' => $this->getAiWritingConfig(),
'features' => $features,
];
}
/**
* Get editor feature flags based on content type.
* This is the single source of truth for which UI elements show per context.
*
* @param string $context block_type: campaign, template, email_pattern, recurring_campaign, sequence_mail, email_body_in_funnel
* @return array
*/
private function getEditorFeatures($context)
{
// Full email editing features (default for campaigns, templates, etc.)
$emailDefaults = [
'email_style_settings' => true,
'email_footer' => true,
'email_preview' => true,
'save_as_template' => true,
'browse_templates' => true,
'smartcodes' => true,
'design_switcher' => true,
'save_draft' => true,
];
// Minimal features for non-email content (patterns, snippets, etc.)
$minimalDefaults = [
'email_style_settings' => false,
'email_footer' => false,
'email_preview' => true,
'save_as_template' => false,
'browse_templates' => false,
'smartcodes' => false,
'design_switcher' => false,
'switch_editor' => false,
'create_pattern' => false,
'save_draft' => true,
'sidebar_panel_title' => __('Pattern Info', 'fluent-crm'),
'sidebar_content' => '<h3>' . __('Editing Pattern', 'fluent-crm') . '</h3>'
. '<p>' . __('Patterns are reusable block layouts that can be inserted into any email. Changes here will apply to all future emails that use this pattern.', 'fluent-crm') . '</p>'
. '<p>' . __('Synced patterns stay linked — editing here updates everywhere. Unsynced patterns are copied on insert.', 'fluent-crm') . '</p>',
];
$presets = [
'campaign' => $emailDefaults,
'template' => $emailDefaults,
'recurring_campaign' => $emailDefaults,
'recurring_mail' => $emailDefaults,
'sequence_mail' => $emailDefaults,
'email_body_in_funnel' => $emailDefaults,
'email_pattern' => $minimalDefaults,
];
$features = isset($presets[$context]) ? $presets[$context] : $emailDefaults;
return apply_filters('fluent_crm/block_editor_features', $features, $context);
}
private function getAiWritingConfig()
{
$credentials = get_option('_fluent_ai_creds', []);
$preferences = fluentcrm_get_option('_ai_writing_settings', []);
if (!is_array($credentials)) {
$credentials = [];
}
$provider = isset($credentials['provider']) ? $credentials['provider'] : '';
$hasApiKey = !empty($credentials['api_key']) || $provider === 'wordpress';
return [
'enabled' => (
is_array($credentials)
&& is_array($preferences)
&& isset($preferences['is_enabled'])
&& $preferences['is_enabled'] === 'yes'
&& $hasApiKey
&& !empty($credentials['provider'])
&& !empty($credentials['model'])
),
];
}
/**
* Register wp_enqueue_scripts callbacks, post-locking filters, and other editor hooks.
*
* @param \WP_Post $post
*/
private function setupEditorHooks($post)
{
$enqueueHook = 'wp_enqueue_scripts';
add_action($enqueueHook, function () use ($post) {
wp_enqueue_script('postbox', admin_url('js/postbox.min.js'), array('jquery-ui-sortable'), false, 1);
wp_enqueue_editor();
wp_enqueue_script('wp-tinymce');
wp_enqueue_style('dashicons');
wp_enqueue_style('media');
wp_enqueue_style('admin-menu');
wp_enqueue_style('admin-bar');
wp_enqueue_style('l10n');
wp_add_inline_script(
'wp-api-fetch',
\sprintf(
'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
wp_json_encode(
array(
'/wp/v2/fcrm-dummy/' . $post->ID . '?context=edit' => array(
'body' => array(
'id' => $post->ID,
'title' => array('raw' => $post->post_title),
'content' => array(
'block_format' => 1,
'raw' => $post->post_content,
),
'excerpt' => array('raw' => ''),
'date' => '',
'date_gmt' => '',
'modified' => '',
'modified_gmt' => '',
'link' => home_url('/'),
'guid' => array(),
'parent' => 0,
'menu_order' => 0,
'author' => 0,
'featured_media' => 0,
'comment_status' => 'closed',
'ping_status' => 'closed',
'template' => '',
'meta' => array(),
'_links' => array(),
'type' => 'fcrm-dummy',
'status' => 'pending', // pending is the best state to remove draft saving possibilities.
'slug' => '',
'generated_slug' => '',
'permalink_template' => home_url('/'),
),
),
)
)
),
'after'
);
}, 11);
add_action($enqueueHook, function ($hook) use ($post) {
// Gutenberg requires the post-locking functions defined within:
// See `show_post_locked_dialog` and `get_post_metadata` filters below.
include_once ABSPATH . 'wp-admin/includes/post.php';
$this->enqueueEditorAssets($hook, $post);
});
// Disable post locking dialogue.
add_filter('show_post_locked_dialog', '__return_false');
// Everyone can richedit! This avoids a case where a page can be cached where a user can't richedit.
$GLOBALS['wp_rich_edit'] = true;
add_filter('user_can_richedit', '__return_true', 1000);
// This prevents other logged-in users taking a lock of the post on the front-end.
add_filter('get_post_metadata', function ($value, $post_id, $meta_key) {
if ($meta_key !== '_edit_lock') {
return $value;
}
return time() . ':' . get_current_user_id();
}, 10, 3);
// Disable Jetpack Blocks for now.
add_filter('jetpack_gutenberg', '__return_false');
}
/*
|--------------------------------------------------------------------------
| Assets: enqueueEditorAssets(), enqueueEditorScripts(),
| enqueueEditorStyles(), enqueueCustomEditorAssets()
|--------------------------------------------------------------------------
*/
private function enqueueEditorAssets($hook, $post)
{
$initial_edits = array(
'title' => $post->post_title,
'content' => $post->post_content,
'excerpt' => $post->post_excerpt,
);
$editor_settings = $this->getEditorSettings($post);
$init_script = <<<JS
( function() {
window._wpLoadBlockEditor = new Promise( function( resolve, reject ) {
wp.domReady( function() {
try {
resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) );
} catch (e) {
console.error('[FCRM Editor] initializeEditor failed:', e);
reject(e);
}
} );
} );
} )();
JS;
$script = sprintf(
$init_script,
$post->post_type,
$post->ID,
wp_json_encode($editor_settings),
wp_json_encode($initial_edits)
);
wp_add_inline_script('wp-edit-post', $script);
$this->enqueueEditorScripts($post);
$this->enqueueEditorStyles();
$this->enqueueCustomEditorAssets();
}
/**
* Enqueue media, tinymce, and postbox init scripts.
*
* @param \WP_Post $post
*/
private function enqueueEditorScripts($post)
{
wp_enqueue_media(
array(
'post' => null
)
);
add_filter('user_can_richedit', '__return_true');
wp_tinymce_inline_scripts();
wp_enqueue_editor();
wp_enqueue_script('wp-tinymce');
}
/**
* Enqueue wp-edit-post, block library styles, and editor format library assets.
*/
private function enqueueEditorStyles()
{
wp_enqueue_style('wp-edit-post');
/*
These styles are usually registered by Gutenberg and register properly when the user is signed in.
However, if the use is not registered they are not added. For now, include them, but this isn't a good long term strategy
See: https://github.com/WordPress/wporg-gutenberg/issues/26
*/
wp_enqueue_style('wp-block-library');
wp_enqueue_style('wp-block-image');
wp_enqueue_style('wp-block-group');
wp_enqueue_style('wp-block-heading');
wp_enqueue_style('wp-block-button');
wp_enqueue_style('wp-block-paragraph');
wp_enqueue_style('wp-block-separator');
wp_enqueue_style('wp-block-columns');
wp_enqueue_style('wp-block-row');
wp_enqueue_style('wp-block-cover');
wp_enqueue_style('wp-block-spacer');
wp_register_style('fluent_crm_editor_styles', FLUENTCRM_PLUGIN_URL . 'assets/guten-editor/style.css', false, FLUENTCRM_PLUGIN_VERSION, 'all');
add_action('fluent_enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets');
/**
* Fires after block assets have been enqueued for the editing interface.
*
* Call `add_action` on any hook before 'admin_enqueue_scripts'.
*
* In the function call you supply, simply use `wp_enqueue_script` and
* `wp_enqueue_style` to add your functionality to the Gutenberg editor.
*
* @since 0.4.0
*/
do_action('fluent_enqueue_block_editor_assets');
}
/**
* Enqueue fcrm_editor_custom JS, inline block editor config, smartcodes, and boot data.
*/
private function enqueueCustomEditorAssets()
{
$editor_asset_file = FLUENTCRM_PLUGIN_PATH . 'assets/guten-editor/index.asset.php';
$editor_asset = file_exists($editor_asset_file) ? require($editor_asset_file) : [];
$editor_deps = !empty($editor_asset['dependencies']) ? $editor_asset['dependencies'] : ['react', 'wp-edit-post', 'wp-plugins'];
$editor_version = !empty($editor_asset['version']) ? $editor_asset['version'] : FLUENTCRM_PLUGIN_VERSION;
if (!in_array('wp-edit-post', $editor_deps)) {
$editor_deps[] = 'wp-edit-post';
}
if (!in_array('wp-reusable-blocks', $editor_deps)) {
$editor_deps[] = 'wp-reusable-blocks';
}
wp_enqueue_script('fcrm_editor_custom', FLUENTCRM_PLUGIN_URL . 'assets/guten-editor/index.js', $editor_deps, $editor_version, true);
$availableDesigns = Helper::getEmailDesignTemplates();
$availableDesigns = array_filter($availableDesigns, function ($design) {
return !empty($design['use_gutenberg']);
});
$designPresetPayloads = [];
foreach ($availableDesigns as $designKey => $design) {
$designPresetPayloads[$designKey] = $design;
$designPresetPayloads[$designKey]['config'] = [
'design_template' => Arr::get($design, 'id', $designKey)
];
}
$blockEditorConfig = [
'modules' => [
'hasWooCommerce' => defined('WC_PLUGIN_FILE'),
'hasFluentCampaign' => defined('FLUENTCAMPAIGN'),
'hasFluentCart' => defined('FLUENTCART_VERSION')
],
'endpoints' => [
'products' => apply_filters('fluent_crm/block_editor_products_endpoint', 'fluent-crm/v2/campaigns-pro/products'),
'cartProducts' => apply_filters('fluent_crm/block_editor_cart_products_endpoint', 'fluent-crm/v2/editor/cart-products'),
'tags' => apply_filters('fluent_crm/block_editor_tags_endpoint', 'fluent-crm/v2/reports/options?fields=tags')
],
'fontSizes' => BlockEditorHelper::getDefaultPreset('font-size'),
'spacingPresets' => BlockEditorHelper::getDefaultPreset('spacing'),
'defaultDesignConfig' => Helper::getTemplateConfig(false),
// Keep the shared design metadata intact for legacy consumers while ensuring
// Gutenberg preset clicks only change the selected design template.
'designTemplatePresets' => $designPresetPayloads,
'default_design_template' => Helper::getDefaultEmailTemplate()
];
wp_add_inline_script(
'fcrm_editor_custom',
'window.fcrmBlockEditorConfig = ' . wp_json_encode($blockEditorConfig) . ';',
'before'
);
// Inject smartcodes into the iframe window
$globalSmartCodes = Helper::getGlobalSmartCodes();
$extendedSmartCodes = Helper::getExtendedSmartCodes();
$transStrings = TransStringsGuten::getStrings();
wp_add_inline_script(
'fcrm_editor_custom',
'window.fcAdmin = window.fcAdmin || {};' .
'window.fcAdmin.globalSmartCodes = ' . wp_json_encode($globalSmartCodes) . ';' .
'window.fcAdmin.extendedSmartCodes = ' . wp_json_encode($extendedSmartCodes) . ';' .
'window.fcAdmin.trans = Object.assign({}, window.fcAdmin.trans || {}, ' . wp_json_encode($transStrings) . ');',
'before'
);
if (!empty($this->editorBootData)) {
wp_add_inline_script(
'fcrm_editor_custom',
'window.fcrmEditorBoot = ' . wp_json_encode($this->editorBootData) . ';',
'before'
);
}
}
/*
|--------------------------------------------------------------------------
| Settings: getEditorSettings(), getAllowedBlocks(), getEditorStyleSheets(),
| getResolvedAssets(), getDefaultEditorStyles()
|--------------------------------------------------------------------------
*/
private function getEditorSettings($post)
{
// Run pattern cleanup here as well (late) so theme/plugin init hooks cannot re-add defaults.
$context = Arr::get($this->editorBootData, 'entity.block_type', '');
$requestData = is_array($_REQUEST) ? $_REQUEST : [];
$this->unregisterDefaultBlockPatterns($context, $requestData);
// Media settings.
$max_upload_size = wp_max_upload_size();
if (!$max_upload_size) {
$max_upload_size = 0;
}
$lock_details = array(
'isLocked' => false,
'user' => '',
);
$allowedBlocks = $this->getAllowedBlocks();
$blockStyleDefaults = BlockEditorHelper::getStyleDefauls();
$editor_settings = array(
'maxUploadFileSize' => $max_upload_size,
'allowedMimeTypes' => get_allowed_mime_types(),
'postLock' => $lock_details,
'postLockUtils' => array(
'nonce' => wp_create_nonce('lock-post_' . $post->ID),
'unlockNonce' => wp_create_nonce('update-post_' . $post->ID),
'ajaxUrl' => admin_url('admin-ajax.php'),
),
'__experimentalFeatures' => array(
'appearanceTools' => true,
'useRootPaddingAwareAlignments' => false,
'border' => [
'color' => 1,
'radius' => 1,
'style' => 1,
'width' => 1,
],
'color' => [
'background' => true,
'button' => 1,
'caption' => 1,
'customDuotone' => 0,
'defaultDuotone' => 0,
'defaultGradients' => 0,
'defaultPalette' => true,
'duotone' => [],
'gradients' => [],
'heading' => 1,
'link' => 1,
'palette' => [
'default' => $blockStyleDefaults['color'],
],
'text' => true,
],
'dimensions' => [
'units' => ['px'],
'defaultAspectRatios' => false,
'aspectRatio' => false,
'minHeight' => 1,
],
'shadow' => [
'defaultPresets' => true,
'presets' => [
'default' => [
[
'name' => 'Natural',
'slug' => 'natural',
'shadow' => '6px 6px 9px rgba(0, 0, 0, 0.2)',
],
[
'name' => 'Deep',
'slug' => 'deep',
'shadow' => '12px 12px 50px rgba(0, 0, 0, 0.4)',
],
[
'name' => 'Sharp',
'slug' => 'sharp',
'shadow' => '6px 6px 0px rgba(0, 0, 0, 0.2)',
],
[
'name' => 'Outlined',
'slug' => 'outlined',
'shadow' => '6px 6px 0px -3px rgba(255, 255, 255, 1), 6px 6px rgba(0, 0, 0, 1)',
],
[
'name' => 'Crisp',
'slug' => 'crisp',
'shadow' => '6px 6px 0px rgba(0, 0, 0, 1)',
],
],
],
],
'spacing' => [
'blockGap' => 1,
'margin' => 1,
'padding' => 1,
'units' => ['px'],
'defaultSpacingSizes' => true,
'spacingScale' => [
'default' => [
'operator' => '*',
'increment' => 1.5,
'steps' => 7,
'mediumStep' => 24,
'unit' => 'px',
],
],
'spacingSizes' => [
'default' => $blockStyleDefaults['spacing'],
]
],
'typography' => [
'defaultFontSizes' => true,
'dropCap' => false,
'fontFamilies' => [
'default' => $blockStyleDefaults['font-family']
],
'fontSizes' => [
'default' => $blockStyleDefaults['font-size']
],
'fontStyle' => true,
'fontWeight' => true,
'letterSpacing' => true,
'textAlign' => true,
'textDecoration' => true,
'textTransform' => true,
'writingMode' => false,
'units' => ['px'],
'fluid' => false
],
'blocks' => [
'core/button' => [
'border' => [
'radius' => true,
]
],
'core/buttons' => [
'border' => [
'radius' => false,
],
'spacing' => [
'blockGap' => false,
],
'layout' => false,
'contentRole' => false
],
'core/image' => [
'lightbox' => [
'allowEditing' => true,
]
],
'core/pullquote' => [
'border' => [
'color' => true,
'radius' => true,
'style' => true,
'width' => true,
]
],
'core/paragraph' => [
'spacing' => [
'margin' => 1,
'padding' => 1,
]
],
'core/columns' => [
// disable block gap for columns block, but keep for inner column blocks
'spacing' => [
'blockGap' => array(
'__experimentalDefault' => '20px',
'sides' => array(
'horizontal'
)
),
'defaultSpacingSizes' => false,
],
'border' => [
'color' => true,
'radius' => false,
'style' => true,
'width' => true,
],
'shadow' => false,
],
'core/group' => [
'__experimentalSettings' => false,
'shadow' => false,
'dimensions' => [
'minHeight' => false
],
'spacing' => [
'blockGap' => false,
'margin' => true,
'padding' => true,
],
'position' => [
'sticky' => false,
],
'layout' => [
'allowSizingOnChildren' => false,
'contentSize' => false
]
],
'core/row' => [
'__experimentalSettings' => false,
'shadow' => false,
'dimensions' => [
'minHeight' => false
],
'spacing' => [
'blockGap' => true,
'margin' => true,
'padding' => true,
],
'position' => [
'sticky' => false,
]
]
],
'layout' => [
'contentSize' => 'var(--theme-block-max-width)',
'wideSize' => 'var(--theme-block-wide-max-width)',
],
'background' => [
'backgroundImage' => 1,
'backgroundSize' => 1,
],
'position' => [
'sticky' => 0,
]
),
'__experimentalDiscussionSettings' => [
'avatarURL' => 'https://secure.gravatar.com/avatar/?s=96&f=y&r=g',
'commentOrder' => 'asc',
'commentsPerPage' => '50',
'defaultCommentsPage' => 'newest',
'defaultCommentStatus' => 'open',
'pageComments' => '',
'threadComments' => '1',
'threadCommentsDepth' => '5'
],
'__unstableGalleryWithImageBlocks' => false,
'__unstableIsBlockBasedTheme' => false,
'enableCustomUnits' => false,
'fontSizes' => [
[
'name' => 'Small',
'size' => 'var(--fcom-font-size-small)',
'slug' => 'small'
],
[
'name' => 'Medium',
'size' => 'var(--fcom-font-size-medium)',
'slug' => 'medium'
],
[
'name' => 'Large',
'size' => 'var(--fcom-font-size-large)',
'slug' => 'large'
],
[
'name' => 'Larger',
'size' => 'var(--fcom-font-size-larger)',
'slug' => 'larger'
],
[
'name' => 'XX-Large',
'size' => 'var(--fcom-font-size-xxlarge)',
'slug' => 'xxlarge'
]
],
'fullscreenMode' => 1,
'enableCustomSpacing' => 1,
'enableCustomLineHeight' => 1,
'enableCustomFields' => false,
'disablePostFormats' => true,
'disableLayoutStyles' => true,
'disableCustomSpacingSizes' => false,
'disableCustomGradients' => 1,
'alignWide' => true,
'disableCustomFontSizes' => false,
'disableCustomColors' => false,
'canUpdateBlockBindings' => false,
'bodyPlaceholder' => __('Start writing your email...', 'fluent-crm'),
'allowedBlockTypes' => $allowedBlocks,
'gradients' => [],
'imageDefaultSize' => 'large',
'imageEditing' => true,
'isRTL' => is_rtl(),
'autosaveInterval' => 999,
'localAutosaveInterval' => 999,
'richEditingEnabled' => true,
'spacingSizes' => [
[
'name' => '2X-Small',
'size' => '0.44rem',
'slug' => '20'
],
[
'name' => 'X-Small',
'size' => '0.67rem',
'slug' => '30'
],
[
'name' => 'Small',
'size' => '1rem',
'slug' => '40'
],
[
'name' => 'Medium',
'size' => '1.5rem',
'slug' => '50'
],
[
'name' => 'Large',
'size' => '2.25rem',
'slug' => '60'
],
[
'name' => 'X-Large',
'size' => '3.38rem',
'slug' => '70'
],
[
'name' => '2X-Large',
'size' => '5.06rem',
'slug' => '80'
]
],
'titlePlaceholder' => __('Add Lesson title', 'fluent-crm')
);
$editor_settings['styles'] = $this->getEditorStyleSheets();
$editor_settings['__unstableResolvedAssets'] = $this->getResolvedAssets();
$editor_settings['defaultEditorStyles'] = $this->getDefaultEditorStyles();
$editor_settings['imageSizes'] = $this->getAvailableImageSizes();
$editor_settings['__experimentalBlockPatterns'] = array_values((array)apply_filters(
'fluent_crm/block_editor_custom_patterns',
[],
$context,
$requestData
));
$editor_settings['__experimentalBlockPatternCategories'] = array_values((array)apply_filters(
'fluent_crm/block_editor_custom_pattern_categories',
[],
$context,
$requestData
));
$editor_settings = apply_filters('fluent_crm/block_editor_settings', $editor_settings);
return $editor_settings;
}
/**
* Return the allowed blocks array for the editor.
*
* @return array
*/
private function getAllowedBlocks()
{
$allowedBlocks = [
'core/block',
'core/buttons',
'core/button',
'core/code',
'core/columns',
'core/column',
'core/footnotes',
'core/freeform',
'core/group',
'core/row',
'core/heading',
'core/html',
'core/image',
'core/list',
'core/list-item',
'core/missing',
'core/paragraph',
'core/preformatted',
'core/pullquote',
'core/quote',
'core/rss',
'core/separator',
'core/spacer',
'core/table',
'core/verse',
'core/freeform',
'fluentcrm/conditional-group'
];
if (defined('WC_PLUGIN_FILE')) {
$allowedBlocks[] = 'fluentcrm/woo-product';
$allowedBlocks[] = 'fluent-crm/woo-product';
}
if (defined('FLUENTCAMPAIGN')) {
$allowedBlocks[] = 'fluent-crm/latest-posts';
if (defined('WC_PLUGIN_FILE')) {
$allowedBlocks[] = 'fluent-crm/woo-products';
}
}
if (defined('FLUENTCART_VERSION')) {
$allowedBlocks[] = 'fluent-crm/cart-products';
$allowedBlocks[] = 'fluent-crm/cart-product';
}
$aiConfig = $this->getAiWritingConfig();
if (!empty($aiConfig['enabled'])) {
$allowedBlocks[] = 'fluent-crm/ai-writer';
}
$allowedBlocks = apply_filters('fluent_crm/new_editor_allowed_block_types', $allowedBlocks);
$allowedBlocks = array_values(array_unique($allowedBlocks));
return $allowedBlocks;
}
/**
* Return the `styles` array for the editor settings (the massive CSS).
*
* @return array
*/
private function getEditorStyleSheets()
{
$defaultPresets = BlockEditorHelper::getStyleDefaultPresets();
$dynamicCss = BlockEditorHelper::getDynamicCssForEditor();
return [
[
'__unstableType' => 'presets',
'css' => ':root{' . $defaultPresets . '--wp--preset--aspect-ratio--square: 1;--wp--preset--aspect-ratio--4-3: 4/3;--wp--preset--aspect-ratio--3-4: 3/4;--wp--preset--aspect-ratio--3-2: 3/2;--wp--preset--aspect-ratio--2-3: 2/3;--wp--preset--aspect-ratio--16-9: 16/9;--wp--preset--aspect-ratio--9-16: 9/16;--wp--preset--color--theme-palette-color-1: var(--theme-palette-color-1);--wp--preset--color--theme-palette-color-2: var(--theme-palette-color-2);--wp--preset--color--theme-palette-color-3: var(--theme-palette-color-3);--wp--preset--color--theme-palette-color-4: var(--theme-palette-color-4);--wp--preset--color--theme-palette-color-5: var(--theme-palette-color-5);--wp--preset--color--theme-palette-color-6: var(--theme-palette-color-6);--wp--preset--color--theme-palette-color-7: var(--theme-palette-color-7);--wp--preset--color--theme-palette-color-8: var(--theme-palette-color-8);--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--gradient--juicy-peach: linear-gradient(to right, #ffecd2 0%, #fcb69f 100%);--wp--preset--gradient--young-passion: linear-gradient(to right, #ff8177 0%, #ff867a 0%, #ff8c7f 21%, #f99185 52%, #cf556c 78%, #b12a5b 100%);--wp--preset--gradient--true-sunset: linear-gradient(to right, #fa709a 0%, #fee140 100%);--wp--preset--gradient--morpheus-den: linear-gradient(to top, #30cfd0 0%, #330867 100%);--wp--preset--gradient--plum-plate: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--wp--preset--gradient--aqua-splash: linear-gradient(15deg, #13547a 0%, #80d0c7 100%);--wp--preset--gradient--love-kiss: linear-gradient(to top, #ff0844 0%, #ffb199 100%);--wp--preset--gradient--new-retrowave: linear-gradient(to top, #3b41c5 0%, #a981bb 49%, #ffc8a9 100%);--wp--preset--gradient--plum-bath: linear-gradient(to top, #cc208e 0%, #6713d2 100%);--wp--preset--gradient--high-flight: linear-gradient(to right, #0acffe 0%, #495aff 100%);--wp--preset--gradient--teen-party: linear-gradient(-225deg, #FF057C 0%, #8D0B93 50%, #321575 100%);--wp--preset--gradient--fabled-sunset: linear-gradient(-225deg, #231557 0%, #44107A 29%, #FF1361 67%, #FFF800 100%);--wp--preset--gradient--arielle-smile: radial-gradient(circle 248px at center, #16d9e3 0%, #30c7ec 47%, #46aef7 100%);--wp--preset--gradient--itmeo-branding: linear-gradient(180deg, #2af598 0%, #009efd 100%);--wp--preset--gradient--deep-blue: linear-gradient(to right, #6a11cb 0%, #2575fc 100%);--wp--preset--gradient--strong-bliss: linear-gradient(to right, #f78ca0 0%, #f9748f 19%, #fd868c 60%, #fe9a8b 100%);--wp--preset--gradient--sweet-period: linear-gradient(to top, #3f51b1 0%, #5a55ae 13%, #7b5fac 25%, #8f6aae 38%, #a86aa4 50%, #cc6b8e 62%, #f18271 75%, #f3a469 87%, #f7c978 100%);--wp--preset--gradient--purple-division: linear-gradient(to top, #7028e4 0%, #e5b2ca 100%);--wp--preset--gradient--cold-evening: linear-gradient(to top, #0c3483 0%, #a2b6df 100%, #6b8cce 100%, #a2b6df 100%);--wp--preset--gradient--mountain-rock: linear-gradient(to right, #868f96 0%, #596164 100%);--wp--preset--gradient--desert-hump: linear-gradient(to top, #c79081 0%, #dfa579 100%);--wp--preset--gradient--ethernal-constance: linear-gradient(to top, #09203f 0%, #537895 100%);--wp--preset--gradient--happy-memories: linear-gradient(-60deg, #ff5858 0%, #f09819 100%);--wp--preset--gradient--grown-early: linear-gradient(to top, #0ba360 0%, #3cba92 100%);--wp--preset--gradient--morning-salad: linear-gradient(-225deg, #B7F8DB 0%, #50A7C2 100%);--wp--preset--gradient--night-call: linear-gradient(-225deg, #AC32E4 0%, #7918F2 48%, #4801FF 100%);--wp--preset--gradient--mind-crawl: linear-gradient(-225deg, #473B7B 0%, #3584A7 51%, #30D2BE 100%);--wp--preset--gradient--angel-care: linear-gradient(-225deg, #FFE29F 0%, #FFA99F 48%, #FF719A 100%);--wp--preset--gradient--juicy-cake: linear-gradient(to top, #e14fad 0%, #f9d423 100%);--wp--preset--gradient--rich-metal: linear-gradient(to right, #d7d2cc 0%, #304352 100%);--wp--preset--gradient--mole-hall: linear-gradient(-20deg, #616161 0%, #9bc5c3 100%);--wp--preset--gradient--cloudy-knoxville: linear-gradient(120deg, #fdfbfb 0%, #ebedee 100%);--wp--preset--gradient--soft-grass: linear-gradient(to top, #c1dfc4 0%, #deecdd 100%);--wp--preset--gradient--saint-petersburg: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);--wp--preset--gradient--everlasting-sky: linear-gradient(135deg, #fdfcfb 0%, #e2d1c3 100%);--wp--preset--gradient--kind-steel: linear-gradient(-20deg, #e9defa 0%, #fbfcdb 100%);--wp--preset--gradient--over-sun: linear-gradient(60deg, #abecd6 0%, #fbed96 100%);--wp--preset--gradient--premium-white: linear-gradient(to top, #d5d4d0 0%, #d5d4d0 1%, #eeeeec 31%, #efeeec 75%, #e9e9e7 100%);--wp--preset--gradient--clean-mirror: linear-gradient(45deg, #93a5cf 0%, #e4efe9 100%);--wp--preset--gradient--wild-apple: linear-gradient(to top, #d299c2 0%, #fef9d7 100%);--wp--preset--gradient--snow-again: linear-gradient(to top, #e6e9f0 0%, #eef1f5 100%);--wp--preset--gradient--confident-cloud: linear-gradient(to top, #dad4ec 0%, #dad4ec 1%, #f3e7e9 100%);--wp--preset--gradient--glass-water: linear-gradient(to top, #dfe9f3 0%, white 100%);--wp--preset--gradient--perfect-white: linear-gradient(-225deg, #E3FDF5 0%, #FFE6FA 100%);--wp--preset--font-size--small: var(--fcom-font-size-small);--wp--preset--font-size--medium: var(--fcom-font-size-medium);--wp--preset--font-size--large: var(--fcom-font-size-large);--wp--preset--font-size--x-large: 42px;--wp--preset--font-size--larger: var(--fcom-font-size-larger);--wp--preset--font-size--xxlarge: var(--fcom-font-size-xxlarge);--wp--preset--shadow--natural: 6px 6px 9px rgba(0, 0, 0, 0.2);--wp--preset--shadow--deep: 12px 12px 50px rgba(0, 0, 0, 0.4);--wp--preset--shadow--sharp: 6px 6px 0px rgba(0, 0, 0, 0.2);--wp--preset--shadow--outlined: 6px 6px 0px -3px rgba(255, 255, 255, 1), 6px 6px rgba(0, 0, 0, 1);--wp--preset--shadow--crisp: 6px 6px 0px rgba(0, 0, 0, 1);}',
'isGlobalStyles' => true
],
[
'__unstableType' => 'presets',
'css' => '.has-theme-palette-color-1-color{color: var(--wp--preset--color--theme-palette-color-1) !important;}.has-theme-palette-color-2-color{color: var(--wp--preset--color--theme-palette-color-2) !important;}.has-theme-palette-color-3-color{color: var(--wp--preset--color--theme-palette-color-3) !important;}.has-theme-palette-color-4-color{color: var(--wp--preset--color--theme-palette-color-4) !important;}.has-theme-palette-color-5-color{color: var(--wp--preset--color--theme-palette-color-5) !important;}.has-theme-palette-color-6-color{color: var(--wp--preset--color--theme-palette-color-6) !important;}.has-theme-palette-color-7-color{color: var(--wp--preset--color--theme-palette-color-7) !important;}.has-theme-palette-color-8-color{color: var(--wp--preset--color--theme-palette-color-8) !important;}.has-theme-palette-color-1-background-color{background-color: var(--wp--preset--color--theme-palette-color-1) !important;}.has-theme-palette-color-2-background-color{background-color: var(--wp--preset--color--theme-palette-color-2) !important;}.has-theme-palette-color-3-background-color{background-color: var(--wp--preset--color--theme-palette-color-3) !important;}.has-theme-palette-color-4-background-color{background-color: var(--wp--preset--color--theme-palette-color-4) !important;}.has-theme-palette-color-5-background-color{background-color: var(--wp--preset--color--theme-palette-color-5) !important;}.has-theme-palette-color-6-background-color{background-color: var(--wp--preset--color--theme-palette-color-6) !important;}.has-theme-palette-color-7-background-color{background-color: var(--wp--preset--color--theme-palette-color-7) !important;}.has-theme-palette-color-8-background-color{background-color: var(--wp--preset--color--theme-palette-color-8) !important;}.has-theme-palette-color-1-border-color{border-color: var(--wp--preset--color--theme-palette-color-1) !important;}.has-theme-palette-color-2-border-color{border-color: var(--wp--preset--color--theme-palette-color-2) !important;}.has-theme-palette-color-3-border-color{border-color: var(--wp--preset--color--theme-palette-color-3) !important;}.has-theme-palette-color-4-border-color{border-color: var(--wp--preset--color--theme-palette-color-4) !important;}.has-theme-palette-color-5-border-color{border-color: var(--wp--preset--color--theme-palette-color-5) !important;}.has-theme-palette-color-6-border-color{border-color: var(--wp--preset--color--theme-palette-color-6) !important;}.has-theme-palette-color-7-border-color{border-color: var(--wp--preset--color--theme-palette-color-7) !important;}.has-theme-palette-color-8-border-color{border-color: var(--wp--preset--color--theme-palette-color-8) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-juicy-peach-gradient-background{background: var(--wp--preset--gradient--juicy-peach) !important;}.has-young-passion-gradient-background{background: var(--wp--preset--gradient--young-passion) !important;}.has-true-sunset-gradient-background{background: var(--wp--preset--gradient--true-sunset) !important;}.has-morpheus-den-gradient-background{background: var(--wp--preset--gradient--morpheus-den) !important;}.has-plum-plate-gradient-background{background: var(--wp--preset--gradient--plum-plate) !important;}.has-aqua-splash-gradient-background{background: var(--wp--preset--gradient--aqua-splash) !important;}.has-love-kiss-gradient-background{background: var(--wp--preset--gradient--love-kiss) !important;}.has-new-retrowave-gradient-background{background: var(--wp--preset--gradient--new-retrowave) !important;}.has-plum-bath-gradient-background{background: var(--wp--preset--gradient--plum-bath) !important;}.has-high-flight-gradient-background{background: var(--wp--preset--gradient--high-flight) !important;}.has-teen-party-gradient-background{background: var(--wp--preset--gradient--teen-party) !important;}.has-fabled-sunset-gradient-background{background: var(--wp--preset--gradient--fabled-sunset) !important;}.has-arielle-smile-gradient-background{background: var(--wp--preset--gradient--arielle-smile) !important;}.has-itmeo-branding-gradient-background{background: var(--wp--preset--gradient--itmeo-branding) !important;}.has-deep-blue-gradient-background{background: var(--wp--preset--gradient--deep-blue) !important;}.has-strong-bliss-gradient-background{background: var(--wp--preset--gradient--strong-bliss) !important;}.has-sweet-period-gradient-background{background: var(--wp--preset--gradient--sweet-period) !important;}.has-purple-division-gradient-background{background: var(--wp--preset--gradient--purple-division) !important;}.has-cold-evening-gradient-background{background: var(--wp--preset--gradient--cold-evening) !important;}.has-mountain-rock-gradient-background{background: var(--wp--preset--gradient--mountain-rock) !important;}.has-desert-hump-gradient-background{background: var(--wp--preset--gradient--desert-hump) !important;}.has-ethernal-constance-gradient-background{background: var(--wp--preset--gradient--ethernal-constance) !important;}.has-happy-memories-gradient-background{background: var(--wp--preset--gradient--happy-memories) !important;}.has-grown-early-gradient-background{background: var(--wp--preset--gradient--grown-early) !important;}.has-morning-salad-gradient-background{background: var(--wp--preset--gradient--morning-salad) !important;}.has-night-call-gradient-background{background: var(--wp--preset--gradient--night-call) !important;}.has-mind-crawl-gradient-background{background: var(--wp--preset--gradient--mind-crawl) !important;}.has-angel-care-gradient-background{background: var(--wp--preset--gradient--angel-care) !important;}.has-juicy-cake-gradient-background{background: var(--wp--preset--gradient--juicy-cake) !important;}.has-rich-metal-gradient-background{background: var(--wp--preset--gradient--rich-metal) !important;}.has-mole-hall-gradient-background{background: var(--wp--preset--gradient--mole-hall) !important;}.has-cloudy-knoxville-gradient-background{background: var(--wp--preset--gradient--cloudy-knoxville) !important;}.has-soft-grass-gradient-background{background: var(--wp--preset--gradient--soft-grass) !important;}.has-saint-petersburg-gradient-background{background: var(--wp--preset--gradient--saint-petersburg) !important;}.has-everlasting-sky-gradient-background{background: var(--wp--preset--gradient--everlasting-sky) !important;}.has-kind-steel-gradient-background{background: var(--wp--preset--gradient--kind-steel) !important;}.has-over-sun-gradient-background{background: var(--wp--preset--gradient--over-sun) !important;}.has-premium-white-gradient-background{background: var(--wp--preset--gradient--premium-white) !important;}.has-clean-mirror-gradient-background{background: var(--wp--preset--gradient--clean-mirror) !important;}.has-wild-apple-gradient-background{background: var(--wp--preset--gradient--wild-apple) !important;}.has-snow-again-gradient-background{background: var(--wp--preset--gradient--snow-again) !important;}.has-confident-cloud-gradient-background{background: var(--wp--preset--gradient--confident-cloud) !important;}.has-glass-water-gradient-background{background: var(--wp--preset--gradient--glass-water) !important;}.has-perfect-white-gradient-background{background: var(--wp--preset--gradient--perfect-white) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;}.has-larger-font-size{font-size: var(--wp--preset--font-size--larger) !important;}.has-xxlarge-font-size{font-size: var(--wp--preset--font-size--xxlarge) !important;}',
'isGlobalStyles' => true
],
[
'__unstableType' => 'theme',
'css' => ':root { --wp--style--global--content-size: var(--theme-block-max-width);--wp--style--global--wide-size: var(--theme-block-wide-max-width); }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--theme-content-spacing); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--theme-content-spacing); }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: var(--theme-content-spacing);margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: var(--theme-content-spacing);margin-block-end: 0;}:root :where(.is-layout-flex){gap: var(--theme-content-spacing);}:root :where(.is-layout-grid){gap: var(--theme-content-spacing);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 0px;}',
'isGlobalStyles' => true
],
[
'__unstableType' => 'user',
'css' => " :root{--theme-block-max-width: 700px;--global-calc-content-width: 700px;--theme-block-wide-max-width: 820px;--theme-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";--theme-font-weight: 400;--theme-text-transform: none;--theme-text-decoration: none;--theme-font-size: 16px;--theme-line-height: 1.60;--theme-letter-spacing: 0em;--theme-button-font-weight: 500;--theme-button-font-size: 16px;--theme-palette-color-1: #4F46E5;--theme-palette-color-2: #7C3AED;--theme-palette-color-3: #1F2937;--theme-palette-color-4: #374151;--theme-palette-color-5: #6B7280;--theme-palette-color-6: #9CA3AF;--theme-palette-color-7: #E5E7EB;--theme-palette-color-8: #ffffff;--theme-text-color: var(--fcom-primary-text, #19283a);--theme-link-initial-color: var(--theme-palette-color-1);--theme-link-hover-color: var(--theme-palette-color-2);--theme-selection-text-color: #ffffff;--theme-selection-background-color: var(--theme-palette-color-1);--theme-border-color: var(--theme-palette-color-5);--theme-headings-color: var(--theme-palette-color-4);--theme-content-spacing: 1.5em;--theme-button-min-height: 40px;--theme-button-shadow: none;--theme-button-transform: none;--theme-button-text-initial-color: #ffffff;--theme-button-text-hover-color: #ffffff;--theme-button-background-initial-color: var(--theme-palette-color-1);--theme-button-background-hover-color: var(--theme-palette-color-2);--theme-button-border: none;--theme-button-padding: 5px 20px;--theme-normal-container-max-width: 1290px;--theme-content-vertical-spacing: 60px;--theme-container-edge-spacing: 90vw;--theme-narrow-container-max-width: 750px;--theme-wide-offset: 130px;--fcom-font-size-small: 16px;--fcom-font-size-medium: 18px;--fcom-font-size-large: 22px;--fcom-font-size-larger: 26px;--fcom-font-size-xxlarge: 32px;--wp--preset--spacing--20: 0.44rem;--wp--preset--spacing--30: 0.67rem;--wp--preset--spacing--40: 1rem;--wp--preset--spacing--50: 1.5rem;--wp--preset--spacing--60: 2.25rem;--wp--preset--spacing--70: 3.38rem;--wp--preset--spacing--80: 5.06rem}body .has-theme-palette-color-1-color{color:var(--theme-palette-color-1)}body .has-theme-palette-color-2-color{color:var(--theme-palette-color-2)}body .has-theme-palette-color-3-color{color:var(--theme-palette-color-3)}body .has-theme-palette-color-4-color{color:var(--theme-palette-color-4)}body .has-theme-palette-color-5-color{color:var(--theme-palette-color-5)}body .has-theme-palette-color-6-color{color:var(--theme-palette-color-6)}body .has-theme-palette-color-7-color{color:var(--theme-palette-color-7)}body .has-theme-palette-color-8-color{color:var(--theme-palette-color-8)}body .has-theme-palette-color-1-background-color{background-color:var(--theme-palette-color-1)}body .has-theme-palette-color-2-background-color{background-color:var(--theme-palette-color-2)}body .has-theme-palette-color-3-background-color{background-color:var(--theme-palette-color-3)}body .has-theme-palette-color-4-background-color{background-color:var(--theme-palette-color-4)}body .has-theme-palette-color-5-background-color{background-color:var(--theme-palette-color-5)}body .has-theme-palette-color-6-background-color{background-color:var(--theme-palette-color-6)}body .has-theme-palette-color-7-background-color{background-color:var(--theme-palette-color-7)}body .has-theme-palette-color-8-background-color{background-color:var(--theme-palette-color-8)}body .has-small-font-size{font-size:var(--fcom-font-size-small)}body .has-medium-font-size{font-size:var(--fcom-font-size-medium)}body .has-large-font-size{font-size:var(--fcom-font-size-large)}body .has-larger-font-size{font-size:var(--fcom-font-size-larger)}body .has-xxlarge-font-size{font-size:var(--fcom-font-size-xxlarge)}body .is-root-container>.alignfull{margin-inline:var(--has-wide, -20px)}body .is-root-container>.wp-block.alignleft{margin-inline-start:calc((100% - min(var(--theme-block-max-width),100%))/2)}body .is-root-container>.wp-block.alignright{margin-inline-end:calc((100% - min(var(--theme-block-max-width),100%))/2)}body :root .wp-element-button{font-family:var(--theme-button-font-family, var(--theme-font-family));font-size:var(--theme-button-font-size);font-weight:var(--theme-button-font-weight);font-style:var(--theme-button-font-style);line-height:var(--theme-button-line-height);letter-spacing:var(--theme-button-letter-spacing);text-transform:var(--theme-button-text-transform);-webkit-text-decoration:var(--theme-button-text-decoration);text-decoration:var(--theme-button-text-decoration)}body :root .wp-block-button[style*=font-weight] .wp-element-button{font-weight:inherit}body .wp-block-columns:last-child{margin-bottom:0}body .has-drop-cap:not(:focus):first-letter{font-size:5.8em;font-weight:700;margin:.1em .12em .05em 0}body figcaption{text-align:center;margin-block:.5em 0}body .wp-block-code,body .wp-block-verse,body .wp-block-preformatted{box-sizing:border-box;tab-size:4;padding:15px 20px;border-radius:3px;background:var(--theme-palette-color-7)}body blockquote{margin-inline:0}body blockquote:where(:not(.is-style-plain)):where(:not(.has-text-align-center):not(.has-text-align-right)){border-inline-start:4px solid var(--theme-palette-color-1)}body blockquote:where(:not(.is-style-plain)).has-text-align-center{padding-block:1.5em;border-block:3px solid var(--theme-palette-color-1)}body blockquote:where(:not(.is-style-plain)).has-text-align-right{border-inline-end:4px solid var(--theme-palette-color-1)}body blockquote:where(:not(.is-style-plain):not(.has-text-align-center):not(.has-text-align-right)){padding-inline-start:1.5em}body blockquote.has-text-align-right{padding-inline-end:1.5em}body blockquote p:last-child{margin-bottom:0}body blockquote cite{font-size:14px}body .wp-block-list{padding-left:30px}body .wp-block-pullquote{position:relative;padding:70px;text-align:initial;border-width:10px;border-style:solid;border-color:var(--theme-palette-color-1)}body .wp-block-pullquote blockquote{border:0;padding:0;margin:0;position:relative;isolation:isolate}body .wp-block-pullquote blockquote p{margin-top:0;margin-bottom:1em}body .wp-block-pullquote blockquote p:last-child{margin-bottom:0}body .wp-block-pullquote blockquote cite{font-size:16px;font-weight:500}body [data-align=left] .wp-block-pullquote,body [data-align=right] .wp-block-pullquote{max-width:50%;margin-top:.3em;margin-bottom:.3em}body .wp-block-table table{border-width:1px}body .wp-block-table table:not(.has-border-color) thead,body .wp-block-table table:not(.has-border-color) tfoot,body .wp-block-table table:not(.has-border-color) td,body .wp-block-table table:not(.has-border-color) th{border-color:var(--theme-table-border-color, var(--theme-border-color))}body .wp-block-table th:not([class*=has-text-align]){text-align:inherit}body .wp-block-table.is-style-stripes{border:0}body .wp-block-button.is-style-outline .wp-element-button{padding:var(--theme-button-padding);border:2px solid;border-color:var(--theme-button-background-initial-color)}body .wp-block-button.is-style-outline .wp-element-button:not(.has-text-color){color:var(--theme-button-background-initial-color)}body .wp-block-button.is-style-outline .wp-element-button:hover{color:var(--theme-button-text-hover-color);border-color:var(--theme-button-background-hover-color);background-color:var(--theme-button-background-hover-color)}body .wp-block-separator{border:none;margin-inline:auto;color:var(--theme-form-field-border-initial-color)}body .wp-block-separator:not(:where(.is-style-wide,.is-style-dots,.alignfull,.alignwide)){max-width:100px !important}body .wp-block-separator:not(.is-style-dots){height:2px;background-color:currentColor}body :root :where(p.has-background,.wp-block-group.has-background){padding:30px;box-sizing:border-box}body h1.has-background,body h2.has-background,body h3.has-background,body h4.has-background,body h5.has-background,body h6.has-background{padding:1.25em 2.375em}body{background-color:var(--fcom-primary-bg, white);background-image:none;font-family:var(--theme-font-family);line-height:var(--theme-line-height)}body .is-root-container{font-size:var(--theme-font-size, 16px);font-family:var(--theme-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\")}.block-editor-iframe__html.is-zoomed-out .block-editor-iframe__body{padding:20px 30px}.editor-visual-editor__post-title-wrapper.edit-post-visual-editor__post-title-wrapper{margin-top:0px !important;padding-top:0;margin-bottom:30px;position:relative}.editor-visual-editor__post-title-wrapper.edit-post-visual-editor__post-title-wrapper h1{font-size:32px;font-weight:700}h1{--theme-font-weight: 700;--theme-font-size: 40px;--theme-line-height: 1.5}h2{--theme-font-weight: 700;--theme-font-size: 35px;--theme-line-height: 1.5}h3{--theme-font-weight: 700;--theme-font-size: 30px;--theme-line-height: 1.5}h4{--theme-font-weight: 700;--theme-font-size: 25px;--theme-line-height: 1.5}h5{--theme-font-weight: 700;--theme-font-size: 20px;--theme-line-height: 1.5}h6{--theme-font-weight: 700;--theme-font-size: 16px;--theme-line-height: 1.5}.wp-block-pullquote{--theme-font-family: Georgia;--theme-font-weight: 600;--theme-font-size: 25px}pre,code,samp,kbd{--theme-font-family: monospace;--theme-font-weight: 400;--theme-font-size: 16px}figcaption{--theme-font-size: 14px}li::marker{color:#959595}.editor-styles-wrapper{--true: initial;--false: ;--wp--style--global--content-size: var(--theme-block-max-width);--wp--style--global--wide-size: var(--theme-block-wide-max-width);box-sizing:border-box;border:var(--has-boxed, var(--theme-boxed-content-border));padding:var(--has-boxed, var(--theme-boxed-content-spacing));box-shadow:var(--has-boxed, var(--theme-boxed-content-box-shadow));border-radius:var(--has-boxed, var(--theme-boxed-content-border-radius));margin-inline:auto;margin-block:var(--has-boxed, 20px);width:calc(100% - 40px);max-width:100%}:is(.is-layout-flow,.is-layout-constrained)>*:where(:not(h1,h2,h3,h4,h5,h6)){margin-block-start:0;margin-block-end:var(--theme-content-spacing)}:is(.is-layout-flow,.is-layout-constrained) :where(h1,h2,h3,h4,h5,h6){margin-block-end:calc(var(--has-theme-content-spacing, 1)*(.3em + 10px))}:root{color:var(--theme-text-color)}a{color:var(--theme-link-initial-color)}.block-editor-block-list__layout.is-root-container>.alignwide{max-width:var(--theme-block-wide-max-width);box-sizing:border-box}.is-root-container{padding:0 20px}\n"
],
[
'css' => file_exists(FLUENTCRM_PLUGIN_PATH . 'assets/guten-editor/index.css')
? file_get_contents(FLUENTCRM_PLUGIN_PATH . 'assets/guten-editor/index.css')
: '',
'__unstableType' => 'user'
],
[
'css' => $dynamicCss,
'__unstableType' => 'user'
]
];
}
/**
* Return the `__unstableResolvedAssets` for the editor settings.
*
* @return array
*/
private function getResolvedAssets()
{
$resolvedStyles = [
'wp-components-css' => includes_url('/css/dist/components/style.min.css'),
'wp-preferences-css' => includes_url('/css/dist/preferences/style.min.css'),
'wp-block-editor-css' => includes_url('/css/dist/block-editor/style.min.css'),
'wp-reusable-blocks-css' => includes_url('/css/dist/reusable-blocks/style.min.css'),
'wp-patterns-css' => includes_url('/css/dist/patterns/style.min.css'),
'wp-editor-css' => includes_url('/css/dist/editor/style.min.css'),
'wp-block-library-css' => includes_url('/css/dist/block-library/style.min.css'),
'wp-block-editor-content-css' => includes_url('/css/dist/block-editor/content.min.css'),
'wp-edit-blocks-css' => includes_url('/css/dist/block-library/editor.min.css'),
];
global $wp_version;
$cssFiles = '';
foreach ($resolvedStyles as $name => $file) {
$cssFiles .= "<link rel='stylesheet' id='{$name}' href='{$file}?ver={$wp_version}' media='all' />\n";
}
return [
'scripts' => '<script src="' . includes_url('/js/dist/vendor/wp-polyfill.min.js?ver=3.15.0') . '" id="wp-polyfill-js"></script>',
'styles' => $cssFiles
];
}
/**
* Return the `defaultEditorStyles` for the editor settings.
*
* @return array
*/
private function getDefaultEditorStyles()
{
return [
[
'css' => ':root{--wp-admin-theme-color:#007cba;--wp-admin-theme-color--rgb:0, 124, 186;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-10--rgb:0, 107, 161;--wp-admin-theme-color-darker-20:#005a87;--wp-admin-theme-color-darker-20--rgb:0, 90, 135;--wp-admin-border-width-focus:2px;--wp-block-synced-color:#7a00df;--wp-block-synced-color--rgb:122, 0, 223;--wp-bound-block-color:var(--wp-block-synced-color);}@media (min-resolution:192dpi){:root{--wp-admin-border-width-focus:1.5px;}}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:18px;line-height:1.5;--wp--style--block-gap:2em;}p{line-height:1.8;}.editor-post-title__block{font-size:2.5em;font-weight:800;margin-bottom:1em;margin-top:2em;}'
]
];
}
/*
|--------------------------------------------------------------------------
| Render: renderPage()
|--------------------------------------------------------------------------
*/
protected function renderPage()
{
add_action('fluent_crm/new_block_editor_footer', function () {
wp_underscore_playlist_templates();
if (function_exists('wp_script_modules') && method_exists(wp_script_modules(), 'print_import_map')) {
wp_script_modules()->print_import_map();
}
wp_print_footer_scripts();
wp_print_media_templates();
});
add_action('fluent_crm_block_editor/head', 'wp_enqueue_scripts', 1);
add_action('fluent_crm_block_editor/head', 'wp_resource_hints', 2);
add_action('fluent_crm_block_editor/head', 'wp_preload_resources', 1);
add_action('fluent_crm_block_editor/head', 'wp_print_styles', 8);
add_action('fluent_crm_block_editor/head', 'wp_print_head_scripts', 9);
$this->unloadOtherScripts();
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<title>FluentCRM Block Editor</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0,viewport-fit=cover"/>
<meta name="mobile-web-app-capable" content="yes">
<meta name="robots" content="noindex">
<?php do_action('fluent_crm_block_editor/head'); ?>
<?php do_action('fluent_crm/block_editor_head'); ?>
</head>
<body class="fcrm_custom_editor">
<div class="wp-site-blocks">
<div id="editor" class="gutenberg__editor"></div>
</div>
<?php
do_action('fluent_crm/new_block_editor_footer');
?>
</body>
</html>
<?php
}
/*
|--------------------------------------------------------------------------
| Isolation: unloadOtherScripts(), buildApprovedSlugsPattern(),
| unregisterDefaultBlockPatterns()
|--------------------------------------------------------------------------
*/
private function unloadOtherScripts()
{
$isSkip = apply_filters('fluent_crm_editor/skip_no_conflict', false);
if ($isSkip) {
return;
}
$approvedSlugsPattern = $this->buildApprovedSlugsPattern();
$pluginUrl = str_replace(['http:', 'https:'], '', plugins_url());
$themesUrl = str_replace(['http:', 'https:'], '', get_theme_root_uri());
add_filter('script_loader_src', function ($src, $handle) use ($approvedSlugsPattern, $pluginUrl, $themesUrl) {
if (!$src) {
return $src;
}
if ($this->isThirdPartyAsset($src, $approvedSlugsPattern, $pluginUrl, $themesUrl)) {
return false;
}
return $src;
}, 1, 2);
add_filter('style_loader_src', function ($src, $handle) use ($approvedSlugsPattern, $pluginUrl, $themesUrl) {
if (!$src) {
return $src;
}
if ($this->isThirdPartyAsset($src, $approvedSlugsPattern, $pluginUrl, $themesUrl)) {
return false;
}
return $src;
}, 1, 2);
add_action('wp_print_scripts', function () use ($approvedSlugsPattern, $pluginUrl, $themesUrl) {
global $wp_scripts;
if (!$wp_scripts) {
return;
}
foreach ($wp_scripts->queue as $script) {
if (empty($wp_scripts->registered[$script]) || empty($wp_scripts->registered[$script]->src)) {
continue;
}
$src = $wp_scripts->registered[$script]->src;
if (!$this->isThirdPartyAsset($src, $approvedSlugsPattern, $pluginUrl, $themesUrl)) {
continue;
}
wp_dequeue_script($wp_scripts->registered[$script]->handle);
}
}, 1);
add_action('wp_print_styles', function () {
$isSkip = apply_filters('fluent_crm_editor/skip_no_conflict', false, 'styles');
if ($isSkip) {
return;
}
// Dequeue theme.json global styles that aren't caught by URL-based filtering
wp_dequeue_style('global-styles');
wp_dequeue_style('global-styles-css-custom-properties');
global $wp_styles;
if (!$wp_styles) {
return;
}
$approvedSlugs = apply_filters('fluent_crm_editor/asset_listed_slugs', [
'\/gutenberg\/',
]);
$approvedSlugs[] = '\/fluent-crm\/';
$approvedSlugs = array_unique($approvedSlugs);
$approvedSlugs = implode('|', $approvedSlugs);
$pluginUrl = plugins_url();
$themeUrl = get_theme_root_uri();
$pluginUrl = str_replace(['http:', 'https:'], '', $pluginUrl);
$themeUrl = str_replace(['http:', 'https:'], '', $themeUrl);
foreach ($wp_styles->queue as $script) {
if (empty($wp_styles->registered[$script]) || empty($wp_styles->registered[$script]->src)) {
continue;
}
$src = $wp_styles->registered[$script]->src;
$pluginMatched = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src);
$themeMatched = (strpos($src, $themeUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src);
if (!$pluginMatched && !$themeMatched) {
continue;
}
wp_dequeue_style($wp_styles->registered[$script]->handle);
}
}, 999999);
}
/**
* Build the regex pattern string from approved asset slugs.
*
* @return string
*/
private function buildApprovedSlugsPattern()
{
/**
* Define the list of approved slugs for FluentCRM assets.
*
* This filter allows modification of the list of slugs that are approved for FluentCRM assets.
*
* @param array $approvedSlugs An array of approved slugs for FluentCRM assets.
*/
$approvedSlugs = apply_filters('fluent_crm_editor/asset_listed_slugs', [
'\/gutenberg\/',
]);
$approvedSlugs[] = 'fluent-crm';
$approvedSlugs = array_unique($approvedSlugs);
return implode('|', $approvedSlugs);
}
/**
* Check if an asset src is a third-party plugin/theme asset that should be blocked.
*
* @param string $src
* @param string $pattern
* @param string $pluginUrl
* @param string $themesUrl
* @return bool
*/
private function isThirdPartyAsset($src, $pattern, $pluginUrl, $themesUrl)
{
$pluginMatched = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $pattern . '/', $src);
if ($pluginMatched) {
return true;
}
$themeMatched = (strpos($src, $themesUrl) !== false) && !preg_match('/' . $pattern . '/', $src);
if ($themeMatched) {
return true;
}
return false;
}
protected function unregisterDefaultBlockPatterns($context = '', $data = [])
{
// Only affect FluentCRM iframe editor requests, never global wp-admin editors.
if (!isset($_REQUEST['fluent_crm_block_editor'])) {
return;
}
$shouldUnregister = (bool)apply_filters('fluent_crm/block_editor_unregister_all_patterns', true, $context, $data);
if (!$shouldUnregister) {
return;
}
// Prevent late core/theme pattern registration hooks from repopulating defaults.
foreach ([
'_register_core_block_patterns_and_categories',
'_register_theme_block_patterns',
'_register_remote_theme_patterns'
] as $callback) {
remove_action('init', $callback, 9);
remove_action('init', $callback, 10);
}
add_filter('should_load_remote_block_patterns', '__return_false', 999);
remove_theme_support('core-block-patterns');
if (class_exists('\WP_Block_Patterns_Registry')) {
$registry = \WP_Block_Patterns_Registry::get_instance();
$patterns = method_exists($registry, 'get_all_registered') ? $registry->get_all_registered() : [];
foreach (array_keys($patterns) as $patternName) {
if (method_exists($registry, 'unregister')) {
$registry->is_registered($patternName) && $registry->unregister($patternName);
}
}
}
if (class_exists('\WP_Block_Pattern_Categories_Registry')) {
$categoryRegistry = \WP_Block_Pattern_Categories_Registry::get_instance();
$categories = method_exists($categoryRegistry, 'get_all_registered') ? $categoryRegistry->get_all_registered() : [];
foreach (array_keys($categories) as $categoryName) {
if (method_exists($categoryRegistry, 'unregister')) {
$categoryRegistry->is_registered($categoryName) && $categoryRegistry->unregister($categoryName);
}
}
}
}
/*
|--------------------------------------------------------------------------
| Helpers: getRequiredCapability(), parseBoolParam(), getAvailableImageSizes()
|--------------------------------------------------------------------------
*/
/**
* Map a block type to the required FluentCRM capability.
*
* @param string $blockType
* @return string
*/
private static function getRequiredCapability($blockType)
{
if (in_array($blockType, ['campaign', 'email_body_in_funnel', 'recurring_campaign', 'recurring_mail', 'sequence_mail'], true)) {
return 'fcrm_manage_emails';
}
if ($blockType === 'template') {
return 'fcrm_manage_email_templates';
}
return 'fcrm_read_emails';
}
/**
* Parse a loosely-typed boolean parameter from a request value.
*
* @param mixed $value
* @return bool
*/
private static function parseBoolParam($value)
{
if (is_bool($value)) {
return $value;
}
if (is_numeric($value)) {
return ((int)$value) === 1;
}
$value = strtolower(trim((string)$value));
if ($value === '1' || $value === 'true' || $value === 'yes' || $value === 'on') {
return true;
}
if ($value === '0' || $value === 'false' || $value === 'no' || $value === 'off') {
return false;
}
return false;
}
/**
* Get available image sizes for the editor.
*
* @return array
*/
private function getAvailableImageSizes()
{
$size_names = apply_filters(
'image_size_names_choose',
array(
'thumbnail' => __('Thumbnail', 'fluent-crm'),
'medium' => __('Medium', 'fluent-crm'),
'large' => __('Large', 'fluent-crm'),
'full' => __('Full Size', 'fluent-crm'),
)
);
$all_sizes = array();
foreach ($size_names as $size_slug => $size_name) {
$all_sizes[] = array(
'slug' => $size_slug,
'name' => $size_name,
);
}
return $all_sizes;
}
}