regeneration is already in progress. */ private function initiate_regeneration_from_tools_page() { $this->verify_tool_execution_nonce(); //phpcs:disable WordPress.Security.NonceVerification.Recommended 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(); } //phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * 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" ); } } /** * Callback to abort the regeneration process from the Status - Tools page. * * @throws \Exception The lookup table doesn't exist, or there's no regeneration process in progress to abort. */ private function abort_regeneration_from_tools_page() { $this->verify_tool_execution_nonce(); if ( ! $this->data_store->check_lookup_table_exists() ) { throw new \Exception( "Can't abort the product attribute lookup data regeneration process: lookup table doesn't exist" ); } if ( ! $this->data_store->regeneration_is_in_progress() ) { throw new \Exception( "Can't abort the product attribute lookup data regeneration process since it's not currently in progress" ); } $queue = WC()->get_instance_of( \WC_Queue::class ); $queue->cancel_all( 'woocommerce_run_product_attribute_lookup_regeneration_callback' ); $this->data_store->unset_regeneration_in_progress_flag(); $this->data_store->set_regeneration_aborted_flag(); $this->enable_or_disable_lookup_table_usage( false ); // Note that we are NOT deleting the options that track the regeneration progress (processed count, last product id to process). // This is on purpose so that the regeneration can be resumed where it stopped. } /** * Callback to resume the regeneration process from the Status - Tools page. * * @throws \Exception The lookup table doesn't exist, or a regeneration process is already in place. */ private function resume_regeneration_from_tools_page() { $this->verify_tool_execution_nonce(); if ( ! $this->data_store->check_lookup_table_exists() ) { throw new \Exception( "Can't resume the product attribute lookup data regeneration process: lookup table doesn't exist" ); } if ( $this->data_store->regeneration_is_in_progress() ) { throw new \Exception( "Can't resume the product attribute lookup data regeneration process: regeneration is already in progress" ); } $this->data_store->unset_regeneration_aborted_flag(); $this->data_store->set_regeneration_in_progress_flag(); $this->enqueue_regeneration_step_run(); } /** * Verify the validity of the nonce received when executing a tool from the Status - Tools page. * * @throws \Exception Missing or invalid nonce received. */ private function verify_tool_execution_nonce() { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput if ( ! isset( $_REQUEST['_wpnonce'] ) || wp_verify_nonce( $_REQUEST['_wpnonce'], 'debug_action' ) === false ) { throw new \Exception( 'Invalid nonce' ); } } /** * Get the name of the product attributes lookup table. * * @return string */ public function get_lookup_table_name() { return $this->lookup_table_name; } /** * Get the SQL statement that creates the product attributes lookup table, including the indices. * * @return string */ public function get_table_creation_sql() { global $wpdb; $collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : ''; return "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, INDEX is_variation_attribute_term_id (is_variation_attribute, term_id), PRIMARY KEY ( `product_or_parent_id`, `term_id`, `product_id`, `taxonomy` ) ) $collate;"; } /** * Create the primary key for the table if it doesn't exist already. * It also deletes the product_or_parent_id_term_id index if it exists, since it's now redundant. * * @return void */ public function create_table_primary_index() { $database_util = wc_get_container()->get( DatabaseUtil::class ); $database_util->create_primary_key( $this->lookup_table_name, array( 'product_or_parent_id', 'term_id', 'product_id', 'taxonomy' ) ); $database_util->drop_table_index( $this->lookup_table_name, 'product_or_parent_id_term_id' ); if ( empty( $database_util->get_index_columns( $this->lookup_table_name ) ) ) { wc_get_logger()->error( "The creation of the primary key for the {$this->lookup_table_name} table failed" ); } if ( ! empty( $database_util->get_index_columns( $this->lookup_table_name, 'product_or_parent_id_term_id' ) ) ) { wc_get_logger()->error( "Dropping the product_or_parent_id_term_id index from the {$this->lookup_table_name} table failed" ); } } /** * Run additional setup needed after a WooCommerce install or update finishes. */ private function run_woocommerce_installed_callback() { // The table must exist at this point (created via dbDelta), but we check just in case. if ( ! $this->data_store->check_lookup_table_exists() ) { return; } // If a table regeneration is in progress, leave it alone. if ( $this->data_store->regeneration_is_in_progress() ) { return; } // If the lookup table has data, or if it's empty because there are no products yet, we're good. // Otherwise (lookup table is empty but products exist) we need to initiate a regeneration if one isn't already in progress. if ( $this->data_store->lookup_table_has_data() || ! $this->get_last_existing_product_id() ) { $must_enable = get_option( 'woocommerce_attribute_lookup_enabled' ) !== 'no'; $this->finalize_regeneration( $must_enable ); } else { $this->initiate_regeneration(); } } }