ed. */ public function add_feature_definition( $features_controller ) { $definition = array( 'option_key' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'is_experimental' => false, 'enabled_by_default' => false, 'order' => 50, 'setting' => $this->get_hpos_setting_for_feature(), 'plugins_are_incompatible_by_default' => true, 'additional_settings' => array( $this->get_hpos_setting_for_sync(), ), ); $features_controller->add_feature_definition( 'custom_order_tables', __( 'High-Performance order storage', 'woocommerce' ), $definition ); } /** * Returns the HPOS setting for rendering HPOS vs Post setting block in Features section of the settings page. * * @return array Feature setting object. */ private function get_hpos_setting_for_feature() { if ( 'yes' === get_transient( 'wc_installing' ) ) { return array(); } $get_value = function () { return $this->custom_orders_table_usage_is_enabled() ? 'yes' : 'no'; }; /** * ⚠️The FeaturesController instance must only be accessed from within the callback functions. Otherwise it * gets called while it's still being instantiated and creates and endless loop. */ $get_desc = function () { $plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); return $this->plugin_util->generate_incompatible_plugin_feature_warning( 'custom_order_tables', $plugin_compatibility ); }; $get_disabled = function () { $compatibility_info = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); $sync_complete = 0 === $this->data_synchronizer->get_current_orders_pending_sync_count(); $disabled = array(); // Changing something here? You might also want to look at `enable|disable` functions in Automattic\WooCommerce\Database\Migrations\CustomOrderTable\CLIRunner. $incompatible_plugins = $this->plugin_util->get_items_considered_incompatible( 'custom_order_tables', $compatibility_info ); $incompatible_plugins = array_diff( $incompatible_plugins, $this->plugin_util->get_plugins_excluded_from_compatibility_ui() ); if ( count( $incompatible_plugins ) > 0 ) { $disabled = array( 'yes' ); } if ( ! $sync_complete && ! $this->changing_data_source_with_sync_pending_is_allowed() ) { $disabled = array( 'yes', 'no' ); } return $disabled; }; return array( 'id' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'title' => __( 'Order data storage', 'woocommerce' ), 'type' => 'radio', 'options' => array( 'no' => __( 'WordPress posts storage (legacy)', 'woocommerce' ), 'yes' => __( 'High-performance order storage (recommended)', 'woocommerce' ), ), 'value' => $get_value, 'disabled' => $get_disabled, 'desc' => $get_desc, 'desc_at_end' => true, 'row_class' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, ); } /** * Returns the setting for rendering sync enabling setting block in Features section of the settings page. * * @return array Feature setting object. */ private function get_hpos_setting_for_sync() { if ( 'yes' === get_transient( 'wc_installing' ) ) { return array(); } $get_value = function () { return get_option( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION ); }; $get_sync_message = function () { $orders_pending_sync_count = $this->data_synchronizer->get_current_orders_pending_sync_count( true ); $sync_in_progress = $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) ); $sync_enabled = $this->data_synchronizer->data_sync_is_enabled(); $sync_is_pending = $orders_pending_sync_count > 0; $sync_message = array(); $is_dangerous = $sync_is_pending && $this->changing_data_source_with_sync_pending_is_allowed(); if ( $is_dangerous ) { $sync_message[] = wp_kses_data( sprintf( // translators: %s: number of pending orders. _n( "There's %s order pending sync.", 'There are %s orders pending sync.', $orders_pending_sync_count, 'woocommerce' ), number_format_i18n( $orders_pending_sync_count ), ) . ' ' . '' . __( 'Switching data storage while sync is incomplete is dangerous and can lead to order data corruption or loss!', 'woocommerce' ) . '' ); } if ( ! $sync_enabled && $this->data_synchronizer->background_sync_is_enabled() ) { $sync_message[] = __( 'Background sync is enabled.', 'woocommerce' ); } if ( $sync_in_progress && $sync_is_pending ) { $sync_message[] = sprintf( // translators: %s: number of pending orders. __( 'Currently syncing orders... %s pending', 'woocommerce' ), number_format_i18n( $orders_pending_sync_count ) ); if ( ! $sync_enabled ) { $stop_sync_url = wp_nonce_url( add_query_arg( array( self::STOP_SYNC_QUERY_ARG => true, ), wc_get_container()->get( FeaturesController::class )->get_features_page_url() ), 'hpos-stop-sync' ); $sync_message[] = sprintf( '%2$s', esc_url( $stop_sync_url ), __( 'Stop sync', 'woocommerce' ) ); } } elseif ( $sync_is_pending ) { $sync_now_url = wp_nonce_url( add_query_arg( array( self::SYNC_QUERY_ARG => true, ), wc_get_container()->get( FeaturesController::class )->get_features_page_url() ), 'hpos-sync-now' ); if ( ! $is_dangerous ) { $sync_message[] = wp_kses_data( sprintf( // translators: %s: number of pending orders. _n( "You can switch order data storage only when the posts and orders tables are in sync. There's currently %s order out of sync.", 'You can switch order data storage only when the posts and orders tables are in sync. There are currently %s orders out of sync. ', $orders_pending_sync_count, 'woocommerce' ), number_format_i18n( $orders_pending_sync_count ) ) ); } $sync_message[] = sprintf( '%2$s', esc_url( $sync_now_url ), __( 'Sync orders now', 'woocommerce' ) ); } return implode( '
', $sync_message ); }; $get_description_is_error = function () { $sync_is_pending = $this->data_synchronizer->get_current_orders_pending_sync_count( true ) > 0; return $sync_is_pending && $this->changing_data_source_with_sync_pending_is_allowed(); }; return array( 'id' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION, 'title' => '', 'type' => 'checkbox', 'desc' => __( 'Enable compatibility mode (Synchronize orders between High-performance order storage and WordPress posts storage).', 'woocommerce' ), 'value' => $get_value, 'desc_tip' => $get_sync_message, 'description_is_error' => $get_description_is_error, 'row_class' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION, ); } /** * Returns a value indicating if changing the authoritative data source for orders while there are orders pending synchronization is allowed. * * @return bool */ private function changing_data_source_with_sync_pending_is_allowed(): bool { /** * Filter to allow changing where order data is stored, even when there are orders pending synchronization. * * DANGER! This filter is intended for usage when doing manual and automated testing in development environments only, * it should NEVER be used in production environments. Order data corruption or loss can happen! * * @param bool $allow True to allow changing order storage when there are orders pending synchronization, false to disallow. * @returns bool * * @since 8.3.0 */ return apply_filters( 'wc_allow_changing_orders_storage_while_sync_is_pending', false ); } /** * Rewrites post edit links for HPOS placeholder posts so that they go to the HPOS order itself. * Hooked onto `get_edit_post_link`. * * @since 9.0.0 * * @param string $link The edit link. * @param int $post_id Post ID. * @return string * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function maybe_rewrite_order_edit_link( $link, $post_id ) { if ( DataSynchronizer::PLACEHOLDER_ORDER_POST_TYPE === get_post_type( $post_id ) ) { $link = OrderUtil::get_order_admin_edit_url( $post_id ); } return $link; } /** * Set the `order_objects` cache group as non-persistent if Custom Order data caching is enabled. * * With order datastore cache enabled, caching of raw data is now handled by the datastore, rather than full object * being stored in persistent cache. * * @return void */ public function maybe_set_order_cache_group_as_non_persistent() { if ( OrderUtil::custom_orders_table_datastore_cache_enabled() ) { // If we're using datastore cache, we don't want to persist the order objects in cache. It should be in-memory only. wp_cache_add_non_persistent_groups( array( $this->order_cache->get_object_type() ) ); } } }