PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
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 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-premium-analytics / src / Sync / class-sync-status-tracker.php

class-sync-status-tracker.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at jetpack_vendor/automattic/jetpack-premium-analytics/src/Sync/class-sync-status-tracker.php

226 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
226