'Invalid template ID(s)' ] ); } $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // Delete from templates table. $deleted = $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $wpdb->prepare( "DELETE FROM {$template_table} WHERE id IN ($placeholders)", //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare ...$ids ) ); // Delete from meta table. $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $wpdb->prepare( "DELETE FROM {$meta_table} WHERE email_template_id IN ($placeholders)", //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare ...$ids ) ); if ( $deleted ) { wp_send_json_success( [ 'ids' => $ids ] ); } else { wp_send_json_error( [ 'message' => __( 'Failed to delete template(s).', 'woo-cart-abandonment-recovery' ) ] ); } } /** * Clone an email template via AJAX. * * @since 2.0.0 * @return void */ public function clone_email_template(): void { $response_data = [ 'message' => $this->get_error_msg( 'permission' ) ]; if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( $response_data ); } if ( empty( $_POST ) ) { $response_data = [ 'message' => $this->get_error_msg( 'missing-data' ) ]; wp_send_json_error( $response_data ); } if ( ! check_ajax_referer( 'wcar_clone_email_template', 'security', false ) ) { $response_data = [ 'message' => $this->get_error_msg( 'nonce' ) ]; wp_send_json_error( $response_data ); } $template_id = filter_input( INPUT_POST, 'id', FILTER_VALIDATE_INT ); if ( ! $template_id ) { wp_send_json_error( [ 'message' => __( 'Invalid template ID', 'woo-cart-abandonment-recovery' ) ] ); } $email_templates = \Cartflows_Ca_Email_Templates::get_instance(); $template = $email_templates->get_template_by_id( $template_id ); $meta_data = $email_templates->get_all_email_template_meta( $template_id ); if ( empty( $template ) ) { wp_send_json_error( [ 'message' => __( 'Template not found', 'woo-cart-abandonment-recovery' ) ] ); } global $wpdb; $inserted = $wpdb->insert( $wpdb->prefix . 'cartflows_ca_email_templates', [ 'template_name' => sanitize_text_field( $template->template_name ) . __( ' - clone', 'woo-cart-abandonment-recovery' ), 'email_subject' => sanitize_text_field( $template->email_subject ), 'email_body' => $template->email_body, 'frequency' => intval( $template->frequency ), 'frequency_unit' => sanitize_text_field( $template->frequency_unit ), 'is_activated' => $template->is_activated, ], [ '%s', '%s', '%s', '%d', '%s', '%d' ] ); if ( ! $inserted ) { wp_send_json_error( [ 'message' => __( 'Failed to clone template', 'woo-cart-abandonment-recovery' ) ] ); } $new_id = $wpdb->insert_id; foreach ( $meta_data as $meta_key => $meta_value ) { $this->add_email_template_meta( $new_id, $meta_key, $meta_value ); } $new_template = (array) $email_templates->get_email_template_by_id( $new_id ); $meta = $email_templates->get_all_email_template_meta( $new_id ); wp_send_json_success( [ 'message' => __( 'Template cloned successfully', 'woo-cart-abandonment-recovery' ), 'template' => array_merge( [ 'id' => $new_template['id'], 'template_name' => $new_template['template_name'], 'email_subject' => $new_template['email_subject'], 'email_body' => $new_template['email_body'], 'is_activated' => boolval( $new_template['is_activated'] ) ? 'on' : '', 'email_frequency' => $new_template['frequency'], 'email_frequency_unit' => $new_template['frequency_unit'], ], $meta ), ] ); } /** * Get all meta key-value pairs for a template, filling missing keys with defaults. * * @param int $email_template_id The email template ID. * @return array */ public function get_all_email_template_meta( $email_template_id ) { global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->prefix}cartflows_ca_email_templates_meta WHERE email_template_id = %d", $email_template_id ), ARRAY_A ); $meta = []; foreach ( $results as $row ) { $meta[ $row['meta_key'] ] = maybe_unserialize( $row['meta_value'] ); } // Fill missing keys with defaults. $defaults = function_exists( 'wcf_ca' ) ? wcf_ca()->options->get_email_template_meta_defaults() : []; return array_merge( $defaults, $meta ); } /** * AJAX: Search WooCommerce products for ProductSearchField. * Handles both search by term and get products by comma-separated IDs. */ public function search_products() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( [ 'message' => $this->get_error_msg( 'permission' ) ] ); } $nonce = isset( $_POST['security'] ) ? sanitize_text_field( $_POST['security'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wcar_search_products' ) ) { wp_send_json_error( [ 'message' => $this->get_error_msg( 'nonce' ) ] ); } $term = isset( $_POST['term'] ) ? sanitize_text_field( wp_unslash( $_POST['term'] ) ) : ''; $product_ids = isset( $_POST['product_ids'] ) ? sanitize_text_field( wp_unslash( $_POST['product_ids'] ) ) : ''; $posts_per_page = isset( $_POST['posts_per_page'] ) ? absint( wp_unslash( $_POST['posts_per_page'] ) ) : 20; $products = []; if ( ! empty( $term ) && strlen( $term ) >= 3 ) { // Use WooCommerce's search method to search the products. $data = \WC_Data_Store::load( 'product' ); $ids = $data->search_products( $term, '', true, false, $posts_per_page ); // Get all product objects including variations. $product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' ); foreach ( $product_objects as $product_object ) { if ( ! $product_object ) { continue; } $products[] = $this->format_product_data( $product_object ); } } elseif ( ! empty( $product_ids ) ) { // Get products by IDs. $ids_array = array_filter( array_map( 'intval', explode( ',', $product_ids ) ) ); if ( ! empty( $ids_array ) ) { foreach ( $ids_array as $product_id ) { $product_object = wc_get_product( $product_id ); if ( $product_object ) { $products[] = $this->format_product_data( $product_object ); } } } } wp_send_json_success( $products ); } /** * Format product data for response. * * @param WC_Product $product_object Product object. * @return array Formatted product data. */ private function format_product_data( $product_object ) { $product_id = $product_object->get_id(); $image_id = get_post_thumbnail_id( $product_id ); $image_url = $image_id ? wp_get_attachment_image_url( $image_id, 'thumbnail' ) : ''; $managing_stock = $product_object->managing_stock(); $is_in_stock = $product_object->is_in_stock(); $product_type = $product_object->get_type(); $availibility_text = $this->get_stock_availability_text( $product_object ); $product_price_range = $this->get_formatted_product_price_range( $product_object ); // Format name for variations. $formatted_name = $product_object->get_name(); // phpcs:disable /* if ( $product_type === 'variation' ) { $formatted_name = $formatted_name . ' - ' . __( 'Variation', 'woo-cart-abandonment-recovery' ); } */ // phpcs:enable return [ 'value' => (string) $product_id, 'label' => $formatted_name, 'image' => $image_url, 'original_price' => $product_object->get_price(), 'product_name' => $product_object->get_name(), 'product_type' => $product_type, 'stock_status' => $availibility_text, 'in_stock' => $is_in_stock, 'price_range' => $product_price_range, ]; } /** * Get stock availability text for product. * * @param WC_Product $product_object Product object. * @return string Stock availability text. */ private function get_stock_availability_text( $product_object ) { if ( ! $product_object->is_in_stock() ) { return __( 'Out of stock', 'woo-cart-abandonment-recovery' ); } if ( $product_object->managing_stock() ) { $stock_quantity = $product_object->get_stock_quantity(); if ( $stock_quantity <= 0 ) { return __( 'Out of stock', 'woo-cart-abandonment-recovery' ); } return sprintf( // translators: %d: Number of items in stock. __( '%d in stock', 'woo-cart-abandonment-recovery' ), $stock_quantity ); } return __( 'In stock', 'woo-cart-abandonment-recovery' ); } /** * Get formatted product price range. * * @param WC_Product $product_object Product object. * @return string Formatted price range. */ private function get_formatted_product_price_range( $product_object ) { if ( $product_object->is_type( 'variable' ) ) { $prices = $product_object->get_variation_prices( true ); if ( ! empty( $prices['price'] ) ) { $min_price = current( $prices['price'] ); $max_price = end( $prices['price'] ); if ( $min_price !== $max_price ) { return wc_format_price_range( $min_price, $max_price ); } } } return $product_object->get_price_html(); } /** * Restore default email templates via AJAX. * * @since 2.0.0 * @return void */ public function restore_email_templates(): void { $response_data = [ 'message' => $this->get_error_msg( 'permission' ) ]; if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( $response_data ); } if ( ! check_ajax_referer( 'wcar_restore_email_templates', 'security', false ) ) { $response_data = [ 'message' => $this->get_error_msg( 'nonce' ) ]; wp_send_json_error( $response_data ); } include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/classes/class-cartflows-ca-database.php'; $db = \Cartflows_Ca_Database::get_instance(); $db->template_table_seeder( true ); global $wpdb; $templates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}cartflows_ca_email_templates ORDER BY id ASC" ); $email_templates = \Cartflows_Ca_Email_Templates::get_instance(); $formatted_templates = []; foreach ( $templates as $template ) { $meta = $email_templates->get_all_email_template_meta( $template->id ); $formatted_templates[] = array_merge( [ 'id' => $template->id, 'template_name' => $template->template_name, 'email_subject' => $template->email_subject, 'email_body' => $template->email_body, 'is_activated' => boolval( $template->is_activated ) ? 'on' : '', 'email_frequency' => $template->frequency, 'email_frequency_unit' => $template->frequency_unit, ], $meta ); } wp_send_json_success( [ 'message' => __( 'Default email templates restored successfully.', 'woo-cart-abandonment-recovery' ), 'templates' => $formatted_templates, ] ); } /** * Send preview email. * * @since 2.0.0 * @return void */ public function send_preview_email(): void { $response_data = [ 'message' => $this->get_error_msg( 'permission' ) ]; if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( $response_data ); } if ( empty( $_POST ) ) { $response_data = [ 'message' => $this->get_error_msg( 'missing-data' ) ]; wp_send_json_error( $response_data ); } /** * Nonce verification */ if ( ! check_ajax_referer( 'wcar_send_preview_email', 'security', false ) ) { $response_data = [ 'message' => $this->get_error_msg( 'nonce' ) ]; wp_send_json_error( $response_data ); } $email_schedule = \Cartflows_Ca_Email_Schedule::get_instance(); $mail_result = $email_schedule->send_email_templates( null, true ); if ( $mail_result ) { wp_send_json_success( [ 'message' => __( 'Test email sent successfully!', 'woo-cart-abandonment-recovery' ) ] ); } else { wp_send_json_error( [ 'message' => __( 'Failed to send test email', 'woo-cart-abandonment-recovery' ) ] ); } } }