Passed
Push — master ( 6176aa...f7c939 )
by Mike
03:08
created

SystemStatusTools   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 577
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 270
dl 0
loc 577
rs 9.28
c 0
b 0
f 0
wmc 39

22 Methods

Rating   Name   Duplication   Size   Complexity  
A clear_sessions() 0 8 1
A regenerate_thumbnails() 0 3 1
A clear_expired_transients() 0 3 1
A db_update_routine() 0 6 1
B get_tools() 0 86 4
A register_routes() 0 40 1
A recount_terms() 0 18 1
A regenerate_product_lookup_tables() 0 5 2
A delete_orphaned_variations() 0 13 1
A get_collection_params() 0 3 1
A clear_expired_download_permissions() 0 14 1
A prepare_item_for_response() 0 10 2
A delete_taxes() 0 6 1
A prepare_links() 0 10 1
A get_item() 0 15 2
A update_item() 0 28 2
A get_items() 0 18 2
A clear_transients() 0 15 3
B execute_tool() 0 34 8
A get_item_schema() 0 55 1
A install_pages() 0 3 1
A reset_roles() 0 4 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 WooCommerce/RestApi
8
 */
9
10
namespace 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' ),
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' ),
89
				'button' => __( 'Clear transients', 'woocommerce' ),
90
				'desc'   => __( 'This tool will clear the product/shop transients cache.', 'woocommerce' ),
91
			),
92
			'clear_expired_transients'           => array(
93
				'name'   => __( 'Expired transients', 'woocommerce' ),
94
				'button' => __( 'Clear transients', 'woocommerce' ),
95
				'desc'   => __( 'This tool will clear ALL expired transients from WordPress.', 'woocommerce' ),
96
			),
97
			'delete_orphaned_variations'         => array(
98
				'name'   => __( 'Orphaned variations', 'woocommerce' ),
99
				'button' => __( 'Delete orphaned variations', 'woocommerce' ),
100
				'desc'   => __( 'This tool will delete all variations which have no parent.', 'woocommerce' ),
101
			),
102
			'clear_expired_download_permissions' => array(
103
				'name'   => __( 'Used-up download permissions', 'woocommerce' ),
104
				'button' => __( 'Clean up download permissions', 'woocommerce' ),
105
				'desc'   => __( 'This tool will delete expired download permissions and permissions with 0 remaining downloads.', 'woocommerce' ),
106
			),
107
			'regenerate_product_lookup_tables'   => array(
108
				'name'   => __( 'Product lookup tables', 'woocommerce' ),
109
				'button' => __( 'Regenerate', 'woocommerce' ),
110
				'desc'   => __( 'This tool will regenerate product lookup table data. This process may take a while.', 'woocommerce' ),
111
			),
112
			'recount_terms'                      => array(
113
				'name'   => __( 'Term counts', 'woocommerce' ),
114
				'button' => __( 'Recount terms', 'woocommerce' ),
115
				'desc'   => __( 'This tool will recount product terms - useful when changing your settings in a way which hides products from the catalog.', 'woocommerce' ),
116
			),
117
			'reset_roles'                        => array(
118
				'name'   => __( 'Capabilities', 'woocommerce' ),
119
				'button' => __( 'Reset capabilities', 'woocommerce' ),
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' ),
121
			),
122
			'clear_sessions'                     => array(
123
				'name'   => __( 'Clear customer sessions', 'woocommerce' ),
124
				'button' => __( 'Clear', 'woocommerce' ),
125
				'desc'   => sprintf(
126
					'<strong class="red">%1$s</strong> %2$s',
127
					__( 'Note:', 'woocommerce' ),
128
					__( 'This tool will delete all customer session data from the database, including current carts and saved carts in the database.', 'woocommerce' )
129
				),
130
			),
131
			'install_pages'                      => array(
132
				'name'   => __( 'Create default WooCommerce pages', 'woocommerce' ),
133
				'button' => __( 'Create pages', 'woocommerce' ),
134
				'desc'   => sprintf(
135
					'<strong class="red">%1$s</strong> %2$s',
136
					__( 'Note:', 'woocommerce' ),
137
					__( 'This tool will install all the missing WooCommerce pages. Pages already defined and set up will not be replaced.', 'woocommerce' )
138
				),
139
			),
140
			'delete_taxes'                       => array(
141
				'name'   => __( 'Delete WooCommerce tax rates', 'woocommerce' ),
142
				'button' => __( 'Delete tax rates', 'woocommerce' ),
143
				'desc'   => sprintf(
144
					'<strong class="red">%1$s</strong> %2$s',
145
					__( 'Note:', 'woocommerce' ),
146
					__( 'This option will delete ALL of your tax rates, use with caution. This action cannot be reversed.', 'woocommerce' )
147
				),
148
			),
149
			'regenerate_thumbnails'              => array(
150
				'name'   => __( 'Regenerate shop thumbnails', 'woocommerce' ),
151
				'button' => __( 'Regenerate', 'woocommerce' ),
152
				'desc'   => __( 'This will regenerate all shop thumbnails to match your theme and/or image settings.', 'woocommerce' ),
153
			),
154
			'db_update_routine'                  => array(
155
				'name'   => __( 'Update database', 'woocommerce' ),
156
				'button' => __( 'Update database', 'woocommerce' ),
157
				'desc'   => sprintf(
158
					'<strong class="red">%1$s</strong> %2$s',
159
					__( 'Note:', 'woocommerce' ),
160
					__( 'This tool will update your WooCommerce database to the latest version. Please ensure you make sufficient backups before proceeding.', 'woocommerce' )
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 WooCommerce\RestApi\Controllers\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' ), 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' ), 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
	 * Prepare a tool item for serialization.
262
	 *
263
	 * @param  array            $item     Object.
264
	 * @param  \WP_REST_Request $request  Request object.
265
	 * @return \WP_REST_Response $response Response data.
266
	 */
267
	public function prepare_item_for_response( $item, $request ) {
268
		$context = empty( $request['context'] ) ? 'view' : $request['context'];
269
		$data    = $this->add_additional_fields_to_object( $item, $request );
270
		$data    = $this->filter_response_by_context( $data, $context );
271
272
		$response = rest_ensure_response( $data );
273
274
		$response->add_links( $this->prepare_links( $item['id'] ) );
275
276
		return $response;
277
	}
278
279
	/**
280
	 * Get the system status tools schema, conforming to JSON Schema.
281
	 *
282
	 * @return array
283
	 */
284
	public function get_item_schema() {
285
		$schema = array(
286
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
287
			'title'      => 'system_status_tool',
288
			'type'       => 'object',
289
			'properties' => array(
290
				'id'          => array(
291
					'description' => __( 'A unique identifier for the tool.', 'woocommerce' ),
292
					'type'        => 'string',
293
					'context'     => array( 'view', 'edit' ),
294
					'arg_options' => array(
295
						'sanitize_callback' => 'sanitize_title',
296
					),
297
				),
298
				'name'        => array(
299
					'description' => __( 'Tool name.', 'woocommerce' ),
300
					'type'        => 'string',
301
					'context'     => array( 'view', 'edit' ),
302
					'arg_options' => array(
303
						'sanitize_callback' => 'sanitize_text_field',
304
					),
305
				),
306
				'action'      => array(
307
					'description' => __( 'What running the tool will do.', 'woocommerce' ),
308
					'type'        => 'string',
309
					'context'     => array( 'view', 'edit' ),
310
					'arg_options' => array(
311
						'sanitize_callback' => 'sanitize_text_field',
312
					),
313
				),
314
				'description' => array(
315
					'description' => __( 'Tool description.', 'woocommerce' ),
316
					'type'        => 'string',
317
					'context'     => array( 'view', 'edit' ),
318
					'arg_options' => array(
319
						'sanitize_callback' => 'sanitize_text_field',
320
					),
321
				),
322
				'success'     => array(
323
					'description' => __( 'Did the tool run successfully?', 'woocommerce' ),
324
					'type'        => 'boolean',
325
					'context'     => array( 'edit' ),
326
				),
327
				'message'     => array(
328
					'description' => __( 'Tool return message.', 'woocommerce' ),
329
					'type'        => 'string',
330
					'context'     => array( 'edit' ),
331
					'arg_options' => array(
332
						'sanitize_callback' => 'sanitize_text_field',
333
					),
334
				),
335
			),
336
		);
337
338
		return $this->add_additional_fields_schema( $schema );
339
	}
340
341
	/**
342
	 * Prepare links for the request.
343
	 *
344
	 * @param string $id ID.
345
	 * @return array
346
	 */
347
	protected function prepare_links( $id ) {
348
		$base  = '/' . $this->namespace . '/' . $this->rest_base;
349
		$links = array(
350
			'item' => array(
351
				'href'       => rest_url( trailingslashit( $base ) . $id ),
352
				'embeddable' => true,
353
			),
354
		);
355
356
		return $links;
357
	}
358
359
	/**
360
	 * Get any query params needed.
361
	 *
362
	 * @return array
363
	 */
364
	public function get_collection_params() {
365
		return array(
366
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
367
		);
368
	}
369
370
	/**
371
	 * Actually executes a tool.
372
	 *
373
	 * @throws Exception When the tool cannot run.
374
	 * @param  string $tool Tool.
375
	 * @return array
376
	 */
377
	public function execute_tool( $tool ) {
378
		$ran   = false;
379
		$tools = $this->get_tools();
380
381
		try {
382
			if ( ! isset( $tools[ $tool ] ) ) {
383
				throw new Exception( __( 'There was an error calling this tool. There is no callback present.', 'woocommerce' ) );
0 ignored issues
show
Bug introduced by
The type WooCommerce\RestApi\Controllers\Version4\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
384
			}
385
386
			$callback = isset( $tools[ $tool ]['callback'] ) ? $tools[ $tool ]['callback'] : array( $this, $tool );
387
388
			if ( ! is_callable( $callback ) ) {
389
				throw new Exception( __( 'There was an error calling this tool. Invalid callback.', 'woocommerce' ) );
390
			}
391
392
			$message = call_user_func( $callback );
393
394
			if ( false === $message ) {
395
				throw new Exception( __( 'There was an error calling this tool. Invalid callback.', 'woocommerce' ) );
396
			}
397
398
			if ( empty( $message ) || ! is_string( $message ) ) {
399
				$message = __( 'Tool ran.', 'woocommerce' );
400
			}
401
402
			$ran = true;
403
		} catch ( Exception $e ) {
404
			$message = $e->getMessage();
405
			$ran     = false;
406
		}
407
408
		return array(
409
			'success' => $ran,
410
			'message' => $message,
411
		);
412
	}
413
414
	/**
415
	 * Tool: clear_transients.
416
	 *
417
	 * @return string Success message.
418
	 */
419
	protected function clear_transients() {
420
		wc_delete_product_transients();
421
		wc_delete_shop_order_transients();
422
		delete_transient( 'wc_count_comments' );
423
424
		$attribute_taxonomies = wc_get_attribute_taxonomies();
425
426
		if ( ! empty( $attribute_taxonomies ) ) {
427
			foreach ( $attribute_taxonomies as $attribute ) {
428
				delete_transient( 'wc_layered_nav_counts_pa_' . $attribute->attribute_name );
429
			}
430
		}
431
432
		\WC_Cache_Helper::get_transient_version( 'shipping', true );
433
		return __( 'Product transients cleared', 'woocommerce' );
434
	}
435
436
	/**
437
	 * Tool: clear_expired_transients.
438
	 *
439
	 * @return string Success message.
440
	 */
441
	protected function clear_expired_transients() {
442
		/* translators: %d: amount of expired transients */
443
		return sprintf( __( '%d transients rows cleared', 'woocommerce' ), wc_delete_expired_transients() );
444
	}
445
446
	/**
447
	 * Tool: delete_orphaned_variations.
448
	 *
449
	 * @return string Success message.
450
	 */
451
	protected function delete_orphaned_variations() {
452
		global $wpdb;
453
454
		$result = absint(
455
			$wpdb->query(
456
				"DELETE products
457
			FROM {$wpdb->posts} products
458
			LEFT JOIN {$wpdb->posts} wp ON wp.ID = products.post_parent
459
			WHERE wp.ID IS NULL AND products.post_type = 'product_variation';"
460
			)
461
		);
462
		/* translators: %d: amount of orphaned variations */
463
		return sprintf( __( '%d orphaned variations deleted', 'woocommerce' ), $result );
464
	}
465
466
	/**
467
	 * Tool: clear_expired_download_permissions.
468
	 *
469
	 * @return string Success message.
470
	 */
471
	protected function clear_expired_download_permissions() {
472
		global $wpdb;
473
474
		$result = absint(
475
			$wpdb->query(
476
				$wpdb->prepare(
477
					"DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
478
					WHERE ( downloads_remaining != '' AND downloads_remaining = 0 ) OR ( access_expires IS NOT NULL AND access_expires < %s )",
479
					date( 'Y-m-d', current_time( 'timestamp' ) )
480
				)
481
			)
482
		);
483
		/* translators: %d: amount of permissions */
484
		return sprintf( __( '%d permissions deleted', 'woocommerce' ), $result );
485
	}
486
487
	/**
488
	 * Tool: regenerate_product_lookup_tables.
489
	 *
490
	 * @return string Success message.
491
	 */
492
	protected function regenerate_product_lookup_tables() {
493
		if ( ! wc_update_product_lookup_tables_is_running() ) {
494
			wc_update_product_lookup_tables();
495
		}
496
		return __( 'Lookup tables are regenerating', 'woocommerce' );
497
	}
498
499
	/**
500
	 * Tool: reset_roles.
501
	 *
502
	 * @return string Success message.
503
	 */
504
	protected function reset_roles() {
505
		\WC_Install::remove_roles();
506
		\WC_Install::create_roles();
507
		return __( 'Roles successfully reset', 'woocommerce' );
508
	}
509
510
	/**
511
	 * Tool: recount_terms.
512
	 *
513
	 * @return string Success message.
514
	 */
515
	protected function recount_terms() {
516
		$product_cats = get_terms(
517
			'product_cat',
518
			array(
519
				'hide_empty' => false,
520
				'fields'     => 'id=>parent',
521
			)
522
		);
523
		_wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), true, false );
524
		$product_tags = get_terms(
525
			'product_tag',
526
			array(
527
				'hide_empty' => false,
528
				'fields'     => 'id=>parent',
529
			)
530
		);
531
		_wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), true, false );
532
		return __( 'Terms successfully recounted', 'woocommerce' );
533
	}
534
535
	/**
536
	 * Tool: clear_sessions.
537
	 *
538
	 * @return string Success message.
539
	 */
540
	protected function clear_sessions() {
541
		global $wpdb;
542
543
		$wpdb->query( "TRUNCATE {$wpdb->prefix}woocommerce_sessions" );
544
		$result = absint( $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key='_woocommerce_persistent_cart_" . get_current_blog_id() . "';" ) ); // WPCS: unprepared SQL ok.
545
		wp_cache_flush();
546
		/* translators: %d: amount of sessions */
547
		return sprintf( __( 'Deleted all active sessions, and %d saved carts.', 'woocommerce' ), absint( $result ) );
548
	}
549
550
	/**
551
	 * Tool: install_pages.
552
	 *
553
	 * @return string Success message.
554
	 */
555
	protected function install_pages() {
556
		\WC_Install::create_pages();
557
		return __( 'All missing WooCommerce pages successfully installed', 'woocommerce' );
558
	}
559
560
	/**
561
	 * Tool: delete_taxes.
562
	 *
563
	 * @return string Success message.
564
	 */
565
	protected function delete_taxes() {
566
		global $wpdb;
567
		$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}woocommerce_tax_rates;" );
568
		$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}woocommerce_tax_rate_locations;" );
569
		\WC_Cache_Helper::incr_cache_prefix( 'taxes' );
570
		return __( 'Tax rates successfully deleted', 'woocommerce' );
571
	}
572
573
	/**
574
	 * Tool: regenerate_thumbnails.
575
	 *
576
	 * @return string Success message.
577
	 */
578
	protected function regenerate_thumbnails() {
579
		\WC_Regenerate_Images::queue_image_regeneration();
580
		return __( 'Thumbnail regeneration has been scheduled to run in the background.', 'woocommerce' );
581
	}
582
583
	/**
584
	 * Tool: db_update_routine.
585
	 *
586
	 * @return string Success message.
587
	 */
588
	protected function db_update_routine() {
589
		$blog_id = get_current_blog_id();
590
		// Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck().
591
		// This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running.
592
		do_action( 'wp_' . $blog_id . '_wc_updater_cron' );
593
		return __( 'Database upgrade routine has been scheduled to run in the background.', 'woocommerce' );
594
	}
595
}
596