feed_service = $feed_service;
$this->quality_checker = $quality_checker;
$this->init_hooks();
}
/**
* Initialize WordPress hooks
*
* @return void
*/
private function init_hooks(): void {
// Add AJAX handlers.
add_action( 'wp_ajax_carticy_ai_checkout_preview_product_feed', array( $this, 'ajax_preview_product_feed' ) );
add_action( 'wp_ajax_carticy_ai_checkout_regenerate_feed', array( $this, 'ajax_regenerate_feed' ) );
add_action( 'wp_ajax_carticy_ai_checkout_recalculate_quality', array( $this, 'ajax_recalculate_quality' ) );
// Add meta box to product edit screen.
add_action( 'add_meta_boxes', array( $this, 'add_product_meta_box' ) );
add_action( 'save_post_product', array( $this, 'save_product_meta' ) );
// Enqueue assets.
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
/**
* Render product feed manager page
*
* @return void
*/
public function render_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Handle bulk actions.
$this->handle_bulk_actions();
// Handle single product toggle.
$this->handle_product_toggle();
// Create list table instance.
$this->list_table = new ProductsListTable( $this->quality_checker );
$this->list_table->prepare_items();
// Get statistics.
$stats = $this->get_feed_statistics();
?>
display_admin_notices(); ?>
feed_service->invalidate_cache();
// Redirect to clean URL.
wp_safe_redirect(
remove_query_arg( array( 'action', 'action2', 'product', '_wpnonce', '_wp_http_referer' ) )
);
exit;
}
/**
* Handle single product toggle
*
* @return void
*/
private function handle_product_toggle(): void {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( empty( $_GET['action'] ) || 'toggle' !== $_GET['action'] || empty( $_GET['product'] ) ) {
return;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$product_id = absint( $_GET['product'] );
// Verify nonce.
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'carticy_toggle_product_' . $product_id ) ) {
wp_die( esc_html__( 'Security check failed', 'carticy-ai-checkout-for-woocommerce' ) );
}
$current_status = get_post_meta( $product_id, '_carticy_ai_checkout_enabled', true );
$new_status = 'yes' === $current_status ? 'no' : 'yes';
update_post_meta( $product_id, '_carticy_ai_checkout_enabled', $new_status );
// Invalidate feed cache.
$this->feed_service->invalidate_cache();
// Redirect to clean URL.
wp_safe_redirect(
remove_query_arg( array( 'action', 'product', '_wpnonce' ) )
);
exit;
}
/**
* Get feed statistics
*
* @return array Statistics data.
*/
private function get_feed_statistics(): array {
global $wpdb;
// Total products.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$total_products = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish'"
);
// ChatGPT enabled products.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$chatgpt_enabled = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
WHERE p.post_type = %s AND p.post_status = %s
AND pm.meta_key = %s AND pm.meta_value = %s",
'product',
'publish',
'_carticy_ai_checkout_enabled',
'yes'
)
);
// Average quality score - only for ChatGPT-enabled products.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$avg_quality = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT AVG(CAST(pm_quality.meta_value AS UNSIGNED))
FROM {$wpdb->postmeta} pm_quality
INNER JOIN {$wpdb->posts} p ON pm_quality.post_id = p.ID
INNER JOIN {$wpdb->postmeta} pm_enabled ON p.ID = pm_enabled.post_id
WHERE p.post_type = %s AND p.post_status = %s
AND pm_quality.meta_key = %s AND pm_quality.meta_value != ''
AND pm_enabled.meta_key = %s AND pm_enabled.meta_value = %s",
'product',
'publish',
'_carticy_ai_checkout_quality_score',
'_carticy_ai_checkout_enabled',
'yes'
)
);
// Products with quality issues (score < 80) - only ChatGPT-enabled products.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$products_with_issues = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->postmeta} pm_quality
INNER JOIN {$wpdb->posts} p ON pm_quality.post_id = p.ID
INNER JOIN {$wpdb->postmeta} pm_enabled ON p.ID = pm_enabled.post_id
WHERE p.post_type = %s AND p.post_status = %s
AND pm_quality.meta_key = %s AND CAST(pm_quality.meta_value AS UNSIGNED) < 80
AND pm_enabled.meta_key = %s AND pm_enabled.meta_value = %s",
'product',
'publish',
'_carticy_ai_checkout_quality_score',
'_carticy_ai_checkout_enabled',
'yes'
)
);
// Feed last updated.
$feed_last_updated = get_option( 'carticy_ai_checkout_feed_last_updated', 0 );
return array(
'total_products' => $total_products,
'chatgpt_enabled' => $chatgpt_enabled,
'avg_quality' => $avg_quality,
'products_with_issues' => $products_with_issues,
'feed_last_updated' => $feed_last_updated,
);
}
/**
* Display admin notices
*
* @return void
*/
private function display_admin_notices(): void {
settings_errors( 'carticy_product_feed' );
}
/**
* Add meta box to product edit screen
*
* @return void
*/
public function add_product_meta_box(): void {
add_meta_box(
'carticy_chatgpt_settings',
__( 'ChatGPT Integration', 'carticy-ai-checkout-for-woocommerce' ),
array( $this, 'render_product_meta_box' ),
'product',
'side',
'default'
);
}
/**
* Render product meta box
*
* @param \WP_Post $post Post object.
* @return void
*/
public function render_product_meta_box( \WP_Post $post ): void {
wp_nonce_field( 'carticy_save_product_meta', 'carticy_product_meta_nonce' );
$enabled = get_post_meta( $post->ID, '_carticy_ai_checkout_enabled', true );
$score = $this->quality_checker->get_cached_quality_score( $post->ID );
$issues = $this->quality_checker->get_cached_quality_issues( $post->ID );
$quality_class = $score >= 80 ? 'quality-excellent' : ( $score >= 60 ? 'quality-good' : 'quality-fair' );
$issues_count = count( $issues );
?>
quality_checker->update_product_quality_cache( $post_id );
// Invalidate feed cache.
$this->feed_service->invalidate_cache();
}
/**
* AJAX handler for feed preview
*
* @return void
*/
public function ajax_preview_product_feed(): void {
check_ajax_referer( 'carticy_product_feed_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ) );
}
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;
if ( ! $product_id ) {
wp_send_json_error( array( 'message' => __( 'Invalid product ID', 'carticy-ai-checkout-for-woocommerce' ) ) );
}
$product = wc_get_product( $product_id );
if ( ! $product ) {
wp_send_json_error( array( 'message' => __( 'Product not found', 'carticy-ai-checkout-for-woocommerce' ) ) );
}
// Get single product feed data.
$feed_data = $this->feed_service->get_product_feed_data( $product );
wp_send_json_success( array( 'feed' => wp_json_encode( $feed_data, JSON_PRETTY_PRINT ) ) );
}
/**
* AJAX handler for feed regeneration
*
* @return void
*/
public function ajax_regenerate_feed(): void {
check_ajax_referer( 'carticy_product_feed_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ) );
}
$this->feed_service->invalidate_cache();
update_option( 'carticy_ai_checkout_feed_last_updated', time() );
wp_send_json_success( array( 'message' => __( 'Feed cache cleared successfully', 'carticy-ai-checkout-for-woocommerce' ) ) );
}
/**
* AJAX handler for quality score recalculation
*
* @return void
*/
public function ajax_recalculate_quality(): void {
check_ajax_referer( 'carticy_product_feed_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ) );
}
$count = $this->quality_checker->recalculate_all_quality_scores();
wp_send_json_success(
array(
'message' => sprintf(
/* translators: %d: number of products processed */
_n(
'%d product quality score recalculated.',
'%d product quality scores recalculated.',
$count,
'carticy-ai-checkout-for-woocommerce'
),
$count
),
)
);
}
/**
* Enqueue admin assets
*
* @param string $hook Current admin page hook.
* @return void
*/
public function enqueue_assets( string $hook ): void {
// Check if we're on product feed manager page OR product edit screen.
$is_feed_page = 'ai-checkout_page_carticy-ai-checkout-product-feed' === $hook;
// Check for product edit screen using get_current_screen().
$screen = get_current_screen();
$is_product_edit = ( 'post.php' === $hook || 'post-new.php' === $hook )
&& $screen
&& 'product' === $screen->post_type;
if ( ! $is_feed_page && ! $is_product_edit ) {
return;
}
// Always enqueue styles (needed for both pages).
wp_enqueue_style(
'carticy-product-manager',
plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/dist/admin-product-manager.min.css',
array(),
CARTICY_AI_CHECKOUT_VERSION
);
// Only enqueue scripts on feed manager page (not needed on product edit).
if ( ! $is_feed_page ) {
return;
}
// Enqueue scripts.
wp_enqueue_script(
'carticy-product-manager',
plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/dist/admin-product-manager.min.js',
array( 'jquery' ),
CARTICY_AI_CHECKOUT_VERSION,
true
);
// Localize script.
wp_localize_script(
'carticy-product-manager',
'carticyProductManager',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'carticy_product_feed_nonce' ),
'i18n' => array(
'confirm_regenerate' => __( 'Are you sure you want to regenerate the product feed cache?', 'carticy-ai-checkout-for-woocommerce' ),
'confirm_recalculate' => __( 'Are you sure you want to recalculate all product quality scores? This may take a few moments.', 'carticy-ai-checkout-for-woocommerce' ),
'copy' => __( 'Copy to Clipboard', 'carticy-ai-checkout-for-woocommerce' ),
'copied' => __( 'Copied to clipboard!', 'carticy-ai-checkout-for-woocommerce' ),
),
)
);
}
}