Skip to content

Commit e14e0a7

Browse files
authored
Merge pull request #6234 from Automattic/staging
Production release v20250408.0
2 parents ee4ec86 + 1cbd9ec commit e14e0a7

32 files changed

+1696
-152
lines changed

.github/actions/run-wp-tests/action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ runs:
8484
uses: ramsey/composer-install@a2636af0004d1c0499ffca16ac0b4cc94df70565 # 3.1.0
8585

8686
- name: Set up WordPress and WordPress Test Library
87-
uses: sjinks/[email protected].3
87+
uses: sjinks/[email protected].4
8888
with:
8989
version: ${{ inputs.wordpress }}
9090

.github/workflows/codeql-analysis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ jobs:
3333
uses: actions/[email protected]
3434

3535
- name: Initialize CodeQL
36-
uses: github/codeql-action/[email protected].11
36+
uses: github/codeql-action/[email protected].13
3737
with:
3838
languages: ${{ matrix.language }}
3939
config-file: ./.github/codeql-config.yml
4040

4141
- name: Perform CodeQL Analysis
42-
uses: github/codeql-action/[email protected].11
42+
uses: github/codeql-action/[email protected].13
4343
with:
4444
category: "/language:${{matrix.language}}"

.github/workflows/stale.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ jobs:
1212
issues: write
1313
pull-requests: write
1414
steps:
15-
- uses: Automattic/vip-actions/stale@bb2853ecb6cd7dba2efe589ebb10cf930759fe69 # trunk
15+
- uses: Automattic/vip-actions/stale@1137b91acf0f5ea4e0db044bcf14ceabed9b068f # trunk

000-vip-init.php

+29-9
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,35 @@
234234
require_once __DIR__ . '/vip-helpers/class-wpcomvip-restrictions.php';
235235

236236
// Load the Telemetry files
237-
require_once __DIR__ . '/telemetry/class-telemetry-system.php';
238-
require_once __DIR__ . '/telemetry/class-tracks.php';
239-
require_once __DIR__ . '/telemetry/class-telemetry-client.php';
240-
require_once __DIR__ . '/telemetry/class-telemetry-event-queue.php';
241-
require_once __DIR__ . '/telemetry/class-telemetry-event.php';
242-
require_once __DIR__ . '/telemetry/tracks/class-tracks-event-dto.php';
243-
require_once __DIR__ . '/telemetry/tracks/class-tracks-event.php';
244-
require_once __DIR__ . '/telemetry/tracks/class-tracks-client.php';
245-
require_once __DIR__ . '/telemetry/tracks/tracks-utils.php';
237+
// Temporary loader during rollout, remove and directly require after rollout.
238+
$telemetry_files = [
239+
__DIR__ . '/telemetry/class-telemetry-system.php',
240+
__DIR__ . '/telemetry/class-telemetry-client.php',
241+
__DIR__ . '/telemetry/class-telemetry-event-queue.php',
242+
__DIR__ . '/telemetry/class-telemetry-event.php',
243+
__DIR__ . '/telemetry/class-telemetry.php',
244+
__DIR__ . '/telemetry/tracks/class-tracks.php',
245+
__DIR__ . '/telemetry/tracks/class-tracks-event-dto.php',
246+
__DIR__ . '/telemetry/tracks/class-tracks-event.php',
247+
__DIR__ . '/telemetry/tracks/class-tracks-client.php',
248+
__DIR__ . '/telemetry/tracks/tracks-utils.php',
249+
__DIR__ . '/telemetry/pendo/class-pendo.php',
250+
__DIR__ . '/telemetry/pendo/class-pendo-track-client.php',
251+
__DIR__ . '/telemetry/pendo/class-pendo-track-event-dto.php',
252+
__DIR__ . '/telemetry/pendo/class-pendo-track-event.php',
253+
__DIR__ . '/telemetry/pendo/pendo-utils.php',
254+
];
255+
256+
// Make sure all telemetry files are present before loading them.
257+
$safe_to_load_telemetry = array_reduce( $telemetry_files, function ( bool $carry, string $file ): bool {
258+
return $carry && file_exists( $file );
259+
}, true );
260+
261+
if ( true === $safe_to_load_telemetry ) {
262+
foreach ( $telemetry_files as $file ) {
263+
require_once $file;
264+
}
265+
}
246266

247267
add_action( 'init', [ WPComVIP_Restrictions::class, 'instance' ] );
248268

__tests__/e2e/package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/class-site-details-index.php

+118-18
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ class Site_Details_Index {
2020
*/
2121
private $timestamp = null;
2222

23+
const SYNC_DATA_OPTION = 'vip_config_sync_data';
24+
25+
const MINUTE_IN_MS = MINUTE_IN_SECONDS * 1000;
26+
const DAY_IN_MS = DAY_IN_SECONDS * 1000;
27+
2328
/**
2429
* Standard singleton except accept a timestamp for mocking purposes.
2530
*
@@ -349,32 +354,127 @@ public function get_site_details() {
349354
}
350355

351356
/**
352-
* Builds the site details structure and then puts it into logstash
353-
* and sends it to the site details service
357+
* Builds the site details and sends it to the site details service.
354358
*/
355359
public function put_site_details() {
356360
$site_details = $this->get_site_details();
357-
$url = null;
358-
$token = null;
361+
$sync_data = get_option( self::SYNC_DATA_OPTION, [] );
362+
$sync_data = is_array( $sync_data ) ? $sync_data : [];
363+
364+
$sync_type = $this->determine_sync_type( $site_details, $sync_data );
365+
366+
// Run a heartbeat sync.
367+
if ( 'heartbeat' === $sync_type ) {
368+
$success = $this->send_sync( '/sites/heartbeat', [
369+
'client_site_id' => $site_details['client_site_id'],
370+
'blog_id' => $site_details['core']['blog_id'],
371+
'timestamp' => $site_details['timestamp'],
372+
] );
373+
374+
if ( $success ) {
375+
$sync_data['last_synced'] = $site_details['timestamp'];
376+
update_option( self::SYNC_DATA_OPTION, $sync_data, false );
377+
}
378+
}
379+
380+
// Run a full sync.
381+
if ( 'full' === $sync_type ) {
382+
$success = $this->send_sync( '/sites', $site_details );
359383

360-
if ( defined( 'VIP_SERVICES_AUTH_TOKENS' ) && ! empty( VIP_SERVICES_AUTH_TOKENS ) ) {
361-
$auth_token_details = json_decode( base64_decode( VIP_SERVICES_AUTH_TOKENS ), true );
362-
$url = $auth_token_details['site']['vip-site-details']['url'] ?? null;
363-
$token = $auth_token_details['site']['vip-site-details']['token'] ?? null;
384+
if ( $success ) {
385+
$timestamp = $site_details['timestamp'];
386+
387+
// Stagger the future syncs if this is the first time we're setting the option.
388+
if ( empty( $sync_data['last_full_synced'] ) ) {
389+
$sync_data['last_full_synced'] = wp_rand( $timestamp - self::DAY_IN_MS, $timestamp );
390+
$sync_data['last_synced'] = wp_rand( $timestamp - self::MINUTE_IN_MS * 25, $timestamp );
391+
} else {
392+
$sync_data['last_full_synced'] = $timestamp;
393+
$sync_data['last_synced'] = $timestamp;
394+
}
395+
396+
$sync_data['last_sync_hash'] = $this->get_site_details_data_hash( $site_details );
397+
update_option( self::SYNC_DATA_OPTION, $sync_data, false );
398+
}
364399
}
400+
}
365401

366-
if ( $url && $token ) {
367-
$args = array(
368-
'method' => 'PUT',
369-
'body' => wp_json_encode( $site_details ),
370-
'headers' => array(
371-
'Authorization' => 'Bearer ' . $token,
372-
'Content-Type' => 'application/json',
373-
),
374-
);
402+
/**
403+
* Determine if we need a full sync, a heartbeat, or none at all.
404+
*/
405+
private function determine_sync_type( $site_details, $sync_data ) {
406+
$current_timestamp = $site_details['timestamp'];
407+
$last_sync_timestamp = $sync_data['last_synced'] ?? 0;
408+
$last_full_sync_timestamp = $sync_data['last_full_synced'] ?? 0;
409+
410+
// Safeguard a reset on the timestamps if they have been incorrectly altered.
411+
$max_allowed_timestamp = $current_timestamp + ( self::MINUTE_IN_MS * 5 );
412+
if ( $last_sync_timestamp > $max_allowed_timestamp || $last_full_sync_timestamp > $max_allowed_timestamp ) {
413+
$last_full_sync_timestamp = 0;
414+
$last_sync_timestamp = 0;
415+
}
416+
417+
// Send a full sync at least once per day.
418+
if ( $current_timestamp - $last_full_sync_timestamp > self::DAY_IN_MS ) {
419+
return 'full';
420+
}
421+
422+
$current_data_hash = $this->get_site_details_data_hash( $site_details );
423+
$last_data_hash = $sync_data['last_sync_hash'] ?? '';
424+
425+
// Send a full sync if the data has changed.
426+
if ( $current_data_hash !== $last_data_hash ) {
427+
return 'full';
428+
}
375429

376-
vip_safe_wp_remote_request( rtrim( $url, '/' ) . '/sites', false, 3, 5, 10, $args );
430+
// Send a heartbeat if it's been more than 25 minutes (1/3 of the stale threshold).
431+
if ( $current_timestamp - $last_sync_timestamp > self::MINUTE_IN_MS * 25 ) {
432+
return 'heartbeat';
377433
}
434+
435+
return 'none';
436+
}
437+
438+
/**
439+
* Hashes the data for comparison purposes, removing the timestamp.
440+
*/
441+
private function get_site_details_data_hash( $site_details ) {
442+
unset( $site_details['timestamp'] );
443+
return hash( 'sha256', wp_json_encode( $site_details ) );
444+
}
445+
446+
/**
447+
* Sends sync data to the service.
448+
*/
449+
private function send_sync( $endpoint, $body ) {
450+
if ( ! defined( 'VIP_SERVICES_AUTH_TOKENS' ) || empty( VIP_SERVICES_AUTH_TOKENS ) ) {
451+
return false;
452+
}
453+
454+
$auth_tokens = json_decode( base64_decode( VIP_SERVICES_AUTH_TOKENS ), true );
455+
456+
$url = $auth_tokens['site']['vip-site-details']['url'] ?? null;
457+
$token = $auth_tokens['site']['vip-site-details']['token'] ?? null;
458+
if ( ! $url || ! $token ) {
459+
return false;
460+
}
461+
462+
$response = vip_safe_wp_remote_request( rtrim( $url, '/' ) . $endpoint, false, 3, 5, 10, [
463+
'method' => 'PUT',
464+
'body' => wp_json_encode( $body ),
465+
'headers' => array(
466+
'Authorization' => 'Bearer ' . $token,
467+
'Content-Type' => 'application/json',
468+
),
469+
] );
470+
471+
$response_code = (int) wp_remote_retrieve_response_code( $response );
472+
if ( is_wp_error( $response ) || 200 !== $response_code ) {
473+
return false;
474+
}
475+
476+
$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
477+
return isset( $response_body['updated'] ) && true === $response_body['updated'];
378478
}
379479

380480
/**

search/search-dev-tools/package-lock.json

+26-21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)