' . '' . ''; return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } /** * Matches the reference product's own real admin-bar node exactly (live-checked: * a plain text label, no icon at all, plus a dropdown submenu of quick * links) rather than ChargeForms' previous icon+label node with no * submenu. The unread-entries badge is a genuine ChargeForms addition * kept on top of that structure — the reference product has no equivalent, and it's * real, useful information, not a deviation worth dropping to match. */ public function register_admin_bar_node( $wp_admin_bar ) { if ( ! current_user_can( 'chargeforms_manage_forms' ) ) { return; } $title = esc_html__( 'ChargeForms', 'chargeforms' ); // Keep the WordPress admin-bar node visually identical to the reference // product: unread counts belong in the in-app Entries nav, not beside the // product name where a badge changes the bar's baseline and spacing. $wp_admin_bar->add_node( [ 'id' => 'chargeforms', 'title' => $title, 'href' => admin_url( 'admin.php?page=chargeforms' ), ] ); $wp_admin_bar->add_node( [ 'id' => 'chargeforms-bar-forms', 'parent' => 'chargeforms', 'title' => esc_html__( 'Forms', 'chargeforms' ), 'href' => admin_url( 'admin.php?page=chargeforms' ), ] ); $wp_admin_bar->add_node( [ 'id' => 'chargeforms-bar-new-form', 'parent' => 'chargeforms', 'title' => esc_html__( 'New Form', 'chargeforms' ), 'href' => admin_url( 'admin.php?page=chargeforms-new' ), ] ); $wp_admin_bar->add_node( [ 'id' => 'chargeforms-bar-entries', 'parent' => 'chargeforms', 'title' => esc_html__( 'Entries', 'chargeforms' ), 'href' => admin_url( 'admin.php?page=chargeforms-entries' ), ] ); $wp_admin_bar->add_node( [ 'id' => 'chargeforms-bar-reports', 'parent' => 'chargeforms', 'title' => esc_html__( 'Reports', 'chargeforms' ), 'href' => admin_url( 'admin.php?page=chargeforms-reports' ), ] ); $wp_admin_bar->add_node( [ 'id' => 'chargeforms-bar-support', 'parent' => 'chargeforms', 'title' => esc_html__( 'Support', 'chargeforms' ), 'href' => admin_url( 'admin.php?page=chargeforms-support' ), ] ); } public function print_badge_styles() { wp_add_inline_style( 'admin-bar', ' .chargeforms-menu-badge { display: inline-block; background: #6c4ef5; color: #fff; font-size: 11px; font-weight: 600; line-height: 1; padding: 2px 6px; border-radius: 999px; vertical-align: middle; } #wpadminbar .chargeforms-menu-badge { margin-left: 2px; } ' ); } public function enqueue_assets( $hook ) { $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( strpos( $page, 'chargeforms' ) !== 0 ) { return; } // User request: Poppins across the whole admin UI. Real, openly- // licensed Google Font, loaded only on ChargeForms screens (same // conditional this whole method already gates on) so it never // touches any other wp-admin page. ChargeForms Bookings' own admin // screens pick this up automatically too — they share this same // 'chargeforms' page-slug prefix and this plugin's --fc-font token // (see admin.css), rather than declaring a second, separate font. // // Off by default (WordPress.org guidelines: no remote call without // explicit opt-in) — Global Settings > General has the toggle; the // admin UI falls back to its own font stack in admin.css when this // is off, so nothing breaks visually, it's just not Poppins. $admin_css_deps = []; if ( \ChargeForms\Includes\Settings::get( 'google_fonts_enabled' ) ) { wp_enqueue_style( 'chargeforms-google-font', 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap', [], null // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- vendor-hosted Google Fonts stylesheet, versioned by Google itself. ); $admin_css_deps[] = 'chargeforms-google-font'; } wp_enqueue_style( 'chargeforms-admin', CHARGEFORMS_URL . 'assets/css/admin.css', $admin_css_deps, CHARGEFORMS_VERSION ); wp_enqueue_style( 'chargeforms-design-tokens-refine', CHARGEFORMS_URL . 'assets/css/design-tokens-refine.css', [ 'chargeforms-admin' ], CHARGEFORMS_VERSION ); // Per-screen handoff locks (Entries/Reports/Payments/New Form/…). wp_enqueue_style( 'chargeforms-design-tokens-screens', CHARGEFORMS_URL . 'assets/css/design-tokens-screens.css', [ 'chargeforms-design-tokens-refine' ], CHARGEFORMS_VERSION ); // Global Settings-only refinement layer. User direction 2026-09-02: // color stays violet, but the control/card anatomy should match the // reference product's own UI/UX exactly — so this stylesheet loads // after the shared design layers, only on Global Settings. if ( 'chargeforms-settings' === $page ) { wp_enqueue_style( 'chargeforms-settings-refine', CHARGEFORMS_URL . 'assets/css/settings-refine.css', [ 'chargeforms-design-tokens-screens' ], CHARGEFORMS_VERSION ); } // Forms list-only refinement layer. Same user direction (2026-09-02) // as the Global Settings skin: the reference product's anatomy, // ChargeForms violet accents. Loads after the shared design layers // on page=chargeforms only; the skin's body-class scope // (class-forms-list-page.php) keeps it off other screens. if ( 'chargeforms' === $page ) { wp_enqueue_style( 'chargeforms-forms-list-refine', CHARGEFORMS_URL . 'assets/css/forms-list-refine.css', [ 'chargeforms-design-tokens-screens' ], CHARGEFORMS_VERSION ); } wp_enqueue_script( 'chargeforms-admin', CHARGEFORMS_URL . 'assets/js/admin.js', [], CHARGEFORMS_VERSION, true ); wp_localize_script( 'chargeforms-admin', 'ChargeFormsGlobalConfig', [ 'restUrl' => esc_url_raw( rest_url( 'chargeforms/v1' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'adminUrl' => esc_url_raw( admin_url() ), ] ); $this->enqueue_vue_screen( 'chargeforms', 'forms-list', 'chargeforms-vue-forms-list' ); if ( 'chargeforms-builder' !== $page ) { return; } $asset_file = CHARGEFORMS_PATH . 'assets/build/builder.asset.php'; $asset = file_exists( $asset_file ) ? include $asset_file : [ 'dependencies' => [ 'wp-element', 'wp-api-fetch' ], 'version' => CHARGEFORMS_VERSION ]; wp_enqueue_script( 'chargeforms-builder', CHARGEFORMS_URL . 'assets/build/builder.js', $asset['dependencies'], $asset['version'], true ); $tokens_path = CHARGEFORMS_PATH . 'assets/build/builder.css'; if ( file_exists( $tokens_path ) ) { wp_enqueue_style( 'chargeforms-builder-tokens', CHARGEFORMS_URL . 'assets/build/builder.css', [ 'chargeforms-admin' ], $asset['version'] ); } $style_path = CHARGEFORMS_PATH . 'assets/build/style-builder.css'; if ( file_exists( $style_path ) ) { wp_enqueue_style( 'chargeforms-builder', CHARGEFORMS_URL . 'assets/build/style-builder.css', [ 'chargeforms-builder-tokens', 'chargeforms-admin' ], $asset['version'] ); } wp_enqueue_style( 'chargeforms-design-tokens-refine', CHARGEFORMS_URL . 'assets/css/design-tokens-refine.css', [ 'chargeforms-admin', 'chargeforms-builder', 'chargeforms-builder-tokens' ], CHARGEFORMS_VERSION ); wp_enqueue_style( 'chargeforms-design-tokens-screens', CHARGEFORMS_URL . 'assets/css/design-tokens-screens.css', [ 'chargeforms-design-tokens-refine' ], CHARGEFORMS_VERSION ); $form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended wp_localize_script( 'chargeforms-builder', 'ChargeFormsConfig', [ 'restUrl' => esc_url_raw( rest_url( 'chargeforms/v1' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'adminUrl' => esc_url_raw( admin_url() ), 'formId' => $form_id ?: null, 'previewBaseUrl' => $form_id ? esc_url_raw( wp_nonce_url( admin_url( 'admin-post.php?action=chargeforms_preview_form&form_id=' . $form_id ), 'chargeforms_preview_' . $form_id ) ) : '', 'globalRequiredMessage' => \ChargeForms\Includes\Settings::get( 'validation_msg_required', __( 'This field is required', 'chargeforms' ) ), 'globalEmailMessage' => \ChargeForms\Includes\Settings::get( 'validation_msg_email', __( 'This field must contain a valid email', 'chargeforms' ) ), 'globalNumericMessage' => \ChargeForms\Includes\Settings::get( 'validation_msg_numeric', __( 'This field must contain numeric value', 'chargeforms' ) ), 'globalUrlMessage' => \ChargeForms\Includes\Settings::get( 'validation_msg_url', __( 'This field must contain a valid url', 'chargeforms' ) ), ] ); } private function enqueue_vue_screen( $target_page, $entry, $handle ) { $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( $target_page !== $page ) { return; } // Vue is opt-in now: the Forms screen renders legacy by default (see // Forms_List_Page::render_mode()), and the Vue entry no-ops when its // mount div is never printed. Enqueuing the bundle unconditionally // shipped ~1.3 MB of unused JS/CSS on every Forms page load. if ( ! isset( $_GET['chargeforms_ui'] ) || 'vue' !== $_GET['chargeforms_ui'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $js = CHARGEFORMS_PATH . "assets/build/vue/{$entry}.js"; $css = CHARGEFORMS_PATH . "assets/build/vue/{$entry}.css"; if ( ! file_exists( $js ) ) { return; } wp_enqueue_script( "chargeforms-vue-{$entry}", CHARGEFORMS_URL . "assets/build/vue/{$entry}.js", [], CHARGEFORMS_VERSION, true ); if ( file_exists( $css ) ) { wp_enqueue_style( "chargeforms-vue-{$entry}", CHARGEFORMS_URL . "assets/build/vue/{$entry}.css", [], CHARGEFORMS_VERSION ); } wp_localize_script( "chargeforms-vue-{$entry}", 'ChargeFormsVueConfig', [ 'restUrl' => esc_url_raw( rest_url( 'chargeforms/v1' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'adminUrl' => esc_url_raw( admin_url() ), ] ); } }