mirror of
https://gh.wpcy.net/https://github.com/CaptainCore/Helm.git
synced 2026-05-17 15:33:48 +08:00
1176 lines
No EOL
50 KiB
PHP
1176 lines
No EOL
50 KiB
PHP
<?php
|
|
|
|
namespace CaptainCoreHelm;
|
|
|
|
class Plugin
|
|
{
|
|
public static function boot(): void
|
|
{
|
|
(new self())->register();
|
|
}
|
|
|
|
/**
|
|
* Register all hooks, organized by function.
|
|
*/
|
|
public function register(): void
|
|
{
|
|
$this->registerCore();
|
|
$this->registerAssets();
|
|
$this->registerIntegrations();
|
|
}
|
|
|
|
/* ============================ 1. Core Registration ============================ */
|
|
|
|
private function registerCore(): void
|
|
{
|
|
// Activation
|
|
register_activation_hook(CCHELM_FILE, [$this, 'onActivate']);
|
|
|
|
// User Profile Options
|
|
add_action('personal_options', [$this, 'renderProfileField']);
|
|
add_action('edit_user_profile', [$this, 'renderProfileField']);
|
|
add_action('personal_options_update', [$this, 'saveProfileField']);
|
|
add_action('edit_user_profile_update', [$this, 'saveProfileField']);
|
|
|
|
// Body Classes
|
|
add_filter('admin_body_class', [$this, 'adminBodyClass']);
|
|
add_filter('body_class', [$this, 'bodyClass']);
|
|
|
|
// Toolbar Logic
|
|
add_filter('cch_toolbar_keep_ids', [$this, 'frontendKeepIds']);
|
|
add_filter('show_admin_bar', [$this, 'forceShowAdminBar'], 10);
|
|
add_action('get_header', [$this, 'removeAdminBarBumpStyles']);
|
|
|
|
// Layout Reset (Critical CSS)
|
|
add_action('admin_head', [$this, 'printLayoutResetStyles'], 0);
|
|
add_action('wp_head', [$this, 'printLayoutResetStylesFront'], 0);
|
|
|
|
// Island UI Rendering
|
|
add_action('admin_footer', [$this, 'renderIsland'], 100);
|
|
add_action('wp_footer', [$this, 'renderIsland'], 100);
|
|
|
|
// Menu Snapshotting
|
|
add_action('load-plugin-install.php', [$this, 'redirectPluginInstallToFeatured']);
|
|
add_action('admin_menu', [$this, 'normalizePluginInstallUrl'], 9998);
|
|
add_action('admin_menu', [$this, 'captureMenuSnapshot'], 9999);
|
|
add_filter('cch_menu_snapshot', [$this, 'normalizeMenuVendors']);
|
|
add_filter('cch_core_menu_ids', [$this, 'frontendCoreIds']);
|
|
|
|
// Theme Variables
|
|
add_action('admin_head', [$this, 'printThemeVars'], 0);
|
|
add_action('wp_head', [$this, 'printThemeVarsFront'], 0);
|
|
|
|
// Error Page Interception
|
|
add_filter('wp_die_handler', [$this, 'interceptWpDieHandler']);
|
|
}
|
|
|
|
private function registerAssets(): void
|
|
{
|
|
add_action('admin_enqueue_scripts', [$this, 'enqueueAdmin']);
|
|
add_action('wp_enqueue_scripts', [$this, 'enqueueFront']);
|
|
}
|
|
|
|
private function registerIntegrations(): void
|
|
{
|
|
// Block Editor
|
|
add_action('enqueue_block_editor_assets', [$this, 'disableEditorFullscreenByDefault']);
|
|
|
|
// Site Editor (FSE)
|
|
add_action('load-site-editor.php', [$this, 'siteEditorInit']);
|
|
|
|
// Customizer
|
|
add_action('customize_controls_init', [$this, 'customizerControlsInit']);
|
|
add_action('customize_controls_print_styles', [$this, 'printLayoutResetStyles'], 5); // Reuse reset
|
|
add_action('customize_preview_init', [$this, 'customizerPreviewInit'], 20);
|
|
add_action('customize_controls_enqueue_scripts', [$this, 'customizerControlsScripts'], 20);
|
|
|
|
// Etch
|
|
add_filter('show_admin_bar', [$this, 'forceShowAdminBarInEtch'], 9999);
|
|
add_action('wp_enqueue_scripts', [$this, 'enqueueAssetsForEtch']);
|
|
|
|
// Elementor
|
|
add_action('admin_init', [$this, 'maybeBootstrapElementorEditor']);
|
|
add_action('elementor/editor/before_enqueue_styles', [$this, 'elementorEnqueueStyles'], 1);
|
|
add_action('elementor/editor/before_enqueue_scripts', [$this, 'elementorEnqueueScripts'], 1);
|
|
add_action('elementor/editor/wp_head', [$this, 'printLayoutResetStyles'], 5); // Reuse reset
|
|
add_action('elementor/editor/footer', [$this, 'elementorRenderIsland'], PHP_INT_MAX);
|
|
add_action('elementor/preview/init', [$this, 'elementorPreviewInit']);
|
|
add_action('elementor/editor/after_enqueue_scripts', [$this, 'elementorEditorScripts'], 20);
|
|
|
|
// Divi & Beaver Builder (Render special styles in footer)
|
|
add_action('wp_footer', [$this, 'printBuilderCompatibilityStyles'], 200);
|
|
|
|
// Builder specific inits to force admin bar behavior
|
|
add_action('wp', [$this, 'diviBuilderInit']);
|
|
add_action('wp', [$this, 'beaverBuilderInit']);
|
|
|
|
// Dark Mode Easter Egg
|
|
add_action('wp_ajax_cch_toggle_dark_mode', [$this, 'ajaxToggleDarkMode']);
|
|
|
|
// Menu Cache Purge
|
|
add_action('wp_ajax_cch_purge_menu_cache', [$this, 'ajaxPurgeMenuCache']);
|
|
}
|
|
|
|
/* =========================== 2. User Preference ========================== */
|
|
|
|
public function onActivate(): void
|
|
{
|
|
if ($uid = get_current_user_id()) {
|
|
update_user_meta($uid, 'cch_helm_enabled', '1');
|
|
}
|
|
}
|
|
|
|
public function renderProfileField(\WP_User $user): void
|
|
{
|
|
$enabled = $this->isEnabledForUser($user->ID);
|
|
?>
|
|
<table class="form-table" role="presentation">
|
|
<tr>
|
|
<th scope="row"><label for="cch_helm_enabled"><?php echo esc_html__('CaptainCore Helm', 'captaincore-helm'); ?></label></th>
|
|
<td>
|
|
<label>
|
|
<input type="checkbox" name="cch_helm_enabled" id="cch_helm_enabled" value="1" <?php checked($enabled, true); ?> />
|
|
<?php echo esc_html__('Use the Helm admin view (Island UI, Quick Menu, hidden left menu).', 'captaincore-helm'); ?>
|
|
</label>
|
|
<?php wp_nonce_field('cch_helm_profile', 'cch_helm_nonce'); ?>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<?php
|
|
}
|
|
|
|
public function saveProfileField(int $userId): void
|
|
{
|
|
if (!current_user_can('edit_user', $userId)) return;
|
|
if (!isset($_POST['cch_helm_nonce']) || !wp_verify_nonce($_POST['cch_helm_nonce'], 'cch_helm_profile')) return;
|
|
$enabled = isset($_POST['cch_helm_enabled']) ? '1' : '0';
|
|
update_user_meta($userId, 'cch_helm_enabled', $enabled);
|
|
}
|
|
|
|
private function isEnabledForUser(int $userId = 0): bool
|
|
{
|
|
$userId = $userId ?: get_current_user_id();
|
|
if (!$userId) return false;
|
|
$val = get_user_meta($userId, 'cch_helm_enabled', true) === '1';
|
|
return (bool) apply_filters('cch_is_helm_enabled', $val, $userId);
|
|
}
|
|
|
|
private function isEnabled(): bool
|
|
{
|
|
return $this->isEnabledForUser(get_current_user_id());
|
|
}
|
|
|
|
private function isDarkMode(): bool
|
|
{
|
|
return get_user_meta(get_current_user_id(), 'cch_admin_dark_mode', true) === '1';
|
|
}
|
|
|
|
public function ajaxToggleDarkMode(): void
|
|
{
|
|
if (!check_ajax_referer('cch_helm_nonce', 'nonce', false) || !current_user_can('read')) {
|
|
wp_send_json_error();
|
|
}
|
|
|
|
$uid = get_current_user_id();
|
|
$current = get_user_meta($uid, 'cch_admin_dark_mode', true);
|
|
$new = $current ? '0' : '1';
|
|
|
|
update_user_meta($uid, 'cch_admin_dark_mode', $new);
|
|
|
|
wp_send_json_success(['active' => $new === '1']);
|
|
}
|
|
|
|
public function ajaxPurgeMenuCache(): void
|
|
{
|
|
if (!check_ajax_referer('cch_helm_nonce', 'nonce', false) || !current_user_can('read')) {
|
|
wp_send_json_error();
|
|
}
|
|
|
|
$uid = get_current_user_id();
|
|
delete_user_meta($uid, 'cch_menu_snapshot');
|
|
delete_user_meta($uid, 'cch_menu_hash');
|
|
|
|
wp_send_json_success(['purged' => true]);
|
|
}
|
|
|
|
/* ============================= 3. Body Classes =========================== */
|
|
|
|
public function adminBodyClass(string $classes): string
|
|
{
|
|
if (!$this->isEnabled()) return $classes;
|
|
|
|
$classes .= ' cch-hide-admin-menu cch-island-mode cch-php-boot';
|
|
|
|
if ($this->isDarkMode()) {
|
|
$classes .= ' cch-dark-mode';
|
|
}
|
|
|
|
return trim($classes);
|
|
}
|
|
|
|
public function bodyClass(array $classes): array
|
|
{
|
|
if ($this->isEnabled() && is_user_logged_in()) {
|
|
$classes[] = 'cch-island-mode';
|
|
$classes[] = 'cch-php-boot';
|
|
if ($this->isDarkMode()) {
|
|
$classes[] = 'cch-dark-mode';
|
|
}
|
|
}
|
|
return $classes;
|
|
}
|
|
|
|
/* ============================== 4. The Island ============================= */
|
|
|
|
public function forceShowAdminBar(bool $show): bool
|
|
{
|
|
if (!$this->isEnabled()) return $show;
|
|
|
|
// Optimizations to avoid forcing inside iframes where not needed
|
|
if (isset($_GET['_wp-find-template'])) return $show;
|
|
if (isset($_GET['brickspreview']) || isset($_GET['bricks_preview'])) return $show;
|
|
if (isset($_GET['fl_builder_ui_iframe'])) return $show;
|
|
|
|
return true;
|
|
}
|
|
|
|
public function removeAdminBarBumpStyles(): void
|
|
{
|
|
if ($this->isEnabled()) remove_action('wp_head', '_admin_bar_bump_cb');
|
|
}
|
|
|
|
public function renderIsland(): void
|
|
{
|
|
if (!$this->isEnabled() || !is_user_logged_in() || is_customize_preview()) return;
|
|
|
|
// Iframe prevention checks
|
|
if (isset($_GET['elementor-preview']) || isset($_GET['_wp-find-template']) ||
|
|
isset($_GET['brickspreview']) || isset($_GET['bricks_preview']) ||
|
|
isset($_GET['fl_builder_ui_iframe']) ||
|
|
// Customizer Iframe Checks
|
|
isset($_GET['customize_changeset_uuid']) || isset($_GET['customize_theme']) || isset($_GET['customize_messenger_channel'])
|
|
) {
|
|
return;
|
|
}
|
|
|
|
$this->renderIslandHtml();
|
|
}
|
|
|
|
/**
|
|
* Renders the HTML for the Island.
|
|
*/
|
|
public function renderIslandHtml(): void
|
|
{
|
|
wp_enqueue_style('dashicons');
|
|
|
|
list($editLink, $editTitle, $viewLink, $viewTitle) = $this->getIslandLinks();
|
|
$shortcut = $this->shortcutLabel();
|
|
|
|
// Styles
|
|
$this->printIslandCss();
|
|
|
|
echo '<div id="cch-island-container">';
|
|
|
|
// Render PHP Context Menu logic here to prevent flash
|
|
$this->renderContextMenu();
|
|
|
|
// Main Island (The Unified Dock)
|
|
echo '<div id="cch-island-main">';
|
|
echo '<button type="button" id="cch-island-toggle" aria-label="Open Quick Menu (' . esc_attr($shortcut) . ')">';
|
|
echo '<span class="dashicons dashicons-menu-alt cch-island-icon"></span>';
|
|
// The spinner
|
|
echo '<span class="dashicons dashicons-update cch-island-loader" style="display:none;"></span>';
|
|
// The label is here, but hidden via CSS until loading triggers
|
|
echo '<span class="cch-island-label">Menu</span>';
|
|
echo '</button>';
|
|
|
|
if (class_exists('QueryMonitor')) {
|
|
echo '<button type="button" id="cch-island-qm" class="cch-island-action" aria-label="Toggle Query Monitor" title="Query Monitor">';
|
|
echo '<span class="dashicons dashicons-performance"></span>';
|
|
echo '</button>';
|
|
}
|
|
|
|
if ($editLink) {
|
|
$editIcon = 'dashicons-edit';
|
|
echo '<a href="' . esc_url($editLink) . '" class="cch-island-action" title="' . esc_attr($editTitle) . '" aria-label="' . esc_attr($editTitle) . '">';
|
|
echo '<span class="dashicons ' . esc_attr($editIcon) . '"></span>';
|
|
echo '</a>';
|
|
} elseif ($viewLink) {
|
|
echo '<a href="' . esc_url($viewLink) . '" class="cch-island-action" title="' . esc_attr($viewTitle) . '" aria-label="' . esc_attr($viewTitle) . '">';
|
|
echo '<span class="dashicons dashicons-visibility"></span>';
|
|
echo '</a>';
|
|
}
|
|
echo '</div>'; // End Main
|
|
|
|
// Hide Button (Optional, floats outside)
|
|
echo '<button type="button" id="cch-ui-hide-btn" title="Hide Menu" aria-label="Hide Menu">';
|
|
echo '<span class="dashicons dashicons-arrow-down-alt2"></span>';
|
|
echo '</button>';
|
|
|
|
echo '</div>'; // End Container
|
|
|
|
// Toast
|
|
echo '<div id="cch-ui-toast">Menu hidden. Press ' . esc_html($shortcut) . ' to restore.</div>';
|
|
}
|
|
|
|
/**
|
|
* Logic to determine Edit/View links based on context (Builders, Backend, Frontend).
|
|
*/
|
|
private function getIslandLinks(): array
|
|
{
|
|
global $wp_admin_bar;
|
|
$editLink = ''; $editTitle = ''; $viewLink = ''; $viewTitle = '';
|
|
|
|
$isDivi = !empty($_GET['et_fb']);
|
|
$isBB = isset($_GET['fl_builder']);
|
|
|
|
// Frontend: Get edit link
|
|
if (!is_admin() && !$isDivi && !$isBB) {
|
|
if ($wp_admin_bar && $node = $wp_admin_bar->get_node('edit')) {
|
|
$editLink = $node->href;
|
|
$editTitle = strip_tags($node->title) ?: 'Edit';
|
|
}
|
|
if (!$editLink && is_singular()) {
|
|
$post = get_queried_object();
|
|
if ($post && isset($post->ID) && current_user_can('edit_post', $post->ID)) {
|
|
$editLink = get_edit_post_link($post->ID);
|
|
$editTitle = 'Edit';
|
|
}
|
|
}
|
|
// Fallback: If no edit link found, link to wp-admin dashboard
|
|
if (!$editLink) {
|
|
$editLink = admin_url();
|
|
$editTitle = 'Dashboard';
|
|
}
|
|
}
|
|
|
|
// Backend or Builders: Get view/exit link
|
|
if (is_admin() || $isDivi || $isBB) {
|
|
if ($isDivi) {
|
|
$viewLink = add_query_arg('et_fb', '0', get_permalink());
|
|
$viewTitle = 'Exit Visual Builder';
|
|
} elseif ($isBB) {
|
|
$viewLink = add_query_arg('fl_builder_action', 'close', get_permalink());
|
|
$viewTitle = 'Exit Beaver Builder';
|
|
} elseif ($wp_admin_bar) {
|
|
$node = $wp_admin_bar->get_node('view') ?: $wp_admin_bar->get_node('view-site');
|
|
if ($node) {
|
|
$viewLink = $node->href;
|
|
$viewTitle = strip_tags($node->title) ?: 'View';
|
|
}
|
|
}
|
|
|
|
if (!$viewLink && !$isDivi && !$isBB) {
|
|
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
|
if ($screen && $screen->base === 'post' && isset($_GET['post'])) {
|
|
$viewLink = get_permalink(intval($_GET['post']));
|
|
$viewTitle = 'View';
|
|
} elseif ($screen && $screen->base === 'post' && isset($_POST['post_ID'])) {
|
|
$viewLink = get_permalink(intval($_POST['post_ID']));
|
|
$viewTitle = 'View';
|
|
}
|
|
}
|
|
|
|
if (!$viewLink && !$isDivi && !$isBB) {
|
|
$viewLink = home_url('/');
|
|
$viewTitle = 'View Site';
|
|
}
|
|
}
|
|
|
|
return [$editLink, $editTitle, $viewLink, $viewTitle];
|
|
}
|
|
|
|
/**
|
|
* Detects context and renders the popup bubble menu if a match is found.
|
|
* Moved from JS to PHP to prevent visual flash on load.
|
|
*/
|
|
private function renderContextMenu(): void
|
|
{
|
|
$match = $this->calculateContextMatch();
|
|
if (!$match || empty($match['subs']) || count($match['subs']) < 2) {
|
|
return;
|
|
}
|
|
|
|
$currentUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
|
|
$currentPage = isset($_GET['page']) ? $_GET['page'] : '';
|
|
$currentWcPath = isset($_GET['path']) ? $_GET['path'] : '';
|
|
|
|
echo '<div id="cch-context-menu" class="cch-context-menu">';
|
|
echo '<div class="cch-context-dropdown">';
|
|
foreach ($match['subs'] as $sub) {
|
|
$isCurrent = false;
|
|
$href = $sub['href'];
|
|
$parsed = parse_url($href);
|
|
$query = [];
|
|
if (isset($parsed['query'])) parse_str($parsed['query'], $query);
|
|
|
|
$subPath = isset($query['path']) ? $query['path'] : '';
|
|
$subPage = isset($query['page']) ? $query['page'] : '';
|
|
|
|
if ($currentWcPath && $subPath) {
|
|
// WooCommerce path-based comparison
|
|
if (urldecode($currentWcPath) === urldecode($subPath)) $isCurrent = true;
|
|
} elseif (strpos($currentUrl, $href) !== false) {
|
|
$isCurrent = true;
|
|
} elseif ($currentPage && $subPage === $currentPage && !$currentWcPath && !$subPath) {
|
|
$isCurrent = true;
|
|
}
|
|
|
|
$classes = 'cch-context-item' . ($isCurrent ? ' cch-context-current' : '');
|
|
|
|
// External Link Check
|
|
$isExternal = false;
|
|
if (isset($parsed['host']) && $parsed['host'] !== $_SERVER['HTTP_HOST']) {
|
|
$isExternal = true;
|
|
}
|
|
|
|
echo '<a href="' . esc_url($href) . '" class="' . esc_attr($classes) . '" ' . ($isExternal ? 'target="_blank"' : '') . '>';
|
|
echo esc_html($sub['label']);
|
|
if ($isExternal) {
|
|
echo '<span class="dashicons dashicons-external cch-external-link-icon"></span>';
|
|
}
|
|
echo '</a>';
|
|
}
|
|
echo '</div>';
|
|
echo '<button class="cch-context-label" type="button">';
|
|
echo '<span class="cch-context-text">' . esc_html($match['label']) . '</span>';
|
|
echo '<span class="dashicons dashicons-arrow-up-alt2"></span>';
|
|
echo '</button>';
|
|
echo '</div>';
|
|
}
|
|
|
|
private function calculateContextMatch(): ?array
|
|
{
|
|
$snapshot = get_user_meta(get_current_user_id(), 'cch_menu_snapshot', true);
|
|
if (!is_array($snapshot) || empty($snapshot)) return null;
|
|
|
|
// Current Request Data
|
|
$currentUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
|
|
$currentPage = isset($_GET['page']) ? $_GET['page'] : '';
|
|
$currentPostType = isset($_GET['post_type']) ? $_GET['post_type'] : '';
|
|
$currentWcPath = isset($_GET['path']) ? $_GET['path'] : '';
|
|
|
|
$bestMatch = null;
|
|
$bestMatchScore = -1;
|
|
|
|
foreach ($snapshot as $item) {
|
|
$score = 0;
|
|
$itemHref = isset($item['href']) ? $item['href'] : '';
|
|
|
|
// Parse item URL
|
|
$parsed = parse_url($itemHref);
|
|
$query = [];
|
|
if (isset($parsed['query'])) parse_str($parsed['query'], $query);
|
|
|
|
$itemPage = isset($query['page']) ? $query['page'] : '';
|
|
$itemPostType = isset($query['post_type']) ? $query['post_type'] : '';
|
|
$itemPath = isset($query['path']) ? $query['path'] : '';
|
|
|
|
// 1. Post Type Guard (Crucial for blocking "Posts" menu on CPTs)
|
|
if ($currentPostType && $currentPostType !== 'post') {
|
|
$isGenericPostLink = (
|
|
strpos($itemHref, '/edit.php') !== false ||
|
|
strpos($itemHref, '/post-new.php') !== false
|
|
);
|
|
|
|
// If generic edit link but no post_type param, and we are on a CPT, reject.
|
|
if ($isGenericPostLink && empty($itemPostType)) {
|
|
$score = -1000;
|
|
}
|
|
// Match
|
|
if ($itemPostType === $currentPostType) {
|
|
$score += 100;
|
|
}
|
|
}
|
|
|
|
// 2. WC Path Matching
|
|
if ($currentWcPath && $itemPath) {
|
|
$dCurrent = urldecode($currentWcPath);
|
|
$dItem = urldecode($itemPath);
|
|
|
|
if ($dCurrent === $dItem) {
|
|
$score += 150;
|
|
} elseif (strpos($dCurrent, '/analytics') === 0 && strpos($dItem, '/analytics') === 0) {
|
|
$score += 80;
|
|
} elseif (strpos($dCurrent, $dItem) === 0 && strlen($dItem) > 1) {
|
|
$score += 50;
|
|
}
|
|
}
|
|
|
|
// 3. Page Param Match
|
|
if ($currentPage && $itemPage) {
|
|
if ($currentPage === $itemPage) {
|
|
$score += 60;
|
|
} else {
|
|
// Prefix match (e.g. wc-admin vs wc-admin-settings)
|
|
$currPrefix = strtok($currentPage, '_-');
|
|
$itemPrefix = strtok($itemPage, '_-');
|
|
if ($currPrefix === $itemPrefix && (strpos($itemPage, '_') !== false || strpos($itemPage, '-') !== false)) {
|
|
$score += 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Substring Match
|
|
if (strpos($currentUrl, $itemHref) !== false) {
|
|
$score += 20;
|
|
}
|
|
|
|
// 5. Recursive Sub-item Check
|
|
if (!empty($item['subs'])) {
|
|
$subScore = 0;
|
|
foreach ($item['subs'] as $sub) {
|
|
$subHref = $sub['href'];
|
|
$sParsed = parse_url($subHref);
|
|
$sQuery = [];
|
|
if (isset($sParsed['query'])) parse_str($sParsed['query'], $sQuery);
|
|
|
|
$subPage = isset($sQuery['page']) ? $sQuery['page'] : '';
|
|
|
|
if ($currentPage && $subPage === $currentPage) {
|
|
$subScore = max($subScore, 100);
|
|
} elseif (strpos($currentUrl, $subHref) !== false) {
|
|
$subScore = max($subScore, 50);
|
|
}
|
|
if ($currentPostType && strpos($subHref, 'post_type=' . $currentPostType) !== false) {
|
|
$subScore = max($subScore, 40);
|
|
}
|
|
}
|
|
$score += $subScore;
|
|
}
|
|
|
|
if ($score > $bestMatchScore && $score > 0) {
|
|
$bestMatchScore = $score;
|
|
$bestMatch = $item;
|
|
}
|
|
}
|
|
|
|
return $bestMatch;
|
|
}
|
|
|
|
private function printIslandCss(): void
|
|
{
|
|
echo '<style>
|
|
#cch-island-container { position: fixed; bottom: 0; left: 50%; transform: translateX(-50%); z-index: 99999; display: flex; flex-direction: column; align-items: center; gap: 6px; padding-bottom: 10px; transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
#cch-island-container.cch-ui-hidden { transform: translateX(-50%) translateY(200%) !important; }
|
|
#cch-island-main { display: flex; align-items: center; justify-content: center; gap: 12px; }
|
|
|
|
/* Buttons */
|
|
#cch-island-toggle { background: var(--cch-color-1, #1d2327); color: #fff; border: 1px solid rgba(255,255,255,0.1); border-radius: 999px; height: 48px; padding: 0 24px; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.25); display: flex; align-items: center; gap: 8px; font-family: -apple-system, sans-serif; font-size: 14px; font-weight: 500; transition: transform 0.1s ease; }
|
|
#cch-island-toggle .dashicons { font-size: 20px; width: 20px; height: 20px; left: 4px; position: relative; }
|
|
|
|
.cch-island-action { background: var(--cch-color-1, #1d2327); color: #fff; border: 1px solid rgba(255,255,255,0.1); border-radius: 50%; width: 48px; height: 48px; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.25); display: flex; align-items: center; justify-content: center; text-decoration: none; transition: transform 0.1s ease; }
|
|
.cch-island-action .dashicons { font-size: 20px; width: 20px; height: 20px; }
|
|
|
|
#cch-ui-hide-btn { width: 16px; height: 16px; background: transparent; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--cch-color-1, #1d2327); }
|
|
#cch-ui-hide-btn:hover { opacity: 0.7; }
|
|
#cch-ui-hide-btn .dashicons { font-size: 12px; width: 12px; height: 12px; margin-top: 1px; }
|
|
|
|
/* Toast */
|
|
#cch-ui-toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%) translateY(20px); background: #1d2327; color: #fff; padding: 10px 20px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.15); font-family: -apple-system, sans-serif; font-size: 13px; font-weight: 500; box-shadow: 0 4px 20px rgba(0,0,0,0.4); opacity: 0; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease; z-index: 100000; white-space: nowrap; }
|
|
#cch-ui-toast.cch-show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
</style>';
|
|
}
|
|
|
|
public function frontendKeepIds(array $ids): array
|
|
{
|
|
if (!is_admin() && $this->isEnabled()) {
|
|
$ids[] = 'wp-admin-bar-my-account';
|
|
$ids[] = 'wp-admin-bar-site-name';
|
|
}
|
|
return $ids;
|
|
}
|
|
|
|
/* ============================= 5. Critical CSS =========================== */
|
|
|
|
public function printLayoutResetStyles(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
// Optimization: Do not run inside builder iframes
|
|
if (isset($_GET['brickspreview']) || isset($_GET['bricks_preview']) || isset($_GET['fl_builder_ui_iframe'])) return;
|
|
|
|
echo '<style id="cch-critical-layout">
|
|
html { margin-top: 0 !important; }
|
|
body { margin-top: 0 !important; }
|
|
#wpadminbar { display: none !important; }
|
|
.elementor-editor-active #wpwrap { margin-top: 0 !important; }
|
|
.edit-site { margin-top: 0 !important; }
|
|
.wp-full-overlay { top: 0 !important; } /* Customizer */
|
|
/* Force Z-Index */
|
|
#cch-island-container { z-index: 500001; }
|
|
#cch-popout { z-index: 500002; }
|
|
@media screen { html { margin-top: 0 !important; } }
|
|
@media screen and (max-width: 782px) { html { margin-top: 0 !important; } }
|
|
@keyframes cch-spin { 100% { transform: rotate(360deg); } }
|
|
</style>';
|
|
}
|
|
|
|
public function printLayoutResetStylesFront(): void
|
|
{
|
|
if (!$this->isEnabled() || !is_user_logged_in()) return;
|
|
$this->printLayoutResetStyles();
|
|
}
|
|
|
|
/* ================================ 6. Assets ============================== */
|
|
|
|
public function enqueueAdmin(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
$this->enqueueAssetsLogic(true);
|
|
}
|
|
|
|
public function enqueueFront(): void
|
|
{
|
|
if (!$this->isEnabled() || !is_user_logged_in()) return;
|
|
$this->enqueueAssetsLogic(false);
|
|
}
|
|
|
|
private function enqueueAssetsLogic(bool $isAdmin): void
|
|
{
|
|
wp_enqueue_style('cch-helm', CCHELM_URL . 'assets/css/helm.css', [], CCHELM_VER);
|
|
wp_enqueue_style('dashicons');
|
|
|
|
// Check user preference for Dark Mode
|
|
$isDarkMode = $this->isDarkMode();
|
|
|
|
// Register it so JS can toggle it, but only enqueue if active
|
|
wp_register_style('cch-dark-overrides', CCHELM_URL . 'assets/css/dark-admin.css', [], CCHELM_VER);
|
|
|
|
if ($isDarkMode && is_admin()) {
|
|
wp_enqueue_style('cch-dark-overrides');
|
|
}
|
|
|
|
$config = $this->getJsConfig($isAdmin);
|
|
|
|
// Pass the state to JS
|
|
$config['darkModeActive'] = (bool) $isDarkMode;
|
|
$config['nonce'] = wp_create_nonce('cch_helm_nonce'); // Needed for AJAX
|
|
$config['darkCssUrl'] = CCHELM_URL . 'assets/css/dark-admin.css'; // Needed for live toggle
|
|
|
|
wp_register_script('cch-helm', CCHELM_URL . 'assets/js/helm.js', [], CCHELM_VER, true);
|
|
wp_enqueue_script('cch-helm');
|
|
wp_add_inline_script('cch-helm', 'window.CCHELM_CONFIG = ' . wp_json_encode($config) . ';', 'before');
|
|
}
|
|
|
|
private function getJsConfig(bool $isAdmin): array
|
|
{
|
|
$coreIds = apply_filters('cch_core_menu_ids', $this->defaultCoreIds());
|
|
$toolbarKeep = apply_filters('cch_toolbar_keep_ids', $this->defaultToolbarKeepIds($isAdmin));
|
|
|
|
$updatesCount = 0;
|
|
if (function_exists('wp_get_update_data')) {
|
|
$data = wp_get_update_data();
|
|
$updatesCount = isset($data['counts']['plugins']) ? (int) $data['counts']['plugins'] : 0;
|
|
} else {
|
|
$updates = get_site_transient('update_plugins');
|
|
if (isset($updates->response) && is_array($updates->response)) {
|
|
$updatesCount = (int) count($updates->response);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'coreIds' => array_values($coreIds),
|
|
'toolbarKeepIds' => array_values($toolbarKeep),
|
|
'updatesCount' => (int) $updatesCount,
|
|
'menuSnapshot' => get_user_meta(get_current_user_id(), 'cch_menu_snapshot', true) ?: [],
|
|
'toolbarLabelMap' => apply_filters('cch_toolbar_label_map', ['wp-admin-bar-comments' => 'Comments']),
|
|
'toolbarIconMap' => apply_filters('cch_toolbar_icon_map', [
|
|
'wp-admin-bar-site-name' => 'dashicons-admin-home',
|
|
'wp-admin-bar-new-content' => 'dashicons-plus',
|
|
'wp-admin-bar-comments' => 'dashicons-admin-comments',
|
|
'wp-admin-bar-updates' => 'dashicons-update',
|
|
'wp-admin-bar-customize' => 'dashicons-admin-customize',
|
|
'wp-admin-bar-search' => 'dashicons-search',
|
|
'wp-admin-bar-my-account' => 'dashicons-admin-users',
|
|
'wp-admin-bar-edit' => 'dashicons-edit',
|
|
]),
|
|
'toolbarSkipIds' => array_values(apply_filters('cch_toolbar_skip_ids', ['wp-admin-bar-menu-toggle', 'wp-admin-bar-search'])),
|
|
];
|
|
}
|
|
|
|
private function defaultCoreIds(): array
|
|
{
|
|
return ['menu-dashboard', 'menu-posts', 'menu-media', 'menu-pages', 'menu-comments', 'menu-appearance', 'menu-plugins', 'menu-users', 'menu-tools', 'menu-settings'];
|
|
}
|
|
|
|
private function defaultToolbarKeepIds(bool $isAdmin): array
|
|
{
|
|
if ($isAdmin) {
|
|
return ['wp-admin-bar-cch-popout-toggle', 'wp-admin-bar-site-name', 'wp-admin-bar-new-content', 'wp-admin-bar-comments', 'wp-admin-bar-my-account', 'wp-admin-bar-view'];
|
|
}
|
|
return ['wp-admin-bar-cch-popout-toggle', 'wp-admin-bar-edit', 'wp-admin-bar-new-content', 'wp-admin-bar-menu-toggle'];
|
|
}
|
|
|
|
public function frontendCoreIds(array $ids): array
|
|
{
|
|
return is_admin() ? $ids : $this->defaultCoreIds();
|
|
}
|
|
|
|
/* ============================ 7. Menu Snapshot =========================== */
|
|
|
|
public function redirectPluginInstallToFeatured(): void
|
|
{
|
|
if (!isset($_GET['tab']) && empty($_GET)) {
|
|
wp_safe_redirect(admin_url('plugin-install.php?tab=featured'));
|
|
exit;
|
|
}
|
|
}
|
|
|
|
public function normalizePluginInstallUrl(): void
|
|
{
|
|
global $submenu;
|
|
|
|
if (!isset($submenu['plugins.php']) || !is_array($submenu['plugins.php'])) {
|
|
return;
|
|
}
|
|
|
|
foreach ($submenu['plugins.php'] as $key => $item) {
|
|
if (isset($item[2]) && $item[2] === 'plugin-install.php') {
|
|
$submenu['plugins.php'][$key][2] = 'plugin-install.php?tab=featured';
|
|
}
|
|
}
|
|
}
|
|
|
|
public function captureMenuSnapshot(): void
|
|
{
|
|
if (!is_user_logged_in()) return;
|
|
|
|
$snapshot = $this->buildMenuSnapshot();
|
|
|
|
// Create a hash of the current menu structure
|
|
$newHash = md5(json_encode($snapshot));
|
|
|
|
// Get the stored hash
|
|
$uid = get_current_user_id();
|
|
$storedHash = get_user_meta($uid, 'cch_menu_hash', true);
|
|
|
|
// Only write to DB if the menu has actually changed
|
|
if ($newHash !== $storedHash) {
|
|
update_user_meta($uid, 'cch_menu_snapshot', $snapshot);
|
|
update_user_meta($uid, 'cch_menu_hash', $newHash);
|
|
}
|
|
}
|
|
|
|
public function normalizeMenuVendors(array $items): array
|
|
{
|
|
foreach ($items as &$it) {
|
|
// Gravity Forms
|
|
$isGfId = isset($it['id']) && $it['id'] === 'toplevel_page_gf_edit_forms';
|
|
$isGfSlug = false;
|
|
if (!empty($it['href'])) {
|
|
$q = [];
|
|
parse_str((string) parse_url($it['href'], PHP_URL_QUERY), $q);
|
|
$isGfSlug = isset($q['page']) && $q['page'] === 'gf_edit_forms';
|
|
}
|
|
if ($isGfId || $isGfSlug) $it['label'] = 'Gravity Forms';
|
|
|
|
// WooCommerce
|
|
if (!empty($it['id'])) {
|
|
if ($it['id'] === 'toplevel_page_woocommerce-marketing') {
|
|
$target = admin_url('admin.php?page=wc-admin&path=/marketing');
|
|
$it['href'] = !empty($it['subs'][0]['href']) ? $it['subs'][0]['href'] : $target;
|
|
}
|
|
if ($it['id'] === 'toplevel_page_woocommerce') {
|
|
$target = admin_url('admin.php?page=wc-admin');
|
|
$it['href'] = !empty($it['subs'][0]['href']) ? $it['subs'][0]['href'] : $target;
|
|
}
|
|
}
|
|
}
|
|
return $items;
|
|
}
|
|
|
|
private function buildMenuSnapshot(): array
|
|
{
|
|
global $menu, $submenu;
|
|
if (!is_array($menu) || empty($menu)) return [];
|
|
|
|
$items = [];
|
|
foreach ($menu as $m) {
|
|
if (strpos($m[4] ?? '', 'wp-menu-separator') !== false) continue;
|
|
|
|
$label = $this->cleanLabel((string)($m[0] ?? ''));
|
|
$slug = (string)($m[2] ?? '');
|
|
|
|
$href = (strpos($slug, '.php') !== false || strpos($slug, 'admin.php') !== false || strpos($slug, 'edit.php') !== false)
|
|
? admin_url($slug) : admin_url('admin.php?page=' . $slug);
|
|
|
|
$subs = [];
|
|
$seenLabels = [];
|
|
if (!empty($submenu[$slug])) {
|
|
foreach ($submenu[$slug] as $s) {
|
|
$subLabel = $this->cleanLabel($s[0] ?? '');
|
|
$subHref = $this->makeAdminHref((string)($s[2] ?? ''), $slug);
|
|
if ($subLabel === '' || $subHref === admin_url('admin.php?page=') || !$this->canAccessHref($subHref)) continue;
|
|
// Skip duplicate labels (e.g., WooCommerce Orders appears twice with different URLs)
|
|
$labelKey = strtolower($subLabel);
|
|
if (isset($seenLabels[$labelKey])) continue;
|
|
$seenLabels[$labelKey] = true;
|
|
$subs[] = ['label' => $subLabel, 'href' => esc_url_raw($subHref)];
|
|
}
|
|
}
|
|
|
|
if (($label === '' || $href === admin_url('admin.php?page=')) && empty($subs)) continue;
|
|
|
|
$topOk = $this->canAccessHref($href);
|
|
if (!$topOk && empty($subs)) continue;
|
|
|
|
$items[] = [
|
|
'id' => (string)($m[5] ?? ''),
|
|
'label' => $label,
|
|
'href' => esc_url_raw($href),
|
|
'iconClass' => (string)($m[6] ?? ''),
|
|
'subs' => $subs,
|
|
];
|
|
}
|
|
return apply_filters('cch_menu_snapshot', $items);
|
|
}
|
|
|
|
private function makeAdminHref(string $target, string $parentSlug): string
|
|
{
|
|
$t = ltrim($target, '/');
|
|
if (preg_match('#^https?://#i', $t)) return esc_url_raw($t);
|
|
|
|
$starts = ['admin.php','index.php','edit.php','upload.php','themes.php','plugins.php','post-new.php','users.php','user-new.php','tools.php','options-general.php','options-writing.php','options-reading.php','options-discussion.php','options-media.php','options-permalink.php','options-privacy.php','customize.php','site-editor.php','theme-editor.php','nav-menus.php','widgets.php','media-new.php','edit-comments.php','edit-tags.php','update-core.php','profile.php','plugin-install.php','plugin-editor.php','theme-install.php','site-health.php','import.php','export.php','export-personal-data.php','erase-personal-data.php'];
|
|
|
|
foreach ($starts as $p) {
|
|
if (stripos($t, $p) === 0) return esc_url_raw(admin_url($t));
|
|
}
|
|
|
|
$isBarePhp = substr($t, -4) === '.php' && strpos($t, '/') === false;
|
|
if ($isBarePhp && strpos($parentSlug, '.php') !== false) {
|
|
return esc_url_raw(admin_url($parentSlug . '?page=' . $t));
|
|
}
|
|
|
|
return esc_url_raw(admin_url('admin.php?page=' . $t));
|
|
}
|
|
|
|
private function cleanLabel(string $labelHtml): string
|
|
{
|
|
$label = wp_strip_all_tags(preg_replace('#<span[^>]*class=("|\')[^"\']*(screen-reader-text|awaiting-mod|update-plugins|count-[^"\'])[^"\']*\\1[^>]*>.*?</span>#si', '', $labelHtml));
|
|
$label = html_entity_decode($label, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
$label = preg_replace('/\s+/u', ' ', trim($label));
|
|
$label = preg_replace('/(?:[\s\x{00A0}]*[()\[\]•·|:\-\x{2013}\x{2014}]*)?\d+(?:\+)?$/u', '', $label);
|
|
return preg_replace('/\b\d+\s+.*in moderation\b/i', '', $label);
|
|
}
|
|
|
|
private function canAccessHref(string $href): bool
|
|
{
|
|
$p = wp_parse_url($href);
|
|
if (!is_array($p) || empty($p['path'])) return true;
|
|
|
|
$path = ltrim((string) $p['path'], '/');
|
|
$q = [];
|
|
if (!empty($p['query'])) parse_str((string) $p['query'], $q);
|
|
|
|
// Taxonomy
|
|
if ($path === 'wp-admin/edit-tags.php') {
|
|
$taxName = isset($q['taxonomy']) ? (string) $q['taxonomy'] : '';
|
|
$tax = $taxName !== '' ? get_taxonomy($taxName) : null;
|
|
if (!$tax) return false;
|
|
if ($taxName === 'link_category') {
|
|
return ((bool) get_option('link_manager_enabled')) && current_user_can('manage_links');
|
|
}
|
|
$showUi = isset($tax->show_ui) ? (bool) $tax->show_ui : true;
|
|
$cap = isset($tax->cap->manage_terms) ? $tax->cap->manage_terms : 'manage_categories';
|
|
return $showUi && current_user_can($cap);
|
|
}
|
|
|
|
// Link Manager
|
|
if ($path === 'wp-admin/link-manager.php' || ($path === "wp-admin/admin.php" && isset($q['page']) && $q['page'] === 'edit-tags.php?taxonomy=link_category')) {
|
|
return ((bool) get_option('link_manager_enabled')) && current_user_can('manage_links');
|
|
}
|
|
|
|
// Post types
|
|
if ($path === 'wp-admin/edit.php') {
|
|
$pt = isset($q['post_type']) ? get_post_type_object($q['post_type']) : get_post_type_object('post');
|
|
if (!$pt) return false;
|
|
$showUi = isset($pt->show_ui) ? (bool) $pt->show_ui : true;
|
|
$cap = isset($pt->cap->edit_posts) ? $pt->cap->edit_posts : 'edit_posts';
|
|
return $showUi && current_user_can($cap);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ============================ 8. Editor Tweaks =========================== */
|
|
|
|
public function disableEditorFullscreenByDefault(): void
|
|
{
|
|
if (!is_admin() || !$this->isEnabled()) return;
|
|
$js = "jQuery(window).load(function(){ if(typeof wp!=='undefined' && wp.data && wp.data.select('core/edit-post') && wp.data.select('core/edit-post').isFeatureActive('fullscreenMode')){ wp.data.dispatch('core/edit-post').toggleFeature('fullscreenMode'); } });";
|
|
wp_add_inline_script('wp-blocks', $js);
|
|
}
|
|
|
|
public function siteEditorInit(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
add_filter('show_admin_bar', '__return_true', PHP_INT_MAX);
|
|
require_once ABSPATH . WPINC . '/class-wp-admin-bar.php';
|
|
require_once ABSPATH . WPINC . '/admin-bar.php';
|
|
|
|
// Fix z-index for Site Editor
|
|
add_action('admin_head', function() {
|
|
echo '<style>
|
|
html, body, .edit-site { margin-top: 0 !important; }
|
|
#wpadminbar { display: none !important; }
|
|
#cch-island-container, #cch-popout, #cch-ui-toast { z-index: 9999999 !important; }
|
|
</style>';
|
|
}, 5);
|
|
|
|
add_action('admin_enqueue_scripts', function(){ wp_enqueue_script('admin-bar'); }, 5);
|
|
}
|
|
|
|
/* ========================= 9. Customizer Integration ===================== */
|
|
|
|
public function customizerControlsInit(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
add_action('customize_controls_enqueue_scripts', [$this, 'enqueueAdmin'], 20);
|
|
add_action('customize_controls_print_footer_scripts', function() {
|
|
echo '<script>document.body.classList.add("cch-island-mode", "cch-hide-admin-menu");</script>';
|
|
$this->renderIslandHtml(); // Use standard HTML render
|
|
}, 1);
|
|
}
|
|
|
|
public function customizerPreviewInit(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
$js = "(function(api){ if(window.__CCHELM_boundPreviewShortcut)return; window.__CCHELM_boundPreviewShortcut=true; const isMac=()=>/Mac|iPhone|iPad|iPod/i.test(navigator.userAgent)||/Mac/i.test(navigator.platform); const isOpenShortcut=(e)=>{ const k=(e.key&&e.key.toLowerCase()==='k')||(e.code&&e.code.toLowerCase()==='keyk')||e.keyCode===75; if(!k)return false; return isMac()?(e.metaKey&&e.altKey&&!e.ctrlKey):(e.ctrlKey&&e.altKey&&!e.metaKey); }; api.bind('preview-ready',function(){ window.addEventListener('keydown',function(e){ if(!isOpenShortcut(e))return; e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); api.preview.send('cch:toggle'); },true); }); })(wp.customize);";
|
|
wp_add_inline_script('customize-preview', $js);
|
|
}
|
|
|
|
public function customizerControlsScripts(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
$js = "(function(api){ if(window.__CCHELM_boundControlsListener)return; window.__CCHELM_boundControlsListener=true; api.bind('ready',function(){ api.previewer.bind('cch:toggle',function(){ var btn=document.getElementById('cch-island-toggle'); if(btn)btn.click(); }); }); })(wp.customize);";
|
|
wp_add_inline_script('customize-controls', $js);
|
|
}
|
|
|
|
/* ========================= 10. Etch Integration ========================== */
|
|
|
|
public function forceShowAdminBarInEtch(bool $show): bool
|
|
{
|
|
return (isset($_GET['etch']) && $_GET['etch'] === 'magic') ? true : $show;
|
|
}
|
|
|
|
public function enqueueAssetsForEtch(): void
|
|
{
|
|
if (!isset($_GET['etch']) || $_GET['etch'] !== 'magic' || !$this->isEnabled()) return;
|
|
wp_enqueue_script('admin-bar');
|
|
wp_register_style('admin-bar-cch-helm', includes_url('css/admin-bar.min.css'), [], '1.0.0', 'all');
|
|
wp_enqueue_style('admin-bar-cch-helm');
|
|
wp_enqueue_script('cch-helm');
|
|
}
|
|
|
|
/* ====================== 11. Elementor Integration ======================== */
|
|
|
|
private function isElementorEditorRequest(): bool
|
|
{
|
|
return is_admin() && isset($_GET['action']) && $_GET['action'] === 'elementor';
|
|
}
|
|
|
|
public function maybeBootstrapElementorEditor(): void
|
|
{
|
|
if ($this->isEnabled() && $this->isElementorEditorRequest()) add_filter('show_admin_bar', '__return_true', PHP_INT_MAX);
|
|
}
|
|
|
|
public function elementorEnqueueStyles(): void
|
|
{
|
|
if ($this->isEnabled() && $this->isElementorEditorRequest()) {
|
|
wp_enqueue_style('admin-bar');
|
|
wp_enqueue_style('dashicons');
|
|
}
|
|
}
|
|
|
|
public function elementorEnqueueScripts(): void
|
|
{
|
|
if ($this->isEnabled() && $this->isElementorEditorRequest()) {
|
|
wp_enqueue_script('admin-bar');
|
|
$this->enqueueAdmin();
|
|
}
|
|
}
|
|
|
|
public function elementorRenderIsland(): void
|
|
{
|
|
if (!$this->isEnabled() || !$this->isElementorEditorRequest()) return;
|
|
require_once ABSPATH . WPINC . '/class-wp-admin-bar.php';
|
|
require_once ABSPATH . WPINC . '/admin-bar.php';
|
|
if (!is_object($GLOBALS['wp_admin_bar'])) {
|
|
$GLOBALS['wp_admin_bar'] = new \WP_Admin_Bar();
|
|
$GLOBALS['wp_admin_bar']->initialize();
|
|
}
|
|
$this->renderIsland();
|
|
}
|
|
|
|
public function elementorPreviewInit(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
if (isset($GLOBALS['__CCHELM_boundElementorPreview'])) return;
|
|
$GLOBALS['__CCHELM_boundElementorPreview'] = true;
|
|
|
|
add_action('wp_enqueue_scripts', function () {
|
|
$js = "(function(){ const isMac=()=>/Mac|iPhone|iPad|iPod/i.test(navigator.userAgent)||/Mac/i.test(navigator.platform); const isOpenShortcut=(e)=>{ const k=(e.key&&e.key.toLowerCase()==='k')||(e.code&&e.code.toLowerCase()==='keyk')||e.keyCode===75; if(!k)return false; return isMac()?(e.metaKey&&e.altKey&&!e.ctrlKey):(e.ctrlKey&&e.altKey&&!e.metaKey); }; window.addEventListener('keydown',function(e){ if(!isOpenShortcut(e))return; e.preventDefault(); e.stopPropagation(); parent.postMessage({type:'cch:toggle'},'*'); },true); })();";
|
|
wp_add_inline_script('jquery-core', $js);
|
|
}, 20);
|
|
}
|
|
|
|
public function elementorEditorScripts(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
$js = "(function(){ window.addEventListener('message',function(e){ if(!e.data||e.data.type!=='cch:toggle')return; var btn=document.getElementById('cch-island-toggle'); if(btn)btn.click(); }); })();";
|
|
wp_add_inline_script('jquery-core', $js);
|
|
}
|
|
|
|
/* ===================== 12. Divi/BB Integration ======================= */
|
|
|
|
public function diviBuilderInit(): void
|
|
{
|
|
if (!empty($_GET['et_fb']) && $this->isEnabled()) {
|
|
add_filter('show_admin_bar', '__return_true', 999);
|
|
}
|
|
}
|
|
|
|
public function beaverBuilderInit(): void
|
|
{
|
|
if (isset($_GET['fl_builder']) && !isset($_GET['fl_builder_ui_iframe']) && $this->isEnabled()) {
|
|
add_filter('show_admin_bar', '__return_true', 9999);
|
|
add_action('wp_enqueue_scripts', [$this, 'enqueueFront'], 9999);
|
|
add_action('fl_builder_ui_enqueue_scripts', [$this, 'enqueueFront']);
|
|
}
|
|
}
|
|
|
|
public function printBuilderCompatibilityStyles(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
|
|
// Divi
|
|
if (!empty($_GET['et_fb'])) {
|
|
echo '<style id="cch-divi-integration">
|
|
html { margin-top: 0 !important; } #wpadminbar { display: none !important; }
|
|
body #cch-island-container { top: 20px !important; bottom: auto !important; z-index: 9999990 !important; }
|
|
body #cch-island-container.cch-ui-hidden { transform: translateX(-50%) translateY(-200%) !important; }
|
|
body #cch-popout { z-index: 9999999 !important; }
|
|
body #cch-ui-toast { top: 30px !important; bottom: auto !important; z-index: 9999999 !important; }
|
|
#cch-ui-hide-btn .dashicons { transform: rotate(180deg); }
|
|
</style>';
|
|
}
|
|
|
|
// Beaver Builder
|
|
if (isset($_GET['fl_builder']) && !isset($_GET['fl_builder_ui_iframe'])) {
|
|
echo '<style id="cch-beaver-builder-integration">
|
|
html { margin-top: 0 !important; } #wpadminbar { display: none !important; }
|
|
body #cch-island-container { z-index: 9999990 !important; }
|
|
body #cch-popout, body #cch-ui-toast { z-index: 9999999 !important; }
|
|
</style>';
|
|
}
|
|
}
|
|
|
|
/* ==================== 13. Error Page Injection ======================= */
|
|
|
|
public function interceptWpDieHandler($handler)
|
|
{
|
|
return [$this, 'renderWpDieIsland'];
|
|
}
|
|
|
|
public function renderWpDieIsland($message, $title = '', $args = [])
|
|
{
|
|
if (!$this->isEnabled()) {
|
|
_default_wp_die_handler($message, $title, $args);
|
|
return;
|
|
}
|
|
|
|
// Bail if this is a JSON, XML, or AJAX request (don't inject HTML UI)
|
|
if (
|
|
(function_exists('wp_is_json_request') && wp_is_json_request()) ||
|
|
(function_exists('wp_is_xml_request') && wp_is_xml_request()) || // WP 5.2+
|
|
(defined('DOING_AJAX') && DOING_AJAX) ||
|
|
(defined('REST_REQUEST') && REST_REQUEST)
|
|
) {
|
|
_default_wp_die_handler($message, $title, $args);
|
|
return;
|
|
}
|
|
|
|
$configJson = wp_json_encode($this->getJsConfig(is_admin()));
|
|
|
|
ob_start();
|
|
$this->printThemeVars();
|
|
$this->renderIslandHtml();
|
|
$islandHtml = ob_get_clean();
|
|
|
|
$cssUrl = CCHELM_URL . 'assets/css/helm.css?ver=' . CCHELM_VER;
|
|
$jsUrl = CCHELM_URL . 'assets/js/helm.js?ver=' . CCHELM_VER;
|
|
$dashiconsUrl = includes_url('css/dashicons.min.css');
|
|
|
|
$injection = "<!-- Helm -->
|
|
<link rel='stylesheet' href='{$dashiconsUrl}' media='all' />
|
|
<link rel='stylesheet' href='{$cssUrl}' media='all' />
|
|
<style>html, body { margin-bottom: 0 !important; } #cch-island-container, #cch-popout { z-index: 2147483647; }</style>
|
|
{$islandHtml}
|
|
<script>window.CCHELM_CONFIG = {$configJson};</script>
|
|
<script src='{$jsUrl}'></script>";
|
|
|
|
if (function_exists('_wp_die_process_input')) list($message, $title, $args) = _wp_die_process_input($message, $title, $args);
|
|
if (is_string($message)) $message .= $injection;
|
|
|
|
_default_wp_die_handler($message, $title, $args);
|
|
}
|
|
|
|
/* ======================= 14. Theme & Utils ========================== */
|
|
|
|
private function isMacUa(): bool
|
|
{
|
|
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
return (stripos($ua, 'macintosh') !== false || stripos($ua, 'mac os x') !== false || stripos($ua, 'iphone') !== false);
|
|
}
|
|
|
|
private function shortcutLabel(): string
|
|
{
|
|
return $this->isMacUa() ? '⌘⌥K' : 'Ctrl+Alt+K';
|
|
}
|
|
|
|
public function printThemeVars(): void
|
|
{
|
|
if (!$this->isEnabled()) return;
|
|
$colors = $this->getSchemeColors();
|
|
echo '<style id="cch-theme-vars">body{';
|
|
foreach ($colors as $index => $color) {
|
|
echo '--cch-color-' . intval($index) . ':' . esc_html(trim($color)) . ';';
|
|
}
|
|
echo '}</style>';
|
|
}
|
|
|
|
public function printThemeVarsFront(): void
|
|
{
|
|
if ($this->isEnabled() && is_user_logged_in()) $this->printThemeVars();
|
|
}
|
|
|
|
private function getSchemeColors(): array
|
|
{
|
|
$scheme = get_user_option('admin_color') ?: 'fresh';
|
|
global $_wp_admin_css_colors;
|
|
|
|
if (empty($_wp_admin_css_colors) && function_exists('register_admin_color_schemes')) register_admin_color_schemes();
|
|
|
|
if (isset($_wp_admin_css_colors[$scheme]->colors) && !empty($_wp_admin_css_colors[$scheme]->colors)) {
|
|
return (array) $_wp_admin_css_colors[$scheme]->colors;
|
|
}
|
|
|
|
$defaults = $this->defaultAdminColorSchemes();
|
|
return $defaults[$scheme] ?? $defaults['fresh'];
|
|
}
|
|
|
|
private function defaultAdminColorSchemes(): array
|
|
{
|
|
return [
|
|
'fresh' => ['#1d2327', '#2c3338', '#2271b1', '#72aee6'],
|
|
'light' => ['#e5e5e5', '#999999', '#d64e07', '#04a4cc'],
|
|
'modern' => ['#1e1e1e', '#3858e9', '#7b90ff'],
|
|
'blue' => ['#096484', '#4796b3', '#52accc', '#74B6CE'],
|
|
'midnight' => ['#25282b', '#363b3f', '#69a8bb', '#e14d43'],
|
|
'sunrise' => ['#b43c38', '#cf4944', '#dd823b', '#ccaf0b'],
|
|
'ectoplasm' => ['#413256', '#523f6d', '#a3b745', '#d46f15'],
|
|
'ocean' => ['#627c83', '#738e96', '#9ebaa0', '#aa9d88'],
|
|
'coffee' => ['#46403c', '#59524c', '#c7a589', '#9ea476'],
|
|
];
|
|
}
|
|
}
|