ram array $taxonomies List of taxonomies to build the cache for. * @return array A dictionary of taxonomies => dictionary of term slug => term id. */ private function get_term_ids_by_slug_cache( $taxonomies ) { $result = array(); foreach ( $taxonomies as $taxonomy ) { $terms = WC()->call_function( 'get_terms', array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'fields' => 'id=>slug', ) ); $result[ $taxonomy ] = array_flip( $terms ); } return $result; } /** * Get the id of the term that defines a variation for a given taxonomy, * or null if there's no such defining id (for variations having "Any " as the definition) * * @param \WC_Product_Variation $variation The variation to get the defining term id for. * @param string $taxonomy The taxonomy to get the defining term id for. * @param array $term_ids_by_slug_cache A term ids by slug as generated by get_term_ids_by_slug_cache. * @return int|null The term id, or null if there's no defining id for that taxonomy in that variation. */ private function get_variation_definition_term_id( \WC_Product_Variation $variation, string $taxonomy, array $term_ids_by_slug_cache ) { $variation_attributes = $variation->get_attributes(); $term_slug = ArrayUtil::get_value_or_default( $variation_attributes, $taxonomy ); if ( $term_slug ) { return $term_ids_by_slug_cache[ $taxonomy ][ $term_slug ]; } else { return null; } } /** * Get the variations of a given variable product. * * @param \WC_Product_Variable $product The product to get the variations for. * @return array An array of WC_Product_Variation objects. */ private function get_variations_of( \WC_Product_Variable $product ) { $variation_ids = $product->get_children(); return array_map( function( $id ) { return WC()->call_function( 'wc_get_product', $id ); }, $variation_ids ); } /** * Check if a given product is a variable product. * * @param \WC_Product $product The product to check. * @return bool True if it's a variable product, false otherwise. */ private function is_variable_product( \WC_Product $product ) { return is_a( $product, \WC_Product_Variable::class ); } /** * Check if a given product is a variation. * * @param \WC_Product $product The product to check. * @return bool True if it's a variation, false otherwise. */ private function is_variation( \WC_Product $product ) { return is_a( $product, \WC_Product_Variation::class ); } /** * Return the list of taxonomies used for variations on a product together with * the associated term ids, with the following format: * * [ * 'taxonomy_name' => * [ * 'term_ids' => [id, id, ...], * 'used_for_variations' => true|false * ], ... * ] * * @param \WC_Product $product The product to get the attribute taxonomies for. * @return array Information about the attribute taxonomies of the product. */ private function get_attribute_taxonomies( \WC_Product $product ) { $product_attributes = $product->get_attributes(); $result = array(); foreach ( $product_attributes as $taxonomy_name => $attribute_data ) { if ( ! $attribute_data->get_id() ) { // Custom product attribute, not suitable for attribute-based filtering. continue; } $result[ $taxonomy_name ] = array( 'term_ids' => $attribute_data->get_options(), 'used_for_variations' => $attribute_data->get_variation(), ); } return $result; } /** * Insert one entry in the lookup table. * * @param int $product_id The product id. * @param int $product_or_parent_id The product id for non-variable products, the main/parent product id for variations. * @param string $taxonomy Taxonomy name. * @param int $term_id Term id. * @param bool $is_variation_attribute True if the taxonomy corresponds to an attribute used to define variations. * @param bool $has_stock True if the product is in stock. */ private function insert_lookup_table_data( int $product_id, int $product_or_parent_id, string $taxonomy, int $term_id, bool $is_variation_attribute, bool $has_stock ) { global $wpdb; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( 'INSERT INTO ' . $this->lookup_table_name . ' ( product_id, product_or_parent_id, taxonomy, term_id, is_variation_attribute, in_stock) VALUES ( %d, %d, %s, %d, %d, %d )', $product_id, $product_or_parent_id, $taxonomy, $term_id, $is_variation_attribute ? 1 : 0, $has_stock ? 1 : 0 ) ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared } /** * Handler for the woocommerce_rest_insert_product hook. * Needed to update the lookup table when the REST API batch insert/update endpoints are used. * * @param \WP_Post $product The post representing the created or updated product. * @param \WP_REST_Request $request The REST request that caused the hook to be fired. * @return void */ private function on_product_created_or_updated_via_rest_api( \WP_Post $product, \WP_REST_Request $request ): void { if ( StringUtil::ends_with( $request->get_route(), '/batch' ) ) { $this->on_product_changed( $product->ID ); } } /** * Tells if a lookup table regeneration is currently in progress. * * @return bool True if a lookup table regeneration is already in progress. */ public function regeneration_is_in_progress() { return 'yes' === get_option( 'woocommerce_attribute_lookup_regeneration_in_progress', null ); } /** * Set a permanent flag (via option) indicating that the lookup table regeneration is in process. */ public function set_regeneration_in_progress_flag() { update_option( 'woocommerce_attribute_lookup_regeneration_in_progress', 'yes' ); } /** * Remove the flag indicating that the lookup table regeneration is in process. */ public function unset_regeneration_in_progress_flag() { delete_option( 'woocommerce_attribute_lookup_regeneration_in_progress' ); } /** * Set a flag indicating that the last lookup table regeneration process started was aborted. */ public function set_regeneration_aborted_flag() { update_option( 'woocommerce_attribute_lookup_regeneration_aborted', 'yes' ); } /** * Remove the flag indicating that the last lookup table regeneration process started was aborted. */ public function unset_regeneration_aborted_flag() { delete_option( 'woocommerce_attribute_lookup_regeneration_aborted' ); } /** * Tells if the last lookup table regeneration process started was aborted * (via deleting the 'woocommerce_attribute_lookup_regeneration_in_progress' option). * * @return bool True if the last lookup table regeneration process was aborted. */ public function regeneration_was_aborted(): bool { return 'yes' === get_option( 'woocommerce_attribute_lookup_regeneration_aborted' ); } /** * Check if the lookup table contains any entry at all. * * @return bool True if the table contains entries, false if the table is empty. */ public function lookup_table_has_data(): bool { global $wpdb; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared return ( (int) $wpdb->get_var( "SELECT EXISTS (SELECT 1 FROM {$this->lookup_table_name})" ) ) !== 0; } }