PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 4.2.5
Jetpack – WP Security, Backup, Speed, & Growth v4.2.5
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 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
jetpack / sync / class.jetpack-sync-module-full-sync.php
class.jetpack-sync-module-full-sync.php
290 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This class does a full resync of the database by
5 * enqueuing an outbound action for every single object
6 * that we care about.
7 *
8 * This class, and its related class Jetpack_Sync_Module, contain a few non-obvious optimisations that should be explained:
9 * - we fire an action called jetpack_full_sync_start so that WPCOM can erase the contents of the cached database
10 * - for each object type, we page through the object IDs and enqueue them by firing some monitored actions
11 * - we load the full objects for those IDs in chunks of Jetpack_Sync_Module::ARRAY_CHUNK_SIZE (to reduce the number of MySQL calls)
12 * - we fire a trigger for the entire array which the Jetpack_Sync_Listener then serializes and queues.
13 */
14
15 require_once 'class.jetpack-sync-wp-replicastore.php';
16
17 class Jetpack_Sync_Module_Full_Sync extends Jetpack_Sync_Module {
18 const STATUS_OPTION_PREFIX = 'jetpack_sync_full_';
19 const FULL_SYNC_TIMEOUT = 3600;
20
21 private $items_added_since_last_pause;
22 private $last_pause_time;
23 private $queue_rate_limit;
24
25 public function name() {
26 return 'full-sync';
27 }
28
29 function init_full_sync_listeners( $callable ) {
30 // synthetic actions for full sync
31 add_action( 'jetpack_full_sync_start', $callable );
32 add_action( 'jetpack_full_sync_end', $callable );
33 add_action( 'jetpack_full_sync_cancelled', $callable );
34 }
35
36 function init_before_send() {
37 // this is triggered after actions have been processed on the server
38 add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
39 }
40
41 function start( $modules = null ) {
42 $was_already_running = $this->is_started() && ! $this->is_finished();
43
44 // remove all evidence of previous full sync items and status
45 $this->reset_data();
46
47 $this->enable_queue_rate_limit();
48
49 if ( $was_already_running ) {
50 /**
51 * Fires when a full sync is cancelled.
52 *
53 * @since 4.2.0
54 */
55 do_action( 'jetpack_full_sync_cancelled' );
56 }
57
58 /**
59 * Fires when a full sync begins. This action is serialized
60 * and sent to the server so that it knows a full sync is coming.
61 *
62 * @since 4.2.0
63 */
64 do_action( 'jetpack_full_sync_start', $modules );
65 $this->update_status_option( 'started', time() );
66
67 // configure modules
68 if ( ! is_array( $modules ) ) {
69 $modules = array();
70 }
71
72 if ( isset( $modules['users'] ) && 'initial' === $modules['users'] ) {
73 $user_module = Jetpack_Sync_Modules::get_module( 'users' );
74 $modules['users'] = $user_module->get_initial_sync_user_config();
75 }
76
77 // by default, all modules are fully enabled
78 if ( count( $modules ) === 0 ) {
79 $default_module_config = true;
80 } else {
81 $default_module_config = false;
82 }
83
84 // set default configuration, calculate totals, and save configuration if totals > 0
85 foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
86 $module_name = $module->name();
87 if ( ! isset( $modules[ $module_name ] ) ) {
88 $modules[ $module_name ] = $default_module_config;
89 }
90
91 // check if this module is enabled
92 if ( ! ( $module_config = $modules[ $module_name ] ) ) {
93 continue;
94 }
95
96 $total_items = $module->estimate_full_sync_actions( $module_config );
97
98 if ( ! is_null( $total_items ) && $total_items > 0 ) {
99 $this->update_status_option( "{$module_name}_total", $total_items );
100 $this->update_status_option( "{$module_name}_config", $module_config );
101 }
102 }
103
104 foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
105 $module_name = $module->name();
106 $module_config = $modules[ $module_name ];
107
108 // check if this module is enabled
109 if ( ! $module_config ) {
110 continue;
111 }
112
113 $items_enqueued = $module->enqueue_full_sync_actions( $module_config );
114
115 if ( ! is_null( $items_enqueued ) && $items_enqueued > 0 ) {
116 $this->update_status_option( "{$module_name}_queued", $items_enqueued );
117 }
118 }
119
120 $this->update_status_option( 'queue_finished', time() );
121
122 $store = new Jetpack_Sync_WP_Replicastore();
123
124 /**
125 * Fires when a full sync ends. This action is serialized
126 * and sent to the server with checksums so that we can confirm the
127 * sync was successful.
128 *
129 * @since 4.2.0
130 */
131 do_action( 'jetpack_full_sync_end', $store->checksum_all() );
132
133 $this->disable_queue_rate_limit();
134
135 return true;
136 }
137
138 function update_sent_progress_action( $actions ) {
139
140 // quick way to map to first items with an array of arrays
141 $actions_with_counts = array_count_values( array_map( 'reset', $actions ) );
142
143 if ( ! $this->is_started() || $this->is_finished() ) {
144 return;
145 }
146
147 if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
148 $this->update_status_option( 'sent_started', time() );
149 }
150
151 foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
152 $module_actions = $module->get_full_sync_actions();
153 $status_option_name = "{$module->name()}_sent";
154 $items_sent = $this->get_status_option( $status_option_name, 0 );
155
156 foreach ( $module_actions as $module_action ) {
157 if ( isset( $actions_with_counts[ $module_action ] ) ) {
158 $items_sent += $actions_with_counts[ $module_action ];
159 }
160 }
161
162 if ( $items_sent > 0 ) {
163 $this->update_status_option( $status_option_name, $items_sent );
164 }
165 }
166
167 if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
168 $this->update_status_option( 'finished', time() );
169 }
170 }
171
172 public function is_started() {
173 return !! $this->get_status_option( 'started' );
174 }
175
176 public function is_finished() {
177 return !! $this->get_status_option( 'finished' );
178 }
179
180 public function get_status() {
181 $status = array(
182 'started' => $this->get_status_option( 'started' ),
183 'queue_finished' => $this->get_status_option( 'queue_finished' ),
184 'sent_started' => $this->get_status_option( 'sent_started' ),
185 'finished' => $this->get_status_option( 'finished' ),
186 'sent' => array(),
187 'queue' => array(),
188 'config' => array(),
189 'total' => array(),
190 );
191
192 foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
193 $name = $module->name();
194
195 if ( $total = $this->get_status_option( "{$name}_total" ) ) {
196 $status[ 'total' ][ $name ] = $total;
197 }
198
199 if ( $queued = $this->get_status_option( "{$name}_queued" ) ) {
200 $status[ 'queue' ][ $name ] = $queued;
201 }
202
203 if ( $sent = $this->get_status_option( "{$name}_sent" ) ) {
204 $status[ 'sent' ][ $name ] = $sent;
205 }
206
207 if ( $config = $this->get_status_option( "{$name}_config" ) ) {
208 $status[ 'config' ][ $name ] = $config;
209 }
210 }
211
212 return $status;
213 }
214
215 public function clear_status() {
216 $prefix = self::STATUS_OPTION_PREFIX;
217 delete_option( "{$prefix}_started" );
218 delete_option( "{$prefix}_queue_finished" );
219 delete_option( "{$prefix}_sent_started" );
220 delete_option( "{$prefix}_finished" );
221
222 foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
223 delete_option( "{$prefix}_{$module->name()}_total" );
224 delete_option( "{$prefix}_{$module->name()}_queued" );
225 delete_option( "{$prefix}_{$module->name()}_sent" );
226 delete_option( "{$prefix}_{$module->name()}_config" );
227 }
228 }
229
230 public function reset_data() {
231 $this->clear_status();
232 require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php';
233 $listener = Jetpack_Sync_Listener::get_instance();
234 $listener->get_full_sync_queue()->reset();
235 }
236
237 private function get_status_option( $option, $default = null ) {
238 $prefix = self::STATUS_OPTION_PREFIX;
239
240 $value = get_option( "{$prefix}_{$option}", $default );
241
242 if ( ! $value ) {
243 // don't cast to int if we didn't find a value - we want to preserve null or false as sentinals
244 return $default;
245 }
246
247 return is_numeric( $value ) ? intval( $value ) : $value;
248 }
249
250 private function update_status_option( $name, $value ) {
251 $prefix = self::STATUS_OPTION_PREFIX;
252 update_option( "{$prefix}_{$name}", $value, false );
253 }
254
255 private function enable_queue_rate_limit() {
256 $this->queue_rate_limit = Jetpack_Sync_Settings::get_setting( 'queue_max_writes_sec' );
257 $this->items_added_since_last_pause = 0;
258 $this->last_pause_time = microtime( true );
259
260 add_action( 'jpsq_item_added', array( $this, 'queue_item_added' ) );
261 add_action( 'jpsq_items_added', array( $this, 'queue_items_added' ) );
262 }
263
264 private function disable_queue_rate_limit() {
265 remove_action( 'jpsq_item_added', array( $this, 'queue_item_added' ) );
266 remove_action( 'jpsq_items_added', array( $this, 'queue_items_added' ) );
267 }
268
269 public function queue_item_added() {
270 $this->queue_items_added( 1 );
271 }
272
273 public function queue_items_added( $item_count ) {
274 // jpsq_item_added and jpsq_items_added both exec 1 db query,
275 // so we ignore $item_count and treat it as always 1
276 $this->items_added_since_last_pause += 1;
277
278 if ( $this->items_added_since_last_pause > $this->queue_rate_limit ) {
279 // sleep for the rest of the second
280 $sleep_til = $this->last_pause_time + 1.0;
281 $sleep_duration = $sleep_til - microtime( true );
282 if ( $sleep_duration > 0.0 ) {
283 usleep( $sleep_duration * 1000000 );
284 $this->last_pause_time = microtime( true );
285 }
286 $this->items_added_since_last_pause = 0;
287 }
288 }
289 }
290