PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-premium-analytics/src/Sync/class-sync-status-tracker.php +225 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,225 @@
1 +<?php
2 +/**
3 + * Analytics-aware sync milestone tracker.
4 + *
5 + * @package automattic/jetpack-premium-analytics
6 + */
7 +
8 +namespace Automattic\Jetpack\PremiumAnalytics\Sync;
9 +
10 +use Automattic\Jetpack\Sync\Modules;
11 +
12 +/**
13 + * Listens for the end of the analytics full sync and persists a one-time milestone option.
14 + *
15 + * /jetpack/v4/sync/status reports current sync state but not whether the analytics full sync
16 + * has completed at least once — which is what tells the dashboard's store section its numbers are complete.
17 + */
18 +class Sync_Status_Tracker {
19 +
20 + /**
21 + * Milestone (unix ts) for the analytics-module initial full sync. Marks the
22 + * dashboard's store data as complete.
23 + */
24 + const INITIAL_ANALYTICS_SYNC_OPTION = 'jetpack_premium_analytics_initial_analytics_sync_finished';
25 +
26 + /**
27 + * Default sync-module names whose end-of-sync event flips the milestone. Provided by
28 + * WooCommerce Analytics, which registers a custom full-sync module under this key.
29 + *
30 + * @var string[]
31 + */
32 + const ANALYTICS_SYNC_MODULES = array( 'woocommerce_analytics' );
33 +
34 + /**
35 + * Action hook fired once when the analytics milestone flips. Consumer plugins
36 + * use this to fire one-time side-effects keyed to store data (emails, tracking
37 + * events, etc.).
38 + *
39 + * @var string
40 + */
41 + const MILESTONE_ACTION = 'jetpack_premium_analytics_initial_full_sync_finished';
42 +
43 + /**
44 + * Jetpack core's sync-status REST route, enriched with the milestone.
45 + */
46 + const SYNC_STATUS_ROUTE = '/jetpack/v4/sync/status';
47 +
48 + /**
49 + * Wire up the listener, the script-data filter, and the sync-status enricher.
50 + *
51 + * Idempotent: safe to call more than once.
52 + *
53 + * @return void
54 + */
55 + public static function configure() {
56 + add_action( 'jetpack_sync_processed_actions', array( self::class, 'on_sync_processed_actions' ) );
57 + add_filter( 'jetpack_admin_js_script_data', array( self::class, 'inject_script_data' ) );
58 + add_filter( 'rest_post_dispatch', array( self::class, 'enrich_sync_status_response' ), 10, 3 );
59 + }
60 +
61 + /**
62 + * Append the milestone to Jetpack core's GET /jetpack/v4/sync/status response.
63 + *
64 + * Unlike the one-time script-data snapshot ({@see inject_script_data()}), this stays live for
65 + * in-session completion — but touches only the already-authorized, successful status payload.
66 + *
67 + * @param mixed $response Result to send to the client. Usually a WP_REST_Response.
68 + * @param mixed $server The REST server instance (unused).
69 + * @param mixed $request The request used to generate the response.
70 + * @return mixed
71 + */
72 + public static function enrich_sync_status_response( $response, $server, $request ) {
73 + if ( ! $request instanceof \WP_REST_Request
74 + || self::SYNC_STATUS_ROUTE !== $request->get_route()
75 + || ! $response instanceof \WP_REST_Response
76 + || $response->is_error() ) {
77 + return $response;
78 + }
79 +
80 + $data = $response->get_data();
81 + if ( is_array( $data ) ) {
82 + $data['initial_full_sync_finished'] = self::milestone();
83 + $response->set_data( $data );
84 + }
85 +
86 + return $response;
87 + }
88 +
89 + /**
90 + * On every batch of processed sync actions, look for the gating
91 + * jetpack_full_sync_end and flip the milestone if it hasn't already fired.
92 + *
93 + * @param array $actions Processed sync actions.
94 + * @return void
95 + */
96 + public static function on_sync_processed_actions( array $actions ): void {
97 + // Bail before the per-batch full-sync lookup ($module->get_status() bypasses
98 + // the status cache) once the milestone is set.
99 + if ( self::milestone_reached() ) {
100 + return;
101 + }
102 +
103 + $module = Modules::get_module( 'full-sync' );
104 + if ( ! $module ) {
105 + return;
106 + }
107 + '@phan-var \Automattic\Jetpack\Sync\Modules\Full_Sync_Immediately|\Automattic\Jetpack\Sync\Modules\Full_Sync $module';
108 +
109 + self::maybe_set_milestone( $module->get_status(), $actions );
110 + }
111 +
112 + /**
113 + * Resolve the configured sync-module names for analytics.
114 + *
115 + * @return string[]
116 + */
117 + public static function get_analytics_sync_modules(): array {
118 + /**
119 + * Filter the sync-module names whose end-of-sync flips the analytics
120 + * milestone. Consumer plugins that register custom full-sync modules
121 + * can add their module keys here.
122 + *
123 + * @param string[] $module_names Default: array( 'woocommerce_analytics' ).
124 + */
125 + return (array) apply_filters( 'jetpack_premium_analytics_sync_modules', self::ANALYTICS_SYNC_MODULES );
126 + }
127 +
128 + /**
129 + * Decide whether the supplied full-sync status and actions represent the analytics sync ending.
130 + *
131 + * Only a full sync whose config includes an analytics module counts — a generic sync can't mark
132 + * store data complete; split out so tests can exercise it without the sync module registry.
133 + *
134 + * @param array $full_status Result of Full_Sync_Immediately::get_status().
135 + * @param array $actions Processed sync actions.
136 + * @return void
137 + */
138 + public static function maybe_set_milestone( array $full_status, array $actions ): void {
139 + if ( self::milestone_reached() ) {
140 + return;
141 + }
142 +
143 + $config = isset( $full_status['config'] ) ? (array) $full_status['config'] : array();
144 + $active = array_filter(
145 + self::get_analytics_sync_modules(),
146 + static function ( $module_name ) use ( $config ) {
147 + return ! empty( $config[ $module_name ] );
148 + }
149 + );
150 + if ( ! $active ) {
151 + return;
152 + }
153 +
154 + $end_action = self::find_full_sync_end_action( $actions );
155 + if ( ! $end_action ) {
156 + return;
157 + }
158 +
159 + // The last update_status() call in Full_Sync_Immediately::send() runs after jetpack_full_sync_end
160 + // fires, so the action's own timestamp is the most reliable "finished at" value (Year 2038 problem aside).
161 + $finished_at = isset( $end_action[3] ) ? (int) $end_action[3] : 0;
162 + if ( $finished_at <= 0 ) {
163 + // Defensive: avoid persisting a zero timestamp, which would equal the "not yet set" sentinel
164 + // and cause the listener to re-trigger on the next batch.
165 + return;
166 + }
167 + $full_status['finished'] = $finished_at;
168 + update_option( self::INITIAL_ANALYTICS_SYNC_OPTION, $finished_at );
169 +
170 + /**
171 + * Fires once when the analytics-relevant initial full sync completes.
172 + *
173 + * @param array $full_status Final full-sync status (with `finished` timestamp).
174 + */
175 + do_action( self::MILESTONE_ACTION, $full_status );
176 + }
177 +
178 + /**
179 + * Inject the milestone into JetpackScriptData so the dashboard can read it at
180 + * page load without an extra HTTP roundtrip.
181 + *
182 + * @param array $data The script data passed by the assets package.
183 + * @return array
184 + */
185 + public static function inject_script_data( array $data ): array {
186 + $data['premium_analytics'] = array(
187 + 'initial_full_sync_finished' => self::milestone(),
188 + );
189 +
190 + return $data;
191 + }
192 +
193 + /**
194 + * The milestone timestamp (0 if not yet reached).
195 + *
196 + * @return int
197 + */
198 + private static function milestone(): int {
199 + return (int) get_option( self::INITIAL_ANALYTICS_SYNC_OPTION, 0 );
200 + }
201 +
202 + /**
203 + * Whether the milestone has fired.
204 + *
205 + * @return bool
206 + */
207 + private static function milestone_reached(): bool {
208 + return self::milestone() > 0;
209 + }
210 +
211 + /**
212 + * Find the jetpack_full_sync_end action in a processed-actions list.
213 + *
214 + * @param array $actions Actions list.
215 + * @return array|null
216 + */
217 + private static function find_full_sync_end_action( array $actions ): ?array {
218 + foreach ( $actions as $action ) {
219 + if ( isset( $action[0] ) && 'jetpack_full_sync_end' === $action[0] ) {
220 + return $action;
221 + }
222 + }
223 + return null;
224 + }
225 +}