transport = $transport; $this->settings = $settings; $this->view = $view; } /** * Register hooks. * * @return void */ public function boot(): void { add_action( 'add_meta_boxes', array( $this, 'add_box' ), 10, 2 ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) ); } /** * Load the stylesheet the panel is laid out with, on the order screen only. * * The panel shares the plugin's one admin stylesheet. Without this it was enqueued on the * settings screen alone, so the attribution table arrived on the order screen unstyled. * * @return void */ public function enqueue(): void { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( null === $screen || ! in_array( $screen->id, self::SCREENS, true ) ) { return; } wp_enqueue_style( 'adpix-analytics-admin', ADPIX_ANALYTICS_URL . 'assets/css/admin.css', array( 'dashicons' ), ADPIX_ANALYTICS_VERSION ); } /** * Add the panel to whichever order screen this site uses. * * Registering against the screen WooCommerce reports keeps the panel working through a migration * in either direction, without the plugin having to know which storage mode is active. * * @param string $screen_id Current screen id. * @param mixed $object Post or order being edited. * @return void */ public function add_box( $screen_id, $object = null ): void { if ( ! $this->settings->load()->is_connected() ) { return; } if ( ! in_array( (string) $screen_id, self::SCREENS, true ) ) { return; } add_meta_box( self::BOX, __( 'AdPix attribution', 'adpix-analytics' ), array( $this, 'render' ), $screen_id, 'side', 'default' ); } /** * Render the panel. * * @param mixed $object Post or order being edited. * @return void */ public function render( $object ): void { if ( ! current_user_can( 'edit_shop_orders' ) && ! current_user_can( SettingsController::CAPABILITY ) ) { return; } $order = $this->resolve_order( $object ); if ( null === $order ) { return; } $this->view->render( 'order-panel', array( 'attribution' => $this->lookup( $order ), ) ); } /** * The order being edited, whichever object type the screen passed us. * * @param mixed $object Post or order. * @return WC_Order|null */ private function resolve_order( $object ): ?WC_Order { if ( $object instanceof WC_Order ) { return $object; } $id = is_object( $object ) && isset( $object->ID ) ? (int) $object->ID : 0; if ( $id <= 0 || ! function_exists( 'wc_get_order' ) ) { return null; } $order = wc_get_order( $id ); return $order instanceof WC_Order ? $order : null; } /** * Ask the collector what it knows about this order. * * Cached for a few minutes because the panel is rendered on every order view and the answer only * changes when the snapshot is rewritten — and an admin screen should not wait on a network round * trip it already made a moment ago. * * @param WC_Order $order Order being edited. * @return array|null Null when the collector has no snapshot or could not be reached. */ private function lookup( WC_Order $order ): ?array { $reference = (string) $order->get_order_number(); if ( '' === $reference ) { return null; } $cache_key = 'adpix_attr_' . md5( $reference ); $cached = get_transient( $cache_key ); if ( is_array( $cached ) ) { return array() !== $cached ? $cached : null; } $response = $this->transport->fetch( 'attribution', array( 'order_id' => $reference ) ); if ( ! $response->is_ok() ) { // A miss is cached too, briefly: an order placed before the plugin was connected has no // snapshot and never will, and re-asking on every page view would not change that. set_transient( $cache_key, array(), $response->is_retryable() ? MINUTE_IN_SECONDS : 10 * MINUTE_IN_SECONDS ); return null; } $data = array( 'attribution' => is_array( $response->get( 'attribution' ) ) ? $response->get( 'attribution' ) : array(), 'snapshot' => is_array( $response->get( 'order_snapshot' ) ) ? $response->get( 'order_snapshot' ) : array(), ); set_transient( $cache_key, $data, 5 * MINUTE_IN_SECONDS ); return $data; } }