PluginProbe
Gutenberg / 4.0.1
Gutenberg v4.0.1
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / compat.php

compat.php in Gutenberg 4.0.1, at lib/compat.php

458 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PHP and WordPress configuration compatibility functions for the Gutenberg
4 * editor plugin.
5 *
6 * @package gutenberg
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 die( 'Silence is golden.' );
11 }
12
13 /**
14 * Splits a UTF-8 string into an array of UTF-8-encoded codepoints.
15 *
16 * @since 0.5.0
17 *
18 * Based on WordPress' _mb_substr() compat function.
19 *
20 * @param string $str The string to split.
21 * @return array
22 */
23 function _gutenberg_utf8_split( $str ) {
24 if ( _wp_can_use_pcre_u() ) {
25 // Use the regex unicode support to separate the UTF-8 characters into
26 // an array.
27 preg_match_all( '/./us', $str, $match );
28 return $match[0];
29 }
30
31 $regex = '/(
32 [\x00-\x7F] # single-byte sequences 0xxxxxxx
33 | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx
34 | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2
35 | [\xE1-\xEC][\x80-\xBF]{2}
36 | \xED[\x80-\x9F][\x80-\xBF]
37 | [\xEE-\xEF][\x80-\xBF]{2}
38 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3
39 | [\xF1-\xF3][\x80-\xBF]{3}
40 | \xF4[\x80-\x8F][\x80-\xBF]{2}
41 )/x';
42
43 // Start with 1 element instead of 0 since the first thing we do is pop.
44 $chars = array( '' );
45 do {
46 // We had some string left over from the last round, but we counted it
47 // in that last round.
48 array_pop( $chars );
49
50 // Split by UTF-8 character, limit to 1000 characters (last array
51 // element will contain the rest of the string).
52 $pieces = preg_split(
53 $regex,
54 $str,
55 1000,
56 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
57 );
58
59 $chars = array_merge( $chars, $pieces );
60
61 // If there's anything left over, repeat the loop.
62 if ( count( $pieces ) > 1 ) {
63 $str = array_pop( $pieces );
64 } else {
65 break;
66 }
67 } while ( $str );
68
69 return $chars;
70 }
71
72 /**
73 * Disables wpautop behavior in classic editor when post contains blocks, to
74 * prevent removep from invalidating paragraph blocks.
75 *
76 * @param array $settings Original editor settings.
77 * @param string $editor_id ID for the editor instance.
78 * @return array Filtered settings.
79 */
80 function gutenberg_disable_editor_settings_wpautop( $settings, $editor_id ) {
81 $post = get_post();
82 if ( 'content' === $editor_id && is_object( $post ) && has_blocks( $post ) ) {
83 $settings['wpautop'] = false;
84 }
85
86 return $settings;
87 }
88 add_filter( 'wp_editor_settings', 'gutenberg_disable_editor_settings_wpautop', 10, 2 );
89
90 /**
91 * Add rest nonce to the heartbeat response.
92 *
93 * @param array $response Original heartbeat response.
94 * @return array New heartbeat response.
95 */
96 function gutenberg_add_rest_nonce_to_heartbeat_response_headers( $response ) {
97 $response['rest-nonce'] = wp_create_nonce( 'wp_rest' );
98 return $response;
99 }
100 add_filter( 'wp_refresh_nonces', 'gutenberg_add_rest_nonce_to_heartbeat_response_headers' );
101
102 /**
103 * As a substitute for the default content `wpautop` filter, applies autop
104 * behavior only for posts where content does not contain blocks.
105 *
106 * @param string $content Post content.
107 * @return string Paragraph-converted text if non-block content.
108 */
109 function gutenberg_wpautop( $content ) {
110 if ( has_blocks( $content ) ) {
111 return $content;
112 }
113
114 return wpautop( $content );
115 }
116 remove_filter( 'the_content', 'wpautop' );
117 add_filter( 'the_content', 'gutenberg_wpautop', 8 );
118
119
120 /**
121 * Check if we need to load the block warning in the Classic Editor.
122 *
123 * @since 3.4.0
124 */
125 function gutenberg_check_if_classic_needs_warning_about_blocks() {
126 global $pagenow;
127
128 if ( ! in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) || ! isset( $_REQUEST['classic-editor'] ) ) {
129 return;
130 }
131
132 $post = get_post();
133 if ( ! $post ) {
134 return;
135 }
136
137 if ( ! has_blocks( $post ) && ! isset( $_REQUEST['cloudflare-error'] ) ) {
138 return;
139 }
140
141 // Enqueue the JS we're going to need in the dialog.
142 wp_enqueue_script( 'wp-a11y' );
143 wp_enqueue_script( 'wp-sanitize' );
144
145 if ( isset( $_REQUEST['cloudflare-error'] ) ) {
146 add_action( 'admin_footer', 'gutenberg_warn_classic_about_cloudflare' );
147 } else {
148 add_action( 'admin_footer', 'gutenberg_warn_classic_about_blocks' );
149 }
150 }
151 add_action( 'admin_enqueue_scripts', 'gutenberg_check_if_classic_needs_warning_about_blocks' );
152
153 /**
154 * Adds a warning to the Classic Editor when trying to edit a post containing blocks.
155 *
156 * @since 3.4.0
157 */
158 function gutenberg_warn_classic_about_blocks() {
159 $post = get_post();
160
161 $gutenberg_edit_link = get_edit_post_link( $post->ID, 'raw' );
162
163 $classic_edit_link = $gutenberg_edit_link;
164 $classic_edit_link = add_query_arg(
165 array(
166 'classic-editor' => '',
167 'hide-block-warning' => '',
168 ),
169 $classic_edit_link
170 );
171
172 $revisions_link = '';
173 if ( wp_revisions_enabled( $post ) ) {
174 $revisions = wp_get_post_revisions( $post );
175
176 // If there's only one revision, that won't help.
177 if ( count( $revisions ) > 1 ) {
178 reset( $revisions ); // Reset pointer for key().
179 $revisions_link = get_edit_post_link( key( $revisions ) );
180 }
181 }
182 ?>
183 <style type="text/css">
184 #blocks-in-post-dialog .notification-dialog {
185 position: fixed;
186 top: 50%;
187 left: 50%;
188 width: 500px;
189 box-sizing: border-box;
190 transform: translate(-50%, -50%);
191 margin: 0;
192 padding: 25px;
193 max-height: 90%;
194 background: #fff;
195 box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
196 line-height: 1.5;
197 z-index: 1000005;
198 overflow-y: auto;
199 }
200
201 @media only screen and (max-height: 480px), screen and (max-width: 450px) {
202 #blocks-in-post-dialog .notification-dialog {
203 top: 0;
204 left: 0;
205 width: 100%;
206 height: 100%;
207 transform: none;
208 max-height: 100%;
209 }
210 }
211 </style>
212
213 <div id="blocks-in-post-dialog" class="notification-dialog-wrap">
214 <div class="notification-dialog-background"></div>
215 <div class="notification-dialog">
216 <div class="blocks-in-post-message">
217 <p><?php _e( 'This post was previously edited in Gutenberg. You can continue in the Classic Editor, but you may lose data and formatting.', 'gutenberg' ); ?></p>
218 <?php
219 if ( $revisions_link ) {
220 ?>
221 <p>
222 <?php
223 /* translators: link to the post revisions page */
224 printf( __( 'You can also <a href="%s">browse previous revisions</a> and restore a version of the post before it was edited in Gutenberg.', 'gutenberg' ), esc_url( $revisions_link ) );
225 ?>
226 </p>
227 <?php
228 } else {
229 ?>
230 <p><strong><?php _e( 'Because this post does not have revisions, you will not be able to revert any changes you make in the Classic Editor.', 'gutenberg' ); ?></strong></p>
231 <?php
232 }
233 ?>
234 </div>
235 <p>
236 <a class="button button-primary blocks-in-post-gutenberg-button" href="<?php echo esc_url( $gutenberg_edit_link ); ?>"><?php _e( 'Edit in Gutenberg', 'gutenberg' ); ?></a>
237 <button type="button" class="button blocks-in-post-classic-button"><?php _e( 'Continue to Classic Editor', 'gutenberg' ); ?></button>
238 </p>
239 </div>
240 </div>
241
242 <script type="text/javascript">
243 /* <![CDATA[ */
244 ( function( $ ) {
245 var dialog = {};
246
247 dialog.init = function() {
248 // The modal
249 dialog.warning = $( '#blocks-in-post-dialog' );
250 // Get the links and buttons within the modal.
251 dialog.warningTabbables = dialog.warning.find( 'a, button' );
252
253 // Get the text within the modal.
254 dialog.rawMessage = dialog.warning.find( '.blocks-in-post-message' ).text();
255
256 // Hide all the #wpwrap content from assistive technologies.
257 $( '#wpwrap' ).attr( 'aria-hidden', 'true' );
258
259 // Detach the warning modal from its position and append it to the body.
260 $( document.body )
261 .addClass( 'modal-open' )
262 .append( dialog.warning.detach() );
263
264 // Reveal the modal and set focus on the Gutenberg button.
265 dialog.warning
266 .removeClass( 'hidden' )
267 .find( '.blocks-in-post-gutenberg-button' ).focus();
268
269 // Attach event handlers.
270 dialog.warningTabbables.on( 'keydown', dialog.constrainTabbing );
271 dialog.warning.on( 'click', '.blocks-in-post-classic-button', dialog.dismissWarning );
272
273 // Make screen readers announce the warning message after a short delay (necessary for some screen readers).
274 setTimeout( function() {
275 wp.a11y.speak( wp.sanitize.stripTags( dialog.rawMessage.replace( /\s+/g, ' ' ) ), 'assertive' );
276 }, 1000 );
277 };
278
279 dialog.constrainTabbing = function( event ) {
280 var firstTabbable, lastTabbable;
281
282 if ( 9 !== event.which ) {
283 return;
284 }
285
286 firstTabbable = dialog.warningTabbables.first()[0];
287 lastTabbable = dialog.warningTabbables.last()[0];
288
289 if ( lastTabbable === event.target && ! event.shiftKey ) {
290 firstTabbable.focus();
291 event.preventDefault();
292 } else if ( firstTabbable === event.target && event.shiftKey ) {
293 lastTabbable.focus();
294 event.preventDefault();
295 }
296 };
297
298 dialog.dismissWarning = function() {
299 // Hide modal.
300 dialog.warning.remove();
301 $( '#wpwrap' ).removeAttr( 'aria-hidden' );
302 $( 'body' ).removeClass( 'modal-open' );
303 };
304
305 $( document ).ready( dialog.init );
306 } )( jQuery );
307 /* ]]> */
308 </script>
309 <?php
310 }
311
312 /**
313 * Adds a warning to the Classic Editor when CloudFlare is blocking REST API requests.
314 *
315 * @since 3.4.0
316 */
317 function gutenberg_warn_classic_about_cloudflare() {
318 ?>
319 <style type="text/css">
320 #cloudflare-block-dialog .notification-dialog {
321 position: fixed;
322 top: 50%;
323 left: 50%;
324 width: 500px;
325 box-sizing: border-box;
326 transform: translate(-50%, -50%);
327 margin: 0;
328 padding: 25px;
329 max-height: 90%;
330 background: #fff;
331 box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
332 line-height: 1.5;
333 z-index: 1000005;
334 overflow-y: auto;
335 }
336
337 #cloudflare-block-dialog ul {
338 list-style: initial;
339 padding-left: 20px;
340 }
341
342 @media only screen and (max-height: 480px), screen and (max-width: 450px) {
343 #cloudflare-block-dialog .notification-dialog {
344 top: 0;
345 left: 0;
346 width: 100%;
347 height: 100%;
348 transform: none;
349 max-height: 100%;
350 }
351 }
352 </style>
353
354 <div id="cloudflare-block-dialog" class="notification-dialog-wrap">
355 <div class="notification-dialog-background"></div>
356 <div class="notification-dialog">
357 <div class="cloudflare-block-message">
358 <h2><?php _e( 'Cloudflare is blocking REST API requests', 'gutenberg' ); ?></h2>
359 <p><?php _e( 'Your site uses Cloudflare, which provides a Web Application Firewall (WAF) to secure your site against attacks. Unfortunately, some of these WAF rules are incorrectly blocking legitimate access to your site, preventing Gutenberg from functioning correctly.', 'gutenberg' ); ?></p>
360 <p><?php _e( "We're working closely with Cloudflare to fix this issue, but in the mean time, you can work around it in one of two ways:", 'gutenberg' ); ?></p>
361 <ul>
362 <li><?php _e( 'If you have a Cloudflare Pro account, log in to Cloudflare, visit the Firewall settings page, open the "Cloudflare Rule Set" details, open the "Cloudflare WordPress" ruleset, then set the rules "WP0025A" and "WP0025B" to "Disable".', 'gutenberg' ); ?></li>
363 <li>
364 <?php
365 printf(
366 /* translators: %s: link to a comment in the Gutenberg repository */
367 __( 'For free Cloudflare accounts, you can <a href="%s">change the REST API URL</a>, to avoid triggering the WAF rules. Please be aware that this may cause issues with other plugins that use the REST API, and removes any other protection Cloudflare may be offering for the REST API.', 'gutenberg' ),
368 'https://github.com/WordPress/gutenberg/issues/2704#issuecomment-410582252'
369 );
370 ?>
371 </li>
372 </ul>
373 <p>
374 <?php
375 printf(
376 /* translators: %s link to an issue in the Gutenberg repository */
377 __( 'If neither of these options are possible for you, please <a href="%s">follow this issue for updates</a>. We hope to have this issue rectified soon!', 'gutenberg' ),
378 'https://github.com/WordPress/gutenberg/issues/2704'
379 );
380 ?>
381 </p>
382 </div>
383 <p>
384 <button type="button" class="button button-primary cloudflare-block-classic-button"><?php _e( 'Continue to Classic Editor', 'gutenberg' ); ?></button>
385 </p>
386 </div>
387 </div>
388
389 <script type="text/javascript">
390 /* <![CDATA[ */
391 ( function( $ ) {
392 var dialog = {};
393
394 dialog.init = function() {
395 // The modal
396 dialog.warning = $( '#cloudflare-block-dialog' );
397 // Get the links and buttons within the modal.
398 dialog.warningTabbables = dialog.warning.find( 'a, button' );
399
400 // Get the text within the modal.
401 dialog.rawMessage = dialog.warning.find( '.cloudflare-block-message' ).text();
402
403 // Hide all the #wpwrap content from assistive technologies.
404 $( '#wpwrap' ).attr( 'aria-hidden', 'true' );
405
406 // Detach the warning modal from its position and append it to the body.
407 $( document.body )
408 .addClass( 'modal-open' )
409 .append( dialog.warning.detach() );
410
411 // Reveal the modal and set focus on the Gutenberg button.
412 dialog.warning
413 .removeClass( 'hidden' )
414 .find( '.cloudflare-block-classic-button' ).focus();
415
416 // Attach event handlers.
417 dialog.warningTabbables.on( 'keydown', dialog.constrainTabbing );
418 dialog.warning.on( 'click', '.cloudflare-block-classic-button', dialog.dismissWarning );
419
420 // Make screen readers announce the warning message after a short delay (necessary for some screen readers).
421 setTimeout( function() {
422 wp.a11y.speak( wp.sanitize.stripTags( dialog.rawMessage.replace( /\s+/g, ' ' ) ), 'assertive' );
423 }, 1000 );
424 };
425
426 dialog.constrainTabbing = function( event ) {
427 var firstTabbable, lastTabbable;
428
429 if ( 9 !== event.which ) {
430 return;
431 }
432
433 firstTabbable = dialog.warningTabbables.first()[0];
434 lastTabbable = dialog.warningTabbables.last()[0];
435
436 if ( lastTabbable === event.target && ! event.shiftKey ) {
437 firstTabbable.focus();
438 event.preventDefault();
439 } else if ( firstTabbable === event.target && event.shiftKey ) {
440 lastTabbable.focus();
441 event.preventDefault();
442 }
443 };
444
445 dialog.dismissWarning = function() {
446 // Hide modal.
447 dialog.warning.remove();
448 $( '#wpwrap' ).removeAttr( 'aria-hidden' );
449 $( 'body' ).removeClass( 'modal-open' );
450 };
451
452 $( document ).ready( dialog.init );
453 } )( jQuery );
454 /* ]]> */
455 </script>
456 <?php
457 }
458