PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / 2.0.4
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More v2.0.4
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 47 releases
content-control / classes / Controllers / Admin / Upgrades.php

Upgrades.php in Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More 2.0.4, at classes/Controllers/Admin/Upgrades.php

440 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Upgrades Controller Class.
4 *
5 * @package ContentControl
6 */
7
8 namespace ContentControl\Controllers\Admin;
9
10 use ContentControl\Base\Controller;
11
12 defined( 'ABSPATH' ) || exit;
13
14 use function __;
15 use function add_filter;
16 use function add_action;
17 use function esc_html_e;
18 use function esc_attr;
19 use function get_current_screen;
20 use function admin_url;
21 use function is_admin;
22 use function current_user_can;
23 use function wp_create_nonce;
24 use function wp_verify_nonce;
25 use function wp_send_json_error;
26 use function wp_unslash;
27 use function is_wp_error;
28 use function ContentControl\get_upgrade_name;
29 use function ContentControl\is_upgrade_complete;
30 use function ContentControl\mark_upgrade_complete;
31
32 /**
33 * Upgrades Controller.
34 *
35 * @package ContentControl\Admin
36 */
37 class Upgrades extends Controller {
38
39 /**
40 * Initialize the settings page.
41 */
42 public function init() {
43 add_action( 'init', [ $this, 'hooks' ] );
44 add_action( 'wp_ajax_content_control_upgrades', [ $this, 'ajax_handler' ] );
45 add_filter( 'content_control/settings-page_localized_vars', [ $this, 'localize_vars' ] );
46 }
47
48 /**
49 * Hook into relevant WP actions.
50 */
51 public function hooks() {
52 if ( is_admin() && current_user_can( 'manage_options' ) ) {
53 add_action( 'admin_notices', [ $this, 'admin_notices' ] );
54 add_action( 'network_admin_notices', [ $this, 'admin_notices' ] );
55 add_action( 'user_admin_notices', [ $this, 'admin_notices' ] );
56 }
57 }
58
59 /**
60 * Get a list of all upgrades.
61 *
62 * @return string[]
63 */
64 public function all_upgrades() {
65 return [
66 // Version 2 upgrades.
67 '\ContentControl\Upgrades\PluginMeta_2',
68 '\ContentControl\Upgrades\Settings_2',
69 '\ContentControl\Upgrades\UserMeta_2',
70 '\ContentControl\Upgrades\Restrictions_2',
71 ];
72 }
73
74 /**
75 * Check if there are any upgrades to run.
76 *
77 * @return boolean
78 */
79 public function has_upgrades() {
80 return count( $this->get_required_upgrades() );
81 }
82
83 /**
84 * Get a list of required upgrades.
85 *
86 * Uses a cached list of done upgrades to prevent extra processing.
87 *
88 * @return \ContentControl\Base\Upgrade[]
89 */
90 public function get_required_upgrades() {
91 static $required_upgrades = null;
92
93 if ( null === $required_upgrades ) {
94 $all_upgrades = $this->all_upgrades();
95 $required_upgrades = [];
96
97 foreach ( $all_upgrades as $upgrade_class ) {
98 if ( ! class_exists( $upgrade_class ) ) {
99 continue;
100 }
101
102 /**
103 * Upgrade class instance.
104 *
105 * @var \ContentControl\Base\Upgrade $upgrade
106 */
107 $upgrade = new $upgrade_class();
108
109 // Check if required, and if so, add it to the list.
110 if ( is_upgrade_complete( $upgrade ) ) {
111 continue;
112 } elseif ( ! $upgrade->is_required() ) {
113 // If its not required, mark it as done.
114 mark_upgrade_complete( $upgrade );
115 continue;
116 }
117
118 $required_upgrades[] = $upgrade;
119 }
120
121 // Sort the required upgrades based on prerequisites.
122 $required_upgrades = $this->sort_upgrades_by_prerequisites( $required_upgrades );
123 }
124
125 return $required_upgrades;
126 }
127
128 /**
129 * Sort upgrades based on prerequisites using a graph-based approach.
130 *
131 * @param \ContentControl\Base\Upgrade[] $upgrades List of upgrades to sort.
132 *
133 * @return \ContentControl\Base\Upgrade[]
134 */
135 private function sort_upgrades_by_prerequisites( $upgrades ) {
136 // Build the graph of upgrades and their dependencies.
137 $graph = [];
138 $upgrade_by_name = [];
139 foreach ( $upgrades as $upgrade ) {
140 $updgrade_name = get_upgrade_name( $upgrade );
141
142 $graph[ $updgrade_name ] = $upgrade->get_dependencies();
143
144 $upgrade_by_name[ $updgrade_name ] = $upgrade;
145 }
146
147 // Perform a topological sort on the graph.
148 $sorted = $this->topological_sort( $graph );
149
150 // Rebuild the list of upgrades in the sorted order.
151 foreach ( $sorted as $key => $value ) {
152 $sorted[ $key ] = $upgrade_by_name[ $value ];
153 }
154
155 // Remove null values, these are upgrades that have been marked as done.
156 $sorted = array_filter( $sorted );
157
158 // Return the sorted upgrades.
159 return $sorted;
160 }
161
162 /**
163 * Perform a topological sort on a graph.
164 *
165 * @param array $graph Graph to sort.
166 *
167 * @return array
168 */
169 private function topological_sort( $graph ) {
170 $visited = [];
171 $sorted = [];
172
173 foreach ( $graph as $node => $dependencies ) {
174 $this->visit_node( $node, $graph, $visited, $sorted );
175 }
176
177 return $sorted;
178 }
179
180 /**
181 * Visit a node in the graph for topological sort.
182 *
183 * @param mixed $node Node to visit.
184 * @param array $graph Graph to sort.
185 * @param array $visited List of visited nodes.
186 * @param array $sorted List of sorted nodes.
187 */
188 private function visit_node( $node, $graph, &$visited, &$sorted ) {
189 if ( isset( $visited[ $node ] ) ) {
190 // Node already visited, skip.
191 return;
192 }
193
194 $visited[ $node ] = true;
195
196 foreach ( $graph[ $node ] as $dependency ) {
197 $this->visit_node( $dependency, $graph, $visited, $sorted );
198 }
199
200 $sorted[] = $node;
201 }
202
203 /**
204 * AJAX Handler
205 */
206 public function ajax_handler() {
207 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
208 if ( ! isset( $_REQUEST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_REQUEST['nonce'] ), 'content_control_upgrades' ) ) {
209 wp_send_json_error( __( 'Invalid nonce.', 'content-control' ) );
210 }
211
212 if ( ! current_user_can( $this->container->get_permission( 'manage_settings' ) ) ) {
213 wp_send_json_error( __( 'You do not have permission to run upgrades.', 'content-control' ) );
214 }
215
216 try {
217 $stream = new \ContentControl\Services\UpgradeStream( 'upgrades' );
218 $upgrades = $this->get_required_upgrades();
219 $count = count( $upgrades );
220
221 // First do/while loop starts the stream and breaks if connection aborted.
222 do {
223 $stream->start();
224 $stream->start_upgrades( $count, __( 'Upgrades started', 'content-control' ) );
225
226 $failed_upgrades = [];
227
228 // This second while loop runs the upgrades.
229 while ( ! empty( $upgrades ) ) {
230 $upgrade = array_shift( $upgrades );
231
232 $result = $upgrade->stream_run( $stream );
233
234 if ( is_wp_error( $result ) ) {
235 $stream->send_error( $result );
236 } elseif ( false !== $result ) {
237 mark_upgrade_complete( $upgrade );
238 } else {
239 // False means the upgrade failed.
240 $failed_upgrades[] = get_upgrade_name( $upgrade );
241 }
242 }
243
244 if ( ! empty( $failed_upgrades ) ) {
245 $stream->send_error( [
246 'message' => __( 'Some upgrades failed to complete.', 'content-control' ),
247 'data' => $failed_upgrades,
248 ] );
249
250 $stream->complete_upgrades( __( 'Upgrades complete with errors.', 'content-control' ) );
251 } else {
252 $stream->complete_upgrades( __( 'Upgrades complete!', 'content-control' ) );
253 }
254 } while ( ! $stream->should_abort() );
255 } catch ( \Exception $e ) {
256 $stream->send_error( $e );
257 }
258 }
259
260 /**
261 * AJAX Handler
262 */
263 public function ajax_handler_demo() {
264 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
265 if ( ! isset( $_REQUEST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_REQUEST['nonce'] ), 'content_control_upgrades' ) ) {
266 wp_send_json_error( __( 'Invalid nonce.', 'content-control' ) );
267 }
268
269 if ( ! current_user_can( $this->container->get_permission( 'manage_settings' ) ) ) {
270 wp_send_json_error( __( 'You do not have permission to run upgrades.', 'content-control' ) );
271 }
272
273 try {
274 $upgrades = $this->get_required_upgrades();
275 $count = count( $upgrades ) * 2;
276
277 $stream = new \ContentControl\Services\UpgradeStream( 'upgrades' );
278
279 $stream->start();
280
281 $count = wp_rand( 3, 10 );
282
283 do {
284 $stream->start_upgrades( $count, __( 'Upgrades started', 'content-control' ) );
285
286 // test loop of 1000 upgrades.
287 $test_delay = 60000;
288 for ( $i = 0; $i < $count; $i++ ) {
289 usleep( $test_delay );
290
291 $task_count = wp_rand( 5, 100 );
292
293 $stream->start_task(
294 __( 'Migrating restrictions', 'content-control' ),
295 $task_count
296 );
297
298 // test loop of 1000 upgrades.
299 for ( $i2 = 0; $i2 < $task_count; $i2++ ) {
300 usleep( $test_delay );
301 $stream->update_task_progress( $i2 + 1 );
302 }
303
304 usleep( $test_delay );
305
306 // translators: %d: number of restrictions migrated.
307 $stream->complete_task( sprintf( __( '%d restrictions migrated', 'content-control' ), $i2 ) );
308 }
309 usleep( $test_delay );
310
311 $stream->complete_upgrades( __( 'Upgrades complete!', 'content-control' ) );
312 } while ( ! $stream->should_abort() && ! empty( $upgrades ) );
313 } catch ( \Exception $e ) {
314 $stream->send_error( $e );
315 }
316 }
317
318 /**
319 * Render admin notices if available.
320 */
321 public function admin_notices() {
322 if ( ! is_admin() ) {
323 return;
324 }
325
326 if ( ! $this->has_upgrades() ) {
327 return;
328 }
329
330 $screen = get_current_screen();
331
332 if ( 'settings_page_content-control-settings' === $screen->id ) {
333 return;
334 }
335
336 ?>
337 <style>
338 .content-control-notice {
339 display: flex;
340 align-items: center;
341 gap: 16px;
342 padding: 8px;
343 margin-top: 16px;
344 margin-bottom: 16px;
345 }
346
347 .content-control-notice .notice-logo {
348 flex: 0 0 60px;
349 max-width: 60px;
350 font-size: 60px;
351 }
352
353 .content-control-notice .notice-content {
354 flex-grow: 1;
355 }
356
357 .content-control-notice p {
358 margin-bottom: 0;
359 max-width: 800px;
360 }
361
362 .content-control-notice .notice-actions {
363 margin-top: 10px;
364 margin-bottom: 0;
365 padding-left: 0;
366 list-style: none;
367
368 display: flex;
369 gap: 16px;
370 align-items: center;
371 }
372 </style>
373
374 <div class="notice notice-info content-control-notice">
375 <div class="notice-logo">
376 <img class="logo" width="60" src="<?php echo esc_attr( $this->container->get_url( 'assets/images/illustration-check.svg' ) ); ?>" />
377 </div>
378
379 <div class="notice-content">
380 <p>
381 <strong>
382 <?php esc_html_e( 'Content Control has been updated and needs to run some database upgrades.', 'content-control' ); ?>
383 </strong>
384 </p>
385 <ul class="notice-actions">
386 <li>
387 <a class="content-control-go-to-settings button button-tertiary" href="<?php echo esc_attr( admin_url( 'options-general.php?page=content-control-settings' ) ); ?>" data-reason="am_now">
388 🚨 <?php esc_html_e( 'Upgrade Now', 'content-control' ); ?>
389 </a>
390 </li>
391 </ul>
392 </div>
393 </div>
394 <?php
395 }
396
397 /**
398 * Add localized vars to settings page if there are upgrades to run.
399 *
400 * @param array $vars Localized vars.
401 *
402 * @return array
403 */
404 public function localize_vars( $vars ) {
405 $vars['hasUpgrades'] = false;
406
407 if ( ! $this->has_upgrades() ) {
408 return $vars;
409 }
410
411 $vars['hasUpgrades'] = true;
412 $vars['upgradeNonce'] = wp_create_nonce( 'content_control_upgrades' );
413 $vars['upgradeUrl'] = admin_url( 'admin-ajax.php?action=content_control_upgrades' );
414 $vars['upgrades'] = [];
415
416 $upgrades = $this->get_required_upgrades();
417
418 foreach ( $upgrades as $upgrade ) {
419 $upgrade_name = get_upgrade_name( $upgrade );
420
421 switch ( $upgrade->get_type() ) {
422 case 'restrictions':
423 $vars['hasRestrictionUpgrades'] = true;
424 break;
425 case 'settings':
426 $vars['hasSettingsUpgrades'] = true;
427 break;
428 }
429
430 $vars['upgrades'][ $upgrade_name ] = [
431 'key' => $upgrade_name,
432 'label' => $upgrade->label(),
433 'description' => $upgrade->description(),
434 ];
435 }
436
437 return $vars;
438 }
439 }
440