__( 'Affiliate Boxes', 'affistyle' ), 'singular_name' => __( 'Affiliate Box', 'affistyle' ), 'menu_name' => __( 'AffiStyle', 'affistyle' ), 'add_new' => __( 'Add New Box', 'affistyle' ), 'add_new_item' => __( 'Add New Affiliate Box', 'affistyle' ), 'edit_item' => __( 'Edit Affiliate Box', 'affistyle' ), 'new_item' => __( 'New Affiliate Box', 'affistyle' ), 'view_item' => __( 'View Affiliate Box', 'affistyle' ), 'search_items' => __( 'Search Affiliate Boxes', 'affistyle' ), 'not_found' => __( 'No affiliate boxes found', 'affistyle' ), 'not_found_in_trash' => __( 'No affiliate boxes found in Trash', 'affistyle' ), ); $args = array( 'labels' => $labels, 'public' => false, 'show_ui' => true, 'show_in_menu' => true, 'menu_icon' => 'dashicons-index-card', 'capability_type' => 'post', 'hierarchical' => false, 'supports' => array( 'title' ), 'show_in_rest' => true, // Enables Gutenberg and REST API support. ); register_post_type( 'affistyle_box', $args ); } /** * Detect active admin post type with comprehensive fallbacks. * * @return string */ public function get_current_admin_post_type() { global $typenow, $post_type, $post; $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! empty( $screen->post_type ) ) { return (string) $screen->post_type; } if ( ! empty( $typenow ) ) { return (string) $typenow; } if ( ! empty( $post_type ) ) { return (string) $post_type; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return sanitize_key( wp_unslash( $_GET['post_type'] ) ); } if ( isset( $post ) && isset( $post->post_type ) ) { return (string) $post->post_type; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return (string) get_post_type( absint( $_GET['post'] ) ); } return ''; } /** * Add body classes on affistyle admin screens to support clean scoped CSS styling. * * @param string $classes Space-separated body classes. * @return string */ public function filter_admin_body_class( $classes ) { $current_type = $this->get_current_admin_post_type(); if ( in_array( $current_type, array( 'affistyle_box', 'aff_box' ), true ) ) { $classes .= ' post-type-affistyle_box affistyle-builder-active'; } return $classes; } /** * Enqueue builder assets (CSS and JS) with cache-busting timestamp versioning. * * @param int $box_id Box ID. */ public function enqueue_builder_assets( $box_id = 0 ) { $css_file = AFFISTYLE_PATH . 'assets/css/admin-style.css'; $front_file = AFFISTYLE_PATH . 'assets/css/affistyle-frontend.css'; $js_file = AFFISTYLE_PATH . 'assets/js/admin-builder.js'; $css_ver = file_exists( $css_file ) ? (string) filemtime( $css_file ) : AFFISTYLE_VERSION; $front_ver = file_exists( $front_file ) ? (string) filemtime( $front_file ) : AFFISTYLE_VERSION; $js_ver = file_exists( $js_file ) ? (string) filemtime( $js_file ) : AFFISTYLE_VERSION; $available_styles = function_exists( 'affistyle_get_available_styles' ) ? affistyle_get_available_styles() : array( 'style-1' => __( 'Style 1 - Horizontal Showcase', 'affistyle' ), 'style-2' => __( 'Style 2 - Vertical Minimalist', 'affistyle' ), ); wp_enqueue_media(); wp_enqueue_style( 'affistyle-admin-css', AFFISTYLE_URL . 'assets/css/admin-style.css', array(), $css_ver ); wp_enqueue_style( 'affistyle-block-frontend-css', AFFISTYLE_URL . 'assets/css/affistyle-frontend.css', array(), $front_ver ); wp_enqueue_script( 'affistyle-admin-js', AFFISTYLE_URL . 'assets/js/admin-builder.js', array( 'jquery' ), $js_ver, array( 'strategy' => 'defer', 'in_footer' => true, ) ); wp_localize_script( 'affistyle-admin-js', 'affistyleAdminData', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'fetch_nonce' => wp_create_nonce( 'affistyle_fetch_product_nonce' ), 'preview_nonce' => wp_create_nonce( 'affistyle_preview_nonce' ), 'box_id' => absint( $box_id ), 'style_labels' => $available_styles, 'i18n' => array( 'fetching' => __( 'Fetching product details...', 'affistyle' ), 'autoFill' => __( 'Auto-Fill Details', 'affistyle' ), 'success' => __( '✓ Product details populated successfully!', 'affistyle' ), 'enterUrl' => __( 'Please enter a valid Amazon product URL.', 'affistyle' ), 'errorGeneric' => __( 'Failed to fetch product data. Please enter details manually.', 'affistyle' ), 'previewSync' => __( 'Live Sync Active', 'affistyle' ), 'previewUpdate' => __( 'Updating live preview...', 'affistyle' ), ), ) ); } /** * Enqueue media uploader and admin styles on AffiStyle editing and list screens. * * @param string $hook Admin page hook. */ public function enqueue_admin_assets( $hook ) { $current_type = $this->get_current_admin_post_type(); if ( ! in_array( $current_type, array( 'affistyle_box', 'aff_box' ), true ) ) { return; } if ( 'edit.php' === $hook ) { $css_file = AFFISTYLE_PATH . 'assets/css/admin-style.css'; $front_file = AFFISTYLE_PATH . 'assets/css/affistyle-frontend.css'; $list_file = AFFISTYLE_PATH . 'assets/js/admin-list-preview.js'; $css_ver = file_exists( $css_file ) ? (string) filemtime( $css_file ) : AFFISTYLE_VERSION; $front_ver = file_exists( $front_file ) ? (string) filemtime( $front_file ) : AFFISTYLE_VERSION; $list_ver = file_exists( $list_file ) ? (string) filemtime( $list_file ) : AFFISTYLE_VERSION; wp_enqueue_style( 'affistyle-admin-css', AFFISTYLE_URL . 'assets/css/admin-style.css', array(), $css_ver ); wp_enqueue_style( 'affistyle-block-frontend-css', AFFISTYLE_URL . 'assets/css/affistyle-frontend.css', array(), $front_ver ); wp_enqueue_script( 'affistyle-admin-preview-js', AFFISTYLE_URL . 'assets/js/admin-list-preview.js', array( 'jquery' ), $list_ver, array( 'strategy' => 'defer', 'in_footer' => true, ) ); wp_localize_script( 'affistyle-admin-preview-js', 'affistyleListPreviewData', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'new_card_url' => admin_url( 'post-new.php?post_type=affistyle_box' ), 'preview_nonce' => wp_create_nonce( 'affistyle_preview_nonce' ), 'i18n' => array( 'copied' => __( 'Copied!', 'affistyle' ), 'copy' => __( 'Copy', 'affistyle' ), 'copyShortcode' => __( 'Copy Shortcode', 'affistyle' ), 'loading' => __( 'Rendering affiliate card preview...', 'affistyle' ), 'error' => __( 'Failed to load preview.', 'affistyle' ), 'emptyTitle' => __( 'No affiliate cards found', 'affistyle' ), 'emptyDesc' => __( 'Create your first high-converting affiliate product card to boost click-through rates and affiliate revenue.', 'affistyle' ), 'addNewCard' => __( 'Add New Card', 'affistyle' ), ), ) ); return; } if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) { return; } global $post; $box_id = ( isset( $post ) && isset( $post->ID ) ) ? absint( $post->ID ) : 0; $this->enqueue_builder_assets( $box_id ); } /** * Render top SaaS app header on Cards list table screen. */ public function render_cards_list_header() { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || ! in_array( $screen->id, array( 'edit-affistyle_box', 'edit-aff_box' ), true ) ) { return; } $affistyle_active_tab = 'cards'; $affistyle_header_view = AFFISTYLE_PATH . 'admin/views/app-header.php'; if ( file_exists( $affistyle_header_view ) ) { echo '
'; include $affistyle_header_view; echo '
'; } } /** * Add Metaboxes to the CPT screen. */ public function add_affistyle_box_metaboxes() { add_meta_box( 'affistyle_card_details', __( 'Affiliate Card Builder Details', 'affistyle' ), array( $this, 'render_metabox_content' ), array( 'affistyle_box', 'aff_box' ), 'normal', 'high' ); } /** * Remove the default WordPress Publish metabox to reclaim full canvas width. * * @param string $post_type Current post type. */ public function remove_default_publish_metabox( $post_type ) { if ( in_array( $post_type, array( 'affistyle_box', 'aff_box' ), true ) ) { remove_meta_box( 'submitdiv', $post_type, 'side' ); remove_meta_box( 'submitdiv', $post_type, 'normal' ); remove_meta_box( 'submitdiv', $post_type, 'advanced' ); } } /** * Filter screen layout columns for affistyle_box post type to force single column workspace. * * @param array $columns Screen columns. * @param string|mixed $screen_id Screen identifier or screen object. * @return array */ public function filter_screen_layout_columns( $columns, $screen_id ) { $target_id = is_object( $screen_id ) && isset( $screen_id->id ) ? $screen_id->id : (string) $screen_id; if ( in_array( $target_id, array( 'affistyle_box', 'aff_box' ), true ) ) { $columns[ $target_id ] = 1; } return $columns; } /** * Force 1 column layout for affistyle_box editor. * * @return int */ public function force_1_column_layout() { return 1; } /** * Render the HTML form inside the metabox. * * @param WP_Post $post Current post object. */ public function render_metabox_content( $post ) { // Ensure builder styles and scripts are unconditionally enqueued. if ( ! wp_style_is( 'affistyle-admin-css', 'enqueued' ) ) { $this->enqueue_builder_assets( $post->ID ); } // Unified single security nonce token matching save handler. wp_nonce_field( 'affistyle_save_box_meta', 'affistyle_box_nonce' ); // Retrieve existing values for Product 1 base fields. $template_style = get_post_meta( $post->ID, '_affistyle_template_style', true ); if ( empty( $template_style ) ) { $template_style = get_post_meta( $post->ID, '_affistyle_style', true ); } $title_1 = get_post_meta( $post->ID, '_affistyle_title_1', true ); $image_1 = get_post_meta( $post->ID, '_affistyle_image_1', true ); if ( empty( $image_1 ) ) { $image_1 = get_post_meta( $post->ID, '_affistyle_image_url', true ); } $description = get_post_meta( $post->ID, '_affistyle_description', true ); $destination = get_post_meta( $post->ID, '_affistyle_destination', true ); if ( empty( $destination ) ) { $destination = get_post_meta( $post->ID, '_affistyle_url_default', true ); } $button_text = get_post_meta( $post->ID, '_affistyle_button_text', true ); $badge_text = get_post_meta( $post->ID, '_affistyle_badge_text', true ); $bg_color = get_post_meta( $post->ID, '_affistyle_bg_color', true ); $btn_color = get_post_meta( $post->ID, '_affistyle_btn_color', true ); $pros = get_post_meta( $post->ID, '_affistyle_pros', true ); $cons = get_post_meta( $post->ID, '_affistyle_cons', true ); $price_1 = get_post_meta( $post->ID, '_affistyle_price_1', true ); if ( empty( $price_1 ) ) { $price_1 = get_post_meta( $post->ID, '_affistyle_price', true ); } $amazon_asin = get_post_meta( $post->ID, '_affistyle_amazon_asin', true ); $rating_score = get_post_meta( $post->ID, '_affistyle_rating_score', true ); $review_count = get_post_meta( $post->ID, '_affistyle_review_count', true ); $show_rating_meta = get_post_meta( $post->ID, '_affistyle_show_rating', true ); $show_rating = ( '' === $show_rating_meta ) ? true : ( '1' === (string) $show_rating_meta ); // Fallbacks. if ( empty( $button_text ) ) { $button_text = __( 'Claim Deal', 'affistyle' ); } if ( empty( $bg_color ) ) { $bg_color = '#ffffff'; } if ( empty( $btn_color ) ) { $btn_color = '#2563eb'; } // Strictly load local metabox-form.php view file. $view_file = AFFISTYLE_PATH . 'admin/views/metabox-form.php'; if ( file_exists( $view_file ) ) { include $view_file; } elseif ( class_exists( 'AffiStyle_Admin_Controller' ) && method_exists( 'AffiStyle_Admin_Controller', 'render_metabox_view' ) ) { AffiStyle_Admin_Controller::render_metabox_view( $post ); } } /** * Save Metabox Data securely for Product 1 Base Fields. * * @param int $post_id Post ID. */ public function save_affistyle_box_meta( $post_id ) { // 1. Verify Nonce. if ( ! isset( $_POST['affistyle_box_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['affistyle_box_nonce'] ) ), 'affistyle_save_box_meta' ) ) { return; } // 2. Prevent autosaves and revisions from overriding meta. if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } // 3. Verify target post type and user edit permissions. $post_type = get_post_type( $post_id ); if ( 'affistyle_box' !== $post_type && 'aff_box' !== $post_type ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } // 4. Save Template Style. if ( isset( $_POST['affistyle_template_style'] ) ) { $template_style = sanitize_text_field( wp_unslash( $_POST['affistyle_template_style'] ) ); $available_styles = function_exists( 'affistyle_get_available_styles' ) ? affistyle_get_available_styles() : array(); if ( ! empty( $template_style ) && ! isset( $available_styles[ $template_style ] ) ) { if ( ! preg_match( '/^style-[0-9]+$/', $template_style ) ) { $template_style = ''; } } update_post_meta( $post_id, '_affistyle_template_style', $template_style ); update_post_meta( $post_id, '_affistyle_style', $template_style ); } // 5. Sanitize and save general card fields. if ( isset( $_POST['affistyle_description'] ) ) { update_post_meta( $post_id, '_affistyle_description', sanitize_textarea_field( wp_unslash( $_POST['affistyle_description'] ) ) ); } if ( isset( $_POST['affistyle_button_text'] ) ) { update_post_meta( $post_id, '_affistyle_button_text', sanitize_text_field( wp_unslash( $_POST['affistyle_button_text'] ) ) ); } if ( isset( $_POST['affistyle_badge_text'] ) ) { update_post_meta( $post_id, '_affistyle_badge_text', sanitize_text_field( wp_unslash( $_POST['affistyle_badge_text'] ) ) ); } if ( isset( $_POST['affistyle_bg_color'] ) ) { update_post_meta( $post_id, '_affistyle_bg_color', sanitize_hex_color( wp_unslash( $_POST['affistyle_bg_color'] ) ) ); } if ( isset( $_POST['affistyle_btn_color'] ) ) { update_post_meta( $post_id, '_affistyle_btn_color', sanitize_hex_color( wp_unslash( $_POST['affistyle_btn_color'] ) ) ); } if ( isset( $_POST['affistyle_pros'] ) ) { update_post_meta( $post_id, '_affistyle_pros', sanitize_textarea_field( wp_unslash( $_POST['affistyle_pros'] ) ) ); } if ( isset( $_POST['affistyle_cons'] ) ) { update_post_meta( $post_id, '_affistyle_cons', sanitize_textarea_field( wp_unslash( $_POST['affistyle_cons'] ) ) ); } if ( isset( $_POST['affistyle_rating_score'] ) ) { $score = floatval( wp_unslash( $_POST['affistyle_rating_score'] ) ); $score = max( 0.0, min( 5.0, $score ) ); update_post_meta( $post_id, '_affistyle_rating_score', $score ); } if ( isset( $_POST['affistyle_review_count'] ) ) { update_post_meta( $post_id, '_affistyle_review_count', sanitize_text_field( wp_unslash( $_POST['affistyle_review_count'] ) ) ); } $show_rating = isset( $_POST['affistyle_show_rating'] ) ? '1' : '0'; update_post_meta( $post_id, '_affistyle_show_rating', $show_rating ); // 6. Save Product 1 specific fields. if ( isset( $_POST['affistyle_title_1'] ) ) { update_post_meta( $post_id, '_affistyle_title_1', sanitize_text_field( wp_unslash( $_POST['affistyle_title_1'] ) ) ); } if ( isset( $_POST['affistyle_image_1'] ) ) { $img_url_1 = esc_url_raw( wp_unslash( $_POST['affistyle_image_1'] ) ); update_post_meta( $post_id, '_affistyle_image_1', $img_url_1 ); update_post_meta( $post_id, '_affistyle_image_url', $img_url_1 ); } if ( isset( $_POST['affistyle_destination'] ) ) { $sanitized_dest = esc_url_raw( wp_unslash( $_POST['affistyle_destination'] ) ); update_post_meta( $post_id, '_affistyle_destination', $sanitized_dest ); update_post_meta( $post_id, '_affistyle_url_default', $sanitized_dest ); } elseif ( isset( $_POST['affistyle_url_default'] ) ) { $sanitized_dest = esc_url_raw( wp_unslash( $_POST['affistyle_url_default'] ) ); update_post_meta( $post_id, '_affistyle_destination', $sanitized_dest ); update_post_meta( $post_id, '_affistyle_url_default', $sanitized_dest ); } // 7. Save Product 1 Price, Original Price, Deal Expiry and ASIN. if ( isset( $_POST['affistyle_price_1'] ) ) { $sanitized_price = sanitize_text_field( wp_unslash( $_POST['affistyle_price_1'] ) ); update_post_meta( $post_id, '_affistyle_price_1', $sanitized_price ); update_post_meta( $post_id, '_affistyle_price', $sanitized_price ); } if ( isset( $_POST['_affistyle_original_price'] ) ) { update_post_meta( $post_id, '_affistyle_original_price', sanitize_text_field( wp_unslash( $_POST['_affistyle_original_price'] ) ) ); } if ( isset( $_POST['_affistyle_deal_expiry'] ) ) { update_post_meta( $post_id, '_affistyle_deal_expiry', sanitize_text_field( wp_unslash( $_POST['_affistyle_deal_expiry'] ) ) ); } if ( isset( $_POST['_affistyle_amazon_compliance'] ) ) { update_post_meta( $post_id, '_affistyle_amazon_compliance', '1' ); } else { update_post_meta( $post_id, '_affistyle_amazon_compliance', '0' ); } if ( isset( $_POST['affistyle_amazon_asin'] ) ) { update_post_meta( $post_id, '_affistyle_amazon_asin', sanitize_text_field( wp_unslash( $_POST['affistyle_amazon_asin'] ) ) ); } // 7.1. Save Estimated Earnings Configuration. if ( isset( $_POST['affistyle_earning_type'] ) ) { $earning_type = sanitize_key( wp_unslash( $_POST['affistyle_earning_type'] ) ); if ( ! in_array( $earning_type, array( 'none', 'commission', 'epc' ), true ) ) { $earning_type = 'none'; } update_post_meta( $post_id, '_affistyle_earning_type', $earning_type ); } if ( isset( $_POST['affistyle_commission_rate'] ) ) { $comm_rate = floatval( wp_unslash( $_POST['affistyle_commission_rate'] ) ); $comm_rate = max( 0.0, min( 100.0, $comm_rate ) ); update_post_meta( $post_id, '_affistyle_commission_rate', $comm_rate ); } if ( isset( $_POST['affistyle_conversion_rate'] ) ) { $conv_rate = floatval( wp_unslash( $_POST['affistyle_conversion_rate'] ) ); $conv_rate = max( 0.0, min( 100.0, $conv_rate ) ); update_post_meta( $post_id, '_affistyle_conversion_rate', $conv_rate ); } if ( isset( $_POST['affistyle_estimated_epc'] ) ) { $est_epc = floatval( wp_unslash( $_POST['affistyle_estimated_epc'] ) ); $est_epc = max( 0.0, $est_epc ); update_post_meta( $post_id, '_affistyle_estimated_epc', $est_epc ); } // 8. Save Auto-Monetization Keywords (Unlimited). if ( isset( $_POST['affistyle_keywords'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Raw input is unslashed and sanitized item-by-item below. $raw_input = wp_unslash( $_POST['affistyle_keywords'] ); $raw_keywords = array(); if ( is_array( $raw_input ) ) { $raw_keywords = $raw_input; } elseif ( is_string( $raw_input ) ) { $split_keywords = preg_split( '/[\r\n,]+/', $raw_input ); if ( is_array( $split_keywords ) ) { $raw_keywords = $split_keywords; } } $clean_keywords = array(); foreach ( $raw_keywords as $raw_kw ) { $sanitized_kw = sanitize_text_field( $raw_kw ); $trimmed_kw = trim( $sanitized_kw ); if ( '' !== $trimmed_kw && ! in_array( $trimmed_kw, $clean_keywords, true ) ) { $clean_keywords[] = $trimmed_kw; } } update_post_meta( $post_id, '_affistyle_keywords', $clean_keywords ); // Invalidate compiled keyword index cache. if ( class_exists( 'AffiStyle_Auto_Linker' ) ) { AffiStyle_Auto_Linker::invalidate_cache(); } } // 9. Save Link Attributes, Secondary URL & Compliance Toggles. $new_tab = isset( $_POST['affistyle_new_tab'] ) ? '1' : '0'; update_post_meta( $post_id, '_affistyle_new_tab', $new_tab ); update_post_meta( $post_id, '_affistyle_link_target', '1' === $new_tab ? '_blank' : '_self' ); $nofollow = isset( $_POST['affistyle_nofollow'] ) ? '1' : '0'; update_post_meta( $post_id, '_affistyle_nofollow', $nofollow ); $sponsored = isset( $_POST['affistyle_sponsored'] ) ? '1' : '0'; update_post_meta( $post_id, '_affistyle_sponsored', $sponsored ); delete_post_meta( $post_id, '_affistyle_url_secondary' ); delete_post_meta( $post_id, '_affistyle_button_secondary' ); $show_price = isset( $_POST['affistyle_show_price'] ) ? '1' : '0'; update_post_meta( $post_id, '_affistyle_show_price', $show_price ); } /** * AJAX handler to scrape and fetch product data from an Amazon URL. */ public function ajax_fetch_amazon_product() { check_ajax_referer( 'affistyle_fetch_product_nonce', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'affistyle' ), ) ); } $url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : ''; if ( empty( $url ) ) { wp_send_json_error( array( 'message' => __( 'Please enter an Amazon product URL.', 'affistyle' ), ) ); } if ( ! class_exists( 'AffiStyle_Scraper' ) ) { wp_send_json_error( array( 'message' => __( 'Scraper component is not available.', 'affistyle' ), ) ); } $result = AffiStyle_Scraper::fetch_amazon_product( $url ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message(), ) ); } wp_send_json_success( $result ); } /** * Register custom columns for the AffiStyle admin list table. * * @param array $columns Existing columns. * @return array Modified columns. */ public function register_admin_columns( $columns ) { $new_columns = array(); if ( isset( $columns['cb'] ) ) { $new_columns['cb'] = $columns['cb']; } $new_columns['affistyle_thumb'] = ''; $new_columns['affistyle_card_title'] = __( 'Card Title & Details', 'affistyle' ); $new_columns['affistyle_shortcode'] = __( 'Shortcode & Actions', 'affistyle' ); if ( isset( $columns['date'] ) ) { $new_columns['date'] = $columns['date']; } return $new_columns; } /** * Set the primary column for the AffiStyle admin list table. * * @param string $default_col Default primary column. * @param string $screen Current screen ID. * @return string Primary column key. */ public function filter_primary_column( $default_col, $screen ) { if ( 'edit-affistyle_box' === $screen || 'edit-aff_box' === $screen ) { return 'affistyle_card_title'; } return $default_col; } /** * Render custom column content in the AffiStyle admin list table. * * @param string $column Column key. * @param int $post_id Post ID. */ public function render_admin_columns( $column, $post_id ) { if ( 'affistyle_thumb' === $column ) { $image = get_post_meta( $post_id, '_affistyle_image_1', true ); if ( empty( $image ) ) { $image = get_post_meta( $post_id, '_affistyle_image_url', true ); } $edit_url = get_edit_post_link( $post_id ); if ( ! empty( $image ) ) { echo ''; } else { echo ''; } return; } if ( 'affistyle_card_title' === $column ) { $raw_title = get_the_title( $post_id ); $title = ! empty( $raw_title ) ? $raw_title : __( '(no title)', 'affistyle' ); $edit_url = get_edit_post_link( $post_id ); echo '
'; echo ''; if ( current_user_can( 'edit_post', $post_id ) ) { echo '' . esc_html( $title ) . ''; } else { echo '' . esc_html( $title ) . ''; } echo ''; $style = get_post_meta( $post_id, '_affistyle_template_style', true ); $style_label = ! empty( $style ) ? strtoupper( str_replace( array( 'style-', '-' ), array( 'Style ', ' ' ), $style ) ) : 'STYLE 1'; $subtitle = get_post_meta( $post_id, '_affistyle_subtitle', true ); $price = get_post_meta( $post_id, '_affistyle_price_1', true ); if ( empty( $price ) ) { $price = get_post_meta( $post_id, '_affistyle_price', true ); } echo '
'; echo '#' . absint( $post_id ) . ''; echo '' . esc_html( $style_label ) . ''; if ( ! empty( $price ) ) { echo '' . esc_html( $price ) . ''; } if ( ! empty( $subtitle ) ) { echo '' . esc_html( $subtitle ) . ''; } echo '
'; echo '
'; return; } if ( 'affistyle_shortcode' !== $column ) { return; } $shortcode = '[affistyle_box id="' . absint( $post_id ) . '"]'; ?>
post_type, array( 'affistyle_box', 'aff_box' ), true ) ) { return $actions; } $preview_link = sprintf( '%s', absint( $post->ID ), esc_html__( '👁 Quick Preview', 'affistyle' ) ); $new_actions = array(); foreach ( $actions as $key => $action ) { if ( 'trash' === $key || 'delete' === $key ) { $new_actions['affistyle_preview'] = $preview_link; } $new_actions[ $key ] = $action; } if ( ! isset( $new_actions['affistyle_preview'] ) ) { $new_actions['affistyle_preview'] = $preview_link; } return $new_actions; } /** * Render the Quick Preview modal HTML in admin footer on edit.php. */ public function render_admin_preview_modal() { $screen = get_current_screen(); if ( ! $screen || ! in_array( $screen->post_type, array( 'affistyle_box', 'aff_box' ), true ) ) { return; } $view_file = AFFISTYLE_PATH . 'admin/views/admin-preview-modal.php'; if ( file_exists( $view_file ) ) { include $view_file; } } /** * Handle AJAX request for box preview modal. */ public function ajax_preview_box() { check_ajax_referer( 'affistyle_preview_nonce', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to preview boxes.', 'affistyle' ), ) ); } $box_id = isset( $_POST['box_id'] ) ? absint( $_POST['box_id'] ) : 0; if ( ! $box_id ) { wp_send_json_error( array( 'message' => __( 'Invalid box ID.', 'affistyle' ), ) ); } if ( ! class_exists( 'AffiStyle_Database' ) || ! class_exists( 'AffiStyle_Shortcode' ) ) { wp_send_json_error( array( 'message' => __( 'Required plugin components are unavailable.', 'affistyle' ), ) ); } $data = AffiStyle_Database::get_box_data( $box_id ); if ( ! $data ) { wp_send_json_error( array( 'message' => __( 'Box not found.', 'affistyle' ), ) ); } $shortcode_handler = new AffiStyle_Shortcode(); $html = $shortcode_handler->generate_card_html( $data ); $style_raw = ! empty( $data['template_style'] ) ? $data['template_style'] : 'style-1'; $style_name = sprintf( /* translators: %s: style number or slug */ __( 'Style: %s', 'affistyle' ), strtoupper( str_replace( '-', ' ', $style_raw ) ) ); $title = get_the_title( $box_id ); if ( empty( $title ) ) { /* translators: %d: Box ID */ $title = sprintf( __( 'Box #%d', 'affistyle' ), $box_id ); } wp_send_json_success( array( 'id' => $box_id, 'title' => $title, 'style' => $style_name, 'shortcode' => '[affistyle_box id="' . $box_id . '"]', 'edit_url' => get_edit_post_link( $box_id, 'raw' ), 'html' => $html, ) ); } /** * Handle AJAX request for builder side-by-side live card preview. */ public function ajax_live_render_preview() { check_ajax_referer( 'affistyle_preview_nonce', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to preview boxes.', 'affistyle' ), ) ); } // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $template_style = isset( $_POST['template_style'] ) ? sanitize_html_class( wp_unslash( $_POST['template_style'] ) ) : 'style-1'; if ( empty( $template_style ) ) { $template_style = 'style-1'; } $post_id = isset( $_POST['box_id'] ) ? absint( $_POST['box_id'] ) : 0; $title_1 = isset( $_POST['title_1'] ) ? sanitize_text_field( wp_unslash( $_POST['title_1'] ) ) : ''; $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : ''; $price_1 = isset( $_POST['price_1'] ) ? sanitize_text_field( wp_unslash( $_POST['price_1'] ) ) : ''; $destination = isset( $_POST['destination'] ) ? esc_url_raw( wp_unslash( $_POST['destination'] ) ) : ''; $button_text = isset( $_POST['button_text'] ) ? sanitize_text_field( wp_unslash( $_POST['button_text'] ) ) : ''; $badge_text = isset( $_POST['badge_text'] ) ? sanitize_text_field( wp_unslash( $_POST['badge_text'] ) ) : ''; $image_1 = isset( $_POST['image_1'] ) ? esc_url_raw( wp_unslash( $_POST['image_1'] ) ) : ''; $pros = isset( $_POST['pros'] ) ? sanitize_textarea_field( wp_unslash( $_POST['pros'] ) ) : ''; $cons = isset( $_POST['cons'] ) ? sanitize_textarea_field( wp_unslash( $_POST['cons'] ) ) : ''; $bg_color = isset( $_POST['bg_color'] ) ? sanitize_hex_color( wp_unslash( $_POST['bg_color'] ) ) : '#ffffff'; $btn_color = isset( $_POST['btn_color'] ) ? sanitize_hex_color( wp_unslash( $_POST['btn_color'] ) ) : '#2563eb'; $rating_score = isset( $_POST['rating_score'] ) ? floatval( wp_unslash( $_POST['rating_score'] ) ) : 0.0; $review_count = isset( $_POST['review_count'] ) ? sanitize_text_field( wp_unslash( $_POST['review_count'] ) ) : ''; $raw_show_rating = isset( $_POST['show_rating'] ) ? sanitize_text_field( wp_unslash( $_POST['show_rating'] ) ) : ''; $show_rating = ( '1' === $raw_show_rating || 'true' === $raw_show_rating ); $raw_show_price = isset( $_POST['show_price'] ) ? sanitize_text_field( wp_unslash( $_POST['show_price'] ) ) : ''; $show_price = ( '1' === $raw_show_price || 'true' === $raw_show_price ); $raw_new_tab = isset( $_POST['new_tab'] ) ? sanitize_text_field( wp_unslash( $_POST['new_tab'] ) ) : ''; $new_tab = '' === $raw_new_tab ? true : ( '1' === $raw_new_tab || 'true' === $raw_new_tab ); $live_target = $new_tab ? '_blank' : '_self'; $raw_sponsored = isset( $_POST['sponsored'] ) ? sanitize_text_field( wp_unslash( $_POST['sponsored'] ) ) : ''; $sponsored = ( '1' === $raw_sponsored || 'true' === $raw_sponsored ); $raw_show_disclosure = isset( $_POST['show_disclosure'] ) ? sanitize_text_field( wp_unslash( $_POST['show_disclosure'] ) ) : ''; $show_disclosure = '' === $raw_show_disclosure ? true : ( '1' === $raw_show_disclosure || 'true' === $raw_show_disclosure ); $disclosure_text = isset( $_POST['disclosure_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['disclosure_text'] ) ) : ''; // Clean fallback values for live preview only where required for structure, strictly preserving user inputs. if ( empty( $title_1 ) ) { $title_1 = $post_id ? get_the_title( $post_id ) : ''; if ( empty( $title_1 ) ) { $title_1 = __( 'Product Headline', 'affistyle' ); } } if ( empty( $button_text ) ) { $button_text = __( 'Claim Deal', 'affistyle' ); } if ( empty( $bg_color ) ) { $bg_color = '#ffffff'; } if ( empty( $btn_color ) ) { $btn_color = '#2563eb'; } if ( empty( $destination ) ) { $destination = '#'; } // Optional Product 2 preview fields if provided. $title_2 = isset( $_POST['title_2'] ) ? sanitize_text_field( wp_unslash( $_POST['title_2'] ) ) : ''; $price_2 = isset( $_POST['price_2'] ) ? sanitize_text_field( wp_unslash( $_POST['price_2'] ) ) : ''; $image_2 = isset( $_POST['image_2'] ) ? esc_url_raw( wp_unslash( $_POST['image_2'] ) ) : ''; $destination_2 = isset( $_POST['destination_2'] ) ? esc_url_raw( wp_unslash( $_POST['destination_2'] ) ) : '#'; $button_text_2 = isset( $_POST['button_text_2'] ) ? sanitize_text_field( wp_unslash( $_POST['button_text_2'] ) ) : __( 'View Alternative', 'affistyle' ); // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $card_data = array( 'id' => $post_id, 'template_style' => $template_style, 'title' => $title_1, 'title_1' => $title_1, 'description' => $description, 'price' => $price_1, 'price_1' => $price_1, 'show_price' => $show_price, 'destination' => $destination, 'button_text' => $button_text, 'badge_text' => $badge_text, 'image_url' => $image_1, 'image_1' => $image_1, 'pros' => $pros, 'cons' => $cons, 'bg_color' => $bg_color, 'btn_color' => $btn_color, 'rating_score' => $rating_score, 'review_count' => $review_count, 'show_rating' => $show_rating, 'show_disclosure' => $show_disclosure, 'disclosure_text' => $disclosure_text, 'sponsored' => $sponsored ? '1' : '0', 'new_tab' => $new_tab ? '1' : '0', 'is_preview' => true, ); if ( ! empty( $title_2 ) || ! empty( $price_2 ) || ! empty( $image_2 ) ) { $card_data['title_2'] = $title_2; $card_data['price_2'] = $price_2; $card_data['image_2'] = $image_2; $card_data['destination_2'] = $destination_2; $card_data['button_text_2'] = $button_text_2; } if ( ! class_exists( 'AffiStyle_Shortcode' ) ) { require_once AFFISTYLE_PATH . 'includes/class-affistyle-shortcode.php'; } $shortcode_handler = new AffiStyle_Shortcode(); $raw_enable_mr_1 = isset( $_POST['enable_multi_retailers_1'] ) ? sanitize_text_field( wp_unslash( $_POST['enable_multi_retailers_1'] ) ) : null; $enable_mr_1 = null !== $raw_enable_mr_1 ? ( '1' === $raw_enable_mr_1 || 'true' === $raw_enable_mr_1 ) : null; $raw_enable_mr_2 = isset( $_POST['enable_multi_retailers_2'] ) ? sanitize_text_field( wp_unslash( $_POST['enable_multi_retailers_2'] ) ) : null; $enable_mr_2 = null !== $raw_enable_mr_2 ? ( '1' === $raw_enable_mr_2 || 'true' === $raw_enable_mr_2 ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Array values are validated inside retailer handler. $live_retailers_1 = isset( $_POST['multi_retailers_1'] ) && is_array( $_POST['multi_retailers_1'] ) ? (array) wp_unslash( $_POST['multi_retailers_1'] ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $live_retailers_2 = isset( $_POST['multi_retailers_2'] ) && is_array( $_POST['multi_retailers_2'] ) ? (array) wp_unslash( $_POST['multi_retailers_2'] ) : null; $mr_enable_filter = function ( $enabled, $box_id, $product_index ) use ( $enable_mr_1, $enable_mr_2 ) { if ( 1 === (int) $product_index && null !== $enable_mr_1 ) { return $enable_mr_1; } if ( 2 === (int) $product_index && null !== $enable_mr_2 ) { return $enable_mr_2; } return $enabled; }; $mr_raw_filter = function ( $raw_retailers, $box_id, $product_index ) use ( $live_retailers_1, $live_retailers_2 ) { if ( 1 === (int) $product_index && null !== $live_retailers_1 ) { return $live_retailers_1; } if ( 2 === (int) $product_index && null !== $live_retailers_2 ) { return $live_retailers_2; } return $raw_retailers; }; add_filter( 'affistyle_pro_enable_multi_retailers', $mr_enable_filter, 999, 3 ); add_filter( 'affistyle_pro_raw_multi_retailers', $mr_raw_filter, 999, 3 ); $disclosure_filter = function ( $enabled ) use ( $show_disclosure ) { unset( $enabled ); return $show_disclosure; }; $text_filter = function ( $text ) use ( $disclosure_text ) { if ( ! empty( $disclosure_text ) ) { return $disclosure_text; } return $text; }; add_filter( 'affistyle_is_ftc_enabled', $disclosure_filter, 999 ); add_filter( 'affistyle_ftc_disclosure_text', $text_filter, 999 ); $target_filter = function ( $default_target ) use ( $live_target ) { unset( $default_target ); return $live_target; }; add_filter( 'affistyle_link_target', $target_filter, 999 ); $card_html = $shortcode_handler->generate_card_html( $card_data ); remove_filter( 'affistyle_link_target', $target_filter, 999 ); remove_filter( 'affistyle_is_ftc_enabled', $disclosure_filter, 999 ); remove_filter( 'affistyle_ftc_disclosure_text', $text_filter, 999 ); remove_filter( 'affistyle_pro_enable_multi_retailers', $mr_enable_filter, 999 ); remove_filter( 'affistyle_pro_raw_multi_retailers', $mr_raw_filter, 999 ); $available_styles = function_exists( 'affistyle_get_available_styles' ) ? affistyle_get_available_styles() : array(); $style_label = isset( $available_styles[ $template_style ] ) ? $available_styles[ $template_style ] : strtoupper( str_replace( '-', ' ', $template_style ) ); if ( is_array( $style_label ) && isset( $style_label['label'] ) ) { $style_label = $style_label['label']; } wp_send_json_success( array( 'html' => $card_html, 'template_style' => $template_style, 'style_label' => $style_label, ) ); } } new AffiStyle_CPT();