WordPress 4.7
[autoinstalls/wordpress.git] / wp-admin / includes / misc.php
1 <?php
2 /**
3  * Misc WordPress Administration API.
4  *
5  * @package WordPress
6  * @subpackage Administration
7  */
8
9 /**
10  * Returns whether the server is running Apache with the mod_rewrite module loaded.
11  *
12  * @since 2.0.0
13  *
14  * @return bool
15  */
16 function got_mod_rewrite() {
17         $got_rewrite = apache_mod_loaded('mod_rewrite', true);
18
19         /**
20          * Filters whether Apache and mod_rewrite are present.
21          *
22          * This filter was previously used to force URL rewriting for other servers,
23          * like nginx. Use the {@see 'got_url_rewrite'} filter in got_url_rewrite() instead.
24          *
25          * @since 2.5.0
26          *
27          * @see got_url_rewrite()
28          *
29          * @param bool $got_rewrite Whether Apache and mod_rewrite are present.
30          */
31         return apply_filters( 'got_rewrite', $got_rewrite );
32 }
33
34 /**
35  * Returns whether the server supports URL rewriting.
36  *
37  * Detects Apache's mod_rewrite, IIS 7.0+ permalink support, and nginx.
38  *
39  * @since 3.7.0
40  *
41  * @global bool $is_nginx
42  *
43  * @return bool Whether the server supports URL rewriting.
44  */
45 function got_url_rewrite() {
46         $got_url_rewrite = ( got_mod_rewrite() || $GLOBALS['is_nginx'] || iis7_supports_permalinks() );
47
48         /**
49          * Filters whether URL rewriting is available.
50          *
51          * @since 3.7.0
52          *
53          * @param bool $got_url_rewrite Whether URL rewriting is available.
54          */
55         return apply_filters( 'got_url_rewrite', $got_url_rewrite );
56 }
57
58 /**
59  * Extracts strings from between the BEGIN and END markers in the .htaccess file.
60  *
61  * @since 1.5.0
62  *
63  * @param string $filename
64  * @param string $marker
65  * @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers.
66  */
67 function extract_from_markers( $filename, $marker ) {
68         $result = array ();
69
70         if (!file_exists( $filename ) ) {
71                 return $result;
72         }
73
74         if ( $markerdata = explode( "\n", implode( '', file( $filename ) ) ));
75         {
76                 $state = false;
77                 foreach ( $markerdata as $markerline ) {
78                         if (strpos($markerline, '# END ' . $marker) !== false)
79                                 $state = false;
80                         if ( $state )
81                                 $result[] = $markerline;
82                         if (strpos($markerline, '# BEGIN ' . $marker) !== false)
83                                 $state = true;
84                 }
85         }
86
87         return $result;
88 }
89
90 /**
91  * Inserts an array of strings into a file (.htaccess ), placing it between
92  * BEGIN and END markers.
93  *
94  * Replaces existing marked info. Retains surrounding
95  * data. Creates file if none exists.
96  *
97  * @since 1.5.0
98  *
99  * @param string       $filename  Filename to alter.
100  * @param string       $marker    The marker to alter.
101  * @param array|string $insertion The new content to insert.
102  * @return bool True on write success, false on failure.
103  */
104 function insert_with_markers( $filename, $marker, $insertion ) {
105         if ( ! file_exists( $filename ) ) {
106                 if ( ! is_writable( dirname( $filename ) ) ) {
107                         return false;
108                 }
109                 if ( ! touch( $filename ) ) {
110                         return false;
111                 }
112         } elseif ( ! is_writeable( $filename ) ) {
113                 return false;
114         }
115
116         if ( ! is_array( $insertion ) ) {
117                 $insertion = explode( "\n", $insertion );
118         }
119
120         $start_marker = "# BEGIN {$marker}";
121         $end_marker   = "# END {$marker}";
122
123         $fp = fopen( $filename, 'r+' );
124         if ( ! $fp ) {
125                 return false;
126         }
127
128         // Attempt to get a lock. If the filesystem supports locking, this will block until the lock is acquired.
129         flock( $fp, LOCK_EX );
130
131         $lines = array();
132         while ( ! feof( $fp ) ) {
133                 $lines[] = rtrim( fgets( $fp ), "\r\n" );
134         }
135
136         // Split out the existing file into the preceding lines, and those that appear after the marker
137         $pre_lines = $post_lines = $existing_lines = array();
138         $found_marker = $found_end_marker = false;
139         foreach ( $lines as $line ) {
140                 if ( ! $found_marker && false !== strpos( $line, $start_marker ) ) {
141                         $found_marker = true;
142                         continue;
143                 } elseif ( ! $found_end_marker && false !== strpos( $line, $end_marker ) ) {
144                         $found_end_marker = true;
145                         continue;
146                 }
147                 if ( ! $found_marker ) {
148                         $pre_lines[] = $line;
149                 } elseif ( $found_marker && $found_end_marker ) {
150                         $post_lines[] = $line;
151                 } else {
152                         $existing_lines[] = $line;
153                 }
154         }
155
156         // Check to see if there was a change
157         if ( $existing_lines === $insertion ) {
158                 flock( $fp, LOCK_UN );
159                 fclose( $fp );
160
161                 return true;
162         }
163
164         // Generate the new file data
165         $new_file_data = implode( "\n", array_merge(
166                 $pre_lines,
167                 array( $start_marker ),
168                 $insertion,
169                 array( $end_marker ),
170                 $post_lines
171         ) );
172
173         // Write to the start of the file, and truncate it to that length
174         fseek( $fp, 0 );
175         $bytes = fwrite( $fp, $new_file_data );
176         if ( $bytes ) {
177                 ftruncate( $fp, ftell( $fp ) );
178         }
179         fflush( $fp );
180         flock( $fp, LOCK_UN );
181         fclose( $fp );
182
183         return (bool) $bytes;
184 }
185
186 /**
187  * Updates the htaccess file with the current rules if it is writable.
188  *
189  * Always writes to the file if it exists and is writable to ensure that we
190  * blank out old rules.
191  *
192  * @since 1.5.0
193  *
194  * @global WP_Rewrite $wp_rewrite
195  */
196 function save_mod_rewrite_rules() {
197         if ( is_multisite() )
198                 return;
199
200         global $wp_rewrite;
201
202         $home_path = get_home_path();
203         $htaccess_file = $home_path.'.htaccess';
204
205         /*
206          * If the file doesn't already exist check for write access to the directory
207          * and whether we have some rules. Else check for write access to the file.
208          */
209         if ((!file_exists($htaccess_file) && is_writable($home_path) && $wp_rewrite->using_mod_rewrite_permalinks()) || is_writable($htaccess_file)) {
210                 if ( got_mod_rewrite() ) {
211                         $rules = explode( "\n", $wp_rewrite->mod_rewrite_rules() );
212                         return insert_with_markers( $htaccess_file, 'WordPress', $rules );
213                 }
214         }
215
216         return false;
217 }
218
219 /**
220  * Updates the IIS web.config file with the current rules if it is writable.
221  * If the permalinks do not require rewrite rules then the rules are deleted from the web.config file.
222  *
223  * @since 2.8.0
224  *
225  * @global WP_Rewrite $wp_rewrite
226  *
227  * @return bool True if web.config was updated successfully
228  */
229 function iis7_save_url_rewrite_rules(){
230         if ( is_multisite() )
231                 return;
232
233         global $wp_rewrite;
234
235         $home_path = get_home_path();
236         $web_config_file = $home_path . 'web.config';
237
238         // Using win_is_writable() instead of is_writable() because of a bug in Windows PHP
239         if ( iis7_supports_permalinks() && ( ( ! file_exists($web_config_file) && win_is_writable($home_path) && $wp_rewrite->using_mod_rewrite_permalinks() ) || win_is_writable($web_config_file) ) ) {
240                 $rule = $wp_rewrite->iis7_url_rewrite_rules(false, '', '');
241                 if ( ! empty($rule) ) {
242                         return iis7_add_rewrite_rule($web_config_file, $rule);
243                 } else {
244                         return iis7_delete_rewrite_rule($web_config_file);
245                 }
246         }
247         return false;
248 }
249
250 /**
251  * Update the "recently-edited" file for the plugin or theme editor.
252  *
253  * @since 1.5.0
254  *
255  * @param string $file
256  */
257 function update_recently_edited( $file ) {
258         $oldfiles = (array ) get_option( 'recently_edited' );
259         if ( $oldfiles ) {
260                 $oldfiles = array_reverse( $oldfiles );
261                 $oldfiles[] = $file;
262                 $oldfiles = array_reverse( $oldfiles );
263                 $oldfiles = array_unique( $oldfiles );
264                 if ( 5 < count( $oldfiles ))
265                         array_pop( $oldfiles );
266         } else {
267                 $oldfiles[] = $file;
268         }
269         update_option( 'recently_edited', $oldfiles );
270 }
271
272 /**
273  * Flushes rewrite rules if siteurl, home or page_on_front changed.
274  *
275  * @since 2.1.0
276  *
277  * @param string $old_value
278  * @param string $value
279  */
280 function update_home_siteurl( $old_value, $value ) {
281         if ( wp_installing() )
282                 return;
283
284         if ( is_multisite() && ms_is_switched() ) {
285                 delete_option( 'rewrite_rules' );
286         } else {
287                 flush_rewrite_rules();
288         }
289 }
290
291
292 /**
293  * Resets global variables based on $_GET and $_POST
294  *
295  * This function resets global variables based on the names passed
296  * in the $vars array to the value of $_POST[$var] or $_GET[$var] or ''
297  * if neither is defined.
298  *
299  * @since 2.0.0
300  *
301  * @param array $vars An array of globals to reset.
302  */
303 function wp_reset_vars( $vars ) {
304         foreach ( $vars as $var ) {
305                 if ( empty( $_POST[ $var ] ) ) {
306                         if ( empty( $_GET[ $var ] ) ) {
307                                 $GLOBALS[ $var ] = '';
308                         } else {
309                                 $GLOBALS[ $var ] = $_GET[ $var ];
310                         }
311                 } else {
312                         $GLOBALS[ $var ] = $_POST[ $var ];
313                 }
314         }
315 }
316
317 /**
318  * Displays the given administration message.
319  *
320  * @since 2.1.0
321  *
322  * @param string|WP_Error $message
323  */
324 function show_message($message) {
325         if ( is_wp_error($message) ){
326                 if ( $message->get_error_data() && is_string( $message->get_error_data() ) )
327                         $message = $message->get_error_message() . ': ' . $message->get_error_data();
328                 else
329                         $message = $message->get_error_message();
330         }
331         echo "<p>$message</p>\n";
332         wp_ob_end_flush_all();
333         flush();
334 }
335
336 /**
337  * @since 2.8.0
338  *
339  * @param string $content
340  * @return array
341  */
342 function wp_doc_link_parse( $content ) {
343         if ( !is_string( $content ) || empty( $content ) )
344                 return array();
345
346         if ( !function_exists('token_get_all') )
347                 return array();
348
349         $tokens = token_get_all( $content );
350         $count = count( $tokens );
351         $functions = array();
352         $ignore_functions = array();
353         for ( $t = 0; $t < $count - 2; $t++ ) {
354                 if ( ! is_array( $tokens[ $t ] ) ) {
355                         continue;
356                 }
357
358                 if ( T_STRING == $tokens[ $t ][0] && ( '(' == $tokens[ $t + 1 ] || '(' == $tokens[ $t + 2 ] ) ) {
359                         // If it's a function or class defined locally, there's not going to be any docs available
360                         if ( ( isset( $tokens[ $t - 2 ][1] ) && in_array( $tokens[ $t - 2 ][1], array( 'function', 'class' ) ) ) || ( isset( $tokens[ $t - 2 ][0] ) && T_OBJECT_OPERATOR == $tokens[ $t - 1 ][0] ) ) {
361                                 $ignore_functions[] = $tokens[$t][1];
362                         }
363                         // Add this to our stack of unique references
364                         $functions[] = $tokens[$t][1];
365                 }
366         }
367
368         $functions = array_unique( $functions );
369         sort( $functions );
370
371         /**
372          * Filters the list of functions and classes to be ignored from the documentation lookup.
373          *
374          * @since 2.8.0
375          *
376          * @param array $ignore_functions Functions and classes to be ignored.
377          */
378         $ignore_functions = apply_filters( 'documentation_ignore_functions', $ignore_functions );
379
380         $ignore_functions = array_unique( $ignore_functions );
381
382         $out = array();
383         foreach ( $functions as $function ) {
384                 if ( in_array( $function, $ignore_functions ) )
385                         continue;
386                 $out[] = $function;
387         }
388
389         return $out;
390 }
391
392 /**
393  * Saves option for number of rows when listing posts, pages, comments, etc.
394  *
395  * @since 2.8.0
396  */
397 function set_screen_options() {
398
399         if ( isset($_POST['wp_screen_options']) && is_array($_POST['wp_screen_options']) ) {
400                 check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
401
402                 if ( !$user = wp_get_current_user() )
403                         return;
404                 $option = $_POST['wp_screen_options']['option'];
405                 $value = $_POST['wp_screen_options']['value'];
406
407                 if ( $option != sanitize_key( $option ) )
408                         return;
409
410                 $map_option = $option;
411                 $type = str_replace('edit_', '', $map_option);
412                 $type = str_replace('_per_page', '', $type);
413                 if ( in_array( $type, get_taxonomies() ) )
414                         $map_option = 'edit_tags_per_page';
415                 elseif ( in_array( $type, get_post_types() ) )
416                         $map_option = 'edit_per_page';
417                 else
418                         $option = str_replace('-', '_', $option);
419
420                 switch ( $map_option ) {
421                         case 'edit_per_page':
422                         case 'users_per_page':
423                         case 'edit_comments_per_page':
424                         case 'upload_per_page':
425                         case 'edit_tags_per_page':
426                         case 'plugins_per_page':
427                         // Network admin
428                         case 'sites_network_per_page':
429                         case 'users_network_per_page':
430                         case 'site_users_network_per_page':
431                         case 'plugins_network_per_page':
432                         case 'themes_network_per_page':
433                         case 'site_themes_network_per_page':
434                                 $value = (int) $value;
435                                 if ( $value < 1 || $value > 999 )
436                                         return;
437                                 break;
438                         default:
439
440                                 /**
441                                  * Filters a screen option value before it is set.
442                                  *
443                                  * The filter can also be used to modify non-standard [items]_per_page
444                                  * settings. See the parent function for a full list of standard options.
445                                  *
446                                  * Returning false to the filter will skip saving the current option.
447                                  *
448                                  * @since 2.8.0
449                                  *
450                                  * @see set_screen_options()
451                                  *
452                                  * @param bool|int $value  Screen option value. Default false to skip.
453                                  * @param string   $option The option name.
454                                  * @param int      $value  The number of rows to use.
455                                  */
456                                 $value = apply_filters( 'set-screen-option', false, $option, $value );
457
458                                 if ( false === $value )
459                                         return;
460                                 break;
461                 }
462
463                 update_user_meta($user->ID, $option, $value);
464
465                 $url = remove_query_arg( array( 'pagenum', 'apage', 'paged' ), wp_get_referer() );
466                 if ( isset( $_POST['mode'] ) ) {
467                         $url = add_query_arg( array( 'mode' => $_POST['mode'] ), $url );
468                 }
469
470                 wp_safe_redirect( $url );
471                 exit;
472         }
473 }
474
475 /**
476  * Check if rewrite rule for WordPress already exists in the IIS 7+ configuration file
477  *
478  * @since 2.8.0
479  *
480  * @return bool
481  * @param string $filename The file path to the configuration file
482  */
483 function iis7_rewrite_rule_exists($filename) {
484         if ( ! file_exists($filename) )
485                 return false;
486         if ( ! class_exists( 'DOMDocument', false ) ) {
487                 return false;
488         }
489
490         $doc = new DOMDocument();
491         if ( $doc->load($filename) === false )
492                 return false;
493         $xpath = new DOMXPath($doc);
494         $rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]');
495         if ( $rules->length == 0 )
496                 return false;
497         else
498                 return true;
499 }
500
501 /**
502  * Delete WordPress rewrite rule from web.config file if it exists there
503  *
504  * @since 2.8.0
505  *
506  * @param string $filename Name of the configuration file
507  * @return bool
508  */
509 function iis7_delete_rewrite_rule($filename) {
510         // If configuration file does not exist then rules also do not exist so there is nothing to delete
511         if ( ! file_exists($filename) )
512                 return true;
513
514         if ( ! class_exists( 'DOMDocument', false ) ) {
515                 return false;
516         }
517
518         $doc = new DOMDocument();
519         $doc->preserveWhiteSpace = false;
520
521         if ( $doc -> load($filename) === false )
522                 return false;
523         $xpath = new DOMXPath($doc);
524         $rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]');
525         if ( $rules->length > 0 ) {
526                 $child = $rules->item(0);
527                 $parent = $child->parentNode;
528                 $parent->removeChild($child);
529                 $doc->formatOutput = true;
530                 saveDomDocument($doc, $filename);
531         }
532         return true;
533 }
534
535 /**
536  * Add WordPress rewrite rule to the IIS 7+ configuration file.
537  *
538  * @since 2.8.0
539  *
540  * @param string $filename The file path to the configuration file
541  * @param string $rewrite_rule The XML fragment with URL Rewrite rule
542  * @return bool
543  */
544 function iis7_add_rewrite_rule($filename, $rewrite_rule) {
545         if ( ! class_exists( 'DOMDocument', false ) ) {
546                 return false;
547         }
548
549         // If configuration file does not exist then we create one.
550         if ( ! file_exists($filename) ) {
551                 $fp = fopen( $filename, 'w');
552                 fwrite($fp, '<configuration/>');
553                 fclose($fp);
554         }
555
556         $doc = new DOMDocument();
557         $doc->preserveWhiteSpace = false;
558
559         if ( $doc->load($filename) === false )
560                 return false;
561
562         $xpath = new DOMXPath($doc);
563
564         // First check if the rule already exists as in that case there is no need to re-add it
565         $wordpress_rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]');
566         if ( $wordpress_rules->length > 0 )
567                 return true;
568
569         // Check the XPath to the rewrite rule and create XML nodes if they do not exist
570         $xmlnodes = $xpath->query('/configuration/system.webServer/rewrite/rules');
571         if ( $xmlnodes->length > 0 ) {
572                 $rules_node = $xmlnodes->item(0);
573         } else {
574                 $rules_node = $doc->createElement('rules');
575
576                 $xmlnodes = $xpath->query('/configuration/system.webServer/rewrite');
577                 if ( $xmlnodes->length > 0 ) {
578                         $rewrite_node = $xmlnodes->item(0);
579                         $rewrite_node->appendChild($rules_node);
580                 } else {
581                         $rewrite_node = $doc->createElement('rewrite');
582                         $rewrite_node->appendChild($rules_node);
583
584                         $xmlnodes = $xpath->query('/configuration/system.webServer');
585                         if ( $xmlnodes->length > 0 ) {
586                                 $system_webServer_node = $xmlnodes->item(0);
587                                 $system_webServer_node->appendChild($rewrite_node);
588                         } else {
589                                 $system_webServer_node = $doc->createElement('system.webServer');
590                                 $system_webServer_node->appendChild($rewrite_node);
591
592                                 $xmlnodes = $xpath->query('/configuration');
593                                 if ( $xmlnodes->length > 0 ) {
594                                         $config_node = $xmlnodes->item(0);
595                                         $config_node->appendChild($system_webServer_node);
596                                 } else {
597                                         $config_node = $doc->createElement('configuration');
598                                         $doc->appendChild($config_node);
599                                         $config_node->appendChild($system_webServer_node);
600                                 }
601                         }
602                 }
603         }
604
605         $rule_fragment = $doc->createDocumentFragment();
606         $rule_fragment->appendXML($rewrite_rule);
607         $rules_node->appendChild($rule_fragment);
608
609         $doc->encoding = "UTF-8";
610         $doc->formatOutput = true;
611         saveDomDocument($doc, $filename);
612
613         return true;
614 }
615
616 /**
617  * Saves the XML document into a file
618  *
619  * @since 2.8.0
620  *
621  * @param DOMDocument $doc
622  * @param string $filename
623  */
624 function saveDomDocument($doc, $filename) {
625         $config = $doc->saveXML();
626         $config = preg_replace("/([^\r])\n/", "$1\r\n", $config);
627         $fp = fopen($filename, 'w');
628         fwrite($fp, $config);
629         fclose($fp);
630 }
631
632 /**
633  * Display the default admin color scheme picker (Used in user-edit.php)
634  *
635  * @since 3.0.0
636  *
637  * @global array $_wp_admin_css_colors
638  *
639  * @param int $user_id User ID.
640  */
641 function admin_color_scheme_picker( $user_id ) {
642         global $_wp_admin_css_colors;
643
644         ksort( $_wp_admin_css_colors );
645
646         if ( isset( $_wp_admin_css_colors['fresh'] ) ) {
647                 // Set Default ('fresh') and Light should go first.
648                 $_wp_admin_css_colors = array_filter( array_merge( array( 'fresh' => '', 'light' => '' ), $_wp_admin_css_colors ) );
649         }
650
651         $current_color = get_user_option( 'admin_color', $user_id );
652
653         if ( empty( $current_color ) || ! isset( $_wp_admin_css_colors[ $current_color ] ) ) {
654                 $current_color = 'fresh';
655         }
656
657         ?>
658         <fieldset id="color-picker" class="scheme-list">
659                 <legend class="screen-reader-text"><span><?php _e( 'Admin Color Scheme' ); ?></span></legend>
660                 <?php
661                 wp_nonce_field( 'save-color-scheme', 'color-nonce', false );
662                 foreach ( $_wp_admin_css_colors as $color => $color_info ) :
663
664                         ?>
665                         <div class="color-option <?php echo ( $color == $current_color ) ? 'selected' : ''; ?>">
666                                 <input name="admin_color" id="admin_color_<?php echo esc_attr( $color ); ?>" type="radio" value="<?php echo esc_attr( $color ); ?>" class="tog" <?php checked( $color, $current_color ); ?> />
667                                 <input type="hidden" class="css_url" value="<?php echo esc_url( $color_info->url ); ?>" />
668                                 <input type="hidden" class="icon_colors" value="<?php echo esc_attr( wp_json_encode( array( 'icons' => $color_info->icon_colors ) ) ); ?>" />
669                                 <label for="admin_color_<?php echo esc_attr( $color ); ?>"><?php echo esc_html( $color_info->name ); ?></label>
670                                 <table class="color-palette">
671                                         <tr>
672                                         <?php
673
674                                         foreach ( $color_info->colors as $html_color ) {
675                                                 ?>
676                                                 <td style="background-color: <?php echo esc_attr( $html_color ); ?>">&nbsp;</td>
677                                                 <?php
678                                         }
679
680                                         ?>
681                                         </tr>
682                                 </table>
683                         </div>
684                         <?php
685
686                 endforeach;
687
688         ?>
689         </fieldset>
690         <?php
691 }
692
693 /**
694  *
695  * @global array $_wp_admin_css_colors
696  */
697 function wp_color_scheme_settings() {
698         global $_wp_admin_css_colors;
699
700         $color_scheme = get_user_option( 'admin_color' );
701
702         // It's possible to have a color scheme set that is no longer registered.
703         if ( empty( $_wp_admin_css_colors[ $color_scheme ] ) ) {
704                 $color_scheme = 'fresh';
705         }
706
707         if ( ! empty( $_wp_admin_css_colors[ $color_scheme ]->icon_colors ) ) {
708                 $icon_colors = $_wp_admin_css_colors[ $color_scheme ]->icon_colors;
709         } elseif ( ! empty( $_wp_admin_css_colors['fresh']->icon_colors ) ) {
710                 $icon_colors = $_wp_admin_css_colors['fresh']->icon_colors;
711         } else {
712                 // Fall back to the default set of icon colors if the default scheme is missing.
713                 $icon_colors = array( 'base' => '#82878c', 'focus' => '#00a0d2', 'current' => '#fff' );
714         }
715
716         echo '<script type="text/javascript">var _wpColorScheme = ' . wp_json_encode( array( 'icons' => $icon_colors ) ) . ";</script>\n";
717 }
718
719 /**
720  * @since 3.3.0
721  */
722 function _ipad_meta() {
723         if ( wp_is_mobile() ) {
724                 ?>
725                 <meta name="viewport" id="viewport-meta" content="width=device-width, initial-scale=1">
726                 <?php
727         }
728 }
729
730 /**
731  * Check lock status for posts displayed on the Posts screen
732  *
733  * @since 3.6.0
734  *
735  * @param array  $response  The Heartbeat response.
736  * @param array  $data      The $_POST data sent.
737  * @param string $screen_id The screen id.
738  * @return array The Heartbeat response.
739  */
740 function wp_check_locked_posts( $response, $data, $screen_id ) {
741         $checked = array();
742
743         if ( array_key_exists( 'wp-check-locked-posts', $data ) && is_array( $data['wp-check-locked-posts'] ) ) {
744                 foreach ( $data['wp-check-locked-posts'] as $key ) {
745                         if ( ! $post_id = absint( substr( $key, 5 ) ) )
746                                 continue;
747
748                         if ( ( $user_id = wp_check_post_lock( $post_id ) ) && ( $user = get_userdata( $user_id ) ) && current_user_can( 'edit_post', $post_id ) ) {
749                                 $send = array( 'text' => sprintf( __( '%s is currently editing' ), $user->display_name ) );
750
751                                 if ( ( $avatar = get_avatar( $user->ID, 18 ) ) && preg_match( "|src='([^']+)'|", $avatar, $matches ) )
752                                         $send['avatar_src'] = $matches[1];
753
754                                 $checked[$key] = $send;
755                         }
756                 }
757         }
758
759         if ( ! empty( $checked ) )
760                 $response['wp-check-locked-posts'] = $checked;
761
762         return $response;
763 }
764
765 /**
766  * Check lock status on the New/Edit Post screen and refresh the lock
767  *
768  * @since 3.6.0
769  *
770  * @param array  $response  The Heartbeat response.
771  * @param array  $data      The $_POST data sent.
772  * @param string $screen_id The screen id.
773  * @return array The Heartbeat response.
774  */
775 function wp_refresh_post_lock( $response, $data, $screen_id ) {
776         if ( array_key_exists( 'wp-refresh-post-lock', $data ) ) {
777                 $received = $data['wp-refresh-post-lock'];
778                 $send = array();
779
780                 if ( ! $post_id = absint( $received['post_id'] ) )
781                         return $response;
782
783                 if ( ! current_user_can('edit_post', $post_id) )
784                         return $response;
785
786                 if ( ( $user_id = wp_check_post_lock( $post_id ) ) && ( $user = get_userdata( $user_id ) ) ) {
787                         $error = array(
788                                 'text' => sprintf( __( '%s has taken over and is currently editing.' ), $user->display_name )
789                         );
790
791                         if ( $avatar = get_avatar( $user->ID, 64 ) ) {
792                                 if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) )
793                                         $error['avatar_src'] = $matches[1];
794                         }
795
796                         $send['lock_error'] = $error;
797                 } else {
798                         if ( $new_lock = wp_set_post_lock( $post_id ) )
799                                 $send['new_lock'] = implode( ':', $new_lock );
800                 }
801
802                 $response['wp-refresh-post-lock'] = $send;
803         }
804
805         return $response;
806 }
807
808 /**
809  * Check nonce expiration on the New/Edit Post screen and refresh if needed
810  *
811  * @since 3.6.0
812  *
813  * @param array  $response  The Heartbeat response.
814  * @param array  $data      The $_POST data sent.
815  * @param string $screen_id The screen id.
816  * @return array The Heartbeat response.
817  */
818 function wp_refresh_post_nonces( $response, $data, $screen_id ) {
819         if ( array_key_exists( 'wp-refresh-post-nonces', $data ) ) {
820                 $received = $data['wp-refresh-post-nonces'];
821                 $response['wp-refresh-post-nonces'] = array( 'check' => 1 );
822
823                 if ( ! $post_id = absint( $received['post_id'] ) ) {
824                         return $response;
825                 }
826
827                 if ( ! current_user_can( 'edit_post', $post_id ) ) {
828                         return $response;
829                 }
830
831                 $response['wp-refresh-post-nonces'] = array(
832                         'replace' => array(
833                                 'getpermalinknonce' => wp_create_nonce('getpermalink'),
834                                 'samplepermalinknonce' => wp_create_nonce('samplepermalink'),
835                                 'closedpostboxesnonce' => wp_create_nonce('closedpostboxes'),
836                                 '_ajax_linking_nonce' => wp_create_nonce( 'internal-linking' ),
837                                 '_wpnonce' => wp_create_nonce( 'update-post_' . $post_id ),
838                         ),
839                         'heartbeatNonce' => wp_create_nonce( 'heartbeat-nonce' ),
840                 );
841         }
842
843         return $response;
844 }
845
846 /**
847  * Disable suspension of Heartbeat on the Add/Edit Post screens.
848  *
849  * @since 3.8.0
850  *
851  * @global string $pagenow
852  *
853  * @param array $settings An array of Heartbeat settings.
854  * @return array Filtered Heartbeat settings.
855  */
856 function wp_heartbeat_set_suspension( $settings ) {
857         global $pagenow;
858
859         if ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) {
860                 $settings['suspension'] = 'disable';
861         }
862
863         return $settings;
864 }
865
866 /**
867  * Autosave with heartbeat
868  *
869  * @since 3.9.0
870  *
871  * @param array $response The Heartbeat response.
872  * @param array $data     The $_POST data sent.
873  * @return array The Heartbeat response.
874  */
875 function heartbeat_autosave( $response, $data ) {
876         if ( ! empty( $data['wp_autosave'] ) ) {
877                 $saved = wp_autosave( $data['wp_autosave'] );
878
879                 if ( is_wp_error( $saved ) ) {
880                         $response['wp_autosave'] = array( 'success' => false, 'message' => $saved->get_error_message() );
881                 } elseif ( empty( $saved ) ) {
882                         $response['wp_autosave'] = array( 'success' => false, 'message' => __( 'Error while saving.' ) );
883                 } else {
884                         /* translators: draft saved date format, see https://secure.php.net/date */
885                         $draft_saved_date_format = __( 'g:i:s a' );
886                         /* translators: %s: date and time */
887                         $response['wp_autosave'] = array( 'success' => true, 'message' => sprintf( __( 'Draft saved at %s.' ), date_i18n( $draft_saved_date_format ) ) );
888                 }
889         }
890
891         return $response;
892 }
893
894 /**
895  * Remove single-use URL parameters and create canonical link based on new URL.
896  *
897  * Remove specific query string parameters from a URL, create the canonical link,
898  * put it in the admin header, and change the current URL to match.
899  *
900  * @since 4.2.0
901  */
902 function wp_admin_canonical_url() {
903         $removable_query_args = wp_removable_query_args();
904
905         if ( empty( $removable_query_args ) ) {
906                 return;
907         }
908
909         // Ensure we're using an absolute URL.
910         $current_url  = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
911         $filtered_url = remove_query_arg( $removable_query_args, $current_url );
912         ?>
913         <link id="wp-admin-canonical" rel="canonical" href="<?php echo esc_url( $filtered_url ); ?>" />
914         <script>
915                 if ( window.history.replaceState ) {
916                         window.history.replaceState( null, null, document.getElementById( 'wp-admin-canonical' ).href + window.location.hash );
917                 }
918         </script>
919 <?php
920 }
921
922 /**
923  * Outputs JS that reloads the page if the user navigated to it with the Back or Forward button.
924  *
925  * Used on the Edit Post and Add New Post screens. Needed to ensure the page is not loaded from browser cache,
926  * so the post title and editor content are the last saved versions. Ideally this script should run first in the head.
927  *
928  * @since 4.6.0
929  */
930 function wp_page_reload_on_back_button_js() {
931         ?>
932         <script>
933                 if ( typeof performance !== 'undefined' && performance.navigation && performance.navigation.type === 2 ) {
934                         document.location.reload( true );
935                 }
936         </script>
937         <?php
938 }