From f1eed269b9ab477ea3c8d2453fa5a12c47d40c8d Mon Sep 17 00:00:00 2001 From: shazzad Date: Fri, 27 Mar 2026 00:54:04 +0600 Subject: [PATCH] Send ping as POST with admin_email and admin_name fields - Switch ping from GET to POST with data in request body - Add admin_email and admin_name to Integration, populated during init - Skip update transient injection when plugin is inactive (fixes ghost updates on deactivation) - Send ping on hourly cron for licensed plugins too - Remove unnecessary install data from other API requests (only ping needs it) - Update tests for POST ping Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Client.php | 59 ++++++++++++++++++++++++++-------- src/Integration.php | 14 ++++++++ src/Tracker.php | 2 ++ src/Updater.php | 10 ++++++ tests/ClientApiRequestTest.php | 4 +-- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/Client.php b/src/Client.php index 4ca2ee8..a61cb91 100644 --- a/src/Client.php +++ b/src/Client.php @@ -52,7 +52,49 @@ public function __construct( Integration $integration ) { * @return array|WP_Error Response data or WP_Error on failure. */ public function ping() { - return $this->request( 'ping', [], 2 ); + $request_url = "{$this->integration->api_url}/products/{$this->integration->product_id}/ping"; + + $body = [ + 'product_version' => $this->integration->product_version, + 'product_status' => $this->integration->product_status, + 'wp_url' => esc_url( site_url( '', 'https' ) ), + 'wp_locale' => get_locale(), + 'wp_version' => get_bloginfo( 'version', 'display' ), + 'admin_email' => $this->integration->admin_email, + 'admin_name' => $this->integration->admin_name, + ]; + + $request = wp_remote_post( + $request_url, + [ + 'timeout' => 2, + 'body' => $body, + ] + ); + + if ( is_wp_error( $request ) ) { + return $request; + } + + $status_code = wp_remote_retrieve_response_code( $request ); + $body = wp_remote_retrieve_body( $request ); + $body = json_decode( $body, true ); + + if ( empty( $body ) ) { + return new WP_Error( + 'wprepo_api_fail', + 'No response from update server' + ); + } + + if ( $status_code >= 400 ) { + return new WP_Error( + ! empty( $body['code'] ) ? $body['code'] : 'wprepo_api_error', + ! empty( $body['message'] ) ? $body['message'] : 'API request failed' + ); + } + + return $body; } /** @@ -165,18 +207,9 @@ public function details( $cache_period = 600 ) { private function request( $method, $args = [], $timeout = 5 ) { $request_url = "{$this->integration->api_url}/products/{$this->integration->product_id}/$method"; - $args = array_merge( - [ - 'product_version' => $this->integration->product_version, - 'product_status' => $this->integration->product_status, - 'wp_url' => esc_url( site_url( '', 'https' ) ), - 'wp_locale' => get_locale(), - 'wp_version' => get_bloginfo( 'version', 'display' ), - ], - $args - ); - - $request_url = add_query_arg( $args, $request_url ); + if ( ! empty( $args ) ) { + $request_url = add_query_arg( $args, $request_url ); + } $request = wp_remote_request( $request_url, diff --git a/src/Integration.php b/src/Integration.php index 2e0248b..c9f6a9b 100644 --- a/src/Integration.php +++ b/src/Integration.php @@ -72,6 +72,20 @@ class Integration { */ public $product_name; + /** + * Site admin email. + * + * @var string + */ + public $admin_email = ''; + + /** + * First admin username. + * + * @var string + */ + public $admin_name = ''; + /** * Sanitized name for the plugin license option. * diff --git a/src/Tracker.php b/src/Tracker.php index ae9262a..2d78b48 100644 --- a/src/Tracker.php +++ b/src/Tracker.php @@ -64,6 +64,8 @@ public function sync_license_data() { return; } + $this->integration->client->ping(); + $response = $this->integration->client->check_license(); if ( is_wp_error( $response ) ) { if ( 'invalid_license' === $response->get_error_code() ) { diff --git a/src/Updater.php b/src/Updater.php index dd41fbb..46aa086 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -62,6 +62,12 @@ public function init() { $this->integration->product_version = $plugin['Version']; $this->integration->product_name = $plugin['Name']; + $this->integration->admin_email = get_option( 'admin_email' ); + + $admins = get_users( [ 'role' => 'administrator', 'number' => 1, 'orderby' => 'ID', 'order' => 'ASC' ] ); + if ( ! empty( $admins ) ) { + $this->integration->admin_name = $admins[0]->display_name; + } // Schedule a cron job to refresh license data hourly. $hook_name = "wprepo_sync_license_data_{$this->integration->license_name}"; @@ -80,6 +86,10 @@ public function init() { * @return object Filtered transient. */ public function pre_set_transient( $transient ) { + if ( 'inactive' === $this->integration->product_status ) { + return $transient; + } + if ( property_exists( $transient, 'checked' ) && ! empty( $transient->checked ) ) { // Use the checked version as fallback when product_version is not yet set // (e.g. pre_set_transient fires before the init hook). diff --git a/tests/ClientApiRequestTest.php b/tests/ClientApiRequestTest.php index db9a531..2c18de2 100644 --- a/tests/ClientApiRequestTest.php +++ b/tests/ClientApiRequestTest.php @@ -41,7 +41,7 @@ public function ping_returns_parsed_body() { $fixture = $this->load_fixture_raw( 'ping-success.json' ); - Functions\expect( 'wp_remote_request' ) + Functions\expect( 'wp_remote_post' ) ->once() ->andReturn( [ 'body' => $fixture ] ); @@ -66,7 +66,7 @@ public function wp_error_from_wp_remote_request_is_returned() { $error = new WP_Error( 'http_error', 'Connection timed out' ); - Functions\expect( 'wp_remote_request' ) + Functions\expect( 'wp_remote_post' ) ->once() ->andReturn( $error );