hile the regeneration is in progress. * * @return bool True if there's any product at all in the database, false otherwise. */ private function initialize_table_and_data() { global $wpdb; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( ' CREATE TABLE ' . $this->lookup_table_name . '( product_id bigint(20) NOT NULL, product_or_parent_id bigint(20) NOT NULL, taxonomy varchar(32) NOT NULL, term_id bigint(20) NOT NULL, is_variation_attribute tinyint(1) NOT NULL, in_stock tinyint(1) NOT NULL ); ' ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared $last_existing_product_id = WC()->call_function( 'wc_get_products', array( 'return' => 'ids', 'limit' => 1, 'orderby' => array( 'ID' => 'DESC', ), ) ); if ( ! $last_existing_product_id ) { // No products exist, nothing to (re)generate. return false; } $this->data_store->set_regeneration_in_progress_flag(); update_option( 'woocommerce_attribute_lookup_last_product_id_to_process', current( $last_existing_product_id ) ); update_option( 'woocommerce_attribute_lookup_last_products_page_processed', 0 ); return true; } /** * Action scheduler callback, performs one regeneration step and then * schedules the next step if necessary. */ private function run_regeneration_step_callback() { if ( ! $this->data_store->regeneration_is_in_progress() ) { return; } $result = $this->do_regeneration_step(); if ( $result ) { $this->enqueue_regeneration_step_run(); } else { $this->finalize_regeneration(); } } /** * Enqueue one regeneration step in action scheduler. */ private function enqueue_regeneration_step_run() { $queue = WC()->get_instance_of( \WC_Queue::class ); $queue->schedule_single( WC()->call_function( 'time' ) + 1, 'woocommerce_run_product_attribute_lookup_regeneration_callback', array(), 'woocommerce-db-updates' ); } /** * Perform one regeneration step: grabs a chunk of products and creates * the appropriate entries for them in the lookup table. * * @return bool True if more steps need to be run, false otherwise. */ private function do_regeneration_step() { $last_products_page_processed = get_option( 'woocommerce_attribute_lookup_last_products_page_processed' ); $current_products_page = (int) $last_products_page_processed + 1; $product_ids = WC()->call_function( 'wc_get_products', array( 'limit' => self::PRODUCTS_PER_GENERATION_STEP, 'page' => $current_products_page, 'orderby' => array( 'ID' => 'ASC', ), 'return' => 'ids', ) ); if ( ! $product_ids ) { return false; } foreach ( $product_ids as $id ) { $this->data_store->create_data_for_product( $id ); } update_option( 'woocommerce_attribute_lookup_last_products_page_processed', $current_products_page ); $last_product_id_to_process = get_option( 'woocommerce_attribute_lookup_last_product_id_to_process' ); return end( $product_ids ) < $last_product_id_to_process; } /** * Cleanup/final option setup after the regeneration has been completed. */ private function finalize_regeneration() { delete_option( 'woocommerce_attribute_lookup_last_product_id_to_process' ); delete_option( 'woocommerce_attribute_lookup_last_products_page_processed' ); update_option( 'woocommerce_attribute_lookup_enabled', 'no' ); $this->data_store->unset_regeneration_in_progress_flag(); } /** * Add a 'Regenerate product attributes lookup table' entry to the Status - Tools page. * * @param array $tools_array The tool definitions array that is passed ro the woocommerce_debug_tools filter. * @return array The tools array with the entry added. */ private function add_initiate_regeneration_entry_to_tools_array( array $tools_array ) { $lookup_table_exists = $this->data_store->check_lookup_table_exists(); $generation_is_in_progress = $this->data_store->regeneration_is_in_progress(); // Regenerate table. if ( $lookup_table_exists ) { $generate_item_name = __( 'Regenerate the product attributes lookup table', 'woocommerce' ); $generate_item_desc = __( 'This tool will regenerate the product attributes lookup table data from existing product(s) data. This process may take a while.', 'woocommerce' ); $generate_item_return = __( 'Product attributes lookup table data is regenerating', 'woocommerce' ); $generate_item_button = __( 'Regenerate', 'woocommerce' ); } else { $generate_item_name = __( 'Create and fill product attributes lookup table', 'woocommerce' ); $generate_item_desc = __( 'This tool will create the product attributes lookup table data and fill it with existing products data. This process may take a while.', 'woocommerce' ); $generate_item_return = __( 'Product attributes lookup table is being filled', 'woocommerce' ); $generate_item_button = __( 'Create', 'woocommerce' ); } $entry = array( 'name' => $generate_item_name, 'desc' => $generate_item_desc, 'requires_refresh' => true, 'callback' => function() use ( $generate_item_return ) { $this->initiate_regeneration_from_tools_page(); return $generate_item_return; }, ); if ( $lookup_table_exists ) { $entry['selector'] = array( 'description' => __( 'Select a product to regenerate the data for, or leave empty for a full table regeneration:', 'woocommerce' ), 'class' => 'wc-product-search', 'search_action' => 'woocommerce_json_search_products', 'name' => 'regenerate_product_attribute_lookup_data_product_id', 'placeholder' => esc_attr__( 'Search for a product…', 'woocommerce' ), ); } if ( $generation_is_in_progress ) { $entry['button'] = sprintf( /* translators: %d: How many products have been processed so far. */ __( 'Filling in progress (%d)', 'woocommerce' ), get_option( 'woocommerce_attribute_lookup_last_products_page_processed', 0 ) * self::PRODUCTS_PER_GENERATION_STEP ); $entry['disabled'] = true; } else { $entry['button'] = $generate_item_button; } $tools_array['regenerate_product_attributes_lookup_table'] = $entry; if ( $lookup_table_exists ) { // Delete the table. $tools_array['delete_product_attributes_lookup_table'] = array( 'name' => __( 'Delete the product attributes lookup table', 'woocommerce' ), 'desc' => sprintf( '%1$s %2$s', __( 'Note:', 'woocommerce' ), __( 'This will delete the product attributes lookup table. You can create it again with the "Create and fill product attributes lookup table" tool.', 'woocommerce' ) ), 'button' => __( 'Delete', 'woocommerce' ), 'requires_refresh' => true, 'callback' => function () { $this->delete_all_attributes_lookup_data(); return __( 'Product attributes lookup table has been deleted.', 'woocommerce' ); }, ); } return $tools_array; } /** * Callback to initiate the regeneration process from the Status - Tools page. * * @throws \Exception The regeneration is already in progress. */ private function initiate_regeneration_from_tools_page() { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput if ( ! isset( $_REQUEST['_wpnonce'] ) || false === wp_verify_nonce( $_REQUEST['_wpnonce'], 'debug_action' ) ) { throw new \Exception( 'Invalid nonce' ); } if ( isset( $_REQUEST['regenerate_product_attribute_lookup_data_product_id'] ) ) { $product_id = (int) $_REQUEST['regenerate_product_attribute_lookup_data_product_id']; $this->check_can_do_lookup_table_regeneration( $product_id ); $this->data_store->create_data_for_product( $product_id ); } else { $this->check_can_do_lookup_table_regeneration(); $this->initiate_regeneration(); } } /** * Enable or disable the actual lookup table usage. * * @param bool $enable True to enable, false to disable. * @throws \Exception A lookup table regeneration is currently in progress. */ private function enable_or_disable_lookup_table_usage( $enable ) { if ( $this->data_store->regeneration_is_in_progress() ) { throw new \Exception( "Can't enable or disable the attributes lookup table usage while it's regenerating." ); } update_option( 'woocommerce_attribute_lookup_enabled', $enable ? 'yes' : 'no' ); } /** * Check if everything is good to go to perform a complete or per product lookup table data regeneration * and throw an exception if not. * * @param mixed $product_id The product id to check the regeneration viability for, or null to check if a complete regeneration is possible. * @throws \Exception Something prevents the regeneration from starting. */ private function check_can_do_lookup_table_regeneration( $product_id = null ) { if ( $product_id && ! $this->data_store->check_lookup_table_exists() ) { throw new \Exception( "Can't do product attribute lookup data regeneration: lookup table doesn't exist" ); } if ( $this->data_store->regeneration_is_in_progress() ) { throw new \Exception( "Can't do product attribute lookup data regeneration: regeneration is already in progress" ); } if ( $product_id && ! wc_get_product( $product_id ) ) { throw new \Exception( "Can't do product attribute lookup data regeneration: product doesn't exist" ); } } }