get_meta( Orders::META_SENT ) ) { return; } // An order made in the admin never went through a shopper's browser. if ( 'admin' === $order->get_created_via() ) { return; } Queue::pushUnique( Hooks::MATOMO_ORDER, [ 'order' => $orderId ], 'matomo:' . $orderId, self::DELAY ); } /** * Send one order. * * @param array $payload `order`: the order id. * * @throws RuntimeException When Matomo cannot be reached or fails, so the queue retries. * * @internal Queue handler. */ public function send( array $payload ): void { $site = Config::site(); $order = wc_get_order( absint( $payload['order'] ?? 0 ) ); if ( null === $site || ! $order instanceof WC_Order || '' !== (string) $order->get_meta( Orders::META_SENT ) ) { return; } $token = Config::token(); $response = wp_remote_post( $site['url'] . 'matomo.php', [ 'timeout' => 15, 'body' => $this->params( $order, $site['site'], $token ), ] ); $id = (string) $order->get_id(); if ( is_wp_error( $response ) ) { Config::recordSend( $id, 0, $response->get_error_message() ); throw new RuntimeException( 'Matomo did not answer: ' . esc_html( $response->get_error_message() ) ); } $code = (int) wp_remote_retrieve_response_code( $response ); if ( $code >= 500 || 0 === $code ) { Config::recordSend( $id, $code, 'HTTP ' . $code ); throw new RuntimeException( 'Matomo answered HTTP ' . (int) $code . '.' ); } if ( $code >= 400 ) { // Matomo says nothing in a 400. For an order the browser already sent it // is the duplicate; otherwise something about the request is wrong, and // sending the same request again would not change the answer. $order->update_meta_data( Orders::META_SENT, 'rejected' ); $order->save_meta_data(); Config::recordSend( $id, $code, __( 'Matomo سفارش را نپذیرفت؛ معمولاً یعنی صفحه‌ی تشکر آن را پیش‌تر ثبت کرده است.', 'rahkar-toolkit' ) ); Logger::info( 'matomo', 'Matomo refused a server-side order.', [ 'order' => $id, 'code' => $code ] ); return; } $order->update_meta_data( Orders::META_SENT, (string) time() ); $order->save_meta_data(); Config::recordSend( $id, $code, '' ); } /** * The tracking request for an order. * * @param WC_Order $order The order. * @param int $site Matomo site id. * @param string|null $token Write token, if set. * * @return array */ private function params( WC_Order $order, int $site, ?string $token ): array { $data = Orders::ecommerce( $order ); $created = $order->get_date_created(); $placed = null === $created ? time() : $created->getTimestamp(); $visitor = Orders::visitorId( $order ); $user = (int) $order->get_customer_id(); $params = [ 'idsite' => $site, 'rec' => 1, 'apiv' => 1, 'send_image' => 0, 'idgoal' => 0, 'ec_id' => $data['id'], 'revenue' => $data['total'], 'ec_st' => $data['subtotal'], 'ec_tx' => $data['tax'], 'ec_sh' => $data['shipping'], 'ec_dt' => $data['discount'], 'ec_items' => (string) wp_json_encode( $data['items'], JSON_UNESCAPED_UNICODE ), 'url' => $this->landing( $order, null === $visitor ), 'rand' => wp_rand(), ]; if ( null !== $visitor ) { $params['_id'] = $visitor; } else { $referrer = (string) $order->get_meta( '_wc_order_attribution_referrer' ); if ( '' !== $referrer && null !== wp_parse_url( $referrer, PHP_URL_HOST ) ) { $params['urlref'] = $referrer; } } if ( $user > 0 ) { $params['uid'] = (string) $user; } $agent = $order->get_customer_user_agent(); if ( '' !== $agent ) { $params['ua'] = $agent; } if ( null !== $token ) { $params['token_auth'] = $token; $params['cdt'] = $placed; $ip = $order->get_customer_ip_address(); if ( '' !== $ip ) { $params['cip'] = $ip; } } elseif ( time() - $placed <= self::UNTOKENED_AGE ) { $params['cdt'] = $placed; } return $params; } /** * The page the request is said to come from. * * With a visitor id it is only the site's home: the visit already has its * pages. Without one it opens a visit, so WooCommerce's UTM tags go on it and * Matomo files the visit under the campaign that brought the order. * * @param WC_Order $order The order. * @param bool $campaign Whether to carry the order's UTM tags. */ private function landing( WC_Order $order, bool $campaign ): string { $url = home_url( '/' ); if ( ! $campaign ) { return $url; } $tags = []; foreach ( [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term' ] as $tag ) { $value = (string) $order->get_meta( '_wc_order_attribution_' . $tag ); if ( '' !== $value && '(none)' !== $value ) { $tags[ $tag ] = $value; } } return [] === $tags ? $url : add_query_arg( array_map( 'rawurlencode', $tags ), $url ); } }