Plugin::make( Flusher::class )->run() ); add_action( Hooks::HUB_BACKFILL, static fn( array $payload = [] ) => Plugin::make( Backfill::class )->run( $payload ) ); add_action( Hooks::HUB_RECONCILE, static fn( array $payload = [] ) => Plugin::make( Reconciler::class )->run( $payload ) ); if ( is_admin() ) { add_action( 'admin_menu', static function (): void { add_submenu_page( Menu::SLUG, __( 'راهکار فروش', 'rahkar-toolkit' ), __( 'راهکار فروش', 'rahkar-toolkit' ), Menu::CAPABILITY, ConnectionPage::SLUG, static fn() => Plugin::make( ConnectionPage::class )->render() ); }, 20 ); } // From the manifest, not the credentials option: deciding whether to watch // orders must not cost a query on every request (DEVELOPMENT.md §2). if ( ! $this->bootSwitch( 'connected' ) ) { return; } // The connector reads orders only through WooCommerce's CRUD API. add_action( 'before_woocommerce_init', static function (): void { if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', RAHKAR_WP_FILE, true ); } } ); add_action( 'woocommerce_after_order_object_save', static fn( $order ) => Plugin::make( OrderWatcher::class )->saved( $order ), 20 ); add_action( 'woocommerce_after_order_refund_object_save', static fn( $refund ) => Plugin::make( OrderWatcher::class )->saved( $refund ), 20 ); add_action( 'woocommerce_order_refunded', static fn( $orderId ) => Plugin::make( OrderWatcher::class )->refunded( $orderId ), 20 ); add_action( 'woocommerce_order_status_changed', static fn( $orderId, $from, $to ) => Plugin::make( OrderWatcher::class )->statusChanged( $orderId, $from, $to ), 20, 3 ); add_action( 'woocommerce_trash_order', static fn( $id ) => Plugin::make( OrderWatcher::class )->removed( $id, false ), 20 ); add_action( 'woocommerce_delete_order', static fn( $id ) => Plugin::make( OrderWatcher::class )->removed( $id, true ), 20 ); add_action( 'woocommerce_untrash_order', static fn( $id ) => Plugin::make( OrderWatcher::class )->restored( $id ), 20 ); $this->registerCatalogue(); $this->registerPrivacy(); // The dashboard signs its campaign links with the connection key; the short // link module asks for it through this filter instead of reading the // connector's classes, and gets nothing when the connector is off. add_filter( 'rahkar_wp_hub_link_secret', static fn( $secret ) => Credentials::load()?->secret ?? $secret ); // Events other modules report: messages, carts, invitations, stock alerts. add_action( \Rahkar\WP\Support\Events::ACTION, static fn( $type, $data, $at = null ) => EventSink::accept( $type, $data, $at ), 10, 3 ); // Priority 20: WooCommerce stores the rating on the same hook at priority 1. add_action( 'comment_post', static fn( $id, $approved ) => Plugin::make( ReviewWatcher::class )->posted( $id, $approved ), 20, 2 ); } /** * Products, categories, customers and coupons. */ private function registerCatalogue(): void { // Variations are WC_Product too, so this one hook covers both. add_action( 'woocommerce_after_product_object_save', static fn( $product ) => Plugin::make( CatalogueWatcher::class )->productSaved( $product ), 20 ); // Stock changes from orders are written straight to the database, without a product save. add_action( 'woocommerce_product_set_stock', static fn( $product ) => Plugin::make( CatalogueWatcher::class )->productSaved( $product ), 20 ); add_action( 'woocommerce_variation_set_stock', static fn( $product ) => Plugin::make( CatalogueWatcher::class )->productSaved( $product ), 20 ); // WordPress's own post hooks, not WooCommerce's `woocommerce_trash_*`: the // dashboard's "Move to Trash" and "Delete Permanently" go straight through // wp_trash_post() and wp_delete_post(), and WooCommerce fires its hooks only // when the deletion starts in its data store. These two see both paths. add_action( 'trashed_post', static fn( $id ) => CatalogueWatcher::postRemoved( (int) $id, false ), 20 ); add_action( 'before_delete_post', static fn( $id ) => CatalogueWatcher::postRemoved( (int) $id, true ), 20 ); add_action( 'untrashed_post', static fn( $id ) => Plugin::make( CatalogueWatcher::class )->untrashed( $id ), 20 ); // WC_Coupon declares no object type of its own, so its generic save hook is // shared with every other data object; the data store's own hooks are not. add_action( 'woocommerce_new_coupon', static fn( $id ) => Plugin::make( CatalogueWatcher::class )->touch( CatalogueWatcher::COUPON, $id ), 20 ); add_action( 'woocommerce_update_coupon', static fn( $id ) => Plugin::make( CatalogueWatcher::class )->touch( CatalogueWatcher::COUPON, $id ), 20 ); add_action( 'created_product_cat', static fn( $id ) => Plugin::make( CatalogueWatcher::class )->touch( CatalogueWatcher::CATEGORY, $id ), 20 ); add_action( 'edited_product_cat', static fn( $id ) => Plugin::make( CatalogueWatcher::class )->touch( CatalogueWatcher::CATEGORY, $id ), 20 ); add_action( 'delete_product_cat', static fn( $id ) => CatalogueWatcher::deleted( CatalogueWatcher::CATEGORY, (int) $id, true ), 20 ); foreach ( [ 'user_register', 'profile_update', 'woocommerce_new_customer', 'woocommerce_update_customer' ] as $hook ) { add_action( $hook, static fn( $userId ) => Plugin::make( CatalogueWatcher::class )->userChanged( $userId ), 20 ); } add_action( 'delete_user', static fn( $userId ) => Plugin::make( CatalogueWatcher::class )->userDeleted( $userId ), 20 ); // Published by the SMS login module when a number is proven. add_action( 'rahkar_wp_phone_verified', static function ( $phone, $user = null ): void { $userId = $user instanceof \WP_User ? $user->ID : 0; $canonical = Contract::phone( $phone ); if ( null !== $canonical ) { Outbox::add( 'customer.phone_verified', [ 'user_id' => Contract::id( $userId ), 'phone' => $canonical, ] ); } Plugin::make( CatalogueWatcher::class )->userChanged( $userId ); }, 20, 2 ); } /** * Erasure requests, the policy text, and module switches. */ private function registerPrivacy(): void { add_filter( 'wp_privacy_personal_data_erasers', static fn( $erasers ) => Privacy::registerEraser( $erasers ) ); add_action( 'update_option_rahkar_wp_enabled_modules', static fn( $old, $new ) => Privacy::modulesChanged( $old, $new ), 20, 2 ); if ( is_admin() ) { add_action( 'admin_init', static function (): void { if ( 'options-privacy.php' === ( $GLOBALS['pagenow'] ?? '' ) ) { Privacy::policyText(); } } ); } } }