This repository has been archived by the owner on Oct 19, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
/
class-gutenberg-ramp.php
545 lines (447 loc) · 14 KB
/
class-gutenberg-ramp.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
<?php
class Gutenberg_Ramp {
private static $instance;
/**
* Criteria is temporarily stored on class instance before it can be validated and updated
* Do not trust raw data stored in $criteria!
* @var mixed null|array
*/
private static $criteria = null;
private $option_name = 'gutenberg_ramp_load_critera';
public $active = false;
public $load_gutenberg = null;
/**
* Get the Gutenberg Ramp singleton instance
*
* @return Gutenberg_Ramp
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new Gutenberg_Ramp();
}
return self::$instance;
}
/**
* Gutenberg_Ramp constructor.
*/
private function __construct() {
$this->option_name = apply_filters( 'gutenberg_ramp_option_name', $this->option_name );
// Load/Unload/Ignore Gutenberg:
add_action( 'plugins_loaded', [ $this, 'load_decision' ], 20, 0 );
/**
* If gutenberg_ramp_load_gutenberg() has not been called, perform cleanup
* unfortunately this must be done on every admin pageload to detect the case where
* criteria were previously being set in a theme, but now are not (due to a code change)
*/
add_action( 'admin_init', [ $this, 'cleanup_option' ], 10, 0 );
/**
* Store the criteria on admin_init
*
* $priority = 5 to ensure that the UI class has fresh data available
* To do that, we need this to run before `gutenberg_ramp_initialize_admin_ui()`
*/
add_action( 'admin_init', [ $this, 'save_criteria' ], 5, 0 );
/**
* Tell Gutenberg when not to load
*
* Gutenberg only calls this filter when checking the primary post
* @TODO duplicate this for WP5.0 core with the new filter name, it's expected to change
*/
add_filter( 'gutenberg_can_edit_post_type', [ $this, 'maybe_allow_gutenberg_to_load' ], 20, 2 );
}
/**
* Get the option name
*
* @return string
*/
public function get_option_name() {
return $this->option_name;
}
/**
* Get the desired criteria
*
* @param string $criteria_name - post_types, post_ids, load
*
* @return mixed
*/
public function get_criteria( $criteria_name = '' ) {
$options = get_option( $this->get_option_name() );
if ( '' === $criteria_name ) {
return $options;
}
if ( empty( $options[ $criteria_name ] ) ) {
return false;
}
return $options[ $criteria_name ];
}
/**
* Set the private class variable $criteria
* self::$criteria going to be used to update the option when `$this->save_criteria()` is run
*
* @param $criteria
*
* @return bool
*/
public function set_criteria( $criteria ) {
if ( $this->sanitize_criteria( $criteria ) ) {
self::$criteria = $criteria;
return true;
}
return false;
}
/**
* Save criteria in WordPres options if it's valid
*/
public function save_criteria() {
if ( null !== self::$criteria && $this->validate_criteria( self::$criteria ) ) {
update_option( $this->get_option_name(), self::$criteria );
}
}
/**
* Make sure that the passed $post_types exist and can support Gutenberg
*
* @param array $post_types
*
* @return bool
*/
public function validate_post_types( $post_types ) {
$supported_post_types = array_keys( $this->get_supported_post_types() );
foreach ( (array) $post_types as $post_type ) {
if ( ! in_array( $post_type, $supported_post_types, true ) ) {
_doing_it_wrong( 'gutenberg_ramp_load_gutenberg', "Cannot enable Gutenberg support for post type \"{$post_type}\"", null );
return false;
}
}
return true;
}
/**
* Validate $criteria
*
* @param $criteria
*
* @return bool
*/
public function validate_criteria( $criteria ) {
if ( ! empty( $criteria['post_types'] ) && ! $this->validate_post_types( $criteria['post_types'] ) ) {
return false;
}
return true;
}
/**
* Sanitize $criteria by making sure it's formatted properly
*
* @param $criteria
*
* @return bool
*/
public function sanitize_criteria( $criteria ) {
if ( ! is_array( $criteria ) || ! $criteria ) {
return false;
}
$criteria_whitelist = [ 'post_ids', 'post_types', 'load' ];
foreach ( $criteria as $key => $value ) {
if ( ! in_array( $key, $criteria_whitelist, true ) ) {
return false;
}
switch ( $key ) {
case 'post_ids':
foreach ( $value as $id ) {
if ( ! ( is_numeric( $id ) && $id > 0 ) ) {
return false;
}
}
break;
case 'post_types':
foreach ( $value as $post_type ) {
if ( sanitize_title( $post_type ) !== $post_type ) {
return false;
}
}
break;
case 'load':
if ( ! in_array( $value, [ 0, 1 ], true ) ) {
return false;
}
break;
default:
break;
}
}
return true;
}
/**
* This is where Ramp will make the decision to load/unload/ignore Gutenberg
*/
public function load_decision() {
/**
* We need to correct the situation when one of two conditions apply:
* * case 1: gutenberg should load according to our criteria but it will not currently do so
* * case 2: gutenberg should not load according to our criteria, but it will currently do so
*/
if ( $this->gutenberg_should_load() && ! $this->gutenberg_will_load() ) {
// this is case 1 ... force gutenberg to load if possible
$this->gutenberg_load();
} elseif ( ! $this->gutenberg_should_load() && $this->gutenberg_will_load() ) {
// this is case 2 ... force gutenberg to bail if possible
// @TODO: define this behavior -- will probably leverage the classic editor plugin or some version thereof
$this->gutenberg_unload();
}
}
/**
* Figure out whether or not Gutenberg should be loaded
* This method is run during `plugins_loaded` so not
*
* @return bool
*/
public function gutenberg_should_load() {
// Always load Gutenberg on the front-end -- this allows blocks to render correctly, etc.
if ( ! is_admin() ) {
return true;
}
// Only load Ramp in edit screens
if ( ! $this->is_eligible_admin_url() ) {
return false;
}
$criteria = $this->get_criteria();
/**
* Return false early -
* If criteria is empty and there are no post types enabled from the Ramp UI
*/
if ( ! $criteria && empty( $this->get_enabled_post_types() ) ) {
return false;
}
// check if we should always or never load
if ( false !== $criteria && array_key_exists( 'load', $criteria ) ) {
if ( $criteria['load'] === 1 ) {
return true;
} elseif ( $criteria['load'] === 0 ) {
return false;
}
}
// CRITERIA
// in order load Gutnberg because of other criteria, we will need to check that a few things are true:
// 1. we are attempting to load post.php ... there's an available post_id
// 2. there's an available post_id in the URL to check
$gutenberg_ramp_post_id = $this->get_current_post_id();
// check post_types
if ( $this->is_allowed_post_type( $gutenberg_ramp_post_id ) ) {
return true;
}
if ( ! $gutenberg_ramp_post_id ) {
return false;
}
// grab the criteria
$gutenberg_ramp_post_ids = ( isset( $criteria['post_ids'] ) ) ? $criteria['post_ids'] : [];
// check post_ids
if ( in_array( $gutenberg_ramp_post_id, $gutenberg_ramp_post_ids, true ) ) {
return true;
}
}
/**
* Get post types that can be supported by Gutenberg.
*
* This will get all registered post types and remove post types:
* * that aren't shown in the admin menu
* * like attachment, revision, etc.
* * that don't support native editor UI
*
*
* Also removes post types that don't support `show_in_rest`:
* @link https://github.com/WordPress/gutenberg/issues/3066
*
* @return array of formatted post types as [ 'slug' => 'label' ]
*/
public function get_supported_post_types() {
if ( 0 === did_action( 'init' ) && ! doing_action( 'init' ) ) {
_doing_it_wrong( 'Gutenberg_Ramp::get_supported_post_types', "get_supported_post_types() was called before the init hook. Some post types might not be registered yet.", '1.0.0' );
}
$post_types = get_post_types(
[
'show_ui' => true,
'show_in_rest' => true,
],
'object'
);
$available_post_types = [];
// Remove post types that don't want an editor
foreach ( $post_types as $name => $post_type_object ) {
if ( post_type_supports( $name, 'editor' ) && ! empty( $post_type_object->label ) ) {
$available_post_types[ $name ] = $post_type_object->label;
}
}
return $available_post_types;
}
/**
* Get all post types with Gutenberg enabled
*
* @return array
*/
public function get_enabled_post_types() {
$ui_enabled_post_types = (array) get_option( 'gutenberg_ramp_post_types', [] );
$helper_enabled_post_types = (array) $this->get_criteria( 'post_types' );
return array_unique( array_merge( $ui_enabled_post_types, $helper_enabled_post_types ) );
}
/**
* Check whether current post type is defined as gutenberg-friendly
*
* @param $post_id
*
* @return bool
*/
public function is_allowed_post_type( $post_id ) {
$allowed_post_types = $this->get_enabled_post_types();
// Exit early, if no allowed post types are found
if ( false === $allowed_post_types || ! is_array( $allowed_post_types ) ) {
return false;
}
// Find the current post type
$current_post_type = false;
if ( 0 === (int) $post_id ) {
if ( isset( $_GET['post_type'] ) ) {
$current_post_type = sanitize_title( $_GET['post_type'] );
} // Regular posts are plain `post-new.php` with no `post_type` parameter defined.
elseif ( $this->is_eligible_admin_url( [ 'post-new.php' ] ) ) {
$current_post_type = 'post';
}
} else {
$current_post_type = get_post_type( $post_id );
}
// Exit if no current post type found
if ( false === $current_post_type ) {
return false;
}
return in_array( $current_post_type, $allowed_post_types, true );
}
/**
* Check whether Gutenberg is already being loaded
*
* @return bool
*/
public function gutenberg_will_load() {
// for WordPress version >= 5, Gutenberg will load
global $wp_version;
$version_arr = explode( '.', $wp_version );
$wp_version_main = (int) $version_arr[0];
if ( $wp_version_main >= 5 ) {
return true;
}
// also, the gutenberg plugin might be the source of an attempted load
if (
has_filter( 'replace_editor', 'gutenberg_init' )
||
has_filter( 'load-post.php', 'gutenberg_intercept_edit_post' )
||
has_filter( 'load-post-new.php', 'gutenberg_intercept_post_new' )
) {
return true;
}
return false;
}
/**
* Activate Gutenberg plugin
*
* @return bool
*/
public function gutenberg_load() {
// perform any actions required before loading gutenberg
do_action( 'gutenberg_ramp_before_load_gutenberg' );
$gutenberg_include = apply_filters( 'gutenberg_ramp_gutenberg_load_path', WP_PLUGIN_DIR . '/gutenberg/gutenberg.php' );
if ( validate_file( $gutenberg_include ) !== 0 ) {
return false;
}
// flag this for the filter
$this->load_gutenberg = true;
if ( file_exists( $gutenberg_include ) ) {
include_once $gutenberg_include;
return true;
}
return false;
}
/**
* This will disable Gutenberg and enable Legacy Editor instead
*/
public function gutenberg_unload() {
/**
* Keep track of whether Gutenberg should be deactivated.
* This variable is used by `maybe_allow_gutenberg_to_load` to modify the `gutenberg_can_edit_post_type` filter
*/
$this->load_gutenberg = false;
/**
* @TODO: Make sure the Classic editor is loaded in WordPress 5.0+
*/
}
//
//
// ----- Utility functions -----
//
//
/**
* A way to get the current post_id during the `plugins_loaded` action because the query may not exist yet
*
* @return int
*/
public function get_current_post_id() {
if ( isset( $_GET['post'] ) && is_numeric( $_GET['post'] ) && ( (int) $_GET['post'] > 0 ) ) {
return absint( $_GET['post'] );
}
return 0;
}
/**
* Check if the current URL is elegible for Gutenberg
*
* @param array $supported_filenames - which /wp-admin/ pages to check for. Defaults to `post.php` and `post-new.php`
* @return bool
*/
public function is_eligible_admin_url( $supported_filenames = [ 'post.php', 'post-new.php' ] ) {
$path = sanitize_text_field( wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) );
$path = trim( $path );
$wp_admin_slug = trim( wp_parse_url( get_admin_url(), PHP_URL_PATH ), '/' );
foreach ( $supported_filenames as $filename ) {
// Require $filename not to be empty to avoid accidents like matching against a plain `/wp-admin/`
if ( ! empty( $filename ) && "/{$wp_admin_slug}/{$filename}" === $path ) {
return true;
}
}
return false;
}
/**
* Remove the stored Gutenberg Ramp settings if `gutenberg_ramp()` isn't used
*/
public function cleanup_option() {
// if the criteria are already such that Gutenberg will never load, no change is needed
if ( $this->get_criteria() === [ 'load' => 0 ] ) {
return;
}
// if the theme did not call its function, then remove the option containing criteria, which will prevent all loading
if ( ! $this->active ) {
delete_option( $this->get_option_name() );
}
}
/**
* Disable Gutenberg if the load decidion has been made to unload it
*
* This is a slight hack since there's no filter (yet) in Gutenberg on the
* post id, just the post type, but because it's (currently) only used to check the
* primary post id when loading the editor, it can be leveraged.
*
* The instance variable load_gutenberg might be set during the load
* decision code above. If it's explicitly false, then the filter returns false,
* else it returns the original value.
*
* @param boolean $can_edit - whether Gutenberg should edit this post type
* @param string $post_type - the post type
*
* @return boolean - whether Gutenberg should edit this post
*/
public function maybe_allow_gutenberg_to_load( $can_edit, $post_type ) {
// Don't enable Gutenberg in post types that don't support Gutenberg.
if ( false === $can_edit ) {
return false;
}
// Return the decision, if a decision has been made.
if ( null !== $this->load_gutenberg ) {
return (bool) $this->load_gutenberg;
}
return $can_edit;
}
}