SystemStatusTools   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 559
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 264
dl 0
loc 559
rs 9.44
c 0
b 0
f 0
wmc 37

21 Methods

Rating   Name   Duplication   Size   Complexity  
A regenerate_product_lookup_tables() 0 5 2
A get_item() 0 15 2
A get_items() 0 18 2
A prepare_links() 0 10 1
A register_routes() 0 40 1
B execute_tool() 0 34 8
A delete_taxes() 0 6 1
A db_update_routine() 0 6 1
A get_collection_params() 0 3 1
A get_item_schema() 0 55 1
A regenerate_thumbnails() 0 3 1
A reset_roles() 0 4 1
A clear_expired_download_permissions() 0 14 1
A clear_transients() 0 15 3
B get_tools() 0 86 4
A clear_sessions() 0 8 1
A recount_terms() 0 18 1
A update_item() 0 28 2
A clear_expired_transients() 0 3 1
A delete_orphaned_variations() 0 13 1
A install_pages() 0 3 1
1
<?php
2
/**
3
 * REST API WC System Status Tools Controller
4
 *
5
 * Handles requests to the /system_status/tools/* endpoints.
6
 *
7
 * @package Automattic/WooCommerce/RestApi
8
 */
9
10
namespace Automattic\WooCommerce\RestApi\Controllers\Version4;
11
12
defined( 'ABSPATH' ) || exit;
13
14
/**
15
 * REST API System Status Tools controller class.
16
 */
17
class SystemStatusTools extends AbstractController {
18
19
	/**
20
	 * Route base.
21
	 *
22
	 * @var string
23
	 */
24
	protected $rest_base = 'system_status/tools';
25
26
	/**
27
	 * Permission to check.
28
	 *
29
	 * @var string
30
	 */
31
	protected $resource_type = 'system_status';
32
33
	/**
34
	 * Register the routes for /system_status/tools/*.
35
	 */
36
	public function register_routes() {
37
		register_rest_route(
38
			$this->namespace,
39
			'/' . $this->rest_base,
40
			array(
41
				array(
42
					'methods'             => \WP_REST_Server::READABLE,
43
					'callback'            => array( $this, 'get_items' ),
44
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
45
					'args'                => $this->get_collection_params(),
46
				),
47
				'schema' => array( $this, 'get_public_item_schema' ),
48
			),
49
			true
50
		);
51
52
		register_rest_route(
53
			$this->namespace,
54
			'/' . $this->rest_base . '/(?P<id>[\w-]+)',
55
			array(
56
				'args'   => array(
57
					'id' => array(
58
						'description' => __( 'Unique identifier for the resource.', 'woocommerce-rest-api' ),
59
						'type'        => 'string',
60
					),
61
				),
62
				array(
63
					'methods'             => \WP_REST_Server::READABLE,
64
					'callback'            => array( $this, 'get_item' ),
65
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
66
				),
67
				array(
68
					'methods'             => \WP_REST_Server::EDITABLE,
69
					'callback'            => array( $this, 'update_item' ),
70
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
71
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
72
				),
73
				'schema' => array( $this, 'get_public_item_schema' ),
74
			),
75
			true
76
		);
77
	}
78
79
	/**
80
	 * A list of available tools for use in the system status section.
81
	 * 'button' becomes 'action' in the API.
82
	 *
83
	 * @return array
84
	 */
85
	public function get_tools() {
86
		$tools = array(
87
			'clear_transients'                   => array(
88
				'name'   => __( 'WooCommerce transients', 'woocommerce-rest-api' ),
89
				'button' => __( 'Clear transients', 'woocommerce-rest-api' ),
90
				'desc'   => __( 'This tool will clear the product/shop transients cache.', 'woocommerce-rest-api' ),
91
			),
92
			'clear_expired_transients'           => array(
93
				'name'   => __( 'Expired transients', 'woocommerce-rest-api' ),
94
				'button' => __( 'Clear transients', 'woocommerce-rest-api' ),
95
				'desc'   => __( 'This tool will clear ALL expired transients from WordPress.', 'woocommerce-rest-api' ),
96
			),
97
			'delete_orphaned_variations'         => array(
98
				'name'   => __( 'Orphaned variations', 'woocommerce-rest-api' ),
99
				'button' => __( 'Delete orphaned variations', 'woocommerce-rest-api' ),
100
				'desc'   => __( 'This tool will delete all variations which have no parent.', 'woocommerce-rest-api' ),
101
			),
102
			'clear_expired_download_permissions' => array(
103
				'name'   => __( 'Used-up download permissions', 'woocommerce-rest-api' ),
104
				'button' => __( 'Clean up download permissions', 'woocommerce-rest-api' ),
105
				'desc'   => __( 'This tool will delete expired download permissions and permissions with 0 remaining downloads.', 'woocommerce-rest-api' ),
106
			),
107
			'regenerate_product_lookup_tables'   => array(
108
				'name'   => __( 'Product lookup tables', 'woocommerce-rest-api' ),
109
				'button' => __( 'Regenerate', 'woocommerce-rest-api' ),
110
				'desc'   => __( 'This tool will regenerate product lookup table data. This process may take a while.', 'woocommerce-rest-api' ),
111
			),
112
			'recount_terms'                      => array(
113
				'name'   => __( 'Term counts', 'woocommerce-rest-api' ),
114
				'button' => __( 'Recount terms', 'woocommerce-rest-api' ),
115
				'desc'   => __( 'This tool will recount product terms - useful when changing your settings in a way which hides products from the catalog.', 'woocommerce-rest-api' ),
116
			),
117
			'reset_roles'                        => array(
118
				'name'   => __( 'Capabilities', 'woocommerce-rest-api' ),
119
				'button' => __( 'Reset capabilities', 'woocommerce-rest-api' ),
120
				'desc'   => __( 'This tool will reset the admin, customer and shop_manager roles to default. Use this if your users cannot access all of the WooCommerce admin pages.', 'woocommerce-rest-api' ),
121
			),
122
			'clear_sessions'                     => array(
123
				'name'   => __( 'Clear customer sessions', 'woocommerce-rest-api' ),
124
				'button' => __( 'Clear', 'woocommerce-rest-api' ),
125
				'desc'   => sprintf(
126
					'<strong class="red">%1$s</strong> %2$s',
127
					__( 'Note:', 'woocommerce-rest-api' ),
128
					__( 'This tool will delete all customer session data from the database, including current carts and saved carts in the database.', 'woocommerce-rest-api' )
129
				),
130
			),
131
			'install_pages'                      => array(
132
				'name'   => __( 'Create default WooCommerce pages', 'woocommerce-rest-api' ),
133
				'button' => __( 'Create pages', 'woocommerce-rest-api' ),
134
				'desc'   => sprintf(
135
					'<strong class="red">%1$s</strong> %2$s',
136
					__( 'Note:', 'woocommerce-rest-api' ),
137
					__( 'This tool will install all the missing WooCommerce pages. Pages already defined and set up will not be replaced.', 'woocommerce-rest-api' )
138
				),
139
			),
140
			'delete_taxes'                       => array(
141
				'name'   => __( 'Delete WooCommerce tax rates', 'woocommerce-rest-api' ),
142
				'button' => __( 'Delete tax rates', 'woocommerce-rest-api' ),
143
				'desc'   => sprintf(
144
					'<strong class="red">%1$s</strong> %2$s',
145
					__( 'Note:', 'woocommerce-rest-api' ),
146
					__( 'This option will delete ALL of your tax rates, use with caution. This action cannot be reversed.', 'woocommerce-rest-api' )
147
				),
148
			),
149
			'regenerate_thumbnails'              => array(
150
				'name'   => __( 'Regenerate shop thumbnails', 'woocommerce-rest-api' ),
151
				'button' => __( 'Regenerate', 'woocommerce-rest-api' ),
152
				'desc'   => __( 'This will regenerate all shop thumbnails to match your theme and/or image settings.', 'woocommerce-rest-api' ),
153
			),
154
			'db_update_routine'                  => array(
155
				'name'   => __( 'Update database', 'woocommerce-rest-api' ),
156
				'button' => __( 'Update database', 'woocommerce-rest-api' ),
157
				'desc'   => sprintf(
158
					'<strong class="red">%1$s</strong> %2$s',
159
					__( 'Note:', 'woocommerce-rest-api' ),
160
					__( 'This tool will update your WooCommerce database to the latest version. Please ensure you make sufficient backups before proceeding.', 'woocommerce-rest-api' )
161
				),
162
			),
163
		);
164
165
		// Jetpack does the image resizing heavy lifting so you don't have to.
166
		if ( ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'photon' ) ) || ! apply_filters( 'woocommerce_background_image_regeneration', true ) ) {
0 ignored issues
show
Bug introduced by
The type Automattic\WooCommerce\R...ollers\Version4\Jetpack was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
167
			unset( $tools['regenerate_thumbnails'] );
168
		}
169
170
		return apply_filters( 'woocommerce_debug_tools', $tools );
171
	}
172
173
	/**
174
	 * Get a list of system status tools.
175
	 *
176
	 * @param \WP_REST_Request $request Full details about the request.
177
	 * @return \WP_Error|\WP_REST_Response
178
	 */
179
	public function get_items( $request ) {
180
		$tools = array();
181
		foreach ( $this->get_tools() as $id => $tool ) {
182
			$tools[] = $this->prepare_response_for_collection(
183
				$this->prepare_item_for_response(
184
					array(
185
						'id'          => $id,
186
						'name'        => $tool['name'],
187
						'action'      => $tool['button'],
188
						'description' => $tool['desc'],
189
					),
190
					$request
191
				)
192
			);
193
		}
194
195
		$response = rest_ensure_response( $tools );
196
		return $response;
197
	}
198
199
	/**
200
	 * Return a single tool.
201
	 *
202
	 * @param  \WP_REST_Request $request Request data.
203
	 * @return \WP_Error|\WP_REST_Response
204
	 */
205
	public function get_item( $request ) {
206
		$tools = $this->get_tools();
207
		if ( empty( $tools[ $request['id'] ] ) ) {
208
			return new \WP_Error( 'woocommerce_rest_system_status_tool_invalid_id', __( 'Invalid tool ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
209
		}
210
		$tool = $tools[ $request['id'] ];
211
		return rest_ensure_response(
212
			$this->prepare_item_for_response(
213
				array(
214
					'id'          => $request['id'],
215
					'name'        => $tool['name'],
216
					'action'      => $tool['button'],
217
					'description' => $tool['desc'],
218
				),
219
				$request
220
			)
221
		);
222
	}
223
224
	/**
225
	 * Update (execute) a tool.
226
	 *
227
	 * @param  \WP_REST_Request $request Request data.
228
	 * @return \WP_Error|\WP_REST_Response
229
	 */
230
	public function update_item( $request ) {
231
		$tools = $this->get_tools();
232
		if ( empty( $tools[ $request['id'] ] ) ) {
233
			return new \WP_Error( 'woocommerce_rest_system_status_tool_invalid_id', __( 'Invalid tool ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
234
		}
235
236
		$tool = $tools[ $request['id'] ];
237
		$tool = array(
238
			'id'          => $request['id'],
239
			'name'        => $tool['name'],
240
			'action'      => $tool['button'],
241
			'description' => $tool['desc'],
242
		);
243
244
		$execute_return = $this->execute_tool( $request['id'] );
245
		$tool           = array_merge( $tool, $execute_return );
246
247
		/**
248
		 * Fires after a WooCommerce REST system status tool has been executed.
249
		 *
250
		 * @param array           $tool    Details about the tool that has been executed.
251
		 * @param \WP_REST_Request $request The current \WP_REST_Request object.
252
		 */
253
		do_action( 'woocommerce_rest_insert_system_status_tool', $tool, $request );
254
255
		$request->set_param( 'context', 'edit' );
256
		$response = $this->prepare_item_for_response( $tool, $request );
257
		return rest_ensure_response( $response );
258
	}
259
260
	/**
261
	 * Get the system status tools schema, conforming to JSON Schema.
262
	 *
263
	 * @return array
264
	 */
265
	public function get_item_schema() {
266
		$schema = array(
267
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
268
			'title'      => 'system_status_tool',
269
			'type'       => 'object',
270
			'properties' => array(
271
				'id'          => array(
272
					'description' => __( 'A unique identifier for the tool.', 'woocommerce-rest-api' ),
273
					'type'        => 'string',
274
					'context'     => array( 'view', 'edit' ),
275
					'arg_options' => array(
276
						'sanitize_callback' => 'sanitize_title',
277
					),
278
				),
279
				'name'        => array(
280
					'description' => __( 'Tool name.', 'woocommerce-rest-api' ),
281
					'type'        => 'string',
282
					'context'     => array( 'view', 'edit' ),
283
					'arg_options' => array(
284
						'sanitize_callback' => 'sanitize_text_field',
285
					),
286
				),
287
				'action'      => array(
288
					'description' => __( 'What running the tool will do.', 'woocommerce-rest-api' ),
289
					'type'        => 'string',
290
					'context'     => array( 'view', 'edit' ),
291
					'arg_options' => array(
292
						'sanitize_callback' => 'sanitize_text_field',
293
					),
294
				),
295
				'description' => array(
296
					'description' => __( 'Tool description.', 'woocommerce-rest-api' ),
297
					'type'        => 'string',
298
					'context'     => array( 'view', 'edit' ),
299
					'arg_options' => array(
300
						'sanitize_callback' => 'sanitize_text_field',
301
					),
302
				),
303
				'success'     => array(
304
					'description' => __( 'Did the tool run successfully?', 'woocommerce-rest-api' ),
305
					'type'        => 'boolean',
306
					'context'     => array( 'edit' ),
307
				),
308
				'message'     => array(
309
					'description' => __( 'Tool return message.', 'woocommerce-rest-api' ),
310
					'type'        => 'string',
311
					'context'     => array( 'edit' ),
312
					'arg_options' => array(
313
						'sanitize_callback' => 'sanitize_text_field',
314
					),
315
				),
316
			),
317
		);
318
319
		return $this->add_additional_fields_schema( $schema );
320
	}
321
322
	/**
323
	 * Prepare links for the request.
324
	 *
325
	 * @param mixed            $item Object to prepare.
326
	 * @param \WP_REST_Request $request Request object.
327
	 * @return array
328
	 */
329
	protected function prepare_links( $item, $request ) {
330
		$base  = '/' . $this->namespace . '/' . $this->rest_base;
331
		$links = array(
332
			'item' => array(
333
				'href'       => rest_url( trailingslashit( $base ) . $item['id'] ),
334
				'embeddable' => true,
335
			),
336
		);
337
338
		return $links;
339
	}
340
341
	/**
342
	 * Get any query params needed.
343
	 *
344
	 * @return array
345
	 */
346
	public function get_collection_params() {
347
		return array(
348
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
349
		);
350
	}
351
352
	/**
353
	 * Actually executes a tool.
354
	 *
355
	 * @throws Exception When the tool cannot run.
356
	 * @param  string $tool Tool.
357
	 * @return array
358
	 */
359
	public function execute_tool( $tool ) {
360
		$ran   = false;
361
		$tools = $this->get_tools();
362
363
		try {
364
			if ( ! isset( $tools[ $tool ] ) ) {
365
				throw new Exception( __( 'There was an error calling this tool. There is no callback present.', 'woocommerce-rest-api' ) );
0 ignored issues
show
Bug introduced by
The type Automattic\WooCommerce\R...lers\Version4\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
366
			}
367
368
			$callback = isset( $tools[ $tool ]['callback'] ) ? $tools[ $tool ]['callback'] : array( $this, $tool );
369
370
			if ( ! is_callable( $callback ) ) {
371
				throw new Exception( __( 'There was an error calling this tool. Invalid callback.', 'woocommerce-rest-api' ) );
372
			}
373
374
			$message = call_user_func( $callback );
375
376
			if ( false === $message ) {
377
				throw new Exception( __( 'There was an error calling this tool. Invalid callback.', 'woocommerce-rest-api' ) );
378
			}
379
380
			if ( empty( $message ) || ! is_string( $message ) ) {
381
				$message = __( 'Tool ran.', 'woocommerce-rest-api' );
382
			}
383
384
			$ran = true;
385
		} catch ( Exception $e ) {
386
			$message = $e->getMessage();
387
			$ran     = false;
388
		}
389
390
		return array(
391
			'success' => $ran,
392
			'message' => $message,
393
		);
394
	}
395
396
	/**
397
	 * Tool: clear_transients.
398
	 *
399
	 * @return string Success message.
400
	 */
401
	protected function clear_transients() {
402
		wc_delete_product_transients();
403
		wc_delete_shop_order_transients();
404
		delete_transient( 'wc_count_comments' );
405
406
		$attribute_taxonomies = wc_get_attribute_taxonomies();
407
408
		if ( ! empty( $attribute_taxonomies ) ) {
409
			foreach ( $attribute_taxonomies as $attribute ) {
410
				delete_transient( 'wc_layered_nav_counts_pa_' . $attribute->attribute_name );
411
			}
412
		}
413
414
		\WC_Cache_Helper::get_transient_version( 'shipping', true );
415
		return __( 'Product transients cleared', 'woocommerce-rest-api' );
416
	}
417
418
	/**
419
	 * Tool: clear_expired_transients.
420
	 *
421
	 * @return string Success message.
422
	 */
423
	protected function clear_expired_transients() {
424
		/* translators: %d: amount of expired transients */
425
		return sprintf( __( '%d transients rows cleared', 'woocommerce-rest-api' ), wc_delete_expired_transients() );
426
	}
427
428
	/**
429
	 * Tool: delete_orphaned_variations.
430
	 *
431
	 * @return string Success message.
432
	 */
433
	protected function delete_orphaned_variations() {
434
		global $wpdb;
435
436
		$result = absint(
437
			$wpdb->query(
438
				"DELETE products
439
			FROM {$wpdb->posts} products
440
			LEFT JOIN {$wpdb->posts} wp ON wp.ID = products.post_parent
441
			WHERE wp.ID IS NULL AND products.post_type = 'product_variation';"
442
			)
443
		);
444
		/* translators: %d: amount of orphaned variations */
445
		return sprintf( __( '%d orphaned variations deleted', 'woocommerce-rest-api' ), $result );
446
	}
447
448
	/**
449
	 * Tool: clear_expired_download_permissions.
450
	 *
451
	 * @return string Success message.
452
	 */
453
	protected function clear_expired_download_permissions() {
454
		global $wpdb;
455
456
		$result = absint(
457
			$wpdb->query(
458
				$wpdb->prepare(
459
					"DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
460
					WHERE ( downloads_remaining != '' AND downloads_remaining = 0 ) OR ( access_expires IS NOT NULL AND access_expires < %s )",
461
					date( 'Y-m-d', current_time( 'timestamp' ) )
462
				)
463
			)
464
		);
465
		/* translators: %d: amount of permissions */
466
		return sprintf( __( '%d permissions deleted', 'woocommerce-rest-api' ), $result );
467
	}
468
469
	/**
470
	 * Tool: regenerate_product_lookup_tables.
471
	 *
472
	 * @return string Success message.
473
	 */
474
	protected function regenerate_product_lookup_tables() {
475
		if ( ! wc_update_product_lookup_tables_is_running() ) {
476
			wc_update_product_lookup_tables();
477
		}
478
		return __( 'Lookup tables are regenerating', 'woocommerce-rest-api' );
479
	}
480
481
	/**
482
	 * Tool: reset_roles.
483
	 *
484
	 * @return string Success message.
485
	 */
486
	protected function reset_roles() {
487
		\WC_Install::remove_roles();
488
		\WC_Install::create_roles();
489
		return __( 'Roles successfully reset', 'woocommerce-rest-api' );
490
	}
491
492
	/**
493
	 * Tool: recount_terms.
494
	 *
495
	 * @return string Success message.
496
	 */
497
	protected function recount_terms() {
498
		$product_cats = get_terms(
499
			'product_cat',
500
			array(
501
				'hide_empty' => false,
502
				'fields'     => 'id=>parent',
503
			)
504
		);
505
		_wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), true, false );
506
		$product_tags = get_terms(
507
			'product_tag',
508
			array(
509
				'hide_empty' => false,
510
				'fields'     => 'id=>parent',
511
			)
512
		);
513
		_wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), true, false );
514
		return __( 'Terms successfully recounted', 'woocommerce-rest-api' );
515
	}
516
517
	/**
518
	 * Tool: clear_sessions.
519
	 *
520
	 * @return string Success message.
521
	 */
522
	protected function clear_sessions() {
523
		global $wpdb;
524
525
		$wpdb->query( "TRUNCATE {$wpdb->prefix}woocommerce_sessions" );
526
		$result = absint( $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key='_woocommerce_persistent_cart_" . get_current_blog_id() . "';" ) ); // WPCS: unprepared SQL ok.
527
		wp_cache_flush();
528
		/* translators: %d: amount of sessions */
529
		return sprintf( __( 'Deleted all active sessions, and %d saved carts.', 'woocommerce-rest-api' ), absint( $result ) );
530
	}
531
532
	/**
533
	 * Tool: install_pages.
534
	 *
535
	 * @return string Success message.
536
	 */
537
	protected function install_pages() {
538
		\WC_Install::create_pages();
539
		return __( 'All missing WooCommerce pages successfully installed', 'woocommerce-rest-api' );
540
	}
541
542
	/**
543
	 * Tool: delete_taxes.
544
	 *
545
	 * @return string Success message.
546
	 */
547
	protected function delete_taxes() {
548
		global $wpdb;
549
		$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}woocommerce_tax_rates;" );
550
		$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}woocommerce_tax_rate_locations;" );
551
		\WC_Cache_Helper::incr_cache_prefix( 'taxes' );
552
		return __( 'Tax rates successfully deleted', 'woocommerce-rest-api' );
553
	}
554
555
	/**
556
	 * Tool: regenerate_thumbnails.
557
	 *
558
	 * @return string Success message.
559
	 */
560
	protected function regenerate_thumbnails() {
561
		\WC_Regenerate_Images::queue_image_regeneration();
562
		return __( 'Thumbnail regeneration has been scheduled to run in the background.', 'woocommerce-rest-api' );
563
	}
564
565
	/**
566
	 * Tool: db_update_routine.
567
	 *
568
	 * @return string Success message.
569
	 */
570
	protected function db_update_routine() {
571
		$blog_id = get_current_blog_id();
572
		// Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck().
573
		// This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running.
574
		do_action( 'wp_' . $blog_id . '_wc_updater_cron' );
575
		return __( 'Database upgrade routine has been scheduled to run in the background.', 'woocommerce-rest-api' );
576
	}
577
}
578