__( '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 '
';
}
}
/**
* 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();