�PNG  IHDR22?�� pHYs  �� OiCCPPhotoshop ICC profilexڝSgTS�=���BK���KoR RB���&*! J�!��Q�EEȠ�����Q, � ��!���������{�kּ������>���� �H3Q5� �B�������.@� $p�d!s�#�~<<+"��x� �M��0���B�\���t�8K�@z�B�@F���&S�`�cb�P-`'������{[�!�� e�Dh;��V�EX0fK�9�-0IWfH���� � 0Q��){`�##x��F�W<�+��*x��<�$9E�[-qWW.(�I+6aa�@.�y�2�4���������x����6��_-��"bb���ϫp@�t~��,/��;�m��%�h^ �u��f�@����W�p�~<�5�j>{�-�]c�K'Xt����o��(�h���w��?�G�%�fI�q^D$.Tʳ?�D��*�A��,���� �`6�B$��BB d�r`)��B(�Ͱ*`/�@4�Qh��p.�U�=p�a��(�� A�a!ڈb�X#����!�H�$ ɈQ"K�5H1R�T UH�=r9�\F��;�2����G1���Q=� �C��7�F� �dt1�����r�=�6��Ыhڏ>C�0��3�l0.��B�8, �c˱"� ���V����cϱw�E� 6wB aAHXLXN�H� $4� 7 �Q�'"��K�&���b21�XH,#��/{�C�7$�C2'��I��T��F�nR#�,��4H#���dk�9�, +ȅ����3��!�[ �b@q��S�(R�jJ��4�e�2AU��Rݨ�T5�ZB���R�Q��4u�9̓IK�����hh�i��t�ݕN��W���G���w ��Ljg(�gw��L�Ӌ�T071���oUX*�*|�� �J�&�*/T����ު U�U�T��^S}�FU3S� Ԗ�U��P�SSg�;���g�oT?�~Y��Y�L�OC�Q��_�� c�x,!k ��u�5�&���|v*�����=���9C3J3W�R�f?�q��tN �(���~���)�)�4L�1e\k����X�H�Q�G�6������E�Y��A�J'\'Gg����S�Sݧ �M=:��.�k���Dw�n��^��Lo��y���}/�T�m���G X� $� �<�5qo</���QC]�@C�a�a�ᄑ��<��F�F�i�\�$�m�mƣ&&!&KM�M�RM��)�;L;L���͢�֙5�=1�2��כ߷`ZxZ,����eI��Z�Yn�Z9Y�XUZ]�F���%ֻ�����N�N���gð�ɶ�����ۮ�m�}agbg�Ů��}�}��= ���Z~s�r:V:ޚΜ�?}����/gX���3��)�i�S��Ggg�s�󈋉K��.�>.���Ƚ�Jt�q]�z���������ۯ�6�i�ܟ�4�)�Y3s���C�Q��? ��0k߬~OCO�g��#/c/�W�װ��w��a�>�>r��>�<7�2�Y_�7��ȷ�O�o�_��C#�d�z����%g��A�[��z|!��?:�e����A���AA�������!h�쐭!��Α�i�P~���a�a��~ '���W�?�p�X�1�5w��Cs�D�D�Dޛg1O9�-J5*>�.j<�7�4�?�.fY��X�XIlK9.*�6nl������� �{�/�]py�����.,:�@L�N8��A*��%�w%� y��g"/�6ш�C\*N�H*Mz�쑼5y$�3�,幄'���L Lݛ:��v m2=:�1����qB�!M��g�g�fvˬe����n��/��k���Y- �B��TZ(�*�geWf�͉�9���+��̳�ې7�����ᒶ��KW-X潬j9�������(�x��oʿ�ܔ���Ĺd�f�f���-�[����n �ڴ �V����E�/��(ۻ��C���<��e����;?T�T�T�T6��ݵa��n��{��4���[���>ɾ�UUM�f�e�I���?�������m]�Nmq����#�׹���=TR��+�G�����w- 6 U����#pDy��� �� :�v�{���vg/jB��F�S��[b[�O�>����z�G��499�?r����C�d�&����ˮ/~�����јѡ�򗓿m|������������x31^�V���w�w��O�| (�h���SЧ��������c3-� cHRMz%������u0�`:�o�_�F5IDATx��ytUս�?�;���fN$$��@B�!�k��� -*�gE���j�O�kQ�ʠ`U�B_ �AÐ0%�Ȕ�@r3ޛ;߳��$�@��v����^�s����������$��ߢ !�W��5�̱'-٘������4�>�Rƫ$�G��0U�H}�_ _� #��k�MJ/=�9Du *ڲ�k�PQ;0��j|�*�$�^컂脛y���>�z×� B��� ��(�:$�ʔ_�6��C!B����MVx��a��ζyY����j��ƋY2�9�F�{r��r�딟F��Q��hY6��� x��[l��RC��������nt@��;P��3��� F�w��7��:�P���ɋ'���mj���Bƪ�$�Q�L��y��߷g��\���z����v5�p�v$�v櫻�`6~K �Y�X����,�:��"�o�e0II"(��,������l_�C���/4�+f�E�!q��:����u����c��樼.UE�@� �]K�W���$-�h]�JG���Dz)�`��.�A/ ���mᏣc�3jK� �!�>���$�Q�!�_�? \�C2(E �M$Z I{�U���s�ǣ���=��@Բe ޻��W�����@ �w����}�9v,)��.}*|"�s��di���,���G�]W�AC�������|<����[��ߕ������K�*�G nߴ!��‹��J�!��6(L�a)��Am��������;adf��:�xm��nh�;֦I��(���o�C~ډa?�=��s����h��E������xp����)���_���t��W�����´;8���`dz=(��@@�+,�;�`�fhl��XȌ�3,uI�~z(J����� �ZX�A����H'�À�㜦�82|��a�n�z2��I���� ���EY�h�ε<�0�q�{��C��v�˅��?"@��χ�x�WS+��N�~�KQ�J�8ل�� _.W��� !TU{�����,��"�� �;�������HI S� ��))�o�1�H�D� ���� (� ;����"Ҕ��,���� �F=���O��&�Ph>�`���y��߲ &S��P�<��'��1s�1cĂ�N����2)����@�u��tv��<�+�-&�����h��[!FSG���' � z z0��<����8d&�����E0�,��<��"!P�b4��� �{S�����X����.�囤���T��hEF��X�q0"-<����h���]�Py*����F⩍��@v�˂�Y08�� .q��a��s����(!<̫1�3�L�H��7sYW�`��qtu�\7��?�@�Y�� %Ka�x�GZ�*W�P��y�����N0r9��Z�c�%���q,��k�����!!,��>���IB��� �x1�Xu�Y���v-���ֺ��N������JP��a�2`�e5��w��u;�6�i���̃��(K���ťa��(l51��JL��O'%���]���ٖ뿉!��{*�p+f ���i8��D�ȑ���ܩ�M��V��ӟz��x�7Z�1i}�^͜���6e����x��)u�d�ˏ�V�eG�\�X1�����=n�U'�~Y����L�\,�(d!x ,�:�TW8� M`r���@��r+v�Q0��>^;˴�VbF��('����� �*O��i��j�Ӆ����^�S3&� ���{������.ی�� N;��NU%d0�Ce\r"�C�b��&t7N�7��� �ȪD�[%l�۪�$��'m?�A9tI�9>��NVL ��� 6v~M�����ъ�B���0����9$&=ώm��W�0���L���&��E���f�f��'+�9��hv��7,���M��6�D�&�^� �����8 D֮�����o��k^z��O��hP�8q�5d*��YVq3�{��������#LX4�?��jP=3�~�؅c�ݰ�̱�ұ��8̲��2�G�����j��έ�9��w7���U�X�w�85C��?!I&���o&����L���I#�^�eV�,�^t5�k� �xs˛\��U�8��."���{���ǵ�l4��ݼ�;��r"���G�/~���gJV�N糦��ZZ^k+NN�D��I$�=OE�z����|*�*{��%k���G��>�`9��b;�s���8,�qs��c��.b��q�%�>r��n�R�Ƨ�%�,�A��le#�8���}; 21�0�b�&�QX�f���q�00����$>�h�N�'�a3��A���/�r ��L5�8�GԲ'�����w;��c#�q1���h$*��V^�k��QĽ�b:i������� ����H F#�_���,��]g����Ը����`RUU���� ��"�qD���>-��ϤI�~��3�̴�����NZ[[555�eeek7�m����YF#[��Đ��R��i��L�H�r ��@��J-�Pŝ��_|�-��6y��ks�V�U~��6���w.����(��0A� 4��f��� /���C�xŊ�ٳ����z=6�����rg���mKo��o�/����a�g���?'9%9��P��斓��~���L{�� �H��{AW��B��$����v�1b�&Mb�ر��ر#�0`�`xMBp! )Z�Nmo�mZ�g�}�eذaWTVV ���'99��C5��&�&+5�bT>|8��PUUETT����&���t��~TE%��'������b�tEGG;RSS�S�N�v��ѯ��t����u1@�ޚ"f��͈���r'L�Ph��m��ƌ�74o��`�x�q�P���NRSS�X,�����l&&&��MFFA!-��`��ҷ�n��Yuu�����x<I�LB%����KԒ50�!�3�bz�����ʸKL���;---�l6[��`��������Ȏ;�����egg4��!D�T��jWujyZ��������K����%����p�^���X,6��sV=�ܖ����6�3�X ���4ҿ��A2�&����_k�;d�� �VfOIEND�B`�
Warning: session_start(): Session cannot be started after headers have already been sent in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 124

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 130

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 131

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 132

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 133

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 134

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 135

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 547

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 548

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 549

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 550

Warning: Cannot modify header information - headers already sent by (output started at /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php:1) in /home/mciraet/www/wp-includes/php-ai-client/src/Providers/Enums/Documentsoliders.php on line 551
init_hooks(); } /** * Initialize the hooks used by the class. */ private function init_hooks() { add_filter( 'woocommerce_order_data_store', array( $this, 'get_orders_data_store' ), 999, 1 ); add_filter( 'woocommerce_order-refund_data_store', array( $this, 'get_refunds_data_store' ), 999, 1 ); add_filter( 'woocommerce_debug_tools', array( $this, 'add_hpos_tools' ), 999 ); add_filter( 'updated_option', array( $this, 'process_updated_option' ), 999, 3 ); add_filter( 'updated_option', array( $this, 'process_updated_option_fts_index' ), 999, 3 ); add_filter( 'pre_update_option', array( $this, 'process_pre_update_option' ), 999, 3 ); add_action( 'woocommerce_after_register_post_type', array( $this, 'register_post_type_for_order_placeholders' ), 10, 0 ); add_action( 'woocommerce_sections_advanced', array( $this, 'sync_now' ) ); add_filter( 'removable_query_args', array( $this, 'register_removable_query_arg' ) ); add_filter( 'get_edit_post_link', array( $this, 'maybe_rewrite_order_edit_link' ), 10, 2 ); add_action( 'before_woocommerce_init', array( $this, 'maybe_set_order_cache_group_as_non_persistent' ) ); add_filter( 'map_meta_cap', array( $this, 'maybe_translate_order_caps' ), 0, 4 ); } /** * Class initialization, invoked by the DI container. * * @internal * @param OrdersTableDataStore $data_store The data store to use. * @param DataSynchronizer $data_synchronizer The data synchronizer to use. * @param LegacyDataCleanup $data_cleanup The legacy data cleanup instance to use. * @param OrdersTableRefundDataStore $refund_data_store The refund data store to use. * @param BatchProcessingController $batch_processing_controller The batch processing controller to use. * @param FeaturesController $features_controller The features controller instance to use. * @param OrderCache $order_cache The order cache engine to use. * @param OrderCacheController $order_cache_controller The order cache controller to use. * @param PluginUtil $plugin_util The plugin util to use. * @param DatabaseUtil $db_util The database util to use. */ final public function init( OrdersTableDataStore $data_store, DataSynchronizer $data_synchronizer, LegacyDataCleanup $data_cleanup, OrdersTableRefundDataStore $refund_data_store, BatchProcessingController $batch_processing_controller, FeaturesController $features_controller, OrderCache $order_cache, OrderCacheController $order_cache_controller, PluginUtil $plugin_util, DatabaseUtil $db_util ) { $this->data_store = $data_store; $this->data_synchronizer = $data_synchronizer; $this->data_cleanup = $data_cleanup; $this->batch_processing_controller = $batch_processing_controller; $this->refund_data_store = $refund_data_store; $this->features_controller = $features_controller; $this->order_cache = $order_cache; $this->order_cache_controller = $order_cache_controller; $this->plugin_util = $plugin_util; $this->db_util = $db_util; } /** * Translate capabilities for HPOS orders when sync is not active. * * Only activates when HPOS is the authoritative source and sync is off, * then lazily delegates to HposOrderCapabilityHelper for the actual * capability translation. * * @since 10.7.0 * * @param string[] $caps The resolved primitive capabilities. * @param string $cap The meta capability being checked. * @param int $user_id The user ID. * @param array $args Additional arguments (object ID). * @return string[] Translated capabilities. */ public function maybe_translate_order_caps( $caps, $cap, $user_id, $args ) { if ( ! $this->custom_orders_table_usage_is_enabled() ) { return $caps; } if ( ! $this->data_synchronizer instanceof DataSynchronizer ) { return $caps; } if ( $this->data_synchronizer->data_sync_is_enabled() ) { return $caps; } return wc_get_container()->get( HposOrderCapabilityHelper::class )->translate_order_caps( $caps, $cap, $user_id, $args ); } /** * Is the custom orders table usage enabled via settings? * This can be true only if the feature is enabled and a table regeneration has been completed. * * @return bool True if the custom orders table usage is enabled */ public function custom_orders_table_usage_is_enabled(): bool { return get_option( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ) === 'yes'; } /** * Is caching of data within the CustomerOrdersTable datastores enabled? * * @return bool True if the caching is enabled within the CustomeOrderTable Datastores. */ public function hpos_data_caching_is_enabled(): bool { return get_option( self::HPOS_DATASTORE_CACHING_ENABLED_OPTION ) === 'yes' && $this->custom_orders_table_usage_is_enabled(); } /** * Gets the instance of the orders data store to use. * * @param \WC_Object_Data_Store_Interface|string $default_data_store The default data store (as received via the woocommerce_order_data_store hook). * * @return \WC_Object_Data_Store_Interface|string The actual data store to use. * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function get_orders_data_store( $default_data_store ) { return $this->get_data_store_instance( $default_data_store, 'order' ); } /** * Gets the instance of the refunds data store to use. * * @param \WC_Object_Data_Store_Interface|string $default_data_store The default data store (as received via the woocommerce_order-refund_data_store hook). * * @return \WC_Object_Data_Store_Interface|string The actual data store to use. * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function get_refunds_data_store( $default_data_store ) { return $this->get_data_store_instance( $default_data_store, 'order_refund' ); } /** * Gets the instance of a given data store. * * @param \WC_Object_Data_Store_Interface|string $default_data_store The default data store (as received via the appropriate hooks). * @param string $type The type of the data store to get. * * @return \WC_Object_Data_Store_Interface|string The actual data store to use. */ private function get_data_store_instance( $default_data_store, string $type ) { if ( $this->custom_orders_table_usage_is_enabled() ) { switch ( $type ) { case 'order_refund': return $this->refund_data_store; default: return $this->data_store; } } else { return $default_data_store; } } /** * Add an entry to Status - Tools to create or regenerate the custom orders table, * and also an entry to delete the table as appropriate. * * @param array $tools_array The array of tools to add the tool to. * @return array The updated array of tools. * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function add_hpos_tools( array $tools_array ): array { if ( ! $this->data_synchronizer->check_orders_table_exists() ) { return $tools_array; } // Cleanup tool. $tools_array = array_merge( $tools_array, $this->data_cleanup->get_tools_entries() ); // Delete HPOS tables tool. if ( $this->custom_orders_table_usage_is_enabled() || $this->data_synchronizer->data_sync_is_enabled() || $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) ) ) { $disabled = true; $message = __( 'This will delete the custom orders tables. The tables can be deleted only if the "High-Performance order storage" is not authoritative and sync is disabled (via Settings > Advanced > Features).', 'woocommerce' ); } else { $disabled = false; $message = __( 'This will delete the custom orders tables. To create them again enable the "High-Performance order storage" feature (via Settings > Advanced > Features).', 'woocommerce' ); } $tools_array['delete_custom_orders_table'] = array( 'name' => __( 'Delete the custom orders tables', 'woocommerce' ), 'desc' => sprintf( '%1$s %2$s', __( 'Note:', 'woocommerce' ), $message ), 'requires_refresh' => true, 'callback' => function () use ( $disabled ) { if ( $disabled ) { return; } $this->features_controller->change_feature_enable( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, false ); $this->delete_custom_orders_tables(); return __( 'Custom orders tables have been deleted.', 'woocommerce' ); }, 'button' => __( 'Delete', 'woocommerce' ), 'disabled' => $disabled, ); return $tools_array; } /** * Delete the custom orders tables and any related options and data in response to the user pressing the tool button. * * @throws \Exception Can't delete the tables. */ private function delete_custom_orders_tables() { if ( $this->custom_orders_table_usage_is_enabled() ) { throw new \Exception( "Can't delete the custom orders tables: they are currently in use (via Settings > Advanced > Features)." ); } delete_option( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ); $this->data_synchronizer->delete_database_tables(); } /** * Handler for the individual setting updated hook. * * @param string $option Setting name. * @param mixed $old_value Old value of the setting. * @param mixed $value New value of the setting. * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function process_updated_option( $option, $old_value, $value ) { if ( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION === $option && 'no' === $value ) { $this->data_synchronizer->cleanup_synchronization_state(); } if ( self::HPOS_DATASTORE_CACHING_ENABLED_OPTION === $option && $old_value !== $value && 'yes' === $value ) { $this->data_store->clear_all_cached_data(); } } /** * Process option that enables FTS index on orders table. Tries to create an FTS index when option is enabled. * * @param string $option Option name. * @param string $old_value Old value of the option. * @param string $value New value of the option. * * @return void * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function process_updated_option_fts_index( $option, $old_value, $value ) { if ( self::HPOS_FTS_INDEX_OPTION !== $option ) { return; } if ( 'yes' !== $value ) { return; } if ( ! $this->custom_orders_table_usage_is_enabled() ) { update_option( self::HPOS_FTS_INDEX_OPTION, 'no', true ); if ( class_exists( 'WC_Admin_Settings' ) ) { WC_Admin_Settings::add_error( __( 'Failed to create FTS index on orders table. This feature is only available when High-performance order storage is enabled.', 'woocommerce' ) ); } return; } if ( ! $this->db_util->fts_index_on_order_address_table_exists() ) { $this->db_util->create_fts_index_order_address_table(); } // Check again to see if index was actually created. if ( $this->db_util->fts_index_on_order_address_table_exists() ) { update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'yes', false ); } else { update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'no', false ); if ( class_exists( 'WC_Admin_Settings ' ) ) { WC_Admin_Settings::add_error( __( 'Failed to create FTS index on address table', 'woocommerce' ) ); } } if ( ! $this->db_util->fts_index_on_order_item_table_exists() ) { $this->db_util->create_fts_index_order_item_table(); } // Check again to see if index was actually created. if ( $this->db_util->fts_index_on_order_item_table_exists() ) { update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'yes', false ); } else { update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'no', false ); if ( class_exists( 'WC_Admin_Settings ' ) ) { WC_Admin_Settings::add_error( __( 'Failed to create FTS index on order item table', 'woocommerce' ) ); } } } /** * Recreate order addresses FTS index. Useful when updating to 9.4 when phone number was added to index, or when other recreating index is needed. * * @since 9.4.0. * * @return array Array with keys status (bool) and message (string). */ public function recreate_order_address_fts_index(): array { $this->db_util->drop_fts_index_order_address_table(); if ( $this->db_util->fts_index_on_order_address_table_exists() ) { return array( 'status' => false, 'message' => __( 'Failed to modify existing FTS index. Please go to WooCommerce > Status > Tools and run the "Re-create Order Address FTS index" tool.', 'woocommerce' ), ); } else { update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'no', false ); } $this->db_util->create_fts_index_order_address_table(); if ( ! $this->db_util->fts_index_on_order_address_table_exists() ) { return array( 'status' => false, 'message' => __( 'Failed to create FTS index on order address table. Please go to WooCommerce > Status > Tools and run the "Re-create Order Address FTS index" tool.', 'woocommerce' ), ); } else { update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'yes', false ); return array( 'status' => true, 'message' => __( 'FTS index recreated.', 'woocommerce' ), ); } } /** * Handler for the setting pre-update hook. * We use it to verify that authoritative orders table switch doesn't happen while sync is pending. * * @param mixed $value New value of the setting. * @param string $option Setting name. * @param mixed $old_value Old value of the setting. * * @throws \Exception Attempt to change the authoritative orders table while orders sync is pending. * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function process_pre_update_option( $value, $option, $old_value ) { if ( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION === $option && $value !== $old_value ) { $this->order_cache->flush(); return $value; } if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $option ) { return $value; } if ( $old_value === $value ) { return $value; } $this->order_cache->flush(); if ( ! $this->data_synchronizer->check_orders_table_exists() ) { $this->data_synchronizer->create_database_tables(); } $tables_created = get_option( DataSynchronizer::ORDERS_TABLE_CREATED ) === 'yes'; if ( ! $tables_created ) { return 'no'; } if ( ! $this->changing_data_source_with_sync_pending_is_allowed() && $this->data_synchronizer->has_orders_pending_sync() ) { throw new \Exception( "The authoritative table for orders storage can't be changed while there are orders out of sync" ); } return $value; } /** * Callback to trigger a sync immediately by clicking a button on the Features screen. * * @return void * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function sync_now() { $section = filter_input( INPUT_GET, 'section' ); if ( 'features' !== $section ) { return; } if ( filter_input( INPUT_GET, self::SYNC_QUERY_ARG, FILTER_VALIDATE_BOOLEAN ) ) { $action = 'sync-now'; } elseif ( filter_input( INPUT_GET, self::STOP_SYNC_QUERY_ARG, FILTER_VALIDATE_BOOLEAN ) ) { $action = 'stop-sync'; } else { return; } if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), "hpos-{$action}" ) ) { WC_Admin_Settings::add_error( 'sync-now' === $action ? esc_html__( 'Unable to start synchronization. The link you followed may have expired.', 'woocommerce' ) : esc_html__( 'Unable to stop synchronization. The link you followed may have expired.', 'woocommerce' ) ); return; } $this->data_cleanup->toggle_flag( false ); if ( 'sync-now' === $action ) { if ( ! $this->data_synchronizer->check_orders_table_exists() && ! $this->data_synchronizer->create_database_tables() ) { WC_Admin_Settings::add_error( __( 'Unable to create HPOS tables for synchronization.', 'woocommerce' ) ); return; } $this->batch_processing_controller->enqueue_processor( DataSynchronizer::class ); } else { $this->batch_processing_controller->remove_processor( DataSynchronizer::class ); } } /** * Tell WP Admin to remove the sync query arg from the URL. * * @param array $query_args The query args that are removable. * * @return array * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function register_removable_query_arg( $query_args ) { $query_args[] = self::SYNC_QUERY_ARG; $query_args[] = self::STOP_SYNC_QUERY_ARG; return $query_args; } /** * Handler for the woocommerce_after_register_post_type post, * registers the post type for placeholder orders. * * @return void * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function register_post_type_for_order_placeholders(): void { wc_register_order_type( DataSynchronizer::PLACEHOLDER_ORDER_POST_TYPE, array( 'public' => false, 'exclude_from_search' => true, 'publicly_queryable' => false, 'show_ui' => false, 'show_in_menu' => false, 'show_in_nav_menus' => false, 'show_in_admin_bar' => false, 'show_in_rest' => false, 'rewrite' => false, 'query_var' => false, 'can_export' => false, 'supports' => array(), 'capabilities' => array(), 'exclude_from_order_count' => true, 'exclude_from_order_views' => true, 'exclude_from_order_reports' => true, 'exclude_from_order_sales_reports' => true, ) ); } /** * Add the definition for the HPOS feature. * * @param FeaturesController $features_controller The instance of FeaturesController. * * @return void * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ 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(), 'default_plugin_compatibility' => FeaturePluginCompatibility::INCOMPATIBLE, '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 = ! $this->data_synchronizer->has_orders_pending_sync(); $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 () { $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 = $this->data_synchronizer->has_orders_pending_sync( true ); $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( __( "There are orders pending sync.", 'woocommerce' ) . '' . __( '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 ) { $orders_pending_sync_count = $this->data_synchronizer->get_current_orders_pending_sync_count( true ); $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( __( "You can switch order data storage only when the posts and orders tables are in sync. There are currently orders out of sync.", 'woocommerce' ), ); } $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->has_orders_pending_sync(); 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() ) ); } } }